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.
+