1
0
mirror of https://github.com/gophernicus/gophernicus.git synced 2024-11-03 04:27:17 -05:00

Initial git commit with version 1.4

This commit is contained in:
Kim Holviala 2014-01-25 11:21:40 +02:00
commit d829e6a347
37 changed files with 5163 additions and 0 deletions

251
ChangeLog Normal file
View File

@ -0,0 +1,251 @@
2012-12-02 Kim Holviala <kim@holviala.com>
* Released version 1.4
* Added logcheck ignore file for Debian
* Fixed compile without HAVE_SHMEM
* Autogenerated caps.txt now works without SHMEM
* Support for Haiku R1 (make && make install works)
* CGIs now have $LOCAL_ADDR
* Debian package no longer depends on lsb-release
* CPU type is now properly detected on AIX
* Makefile supports cross-compiling (CC & HOSTCC)
* Option -nr disables root user checking (for debugging)
* Platform detection for Linux/mips boards (routers)
* Fixes for OpenBSD (thanks to Brian Callahan)
* Last remaining sprintf() replaced with snprintf()
* max() is no longer a function but a #define
* Clang fixes (thanks to Jacob Dahl Pind)
* Removed system-info.sh & dmidecode from Debian package
2012-06-12 Kim Holviala <kim@holviala.com>
* Released version 1.3
* Support for headerless HTTP/0.9
* Code cleanups
* Platform probing now knows about RedHat and Slackware
* lsb_release no longer gets called if it doesn't exist
(bugreport from Jacob Dahl Pind)
* Changed menu errors from type "i" text to type "3"
(bugreport from Damien Carol)
* Removed the special OSX Carbonized build
* Fixed a bug in menu.c: popen() needs pclose()
* Fixed a off-by-one in gopher_filetype()
* Changed the filetype of documents (doc/ps/pdf) to "d"
2012-05-04 Kim Holviala <kim@holviala.com>
* Released version 1.2 (finally...)
* On the fly selector rewriting (like Apache mod_rewrite)
* Gophertags are properly converted to output charset
* Server admin email can be specified for caps.txt
* Server location for caps.txt
* Split options.c from gophernicus.c
* Removed support for Gopher over HTTP proxies
* Hack to make "gopher example.com" work
(UMN gopher client assumes gopher+ which we don't support)
2010-12-01 Kim Holviala <kim@holviala.com>
* Released version 1.1
* Content-based detection of gif/png/jpg/ps/pdf/html/gz
* Gophermap virtual host list (%) only lists FQDNs
* Serve out caps.txt from a file if it exists
* Option -na disables autogenerated caps.txt
* Changed option -l to -b (show BSD license)
* Changed option -m to -l (log to file)
* README and LICENSE weren't zero-terminated strings (duh)
* Querying /server-status no longer updates statistics
(because Munin statistics collection was being throttled)
* Internal charset variables are now enums and not strings
2010-10-05 Kim Holviala <kim@holviala.com>
* Released version 1.0
* Support for caps.txt as suggested by Cameron Kaiser
* Support for gophertags (lifted from Bucktooth)
* HTTP requests are redirected to a public gopher proxy
* Allow directories named "gophermap" and "gophertag"
(only files are special)
* Removed duplicate call to strniconv()
* A few ENABLE_STRICT_RFC1436 additions
* Much more descriptive error logging
* Self references /./ are removed from request
* Apache-compatible file logging in combined log format (-m)
* FIFOs in directories no longer crash the server (duh!)
* Support for Mac OS X (tested with 10.5 on Intel)
* Reworked Makefile with platform-specific build targets
* Fixed a call to dirname(path) (don't assume it modifies path)
* Makefile installer now supports xinetd and launchd
* Makefile installer installs default /var/gopher/gophermap and
links the document directory as /docs/
* Added SERVER_ARCH, SERVER_VERSION and SERVER_DESCRIPTION to
CGI env variables
* Fixed a segfault where shm was used uninitialized (duh!)
* Generate native Debian/Ubuntu package with "make deb"
* platform() now tries to figure out Linux distribution
* platform() knows about Linux ARM boards
* Added argument -nm (No shared Memory) for debugging
* Fixed AIX make glitch when compiling bin2c (bug in make?)
* Changed the name of the project from "Gophernicus Server"
to plain "Gophernicus"
* Menus without footer (-nf) were missing the dot at the end
* Footer message is now right-aligned
* server-status and caps.txt requests now update sessions
* Compile-time option ENABLE_AUTOHIDING hides manually listed
resources from generated menus (to prevent double listings)
2010-07-03 Kim Holviala <kim@holviala.com>
* Released version 0.9
* Added option to disable HTTP-style query strings
* Fixed a regression where some binary files were served out
as text (which broke them)
* BinHex files are now mapped to filetype 4
* Disabling vhosting disables sessions (mostly)
2010-04-30 Kim Holviala <kim@holviala.com>
* Released version 0.8
* Security bug in hURL handler fixed
* Error pages are now correctly generated for type 'h'
* Menu error page more compatible with clients
* New macro sstrncmp() compares without explicit sizeof()
* Removed all traces of gopher++ protocol (extra headers)
as it just didn't work with older (circa '92) servers
* Added option to disable automatic menu headers (titles)
* Do a chdir() to the resource dir before doing anything
* Double-slashes were slashed in QUERY_STRING by accident
* Relative links to external hosts work properly in gophermap
* Gophermaps can include other gophermaps with =/path/to/file
* Executable gophermaps are parsed just like static ones
2010-04-13 Kim Holviala <kim@holviala.com>
* Released version 0.7
* This release is feature complete, no new features in sight
* Support for NetBSD (a typo prevented building - duh)
* Replaced the poorly-working scandir() with opendir/qsort
* Directory listings (menus) are limited to 1024 entries
* Changed the filetype of movies from "v" to ";" (which sucks)
* Fixed a compatibility issue with bucktooth gophermaps
* Finally wrote decent documentation (README and INSTALL)
2010-04-11 Kim Holviala <kim@holviala.com>
* Released version 0.6
* platform() results are kept in shared memory
* Support for AIX 5.1 and newer
* IPv4-in-IPv6 prefix ::ffff: is removed from remote_addr
* Replaced install(1) with the install-sh script
* /server-status CPULoad can be parsed from /usr/bin/uptime
* Replaced text2c/hexdump with bin2c.c (less dependencies)
* Files with extension .q (type 7 query) are considered CGIs
* Filetype handling completely rewritten
* Configurable gopher filetypes using the "-e ext=X" argument
* Per-directory filetype overrides in gophermaps with ":ext=X"
* Refuse to serve out gophermaps (why didn't I catch this before?)
* Cleaned up main()
* Output filters - run files through an external program (php!)
2010-04-05 Kim Holviala <kim@holviala.com>
* Released version 0.5
* Code tested to work on 32-bit Linux/armv5tel
* Filetype 7 query errors are now handled properly
* HTTP-style query string overrides type 7 query
* Protocol detection (0/+/++) works properly
* gopher++ extra headers parsed correctly
* gopher++ works ok with a patched NSCA Mosaic!
* ISO-8859-1 (Latin-1) output
* Full UTF-8 output support (without widechars)
* Gophermaps are converted to output charset
* All type 0 output is converted to output charset
* All charset conversions can be disabled with option -no
* !Titles in gophermaps are converted to gopher menu titles
* Automatically generates gopher title resources for menus
* Compile-time option to strictly adhere to RFC 1436
* Compile-time option to disable all gopher++ support
2010-03-29 Kim Holviala <kim@holviala.com>
* Released version 0.4
* Renamed the project to "Gophernicus Server"
* Major rewrite with much cleaner code
* Changed all strncpy's to the OpenBSD strlcpy
* Server can guess the request type (menu/text/binary)
* Errors are formatted for current filetype (menu/text)
* Errors for images (types g&I) are outputted as an image
* /~luser (/home/luser/public_gopher) must be owned by luser
* Configurable output width for menus
* Files are outputted using sendfile() if available
* Locale forced to POSIX for strftime()
* Filetype '-' in gophermaps hides files
* Gopher+ requests are now handled gracefully
* Relative selectors in gophemaps work
* Filesizes in menus are now human-readable (KB/MB/GB etc)
* Refuse to serve world-writeable content
* Support for Apache-style /server-status
* HTTP requests for /server-status work (munin monitoring ftw!)
* Session tracking using shared memory
* Referer support for CGIs
* Replaced iconv() with own charset conversion routine
* Automatic throttling for users who hit the server too much
* Replaced static compile-time uname with uname()
* Support for virtual hosting (with gopher0 no less!)
2010-01-07 Kim Holviala <kim@holviala.com>
* Released version 0.3
* Automatic detection of text vs. binary filetype for files
which have no (known) suffix
* hURL redirect pages now respect -f (no footer) option
* IPv6 support for logging & CGI REMOTE_ADDR
* Inline gophermaps
* Removed support for relative resource names in gophermaps
* Support for virtual userdirs (~user -> /home/user/public_gopher)
* Automatic listing of userdirs in gophermap
* Redirect accidental http requests to gopher
* License included in the binary -> install no longer installs docs
* Changed command line options (I was running out of arg letters...)
* Debug to syslog with '-d' option
2010-01-02 Kim Holviala <kim@holviala.com>
* Released version 0.2
* Logging to syslog
* Support for gophermaps
* Support for executable gophermaps
* Support for CGI scripts
* Support for type 7 search queries
* Support for hURL redirect pages
* Support for non-ASCII resource/file names (tested with UTF-8)
* Support for both %hex and #octal request encodings
* Try to get server hostname from $HOSTNAME or gethostname()
* Basic support for different platforms via HAVE_XX defines
2009-12-30 Kim Holviala <kim@holviala.com>
* Released version 0.1
* Basic rfc1436 functionality works
* Fancy menus with file dates & sizes
* Options via command line (with sensible defaults)
2009-12-28 Kim Holviala <kim@holviala.com>
* Started coding kgopherd
* Trying to remember how "C" works...

113
INSTALL Normal file
View File

@ -0,0 +1,113 @@
Compiling and installing Gophernicus
====================================
Gophernicus requires a C compiler but no extra libraries aside
from standard LIBC ones. Care has been taken to use only
standard POSIX syscalls so that it should work pretty much on
any *nix system. Compiling has only been tested with GCC, but
it will most likely work with others too.
To compile and install run:
$ gzip -cd gophernicus-*.tar.gz | tar xvf -
$ cd gophernicus-*
$ make
$ sudo make install
Then add the below line to your /etc/inetd.conf and restart
inetd. If your system comes with something else than standard
inetd "make install" should have done the right thing already.
gopher stream tcp nowait nobody /usr/sbin/in.gophernicus in.gophernicus -h <hostname>
The -h <hostname> parameter is mandatory for a properly working
Gophernicus. Other parameters can also be added, see the full
list by running "/usr/sbin/in.gophernicus -?"
By default Gophernicus serves gopher documents from /var/gopher
although that can be changed by using the -r <root> parameter.
To enable virtual hosting create hostname directories under
the gopher root and make sure you have at least the primary
hostname (the one set with -h <hostname>) directory available
(mkdir /var/gopher/$HOSTNAME).
Compiling on Debian Linux (and Ubuntu)
======================================
The above commands work on Debian just fine, but if you prefer
having everything installed as packages run "make deb" instead
of plain "make". If all the dependencies were in place you'll
end up with an offical-looking deb package in the parent
directory (don't ask - that's just how it works). And instead
of "sudo make install" you should just install the deb with
"dpkg -i ../gophernicus_*.deb" after which It Should Just
Work(tm).
Compiling on Mac OS X
=====================
When you run "make install" on OSX-like system with launchd
the install routine also installs a plist file and tells launchd
to start the server up. In other words, It Just Works(tm).
Cross-compiling
===============
Cross-compiling to a different target architecture can be done
by defining HOSTCC and CC to be different compilers. HOSTCC
must point to a local arch compiler, and CC to the target
arch one.
$ make HOSTCC=gcc CC=target-arch-gcc
Shared memory issues
====================
Gophernicus uses SYSV shared memory for session tracking and
statistics. It creates the shared memory block using mode 600
and a predefined key which means that a shared memory block
created with one user cannot be used by another user. Simply
said, running in.gophernicus as yourself will allocate that
memory, and then running the binary through inetd as another
user (nobody) will be denied access to that memory.
If that happens you can simply delete the memory block and
let Gophernicus recreate it - no harm done.
$ su -
# ipcs -m | grep beeb
# ipcrm -M <key from the above command>
Porting to different platforms
==============================
If you need to port Gophernicus to a new platform, please take
a look at gophernicus.h which has a bunch of HAVE_* #defines.
Fiddling with those usually makes it possible to compile a working
server. If you succeed in compiling Gophernicus to a new
platform please send the patches to kim@holviala.com so I can
include them into the next release.
Tested (and semi-supported) platforms include:
OS Arch Compiler
+-------------+-------------+-------------+
AIX 5.1 POWER3 gcc 4
AIX 6.1 POWER4 gcc 4
AIX 7.1 POWER5 gcc 4
CentOS 5.5 x86_64 gcc 4
Debian Linux x86_64 gcc 4
Debian Linux armv5tel gcc 4
MacOSX 10.5 i386 gcc 4
MacOSX 10.7 x86_64 llvm-gcc 4
MacOSX 10.8 x86_64 clang 3
NetBSD 5.0 x86_64 gcc 4
Haiku R1 i386 gcc 2

22
LICENSE Normal file
View File

@ -0,0 +1,22 @@
Gophernicus - Copyright (c) 2009-2012 Kim Holviala <kim@holviala.com>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

248
Makefile Normal file
View File

@ -0,0 +1,248 @@
##
## Gophernicus server Makefile
##
#
# Variables and default configuration
#
NAME = gophernicus
PACKAGE = $(NAME)
BINARY = in.$(NAME)
VERSION = 1.4
SOURCES = $(NAME).c file.c menu.c string.c platform.c session.c options.c
HEADERS = functions.h files.h
OBJECTS = $(SOURCES:.c=.o)
DOCS = LICENSE README INSTALL TODO ChangeLog README.Gophermap gophertag
INSTALL = PATH=$$PATH:/usr/sbin ./install-sh -o 0 -g 0
DESTDIR = /usr
SBINDIR = $(DESTDIR)/sbin
DOCDIR = $(DESTDIR)/share/doc/$(PACKAGE)
ROOT = /var/gopher
OSXROOT = /Library/GopherServer
WRTROOT = /gopher
MAP = gophermap
INETD = /etc/inetd.conf
XINETD = /etc/xinetd.d
LAUNCHD = /Library/LaunchDaemons
PLIST = org.gophernicus.server.plist
NET_SRV = /boot/common/settings/network/services
DIST = $(PACKAGE)-$(VERSION)
TGZ = $(DIST).tar.gz
RELDIR = /var/gopher/gophernicus.org/software/gophernicus/server/
CC = gcc
HOSTCC = $(CC)
CFLAGS = -O2 -Wall
LDFLAGS =
#
# Platform support, compatible with both BSD and GNU make
#
all:
@case `uname` in \
Darwin) $(MAKE) ROOT="$(OSXROOT)" $(BINARY); ;; \
Haiku) $(MAKE) EXTRA_LDFLAGS="-lnetwork" $(BINARY); ;; \
*) $(MAKE) $(BINARY); ;; \
esac
generic: $(BINARY)
#
# Special targets
#
deb:
dpkg-buildpackage -rfakeroot -uc -us
#
# Building
#
$(NAME).c: $(NAME).h $(HEADERS)
$(BINARY): $(OBJECTS)
$(CC) $(LDFLAGS) $(EXTRA_LDFLAGS) $(OBJECTS) -o $@
.c.o:
$(CC) -c $(CFLAGS) $(EXTRA_CFLAGS) -DVERSION="\"$(VERSION)\"" -DDEFAULT_ROOT="\"$(ROOT)\"" $< -o $@
headers: $(HEADERS)
@echo
functions.h:
echo "/* Automatically generated function definitions */" > $@
echo >> $@
grep -h "^[a-z]" $(SOURCES) | grep -v "int main" | sed -e "s/ =.*$$//" -e "s/ *$$/;/" >> $@
@echo
bin2c: bin2c.c
$(HOSTCC) bin2c.c -o $@
@echo
files.h: bin2c
sed -n -e "1,/^ $$/p" README > README.options
./bin2c -0 -n README README.options > $@
./bin2c -0 LICENSE >> $@
./bin2c -n ERROR_GIF error.gif >> $@
@echo
#
# Cleanup after building
#
clean: clean-build clean-deb
clean-build:
rm -f $(BINARY) $(OBJECTS) $(TGZ) $(HEADERS) README.options bin2c
clean-deb:
if [ -d debian/$(PACKAGE) ]; then fakeroot debian/rules clean; fi
#
# Install targets
#
install:
@case `uname` in \
Darwin) $(MAKE) ROOT="$(OSXROOT)" install-files install-docs install-root install-osx install-done; ;; \
Haiku) $(MAKE) SBINDIR=/boot/common/bin DOCDIR=/boot/common/share/doc/$(PACKAGE) \
install-files install-docs install-root install-haiku install-done; ;; \
*) $(MAKE) install-files install-docs install-root; ;; \
esac
@if [ -d "$(XINETD)" ]; then $(MAKE) install-xinetd install-done; fi
@if [ -f "$(INETD)" ]; then $(MAKE) install-inetd; fi
.PHONY: install
install-done:
@echo
@echo "======================================================================"
@echo
@echo "Gophernicus has now been succesfully installed. To try it out, launch"
@echo "your favorite gopher browser and navigate to this URL:"
@echo
@echo " gopher://`hostname`/"
@echo
@echo "======================================================================"
@echo
install-files:
mkdir -p $(SBINDIR)
$(INSTALL) -s -m 755 $(BINARY) $(SBINDIR)
@echo
install-docs:
mkdir -p $(DOCDIR)
$(INSTALL) -m 644 $(DOCS) $(DOCDIR)
@echo
install-root:
if [ ! -d "$(ROOT)" ]; then \
mkdir -p $(ROOT); \
$(INSTALL) -m 644 $(MAP) $(ROOT); \
ln -s $(DOCDIR) $(ROOT)/docs; \
fi
@echo
install-inetd:
@echo
@echo "======================================================================"
@echo
@echo "Looks like your system has the traditional internet superserver inetd."
@echo "Automatic installations aren't supported, so please add the following"
@echo "line to the end of your /etc/inetd.conf and restart or kill -HUP the"
@echo "inetd process."
@echo
@echo "gopher stream tcp nowait nobody $(SBINDIR)/$(BINARY) $(BINARY) -h `hostname`"
@echo
@echo "======================================================================"
@echo
install-xinetd:
if [ -d "$(XINETD)" -a ! -f "$(XINETD)/$(NAME)" ]; then \
sed -e "s/@HOSTNAME@/`hostname`/g" $(NAME).xinetd > $(XINETD)/$(NAME); \
[ -x /sbin/service ] && /sbin/service xinetd reload; \
fi
@echo
install-osx:
if [ -d "$(LAUNCHD)" -a ! -f "$(LAUNCHD)/$(PLIST)" ]; then \
sed -e "s/@HOSTNAME@/`hostname`/g" org.gophernicus.server.plist > \
$(LAUNCHD)/$(PLIST); \
launchctl load $(LAUNCHD)/$(PLIST); \
fi
@echo
chown -h root:admin $(ROOT) $(ROOT)/*
chmod -h 0775 $(ROOT) $(ROOT)/docs
@echo
install-haiku:
if [ -f "$(NET_SRV)" -a ! "`grep -m1 gopher $(NET_SRV)`" ]; then \
(echo ""; \
echo "service gopher {"; \
echo " family inet"; \
echo " protocol tcp"; \
echo " port 70"; \
echo " launch in.gophernicus -h `hostname`"; \
echo "}") >> $(NET_SRV); \
fi
@echo
chown user:root $(DOCDIR)/* $(SBINDIR)/$(BINARY) $(ROOT)/$(MAP)
@echo
ps | grep net_server | grep -v grep | awk '{ print $$2 }' | xargs kill
nohup /boot/system/servers/net_server >/dev/null 2>/dev/null &
@echo
#
# Uninstall targets
#
uninstall: uninstall-xinetd uninstall-launchd
rm -f $(SBINDIR)/$(BINARY)
for DOC in $(DOCS); do rm -f $(DOCDIR)/$$DOC; done
rmdir -p $(SBINDIR) $(DOCDIR) 2>/dev/null || true
@echo
uninstall-xinetd:
if grep -q $(BINARY) "$(XINETD)/gopher" 2>/dev/null; then \
rm -f $(XINETD)/gopher; \
[ -x /sbin/service ] && service xinetd reload; \
fi
@echo
uninstall-launchd:
if [ -f $(LAUNCHD)/$(PLIST) ]; then \
launchctl unload $(LAUNCHD)/$(PLIST); \
rm -f $(LAUNCHD)/$(PLIST); \
fi
if [ -L $(ROOT) ]; then \
rm -f $(ROOT); \
fi
@echo
#
# Release targets
#
dist: clean functions.h
mkdir -p /tmp/$(DIST)
tar -cf - ./ | (cd /tmp/$(DIST) && tar -xf -)
(cd /tmp/ && tar -cvf - $(DIST)) | gzip > $(TGZ)
rm -rf /tmp/$(DIST)
release: dist
cp $(TGZ) $(RELDIR)
#
# List all C defines
#
defines:
$(CC) -dM -E $(NAME).c

237
README Normal file
View File

@ -0,0 +1,237 @@
Gophernicus - Copyright (c) 2009-2012 Kim Holviala <kim@holviala.com>
Gophernicus is a modern full-featured (and hopefully) secure gopher
daemon for inetd. It is licensed under the BSD license.
Command line options:
-h hostname Change server hostname (FQDN) [$HOSTNAME]
-p port Change server port [70]
-r root Change gopher root [/var/gopher]
-t type Change default gopher filetype [0]
-g mapfile Change gophermap file [gophermap]
-a tagfile Change gophertag file [gophertag]
-c cgidir Change CGI script directory [/cgi-bin/]
-u userdir Change users personal gopherspace [public_gopher]
-l logfile Log to Apache-compatible combined format logfile
-w width Change default page width [70]
-o charset Change default output charset [US-ASCII]
-s seconds Session timeout in seconds [1800]
-i hits Maximum hits until throttling [4096]
-k kbytes Maximum transfer until throttling [4194304]
-f filterdir Specify directory for output filters
-e ext=type Map file extension to gopher filetype
-R old=new Rewrite the beginning of a selector
-D text|file Set or load server description for caps.txt
-L text|file Set or load server location for caps.txt
-A admin Set admin email for caps.txt
-nv Disable virtual hosting
-nl Disable parent directory links
-nh Disable menu header (title)
-nf Disable menu footer
-nd Disable dates and filesizes in menus
-nc Disable file content detection
-no Disable charset conversion for output
-nq Disable HTTP-style query strings (?query)
-ns Disable logging to syslog
-na Disable autogenerated caps.txt
-nm Disable shared memory use (for debugging)
-nr Disable root user checking (for debugging)
-d Debug to syslog (not for production use)
-b Display the BSD license
-? Display this help
Setting up a gopher site
========================
After succesfully installing Gophernicus (see INSTALL) you need to set
up the gopher root directory. By default Gophernicus serves documents
from /var/gopher so start by creating that directory and making sure
it's world-readable. Then, simply add files and directories under your
root, fire up a gopher browser (Firefox, Lynx) and open up this URL:
gopher://<HOSTNAME>/ (where <HOSTNAME> is your servers hostname)
That's it, your first gopher site is now up and running. If the links
on the root menu don't work, make sure you are using the -h <HOSTNAME>
parameter in your inetd.conf (with a valid resolveable hostname
instead of <HOSTNAME> - see INSTALL).
Security
========
Gophernicus has been written with high security in mind. There should
be no buffer overflows or memory allocation problems so it should be
safe to run a publicly available gopher server with Gophernicus.
However, the security settings (which are non-changeable) are so strict
that you need to keep one thing in mind. Gophernicus will only serve
world-readable content. Being readable by the server process is not
enough, all files and directories MUST be world-readable or they are
simply hidden from all listings and denied if a client asks for them.
Gophermaps
==========
By default all gopher menus are automatically generated from the
content of the directory being viewed. If you want to have
informational text along with the files, or if you want to completely
replace the generated menu with your own you need to take a look at
gophermaps. See the file README.gophermap for more information.
Gophertags
==========
A gophertag file can be used to virtually rename a directory. Let's
assume that you have a directory called "foo" somewhere - it will
be listed as "foo" in all automatically generated menus. Now if you
create a file foo/gophertag and put the text "bar" into it the menus
will show "bar" but the links will still point to "foo". This is
useful for creating descriptive names for directories without
littering the file system with spaces and weird characters.
Personal gopherspaces
=====================
Gophernicus supports users personal gopherspaces. If a user has
world-readable directory called public_gopher/ under his home, a
request for gopher://<HOSTNAME>/1/~user/ will serve documents from
that directory.
Virtual hosting
===============
Gophernicus supports virtual hosting, or serving more than one logical
domain using the same IP address. Since gopher (RFC1436) doesn't
support virtual hosting this requires some clever (but mostly invisble)
hacks.
To enable virtual hosting create one or more directories under your
gopher root which are named after your domain names. The primary vhost
directory (set with the -h <HOSTNAME> option) must exist or virtual
hosting will be disabled. Then simply add content to the hostname
directories and you're up and running.
Almost.
To make gopher clients work properly with virtual hosting, create a
root gophermap for each of your domains and include the "%" type
character to create a list of all available virtual hosts (see
README.gophermap). The generated virtual host links will be created so
that standard gopher clients will find the correct domain even when
they don't specifically tell the server which host they're trying to
reach.
CGI support
===========
Gophernicus supports most parts of the CGI/1.1 standard. Most standard
CGI variables are set, and some non-standard ones are added.
By default all scripts and binaries under any directory called
/cgi-bin/ are executed as CGI scripts (this includes cgi-bin
directories under users personal gopherspaces). Also, if a gophermap
is marked executable it is also processed as an CGI script.
As with regular files, CGI scripts must be world-executable (and
readable) or they will be ignored. Make sure your CGI script is safe
with ANY user input as poorly coded CGI scripts are the number #1
security problem with publicly open *nix servers.
Output filtering and PHP support
================================
In addition to CGI scripts Gophernicus supports output filtering
scripts. By default output filtering is turned off, but you can turn
it on by using the -f <FILTERDIR> option, creating that directory
and creating one or more scripts in there named by either the file
suffix, or by the gopher filetype char.
If a file is to be served out which matches either the file suffix
script, or the filetype script then instead of simply sending the
file to client the output filter script is executed with the
original file as the first parameter and the output of the script
is then sent to client.
For PHP support install the CLI version of the PHP interpreter and
then symlink (or copy) that binary to the directory specified with
-f option using the destination name "php".
$ ln -s /usr/bin/php5-cli /usr/lib/gophernicus/filters/php
After that all files with the php suffix will be "filtered" through
the PHP command line interpreter. In other words, PHP starts working.
And don't use the CGI version of PHP as it outputs HTTP headers the
gopher protocol doesn't have.
Charset support and conversions
===============================
Gophernicus supports three charsets: US-ASCII, ISO-8859-1 and UTF-8.
All textual input is internally upconverted to UTF-8 and then
downconverted to whatever charset the client is asking for. The
conversion is input autosensing which means that you don't have to
specify your filesystem charset, or the charset of your text files -
it's all detected automatically.
With standard gopher clients this is a bit of a problem as your text
files WILL be converted to 7-bit US-ASCII. This means that all 8-bit
charaters WILL BE LOST. This decision was made because no gopher
client that I tested was reliably cabable of decoding anything else
than pure US-ASCII. If you want to disable the conversion use the
"-no" option, or if you'd like to change the default output charset to
something else than US-ASCII just use for example the "-o ISO-8859-1"
option.
Selector rewriting
==================
Selector rewriting lets you rewrite parts of the selector on the fly.
Well, not parts, but really just the start of it. And the rewrite
enging here is nothing like Apache's mod_rewrite as I was too lazy
to integrate any regex libraries... So, all it does is rewrite a
fixed string at the start of the selector to something else. This
will let you move your directories around while making sure that
existing deeplinks still work.
Examples:
-R "/~user=/~luser"
-R "/old-dir=/new-dir"
Session tracking and statistics
===============================
To enable virtual hosting with gopher (RFC1436) clients Gophernicus
tracks users and their session. As a side effect of that session
tracking, Gophernicus has simple throttling controls to keep nasty
users from killing your precious 120MHz PPC 604e server from dying
under the load. The throttling defaults are high enough that normal
human users will never hit the limits, but it's possible (and mostly
preferrable) that a badly behaving crawling agent will be throttled.
The current sessions and other real-time status data can be viewed
by opening the URL gopher://<HOSTNAME>/0/server-status . This status
view has been modeled after the Apache server-status which means
that it's possible to integrate Gophernicus into existing server
monitoring systems. To ease up such integrations, Gophernicus
supports HTTP requests of the server-status page using an URL like
http://<HOSTNAME>:70/server-status?auto

109
README.Gophermap Normal file
View File

@ -0,0 +1,109 @@
!Sample gophermap for Gophernicus
##
## This is a sample gophermap.
##
#
Creating a file called "gophermap" into a directory disables the
normal resource listing and replaces it with the contents of the map
file. You can also have inline gophermaps - files with a ".gophermap"
extension are parsed as gophermaps and displayed in between normal
resources in alphabetical order.
In a gophermap any line that doesn't contain a <TAB> character is
automatically converted to an type "i" gopher resource which are
displayed as plain text in the client. Lines which contain tabs are
intepreted as gopher resource lines which the client will render as
links. The first line of a gophermap should be a !Title line
describing the menu.
Dynamic gophermaps are possible by making the gophermap a script and
marking it as executable. All script output is parsed just like a
static gophermap, for example lines without tabs are converted to "i"
resources. Executable gophermaps are always ran through the default
shell (/bin/sh) so depending on your operating system that's either
slow, or really unbearably slow...
The format of a gophermap resource line is simple:
Xname<TAB>selector<TAB>host<TAB>port
Where:
X is the gopher filetype
name is an explanation of the resource
selector is the path to resource
host:port are the hostname and port number to go to
Type and name are mandatory. If you don't specify a selector, the
name field will be also used as the selector. If you don't specify
host or port the host:port of the current server are used instead.
Also make sure to use ONLY ONE TAB between the fields.
Valid filetypes include:
0 text file
1 directory
3 error message
5 archive file (zip, tar etc)
7 search query
8 telnet session
9 binary file
g GIF image
h HTML file
i info text
I generic image file (other than GIF)
d document file (ps, pdf, doc etc)
s sound file
; video file
c calendar file
M MIME file (mbox, emails etc)
Additional type characters supported by Gophernicus:
# comment - rest of the line is ignored
!title menu title (use on the first line)
-file hide the file from listings
:ext=type change filetype (for this directory only)
~ include a list of users with valid ~/public_gopher
% include a list of available virtual hosts
=mapfile include or execute other gophermap
* stop processing gophermap, include file listing
. stop processing gophermap (default)
Examples of valid resource lines:
1subdir
1Relative internal link subdir
1Absolute internal link /subdir
1External link / gopher.floodgap.com 70
1External relative link (which shouldn't work) subdir/ gopher.domain.dom 70
0Finger-to-gopher link kim holviala.com 79
hLink to a website URL:http://www.google.com/
hLink to a local html page /path/to/file.html
5Link to an tar archive /path/to/archive.tar.gz
9Link to a binary file /path/to/binary
7Search engine query /query
8Telnet session kim holviala.com 79
# Hide a few files from the menu listing generated by *
-hiddenfile.txt
-hiddendir
# Change filetypes for this directory
:png=g
:foo=b
Include links to users own gopherspaces:
~
List all available virtual hosts:
%
Include sub-gophermap:
=LICENSE
Execute script and parse output as subgophermap:
=/usr/bin/uptime
Here we stop processing the gophermap and include the regular menu:
*

10
TODO Normal file
View File

@ -0,0 +1,10 @@
Things I might work on:
* Fix vhost part of README
* Support for config files
* Standalone pre-forked (threaded?) version
* Password-protected resources
* Allow/Deny per IP/host
* Automated feature testing script
* SSL/TLS

67
bin2c.c Executable file
View File

@ -0,0 +1,67 @@
/*
* Convert any file into a C #define
*
* Yes, this would have been a perl one-liner, but I didn't want
* to include compile-time dependency for perl...
*/
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
FILE *fp;
char *source = NULL;
char *name = NULL;
int first = 1;
int zero = 0;
int c;
int i;
/* Parse args */
while ((c = getopt(argc, argv, "n:0")) != -1) {
switch(c) {
case 'n': name = optarg; break;
case '0': zero = 1; break;
}
}
source = argv[optind];
if (!name) name = source;
/* Check args */
if (!source) {
fprintf(stderr, "Usage: %s [-0] [-n <name>] <source>\n", argv[0]);
return 1;
}
/* Try to open the source file */
if ((fp = fopen(source, "r")) == NULL) {
perror("Couldn't open source file");
return 1;
}
/* Convert */
printf("/* Automatically generated from %s */\n\n"
"#define %s { \\\n", source, name);
do {
for (i = 0; i < 16; i++) {
if ((c = fgetc(fp)) == EOF) {
if (zero--) c = '\0';
else break;
}
if (i == 0 && !first) printf(", \\\n");
if (i > 0) printf(", ");
printf("0x%02x", c);
first = 0;
}
} while (c != EOF);
printf("}\n\n");
fclose(fp);
return 0;
}

