mirror of
https://github.com/gophernicus/gophernicus.git
synced 2024-11-03 04:27:17 -05:00
Initial git commit with version 1.4
This commit is contained in:
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…
Reference in New Issue
Block a user