geomyidae

A small C-based gopherd. (gopher://bitreich.org/1/scm/geomyidae)
git clone git://r-36.net/geomyidae
Log | Files | Refs | README | LICENSE

commit 97230aa165bee0bffae624707ff98d6098da1823
Author: Christoph Lohmann <20h@r-36.net>
Date:   Fri, 19 Nov 2010 09:20:38 +0100

Initial commit at 0.16 of geomyidae.

Diffstat:
LICENSE | 24++++++++++++++++++++++++
Makefile | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
README | 43+++++++++++++++++++++++++++++++++++++++++++
arg.h | 19+++++++++++++++++++
geomyidae.8 | 407+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
handlr.c | 196+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
handlr.h | 20++++++++++++++++++++
ind.c | 287+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ind.h | 48++++++++++++++++++++++++++++++++++++++++++++++++
index.gph | 6++++++
main.c | 385+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
rc.d/Archlinux.conf.d | 4++++
rc.d/Archlinux.rc.d | 38++++++++++++++++++++++++++++++++++++++
rc.d/Gentoo.conf.d | 5+++++
rc.d/Gentoo.init.d | 17+++++++++++++++++
rc.d/NetBSD.rc.d | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
rc.d/README | 5+++++
17 files changed, 1615 insertions(+), 0 deletions(-)

diff --git a/LICENSE b/LICENSE @@ -0,0 +1,24 @@ +MIT/X Consortium License + +© 2006-2010 Christoph Lohmann <20h@r-36.net> +© 2007 Jeff Woodall <jgw@freeshell.org> +© 2008 J. A. Neitzel <jan@v6shell.org> +© 2010 James Penketh <tamber@furryhelix.co.uk> + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile @@ -0,0 +1,56 @@ +PROGRAM = geomyidae +VERSION = 0.16 + +PREFIX ?= /usr +BINDIR ?= $(PREFIX)/bin +MANDIR ?= $(PREFIX)/man/man8 + +#CPPFLAGS += -D_BSD_SOURCE +CFLAGS += -O2 -Wall -I. -I/usr/include +LDFLAGS += -L/usr/lib -L. -lc + +CFILES = main.c ind.c handlr.c + +OBJECTS = ${CFILES:.c=.o} + +all: $(PROGRAM) + +${PROGRAM}: ${OBJECTS} + ${CC} ${LDFLAGS} -o ${PROGRAM} ${OBJECTS} + +.SUFFIXES : .c .h + +.c.o : + ${CC} ${CFLAGS} ${CPPFLAGS} -c $< +.c : + ${CC} ${CFLAGS} ${CPPFLAGS} -c $< + +clean : + @rm -f *.o ${PROGRAM} core *~ + +install: $(PROGRAM) + @mkdir -p ${DESTDIR}${BINDIR} + @cp -f ${PROGRAM} ${DESTDIR}${BINDIR} + @strip ${DESTDIR}${BINDIR}/${PROGRAM} + @chmod 755 ${DESTDIR}${BINDIR}/${PROGRAM} + @mkdir -p ${DESTDIR}${MANDIR} + @cp -f geomyidae.8 ${DESTDIR}${MANDIR} + @chmod 644 ${DESTDIR}${MANDIR}/${PROGRAM}.8 + +uninstall: + @rm -f ${DESTDIR}${BINDIR}/${PROGRAM} + @rm -f ${DESTDIR}${MANDIR}/${PROGRAM}.8 + +dist: clean + @mkdir -p "${PROGRAM}-${VERSION}" + @cp -r rc.d README LICENSE index.gph Makefile geomyidae.8 \ + *.c *.h "${PROGRAM}-${VERSION}" + @chmod 755 "${PROGRAM}-${VERSION}" + @chmod 744 "${PROGRAM}-${VERSION}"/* + @tar -cf "${PROGRAM}-${VERSION}.tar" "${PROGRAM}-${VERSION}" + @gzip "${PROGRAM}-${VERSION}.tar" + @mv "${PROGRAM}-${VERSION}.tar.gz" "${PROGRAM}-${VERSION}.tgz" + @rm -rf "${PROGRAM}-${VERSION}" + +.PHONY: all clean dist install uninstall + diff --git a/README b/README @@ -0,0 +1,43 @@ +A gopherd for Linux/BSD. + +Features: + * gopher menus (see index.gph for an example) + * dir listings (if no index.gph was found) + * cgi support (.cgi files are executed) + * search support in CGI files + * logging (-l option) and loglevels (-v option) + +Usage: + + geomyidae [-d] [-l logfile] [-v loglvl] [-b htdocs] [-p port] [-o sport] + [-u user] [-g group] [-h host] [-i IP] + -d don't fork into background + -l logfile setting this will turn on logging into logfile + -v loglevel see below (default 7) + -b htdocs the htdocs root for serving files (default + /var/gopher) + -p port set the port where geomyidae should listen on + (default 70) + -o sport set the port that should be shown in the dir + listings + -u user which user rights the serving children should get + -g group which group rights the serving children should get + -i IP IP which geomyidae should bind to + -h host host that should be used in the dir listings + +Loglevels: + + 0 - no logging + 1 - served plain files + 2 - dir listings + 4 - HTTP redirects + 8 - not found queries + + 1 + 2 + 4 = 7 (files + dir listings + HTTP) + +Init scripts: + The rc.d directory includes startup scripts for various distributions. + + +Have fun! + diff --git a/arg.h b/arg.h @@ -0,0 +1,19 @@ +#ifndef ARG_H +#define ARG_H + +#define USED(x) ((void)(x)) + +extern char *argv0; + +#define ARGBEGIN for(argv0 = *argv, argv++, argc--;\ + argv[0] && argv[0][0]=='-' && argv[0][1];\ + argc--, argv++) {\ + char _argc;\ + _argc = argv[0][1];\ + switch(_argc) +#define ARGEND USED(_argc);} USED(argv);USED(argc); +#define EARGF(x) ((argv[1] == nil)? ((x), abort(), (char *)0) :\ + (argc--, argv++, argv[0])) + +#endif + diff --git a/geomyidae.8 b/geomyidae.8 @@ -0,0 +1,407 @@ +.\" geomyidae.8 handcrafted in GNU groff -mdoc using nvi +.\" +.Dd March 9, 2008 +.Dt GEOMYIDAE 8 +.Os +. +.Sh NAME +.Nm geomyidae +.Nd a gopher daemon for Linux/BSD +. +.Sh SYNOPSIS +.Nm +.Bk -words +.Op Fl d +.Op Fl l Ar logfile +.Op Fl v Ar loglevel +.Op Fl b Ar base +.Op Fl p Ar port +.Op Fl o Ar sport +.Op Fl u Ar user +.Op Fl g Ar group +.Op Fl h Ar host +.Op Fl i Ar IP +.Ek +. +.Sh DESCRIPTION +.Nm +is a daemon for serving the protocol specified in +.Em RFC 1436 +(Gopher). Under 1000 lines of C by design, it is lightweight yet supports +dynamic content, automatic file/directory indexing, logging and privilege +separation. +. +.Sh IMPLEMENTATION +Installation is straightforward: grab the zipped tar file, expand it in +an appropriate temp directory, change to the +.Qq "../geomyidae-x.xx" +directory, tweak the Makefile if desired (installs in +.Qq "/usr/bin" +by default), then run the +.Sq "make ; make install" +commands. The resulting executable should be run by root. +. +.Ss Installation example: +.Pp +.Bd -literal + % wget http://www.r-36.net/src/geomyidae/geomyidae-current.tgz; + % tar -xzvf geomyidae-*.tgz; + % cd geomyidae-*; + % make; sudo make install; + % sudo mkdir -p /var/gopher; + % sudo cp index.gph /var/gopher; + % sudo eomyidae -l /var/log/geomyidae.log -b /var/gopher -p 70; + % tail -f /var/log/geomyidae.log; + # + # Use whatever gopher client you like to gopher to + # gopher://localhost + # + # Have fun! +.Ed +. +.Ss Running +Geomyidae should normally be started by root. Though, it can be started +by a regular user provided that the base directory and its contents are owned +by the same user. Geomyidae will only serve content within the base directory +tree and will drop privileges to the +.Fl u Ar user +and +.Fl g Ar group +values if set. See +.Ic OPTIONS +below for specifics. +. +.Sh OPTIONS +geomyidae options and default settings: +.Pp +.Bl -tag -width ".Fl test Ao Ar string Ac" +. +.It Fl d +Don't fork into background. +. +.It Fl l Ar logfile +Specify the file where the log output will be written (default: no default). +. +.It Fl v Ar loglevel +Set the logging level (default: 15). +. +.Bd -literal +Loglevels: + 0 - no logging + 1 - served plain files + 2 - directory listings + 4 - HTTP redirects + 8 - errors (e.g., not found) + e.g.: + 1 + 2 + 4 + 8 = 15 + (files + directories + HTTP + errors) +.Ed +. +.It Fl b Ar base +Root directory to serve (default: /var/gopher) +. +.It Fl p Ar port +Port geomyidae should listen on (default: 70) +. +.It Fl o Ar sport +Port geomyidae displays within base directory (default: 70). +. +Use in conjunction with +.Ic -p +for obfuscating actual port neomyidae is running on. +. +.It Fl u Ar user +Sets the user to which privileges drop when geomyidae is ready +to accept network connections (default: user geomyidae runs as). +Helps improve security by reducing privileges during request +processing. +. +.It Fl g Ar group +Sets the group to which privileges drop when geomyidae is ready +to accept network connections (default: group geomyidae runs as). +Helps improve security by reducing privileges during request +processing. +. +.It Fl h Ar host +Host to use in directory listings (default: localhost) +. +.It Fl i Ar IP +IP to which geomyidae binds to (default: 127.0.0.1) +.El +. +.Sh FORMATTING +Structured Gopher space(s) can be created with geomyidae through the +use of special indexing files of the form +.Ic <name>.gph +which, if present, geomyidae uses to format and/or filter the contents of +the base directory (/var/gopher by default) and create gopher menus. +However, index files are +.Em not +required: if no .gph files are found geomyidae simply lists the directory +contents in alphanumeric order. In addition, a directory can utilize +multiple index files to create a layered gopher environment without the +use of sub-directories: ie. pictures.gph, music.gph, documents.gph could +be "directories" within main.gph, yet all reside in /var/gopher along with +their respective files (*.jpg, *.mp3, *.pdf for example). +. +.Ss Anatomy of an index.gph file +In general, each line of an index.gph file has the following structure: +.Pp +.Bl -inset -offset indent +.It Ic [<type>|<desc>|<path>|<port>] +.El +.Pp +where, +.Bl -inset -offset indent +.It Ic <type> += A valid gopher Item Type. +.Pp +Some common Gopher Types as defined in +.Em RFC 1436 +: +. +.Bd -literal + 0 Item is a file + 1 Gopher directory + 3 Error + 7 Item is an Index-Search server. + 8 Item points to a text-based telnet session. + 9 Binary file. Client reads until TCP connection closes! + g GIF format graphics file. + I Indeterminate image file. Client decides how to display. +.Ed +.Pp +In addition, geomyidae provides these: +.Bd -literal + h Item is a hypertext (HTTP) link + i Informational Item (used for descriptive purposes) +.Ed +. +.Pp +Note: geomyidae doesn't require "informational" text to be formally +Typed as "[i|...]"; any line +.Em not +beginning with "[" is treated as informational, greatly simplifying the +formatting of index.gph files. +If a line begins with "t", this "t" is left out. This measurement is +there to allow lines "informational" text beginning with "[". +. +.It Ic <desc> += description of gopher item. Most printable characters should work. +. +.It Ic <path> += full path to gopher item (base value is / ). Use the "Err" path for +items not intended to be served. +. +.It Ic <host> += hostname or IP hosting the gopher item. Must be resolvable for the +intended clients. +. +.It Ic <port> += TCP port number ( usually 70) +. +May be omitted if defaults are used. +.El +. +.Ss index.gph Example +A root.gph file for a server running on host=frog.bog, port=70. Note use +of optional [i]nformational Item (line 2) for vertical space insertion: +.Pp +.Bd -literal -offset indent +Welcome to Frog.bog +[i||||] +[0|About this server|about.txt|frog.bog|70] +[0|Daily Log|/dtail.cgi|frog.bog|70] +[1|Phlog: like a blog, but not|/PHLOG|frog.bog|70] +[9|Some binary file|widget.exe|frog.bog|70] +[I|Snowflake picture|snowflake.jpg|frog.bog|70] + +Links and Searches +[1|Go to R-36.net|/|gopher.r-36.net|70] +[h|Go to NetBSD.org|URL:http://netbsd.org|frog.bog|70] +[7|Query US Weather by Zipcode|/weather.cgi?|frog.bog|70] +[7|Search Veronica II|/v2/vs|gopher.floodgap.com|70] +[8|Telnet to SDF Public Access Unix System||freeshell.org|23] +.Ed +. +.Pp +The above looks something like this in a text-based gopher client: +.Pp +.Bl -tag -width ".It Ic WIDTHS" -compact -offset indent +.D1 Welcome to Frog.bog +.Pp +.It Ic (FILE) +About this server +.It Ic (FILE) +Daily Log +.It Ic (DIR) +Phlog: like a blog, but not +.It Ic (BIN) +Some binary file +.It Ic (IMG) +Snowflake picture +.El +.Pp +.Bl -tag -width ".It Ic WIDTHS" -compact -offset indent +.D1 Links and Searches +.It Ic (DIR) +Go to R-36.net +.It Ic (HTML) +Go to NetBSD.org +.It Ic (?) +Query US Weather by Zipcode +.It Ic (?) +Search Veronica II +.It Ic (TEL) +Telnet to SDF Public Access Unix System +.El +.Pp +.Sh USING DYNAMIC CONTENT +Dynamic content can be generated under geomyidae by simply creating a file +in for form of +.Ic <name>.cgi +in a directory that is being served. Such files are run as a shell script. +(See below for description.) +.Pp +ex. dtail.cgi - prints daily log entries +. +.Bd -literal -offset indent +#!/bin/sh -e +echo "Logged activity for `date '+%A, %B %d, %Y'` :" +echo "===============================================" +LOG="/var/log/gopherd.log" +DATE=`date "+%a %b %d"` +/usr/bin/grep "$DATE" $LOG +exit 0 +.Ed +. +.Pp +Geomyidae supports two variable queries. The basic form is +.Pp +.D1 executable.cgi{argv[1]}?{argv[2]} +.Pp +where +.Pp +.D1 argv[1] = strings (everything before the '?' in the query) +.D1 argv[2] = arguments (everything behind the '?' in the query) +.Pp +A search query request must have an item Type of "7" to be called +from an index.gph file. It may also need a "?" suffix in the <path> +field. +. +.Pp +ex. hello.cgi - say hello to user +. +.Bd -literal -offset indent +#!/bin/sh +NAME=$1 +echo "" +echo Hello $NAME - welcome to Frog.bog +exit 0 +.Ed +. +.Pp +Call the above with the following index.gph entry: +.Pp +.D1 [7|Hello You - Please enter your name|/hello.cgi?|frog.bog|70] +. +.Pp +And do a simple +.Xr snarf 1 +query: +.Pp +.D1 % snarf Qo gopher://frog.bog/7/hello.cgi?Christoph Qc - +.D1 Hello Christoph - welcome to Frog.bog +.Dl % +. +.Sh LOG FILES +.Pp +The log file (/var/log/gopherd.log is default) has the following structure: +. +.Pp +.Ic [<date>|<IP:port>] <item path> <query> (<status>) +. +.Pp +where, +. +.Bl -inset +.It Ic <date> += access date and time (std 'date' format) +.Bl -inset -offset indent +ex. +.Qq "Sun Feb 17 06:11:10 PST 2008" +.El +.It Ic <IP:port> += client IP address and port served +.Bl -inset -offset indent +ex. +.Qq "24.208.18.127:16857" +.El +.Pp +.It Ic <item path> += full path to item served +.Bl -inset -offset indent +ex. +.D1 Qo "/PICS/simple2.jpg" Qc for an image file +.D1 Qo "/PICS" Qc for a directory access +.El +.It Ic <query> += query term submitted (Type 7 requests only) +.Bl -inset -offset indent +ex. +.Dl % snarf Qq "gopher://frog.bog/7/hello.cgi?Christoph" +.Dl would log Qo "Christoph" Qc as the query term. +.El +.It Ic (<status>) += status of client request +.Bl -inset -offset indent +ex. - some common status entries: +.El +.Pp +.Bl -hang -width XXXXXXXXXXXXXXXX -compact -offset XXXXXXXXXXXX +.It Qo (serving) Qc +=> a successful request +.It Qo (not found) Qc +=> an unsuccessful request +.It Qo (HTTP redirect) Qc +=> web link redirect (Type h) +.It Qo (dir listing) Qc +=> unindexed directory listing +.El +.El +. +.Sh FILES +README, LICENSE, index.gph +. +.Sh "SEE ALSO" +Links for further information on gopher: +.Pp +.D1 Pa gopher://gopher.gopherproject.org +.D1 Pa http://www.gopherproject.org +.Pp +.Sh STANDARDS +.Em Internet RFC 1436 +. +.Sh HISTORY +Geomyidae started as a Linux/BSD port of the Plan 9 gopherd_P9 server. +Originally called gopherd_BSD, the name was later changed to Geomyidae +(latin), the taxonomic family of burrowing rodents known as "pocket +gophers" which are in fact the true gophers. Because of inconsitencies +and the UNIX culture, the name was changed to lowercase in 2010. +. +.Sh AUTHORS +See LICENSE file for authors in the distribution. +. +.Sh LICENSE +Geomyidae is released under the MIT/X Consortium License. +. +.Sh BUGS +Geomyidae occasionally aborts silently if too many simultaneous +requests are made. Limiting gopher traffic via firewall rules +may help. +.Pp +Dynamic content functionality may vary across gopher clients. +. +.Ss "Reporting Bugs" +Report bugs to: +.An "Christoph Lohmann" Aq 20h@R-36.net diff --git a/handlr.c b/handlr.c @@ -0,0 +1,196 @@ +/* + * Copy me if you can. + * by 20h + */ + +#include <unistd.h> +#include <memory.h> +#include <netdb.h> +#include <netinet/in.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include "ind.h" +#include "arg.h" + +void +handledir(int sock, char *path, char *port, char *base, char *args, + char *sear) +{ + char *pa, *file, *e, *addr, *par, *b; + struct dirent **dirent; + int ndir, i; + struct stat st; + filetype *type; + + USED(sear); + addr = nil; + + pa = gstrdup(path); + e = strrchr(pa, '/'); + if(e != nil) { + *e = '\0'; + + if(args == nil) { + addr = gmallocz(512, 2); + if(gethostname(addr, 512) == -1) { + perror("gethostname"); + close(sock); + free(addr); + free(pa); + return; + } + } else + addr = gstrdup(args); + + par = gstrdup(pa); + b = strrchr(par + strlen(base), '/'); + if(b != nil) { + *b = '\0'; + tprintf(sock, "1..\t%s\t%s\t%s\r\n", + par + strlen(base), addr, port); + } + free(par); + + ndir = scandir(pa, &dirent, 0, alphasort); + if(ndir < 0) { + perror("scandir"); + close(sock); + free(addr); + free(pa); + return; + } else { + for(i = 0; i < ndir; i++) { + if(dirent[i]->d_name[0] == '.') { + free(dirent[i]); + continue; + } + + type = gettype(dirent[i]->d_name); + file = smprintf("%s/%s", pa, + dirent[i]->d_name); + if(stat(file, &st) >= 0 && S_ISDIR(st.st_mode)) + type = gettype("index.gph"); + e = file + strlen(base); + tprintf(sock, "%c%s\t%s\t%s\t%s\r\n", *type->type, + dirent[i]->d_name, e, addr, port); + free(file); + free(dirent[i]); + } + free(dirent); + } + tprintf(sock, "\r\n"); + } + + if(addr != nil) + free(addr); + free(pa); + close(sock); + return; +} + +void +handlegph(int sock, char *file, char *port, char *base, char *args, + char *sear) +{ + Indexs *act; + int i; + char addr[512]; + + USED(base); + USED(args); + USED(sear); + + act = scanfile(file); + if(act != nil) { + if(args == nil) { + if(gethostname(addr, sizeof(addr)) == -1) { + perror("gethostname"); + close(sock); + return; + } + } else + snprintf(addr, sizeof(addr), "%s", args); + + + for(i = 0; i < act->num; i++) { + if(!strncmp(act->n[i]->e[3], "server", 6)) { + free(act->n[i]->e[3]); + act->n[i]->e[3] = gstrdup(addr); + } + if(!strncmp(act->n[i]->e[4], "port", 4)) { + free(act->n[i]->e[4]); + act->n[i]->e[4] = gstrdup(port); + } + tprintf(sock, "%.1s%s\t%s\t%s\t%s\r\n", + act->n[i]->e[0], act->n[i]->e[1], + act->n[i]->e[2], act->n[i]->e[3], + act->n[i]->e[4]); + + freeelem(act->n[i]); + act->n[i] = nil; + } + tprintf(sock, "\r\n.\r\n\r\n"); + + freeindex(act); + } + + close(sock); + return; +} + +void +handlebin(int sock, char *file, char *port, char *base, char *args, + char *sear) +{ + char sendb[1024]; + int len, fd; + + len = -1; + USED(port); + USED(base); + USED(args); + USED(sear); + + fd = open(file, O_RDONLY); + if(fd >= 0) { + while((len = read(fd, sendb, sizeof(sendb))) > 0) + send(sock, sendb, len, 0); + close(fd); + } + + close(sock); + return; +} + +void +handlecgi(int sock, char *file, char *port, char *base, char *args, + char *sear) +{ + char *p; + + USED(port); + USED(base); + + p = strrchr(file, '/'); + if(p == nil) + p = file; + + dup2(sock, 1); + dup2(sock, 0); + dup2(sock, 2); + + if(sear == nil) + sear = ""; + + execl(file, p, sear, args, nil); + + close(sock); + return; +} + diff --git a/handlr.h b/handlr.h @@ -0,0 +1,20 @@ +/* + * Copy me if you can. + * by 20h + */ + +#ifndef HANDLR_H +#define HANDLR_H + +#define nil NULL + +void handledir(int sock, char *path, char *port, char *base, char *args, + char *sear); +void handlegph(int sock, char *file, char *port, char *base, char *args, + char *sear); +void handlebin(int sock, char *file, char *port, char *base, char *args, + char *sear); +void handlecgi(int sock, char *file, char *port, char *base, char *args, + char *sear); + +#endif diff --git a/ind.c b/ind.c @@ -0,0 +1,287 @@ +/* + * Copy me if you can. + * by 20h + */ + +#include <unistd.h> +#include <stdarg.h> +#include <string.h> +#include <memory.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include "ind.h" +#include "handlr.h" + +filetype type[] = { + {"default", "0", handlebin}, + {"gph", "1", handlegph}, + {"cgi", "0", handlecgi}, + {"bin", "9", handlebin}, + {"tgz", "9", handlebin}, + {"gz", "9", handlebin}, + {"jpg", "I", handlebin}, + {"gif", "g", handlebin}, + {"png", "I", handlebin}, + {"bmp", "I", handlebin}, + {"txt", "0", handlebin}, + {"html", "0", handlebin}, + {"htm", "0", handlebin}, + {"xhtml", "0", handlebin}, + {"css", "0", handlebin}, + {nil, nil, nil}, +}; + +filetype * +gettype(char *filename) +{ + char *end; + int i; + + end = strrchr(filename, '.'); + if(end == nil) + return &type[0]; + end++; + + for(i = 0; type[i].end != nil; i++) + if(!strncasecmp(end, type[i].end, strlen(type[i].end))) + return &type[i]; + + return &type[0]; +} + +void * +gmallocz(int l, int d) +{ + char *ret; + + ret = malloc(l); + if(ret == nil) { + perror("malloc"); + exit(1); + } + + if(d) + memset(ret, 0, l); + + return (void *)ret; +} + +char * +gstrdup(char *str) +{ + char *ret; + + ret = strdup(str); + if(ret == nil) { + perror("strdup"); + exit(1); + } + + return ret; +} + +char * +readln(int fd) +{ + char *ret; + int len; + + len = 1; + + ret = malloc(2); + while(read(fd, &ret[len - 1], 1) > 0 && ret[len - 1] != '\n') + ret = realloc(ret, ++len + 1); + if(ret[len - 1] != '\n') { + free(ret); + return nil; + } + ret[len - 1] = '\0'; + + return ret; +} + +void +freeelem(Elems *e) +{ + + if(e != nil) { + if(e->e != nil) { + for(;e->num > 0; e->num--) + if(e->e[e->num - 1] != nil) + free(e->e[e->num - 1]); + free(e->e); + } + free(e); + } + return; +} + +void +freeindex(Indexs *i) +{ + + if(i != nil) { + if(i->n != nil) { + for(;i->num > 0; i->num--) + if(i->n[i->num - 1] != nil) + freeelem(i->n[i->num - 1]); + free(i->n); + } + free(i); + } + + return; +} + +void +addelem(Elems *e, char *s) +{ + + e->num++; + e->e = realloc(e->e, sizeof(char *) * e->num); + e->e[e->num - 1] = gmallocz(strlen(s) + 1, 0); + strcpy(e->e[e->num - 1], s); + + return; +} + +Elems * +getadv(char *str) +{ + char *b, *e; + Elems *ret; + + ret = gmallocz(sizeof(Elems), 2); + if(*str != '[') { + b = str; + if(*str == 't') + b++; + addelem(ret, "i"); + addelem(ret, b); + addelem(ret, "Err"); + addelem(ret, "server"); + addelem(ret, "port"); + + return ret; + } + + b = str + 1; + while((e = strchr(b, '|')) != nil) { + *e = '\0'; + e++; + addelem(ret, b); + b = e; + } + + e = strchr(b, ']'); + if(e != nil) { + *e = '\0'; + addelem(ret, b); + } + if(ret->e == nil) { + free(ret); + return nil; + } + + return ret; +} + +Indexs * +scanfile(char *fname) +{ + char *ln; + int fd; + Indexs *ret; + Elems *el; + + fd = open(fname, O_RDONLY); + if(fd < 0) + return nil; + + ret = gmallocz(sizeof(Indexs), 2); + + while((ln = readln(fd)) != nil) { + el = getadv(ln); + free(ln); + if(el == nil) + continue; + + ret->num++; + ret->n = realloc(ret->n, sizeof(Elems) * ret->num); + ret->n[ret->num - 1] = el; + el = nil; + } + close(fd); + + if(ret->n == nil) { + free(ret); + return nil; + } + + return ret; +} + +void +tprintf(int fd, char *fmt, ...) +{ + va_list fmtargs; + int fd2; + FILE *fp; + + fd2 = dup(fd); + fp = fdopen(fd2, "w"); + if(fp == nil) { + perror("fdopen"); + return; + } + + va_start(fmtargs, fmt); + vfprintf(fp, fmt, fmtargs); + va_end(fmtargs); + + fclose(fp); + + return; +} + +int +initlogging(char *logf) +{ + int fd; + + fd = open(logf, O_APPEND | O_WRONLY | O_CREAT, 0644); + + return fd; +} + +void +stoplogging(int fd) +{ + + close(fd); + + return; +} + +char * +smprintf(char *fmt, ...) +{ + va_list fmtargs; + char *ret; + int size; + + ret = ""; + + va_start(fmtargs, fmt); + size = vsnprintf(ret, 0, fmt, fmtargs); + va_end(fmtargs); + + ret = gmallocz(++size, 2); + va_start(fmtargs, fmt); + vsnprintf(ret, size, fmt, fmtargs); + va_end(fmtargs); + + return ret; +} + diff --git a/ind.h b/ind.h @@ -0,0 +1,48 @@ +/* + * Copy me if you can. + * by 20h + */ + +#ifndef IND_H +#define IND_H + +#include <stdio.h> +#define nil NULL + +extern int glfd; + +typedef struct Elems Elems; +struct Elems { + char **e; + int num; +}; + +typedef struct Indexs Indexs; +struct Indexs { + Elems **n; + int num; +}; + +typedef struct filetype filetype; +struct filetype { + char *end; + char *type; + void (* f)(int, char *, char *, char *, char *, char *); +}; + +filetype *gettype(char *filename); +void *gmallocz(int l, int d); +char *gstrdup(char *str); +Indexs *scanfile(char *fname); +Elems *getadv(char *str); +void addelem(Elems *e, char *s); +void freeindex(Indexs *i); +void freeelem(Elems *e); +char *readln(int fd); +void tprintf(int fd, char *fmt, ...); +int initlogging(char *logf); +void stoplogging(int fd); +char *smprintf(char *fmt, ...); + +#endif + diff --git a/index.gph b/index.gph @@ -0,0 +1,6 @@ +comment +tcomment +[1|R-36|/|server|port] +[0|file - comment|/file.dat|server|port] +[h|http://www.heise.de|URL:http://www.heise.de|server|port] + diff --git a/main.c b/main.c @@ -0,0 +1,385 @@ +/* + * Copy me if you can. + * by 20h + */ + +#include <unistd.h> +#include <dirent.h> +#include <memory.h> +#include <netdb.h> +#include <netinet/in.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <sys/types.h> +#include <signal.h> +#include <time.h> +#include <pwd.h> +#include <grp.h> +#include "ind.h" +#include "handlr.h" +#include "arg.h" + +enum { + NOLOG = 0, + FILES = 1, + DIRS = 2, + HTTP = 4, + ERRORS = 8, +}; + +int glfd = -1; +int loglvl = 15; + +char *argv0; +char *stdbase = "/var/gopher"; +char *stdport = "70"; +char *indexf = "/index.gph"; +char *err = "0Sorry, but the requested token could not be found\tErr" + "\tlocalhost\t70\r\n.\r\n\r\n"; +char *htredir = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n" + " \"DTD/xhtml-transitional.dtd\">\n" + "<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"en\">\n" + " <head>\n" + " <title>gopher redirect</title>\n" + "\n" + " <meta http-equiv=\"Refresh\" content=\"1;url=%s\" />\n" + " </head>\n" + " <body>\n" + " This page is for redirecting you to: <a href=\"%s\">%s</a>.\n" + " </body>\n" + "</html>\n"; + +int +dropprivileges(struct group *gr, struct passwd *pw) +{ + + if(gr != nil) + if(setgroups(1, &gr->gr_gid) != 0 || setgid(gr->gr_gid) != 0) + return -1; + if(pw != nil) { + if(gr == nil) { + if(setgroups(1, &pw->pw_gid) != 0 || + setgid(pw->pw_gid) != 0) + return -1; + } + if(setuid(pw->pw_uid) != 0) + return -1; + } + + return 0; +} + +char * +securepath(char *p, int len) +{ + int i; + + if(len < 2) + return p; + + for(i = 1; i < strlen(p); i++) { + if(p[i - 1] == '.' && p[i] == '.') { + if(p[i - 2] == '/') + p[i] = '/'; + if(p[i + 1] == '/') + p[i] = '/'; + if(len == 2) + p[i] = '/'; + } + } + + return p; +} + +void +logentry(char *host, char *port, char *qry, char *status) +{ + time_t tim; + struct tm *ptr; + char timstr[128]; + + if(glfd >= 0) { + tim = time(0); + ptr = localtime(&tim); + + strftime(timstr, sizeof(timstr), "%a %b %d %H:%M:%S %Z %Y", + ptr); + + tprintf(glfd, "[%s|%s:%s] %s (%s)\n", + timstr, host, port, qry, status); + } + + return; +} + +void +handlerequest(int sock, char *base, char *ohost, char *port, char *clienth, + char *clientp) +{ + struct stat dir; + char recvc[1024], recvb[1024], path[1024], *args, *sear, *c; + int len, fd; + filetype *type; + + memset(&dir, 0, sizeof(dir)); + memset(recvb, 0, sizeof(recvb)); + memset(recvc, 0, sizeof(recvc)); + + len = recv(sock, recvb, sizeof(recvb), 0); + if(len > 1) { + if(recvb[len - 2] == '\r') + recvb[len - 2] = '\0'; + if(recvb[len - 1] == '\n') + recvb[len - 1] = '\0'; + } + strcpy(recvc, recvb); + + if(!strncmp(recvb, "URL:", 4)) { + len = snprintf(path, sizeof(path), htredir, + recvb + 4, recvb + 4, recvb + 4); + if(len > sizeof(path)) + len = sizeof(path); + send(sock, path, len, 0); + if(loglvl & HTTP) + logentry(clienth, clientp, recvc, "HTTP redirect"); + return; + } + + sear = strchr(recvb, '\t'); + if(sear != nil) + *sear++ = '\0'; + args = strchr(recvb, '?'); + if(args != nil) + *args++ = '\0'; + else + args = ohost; + + securepath(recvb, len - 2); + snprintf(path, sizeof(path), "%s%s", base, recvb); + if(stat(path, &dir) != -1 && S_ISDIR(dir.st_mode)) + strncat(path, indexf, sizeof(path) - strlen(path)); + + fd = open(path, O_RDONLY); + if(fd >= 0) { + close(fd); + if(loglvl & FILES) + logentry(clienth, clientp, recvc, "serving"); + + c = strrchr(path, '/'); + if(c == nil) + c = path; + type = gettype(c); + type->f(sock, path, port, base, args, sear); + } else { + if(S_ISDIR(dir.st_mode)) { + handledir(sock, path, port, base, args, sear); + if(loglvl & DIRS) + logentry(clienth, clientp, recvc, + "dir listing"); + return; + } + + send(sock, err, strlen(err), 0); + if(loglvl & ERRORS) + logentry(clienth, clientp, recvc, "not found"); + close(sock); + } + + return; +} + +void +hndlsigchld(int signo) +{ + int status; + + while(waitpid(-1, &status, WNOHANG) > 0); + + return; +} + +void +usage(void) +{ + + tprintf(2, "usage: %s [-d] [-l logfile] [-v loglvl] [-b base]" + " [-p port] [-o sport] [-u user] [-g group] [-h host]" + " [-i IP]\n", + argv0); + + exit(1); +} + +int +main(int argc, char *argv[]) +{ + struct addrinfo hints, *ai; + struct sockaddr_storage clt; + socklen_t cltlen; + int sock, list, opt, dofork; + char *port, *base, *logfile, clienth[NI_MAXHOST], clientp[NI_MAXSERV]; + char *user, *group, *bindip, *ohost, *sport; + struct passwd *us; + struct group *gr; + + base = stdbase; + port = stdport; + dofork = 1; + logfile = nil; + user = nil; + group = nil; + us = nil; + gr = nil; + bindip = nil; + ohost = nil; + sport = port; + + ARGBEGIN { + case 'b': + base = EARGF(usage()); + break; + case 'p': + port = EARGF(usage()); + break; + case 'l': + logfile = EARGF(usage()); + break; + case 'd': + dofork = 0; + break; + case 'v': + loglvl = atoi(EARGF(usage())); + break; + case 'u': + user = EARGF(usage()); + break; + case 'g': + group = EARGF(usage()); + break; + case 'i': + bindip = EARGF(usage()); + break; + case 'h': + ohost = EARGF(usage()); + break; + case 'o': + sport = EARGF(usage()); + break; + default: + usage(); + } ARGEND; + + if(group != nil) { + if((gr = getgrnam(group)) == nil) { + perror("no such group"); + return 1; + } + } + + if(user != nil) { + if((us = getpwnam(user)) == nil) { + perror("no such user"); + return 1; + } + } + + if(dofork && fork() != 0) + return 0; + + if(logfile != nil) { + glfd = initlogging(logfile); + if(glfd < 0) { + perror("initlogging"); + return 1; + } + } + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags |= AI_PASSIVE; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + hints.ai_family = AF_INET; + if(getaddrinfo(bindip, port, &hints, &ai)) { + perror("getaddrinfo"); + return 1; + } + if(ai == nil) { + perror("getaddrinfo"); + return 1; + } + + list = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if(list < 0) { + perror("socket"); + return 1; + } + + opt = 1; + if(setsockopt(list, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) { + perror("setsockopt"); + return 1; + } + + if(bind(list, ai->ai_addr, ai->ai_addrlen)) { + perror("bind"); + return 1; + } + + if(listen(list, 255)) { + perror("listen"); + return 1; + } + + freeaddrinfo(ai); + + if(dropprivileges(gr, us) < 0) { + perror("cannot drop privileges"); + return 1; + } + + if(dofork) { + signal(SIGINT, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + } + signal(SIGCHLD, hndlsigchld); + + cltlen = sizeof(clt); + for(;;) { + sock = accept(list, (struct sockaddr *)&clt, &cltlen); + if(sock < 0) { + perror("accept"); + close(list); + return 1; + } + + getnameinfo((struct sockaddr *)&clt, cltlen, clienth, + sizeof(clienth), clientp, sizeof(clientp), + NI_NUMERICHOST); + + switch(fork()) { + case -1: + perror("fork"); + close(sock); + break; + case 0: + handlerequest(sock, base, ohost, sport, clienth, + clientp); + return 1; + default: + wait(&opt); + close(sock); + break; + } + } + + close(list); + if(logfile != nil) + stoplogging(glfd); + return 0; +} + diff --git a/rc.d/Archlinux.conf.d b/rc.d/Archlinux.conf.d @@ -0,0 +1,4 @@ +# +# Parameters to be passed to geomyidae +# +GEOMYIDAE_ARGS="-u nobody -g nobody -b /srv/gopher -o 70 -l /var/log/geomyidae.log -h localhost" diff --git a/rc.d/Archlinux.rc.d b/rc.d/Archlinux.rc.d @@ -0,0 +1,38 @@ +#!/bin/bash + +. /etc/rc.conf +. /etc/rc.d/functions +. /etc/conf.d/geomyidae + +PID=`pidof -o %PPID /usr/bin/geomyidae` +case "$1" in + start) + stat_busy "Starting geomyidae" + [ -z "$PID" ] && /usr/bin/geomyidae $GEOMYIDAE_ARGS 2>&1 + if [ $? -gt 0 ]; then + stat_fail + else + PID=`pidof -o %PPID /usr/bin/geomyidae` + echo $PID >/var/run/geomyidae.pid + add_daemon geomyidae + stat_done + fi + ;; + stop) + stat_busy "Stopping geomyidae" + [ ! -z "$PID" ] && kill $PID &>/dev/null + if [ $? -gt 0 ]; then + stat_fail + else + rm_daemon geomyidae + stat_done + fi + ;; + restart) + $0 stop + $0 start + ;; + *) + echo "usage: $0 {start|stop|restart}" +esac +exit 0 diff --git a/rc.d/Gentoo.conf.d b/rc.d/Gentoo.conf.d @@ -0,0 +1,5 @@ +# +# Parameters to be passed to geomyidae +# +GEOMYIDAE_ARGS="-u gopherd -g gopherd -b /var/gopher -o 70 -l /var/log/geomyidae.log -h localhost" + diff --git a/rc.d/Gentoo.init.d b/rc.d/Gentoo.init.d @@ -0,0 +1,17 @@ +#!/sbin/runscript +# Copyright 1999-2010 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 +# $Header: $ + +start(){ + ebegin "Starting geomyidae" + [ -n "$GEOMYIDAE_ARGS" ] && GEOMYIDAE_ARGS="-- $GEOMYIDAE_ARGS" + start-stop-daemon --start --pidfile /var/run/geomyidae.pid --exec /usr/sbin/geomyidae $GEOMYIDAE_ARGS + eend $? "Failed to start geomyidae" +} + +stop(){ + ebegin "Stopping geomyidae" + start-stop-daemon --stop --pidfile /var/run/geomyidae.pid + eend $? "Failed to stop geomyidae" +} diff --git a/rc.d/NetBSD.rc.d b/rc.d/NetBSD.rc.d @@ -0,0 +1,55 @@ +#!/bin/sh +# + +# REQUIRE: local +# PROVIDE: geomyidae + +$_rc_subr_loaded . /etc/rc.subr + +name="geomyidae" +rcvar=$name +command="/usr/pkg/sbin/${name}" + +##################################################### +# Geomyidae Options Section - "?" => geomyidae(8) # +# Uncomment & define options (defaults are shown) # +##################################################### +# +#LOGFILE="-l /var/log/gopherd.log" +#LOGLEVEL="-v 15" +#HTDOCS="-b /var/gopher" +#PORT="-p 70" +#SPORT="-o 70" +#USR="-u $USER" +#GRP="-g $GROUP" +#HOST="-h localhost" +#IP="-i 127.0.0.1" + +###################################################### +# Now remove any UNDEFINED options from line below: # +###################################################### +# +command_args="$LOGFILE $LOGLEVEL $HTDOCS $PORT $SPORT $USR $GRP $HOST $IP" + + +###################################################### +# Uncomment this section if a PID file is desired # +###################################################### + +#pidfile="/var/run/${name}.pid" +#start_cmd="geomyidae_start" +# +#geomyidae_start() +#{ +# echo "Starting $name" +# $command $command_args +# pgrep -x $name > $pidfile +#} + +###################################################### +# Lastly, add the following to /etc/rc.conf: # +# "geomyidae=YES" (without the quotes) # +###################################################### + +load_rc_config $name +run_rc_command "$1" diff --git a/rc.d/README b/rc.d/README @@ -0,0 +1,5 @@ +These are init scripts, used in various distribution, for Geomyidae. + +All files are examples sent in by users. Please review them before +using. +