37
debian/changelog vendored Normal file
View File

@ -0,0 +1,37 @@
gophernicus (1.4) unstable; urgency=low
* New upstream release
-- Kim Holviala <kim@holviala.com> Sun, 02 Dec 2012 09:54:55 +0200
gophernicus (1.3) unstable; urgency=low
* New upstream release
* Fixed one off-by-one
-- Kim Holviala <kim@holviala.com> Tue, 12 Jun 2012 13:07:46 +0300
gophernicus (1.2) unstable; urgency=low
* New upstream release
-- Kim Holviala <kim@holviala.com> Fri, 04 May 2012 22:05:01 +0300
gophernicus (1.1) unstable; urgency=low
* New upstream release
* Security update, so upgrading is recommended
-- Kim Holviala <kim@holviala.com> Sat, 16 Oct 2010 08:54:13 +0300
gophernicus (1.0) unstable; urgency=low
* Initial debianized release
* File logging by default, installs logrotate config
* Pre-configures php support but only suggests php5-cli
* Create a default gopher root
* Installs an @reboot cron entry to snoop system type
using a dmidecode script (which must be run as root)
-- Kim Holviala <kim@holviala.com> Mon, 27 Sep 2010 08:11:12 +0300

1
debian/compat vendored Normal file
View File

@ -0,0 +1 @@
5

16
debian/control vendored Normal file
View File

@ -0,0 +1,16 @@
Source: gophernicus
Section: net
Priority: extra
Maintainer: Kim Holviala <kim@holviala.com>
Build-Depends: debhelper (>= 5)
Standards-Version: 3.7.3
Homepage: gopher://gophernicus.org/1/software/gophernicus/server/
Package: gophernicus
Architecture: any
Depends: ${shlibs:Depends}, debconf, netbase, openbsd-inetd | inet-superserver
Recommends: lsb-release
Suggests: lynx, php5-cli
Description: Modern full-featured gopher server for inetd
Gophernicus is a modern full-featured (and hopefully) secure
gopher daemon for inetd.

