mirror of
https://github.com/gophernicus/gophernicus.git
synced 2025-05-18 00:58:35 -04:00
Initial git commit with version 1.4
This commit is contained in:
commit
d829e6a347
251
ChangeLog
Normal file
251
ChangeLog
Normal 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
113
INSTALL
Normal 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
22
LICENSE
Normal 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
248
Makefile
Normal 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
237
README
Normal 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
109
README.Gophermap
Normal 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
10
TODO
Normal 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
67
bin2c.c
Executable 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
37
debian/changelog
vendored
Normal 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
1
debian/compat
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
5
|
16
debian/control
vendored
Normal file
16
debian/control
vendored
Normal 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
22
debian/copyright
vendored
Normal 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
4
debian/dirs
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
usr/sbin
|
||||||
|
var/gopher
|
||||||
|
var/log/gophernicus
|
||||||
|
usr/lib/gophernicus/filters
|
5
debian/docs
vendored
Normal file
5
debian/docs
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
README
|
||||||
|
README.Gophermap
|
||||||
|
TODO
|
||||||
|
INSTALL
|
||||||
|
gophertag
|
14
debian/gophernicus.config
vendored
Executable file
14
debian/gophernicus.config
vendored
Executable 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
|
||||||
|
|
3
debian/gophernicus.logcheck.ignore.server
vendored
Normal file
3
debian/gophernicus.logcheck.ignore.server
vendored
Normal 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
7
debian/gophernicus.logrotate
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
/var/log/gophernicus/server.log {
|
||||||
|
rotate 6
|
||||||
|
weekly
|
||||||
|
compress
|
||||||
|
missingok
|
||||||
|
notifempty
|
||||||
|
}
|
4
debian/gophernicus.templates
vendored
Normal file
4
debian/gophernicus.templates
vendored
Normal 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
37
debian/postinst
vendored
Normal 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
8
debian/postrm
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
if [ "$1" = "purge" ]; then
|
||||||
|
update-inetd --remove "#<off># gopher"
|
||||||
|
fi
|
||||||
|
|
||||||
|
#DEBHELPER#
|
||||||
|
|
6
debian/prerm
vendored
Normal file
6
debian/prerm
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
update-inetd --disable gopher
|
||||||
|
|
||||||
|
#DEBHELPER#
|
||||||
|
|
85
debian/rules
vendored
Executable file
85
debian/rules
vendored
Executable 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
|
25
examples/counter/counter.sh
Executable file
25
examples/counter/counter.sh
Executable 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
402
file.c
Normal 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
36
gophermap
Normal 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
735
gophernicus.c
Normal 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
410
gophernicus.h
Normal 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
11
gophernicus.xinetd
Normal 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
|
||||||
|
}
|
520
install-sh
Executable file
520
install-sh
Executable 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
665
menu.c
Normal 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
186
options.c
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
29
org.gophernicus.server.plist
Normal file
29
org.gophernicus.server.plist
Normal 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
277
platform.c
Normal 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
144
session.c
Normal 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
416
string.c
Normal 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
|
Loading…
x
Reference in New Issue
Block a user