Initial commit at 0.16 of geomyidae. - geomyidae - A small C-based gopherd.
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  
+© 2008 J. A. Neitzel  
+© 2010 James Penketh  
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+tto deal in the Software without restriction, including without limitation
+tthe 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
+ttree 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
+tto 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
+tto 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 .gph
+which, if present, geomyidae uses to format and/or filter the contents of
+tthe 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
+ttheir 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 [|||]
+.El
+.Pp
+where,
+.Bl -inset -offset indent
+.It Ic 
+= 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
+tthere to allow lines "informational" text beginning with "[".
+.
+.It Ic 
+= description of gopher item. Most printable characters should work.
+.
+.It Ic 
+= full path to gopher item (base value is / ).  Use the "Err" path for
+items not intended to be served.
+.
+.It Ic 
+= hostname or IP hosting the gopher item. Must be resolvable for the
+intended clients.
+.
+.It Ic 
+= 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
+t[i||||]
+t[0|About this server|about.txt|frog.bog|70]
+t[0|Daily Log|/dtail.cgi|frog.bog|70]
+t[1|Phlog: like a blog, but not|/PHLOG|frog.bog|70]
+t[9|Some binary file|widget.exe|frog.bog|70]
+t[I|Snowflake picture|snowflake.jpg|frog.bog|70]
+
+Links and Searches
+t[1|Go to R-36.net|/|gopher.r-36.net|70]
+t[h|Go to NetBSD.org|URL:http://netbsd.org|frog.bog|70]
+t[7|Query US Weather by Zipcode|/weather.cgi?|frog.bog|70]
+t[7|Search Veronica II|/v2/vs|gopher.floodgap.com|70]
+t[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 .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 
+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 [|]   ()
+.
+.Pp
+where,
+.
+.Bl -inset
+.It Ic 
+= access date and time (std 'date' format)
+.Bl -inset -offset indent
+ex.
+.Qq "Sun Feb 17 06:11:10 PST 2008"
+.El
+.It Ic 
+= client IP address and port served
+.Bl -inset -offset indent
+ex.
+.Qq "24.208.18.127:16857"
+.El
+.Pp
+.It Ic 
+= 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 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 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 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#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 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#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
+ttprintf(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 
+#define nil NULL
+
+extern int glfd;
+
+ttypedef struct Elems Elems;
+struct Elems {
+        char **e;
+        int num;
+};
+
+ttypedef struct Indexs Indexs;
+struct Indexs {
+        Elems **n;
+        int num;
+};
+
+ttypedef 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
+ttcomment
+t[1|R-36|/|server|port]
+t[0|file - comment|/file.dat|server|port]
+t[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 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#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 = "\n"
+                "\n"
+                "\n"
+                "  \n"
+                "    gopher redirect\n"
+                "\n"
+                "    \n"
+                "  \n"
+                "  \n"
+                "    This page is for redirecting you to: %s.\n"
+                "  \n"
+                "\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.
+