22
debian/copyright vendored Normal file
View File

@ -0,0 +1,22 @@
Gophernicus - Copyright (c) 2009-2010 Kim Holviala <kim@holviala.com>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

4
debian/dirs vendored Normal file
View File

@ -0,0 +1,4 @@
usr/sbin
var/gopher
var/log/gophernicus
usr/lib/gophernicus/filters

5
debian/docs vendored Normal file
View File

@ -0,0 +1,5 @@
README
README.Gophermap
TODO
INSTALL
gophertag

14
debian/gophernicus.config vendored Executable file
View File

@ -0,0 +1,14 @@
#!/bin/sh
# Source debconf library
. /usr/share/debconf/confmodule
# Ask for a hostname
db_input high gophernicus/fqdn || true
db_go
# Clear old config on reconfigure
if [ "$1" = "reconfigure" ]; then
update-inetd --remove "#<off># gopher"
fi

View File

@ -0,0 +1,3 @@
^\w{3} [ :0-9]{11} [._[:alnum:]-]+ in.gophernicus\[[0-9]+\]: request for "[^"]*" from [._[:alnum:]:-]+$
^\w{3} [ :0-9]{11} [._[:alnum:]-]+ in.gophernicus\[[0-9]+\]: error "No such file or directory" for request "[^"]*" from [._[:alnum:]:-]+$
^\w{3} [ :0-9]{11} [._[:alnum:]-]+ in.gophernicus\[[0-9]+\]: error "User not found" for request "/~[^"]*" from [._[:alnum:]:-]+$

7
debian/gophernicus.logrotate vendored Normal file
View File

@ -0,0 +1,7 @@
/var/log/gophernicus/server.log {
rotate 6
weekly
compress
missingok
notifempty
}

4
debian/gophernicus.templates vendored Normal file
View File

@ -0,0 +1,4 @@
Template: gophernicus/fqdn
Type: string
Default:
Description: Fully-qualified hostname for the gopher server:

37
debian/postinst vendored Normal file
View File

@ -0,0 +1,37 @@
#!/bin/sh
#DEBHELPER#
# Source debconf library.
. /usr/share/debconf/confmodule
# Configuration
HOSTNAME="`hostname`"
ROOT=/var/gopher
PACKAGE=gophernicus
LOGDIR=/var/log/$PACKAGE
LIBDIR=/usr/lib/$PACKAGE
BINARY=in.gophernicus
USER=nobody
# Get hostname from debconf
db_get gophernicus/fqdn
if [ "$RET" ]; then
HOSTNAME="`echo $RET | tr -cd 'A-Za-z0-9.-'`"
fi
# Generate inetd service
SERVICE="gopher\t\tstream\ttcp\tnowait\t$USER\t/usr/sbin/$BINARY\t$BINARY -h $HOSTNAME -l $LOGDIR/server.log -f /usr/lib/$PACKAGE/filters"
# Configure gophernicus
if [ "$1" = "configure" ]; then
if [ ! -f "$ROOT/gophermap" ]; then
cp $LIBDIR/gophermap $ROOT/
fi
chown $USER.adm $LOGDIR
chmod 0750 $LOGDIR
update-inetd --add "$SERVICE"
fi

8
debian/postrm vendored Normal file
View File

@ -0,0 +1,8 @@
#!/bin/sh
if [ "$1" = "purge" ]; then
update-inetd --remove "#<off># gopher"
fi
#DEBHELPER#

6
debian/prerm vendored Normal file
View File

@ -0,0 +1,6 @@
#!/bin/sh
update-inetd --disable gopher
#DEBHELPER#

85
debian/rules vendored Executable file
View File

@ -0,0 +1,85 @@
#!/usr/bin/make -f
# -*- makefile -*-
# Gophernicus debian/rules that uses debhelper.
# This file was originally written by Joey Hess and Craig Small.
# As a special exception, when this file is copied by dh-make into a
# dh-make output file, you may use that output file without restriction.
# This special exception was added by Craig Small in version 0.37 of dh-make.
# Uncomment this to turn on verbose mode.
#export DH_VERBOSE=1
# Installation directory
DEST=$(CURDIR)/debian/gophernicus
configure:
build: build-stamp
build-stamp:
dh_testdir
$(MAKE)
touch $@
clean:
dh_testdir
dh_testroot
rm -f build-stamp
$(MAKE) clean-build
dh_clean
install: build
dh_testdir
dh_testroot
dh_clean -k
dh_installdirs
# Add here commands to install the package into debian/gophernicus
$(MAKE) DESTDIR=$(DEST)/usr install-files
rm -rf $(DEST)/tmp
ln -s /usr/share/doc/gophernicus/ $(DEST)/var/gopher/docs
ln -s /usr/bin/php5 $(DEST)/usr/lib/gophernicus/filters/php
cp gophermap $(DEST)/usr/lib/gophernicus/
# Build architecture-independent files here.
binary-indep: build install
# We have nothing to do by default.
# Build architecture-dependent files here.
binary-arch: build install
dh_testdir
dh_testroot
dh_installchangelogs ChangeLog
dh_installdocs
# dh_installexamples
# dh_install
# dh_installmenu
dh_installdebconf
dh_installlogrotate
dh_installlogcheck
# dh_installemacsen
# dh_installpam
# dh_installmime
# dh_python
# dh_installinit
# dh_installcron
# dh_installinfo
# dh_installman
dh_link
dh_strip
dh_compress
dh_fixperms
# dh_perl
# dh_makeshlibs
dh_installdeb
dh_shlibdeps
dh_gencontrol
dh_md5sums
dh_builddeb
binary: binary-indep binary-arch
.PHONY: build clean binary-indep binary-arch binary install configure

BIN
error.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 560 B

25
examples/counter/counter.sh Executable file
View File

@ -0,0 +1,25 @@
#!/bin/sh
##
## A simple visitor counter to use with gophermaps
##
## Usage: counter.sh <pre message> <post message>
##
# Figure out a safe file to keep our counter
HASH=`echo "$SELECTOR" | md5sum | cut -d" " -f1`
FILE=/var/tmp/gopher-counter-$HASH
# Get count and the previous visitors IP address
COUNT="`cut -d' ' -f1 $FILE`"
OLD_ADDR="`cut -d' ' -f2 $FILE`"
# Increase counter only if the user is new
if [ "$OLD_ADDR" != "$REMOTE_ADDR" ]; then
COUNT=$(( COUNT + 1 ))
echo "$COUNT $REMOTE_ADDR" > $FILE
fi
# Output counter message
echo "$1$COUNT$2"

402
file.c Normal file
View File

@ -0,0 +1,402 @@
/*
* Gophernicus - Copyright (c) 2009-2012 Kim Holviala <kim@holviala.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "gophernicus.h"
/*
* Send a binary file to the client
*/
void send_binary_file(state *st)
{
/* Faster sendfile() version */
#ifdef HAVE_SENDFILE
int fd;
off_t offset = 0;
if (st->debug) syslog(LOG_INFO, "outputting binary file \"%s\"", st->req_realpath);
if ((fd = open(st->req_realpath, O_RDONLY)) == ERROR) return;
sendfile(1, fd, &offset, st->req_filesize);
close(fd);
/* More compatible POSIX fread()/fwrite() version */
#else
FILE *fp;
char buf[BUFSIZE];
int bytes;
if (st->debug) syslog(LOG_INFO, "outputting binary file \"%s\"", st->req_realpath);
if ((fp = fopen(st->req_realpath , "r")) == NULL) return;
while ((bytes = fread(buf, 1, sizeof(buf), fp)) > 0)
fwrite(buf, bytes, 1, stdout);
fclose(fp);
#endif
}
/*
* Send a text file to the client
*/
void send_text_file(state *st)
{
FILE *fp;
char in[BUFSIZE];
char out[BUFSIZE];
int line;
if (st->debug) syslog(LOG_INFO, "outputting text file \"%s\"", st->req_realpath);
if ((fp = fopen(st->req_realpath , "r")) == NULL) return;
/* Loop through the file line by line */
line = 0;
while (fgets(in, sizeof(in), fp)) {
/* Covert to output charset & print */
if (st->opt_iconv) sstrniconv(st->out_charset, out, in);
else sstrlcpy(out, in);
chomp(out);
#ifdef ENABLE_STRICT_RFC1436
if (strcmp(out, ".") == MATCH) printf(".." CRLF);
else
#endif
printf("%s" CRLF, out);
line++;
}
#ifdef ENABLE_STRICT_RFC1436
printf("." CRLF);
#endif
fclose(fp);
}
/*
* Print hURL redirect page
*/
void url_redirect(state *st)
{
char dest[BUFSIZE];
char *c;
/* Basic security checking */
sstrlcpy(dest, st->req_selector + 4);
if (sstrncmp(dest, "http://") != MATCH &&
sstrncmp(dest, "ftp://") != MATCH &&
sstrncmp(dest, "mailto:") != MATCH)
die(st, ERR_ACCESS, "Refusing to HTTP redirect unsafe protocols");
if ((c = strchr(dest, '"'))) *c = '\0';
if ((c = strchr(dest, '?'))) *c = '\0';
/* Log the redirect */
if (st->opt_syslog) {
syslog(LOG_INFO, "request for \"gopher://%s:%i/h%s\" from %s",
st->server_host,
st->server_port,
st->req_selector,
st->req_remote_addr);
}
log_combined(st, HTTP_OK);
/* Output HTML */
printf("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n"
"<HTML>\n<HEAD>\n"
" <META HTTP-EQUIV=\"Refresh\" content=\"1;URL=%1$s\">\n"
" <META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html;charset=iso-8859-1\">\n"
" <TITLE>URL Redirect page</TITLE>\n"
"</HEAD>\n<BODY>\n"
"<STRONG>Redirecting to <A HREF=\"%1$s\">%1$s</A></STRONG>\n"
"<PRE>\n", dest);
footer(st);
printf("</PRE>\n</BODY>\n</HTML>\n");
}
/*
* Handle /server-status
*/
#ifdef HAVE_SHMEM
void server_status(state *st, shm_state *shm, int shmid)
{
struct shmid_ds shm_ds;
time_t now;
time_t uptime;
int sessions;
int i;
/* Log the request */
if (st->opt_syslog) {
syslog(LOG_INFO, "request for \"gopher://%s:%i/0" SERVER_STATUS "\" from %s",
st->server_host,
st->server_port,
st->req_remote_addr);
}
log_combined(st, HTTP_OK);
/* Quit if shared memory isn't initialized yet */
if (!shm) return;
/* Update counters */
shm->hits++;
shm->kbytes += 1;
/* Get server uptime */
now = time(NULL);
uptime = (now - shm->start_time) + 1;
/* Get shared memory info */
shmctl(shmid, IPC_STAT, &shm_ds);
/* Print statistics */
printf("Total Accesses: %li" CRLF
"Total kBytes: %li" CRLF
"Uptime: %i" CRLF
"ReqPerSec: %.3f" CRLF
"BytesPerSec: %li" CRLF
"BytesPerReq: %li" CRLF
"BusyServers: %i" CRLF
"IdleServers: 0" CRLF
"CPULoad: %.2f" CRLF,
shm->hits,
shm->kbytes,
(int) uptime,
(float) shm->hits / (float) uptime,
shm->kbytes * 1024 / (int) uptime,
shm->kbytes * 1024 / (shm->hits + 1),
(int) shm_ds.shm_nattch,
loadavg());
/* Print active sessions */
sessions = 0;
for (i = 0; i < SHM_SESSIONS; i++) {
if ((now - shm->session[i].req_atime) < st->session_timeout) {
sessions++;
printf("Session: %-4i %-40s %-4li %-7li gopher://%s:%i/%c%s" CRLF,
(int) (now - shm->session[i].req_atime),
shm->session[i].req_remote_addr,
shm->session[i].hits,
shm->session[i].kbytes,
shm->session[i].server_host,
shm->session[i].server_port,
shm->session[i].req_filetype,
shm->session[i].req_selector);
}
}
printf("Total Sessions: %i" CRLF, sessions);
}
#endif
/*
* Handle /caps.txt
*/
void caps_txt(state *st, shm_state *shm)
{
/* Log the request */
if (st->opt_syslog) {
syslog(LOG_INFO, "request for \"gopher://%s:%i/0" CAPS_TXT "\" from %s",
st->server_host,
st->server_port,
st->req_remote_addr);
}
log_combined(st, HTTP_OK);
/* Update counters */
#ifdef HAVE_SHMEM
if (shm) {
shm->hits++;
shm->kbytes += 1;
/* Update session data */
st->req_filesize += 1024;
update_shm_session(st, shm);
}
#endif
/* Standard caps.txt stuff */
printf("CAPS" CRLF
CRLF
"##" CRLF
"## This is an automatically generated caps file." CRLF
"##" CRLF
CRLF
"CapsVersion=1" CRLF
"ExpireCapsAfter=%i" CRLF
CRLF
"PathDelimeter=/" CRLF
"PathIdentity=." CRLF
"PathParent=.." CRLF
"PathParentDouble=FALSE" CRLF
"PathKeepPreDelimeter=FALSE" CRLF
CRLF
"ServerSoftware=" SERVER_SOFTWARE CRLF
"ServerSoftwareVersion=" VERSION CRLF
"ServerArchitecture=%s" CRLF,
st->session_timeout,
st->server_platform);
/* Optional keys */
if (*st->server_description)
printf("ServerDescription=%s" CRLF, st->server_description);
if (*st->server_location)
printf("ServerGeolocationString=%s" CRLF, st->server_location);
if (*st->server_admin)
printf("ServerAdmin=%s" CRLF, st->server_admin);
}
/*
* Setup environment variables as per the CGI spec
*/
void setenv_cgi(state *st, char *script)
{
char buf[BUFSIZE];
/* Security */
setenv("PATH", SAFE_PATH, 1);
/* Set up the environment as per CGI spec */
setenv("GATEWAY_INTERFACE", "CGI/1.1", 1);
setenv("CONTENT_LENGTH", "0", 1);
setenv("QUERY_STRING", st->req_query_string, 1);
snprintf(buf, sizeof(buf), SERVER_SOFTWARE_FULL, st->server_platform);
setenv("SERVER_SOFTWARE", buf, 1);
setenv("SERVER_ARCH", st->server_platform, 1);
setenv("SERVER_DESCRIPTION", st->server_description, 1);
snprintf(buf, sizeof(buf), SERVER_SOFTWARE "/" VERSION);
setenv("SERVER_VERSION", buf, 1);
if (st->req_protocol == PROTO_HTTP)
setenv("SERVER_PROTOCOL", "HTTP/0.9", 1);
else
setenv("SERVER_PROTOCOL", "RFC1436", 1);
setenv("SERVER_NAME", st->server_host, 1);
snprintf(buf, sizeof(buf), "%i", st->server_port);
setenv("SERVER_PORT", buf, 1);
setenv("REQUEST_METHOD", "GET", 1);
setenv("DOCUMENT_ROOT", st->server_root, 1);
setenv("SCRIPT_NAME", st->req_selector, 1);
setenv("SCRIPT_FILENAME", script, 1);
setenv("LOCAL_ADDR", st->req_local_addr, 1);
setenv("REMOTE_ADDR", st->req_remote_addr, 1);
setenv("HTTP_REFERER", st->req_referrer, 1);
setenv("HTTP_ACCEPT_CHARSET", strcharset(st->out_charset), 1);
/* Gophernicus extras */
snprintf(buf, sizeof(buf), "%c", st->req_filetype);
setenv("GOPHER_FILETYPE", buf, 1);
setenv("GOPHER_CHARSET", strcharset(st->out_charset), 1);
setenv("GOPHER_REFERER", st->req_referrer, 1);
snprintf(buf, sizeof(buf), "%i", st->out_width);
setenv("COLUMNS", buf, 1);
/* Bucktooth extras */
if (*st->req_query_string) {
snprintf(buf, sizeof(buf), "%s?%s",
st->req_selector, st->req_query_string);
setenv("SELECTOR", buf, 1);
}
else setenv("SELECTOR", st->req_selector, 1);
setenv("SERVER_HOST", st->server_host, 1);
setenv("REQUEST", st->req_selector, 1);
setenv("SEARCHREQUEST", st->req_query_string, 1);
}
/*
* Execute a CGI script
*/
void run_cgi(state *st, char *script, char *arg)
{
/* Setup environment & execute the binary */
if (st->debug) syslog(LOG_INFO, "executing script \"%s\"", script);
setenv_cgi(st, script);
execl(script, script, arg, NULL);
/* Didn't work - die */
die(st, ERR_ACCESS, NULL);
}
/*
* Handle file selectors
*/
void gopher_file(state *st)
{
struct stat file;
char buf[BUFSIZE];
char *c;
/* Refuse to serve out gophermaps/tags */
if ((c = strrchr(st->req_realpath, '/'))) c++;
else c = st->req_realpath;
if (strcmp(c, st->map_file) == MATCH)
die(st, ERR_ACCESS, "Refusing to serve out a gophermap file");
if (strcmp(c, st->tag_file) == MATCH)
die(st, ERR_ACCESS, "Refusing to serve out a gophertag file");
/* Check for & run CGI and query scripts */
if (strstr(st->req_realpath, st->cgi_file) || st->req_filetype == TYPE_QUERY)
run_cgi(st, st->req_realpath, NULL);
/* Check for a file suffix filter */
if (*st->filter_dir && (c = strrchr(st->req_realpath, '.'))) {
snprintf(buf, sizeof(buf), "%s/%s", st->filter_dir, c + 1);
/* Filter file through the script */
if (stat(buf, &file) == OK && (file.st_mode & S_IXOTH))
run_cgi(st, buf, st->req_realpath);
}
/* Check for a filetype filter */
if (*st->filter_dir) {
snprintf(buf, sizeof(buf), "%s/%c", st->filter_dir, st->req_filetype);
/* Filter file through the script */
if (stat(buf, &file) == OK && (file.st_mode & S_IXOTH))
run_cgi(st, buf, st->req_realpath);
}
/* Output regular files */
if (st->req_filetype == TYPE_TEXT || st->req_filetype == TYPE_MIME)
send_text_file(st);
else
send_binary_file(st);
}

36
gophermap Normal file
View File

@ -0,0 +1,36 @@
!Welcome to Gophernicus!
#
# $ figlet -f chunky Gophernicus
#
_______ __ __
| __|.-----.-----.| |--.-----.----.-----.|__|.----.--.--.-----.
| | || _ | _ || | -__| _| || || __| | |__ --|
|_______||_____| __||__|__|_____|__| |__|__||__||____|_____|_____|
|__|
#
# Shamelessly lifted from Apache 1.3...
#
If you can see this, it means that the installation of Gophernicus
on this system was successful. You may now add content to this
directory and replace this page.
#
# Real-time configuration output (WOO!)
#
Generic information:
=echo " current time...: `date`"
=echo " your ip address: $REMOTE_ADDR"
=echo " server uptime..: `uptime | sed 's/.*up \([^,]*\), .*/\1/' `"
=echo " server version.: $SERVER_VERSION"
=echo " server platform: $SERVER_ARCH"
=echo " description....: $SERVER_DESCRIPTION"
Server configuration:
=echo " config file....: `for FILE in /Library/LaunchDaemons/org.gophernicus.server.plist /boot/common/settings/network/services /etc/xinetd.d/gophernicus /etc/inetd.conf; do if [ -f $FILE ]; then echo $FILE; break; fi; done`"
=echo " server hostname: $SERVER_HOST"
=echo " root directory.: $DOCUMENT_ROOT"
=echo " running as user: `whoami`"
=echo " output charset.: $GOPHER_CHARSET"
=echo " output width...: $COLUMNS characters"
*

735
gophernicus.c Normal file
View File

@ -0,0 +1,735 @@
/*
* Gophernicus - Copyright (c) 2009-2012 Kim Holviala <kim@holviala.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "gophernicus.h"
/*
* Print gopher menu line
*/
void info(state *st, char *str, char type)
{
char buf[BUFSIZE];
char selector[16];
/* Convert string to output charset */
if (st->opt_iconv) sstrniconv(st->out_charset, buf, str);
else sstrlcpy(buf, str);
/* Handle gopher title resources */
strclear(selector);
if (type == TYPE_TITLE) {
sstrlcpy(selector, "TITLE");
type = TYPE_INFO;
}
/* Output info line */
strcut(buf, st->out_width);
printf("%c%s\t%s\t%s" CRLF,
type, buf, selector, DUMMY_HOST);
}
/*
* Print footer
*/
void footer(state *st)
{
char line[BUFSIZE];
char buf[BUFSIZE];
char msg[BUFSIZE];
if (!st->opt_footer) {
#ifndef ENABLE_STRICT_RFC1436
if (st->req_filetype == TYPE_MENU || st->req_filetype == TYPE_QUERY)
#endif
printf("." CRLF);
return;
}
/* Create horizontal line */
strrepeat(line, '_', st->out_width);
/* Create right-aligned footer message */
snprintf(buf, sizeof(buf), FOOTER_FORMAT, st->server_platform);
snprintf(msg, sizeof(msg), "%*s", st->out_width - 1, buf);
/* Menu footer? */
if (st->req_filetype == TYPE_MENU || st->req_filetype == TYPE_QUERY) {
info(st, line, TYPE_INFO);
info(st, msg, TYPE_INFO);
printf("." CRLF);
}
/* Plain text footer */
else {
printf("%s" CRLF, line);
printf("%s" CRLF, msg);
#ifdef ENABLE_STRICT_RFC1436
printf("." CRLF);
#endif
}
}
/*
* Print error message & exit
*/
void die(state *st, char *message, char *description)
{
int en = errno;
static const char error_gif[] = ERROR_GIF;
/* Handle NULL description */
if (description == NULL) description = strerror(en);
/* Log the error */
if (st->opt_syslog) {
syslog(LOG_ERR, "error \"%s\" for request \"%s\" from %s",
description, st->req_selector, st->req_remote_addr);
}
log_combined(st, HTTP_404);
/* Handle menu errors */
if (st->req_filetype == TYPE_MENU || st->req_filetype == TYPE_QUERY) {
printf("3" ERROR_PREFIX "%s\tTITLE\t" DUMMY_HOST CRLF, message);
footer(st);
}
/* Handle image errors */
else if (st->req_filetype == TYPE_GIF || st->req_filetype == TYPE_IMAGE) {
fwrite(error_gif, sizeof(error_gif), 1, stdout);
}
/* Handle HTML errors */
else if (st->req_filetype == TYPE_HTML) {
printf("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n"
"<HTML>\n<HEAD>\n"
" <META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html;charset=iso-8859-1\">\n"
" <TITLE>" ERROR_PREFIX "%1$s</TITLE>\n"
"</HEAD>\n<BODY>\n"
"<STRONG>" ERROR_PREFIX "%1$s</STRONG>\n"
"<PRE>\n", message);
footer(st);
printf("</PRE>\n</BODY>\n</HTML>\n");
}
/* Use plain text error for other filetypes */
else {
printf(ERROR_PREFIX "%s" CRLF, message);
footer(st);
}
/* Quit */
exit(EXIT_FAILURE);
}
/*
* Apache-compatible combined logging
*/
void log_combined(state *st, int status)
{
FILE *fp;
struct tm *ltime;
char timestr[64];
time_t now;
/* Try to open the logfile for appending */
if (!*st->log_file) return;
if ((fp = fopen(st->log_file , "a")) == NULL) return;
/* Format time */
now = time(NULL);
ltime = localtime(&now);
strftime(timestr, sizeof(timestr), HTTP_DATE, ltime);
/* Generate log entry */
fprintf(fp, "%s %s:%i - [%s] \"GET %c%s HTTP/1.0\" %i %li \"%s\" \"" HTTP_USERAGENT "\"\n",
st->req_remote_addr,
st->server_host,
st->server_port,
timestr,
st->req_filetype,
st->req_selector,
status,
(long) st->req_filesize,
st->req_referrer);
fclose(fp);
}
/*
* Convert gopher selector to an absolute path
*/
void selector_to_path(state *st)
{
DIR *dp;
struct dirent *dir;
struct stat file;
#ifdef HAVE_PASSWD
struct passwd *pwd;
char *path = EMPTY;
char *c;
#endif
char buf[BUFSIZE];
int i;
/* Handle selector rewriting */
for (i = 0; i < st->rewrite_count; i++) {
/* Match found? */
if (strstr(st->req_selector, st->rewrite[i].match) == st->req_selector) {
/* Replace match with a new string */
snprintf(buf, sizeof(buf), "%s%s",
st->rewrite[i].replace,
st->req_selector + strlen(st->rewrite[i].match));
if (st->debug) {
syslog(LOG_INFO, "rewriting selector \"%s\" -> \"%s\"",
st->req_selector, buf);
}
sstrlcpy(st->req_selector, buf);
}
}
#ifdef HAVE_PASSWD
/* Virtual userdir (~user -> /home/user/public_gopher)? */
if (*(st->user_dir) && sstrncmp(st->req_selector, "/~") == MATCH) {
/* Parse userdir login name & path */;
sstrlcpy(buf, st->req_selector + 2);
if ((c = strchr(buf, '/'))) {
*c = '\0';
path = c + 1;
}
/* Check user validity */
if ((pwd = getpwnam(buf)) == NULL)
die(st, ERR_NOTFOUND, "User not found");
if (pwd->pw_uid < PASSWD_MIN_UID)
die(st, ERR_NOTFOUND, "User found but UID too low");
/* Generate absolute path to users own gopher root */
snprintf(st->req_realpath, sizeof(st->req_realpath),
"%s/%s/%s", pwd->pw_dir, st->user_dir, path);
/* Check ~public_gopher access rights */
if (stat(st->req_realpath, &file) == ERROR)
die(st, ERR_NOTFOUND, NULL);
if ((file.st_mode & S_IROTH) == 0)
die(st, ERR_ACCESS, "~/public_gopher not world-readable");
if (file.st_uid != pwd->pw_uid)
die(st, ERR_ACCESS, "~/ and ~/public_gopher owned by different users");
/* Userdirs always come from the default vhost */
if (st->opt_vhost)
sstrlcpy(st->server_host, st->server_host_default);
return;
}
#endif
/* Virtual hosting */
if (st->opt_vhost) {
/* Try looking for the selector from the current vhost */
snprintf(st->req_realpath, sizeof(st->req_realpath), "%s/%s%s",
st->server_root, st->server_host, st->req_selector);
if (stat(st->req_realpath, &file) == OK) return;
/* Loop through all vhosts looking for the selector */
if ((dp = opendir(st->server_root)) == NULL) die(st, ERR_NOTFOUND, NULL);
while ((dir = readdir(dp))) {
/* Skip .hidden dirs and . & .. */
if (dir->d_name[0] == '.') continue;
/* Special case - skip lost+found (don't ask) */
if (sstrncmp(dir->d_name, "lost+found") == MATCH) continue;
/* Generate path to the found vhost */
snprintf(st->req_realpath, sizeof(st->req_realpath), "%s/%s%s",
st->server_root, dir->d_name, st->req_selector);
/* Did we find the selector under this vhost? */
if (stat(st->req_realpath, &file) == OK) {
/* Virtual host found - update state & return */
sstrlcpy(st->server_host, dir->d_name);
return;
}
}
closedir(dp);
}
/* Handle normal selectors */
snprintf(st->req_realpath, sizeof(st->req_realpath),
"%s%s", st->server_root, st->req_selector);
}
/*
* Get local IP address
*/
char *get_local_address(void)
{
#ifdef HAVE_IPv4
struct sockaddr_in addr;
socklen_t addrsize = sizeof(addr);
#endif
#ifdef HAVE_IPv6
struct sockaddr_in6 addr6;
socklen_t addr6size = sizeof(addr6);
static char address[INET6_ADDRSTRLEN];
#endif
char *c;
/* Try IPv4 first */
#ifdef HAVE_IPv4
if (getsockname(0, (struct sockaddr *) &addr, &addrsize) == OK) {
c = inet_ntoa(addr.sin_addr);
if (strlen(c) > 0 && *c != '0') return c;
}
#endif
/* IPv4 didn't work - try IPv6 */
#ifdef HAVE_IPv6
if (getsockname(0, (struct sockaddr *) &addr6, &addr6size) == OK) {
if (inet_ntop(AF_INET6, &addr6.sin6_addr, address, sizeof(address))) {
/* Strip ::ffff: IPv4-in-IPv6 prefix */
if (sstrncmp(address, "::ffff:") == MATCH) return (address + 7);
else return address;
}
}
#endif
/* Nothing works... I'm out of ideas */
return DEFAULT_ADDR;
}
/*
* Get remote peer IP address
*/
char *get_peer_address(void)
{
#ifdef HAVE_IPv4
struct sockaddr_in addr;
socklen_t addrsize = sizeof(addr);
#endif
#ifdef HAVE_IPv6
struct sockaddr_in6 addr6;
socklen_t addr6size = sizeof(addr6);
static char address[INET6_ADDRSTRLEN];
#endif
char *c;
/* Are we a CGI script? */
if ((c = getenv("REMOTE_ADDR"))) return c;
/* if ((c = getenv("REMOTE_HOST"))) return c; */
/* Try IPv4 first */
#ifdef HAVE_IPv4
if (getpeername(0, (struct sockaddr *) &addr, &addrsize) == OK) {
c = inet_ntoa(addr.sin_addr);
if (strlen(c) > 0 && *c != '0') return c;
}
#endif
/* IPv4 didn't work - try IPv6 */
#ifdef HAVE_IPv6
if (getpeername(0, (struct sockaddr *) &addr6, &addr6size) == OK) {
if (inet_ntop(AF_INET6, &addr6.sin6_addr, address, sizeof(address))) {
/* Strip ::ffff: IPv4-in-IPv6 prefix */
if (sstrncmp(address, "::ffff:") == MATCH) return (address + 7);
else return address;
}
}
#endif
/* Nothing works... I'm out of ideas */
return DEFAULT_ADDR;
}
/*
* Initialize state struct to default/empty values
*/
void init_state(state *st)
{
static const char *filetypes[] = { FILETYPES };
char buf[BUFSIZE];
char *c;
int i;
/* Request */
strclear(st->req_selector);
strclear(st->req_realpath);
strclear(st->req_query_string);
strclear(st->req_referrer);
sstrlcpy(st->req_local_addr, get_local_address());
sstrlcpy(st->req_remote_addr, get_peer_address());
/* strclear(st->req_remote_host); */
st->req_filetype = DEFAULT_TYPE;
st->req_protocol = PROTO_GOPHER;
st->req_filesize = 0;
/* Output */
st->out_width = DEFAULT_WIDTH;
st->out_charset = DEFAULT_CHARSET;
/* Settings */
sstrlcpy(st->server_root, DEFAULT_ROOT);
sstrlcpy(st->server_host_default, DEFAULT_HOST);
if ((c = getenv("HOSTNAME")))
sstrlcpy(st->server_host, c);
else if ((gethostname(buf, sizeof(buf))) != ERROR)
sstrlcpy(st->server_host, buf);
st->server_port = DEFAULT_PORT;
st->default_filetype = DEFAULT_TYPE;
sstrlcpy(st->map_file, DEFAULT_MAP);
sstrlcpy(st->tag_file, DEFAULT_TAG);
sstrlcpy(st->cgi_file, DEFAULT_CGI);
sstrlcpy(st->user_dir, DEFAULT_USERDIR);
strclear(st->log_file);
st->hidden_count = 0;
st->filetype_count = 0;
strclear(st->filter_dir);
st->rewrite_count = 0;
strclear(st->server_description);
strclear(st->server_location);
strclear(st->server_platform);
strclear(st->server_admin);
/* Session */
st->session_timeout = DEFAULT_SESSION_TIMEOUT;
st->session_max_kbytes = DEFAULT_SESSION_MAX_KBYTES;
st->session_max_hits = DEFAULT_SESSION_MAX_HITS;
/* Feature options */
st->opt_vhost = TRUE;
st->opt_parent = TRUE;
st->opt_header = TRUE;
st->opt_footer = TRUE;
st->opt_date = TRUE;
st->opt_syslog = TRUE;
st->opt_magic = TRUE;
st->opt_iconv = TRUE;
st->opt_query = TRUE;
st->opt_caps = TRUE;
st->opt_shm = TRUE;
st->opt_root = TRUE;
st->debug = FALSE;
/* Load default suffix -> filetype mappings */
for (i = 0; filetypes[i]; i += 2) {
if (st->filetype_count < MAX_FILETYPES) {
sstrlcpy(st->filetype[st->filetype_count].suffix, filetypes[i]);
st->filetype[st->filetype_count].type = *filetypes[i + 1];
st->filetype_count++;
}
}
}
/*
* Main
*/
int main(int argc, char *argv[])
{
struct stat file;
state st;
char self[64];
char selector[BUFSIZE];
char buf[BUFSIZE];
char *dest;
char *c;
#ifdef HAVE_SHMEM
struct shmid_ds shm_ds;
shm_state *shm;
int shmid;
#endif
/* Get the name of this binary */
if ((c = strrchr(argv[0], '/'))) sstrlcpy(self, c + 1);
else sstrlcpy(self, argv[0]);
/* Initialize state */
#ifdef HAVE_LOCALES
setlocale(LC_TIME, DATE_LOCALE);
#endif
init_state(&st);
/* Handle command line arguments */
parse_args(&st, argc, argv);
/* Open syslog() */
if (st.opt_syslog) openlog(self, LOG_PID, LOG_DAEMON);
/* Make sure the computer is turned on */
#ifdef __HAIKU__
if (is_computer_on() != TRUE)
die(&st, ERR_ACCESS, "Please turn on the computer first");
#endif
/* Refuse to run as root */
#ifdef HAVE_PASSWD
if (st.opt_root && getuid() == 0)
die(&st, ERR_ACCESS, "Refusing to run as root");
#endif
/* Try to get shared memory */
#ifdef HAVE_SHMEM
if ((shmid = shmget(SHM_KEY, sizeof(shm_state), IPC_CREAT | SHM_MODE)) == ERROR) {
/* Getting memory failed -> delete the old allocation */
shmctl(shmid, IPC_RMID, &shm_ds);
shm = NULL;
}
else {
/* Map shared memory */
if ((shm = (shm_state *) shmat(shmid, (void *) 0, 0)) == (void *) ERROR)
shm = NULL;
/* Initialize mapped shared memory */
if (shm && shm->start_time == 0) {
shm->start_time = time(NULL);
/* Keep server platform & description in shm */
platform(&st);
sstrlcpy(shm->server_platform, st.server_platform);
sstrlcpy(shm->server_description, st.server_description);
}
}
/* For debugging shared memory issues */
if (!st.opt_shm) shm = NULL;
/* Get server platform and description */
if (shm) {
sstrlcpy(st.server_platform, shm->server_platform);
if (!*st.server_description)
sstrlcpy(st.server_description, shm->server_description);
}
else
#endif
platform(&st);
/* Read selector, remove CRLF & encodings */
if (fgets(selector, sizeof(selector) - 1, stdin) == NULL)
selector[0] = '\0';
chomp(selector);
strndecode(selector, selector, sizeof(selector));
if (st.debug) syslog(LOG_INFO, "client sent us \"%s\"", selector);
/* Handle hURL: redirect page */
if (sstrncmp(selector, "URL:") == MATCH) {
st.req_filetype = TYPE_HTML;
sstrlcpy(st.req_selector, selector);
url_redirect(&st);
return OK;
}
/* Handle gopher+ root requests (UMN gopher client is seriously borken) */
if (sstrncmp(selector, "\t$") == MATCH) {
printf("+-1" CRLF);
printf("+INFO: 1Main menu\t\t%s\t%i" CRLF,
st.server_host,
st.server_port);
printf("+VIEWS:" CRLF " application/gopher+-menu: <512b>" CRLF);
printf("." CRLF);
if (st.debug) syslog(LOG_INFO, "got a request for gopher+ root menu");
return OK;
}
/* Convert HTTP request to gopher (respond using headerless HTTP/0.9) */
if (sstrncmp(selector, "GET ") == MATCH ||
sstrncmp(selector, "POST ") == MATCH ) {
if ((c = strchr(selector, ' '))) sstrlcpy(selector, c + 1);
if ((c = strchr(selector, ' '))) *c = '\0';
st.req_protocol = PROTO_HTTP;
if (st.debug) syslog(LOG_INFO, "got HTTP request for \"%s\"", selector);
}
/* Save default server_host & fetch session data (including new server_host) */
sstrlcpy(st.server_host_default, st.server_host);
#ifdef HAVE_SHMEM
if (shm) get_shm_session(&st, shm);
#endif
/* Loop through the selector, fix it & separate query_string */
dest = st.req_selector;
if (selector[0] != '/') *dest++ = '/';
for (c = selector; *c;) {
/* Skip duplicate slashes and /./ */
while (*c == '/' && *(c + 1) == '/') c++;
if (*c == '/' && *(c + 1) == '.' && *(c + 2) == '/') c += 2;
/* Start of a query string (either type 7 or HTTP-style)? */
if (*c == '\t' || (st.opt_query && *c == '?')) {
sstrlcpy(st.req_query_string, c + 1);
if ((c = strchr(st.req_query_string, '\t'))) *c = '\0';
break;
}
/* Start of virtual host hint? */
if (*c == ';') {
if (st.opt_vhost) sstrlcpy(st.server_host, c + 1);
/* Skip vhost on selector */
while (*c && *c != '\t') c++;
continue;
}
/* Copy valid char */
*dest++ = *c++;
}
*dest = '\0';
/* Deny requests for Slashdot and /../ hackers */
if (strstr(st.req_selector, "/."))
die(&st, ERR_ACCESS, "Refusing to serve out dotfiles");
/* Handle /server-status requests */
#ifdef HAVE_SHMEM
if (sstrncmp(st.req_selector, SERVER_STATUS) == MATCH) {
if (shm) server_status(&st, shm, shmid);
return OK;
}
#endif
/* Remove possible extra cruft from server_host */
if ((c = strchr(st.server_host, '\t'))) *c = '\0';
/* Guess request filetype so we can die() with style... */
st.req_filetype = gopher_filetype(&st, st.req_selector, FALSE);
/* Convert seletor to path & stat() */
selector_to_path(&st);
if (st.debug) syslog(LOG_INFO, "path to resource is \"%s\"", st.req_realpath);
if (stat(st.req_realpath, &file) == ERROR) {
/* Handle virtual /caps.txt requests */
if (st.opt_caps && sstrncmp(st.req_selector, CAPS_TXT) == MATCH) {
#ifdef HAVE_SHMEM
caps_txt(&st, shm);
#else
caps_txt(&st, NULL);
#endif
return OK;
}
/* Requested file not found - die() */
die(&st, ERR_NOTFOUND, NULL);
}
/* Fetch request filesize from stat() */
st.req_filesize = file.st_size;
/* Everyone must have read access but no write access */
if ((file.st_mode & S_IROTH) == 0)
die(&st, ERR_ACCESS, "File or directory not world-readable");
if ((file.st_mode & S_IWOTH) != 0)
die(&st, ERR_ACCESS, "File or directory world-writeable");
/* If stat said it was a dir then it's a menu */
if ((file.st_mode & S_IFMT) == S_IFDIR) st.req_filetype = TYPE_MENU;
/* Not a dir - let's guess the filetype again... */
else if ((file.st_mode & S_IFMT) == S_IFREG)
st.req_filetype = gopher_filetype(&st, st.req_realpath, st.opt_magic);
/* Menu selectors must end with a slash */
if (st.req_filetype == TYPE_MENU && strlast(st.req_selector) != '/')
sstrlcat(st.req_selector, "/");
/* Change directory to wherever the resource was */
sstrlcpy(buf, st.req_realpath);
if ((file.st_mode & S_IFMT) != S_IFDIR) c = dirname(buf);
else c = buf;
if (chdir(c) == ERROR) die(&st, ERR_ACCESS, NULL);
/* Keep count of hits and data transfer */
#ifdef HAVE_SHMEM
if (shm) {
shm->hits++;
shm->kbytes += st.req_filesize / 1024;
/* Update user session */
update_shm_session(&st, shm);
}
#endif
/* Log the request */
if (st.opt_syslog) {
syslog(LOG_INFO, "request for \"gopher://%s:%i/%c%s\" from %s",
st.server_host,
st.server_port,
st.req_filetype,
st.req_selector,
st.req_remote_addr);
}
/* Check file type & act accordingly */
switch (file.st_mode & S_IFMT) {
case S_IFDIR:
log_combined(&st, HTTP_OK);
gopher_menu(&st);
break;
case S_IFREG:
log_combined(&st, HTTP_OK);
gopher_file(&st);
break;
default:
die(&st, ERR_ACCESS, "Refusing to serve out special files");
}
/* Clean exit */
return OK;
}

410
gophernicus.h Normal file
View File

@ -0,0 +1,410 @@
/*
* Gophernicus - Copyright (c) 2009-2012 Kim Holviala <kim@holviala.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef _GOPHERNICUS_H
#define _GOPHERNICUS_H
/*
* Features
*/
#undef ENABLE_STRICT_RFC1436 /* Follow RFC1436 to the letter */
#undef ENABLE_AUTOHIDING /* Hide manually listed resources from generated menus */
/*
* Platform configuration
*/
/* Defaults should fit standard POSIX systems */
#define HAVE_IPv4 /* IPv4 should work anywhere */
#define HAVE_IPv6 /* Requires modern POSIX */
#define HAVE_PASSWD /* For systems with passwd-like userdb */
#define PASSWD_MIN_UID 100 /* Minimum allowed UID for ~userdirs */
#define HAVE_LOCALES /* setlocale() and friends */
#define HAVE_SHMEM /* Shared memory support */
#define HAVE_UNAME /* uname() */
#define HAVE_POPEN /* popen() */
#undef HAVE_STRLCPY /* strlcpy() from OpenBSD */
#undef HAVE_SENDFILE /* sendfile() in Linux & others */
/* Linux */
#ifdef __linux
#undef PASSWD_MIN_UID
#define PASSWD_MIN_UID 500
#define HAVE_SENDFILE
#endif
/* Embedded Linux with uClibc */
#ifdef __UCLIBC__
lskdjf
#undef HAVE_SHMEM
#undef HAVE_PASSWD
#endif
/* Haiku */
#ifdef __HAIKU__
#undef HAVE_SHMEM
#undef HAVE_PASSWD
#endif
/* OpenBSD */
#ifdef __OpenBSD__
#define HAVE_STRLCPY
#endif
/* Add other OS-specific defines here */
/*
* Include headers
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <string.h>
#include <libgen.h>
#include <time.h>
#include <syslog.h>
#include <errno.h>
#include <pwd.h>
#include <limits.h>
#ifdef HAVE_SENDFILE
#include <sys/sendfile.h>
#include <fcntl.h>
#endif
#ifdef HAVE_LOCALES
#include <locale.h>
#endif
#ifdef HAVE_SHMEM
#include <sys/ipc.h>
#include <sys/shm.h>
#else
#define shm_state void
#endif
#if defined(HAVE_IPv4) || defined(HAVE_IPv6)
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif
#ifdef HAVE_UNAME
#include <sys/utsname.h>
#endif
/*
* Compile-time configuration
*/
/* Common stuff */
#define CRLF "\r\n"
#define EMPTY ""
#define PARENT ".."
#define ROOT "/"
#define FALSE 0
#define TRUE 1
#define QUIT 1
#define OK 0
#define ERROR -1
#define MATCH 0
/* Gopher filetypes */
#define TYPE_TEXT '0'
#define TYPE_MENU '1'
#define TYPE_ERROR '3'
#define TYPE_GZIP '5'
#define TYPE_QUERY '7'
#define TYPE_BINARY '9'
#define TYPE_GIF 'g'
#define TYPE_HTML 'h'
#define TYPE_INFO 'i'
#define TYPE_IMAGE 'I'
#define TYPE_MIME 'M'
#define TYPE_DOC 'd'
#define TYPE_TITLE '!'
/* Protocols */
#define PROTO_GOPHER 'g'
#define PROTO_HTTP 'h'
/* Charsets */
#define AUTO 0
#define US_ASCII 1
#define ISO_8859_1 2
#define UTF_8 3
/* HTTP protocol stuff for logging */
#define HTTP_OK 200
#define HTTP_404 404
#define HTTP_DATE "%d/%b/%Y:%T %z"
#define HTTP_USERAGENT "Unknown gopher client"
/* Defaults for settings */
#define DEFAULT_HOST "localhost"
#define DEFAULT_PORT 70
#define DEFAULT_TYPE TYPE_TEXT
#define DEFAULT_MAP "gophermap"
#define DEFAULT_TAG "gophertag"
#define DEFAULT_CGI "/cgi-bin/"
#define DEFAULT_USERDIR "public_gopher"
#define DEFAULT_ADDR "unknown"
#define DEFAULT_WIDTH 70
#define DEFAULT_CHARSET US_ASCII
#define MIN_WIDTH 33
#define MAX_WIDTH 200
/* Session defaults */
#define DEFAULT_SESSION_TIMEOUT 1800
#define DEFAULT_SESSION_MAX_KBYTES 4194304
#define DEFAULT_SESSION_MAX_HITS 4096
/* Dummy values for gopher protocol */
#define DUMMY_SELECTOR "null"
#define DUMMY_HOST "null.host\t1"
/* Safe $PATH for exec() */
#ifdef __HAIKU__
#define SAFE_PATH "/boot/common/bin:/bin"
#else
#define SAFE_PATH "/usr/bin:/bin"
#endif
/* Special requests */
#define SERVER_STATUS "/server-status"
#define CAPS_TXT "/caps.txt"
/* Error messages */
#define ERR_ACCESS "Access denied!"
#define ERR_NOTFOUND "File or directory not found!"
#define ERROR_HOST "error.host\t1"
#define ERROR_PREFIX "Error: "
/* Strings */
#define SERVER_SOFTWARE "Gophernicus"
#define SERVER_SOFTWARE_FULL SERVER_SOFTWARE "/" VERSION " (%s)"
#define HEADER_FORMAT "[%s]"
#define FOOTER_FORMAT "Gophered by Gophernicus/" VERSION " on %s"
#define UNITS "KB", "MB", "GB", "TB", "PB", NULL
#define DATE_FORMAT "%Y-%b-%d %H:%M" /* See man 3 strftime */
#define DATE_WIDTH 17
#define DATE_LOCALE "POSIX"
#define USERDIR_FORMAT "~%s", pwd->pw_name /* See man 3 getpwent */
#define VHOST_FORMAT "gopher://%s/"
/* ISO-8859-1 to US-ASCII look-alike conversion table */
#define ASCII \
"E?,f..++^%S<??Z?" \
"?''\"\"*--~?s>??zY" \
" !c_*Y|$\"C?<?-R-" \
"??23'u?*,1?>????" \
"AAAAAAACEEEEIIII" \
"DNOOOOO*OUUUUYTB" \
"aaaaaaaceeeeiiii" \
"dnooooo/ouuuuyty"
#define UNKNOWN '?'
/* Sizes & maximums */
#define BUFSIZE 1024 /* Default size for string buffers */
#define MAX_HIDDEN 32 /* Maximum number of hidden files */
#define MAX_FILETYPES 128 /* Maximum number of suffix to filetype mappings */
#define MAX_FILTERS 16 /* Maximum number of file filters */
#define MAX_SDIRENT 1024 /* Maximum number of files per directory to handle */
#define MAX_REWRITE 32 /* Maximum number of selector rewrite options */
/* Struct for file suffix -> gopher filetype mapping */
typedef struct {
char suffix[15];
char type;
} ftype;
/* Struct for selector rewriting */
typedef struct {
char match[BUFSIZE];
char replace[BUFSIZE];
} srewrite;
/* Struct for keeping the current options & state */
typedef struct {
/* Request */
char req_selector[BUFSIZE];
char req_realpath[BUFSIZE];
char req_query_string[BUFSIZE];
char req_referrer[BUFSIZE];
char req_local_addr[64];
char req_remote_addr[64];
char req_filetype;
char req_protocol;
off_t req_filesize;
/* Output */
int out_width;
int out_charset;
/* Settings */
char server_description[64];
char server_location[64];
char server_platform[64];
char server_admin[64];
char server_root[256];
char server_host_default[64];
char server_host[64];
int server_port;
char default_filetype;
char map_file[64];
char tag_file[64];
char cgi_file[64];
char user_dir[64];
char log_file[256];
char hidden[MAX_HIDDEN][256];
int hidden_count;
ftype filetype[MAX_FILETYPES];
int filetype_count;
char filter_dir[64];
srewrite rewrite[MAX_REWRITE];
int rewrite_count;
/* Session */
int session_timeout;
int session_max_kbytes;
int session_max_hits;
/* Feature options */
char opt_parent;
char opt_header;
char opt_footer;
char opt_date;
char opt_syslog;
char opt_magic;
char opt_iconv;
char opt_vhost;
char opt_query;
char opt_caps;
char opt_shm;
char opt_root;
char debug;
} state;
/* Shared memory for session & accounting data */
#ifdef HAVE_SHMEM
#define SHM_KEY 0xbeeb0006 /* Unique identifier + struct version */
#define SHM_MODE 0600 /* Access mode for the shared memory */
#define SHM_SESSIONS 256 /* Max amount of user sessions to track */
typedef struct {
long hits;
long kbytes;
time_t req_atime;
char req_selector[128];
char req_remote_addr[64];
char req_filetype;
char server_host[64];
int server_port;
} shm_session;
typedef struct {
time_t start_time;
long hits;
long kbytes;
char server_platform[64];
char server_description[64];
shm_session session[SHM_SESSIONS];
} shm_state;
#endif
/* Struct for directory sorting */
typedef struct {
char name[128]; /* Should be 256 but we're saving stack space */
mode_t mode;
uid_t uid;
gid_t gid;
off_t size;
time_t mtime;
} sdirent;
/* File suffix to gopher filetype mappings */
#define FILETYPES \
"txt","0","pl","0","py","0","sh","0","tcl","0","c","0","cpp","0", "h","0","log","0", \
"conf","0","php","0","php3","0", \
"map","1","menu","1", \
"hqx","4", \
"Z","5","gz","5","tgz","5","tar","5","zip","5","bz2","5","rar","5","sea","5", \
"q","7","qry","7", \
"iso","9","so","9","o","9","rtf","9","ttf","9","bin","9", \
"ics","c","ical","c", \
"gif","g", \
"html","h","htm","h","xhtml","h","css","h","swf","h","rdf","h","rss","h","xml","h", \
"jpg","I","jpeg","I","png","I","bmp","I","svg","I","tif","I","tiff","I", \
"ico","I","xbm","I","xpm","I","pcx","I", \
"mbox","M", \
"pdf","d","ps","d","doc","d","ppt","d","xls","d","xlsx","d","docx","d","pptx","d", \
"mp3","s","wav","s","mid","s","wma","s","flac","s","ogg","s","aiff","s","aac","s", \
"avi",";","mp4",";","mpg",";","mov",";","qt",";","asf",";","mpv",";","m4v",";", \
NULL, NULL
/*
* Useful macros
*/
#define strclear(str) str[0] = '\0';
#define sstrlcpy(dest, src) strlcpy(dest, src, sizeof(dest))
#define sstrlcat(dest, src) strlcat(dest, src, sizeof(dest))
#define sstrncmp(s1, s2) strncmp(s1, s2, sizeof(s2) - 1)
#define sstrncasecmp(s1, s2) strncasecmp(s1, s2, sizeof(s2) - 1)
#define sstrniconv(charset, out, in) strniconv(charset, out, in, sizeof(out))
#define max(a,b) (((a) > (b)) ? (a) : (b))
#define min(a,b) (((a) < (b)) ? (a) : (b))
/*
* Include generated headers
*/
#include "functions.h"
#include "files.h"
#endif

11
gophernicus.xinetd Normal file
View File

@ -0,0 +1,11 @@
# default: on
# description: Gophernicus - Modern full-featured gopher server
service gopher
{
socket_type = stream
wait = no
user = nobody
server = /usr/sbin/in.gophernicus
server_args = -r/var/gopher -h@HOSTNAME@
disable = no
}

1
gophertag Normal file
View File

@ -0,0 +1 @@
Gophernicus documentation

520
install-sh Executable file
View File

@ -0,0 +1,520 @@
#!/bin/sh
# install - install a program, script, or datafile
scriptversion=2009-04-28.21; # UTC
# This originates from X11R5 (mit/util/scripts/install.sh), which was
# later released in X11R6 (xc/config/util/install.sh) with the
# following copyright and license.
#
# Copyright (C) 1994 X Consortium
#
# 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
# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC-
# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Except as contained in this notice, the name of the X Consortium shall not
# be used in advertising or otherwise to promote the sale, use or other deal-
# ings in this Software without prior written authorization from the X Consor-
# tium.
#
#
# FSF changes to this file are in the public domain.
#
# Calling this script install-sh is preferred over install.sh, to prevent
# `make' implicit rules from creating a file called install from it
# when there is no Makefile.
#
# This script is compatible with the BSD install script, but was written
# from scratch.
nl='
'
IFS=" "" $nl"
# set DOITPROG to echo to test this script
# Don't use :- since 4.3BSD and earlier shells don't like it.
doit=${DOITPROG-}
if test -z "$doit"; then
doit_exec=exec
else
doit_exec=$doit
fi
# Put in absolute file names if you don't have them in your path;
# or use environment vars.
chgrpprog=${CHGRPPROG-chgrp}
chmodprog=${CHMODPROG-chmod}
chownprog=${CHOWNPROG-chown}
cmpprog=${CMPPROG-cmp}
cpprog=${CPPROG-cp}
mkdirprog=${MKDIRPROG-mkdir}
mvprog=${MVPROG-mv}
rmprog=${RMPROG-rm}
stripprog=${STRIPPROG-strip}
posix_glob='?'
initialize_posix_glob='
test "$posix_glob" != "?" || {
if (set -f) 2>/dev/null; then
posix_glob=
else
posix_glob=:
fi
}
'
posix_mkdir=
# Desired mode of installed file.
mode=0755
chgrpcmd=
chmodcmd=$chmodprog
chowncmd=
mvcmd=$mvprog
rmcmd="$rmprog -f"
stripcmd=
src=
dst=
dir_arg=
dst_arg=
copy_on_change=false
no_target_directory=
usage="\
Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE
or: $0 [OPTION]... SRCFILES... DIRECTORY
or: $0 [OPTION]... -t DIRECTORY SRCFILES...
or: $0 [OPTION]... -d DIRECTORIES...
In the 1st form, copy SRCFILE to DSTFILE.
In the 2nd and 3rd, copy all SRCFILES to DIRECTORY.
In the 4th, create DIRECTORIES.
Options:
--help display this help and exit.
--version display version info and exit.
-c (ignored)
-C install only if different (preserve the last data modification time)
-d create directories instead of installing files.
-g GROUP $chgrpprog installed files to GROUP.
-m MODE $chmodprog installed files to MODE.
-o USER $chownprog installed files to USER.
-s $stripprog installed files.
-t DIRECTORY install into DIRECTORY.
-T report an error if DSTFILE is a directory.
Environment variables override the default commands:
CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG
RMPROG STRIPPROG
"
while test $# -ne 0; do
case $1 in
-c) ;;
-C) copy_on_change=true;;
-d) dir_arg=true;;
-g) chgrpcmd="$chgrpprog $2"
shift;;
--help) echo "$usage"; exit $?;;
-m) mode=$2
case $mode in
*' '* | *' '* | *'
'* | *'*'* | *'?'* | *'['*)
echo "$0: invalid mode: $mode" >&2
exit 1;;
esac
shift;;
-o) chowncmd="$chownprog $2"
shift;;
-s) stripcmd=$stripprog;;
-t) dst_arg=$2
shift;;
-T) no_target_directory=true;;
--version) echo "$0 $scriptversion"; exit $?;;
--) shift
break;;
-*) echo "$0: invalid option: $1" >&2
exit 1;;
*) break;;
esac
shift
done
if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then
# When -d is used, all remaining arguments are directories to create.
# When -t is used, the destination is already specified.
# Otherwise, the last argument is the destination. Remove it from $@.
for arg
do
if test -n "$dst_arg"; then
# $@ is not empty: it contains at least $arg.
set fnord "$@" "$dst_arg"
shift # fnord
fi
shift # arg
dst_arg=$arg
done
fi
if test $# -eq 0; then
if test -z "$dir_arg"; then
echo "$0: no input file specified." >&2
exit 1
fi
# It's OK to call `install-sh -d' without argument.
# This can happen when creating conditional directories.
exit 0
fi
if test -z "$dir_arg"; then
trap '(exit $?); exit' 1 2 13 15
# Set umask so as not to create temps with too-generous modes.
# However, 'strip' requires both read and write access to temps.
case $mode in
# Optimize common cases.
*644) cp_umask=133;;
*755) cp_umask=22;;
*[0-7])
if test -z "$stripcmd"; then
u_plus_rw=
else
u_plus_rw='% 200'
fi
cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;;
*)
if test -z "$stripcmd"; then
u_plus_rw=
else
u_plus_rw=,u+rw
fi
cp_umask=$mode$u_plus_rw;;
esac
fi
for src
do
# Protect names starting with `-'.
case $src in
-*) src=./$src;;
esac
if test -n "$dir_arg"; then
dst=$src
dstdir=$dst
test -d "$dstdir"
dstdir_status=$?
else
# Waiting for this to be detected by the "$cpprog $src $dsttmp" command
# might cause directories to be created, which would be especially bad
# if $src (and thus $dsttmp) contains '*'.
if test ! -f "$src" && test ! -d "$src"; then
echo "$0: $src does not exist." >&2
exit 1
fi
if test -z "$dst_arg"; then
echo "$0: no destination specified." >&2
exit 1
fi
dst=$dst_arg
# Protect names starting with `-'.
case $dst in
-*) dst=./$dst;;
esac
# If destination is a directory, append the input filename; won't work
# if double slashes aren't ignored.
if test -d "$dst"; then
if test -n "$no_target_directory"; then
echo "$0: $dst_arg: Is a directory" >&2
exit 1
fi
dstdir=$dst
dst=$dstdir/`basename "$src"`
dstdir_status=0
else
# Prefer dirname, but fall back on a substitute if dirname fails.
dstdir=`
(dirname "$dst") 2>/dev/null ||
expr X"$dst" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
X"$dst" : 'X\(//\)[^/]' \| \
X"$dst" : 'X\(//\)$' \| \
X"$dst" : 'X\(/\)' \| . 2>/dev/null ||
echo X"$dst" |
sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
s//\1/
q
}
/^X\(\/\/\)[^/].*/{
s//\1/
q
}
/^X\(\/\/\)$/{
s//\1/
q
}
/^X\(\/\).*/{
s//\1/
q
}
s/.*/./; q'
`
test -d "$dstdir"
dstdir_status=$?
fi
fi
obsolete_mkdir_used=false
if test $dstdir_status != 0; then
case $posix_mkdir in
'')
# Create intermediate dirs using mode 755 as modified by the umask.
# This is like FreeBSD 'install' as of 1997-10-28.
umask=`umask`
case $stripcmd.$umask in
# Optimize common cases.
*[2367][2367]) mkdir_umask=$umask;;
.*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;;
*[0-7])
mkdir_umask=`expr $umask + 22 \
- $umask % 100 % 40 + $umask % 20 \
- $umask % 10 % 4 + $umask % 2
`;;
*) mkdir_umask=$umask,go-w;;
esac
# With -d, create the new directory with the user-specified mode.
# Otherwise, rely on $mkdir_umask.
if test -n "$dir_arg"; then
mkdir_mode=-m$mode
else
mkdir_mode=
fi
posix_mkdir=false
case $umask in
*[123567][0-7][0-7])
# POSIX mkdir -p sets u+wx bits regardless of umask, which
# is incompatible with FreeBSD 'install' when (umask & 300) != 0.
;;
*)
tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$
trap 'ret=$?; rmdir "$tmpdir/d" "$tmpdir" 2>/dev/null; exit $ret' 0
if (umask $mkdir_umask &&
exec $mkdirprog $mkdir_mode -p -- "$tmpdir/d") >/dev/null 2>&1
then
if test -z "$dir_arg" || {
# Check for POSIX incompatibilities with -m.
# HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or
# other-writeable bit of parent directory when it shouldn't.
# FreeBSD 6.1 mkdir -m -p sets mode of existing directory.
ls_ld_tmpdir=`ls -ld "$tmpdir"`
case $ls_ld_tmpdir in
d????-?r-*) different_mode=700;;
d????-?--*) different_mode=755;;
*) false;;
esac &&
$mkdirprog -m$different_mode -p -- "$tmpdir" && {
ls_ld_tmpdir_1=`ls -ld "$tmpdir"`
test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1"
}
}
then posix_mkdir=:
fi
rmdir "$tmpdir/d" "$tmpdir"
else
# Remove any dirs left behind by ancient mkdir implementations.
rmdir ./$mkdir_mode ./-p ./-- 2>/dev/null
fi
trap '' 0;;
esac;;
esac
if
$posix_mkdir && (
umask $mkdir_umask &&
$doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir"
)
then :
else
# The umask is ridiculous, or mkdir does not conform to POSIX,
# or it failed possibly due to a race condition. Create the
# directory the slow way, step by step, checking for races as we go.
case $dstdir in
/*) prefix='/';;
-*) prefix='./';;
*) prefix='';;
esac
eval "$initialize_posix_glob"
oIFS=$IFS
IFS=/
$posix_glob set -f
set fnord $dstdir
shift
$posix_glob set +f
IFS=$oIFS
prefixes=
for d
do
test -z "$d" && continue
prefix=$prefix$d
if test -d "$prefix"; then
prefixes=
else
if $posix_mkdir; then
(umask=$mkdir_umask &&
$doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break
# Don't fail if two instances are running concurrently.
test -d "$prefix" || exit 1
else
case $prefix in
*\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;;
*) qprefix=$prefix;;
esac
prefixes="$prefixes '$qprefix'"
fi
fi
prefix=$prefix/
done
if test -n "$prefixes"; then
# Don't fail if two instances are running concurrently.
(umask $mkdir_umask &&
eval "\$doit_exec \$mkdirprog $prefixes") ||
test -d "$dstdir" || exit 1
obsolete_mkdir_used=true
fi
fi
fi
if test -n "$dir_arg"; then
{ test -z "$chowncmd" || $doit $chowncmd "$dst"; } &&
{ test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } &&
{ test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false ||
test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1
else
# Make a couple of temp file names in the proper directory.
dsttmp=$dstdir/_inst.$$_
rmtmp=$dstdir/_rm.$$_
# Trap to clean up those temp files at exit.
trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0
# Copy the file name to the temp name.
(umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") &&
# and set any options; do chmod last to preserve setuid bits.
#
# If any of these fail, we abort the whole thing. If we want to
# ignore errors from any of these, just make sure not to ignore
# errors from the above "$doit $cpprog $src $dsttmp" command.
#
{ test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } &&
{ test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } &&
{ test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } &&
{ test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } &&
# If -C, don't bother to copy if it wouldn't change the file.
if $copy_on_change &&
old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` &&
new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` &&
eval "$initialize_posix_glob" &&
$posix_glob set -f &&
set X $old && old=:$2:$4:$5:$6 &&
set X $new && new=:$2:$4:$5:$6 &&
$posix_glob set +f &&
test "$old" = "$new" &&
$cmpprog "$dst" "$dsttmp" >/dev/null 2>&1
then
rm -f "$dsttmp"
else
# Rename the file to the real destination.
$doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null ||
# The rename failed, perhaps because mv can't rename something else
# to itself, or perhaps because mv is so ancient that it does not
# support -f.
{
# Now remove or move aside any old file at destination location.
# We try this two ways since rm can't unlink itself on some
# systems and the destination file might be busy for other
# reasons. In this case, the final cleanup might fail but the new
# file should still install successfully.
{
test ! -f "$dst" ||
$doit $rmcmd -f "$dst" 2>/dev/null ||
{ $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null &&
{ $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; }
} ||
{ echo "$0: cannot unlink or rename $dst" >&2
(exit 1); exit 1
}
} &&
# Now rename the file to the real destination.
$doit $mvcmd "$dsttmp" "$dst"
}
fi || exit 1
trap '' 0
fi
done
# Local variables:
# eval: (add-hook 'write-file-hooks 'time-stamp)
# time-stamp-start: "scriptversion="
# time-stamp-format: "%:y-%02m-%02d.%02H"
# time-stamp-time-zone: "UTC"
# time-stamp-end: "; # UTC"
# End:

665
menu.c Normal file
View File

@ -0,0 +1,665 @@
/*
* Gophernicus - Copyright (c) 2009-2012 Kim Holviala <kim@holviala.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "gophernicus.h"
/*
* Alphabetic folders first sort for sortdir()
*/
int foldersort(const void *a, const void *b)
{
mode_t amode;
mode_t bmode;
amode = (*(sdirent *) a).mode & S_IFMT;
bmode = (*(sdirent *) b).mode & S_IFMT;
if (amode == S_IFDIR && bmode != S_IFDIR) return -1;
if (amode != S_IFDIR && bmode == S_IFDIR) return 1;
return strcmp((*(sdirent *) a).name, (*(sdirent *) b).name);
}
/*
* Scan, stat and sort a directory folders first (scandir replacement)
*/
int sortdir(char *path, sdirent *list, int max)
{
DIR *dp;
struct dirent *d;
struct stat s;
char buf[BUFSIZE];
int i;
/* Try to open the dir */
if ((dp = opendir(path)) == NULL) return 0;
i = 0;
/* Loop through the directory & stat() everything */
while (max--) {
if ((d = readdir(dp)) == NULL) break;
snprintf(buf, sizeof(buf), "%s/%s", path, d->d_name);
if (stat(buf, &s) == ERROR) continue;
if (strlen(d->d_name) > sizeof(list[i].name)) continue;
sstrlcpy(list[i].name, d->d_name);
list[i].mode = s.st_mode;
list[i].uid = s.st_uid;
list[i].gid = s.st_gid;
list[i].size = s.st_size;
list[i].mtime = s.st_mtime;
i++;
}
closedir(dp);
/* Sort the entries */
if (i > 1) qsort(list, i, sizeof(sdirent), foldersort);
/* Return number of entries found */
return i;
}
/*
* Print a list of users with ~/public_gopher
*/
#ifdef HAVE_PASSWD
void userlist(state *st)
{
struct passwd *pwd;
struct stat dir;
char buf[BUFSIZE];
struct tm *ltime;
char timestr[20];
int width;
/* Width of filenames for fancy listing */
width = st->out_width - DATE_WIDTH - 15;
/* Loop through all users */
setpwent();
while ((pwd = getpwent())) {
/* Skip too small uids */
if (pwd->pw_uid < PASSWD_MIN_UID) continue;
/* Look for a world-readable user-owned ~/public_gopher */
snprintf(buf, sizeof(buf), "%s/%s", pwd->pw_dir, st->user_dir);
if (stat(buf, &dir) == ERROR) continue;
if ((dir.st_mode & S_IROTH) == 0) continue;
if (dir.st_uid != pwd->pw_uid) continue;
/* Found one */
snprintf(buf, sizeof(buf), USERDIR_FORMAT);
if (st->opt_date) {
ltime = localtime(&dir.st_mtime);
strftime(timestr, sizeof(timestr), DATE_FORMAT, ltime);
printf("1%-*.*s %s - \t/~%s/\t%s\t%i" CRLF,
width, width, buf, timestr, pwd->pw_name,
st->server_host, st->server_port);
}
else {
printf("1%.*s\t/~%s/\t%s\t%i" CRLF, st->out_width, buf,
pwd->pw_name, st->server_host_default, st->server_port);
}
}
endpwent();
}
#endif
/*
* Print a list of available virtual hosts
*/
void vhostlist(state *st)
{
sdirent dir[MAX_SDIRENT];
struct tm *ltime;
char timestr[20];
char buf[BUFSIZE];
int width;
int num;
int i;
/* Scan the root dir for vhost dirs */
num = sortdir(st->server_root, dir, MAX_SDIRENT);
if (num < 0) die(st, ERR_NOTFOUND, "WTF?");
/* Width of filenames for fancy listing */
width = st->out_width - DATE_WIDTH - 15;
/* Loop through the directory entries */
for (i = 0; i < num; i++) {
/* Skip dotfiles */
if (dir[i].name[0] == '.') continue;
/* Require FQDN */
if (!strchr(dir[i].name, '.')) continue;
/* We only want world-readable directories */
if ((dir[i].mode & S_IROTH) == 0) continue;
if ((dir[i].mode & S_IFMT) != S_IFDIR) continue;
/* Generate display string for vhost */
snprintf(buf, sizeof(buf), VHOST_FORMAT, dir[i].name);
/* Fancy listing */
if (st->opt_date) {
ltime = localtime(&dir[i].mtime);
strftime(timestr, sizeof(timestr), DATE_FORMAT, ltime);
printf("1%-*.*s %s - \t/;%s\t%s\t%i" CRLF,
width, width, buf, timestr, dir[i].name,
dir[i].name, st->server_port);
}
/* Teh boring version */
else {
printf("1%.*s\t/;%s\t%s\t%i" CRLF, st->out_width, buf,
dir[i].name, dir[i].name, st->server_port);
}
}
}
/*
* Return gopher filetype for a file
*/
char gopher_filetype(state *st, char *file, char magic)
{
FILE *fp;
char buf[BUFSIZE];
char *c;
int i;
/* If it ends with an slash it's a menu */
if (!*file) return st->default_filetype;
if (strlast(file) == '/') return TYPE_MENU;
/* Get file suffix */
if ((c = strrchr(file, '.'))) {
c++;
/* Loop through the filetype array looking for a match*/
for (i = 0; i < st->filetype_count; i++)
if (strcasecmp(st->filetype[i].suffix, c) == MATCH)
return st->filetype[i].type;
}
/* Are we allowed to look inside files? */
if (!magic) return st->default_filetype;
/* Read data from the file */
if ((fp = fopen(file , "r")) == NULL) return st->default_filetype;
i = fread(buf, 1, sizeof(buf) - 1, fp);
buf[i] = '\0';
fclose(fp);
/* GIF images */
if (sstrncmp(buf, "GIF89a") == MATCH ||
sstrncmp(buf, "GIF87a") == MATCH) return TYPE_GIF;
/* JPEG images */
if (sstrncmp(buf, "\377\330\377\340") == MATCH) return TYPE_IMAGE;
/* PNG images */
if (sstrncmp(buf, "\211PNG") == MATCH) return TYPE_IMAGE;
/* mbox */
if (strstr(buf, "\nFrom: ") &&
strstr(buf, "\nSubject: ")) return TYPE_MIME;
/* MIME */
if (strstr(buf, "\nContent-Type: ")) return TYPE_MIME;
/* HTML files */
if (buf[0] == '<' &&
(strstr(buf, "<html") ||
strstr(buf, "<HTML"))) return TYPE_HTML;
/* PDF and PostScript */
if (sstrncmp(buf, "%PDF-") == MATCH ||
sstrncmp(buf, "%!") == MATCH) return TYPE_DOC;
/* compress and gzip */
if (sstrncmp(buf, "\037\235\220") == MATCH ||
sstrncmp(buf, "\037\213\010") == MATCH) return TYPE_GZIP;
/* Unknown content - binary or text? */
if (memchr(buf, '\0', i)) return TYPE_BINARY;
return st->default_filetype;
}
/*
* Handle gophermaps
*/
int gophermap(state *st, char *mapfile, int depth)
{
FILE *fp;
struct stat file;
char line[BUFSIZE];
char *selector;
char *name;
char *host;
char *c;
char type;
int port;
int exe;
/* Prevent include loops */
if (depth > 4) return OK;
/* Try to figure out whether the map is executable */
if (stat(mapfile, &file) == OK) {
if ((file.st_mode & S_IXOTH)) exe = TRUE;
else exe = FALSE;
}
/* As a fallback let's just feed everything to shell.. */
else exe = TRUE;
/* Debug output */
if (st->debug) {
if (exe) syslog(LOG_INFO, "parsing executable gophermap \"%s\"", mapfile);
else syslog(LOG_INFO, "parsing static gophermap \"%s\"", mapfile);
}
/* Try to execute or open the mapfile */
#ifdef HAVE_POPEN
if (exe) {
setenv_cgi(st, mapfile);
if ((fp = popen(mapfile , "r")) == NULL) return OK;
}
else
#endif
if ((fp = fopen(mapfile , "r")) == NULL) return OK;
/* Read lines one by one */
while (fgets(line, sizeof(line) - 1, fp)) {
/* Parse type & name */
chomp(line);
type = line[0];
name = line + 1;
/* Ignore #comments */
if (type == '#') continue;
/* Stop handling gophermap? */
if (type == '*') return OK;
if (type == '.') return QUIT;
/* Print a list of users with public_gopher */
if (type == '~') {
#ifdef HAVE_PASSWD
userlist(st);
#endif
continue;
}
/* Print a list of available virtual hosts */
if (type == '%') {
if (st->opt_vhost) vhostlist(st);
continue;
}
/* Hide files in menus */
if (type == '-') {
if (st->hidden_count < MAX_HIDDEN)
sstrlcpy(st->hidden[st->hidden_count++], name);
continue;
}
/* Override filetype mappings */
if (type == ':') {
add_ftype_mapping(st, name);
continue;
}
/* Include gophermap or shell exec */
if (type == '=') {
gophermap(st, name, depth + 1);
continue;
}
/* Title resource */
if (type == TYPE_TITLE) {
info(st, name, TYPE_TITLE);
continue;
}
/* Print out non-resources as info text */
if (!strchr(line, '\t')) {
info(st, line, TYPE_INFO);
continue;
}
/* Parse selector */
selector = EMPTY;
if ((c = strchr(name, '\t'))) {
*c = '\0';
selector = c + 1;
}
if (!*selector) selector = name;
/* Parse host */
host = st->server_host;
if ((c = strchr(selector, '\t'))) {
*c = '\0';
host = c + 1;
}
/* Parse port */
port = st->server_port;
if ((c = strchr(host, '\t'))) {
*c = '\0';
port = atoi(c + 1);
}
/* Handle remote, absolute and hURL gopher resources */
if (sstrncmp(selector, "URL:") == MATCH ||
selector[0] == '/' ||
host != st->server_host) {
printf("%c%s\t%s\t%s\t%i" CRLF, type, name,
selector, host, port);
}
/* Handle relative resources */
else {
printf("%c%s\t%s%s\t%s\t%i" CRLF, type, name,
st->req_selector, selector, host, port);
/* Automatically hide manually defined selectors */
#ifdef ENABLE_AUTOHIDING
if (st->hidden_count < MAX_HIDDEN)
sstrlcpy(st->hidden[st->hidden_count++], selector);
#endif
}
}
/* Clean up & return */
#ifdef HAVE_POPEN
if (exe) pclose(fp);
else
#endif
fclose(fp);
return QUIT;
}
/*
* Handle gopher menus
*/
void gopher_menu(state *st)
{
FILE *fp;
sdirent dir[MAX_SDIRENT];
struct tm *ltime;
struct stat file;
char buf[BUFSIZE];
char pathname[BUFSIZE];
char displayname[BUFSIZE];
char encodedname[BUFSIZE];
char timestr[20];
char sizestr[20];
char *parent;
char *c;
char type;
int width;
int num;
int i;
int n;
/* Check for a gophermap */
snprintf(pathname, sizeof(pathname), "%s/%s",
st->req_realpath, st->map_file);
if (stat(pathname, &file) == OK &&
(file.st_mode & S_IFMT) == S_IFREG) {
/* Parse gophermap */
if (gophermap(st, pathname, 0) == QUIT) {
footer(st);
return;
}
}
else {
/* Check for a gophertag */
snprintf(pathname, sizeof(pathname), "%s/%s",
st->req_realpath, st->tag_file);
if (stat(pathname, &file) == OK &&
(file.st_mode & S_IFMT) == S_IFREG) {
/* Read & output gophertag */
if ((fp = fopen(pathname , "r"))) {
fgets(buf, sizeof(buf), fp);
chomp(buf);
info(st, buf, TYPE_TITLE);
info(st, EMPTY, TYPE_INFO);
fclose(fp);
}
}
/* No gophermap or tag found - print default header */
else if (st->opt_header) {
/* Use the selector as menu title */
sstrlcpy(displayname, st->req_selector);
/* Shorten too long titles */
while (strlen(displayname) > (st->out_width - sizeof(HEADER_FORMAT))) {
if ((c = strchr(displayname, '/')) == NULL) break;
if (!*++c) break;
sstrlcpy(displayname, c);
}
/* Output menu title */
snprintf(buf, sizeof(buf), HEADER_FORMAT, displayname);
info(st, buf, TYPE_TITLE);
info(st, EMPTY, TYPE_INFO);
}
}
/* Scan the directory */
num = sortdir(st->req_realpath, dir, MAX_SDIRENT);
if (num < 0) die(st, ERR_NOTFOUND, "WTF?");
/* Create link to parent directory */
if (st->opt_parent) {
sstrlcpy(buf, st->req_selector);
parent = dirname(buf);
/* Root has no parent */
if (strcmp(st->req_selector, ROOT) != MATCH) {
/* Prevent double-slash */
if (strcmp(parent, ROOT) == MATCH) parent++;
/* Print link */
printf("1%-*s\t%s/\t%s\t%i" CRLF,
st->opt_date ? (st->out_width - 1) : (int) strlen(PARENT),
PARENT, parent, st->server_host, st->server_port);
}
}
/* Width of filenames for fancy listing */
width = st->out_width - DATE_WIDTH - 15;
/* Loop through the directory entries */
for (i = 0; i < num; i++) {
/* Get full path+name */
snprintf(pathname, sizeof(pathname), "%s/%s",
st->req_realpath, dir[i].name);
/* Skip dotfiles and non world-readables */
if (dir[i].name[0] == '.') continue;
if ((dir[i].mode & S_IROTH) == 0) continue;
/* Skip gophermaps and tags (but not dirs) */
if ((dir[i].mode & S_IFMT) != S_IFDIR) {
if (strcmp(dir[i].name, st->map_file) == MATCH) continue;
if (strcmp(dir[i].name, st->tag_file) == MATCH) continue;
}
/* Skip files marked for hiding */
for (n = 0; n < st->hidden_count; n++)
if (strcmp(dir[i].name, st->hidden[n]) == MATCH) break;
if (n < st->hidden_count) continue; /* Cruel hack... */
/* Generate display name with correct output charset */
if (st->opt_iconv)
sstrniconv(st->out_charset, displayname, dir[i].name);
else
sstrlcpy(displayname, dir[i].name);
/* #OCT-encode filename */
strnencode(encodedname, dir[i].name, sizeof(encodedname));
/* Handle inline .gophermap */
if (strstr(displayname, st->map_file) > displayname) {
gophermap(st, pathname, 0);
continue;
}
/* Handle directories */
if ((dir[i].mode & S_IFMT) == S_IFDIR) {
/* Check for a gophertag */
snprintf(buf, sizeof(buf), "%s/%s",
pathname, st->tag_file);
if (stat(buf, &file) == OK &&
(file.st_mode & S_IFMT) == S_IFREG) {
/* Use the gophertag as displayname */
if ((fp = fopen(buf , "r"))) {
fgets(buf, sizeof(buf), fp);
chomp(buf);
fclose(fp);
/* Skip empty gophertags */
if (*buf) {
/* Convert to output charset */
if (st->opt_iconv) sstrniconv(st->out_charset, displayname, buf);
else sstrlcpy(displayname, buf);
}
}
}
/* Dir listing with dates */
if (st->opt_date) {
ltime = localtime(&dir[i].mtime);
strftime(timestr, sizeof(timestr), DATE_FORMAT, ltime);
/* Hack to get around UTF-8 byte != char */
n = width - strcut(displayname, width);
strrepeat(buf, ' ', n);
printf("1%s%s %s - \t%s%s/\t%s\t%i" CRLF,
displayname,
buf,
timestr,
st->req_selector,
encodedname,
st->server_host,
st->server_port);
}
/* Regular dir listing */
else {
strcut(displayname, st->out_width);
printf("1%s\t%s%s/\t%s\t%i" CRLF,
displayname,
st->req_selector,
encodedname,
st->server_host,
st->server_port);
}
continue;
}
/* Skip special files (sockets, fifos etc) */
if ((dir[i].mode & S_IFMT) != S_IFREG) continue;
/* Get file type */
type = gopher_filetype(st, pathname, st->opt_magic);
/* File listing with dates & sizes */
if (st->opt_date) {
ltime = localtime(&dir[i].mtime);
strftime(timestr, sizeof(timestr), DATE_FORMAT, ltime);
strfsize(sizestr, dir[i].size, sizeof(sizestr));
/* Hack to get around UTF-8 byte != char */
n = width - strcut(displayname, width);
strrepeat(buf, ' ', n);
printf("%c%s%s %s %s\t%s%s\t%s\t%i" CRLF, type,
displayname,
buf,
timestr,
sizestr,
st->req_selector,
encodedname,
st->server_host,
st->server_port);
}
/* Regular file listing */
else {
strcut(displayname, st->out_width);
printf("%c%s\t%s%s\t%s\t%i" CRLF, type,
displayname,
st->req_selector,
encodedname,
st->server_host,
st->server_port);
}
}
/* Print footer */
footer(st);
}

186
options.c Normal file
View File

@ -0,0 +1,186 @@
/*
* Gophernicus - Copyright (c) 2009-2012 Kim Holviala <kim@holviala.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "gophernicus.h"
/*
* Add one suffix->filetype mapping to the filetypes array
*/
void add_ftype_mapping(state *st, char *suffix)
{
char *type;
int i;
/* Let's not do anything stupid */
if (!*suffix) return;
if (!(type = strchr(suffix, '='))) return;
/* Extract type from the suffix=X string */
*type++ = '\0';
if (!*type) return;
/* Loop through the filetype array */
for (i = 0; i < st->filetype_count; i++) {
/* Old entry found? */
if (strcasecmp(st->filetype[i].suffix, suffix) == MATCH) {
st->filetype[i].type = *type;
return;
}
}
/* No old entry found - add new entry */
if (i < MAX_FILETYPES) {
sstrlcpy(st->filetype[i].suffix, suffix);
st->filetype[i].type = *type;
st->filetype_count++;
}
}
/*
* Add one selector rewrite mapping to the array
*/
void add_rewrite_mapping(state *st, char *match)
{
char *replace;
/* Check input and split it into match & replace */
if (!*match) return;
if (!(replace = strchr(match, '='))) return;
*replace++ = '\0';
if (!*replace) return;
/* Insert match/replace values into the array */
if (st->rewrite_count < MAX_REWRITE) {
sstrlcpy(st->rewrite[st->rewrite_count].match, match);
sstrlcpy(st->rewrite[st->rewrite_count].replace, replace);
st->rewrite_count++;
}
}
/*
* Parse command-line arguments
*/
void parse_args(state *st, int argc, char *argv[])
{
FILE *fp;
static const char readme[] = README;
static const char license[] = LICENSE;
struct stat file;
char buf[BUFSIZE];
int opt;
/* Parse args */
while ((opt = getopt(argc, argv, "h:p:r:t:g:a:c:u:m:l:w:o:s:i:k:f:e:R:D:L:A:P:n:db?-")) != ERROR) {
switch(opt) {
case 'h': sstrlcpy(st->server_host, optarg); break;
case 'p': st->server_port = atoi(optarg); break;
case 'r': sstrlcpy(st->server_root, optarg); break;
case 't': st->default_filetype = *optarg; break;
case 'g': sstrlcpy(st->map_file, optarg); break;
case 'a': sstrlcpy(st->map_file, optarg); break;
case 'c': sstrlcpy(st->cgi_file, optarg); break;
case 'u': sstrlcpy(st->user_dir, optarg); break;
case 'm': /* obsolete, replaced by -l */
case 'l': sstrlcpy(st->log_file, optarg); break;
case 'w': st->out_width = atoi(optarg); break;
case 'o':
if (sstrncasecmp(optarg, "UTF-8") == MATCH) st->out_charset = UTF_8;
if (sstrncasecmp(optarg, "ISO-8859-1") == MATCH) st->out_charset = ISO_8859_1;
break;
case 's': st->session_timeout = atoi(optarg); break;
case 'i': st->session_max_kbytes = abs(atoi(optarg)); break;
case 'k': st->session_max_hits = abs(atoi(optarg)); break;
case 'f': sstrlcpy(st->filter_dir, optarg); break;
case 'e': add_ftype_mapping(st, optarg); break;
case 'R': add_rewrite_mapping(st, optarg); break;
case 'D': sstrlcpy(st->server_description, optarg); break;
case 'L': sstrlcpy(st->server_location, optarg); break;
case 'A': sstrlcpy(st->server_admin, optarg); break;
case 'n':
if (*optarg == 'v') { st->opt_vhost = FALSE; break; }
if (*optarg == 'l') { st->opt_parent = FALSE; break; }
if (*optarg == 'h') { st->opt_header = FALSE; break; }
if (*optarg == 'f') { st->opt_footer = FALSE; break; }
if (*optarg == 'd') { st->opt_date = FALSE; break; }
if (*optarg == 'c') { st->opt_magic = FALSE; break; }
if (*optarg == 'o') { st->opt_iconv = FALSE; break; }
if (*optarg == 'q') { st->opt_query = FALSE; break; }
if (*optarg == 's') { st->opt_syslog = FALSE; break; }
if (*optarg == 'a') { st->opt_caps = FALSE; break; }
if (*optarg == 'm') { st->opt_shm = FALSE; break; }
if (*optarg == 'r') { st->opt_root = FALSE; break; }
break;
case 'd': st->debug = TRUE; break;
case 'b': puts(license); exit(EXIT_SUCCESS);
default : puts(readme); exit(EXIT_SUCCESS);
}
}
/* Sanitize options */
if (st->out_width > MAX_WIDTH) st->out_width = MAX_WIDTH;
if (st->out_width < MIN_WIDTH) st->out_width = MIN_WIDTH;
if (st->out_width < MIN_WIDTH + DATE_WIDTH) st->opt_date = FALSE;
if (!st->opt_syslog) st->debug = FALSE;
/* Primary vhost directory must exist or we disable vhosting */
if (st->opt_vhost) {
snprintf(buf, sizeof(buf), "%s/%s", st->server_root, st->server_host);
if (stat(buf, &file) == ERROR) st->opt_vhost = FALSE;
}
/* If -D arg looks like a file load the file contents */
if (*st->server_description == '/') {
if ((fp = fopen(st->server_description , "r"))) {
fgets(st->server_description, sizeof(st->server_description), fp);
chomp(st->server_description);
fclose(fp);
}
else strclear(st->server_description);
}
/* If -L arg looks like a file load the file contents */
if (*st->server_location == '/') {
if ((fp = fopen(st->server_location , "r"))) {
fgets(st->server_location, sizeof(st->server_location), fp);
chomp(st->server_location);
fclose(fp);
}
else strclear(st->server_location);
}
}

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>org.gophernicus.server</string>
<key>ProgramArguments</key>
<array>
<string>/usr/sbin/in.gophernicus</string>
<string>-h@HOSTNAME@</string>
<string>-r/Library/GopherServer</string>
</array>
<key>Sockets</key>
<dict>
<key>Listeners</key>
<dict>
<key>SockServiceName</key>
<string>gopher</string>
</dict>
</dict>
<key>UserName</key>
<string>nobody</string>
<key>inetdCompatibility</key>
<dict>
<key>Wait</key>
<false/>
</dict>
</dict>
</plist>

277
platform.c Normal file
View File

@ -0,0 +1,277 @@
/*
* Gophernicus - Copyright (c) 2009-2012 Kim Holviala <kim@holviala.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "gophernicus.h"
/*
* Get OS name, version & architecture we're running on
*/
void platform(state *st)
{
#ifdef HAVE_UNAME
#if defined(_AIX) || defined(__linux) || defined(__APPLE__)
FILE *fp;
#endif
#if defined(__arm__) || defined(__mips__) || defined(__APPLE__)
char buf[BUFSIZE];
#endif
#ifdef __linux
struct stat file;
#endif
struct utsname name;
char sysname[64];
char release[64];
char machine[64];
char *c;
/* Fetch system information */
uname(&name);
strclear(sysname);
strclear(release);
strclear(machine);
/* AIX-specific */
#ifdef _AIX
/* Fix uname() results */
sstrlcpy(machine, "powerpc");
snprintf(release, sizeof(release), "%s.%s",
name.version,
name.release);
/* Get CPU type */
if ((fp = popen("/usr/sbin/getsystype -i", "r"))) {
fgets(machine, sizeof(machine), fp);
pclose(fp);
strreplace(machine, ' ', '_');
chomp(machine);
}
/* Get hardware name using shell uname */
if (!*st->server_description &&
(fp = popen("/usr/bin/uname -M", "r"))) {
fgets(st->server_description,
sizeof(st->server_description), fp);
pclose(fp);
strreplace(st->server_description, ',', ' ');
chomp(st->server_description);
}
#endif
/* Mac OS X, just like Unix but totally different... */
#ifdef __APPLE__
/* Hardcode OS name */
sstrlcpy(sysname, "MacOSX");
/* Get OS X version */
if ((fp = popen("/usr/bin/sw_vers -productVersion", "r"))) {
fgets(release, sizeof(release), fp);
chomp(release);
pclose(fp);
}
/* Get hardware name */
if (!*st->server_description &&
(fp = popen("/usr/sbin/sysctl -n hw.model", "r"))) {
/* Read hardware name */
fgets(buf, sizeof(buf), fp);
pclose(fp);
/* Clones are gone now so we'll hardcode the manufacturer */
sstrlcpy(st->server_description, "Apple ");
sstrlcat(st->server_description, buf);
/* Remove hardware revision */
for (c = st->server_description; *c; c++)
if (*c >= '0' && *c <= '9') { *c = '\0'; break; }
}
#endif
/* Linux uname() just says Linux/2.6 - let's dig deeper... */
#ifdef __linux
/* Most Linux ARM/MIPS boards have hardware name in /proc/cpuinfo */
#if defined(__arm__) || defined(__mips__)
if (!*st->server_description && (fp = fopen("/proc/cpuinfo" , "r"))) {
while (fgets(buf, sizeof(buf), fp)) {
#ifdef __arm__
if ((c = strkey(buf, "Hardware"))) {
#else
if ((c = strkey(buf, "machine"))) {
#endif
sstrlcpy(st->server_description, c);
chomp(st->server_description);
break;
}
}
fclose(fp);
}
#endif
/* Identify RedHat */
if (!*sysname && (fp = fopen("/etc/redhat-release", "r"))) {
fgets(sysname, sizeof(sysname), fp);
fclose(fp);
if ((c = strstr(sysname, "release "))) sstrlcpy(release, c + 8);
if ((c = strchr(release, ' '))) *c = '\0';
if ((c = strchr(sysname, ' '))) *c = '\0';
if (strcmp(sysname, "Red") == MATCH) sstrlcpy(sysname, "RedHat");
}
/* Identify Slackware */
if (!*sysname && (fp = fopen("/etc/slackware-version", "r"))) {
fgets(sysname, sizeof(sysname), fp);
fclose(fp);
if ((c = strchr(sysname, ' '))) {
sstrlcpy(release, c + 1);
*c = '\0';
}
}
/* Uh-oh.... how about a standard Linux with lsb_release? */
if (stat("/usr/bin/lsb_release", &file) == OK && (file.st_mode & S_IXOTH)) {
if (!*sysname && (fp = popen("/usr/bin/lsb_release -i -s", "r"))) {
fgets(sysname, sizeof(sysname), fp);
chomp(sysname);
pclose(fp);
}
if (!*release && (fp = popen("/usr/bin/lsb_release -r -s", "r"))) {
fgets(release, sizeof(release), fp);
chomp(release);
pclose(fp);
}
}
/* OK, nothing worked - let's try /etc/issue for sysname */
if (!*sysname && (fp = fopen("/etc/issue", "r"))) {
fgets(sysname, sizeof(sysname), fp);
fclose(fp);
if ((c = strchr(sysname, ' '))) *c = '\0';
if ((c = strchr(sysname, '\\'))) *c = '\0';
chomp(sysname);
}
/* Debian version should be in /etc/debian_version */
if (!*release && (fp = fopen("/etc/debian_version", "r"))) {
fgets (release, sizeof(release), fp);
fclose(fp);
if ((c = strchr(release, '/'))) *c = '\0';
chomp(release);
}
#endif
/* Haiku OS */
#ifdef __HAIKU__
/* Fix release name */
snprintf(release, sizeof(release), "R%s", name.release);
#endif
/* Fill in the blanks using uname() data */
if (!*sysname) sstrlcpy(sysname, name.sysname);
if (!*release) sstrlcpy(release, name.release);
if (!*machine) sstrlcpy(machine, name.machine);
/* We're only interested in major.minor version */
if ((c = strchr(release, '.'))) if ((c = strchr(c + 1, '.'))) *c = '\0';
if ((c = strchr(release, '-'))) *c = '\0';
if ((c = strchr(release, '/'))) *c = '\0';
/* Create a nicely formatted platform string */
snprintf(st->server_platform, sizeof(st->server_platform), "%s/%s %s",
sysname,
#if defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__NetBSD__)
machine,
release);
#else
release,
machine);
#endif
/* Debug */
if (st->debug) {
syslog(LOG_INFO, "generated platform string \"%s\"",
st->server_platform);
}
#else
/* Fallback reply */
sstrlcpy(st->server_platform, "Unknown computer-like system");
#endif
}
/*
* Return current CPU load
*/
float loadavg(void)
{
FILE *fp;
char buf[BUFSIZE];
/* Faster Linux version */
#ifdef __linux
buf[0] = '\0';
if ((fp = fopen("/proc/loadavg" , "r")) == NULL) return 0;
fgets(buf, sizeof(buf), fp);
fclose(fp);
return (float) atof(buf);
/* Generic slow version - parse the output of uptime */
#else
#ifdef HAVE_POPEN
char *c;
if ((fp = popen("/usr/bin/uptime", "r"))) {
fgets(buf, sizeof(buf), fp);
pclose(fp);
if ((c = strstr(buf, "average: ")) || (c = strstr(buf, "averages: ")))
return (float) atof(c + 10);
}
#endif
/* Fallback reply */
return 0;
#endif
}

144
session.c Normal file
View File

@ -0,0 +1,144 @@
/*
* Gophernicus - Copyright (c) 2009-2012 Kim Holviala <kim@holviala.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "gophernicus.h"
/*
* Locate shared memory session ID
*/
#ifdef HAVE_SHMEM
int get_shm_session_id(state *st, shm_state *shm)
{
time_t now;
int i;
/* Get current time */
now = time(NULL);
/* Locate user's old session using remote_addr */
for (i = 0; i < SHM_SESSIONS; i++) {
if (strcmp(st->req_remote_addr, shm->session[i].req_remote_addr) == MATCH &&
(now - shm->session[i].req_atime) < st->session_timeout) break;
}
/* Return -1 on error */
if (i == SHM_SESSIONS) return ERROR;
else return i;
}
#endif
/*
* Get shared memory session data
*/
#ifdef HAVE_SHMEM
void get_shm_session(state *st, shm_state *shm)
{
int i;
/* Get session id */
if ((i = get_shm_session_id(st, shm)) == ERROR) return;
/* Get session data */
if (st->opt_vhost) {
sstrlcpy(st->server_host, shm->session[i].server_host);
st->server_port = shm->session[i].server_port;
}
}
#endif
/*
* Update shared memory session data
*/
#ifdef HAVE_SHMEM
void update_shm_session(state *st, shm_state *shm)
{
time_t now;
char buf[BUFSIZE];
int delay;
int i;
/* Get current time */
now = time(NULL);
/* No existing session found? */
if ((i = get_shm_session_id(st, shm)) == ERROR) {
/* Look for an empty/expired session slot */
for (i = 0; i < SHM_SESSIONS; i++) {
if ((now - shm->session[i].req_atime) > st->session_timeout) {
/* Found slot -> initialize it */
sstrlcpy(shm->session[i].req_remote_addr, st->req_remote_addr);
shm->session[i].hits = 0;
shm->session[i].kbytes = 0;
break;
}
}
}
/* No available session slot found? */
if (i == SHM_SESSIONS) return;
/* Get referrer from old session data */
if (*shm->session[i].server_host) {
snprintf(buf, sizeof(buf), "gopher://%s:%i/%c%s",
shm->session[i].server_host,
shm->session[i].server_port,
shm->session[i].req_filetype,
shm->session[i].req_selector);
sstrlcpy(st->req_referrer, buf);
}
/* Update session data */
sstrlcpy(shm->session[i].server_host, st->server_host);
shm->session[i].server_port = st->server_port;
sstrlcpy(shm->session[i].req_selector, st->req_selector);
shm->session[i].req_filetype = st->req_filetype;
shm->session[i].req_atime = now;
shm->session[i].hits++;
shm->session[i].kbytes += st->req_filesize / 1024;
/* Transfer limits exceeded? */
if ((st->session_max_kbytes && shm->session[i].kbytes > st->session_max_kbytes) ||
(st->session_max_hits && shm->session[i].hits > st->session_max_hits)) {
/* Calculate throttle delay */
delay = max(shm->session[i].kbytes / st->session_max_kbytes,
shm->session[i].hits / st->session_max_hits);
/* Throttle user */
syslog(LOG_INFO, "throttling user from %s for %i seconds",
st->req_remote_addr, delay);
sleep(delay);
}
}
#endif

416
string.c Normal file
View File

@ -0,0 +1,416 @@
/*
* Gophernicus - Copyright (c) 2009-2012 Kim Holviala <kim@holviala.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "gophernicus.h"
/*
* Repeat a character num times and zero-terminate
*/
void strrepeat(char *dest, char c, size_t num)
{
memset(dest, c, num);
dest[num] = '\0';
}
/*
* Replace characters in-place
*/
void strreplace(char *str, char from, char to)
{
while (*str) {
if (*str == from) *str = to;
str++;
}
}
/*
* Cut string to width, return resulting width (UTF-8 aware)
*/
int strcut(char *str, size_t width)
{
unsigned char c;
int w = 0;
int i;
while (width-- && (c = *str++)) {
if (c >= 0x80 && (*str & 0xc0) == 0x80) {
i = 0;
if ((c & 0xf8) == 0xf0) i = 3;
else if ((c & 0xf0) == 0xe0) i = 2;
else if ((c & 0xe0) == 0xc0) i = 1;
while (i--) if (!*str++) break;
}
w++;
}
*str = '\0';
return w;
}
/*
* Match key and return value (key: value)
*/
char *strkey(char *header, char *key)
{
char *c;
size_t len;
if ((len = strlen(key)) == 0) return NULL;
if (strncasecmp(header, key, len) == MATCH) {
c = header + len;
do { c++; } while (*c == ' ' || *c == '\t');
if (*c != ':') return NULL;
do { c++; } while (*c == ' ' || *c == '\t');
return c;
}
return NULL;
}
/*
* Return last character of a string
*/
char strlast(char *str)
{
int len;
if ((len = (int)strlen(str) - 1) >= 0) return str[len];
else return 0;
}
/*
* Remove CRLF from a string
*/
void chomp(char *str)
{
char *c;
if ((c = strrchr(str, '\n'))) *c = '\0';
if ((c = strrchr(str, '\r'))) *c = '\0';
}
/*
* Return charset name
*/
char *strcharset(int charset)
{
if (charset == AUTO) return "auto";
if (charset == US_ASCII) return "US-ASCII";
if (charset == ISO_8859_1) return "ISO-8859-1";
if (charset == UTF_8) return "UTF-8";
return "(unknown)";
}
/*
* Convert a string between UTF-8, ISO-8859-1 and US-ASCII
*/
void strniconv(int charset, char *out, char *in, size_t outsize)
{
char ascii[] = ASCII;
unsigned long c;
size_t len;
int i;
/* Loop through the input string */
len = strlen(in);
while (--outsize && len > 0) {
/* Get one input char */
c = (unsigned char) *in++;
len--;
/* 7-bit chars are the same in all three charsets */
if (c < 0x80) {
*out++ = (unsigned char) c;
continue;
}
/* Assume ISO-8859-1 which requires 0 extra bytes */
i = 0;
/* UTF-8? (We'll actually check the next char here, not current) */
if ((*in & 0xc0) == 0x80) {
/* Four-byte UTF-8? */
if ((c & 0xf8) == 0xf0 && len >= 3) { c &= 0x07; i = 3; }
/* Three-byte UTF-8? */
else if ((c & 0xf0) == 0xe0 && len >= 2) { c &= 0x0f; i = 2; }
/* Two-byte UTF-8? */
else if ((c & 0xe0) == 0xc0 && len >= 1) { c &= 0x1f; i = 1; }
/* Parse rest of the UTF-8 bytes */
while (i--) {
c <<= 6;
c |= *in++ & 0x3f;
len--;
}
}
/*
* At this point we've got one 32bit UTF character in c and
* we're ready to convert it to the specified output charset
*/
/* Handle UTF-8 */
if (charset == UTF_8) {
i = 0;
/* Two-byte encoding? */
if (c < 0x800 && outsize > 2) { *out++ = (c >> 6) | 0xc0; i = 1; }
/* Three-byte encoding? */
else if (c < 0x10000 && outsize > 3) { *out++ = (c >> 12) | 0xe0; i = 2; }
/* Four-byte encoding? */
else if (c < 0x110000 && outsize > 4) { *out++ = (c >> 18) | 0xf0; i = 3; }
/* Encode rest of the UTF-8 bytes */
while (i--) {
*out++ = ((c >> (i * 6)) & 0x3f) | 0x80;
outsize--;
}
continue;
}
/* Handle ISO-8859-1 */
if (charset == ISO_8859_1) {
if (c >= 0xa0 && c <= 0xff)
*out++ = (unsigned char) c;
else
*out++ = UNKNOWN;
continue;
}
/* Handle all other charsets as 7-bit US-ASCII */
if (c >= 0x80 && c <= 0xff)
*out++ = ascii[c - 0x80];
else
*out++ = UNKNOWN;
}
/* Zero-terminate output */
*out = '\0';
}
/*
* Encode string with #OCT encoding
*/
void strnencode(char *out, const char *in, size_t outsize)
{
unsigned char c;
/* Loop through the input string */
while (--outsize) {
/* End of source? */
if (!(c = *in++)) break;
/* Need to encode the char? */
if (c < '+' || c > '~') {
/* Can we fit the encoded version into outbuffer? */
if (outsize < 5) break;
/* Output encoded char */
snprintf(out, outsize, "#%.3o", c);
out += 4;
}
/* Copy regular chars */
else *out++ = c;
}
/* Zero-terminate output */
*out = '\0';
}
/*
* Decode both %HEX and #OCT encodings
*/
void strndecode(char *out, char *in, size_t outsize)
{
unsigned char c;
unsigned int i;
/* Loop through the input string */
while (--outsize) {
/* End of source? */
if (!(c = *in++)) break;
/* Parse %hex encoding */
if (c == '%' && strlen(in) >= 2) {
sscanf(in, "%2x", &i);
*out++ = i;
in += 2;
continue;
}
/* Parse #octal encoding */
if (c == '#' && strlen(in) >= 3) {
sscanf(in, "%3o", &i);
*out++ = i;
in += 3;
continue;
}
/* Copy non-encoded chars */
*out++ = c;
}
/* Zero-terminate output */
*out = '\0';
}
/*
* Format number to human-readable filesize with unit
*/
void strfsize(char *out, off_t size, size_t outsize)
{
static char *unit[] = { UNITS };
int u;
float s;
/* Start with kilobytes */
s = ((float) size) / 1024;
u = 0;
/* Loop through the units until the size is small enough */
while (s >= 1000 && unit[(u + 1)]) {
s = s / 1024;
u++;
}
/* Format size */
snprintf(out, outsize, "%7.1f %s", s, unit[u]);
}
#ifndef HAVE_STRLCPY
/*
* Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/types.h>
#include <string.h>
/*
* Copy src to string dst of size siz. At most siz-1 characters
* will be copied. Always NUL terminates (unless siz == 0).
* Returns strlen(src); if retval >= siz, truncation occurred.
*/
size_t strlcpy(char *dst, const char *src, size_t siz)
{
char *d = dst;
const char *s = src;
size_t n = siz;
/* Copy as many bytes as will fit */
if (n != 0) {
while (--n != 0) {
if ((*d++ = *s++) == '\0')
break;
}
}
/* Not enough room in dst, add NUL and traverse rest of src */
if (n == 0) {
if (siz != 0)
*d = '\0'; /* NUL-terminate dst */
while (*s++)
;
}
return(s - src - 1); /* count does not include NUL */
}
/*
* Appends src to string dst of size siz (unlike strncat, siz is the
* full size of dst, not space left). At most siz-1 characters
* will be copied. Always NUL terminates (unless siz <= strlen(dst)).
* Returns strlen(src) + MIN(siz, strlen(initial dst)).
* If retval >= siz, truncation occurred.
*/
size_t strlcat(char *dst, const char *src, size_t siz)
{
char *d = dst;
const char *s = src;
size_t n = siz;
size_t dlen;
/* Find the end of dst and adjust bytes left but don't go past end */
while (n-- != 0 && *d != '\0')
d++;
dlen = d - dst;
n = siz - dlen;
if (n == 0)
return(dlen + strlen(s));
while (*s != '\0') {
if (n != 1) {
*d++ = *s;
n--;
}
s++;
}
*d = '\0';
return(dlen + (s - src)); /* count does not include NUL */
}
#endif