pax_global_header00006660000000000000000000000064137742206140014520gustar00rootroot0000000000000052 comment=0dc47480f140d2b72e751f64bdffea554b58adb9 gophernicus-3.1.1/000077500000000000000000000000001377422061400140505ustar00rootroot00000000000000gophernicus-3.1.1/.gitignore000066400000000000000000000001641377422061400160410ustar00rootroot00000000000000# # Build leftovers # src/*.o src/files.h src/filetypes.h src/bin2c src/gophernicus Makefile README README.options gophernicus-3.1.1/.travis.yml000066400000000000000000000007621377422061400161660ustar00rootroot00000000000000language: c group: travis_latest dist: bionic git: depth: false quiet: true os: - linux - osx compiler: - clang - gcc addons: apt: packages: - build-essential - fakeroot - libwrap0-dev - debhelper script: - if [ "$TRAVIS_OS_NAME" = "linux" ]; then ./configure --listener=systemd,inetd,xinetd --hostname=travis; fi - if [ "$TRAVIS_OS_NAME" = "osx" ]; then ./configure --listener=mac --hostname=travis; fi - make - sudo make install - .travis/test.sh gophernicus-3.1.1/.travis/000077500000000000000000000000001377422061400154365ustar00rootroot00000000000000gophernicus-3.1.1/.travis/test.answer000066400000000000000000000002221377422061400176320ustar00rootroot00000000000000iA null.host 1 iLine null.host 1 iRight null.host 1 iHere null.host 1 iFor null.host 1 iTests null.host 1 0file file localhost 70 . gophernicus-3.1.1/.travis/test.gophermap000066400000000000000000000000731377422061400203210ustar00rootroot00000000000000A Line Right Here For Tests 0file file localhost 70 gophernicus-3.1.1/.travis/test.sh000077500000000000000000000007701377422061400167600ustar00rootroot00000000000000#!/bin/bash if uname | grep -q 'Darwin' ; then # I do not have hardware to make this work on and am uninterested in # making another testsuite for travis Macs at this time exit 0 fi sudo cp .travis/test.gophermap /var/gopher/gophermap sudo chmod 644 /var/gopher/gophermap echo -e "\n" | gophernicus -nf -nu -nv -nx -nf -nd > test.output if ! cmp .travis/test.answer test.output ; then exit 1 fi if ! gophernicus -v | grep -q 'Gophernicus/3.1.1 "Dungeon Edition"' ; then exit 1 fi gophernicus-3.1.1/INSTALL.md000066400000000000000000000112451377422061400155030ustar00rootroot00000000000000# 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. Please make sure that you checkout to the correct version you want. Currently, you most likely want version 3.1.1. To compile and install run: ``` $ git clone -b 3.1.1 https://github.com/gophernicus/gophernicus.git $ cd gophernicus $ ./configure --listener=somelistener $ make $ sudo make install ``` Important configure arguments include: - `--listener`. This is the only required argument. You must choose a listener that passes network requests to gophernicus, as gophernicus dosen't do this by itself. The options are: - systemd, a common init system on many Linux distributions that can do this without an external program. - inetd, an older, well-known implementation that is very simple. - xinetd, a modern reimplementation of inetd using specific config files. - mac, to be used on Mac OSX machines. - haiku, to be used on Haiku machines. - autodetect, which looks at what you have avaliable (unrecommended, please manually specify where possible). - `--hostname`. This is by default attempted to be autodetected by the configure script, using the command `hostname`. It is expected to be the publicly-accessible address of the server. However, this might be completely wrong, especially on your personal machine at home or on some cheap VPS. If you know you have a fixed numerical IP, you can also directly use that. For testing, just keep the default value of `localhost` which will result in selectors working only when you're connecting locally. - `--gopherroot`. The location in which your gopher server will serve from. By default is `/var/gopher`. Also can be changed later using the `-r ` parameter in configuration files. That's it - Gophernicus should now be installed, preconfigured and running under gopher:///. And more often than not, It Just Works(tm). ## Compiling with TCP wrappers Gophernicus uses no extra libraries... well... except libwrap (TCP wrappers) if it is installed with headers in default Unix directories at the time of compiling. If you have the headers installed and don't want wrapper support, too bad (for now, see issue #89). For configuring IP access lists with TCP wrappers, take a look at the files `/etc/hosts.allow` and `/etc/hosts.deny` (because the manual pages suck). Use the daemon name "gophernicus" to make your access lists. ## Distributions ### Debian (and -based) (including Ubuntu) distributions We used to distribute a `debian/` directory for people to `make deb` and then install a deb. However, thanks to the work of Ryan Kavanagh, gophernicus will be distributed in the official debian repositories in the next stable release! In the interim, either keep using the old version or install without deb. ## 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. ``` $ export HOSTCC=gcc CC=target-arch-gcc $ ./configure .... $ make ``` ## 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 gophernicus under various different user accounts may create a situation where the memory block is locked to the wrong user. If that happens you can simply delete the memory block and let Gophernicus recreate it - no harm done: ``` $ sudo make clean-shm ``` ## 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 `#define HAVE_...` 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 so we can include them into the next release -- or even better, commit them to your fork on Github and make a pull request! ## For packagers Are you looking to package gophernicus for a Linux distribution? Thanks! Please see issue #50 to help. Some tips: - Hostnames will need to be configured by users at runtime, the installed gophernicus.env will need to be a config file. - You probably want to support as many listeners as possible. We allow this through the use of a comma seperated list to `--listener`. - The default gopher root is `/var/gopher`; many disributions prefer `/srv`. gophernicus-3.1.1/LICENSE000066400000000000000000000026221377422061400150570ustar00rootroot00000000000000BSD 2-Clause License Copyright (c) 2009-2018 Kim Holviala Copyright (c) 2019 Gophernicus Developers All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. 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 HOLDER OR CONTRIBUTORS 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. gophernicus-3.1.1/Makefile.in000066400000000000000000000141301377422061400161140ustar00rootroot00000000000000NAME = gophernicus PACKAGE = $(NAME) BINARY = $(NAME) VERSION = 3.1.1 CODENAME = Dungeon Edition SOURCES = src/$(NAME).c src/file.c src/menu.c src/string.c src/platform.c src/session.c src/options.c src/log.c HEADERS = src/files.h src/filetypes.h OBJECTS = $(SOURCES:.c=.o) README = README.md MANPAGE = gophernicus.8 MAP = gophermap DESTDIR ?= / PREFIX = @PREFIX@ BINDIR = @BINDIR@ SBINDIR = @SBINDIR@ MANDIR = @MANDIR@ MAN8DIR = @MAN8DIR@ INSTALL = @INSTALL@ INSTALL_ARGS = -o 0 -g 0 ROOT = @ROOT@ OSXROOT = /Library/GopherServer INETD = @INETD_CONF@ XINETD = @XINETD_CONF@ # get OPTIONS line from gophernicus.env and use that also for inetd INETOPT = $$(grep '^OPTIONS=' init/$(NAME).env | tail -n 1 | sed -e 's/OPTIONS="*//;s/"*$$//') LAUNCHD = @LAUNCHD@ PLIST = org.$(NAME).server.plist HAIKU_SRV = @HAIKUSRV@ DEFAULT = @DEFAULT@ SYSCONF = @SYSCONF@ SYSTEMD = @SYSTEMD@ CC ?= @CC@ HOSTCC ?= @HOSTCC@ CFLAGS := -O2 -Wall @LIBWRAP@ $(CFLAGS) LDFLAGS := $(LDFLAGS) IPCRM ?= @IPCRM@ MAKE ?= @MAKE@ all: $(MAKE) headers $(MAKE) src/$(BINARY) headers: $(HEADERS) src/$(NAME).c: src/$(NAME).h src/$(BINARY): $(OBJECTS) $(CC) $(OBJECTS) $(CFLAGS) -o $@ $(LDFLAGS) .c.o: $(CC) -c $(CFLAGS) -DVERSION="\"$(VERSION)\"" -DCODENAME="\"$(CODENAME)\"" -DDEFAULT_ROOT="\"$(ROOT)\"" $< -o $@ src/filetypes.h: src/filetypes.conf sh src/filetypes.sh < src/filetypes.conf > $@ src/bin2c: src/bin2c.c $(HOSTCC) src/bin2c.c -o $@ src/files.h: src/bin2c sed -e '/^(end of option list)/,$$d' README.md > README.options ./src/bin2c -0 -n README README.options > $@ ./src/bin2c -0 LICENSE >> $@ ./src/bin2c -n ERROR_GIF error.gif >> $@ # Clean cases clean: @CLEAN_SHM@ rm -rf src/$(BINARY) $(OBJECTS) $(HEADERS) README.options README src/bin2c clean-shm: $(IPCRM) -M $$(awk '/SHM_KEY/ { print $$3 }' src/$(NAME).h) || true # Install cases install: src/$(BINARY) @CLEAN_SHM@ install-man @INSTALL_ROOT@ @INSTALL_OSX@ @INSTALL_HAIKU@ @INSTALL_SYSTEMD@ @INSTALL_XINETD@ @INSTALL_INETD_MANUAL@ @INSTALL_INETD_UPDATE@ $(INSTALL) -d -m 755 $(DESTDIR)$(SBINDIR) $(INSTALL) -s -m 755 -t $(DESTDIR)$(SBINDIR) src/$(BINARY) @$(MAKE) -s install-msg install-msg: echo echo "======================================================================" echo echo "If there were no errors shown above," echo "Gophernicus has now been succesfully installed." echo @INSTALL_MSG_INETD@ @INSTALL_MSG_XINETD@ @INSTALL_MSG_SYSTEMD@ @INSTALL_MSG_OSX@ @INSTALL_MSG_HAIKU@ $(MAKE) install-msg-final install-msg-inetd: echo "Please restart the inetd daemon to allow your gopher root to be" echo "accessed." echo "On systemd distributions:" echo " systemctl restart inetd" echo install-msg-xinetd: echo "Please restart the xinetd daemon to allow your gopher root to be" echo "accessed." echo "On systemd distributions:" echo " systemctl restart xinetd" echo "You can configure arguments, including the hostname, in $(INETD)." echo install-msg-systemd: echo "Please enable and start gophernicus.socket using the commands:" echo " systemctl enable gophernicus.socket" echo " systemctl start gophernicus.socket" echo "to allow your gopher root to be accessed." echo "You can configure arguments, including the hostname, in" echo "$(DEFAULT)/$(NAME) or $(SYSCONF)/$(NAME)." echo install-msg-osx: echo "Please reboot your computer to allow your gopher root to be accessed." echo "You can configure arguments, including the hostname, in $(LAUNCHD)." echo install-msg-haiku: echo "Please reboot your computer to allow your gopher root to be accessed." echo "You can configure arguments, including the hostname, in $(HAIKU_SRV)" echo "under the gophernicus section." echo install-msg-final: echo "======================================================================" echo install-man: $(INSTALL) -d -m 755 $(DESTDIR)$(MAN8DIR) $(INSTALL) -m 644 -t $(DESTDIR)$(MAN8DIR) $(MANPAGE) install-root: $(INSTALL) -d -m 755 $(DESTDIR)$(ROOT) $(INSTALL) -m 644 $(MAP).sample $(DESTDIR)$(ROOT)/gophermap install-inetd-update: install-root update-inetd --add "$$(sed -e "s:@BINARY_PATH@:$(DESTDIR)$(SBINDIR)/$(BINARY):g" -e "s/@OPTIONS@/$(INETOPT)/g" init/inetlin.in)" update-inetd --enable gopher install-inetd-manual: install-root sed -e "s:@BINARY_PATH@:$(DESTDIR)$(SBINDIR)/$(BINARY):g" -e "s/@OPTIONS@/$(INETOPT)/g" init/inetlin.in >> $(DESTDIR)$(INETD) install-xinetd: install-root $(INSTALL) -d -m 755 $(DESTDIR)/etc/xinetd.d sed -i -e "s:@BINARY@:$(DESTDIR)$(SBINDIR)/$(BINARY):g" init/$(NAME).xinetd $(INSTALL) -m 644 -T init/$(NAME).xinetd $(DESTDIR)$(XINETD) install-osx: install-root $(INSTALL) -m 644 init/$(PLIST) $(DESTDIR)$(LAUNCHD) chown -h root:admin $(DESTDIR)$(ROOT) $(DESTDIR)$(ROOT)/* chmod -h 0775 $(DESTDIR)$(ROOT) install-haiku: install-root sed -e "s/@BINARY@/$(BINARY)/g" init/haiku_snippet.in >> $(DESTDIR)$(HAIKU_SRV) chown user:root $(DESTDIR)$(DOCDIR)/* $(DESTDIR)$(SBINDIR)/$(BINARY) $(DESTDIR)$(ROOT)/$(MAP) install-systemd: install-root $(INSTALL) -d -m 755 $(DESTDIR)$(SYSCONF) $(INSTALL) -m 644 -T init/$(NAME).env $(DESTDIR)$(SYSCONF)/$(NAME) $(INSTALL) -d -m 755 $(DESTDIR)$(DEFAULT) $(INSTALL) -m 644 -T init/$(NAME).env $(DESTDIR)$(DEFAULT)/$(NAME) $(INSTALL) -d -m 755 $(DESTDIR)$(SYSTEMD) $(INSTALL) -m 644 -t $(DESTDIR)$(SYSTEMD) init/$(NAME).socket sed -i -e "s:@BINARY@:$(DESTDIR)$(SBINDIR)/$(BINARY):g" init/$(NAME)\@.service $(INSTALL) -m 644 -t $(DESTDIR)$(SYSTEMD) init/$(NAME)\@.service uninstall: @UNINSTALL_INETD_UPDATE@ @UNINSTALL_INETD_MANUAL@ @UNINSTALL_XINETD@ @UNINSTALL_OSX@ @UNINSTALL_SYSTEMD@ rm -f $(DESTDIR)$(SBINDIR)/$(BINARY) rm -f $(DESTDIR)$(MAN8DIR)/$(MANPAGE) uninstall-inetd-update: update-inetd --remove "^gopher.*gophernicus" uninstall-inetd-manual: sed -i '/^gopher/d' $(DESTDIR)$(INETD) uninstall-xinetd: rm -f $(DESTDIR)$(XINETD) uninstall-osx: rm -f $(DESTDIR)$(LAUNCHD)/$(PLIST) uninstall-haiku: rm -f $(DESTDIR)$(HAIKU_SRV) uninstall-systemd: rm -f $(DESTDIR)$(SYSCONF)/$(NAME)/$(NAME).env rm -f $(DESTDIR)$(DEFAULT)/$(NAME)/$(NAME).env rm -f $(DESTDIR)$(SYSTEMD)/$(NAME).socket rm -f $(DESTDIR)$(SYSTEMD)/$(NAME)\@.service gophernicus-3.1.1/README.gophermap000066400000000000000000000066231377422061400167200ustar00rootroot00000000000000!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 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: Xnameselectorhostport 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 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 user example.test 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 user example.test 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: * gophernicus-3.1.1/README.md000066400000000000000000000337101377422061400153330ustar00rootroot00000000000000# Gophernicus This release: Version DEVEL NOTE: The master branch is rolling Development! DO NOT USE unless you want fiery dragons! (you probably want to `git checkout 3.1.1`) *Copyright (c) 2009-2018 Kim Holviala* *Copyright (c) 2019 Gophernicus Developers* Gophernicus is a modern full-featured (and hopefully) secure gopher daemon. It is licensed under the BSD license. (If you are looking for installation documentation, please see INSTALL.md). ## Support/Contact Developers can be reached at gophernicus AT gophernicus DOT org. Our IRC channel is on irc.freenode.net #gophernicus. You most likely want to subscribe to the gophernicus mailing list at https://lists.tildeverse.org/postorius/lists/gophernicus.lists.tildeverse.org/, especially if you maintain a server. This is where all important announcements are made. ## Command line options -h hostname Change server hostname (FQDN) [$HOSTNAME] -p port Change server port [70] -T port Change TLS/SSL port [0 = disabled] -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 [67] -o charset Change default output charset [UTF-8] -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 -U paths Specify a colon-separated list of extra unveil(2) paths (OpenBSD only). -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 -nt Disable /server-status -nm Disable shared memory use (for debugging) -nr Disable root user checking (for debugging) -np Disable HAproxy proxy protocol -nx Disable execution of gophermaps and scripts -nu Disable personal gopherspaces -d Debug output in syslog and /server-status -v Display version number and build date -b Display the BSD license -? Display this help (end of option list) -- keep this line for automatic extraction! ## Setting up a gopher site After succesfully installing Gophernicus (see [INSTALL](./INSTALL.md)) 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 with the OverbiteFF extension, Lynx) and open up `gopher://HOSTNAME/` (where `HOSTNAME` is your server 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 configuration (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. The `-nx` option prevents execution of any script or external file, and the `-nu` option suppresses scanning for and serving of `~user` directories (which are normally at `~/public_html/` for each user). ### OpenBSD-specific Security If you are running Gophernicus on OpenBSD, you may (depending on what features you want to use) be able to take advantage of unveil(2) and pledge(2). If you run without executable map support (i.e. you run with `-nx`) then unveil(2) will be enabled and the server root will automatically be unveiled. If run with personal gopherspaces enabled (i.e. you run without `-nu`), then the password database (`/etc/pwd.db`) will automatically be unveiled, but you will have to manually unveil the filesystem path(s) from which to serve personal gopherspaces (see `-U`). Running with `-nm -nu -nx` results in the strictest set of pledge(2) promises. If you have executable maps enabled (i.e. you run without `-nx`), then the promises are relaxed to allow `exec`. If you have personal gopherspaces enabled (i.e. you run without `-nu`), then the promises are relaxed to allow `getpw`. If you have shared memory enabled (i.e. you run without `-nm`), then pledge(2) support cannot be used at all. In short, you probably want to run Gophernicus with `-nm -nu -nx` and then remove the flags that would otherwise disable the features you want. To see what is going on with regards to pledge(2) and unveil(2), run Gophernicus with `-d` (to turn on debug logging) and look in your system logs. ## 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 [README.gophermap](./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. This is suppressed if the `-nu` option is given. In this case, any `~` entry which otherwise initiates listing of user directories will be displayed literally. ## 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 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 (kind of) up and running. There is a serious issue with virtual hosting. As stated previously, RFC1436 dosen't support virtual hosting. Clients won't like it. How the virtual hosting works, is that it loops through the vhosts looking for the selector. As you might think, the root gophermap exists on all of the vhosts, meaning it might not use the correct vhost. There is currently no easy way to fix this. It is recommended to add '%' on a line by itself to the bottom of your root gophermaps. This will add "special" links of the format example.com/;example.com which forces the correct vhost. ## 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 Unix/Linux/BSD servers. The `-nx` option prevents execution of any script or external file. In this case, they will be simply ignored and no output is given. ## 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` . ## TLS/SSL and proxy support As of version 2.3 Gophernicus supports the HAproxy proxy protocol version 1. This makes it possible to build a cluster of gopher servers and use HAproxy in front of them all handling client routing to different backend servers. More useful is putting Gophernicus behind Stunnel4 for TLS/SSL support and use the same proxy protocol to tell Gophernicus the correct remote IP address. The below sample stunnel configuration is all you need to TLS-enable your gopher server. Well, you'll need a certificate too and for that I recommend Let's Encrypt. In addition to configuring Stunnel for TLS you should add `-T TLSPORT` to Gophernicus options so that it knows which connetions are coming in encrypted and which are not. Using proper `-T` also makes it possible for CGI programs to use the `$TLS` environment variable to know whether the current request was encrypted or not. Example: ; ; Gophernicus behind Stunnel4 for gopher over TLS ; ; User/group for stunnel daemon setuid = stunnel4 setgid = stunnel4 ; PID file location pid = /var/run/stunnel4/gophernicus.pid ; Log to file, not syslog output = /var/log/stunnel4/gophernicus.log syslog = no ; Certificate in pem format is needed for TLS cert = /etc/ssl/private/gophernicus.pem ; Enable TCP wrappers libwrap = yes service = gophernicus-tls ; Gopher over TLS service [gophernicus] accept = :::7070 connect = 127.0.0.1:70 protocol = proxy gophernicus-3.1.1/build-aux/000077500000000000000000000000001377422061400157425ustar00rootroot00000000000000gophernicus-3.1.1/build-aux/install-sh000077500000000000000000000325511377422061400177540ustar00rootroot00000000000000#!/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: gophernicus-3.1.1/changelog000066400000000000000000000046561377422061400157350ustar00rootroot000000000000003.1.1 ===== * Fix various build system issues 3.1 === * Allow document roots specified with relative paths * Drop debian packaging * New build system * Rework logging logic * Replace spurious tabs in menu with dashes * Don't install documentation * Fix parallel builds * Declare function prototypes explicitly * Sort the ~ selector by date modified * Rework manual page * Propagate FLAGS value to recursive MAKE calls * Fix memory leak in gophermap parsing * Add option `-nH` to disable HTTP response to HTTP GET/POST requests (#66) * Make description in die() appears everywhere * fix LDFLAGS * Filetypes2 (#57) Re-do filetypes system. * corrected some typos in author's names (#55) * build: Force create symlink * ci: New Travis system for linux and macOS builds and installs * refactor: New hierarchy and refactored Makefile * use user-defined CC by default * add -b option to git clone instead of git checkout * Add OpenBSD pledge(2) and unveil(2) support. * change default charset to utf-8 * change max-width to 67 (#39) 3.0.1 ===== * add installation notes to git checkout before installing * fix typo in debian packaging (fixes `make deb`) * update docs to reflect new changes * fix indenting and typos in docs 3.0 (from 101) ============== N.B. this version has two important changes that may make it backwards-incompatible: * binary changed from in.gophernicus to gophernicus * virtual hosting NEVER WORKED and does not work in the way previously described Other changes: * prevent leak of executable gophermap contents * make sure {x,}inetd works when systemd is on the system * allow -j flag to work * add hb9kns (yargo) and fosslinux into developer roles * add -nx flag, blocks executable gophermaps * add -nu flag, disable ~/public_gopher * modify various documentation to markdown * fix various formattings and typos * allow inetd targets to work without update-inetd * correct handling of inetd.conf * remove list of supported platforms * remove example gophermaps * add dependencies for various distros to INSTALL.md * fix query urls * add travis ci * add documentation about CI Upgrade guide: If you are running gophernicus on a **production** system, **do not** upgrade to 3.0. Wait for 3.1. As a general guide: If you are running 101 and haven't upgraded to newer versions **because** of instability worries, **wait for 3.1**. If you were running other rolling-release versions, **upgrade now**. gophernicus-3.1.1/changelog.old000066400000000000000000000235441377422061400165070ustar00rootroot00000000000000This contains changelog up to 1.4. There are no changelogs from 1.4 until 3.0. 2012-12-02 Kim Holviala * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * Started coding kgopherd * Trying to remember how "C" works... gophernicus-3.1.1/configure000077500000000000000000000300771377422061400157660ustar00rootroot00000000000000#!/bin/sh # Is similar to an autoconf configure script, but is written by hand usage() { printf "Usage: %s [options]\\n\\n" "$0" printf " --prefix=/usr/local Prefix for all files\\n" printf " --bindir=PREFIX/bin Prefix for binaries\\n" printf " --sbindir=PREFIX/sbin Prefix for system binaries\\n" printf " --mandir=PREFIX/share/man Prefix for manpages\\n" printf " --man8dir=MANDIR/man8 Prefix for section 8 manpages\\n" printf " --gopherroot=/var/gopher Path to gopher root\\n" printf " --sysconfig=/etc/sysconfig Path to sysconfig directory\\n" printf " --default=/etc/default Path to 'default' configuration directory\\n" printf " --launchd=/Library/LaunchDaemons Path to launchd for MacOS\\n" printf " --haikusrv=/boot/common/settings/network/services Path to services directory in Haiku\\n" printf " --systemd=/lib/systemd/system Path to systemd directory when using systemd listener\\n" printf " --os=autodetected Your target OS, one of linux, mac, haiku, netbsd, openbsd or freebsd\\n" printf " --listener=somelistener Program to recieve and pass network requests; one or more of systemd, inetd, xinetd, comma-seperated, or autodetect, mac or haiku (parameter required, mac/haiku required on respective OSes)\\n" printf " --hostname=autodetected Desired hostname for gophernicus to identify as\\n" } # Set values for each option while [ "$#" -gt 0 ] ; do opt="${1%%=*}" opt="${opt##--}" value="${1##*=}" case "${opt}" in prefix) PREFIX="${value}"; shift ;; bindir) BINDIR="${value}"; shift ;; sbindir) SBINDIR="${value}"; shift ;; docdir) DOCDIR="${value}"; shift ;; mandir) MANDIR="${value}"; shift ;; man8dir) MAN8DIR="${value}"; shift ;; gopherroot) GOPHERROOT="${value}"; shift ;; sysconfig) SYSCONFIG="${value}"; shift ;; default) DEFAULTCONF="${value}"; shift ;; os) OS="${value}"; shift ;; launchd) LAUNCHD="${value}"; shift ;; haikusrv) HAIKUSRV="${value}"; shift ;; systemd) SYSTEMD="${value}"; shift ;; listener) LISTENERS="${value}"; shift ;; hostname) HOSTNAME="${value}"; shift ;; help) usage; exit 0 ;; *) usage; exit 2 ;; esac done # Set default values : ${PREFIX:=/usr/local} : ${BINDIR:=${PREFIX}/bin} : ${SBINDIR:=${PREFIX}/sbin} : ${DOCDIR:=${PREFIX}/share/doc} : ${MANDIR:=${PREFIX}/share/man} : ${MAN8DIR:=${MANDIR}/man8} : ${GOPHERROOT:=/var/gopher} : ${SYSCONFIG:=/etc/sysconfig} : ${DEFAULTCONF:=/etc/default} : ${LAUNCHD:=/Library/LaunchDaemons} : ${HAIKUSRV:=/boot/common/settings/network/services} : ${SYSTEMD:=/lib/systemd/system} : ${CC:=cc} : ${HOSTCC:=${CC}} : ${CFLAGS:=-O2} : ${HOSTNAME:=autodetect} # Check for a compiler that actually works printf "checking for working compiler... " cat > conftest.c < int main() { return 0; } EOF if ! ${CC} -o conftest conftest.c; then printf "no\\n" exit 1 else printf "${CC}\\n" fi # Autodetect the OS if [ -z "${OS}" ]; then # If it can't find uname, it needs to be manually specified printf "checking for uname... " if ! UNAME="$(command -v uname)"; then printf "please provide OS in options\\n" exit 1 else printf "%s\\n" "${UNAME}" fi # If it can, it presses on printf "checking for OS... " case "$(${UNAME})" in Linux) OS=linux ;; Haiku) OS=haiku INSTALL_HAIKU="install-haiku" ;; Darwin) OS=mac INSTALL_OSX="install-osx" ;; NetBSD) OS=netbsd ;; OpenBSD) OS=openbsd ;; FreeBSD) OS=freebsd ;; *) printf "unknown, pressing on anyway\\n" ;; esac printf "%s\\n" "${OS}" fi # Checks for an install command and falls back to install-sh printf "checking for install... " if ! INSTALL="$(command -v install)"; then printf "install-sh" INSTALL=build-aux/install-sh else # Check it has required features (*cough* macos) mkdir testconf touch testfile ${INSTALL} -t testconf testfile 2>&1 >/dev/null || INSTALL=build-aux/install-sh rm testconf/testfile ${INSTALL} -T testfile testconf/testfile 2>&1 >/dev/null || INSTALL=build-aux/install-sh rm -r testconf testfile printf "%s" "${INSTALL}" fi printf "\\n" listeners="$(echo ${LISTENERS} | tr ',' ' ')" for listener in ${listeners}; do # Check for listener validity and autodetect if required # Checks that take place: # mac OS = mac listener (both ways) # haiku OS = haiku listener (both ways) # systemd listener = linux OS printf "checking for listener... " if [ -z "${listener}" ]; then printf "not given\\n" exit 1 elif [ "${listener}" = "mac" ] && [ "${OS}" != "mac" ]; then printf "mac listener only valid with macos\\n" exit 1 elif [ "${listener}" = "haiku" ] && [ "${OS}" != "haiku" ]; then printf "haiku listener only valid with haiku\\n" exit 1 elif [ "${listener}" = "systemd" ] && [ "${OS}" != "linux" ]; then printf "systemd listener only valid with linux\\n" exit 1 elif [ "${listener}" = "autodetect" ]; then # OS-specific listeners case "${OS}" in mac) listener=mac printf "mac\\n" break ;; haiku) listener=haiku printf "haiku\\n" break ;; esac if [ -d "/lib/systemd/system" ] ; then listener=systemd printf "systemd\\n" fi printf "checking for inetd... " if command -v update-inetd && [ "${listener}" = "autodetect" ]; then listener=inetd printf "inetd\\n" fi printf "checking for xinetd... " if XINETD="$(command -v xinetd)" && [ "${listener}" = "autodetect" ]; then listener=xinetd printf "xinetd\\n" fi # Ensure we detected something if [ "${listener}" = "autodetect" ]; then printf "unable to autodetect, please manually specify\\n" exit 1 fi elif [ "${OS}" = "haiku" ] && [ "${listener}" != "haiku" ]; then printf "only haiku listener supported on haiku\\n" exit 1 elif [ "${OS}" = "mac" ] && [ "${listener}" != "mac" ]; then printf "only mac listener supported on mac\\n" exit 1 else printf "%s\\n" "${listener}" fi # Act accordingly based on whichever listener we are given case "${listener}" in systemd) INSTALL_SYSTEMD="install-systemd" UNINSTALL_SYSTEMD="uninstall-systemd" INSTALL_MSG_SYSTEMD='$(MAKE) install-msg-systemd' ;; xinetd) INSTALL_XINETD="install-xinetd" UNINSTALL_XINETD="uninstall-xinetd" XINETD_CONF="/etc/xinetd.d/gophernicus" INSTALL_MSG_XINETD='$(MAKE) install-msg-xinetd' ;; inetd) INSTALL_INETD="install-inetd" INETD_CONF="/etc/inetd.conf" printf "checking for update-inetd... " if ! UPDATE_INETD="$(command -v update-inetd)"; then printf "not found\\n" INSTALL_INETD_MANUAL="install-inetd-manual" UNINSTALL_INETD_UPDATE="uninstall-inetd-manual" else printf "%s\\n" "${UPDATE_INETD}" INSTALL_INETD_UPDATE="install-inetd-update" UNINSTALL_INETD_UPDATE="uninstall-inetd-update" fi INSTALL_MSG_INETD='$(MAKE) install-msg-inetd' ;; mac) INSTALL_OSX="install-osx" UNINSTALL_OSX="uninstall-osx" INSTALL_MSG_OSX='$(MAKE) install-msg-osx' ;; haiku) INSTALL_HAIKU="install-haiku" UNINSTALL_HAIKU="uninstall-haiku" INSTALL_MSG_HAIKU='$(MAKE) install-msg-haiku' ;; *) printf "The listener %s is not offically supported; continuing anyway.\\n" "${listener}" ;; esac done # Try to detect hostname printf "checking current hostname... " if [ "${HOSTNAME}" = "autodetect" ]; then HOSTNAME="$(hostname)" # If no hostname then we couldn't autodetect if [ $? != 0 ] || [ -z "${HOSTNAME}" ]; then printf "unable to detect hostname\\n" exit 1 fi fi printf "%s\\n" "${HOSTNAME}" # Use libwrap when it is avaliable printf "checking for libwrap... " cat > conftest.c < int main() {} EOF if ${CC} -o conftest -lwrap conftest.c 2>/dev/null; then LIBWRAP="-DHAVE_LIBWRAP -lwrap" printf "yes" else LIBWRAP= printf "no, but program will still work" fi printf "\\n" # Check and use SHM if avaliable printf "checking for ipcrm (SHM management)... " if ! IPCRM="$(command -v ipcrm)"; then printf "not found" else printf "%s" "${IPCRM}" CLEAN_SHM="clean-shm" fi printf "\\n" # Trying to autodetect make printf "checking for make... " if ! MAKE="$(command -v make)"; then printf "not found, please pass MAKE=/path/to/make to make invocation" MAKE="make" else printf "%s" "${MAKE}" fi printf "\\n" # Don't replace an existing root printf "checking for existing gopher root... " if [ -d "${GOPHERROOT}" ] || [ -f "${GOPHERROOT}/gophermap" ]; then INSTALL_ROOT="install-root" printf "yes" else printf "no" fi printf "\\n" # Sub in values cp Makefile.in Makefile printf "creating Makefile... " sed -i -e "s:@CC@:${CC}:" Makefile sed -i -e "s:@HOSTCC@:${HOSTCC}:" Makefile sed -i -e "s:@LIBWRAP@:${LIBWRAP}:" Makefile sed -i -e "s:@INSTALL@:${INSTALL}:" Makefile sed -i -e "s:@MAKE@:${MAKE}:" Makefile sed -i -e "s:@PREFIX@:${PREFIX}:" Makefile sed -i -e "s:@BINDIR@:${BINDIR}:" Makefile sed -i -e "s:@SBINDIR@:${SBINDIR}:" Makefile sed -i -e "s:@DOCDIR@:${DOCDIR}:" Makefile sed -i -e "s:@MANDIR@:${MANDIR}:" Makefile sed -i -e "s:@MAN8DIR@:${MAN8DIR}:" Makefile sed -i -e "s:@IPCRM@:${IPCRM}:" Makefile sed -i -e "s:@CLEAN_SHM@:${CLEAN_SHM}:" Makefile sed -i -e "s:@SYSCONF@:${SYSCONFIG}:" Makefile sed -i -e "s:@DEFAULT@:${DEFAULTCONF}:" Makefile sed -i -e "s:@HOSTNAME@:${HOSTNAME}:" Makefile sed -i -e "s:@ROOT@:${GOPHERROOT}:" Makefile sed -i -e "s:@HAIKUSRV@:${HAIKUSRV}:" Makefile sed -i -e "s:@LAUNCHD@:${LAUNCHD}:" Makefile sed -i -e "s:@SYSTEMD@:${SYSTEMD}:" Makefile sed -i -e "s:@INSTALL_ROOT@:${INSTALL_ROOT}:" Makefile sed -i -e "s:@INSTALL_OSX@:${INSTALL_OSX}:" Makefile sed -i -e "s:@INSTALL_INETD_MANUAL@:${INSTALL_INETD_MANUAL}:" Makefile sed -i -e "s:@INSTALL_INETD_UPDATE@:${INSTALL_INETD_UPDATE}:" Makefile sed -i -e "s:@INSTALL_XINETD@:${INSTALL_XINETD}:" Makefile sed -i -e "s:@INSTALL_SYSTEMD@:${INSTALL_SYSTEMD}:" Makefile sed -i -e "s:@INSTALL_HAIKU@:${INSTALL_HAIKU}:" Makefile sed -i -e "s:@INSTALL_MSG_INETD@:${INSTALL_MSG_INETD}:" Makefile sed -i -e "s:@INSTALL_MSG_XINETD@:${INSTALL_MSG_XINETD}:" Makefile sed -i -e "s:@INSTALL_MSG_SYSTEMD@:${INSTALL_MSG_SYSTEMD}:" Makefile sed -i -e "s:@INSTALL_MSG_OSX@:${INSTALL_MSG_OSX}:" Makefile sed -i -e "s:@INSTALL_MSG_HAIKU@:${INSTALL_MSG_HAIKU}:" Makefile sed -i -e "s:@UNINSTALL_OSX@:${UNINSTALL_OSX}:" Makefile sed -i -e "s:@UNINSTALL_INETD_MANUAL@:${UNINSTALL_INETD_MANUAL}:" Makefile sed -i -e "s:@UNINSTALL_INETD_UPDATE@:${UNINSTALL_INETD_UPDATE}:" Makefile sed -i -e "s:@UNINSTALL_XINETD@:${UNINSTALL_XINETD}:" Makefile sed -i -e "s:@UNINSTALL_SYSTEMD@:${UNINSTALL_SYSTEMD}:" Makefile sed -i -e "s:@UNINSTALL_HAIKU@:${UNINSTALL_HAIKU}:" Makefile sed -i -e "s:@INETD_CONF@:${INETD_CONF}:" Makefile sed -i -e "s:@XINETD_CONF@:${XINETD_CONF}:" Makefile printf "done\\n" # Also sub in $HOSTNAME to the various init systems (whether or not we really # use them, its just easier) for f in gophernicus.env haiku_snippet org.gophernicus.server.plist \ gophernicus.xinetd; do printf "creating init/${f}... " sed -e "s:@HOSTNAME@:${HOSTNAME}:" "init/${f}.in" > "init/${f}" printf "done\\n" done # And generate gophernicus@.service printf "creating init/gophernicus@.service... " sed -e "s:@DEFAULT@:${DEFAULTCONF}:" \ -e "s:@SYSCONFIG@:${SYSCONFIG}:" \ 'init/gophernicus@.service.in' > 'init/gophernicus@.service' printf "done\\n" # Cleanup rm -f conftest conftest.c gophernicus-3.1.1/error.gif000066400000000000000000000010601377422061400156650ustar00rootroot00000000000000GIF89a00ãÏþþ%&ÿPOÿ{{ÿ£¢ÿÉÊÿûùÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù ,00þÉI+8kË»¯Z(_é…B C»¡)#ÚJxžÄ l3NívÐ H¤”dX†¢q—¬–˜& 3(H§«•@þeg˜ÀL#ÉpBê\Ân`w5ÎÀHixSz<|}>'\„…‡q-‘X€v:…†’œ~t—8naš<‘s”Œ¦bQp²=œ+¬ ¡—zQE‡DÀ’¹¹Š®]b—ÀѳÃ=ÄÑWÈÈÊ[wÎÐÑ×)*^àÀØÉéÊ–{däåÀéïðòê÷ëÌIwðÑÏþÌá×EЩê\x ÀÀça9‡ féaE‹Þ 3BÜØ1ãÇ…EÞBÆÊ“ðRªT7¤ÅLQ íÝLWÃÅLzuÞìéR$Ð…BUöü \ÒŒÅe< òéÈ-*©šƒéT) 4&Q¦ãZ¬èU¬P•I¶€Ô³hª½8¦Û‡žZMûNæ½wñ±È»Kå\¿üMÇBÎ''¡ [Å—ˆrËm vjV׸Ìãe‘7Ïììøó2Ñ3©]6õÀÎJ0/r}ZÂhön¾óì™V26jæ]%/ðܺ_Û~cüx“ˆŒa¿éÁе–-¡2¹¾hÄîϽ;7;gophernicus-3.1.1/gophermap.sample000066400000000000000000000027231377422061400172410ustar00rootroot00000000000000!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 " your ip address: $REMOTE_ADDR" =echo " server time....: $(date)" =echo " server uptime..: $(uptime | sed 's/.*up *\([^,]*\), .*/\1/')" =echo " server version.: $SERVER_VERSION \"$SERVER_CODENAME\"" =echo " server platform: $SERVER_ARCH" =echo " description....: $SERVER_DESCRIPTION" Server configuration: =echo " config file....: $(for FILE in /etc/sysconfig/gophernicus /etc/default/gophernicus /Library/LaunchDaemons/org.gophernicus.server.plist /boot/common/settings/network/services /lib/systemd/system/gophernicus\@.service /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" * gophernicus-3.1.1/gophernicus.8000066400000000000000000000123151377422061400164710ustar00rootroot00000000000000.Dd April 13, 2020 .Dt GOPHERNICUS 1 .Os .Sh NAME .Nm gophernicus .Nd A modern, full-featured and secure gopher server .Sh SYNOPSIS .Nm .Op Fl h Ar hostname .Op Fl p Ar port .Op Fl r Ar port .Op Fl t Ar type .Op Fl g Ar mapfile .Op Fl a Ar tagfile .Op Fl c Ar dir .Op Fl u Ar dir .Op Fl l Ar file .Op Fl w Ar width .Op Fl o Ar charset .Op Fl s Ar seconds .Op Fl i Ar hits .Op Fl k Ar KiB .Op Fl e Ar ext Ns = Ns Ar type Oo Fl e Ar ext Ns = Ns Ar type Oc ... .Op Fl R Ar old Ns = Ns Ar new Oo Fl R Ar old Ns = Ns Ar new Oc ... .Op Fl D Ar text .Op Fl L Ar text .Op Fl U Ar paths .Op Fl nv .Op Fl nl .Op Fl nh .Op Fl nf .Op Fl nd .Op Fl nc .Op Fl no .Op Fl nq .Op Fl ns .Op Fl na .Op Fl nt .Op Fl nm .Op Fl nr .Op Fl np .Op Fl nx .Op Fl nu .Op Fl nH .Op Fl d .Op Fl b .Op Fl \&? .Sh DESCRIPTION .Nm is a gopher server. It serves almost compliant RFC 1436, with small changes to make it more modern. .Nm supports userdirs, executable gophermaps, CGI, and virtual hosting (in the limits of the RFC). .Pp .Nm can log to .Xr syslog 3 or per-server files in Apache format. .Pp The options are as follows: .Bl -tag -width Dssmacro=value .It Fl h Ar hostname Changes the server hostname shown on the output menu. The default is the fully qualified domain name of the machine. .It Fl p Ar port Changes the server port shown on the output menu. The default is .Pa 70 . .It Fl T Ar port Changes the SSL/TLS port. The default is .Pa 0 (disabled). .It Fl r Ar directory Set the document root of the server. The default is .Pa /var/gopher . .It Fl t Ar type Set the default gopher filetype. The default is .Pa 0 . .It Fl g Ar mapfile Set the gophermap file name. The default is .Pa gophermap . .It Fl a Ar tagfile Set the gophertag file name. The default is .Pa gophertag . .It Fl c Ar directory Set the CGI script directory, under the document root. The default is .Pa /cgi-bin/ . .It Fl u Ar directory Set the name of the personal gopherspace directory. Each user can deploy a personal gopherspace by creating a directory .Pa directory in their home directory. The default is .Pa public_gopher . .It Fl l Ar file Log to .Pa file in Apache-compatible combined format. Disabled by default. .It Fl w Ar width Set default page width. The default is .Pa 67 . .It Fl o Ar charset Select the output charset. It can be .Ar UTF-8 , .Ar US-ASCII , or .Ar ISO-9959-1 . The default is .Ar UTF-8 . .It Fl s Ar seconds Session timeout in seconds. The default is .Pa 1800 . .It Fl i Ar hits Maximum hits until throttling. The default is .Pa 4069 . .It Fl k Ar kilobytes Maximum transfer size in KiB until throttling. The default is .Pa 4194304 (4 GiB). .It Fl f Ar directory Set directory where output filters are found. Disabled by default. .It Fl e Ar ext Ns = Ns Ar type Map file extension .Ar ext to gopher filetype .Ar type . .It Fl R Ar old Ns = Ns Ar new Rewrite the start of a selector. .It Fl D Ar description Set server description to appear in .Pa /caps.txt . If .Ar description looks like a filename (starts with a /), use the contents of the file. .It Fl L Ar location Set server location to appear in .Pa /caps.txt . If .Ar location looks like a filename (starts with a /), use the contents of the file. .It Fl A Ar email Set the email of the administrator to appear in .Pa /caps.txt . .It Fl U Ar paths Set a colon-separated list of extra .Xr unveil 2 paths (only for OpenBSD). .It Fl nv Disable virtual hosting. .It Fl nl Disable parent directory links. .It Fl nh Disable menu header. .It Fl nf Disable menu footer. .It Fl nd Don't show date and size of files in menu. .It Fl nc Disable file content detection (similar to .Xr magic 5 Ns ). .It Fl no Disable output charset conversion. .It Fl nq Disable HTTP-style query strings .Ql ?foo=bar&baz=quux . .It Fl ns Disable logging to .Xr syslog 3 . .It Fl na Disable autogenerated .Pa /caps.txt . .It Fl nt Disable .Pa /server-status . .It Fl nm Disable shared memory use (for debugging purposes). .It Fl nr Disable root user check (for debugging purposes). By default, .Nm will refuse to run as root. .It Fl np Disable HAProxy proxy protocol. .It Fl nx Disable execution of gophermaps and scripts. .It Fl nH Disable HTTP response to HTTP GET and POST requests. .It Fl d Print debug output in .Xr syslog 3 and .Pa /server-status . When .Fl ns (disable .Xr syslog 3 ) is used this option has no effect. .It Fl v Display version information and build date. .It Fl b Display licensing information. .It Fl \&? Display help. .El .Pp Default values may have been changed at compile time by .Pa gophernicus.env . .Sh STANDARDS .Nm mostly conforms to .Lk https://tools.ietf.org/html/rfc1436 "RFC 1436" .Sh AUTHORS .An -nosplit .An Kim Holviala. wrote the original implementation (2009\(en2018). .Pp .An fosslinux and .An hb9kns are the current maintainers (2019\(en). .Pp Code contributed by others. .Pp Developers can be reached at .Mt gophernicus.AT.gophernicus.DOT.org .Sh BUGS Known bugs are listed as issues on .Nm Ap s GitHub page: .Lk https://github.com/gophernicus/gophernicus/issues .Pp Please report any bug you might experience there as well. .Sh COPYRIGHT Copyright \(co .An Kim Holviala 2009\(en2018. .Pp Copyright \(co .An Nm gophernicus Ns \& developers 2019. .Pp Licensed to you under the terms of the BSD 2-clause license. Please see .Pa LICENSE for the full terms of the license. gophernicus-3.1.1/gophertag000066400000000000000000000000321377422061400157460ustar00rootroot00000000000000Gophernicus documentation gophernicus-3.1.1/init/000077500000000000000000000000001377422061400150135ustar00rootroot00000000000000gophernicus-3.1.1/init/.gitignore000066400000000000000000000001531377422061400170020ustar00rootroot00000000000000haiku_snippet org.gophernicus.server.plist gophernicus.xinetd inetlin gophernicus.env gophernicus@.service gophernicus-3.1.1/init/gophernicus.env000066400000000000000000000004051377422061400200520ustar00rootroot00000000000000# Options for Gophernicus # # See README or run "gophernicus -?" to see a full list of # configuration options. # # Example: # OPTIONS="-h full.hostname -D \"I find your lack of gopher disturbing.\"" # # modify and set your options here: OPTIONS="-h beast -nv" gophernicus-3.1.1/init/gophernicus.env.in000066400000000000000000000004121377422061400204550ustar00rootroot00000000000000# Options for Gophernicus # # See README or run "gophernicus -?" to see a full list of # configuration options. # # Example: # OPTIONS="-h full.hostname -D \"I find your lack of gopher disturbing.\"" # # modify and set your options here: OPTIONS="-h @HOSTNAME@ -nv" gophernicus-3.1.1/init/gophernicus.socket000066400000000000000000000001651377422061400205550ustar00rootroot00000000000000[Unit] Description=Gophernicus gopher server [Socket] ListenStream=70 Accept=yes [Install] WantedBy=sockets.target gophernicus-3.1.1/init/gophernicus.xinetd.in000066400000000000000000000003401377422061400211600ustar00rootroot00000000000000# default: on # description: Gophernicus - Modern full-featured gopher server service gopher { socket_type = stream wait = no user = nobody server = @BINARY@ server_args = -r/var/gopher -h@HOSTNAME@ disable = no } gophernicus-3.1.1/init/gophernicus@.service.in000066400000000000000000000003311377422061400214250ustar00rootroot00000000000000[Unit] Description=Gophernicus gopher server [Service] EnvironmentFile=-@DEFAULT@/gophernicus EnvironmentFile=-@SYSCONFIG@/gophernicus ExecStart=@BINARY@ $OPTIONS SuccessExitStatus=1 StandardInput=socket User=nobody gophernicus-3.1.1/init/haiku_snippet.in000066400000000000000000000001331377422061400202030ustar00rootroot00000000000000 service gopher { family inet protocol tcp port 70 launch @BINARY@ -h @HOSTNAME@ } gophernicus-3.1.1/init/inetlin.in000066400000000000000000000000701377422061400170020ustar00rootroot00000000000000gopher stream tcp nowait nobody @BINARY_PATH@ @OPTIONS@ gophernicus-3.1.1/init/org.gophernicus.server.plist.in000066400000000000000000000012441377422061400231170ustar00rootroot00000000000000 Label org.gophernicus.server ProgramArguments /usr/local/sbin/gophernicus -h@HOSTNAME@ -r/Library/GopherServer Sockets Listeners SockServiceName gopher UserName nobody inetdCompatibility Wait gophernicus-3.1.1/release.sh000077500000000000000000000023111377422061400160240ustar00rootroot00000000000000#!/bin/sh set -ex RELEASE=3.1.1 # Create release branch git branch "v${RELEASE}" # Create tmpdir TMPDIR=$(mktemp -d) cp -r . "${TMPDIR}/gophernicus-${RELEASE}" cd "${TMPDIR}/gophernicus-${RELEASE}" # Remove build leftovers etc rm -rf src/*.o src/files.h src/filetypes.h src/bin2c src/gophernicus Makefile README README.options # Remove things rm -rf .git .gitignore .travis .travis.yml release.sh # Change readme for devel sed -i '/The master branch is rolling Development/d' README.md sed -i "s/ DEVEL$/ ${RELEASE}/" README.md # Create tarball(s) cd "${TMPDIR}" tar -czvf "gophernicus-${RELEASE}.tar.gz" "gophernicus-${RELEASE}" tar -cjvf "gophernicus-${RELEASE}.tar.bz2" "gophernicus-${RELEASE}" tar -cJvf "gophernicus-${RELEASE}.tar.xz" "gophernicus-${RELEASE}" # Create binaries cd "${TMPDIR}/gophernicus-${RELEASE}" # x86_64-glibc ./configure --listener=none --os=linux --hostname=HOSTNAME make strip src/gophernicus cp src/gophernicus "../gophernicus-${RELEASE}-x86_64-glibc" make clean # x86-64-static export CFLAGS="-static" export LDFLAGS="-static" ./configure --listener=none --os=linux --hostname=HOSTNAME make strip src/gophernicus cp src/gophernicus "../gophernicus-${RELEASE}-x86_64-static" make clean gophernicus-3.1.1/src/000077500000000000000000000000001377422061400146375ustar00rootroot00000000000000gophernicus-3.1.1/src/bin2c.c000066400000000000000000000051651377422061400160070ustar00rootroot00000000000000/* * bin2c * * Copyright (c) 2009-2018 Kim Holviala * Copyright (c) 2019 Gophernicus Developers * * 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. */ /* * 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 #include 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 ] \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; } gophernicus-3.1.1/src/file.c000066400000000000000000000262321377422061400157270ustar00rootroot00000000000000/* * Gophernicus * * Copyright (c) 2009-2018 Kim Holviala * Copyright (c) 2019 Gophernicus Developers * * 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; log_debug("send 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; log_debug("send 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; log_debug("sending 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]; /* Basic security checking */ sstrlcpy(dest, st->req_selector + 4); if (sstrncmp(dest, "http://") != MATCH && sstrncmp(dest, "https://") != MATCH && sstrncmp(dest, "ftp://") != MATCH && sstrncmp(dest, "irc://") != MATCH && sstrncmp(dest, "mailto:") != MATCH) die(st, ERR_ACCESS, "Refusing to HTTP redirect unsafe protocols"); log_info("request for \"gopher%s://%s:%i/h%s\" from %s", st->server_port == st->server_tls_port ? "s" : "", st->server_host, st->server_port, st->req_selector, st->req_remote_addr); log_combined(st, HTTP_OK); /* Output HTML */ printf("\n" "\n\n" " \n" " \n" " URL Redirect page\n" "\n\n" "Redirecting to %1$s\n" "
\n", dest);
	footer(st);
	printf("
\n\n\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_info("request for \"gopher%s://%s:%i/0" SERVER_STATUS "\" from %s", st->server_port == st->server_tls_port ? "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++; if (st->debug) { printf("Session: %-4i %-40s %-4li %-7li gopher%s://%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_port == st->server_tls_port ? "s" : ""), 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_info("request for \"gopher%s://%s:%i/0" CAPS_TXT "\" from %s", st->server_port == st->server_tls_port ? "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 "ServerSupportsStdinScripts=TRUE" CRLF "ServerDefaultEncoding=%s" CRLF "ServerTLSPort=%i" CRLF CRLF "ServerSoftware=" SERVER_SOFTWARE CRLF "ServerSoftwareVersion=" VERSION " \"" CODENAME "\"" CRLF "ServerArchitecture=%s" CRLF, st->session_timeout, strcharset(st->out_charset), st->server_tls_port, 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); if (st->server_port == st->server_tls_port) { setenv("HTTPS", "on", 1); setenv("TLS", "on", 1); } setenv("SERVER_NAME", st->server_host, 1); snprintf(buf, sizeof(buf), "%i", st->server_port); setenv("SERVER_PORT", buf, 1); snprintf(buf, sizeof(buf), "%i", st->server_tls_port); setenv("SERVER_TLS_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); #ifdef HAVE_SHMEM snprintf(buf, sizeof(buf), "%x", st->session_id); setenv("SESSION_ID", buf, 1); #endif 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); snprintf(buf, sizeof(buf), CODENAME); setenv("SERVER_CODENAME", 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_search, 1); } /* * Execute a CGI script */ static void run_cgi(state *st, char *script, char *arg) { if (st->opt_exec) { /* Setup environment & execute the binary */ log_debug("executing script \"%s\"", script); setenv_cgi(st, script); execl(script, script, arg, NULL); } else { log_debug("execution of script \"%s\" blocked by `-nx'", script); } /* 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); } gophernicus-3.1.1/src/filetypes.conf000066400000000000000000000022061377422061400175120ustar00rootroot00000000000000# This file contains definitions of file types which will be created # during compile time as filetypes.h and included in gophernicus.h. # If no definitions are found here, filetypes.h will be empty. # You should keep the defaults, though! # The syntax is very simple: Each line consists of one character, # representing a gopher file type, followed by file extensions # (without dots) of files to be handled as that gopher type. # Fields must be separated by TAB or SPC. # Empty lines and lines starting with a lone '#' are ignored. # (Do not use lines with several '###...' though! Stupid script!) # No more than MAX_FILETYPES (defined in gophernicus.h) extensions # should be listed here (that's by default 1000 and should suffice)! # defaults as included in gophernicus.h until version 3.0: 0 txt pl py sh tcl c cpp h log conf php php3 1 map menu 4 hqx 5 Z gz tgz tar zip bz2 rar sea 7 q qry 9 iso so o rtf ttf bin c ics ical g gif h html htm xhtml css swf rdf rss xml I jpg jpeg png bmp svg tif tiff ico xbm xpm pcx M mbox d pdf ps doc ppt xls xlsx docx pptx s mp3 wav mid wma flac ogg aiff aac ; avi mp4 mpg mov qt asf mpv m4v webm ogv gophernicus-3.1.1/src/filetypes.sh000066400000000000000000000011341377422061400171760ustar00rootroot00000000000000#!/bin/sh # script for conversion of filetypes.conf into filetypes.h # (called by Makefile before compilation) # (2020-1-16 // HB9KNS) cat < * Copyright (c) 2019 Gophernicus Developers * * 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" /* * Libwrap needs these defined */ #ifdef HAVE_LIBWRAP #include int allow_severity = LOG_DEBUG; int deny_severity = LOG_ERR; #endif /* * 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, const char *message, const char *description) { static const char error_gif[] = ERROR_GIF; log_fatal("error \"%s %s\" for request \"%s\" from %s", message, description ? 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 %s\tTITLE\t" DUMMY_HOST CRLF, message, description); 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("\n" "\n\n" " \n" " " ERROR_PREFIX "%1$s %2$s\n" "\n\n" "" ERROR_PREFIX "%1$s %2$s\n" "
", message, description);
		footer(st);
		printf("
\n\n\n"); } /* Use plain text error for other filetypes */ else { printf(ERROR_PREFIX "%s %s" CRLF, message, description); 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 */ static 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)); log_debug("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->opt_personal_spaces && *(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 */ static 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 UNKNOWN_ADDR; } /* * Get remote peer IP address */ static 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 UNKNOWN_ADDR; } /* * Initialize state struct to default/empty values */ static 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_search); 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->server_tls_port = DEFAULT_TLS_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); #ifdef __OpenBSD__ st->extra_unveil_paths = NULL; #endif /* 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_status = TRUE; st->opt_shm = TRUE; st->opt_root = TRUE; st->opt_proxy = TRUE; st->opt_exec = TRUE; st->opt_personal_spaces = TRUE; st->opt_http_requests = 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 = -1; #endif #ifdef ENABLE_HAPROXY1 char remote[BUFSIZE]; char local[BUFSIZE]; int dummy; #endif #ifdef __OpenBSD__ char pledges[256]; char *extra_unveil; #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); srand(time(NULL) / (getpid() + getppid())); /* Handle command line arguments */ parse_args(&st, argc, argv); /* Initalize logging */ log_init(st.opt_syslog, st.debug); /* Convert relative gopher roots to absolute roots */ if (st.server_root[0] != '/') { char cwd[512]; getcwd(cwd, sizeof(cwd)); if (cwd == NULL) { die(&st, NULL, "unable to get current path"); } snprintf(buf, sizeof(buf), "%s/%s", cwd, st.server_root); sstrlcpy(st.server_root, buf); } #ifdef __OpenBSD__ /* unveil(2) support. * * We only enable unveil(2) if the user isn't expecting to shell-out to * arbitrary commands. */ if (st.opt_exec) { if (st.extra_unveil_paths != NULL) { die(&st, NULL, "-U and executable maps cannot co-exist"); } log_debug("executable gophermaps are enabled, no unveil(2)"); } else { if (unveil(st.server_root, "r") == -1) die(&st, NULL, "unveil"); /* * If we want personal gopherspaces, then we have to unveil(2) the user * database. This isn't actually needed if pledge(2) is enabled, as the * 'getpw' promise will ensure access to this file, but it doesn't hurt * to unveil it anyway. */ if (st.opt_personal_spaces) { log_debug("unveiling /etc/pwd.db"); if (unveil("/etc/pwd.db", "r") == -1) die(&st, NULL, "unveil"); } /* Any extra unveil paths that the user has specified */ char *p = st.extra_unveil_paths; while (p != NULL) { extra_unveil = strsep(&p, ":"); if (*extra_unveil == '\0') continue; /* empty path */ log_debug("unveiling extra path: %s\n", extra_unveil); if (unveil(extra_unveil, "r") == -1) die(&st, NULL, "unveil"); } if (unveil(NULL, NULL) == -1) die(&st, NULL, "unveil"); } /* pledge(2) support */ if (st.opt_shm) { /* pledge(2) never allows shared memory */ log_debug("shared-memory enabled, can't pledge(2)"); } else { strlcpy(pledges, "stdio rpath inet sendfd recvfd proc", sizeof(pledges)); /* Executable maps shell-out using popen(3) */ if (st.opt_exec) { strlcat(pledges, " exec", sizeof(pledges)); log_debug("executable gophermaps enabled, adding `exec' to pledge(2)"); } /* Personal spaces require getpwnam(3) and getpwent(3) */ if (st.opt_personal_spaces) { strlcat(pledges, " getpw", sizeof(pledges)); log_debug("personal gopherspaces enabled, adding `getpw' to pledge(2)"); } if (pledge(pledges, NULL) == -1) die(&st, NULL, "pledge"); } #endif /* Check if TCP wrappers have something to say about this connection */ #ifdef HAVE_LIBWRAP if (sstrncmp(st.req_remote_addr, UNKNOWN_ADDR) != MATCH && hosts_ctl(self, STRING_UNKNOWN, st.req_remote_addr, STRING_UNKNOWN) == WRAP_DENIED) die(&st, ERR_ACCESS, "Refused connection"); #endif /* 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, "Cowardly refusing to run as root"); #endif /* Try to get shared memory */ #ifdef HAVE_SHMEM if (st.opt_shm) { 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); } } } else { 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 */ get_selector: if (fgets(selector, sizeof(selector) - 1, stdin) == NULL) strclear(selector); /* Remove trailing CRLF */ chomp(selector); log_debug("client sent \"%s\"", selector); /* Handle HAproxy/Stunnel proxy protocol v1 */ #ifdef ENABLE_HAPROXY1 if (sstrncmp(selector, "PROXY TCP") == MATCH && st.opt_proxy) { log_debug("got proxy protocol header \"%s\"", selector); sscanf(selector, "PROXY TCP%d %s %s %d %d", &dummy, remote, local, &dummy, &st.server_port); /* Strip ::ffff: IPv4-in-IPv6 prefix and override old addresses */ sstrlcpy(st.req_local_addr, local + ((sstrncmp(local, "::ffff:") == MATCH) ? 7 : 0)); sstrlcpy(st.req_remote_addr, remote + ((sstrncmp(remote, "::ffff:") == MATCH) ? 7 : 0)); /* My precious \o/ */ goto get_selector; } #endif /* 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); log_debug("got a request for gopher+ root menu"); return OK; } /* Convert HTTP request to gopher (respond using headerless HTTP/0.9) */ if (st.opt_http_requests && ( 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; log_debug("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 /* Parse search from selector */ if ((c = strchr(selector, '\t'))) { sstrlcpy(st.req_search, c + 1); *c = '\0'; } /* Parse ?query from selector */ if (st.opt_query && (c = strchr(selector, '?'))) { sstrlcpy(st.req_query_string, c + 1); *c = '\0'; } /* Parse ;vhost from selector */ if (st.opt_vhost && (c = strchr(selector, ';'))) { sstrlcpy(st.server_host, c + 1); *c = '\0'; } /* 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; /* Copy valid char */ *dest++ = *c++; } *dest = '\0'; /* Main query parameters compatibility with older versions of Gophernicus */ if (*st.req_query_string && !*st.req_search) sstrlcpy(st.req_search, st.req_query_string); if (!*st.req_query_string && *st.req_search) sstrlcpy(st.req_query_string, st.req_search); /* Remove encodings from selector */ strndecode(st.req_selector, st.req_selector, sizeof(st.req_selector)); /* 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 (st.opt_status && 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); log_debug("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 */ log_info("request for \"gopher%s://%s:%i/%c%s\" from %s", st.server_port == st.server_tls_port ? "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; } gophernicus-3.1.1/src/gophernicus.h000066400000000000000000000307371377422061400173500ustar00rootroot00000000000000/* * Gophernicus * * Copyright (c) 2009-2018 Kim Holviala * Copyright (c) 2019 Gophernicus Developers * * 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 /* * Ignore useless snprintf() format truncation warnings on GCC 7+ */ #if __GNUC__ >= 7 #pragma GCC diagnostic ignored "-Wformat-truncation" #endif /* * Features */ #undef ENABLE_STRICT_RFC1436 /* Follow RFC1436 to the letter */ #undef ENABLE_AUTOHIDING /* Hide manually listed resources from generated menus */ #define ENABLE_HAPROXY1 /* Autodetect HAproxy/Stunnel proxy protocol v1 */ /* * 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 */ /* #undef HAVE_LIBWRAP autodetected, don't enable here */ /* Linux */ #ifdef __linux #undef PASSWD_MIN_UID #define PASSWD_MIN_UID 500 #define _FILE_OFFSET_BITS 64 #endif /* Embedded Linux with uClibc */ #ifdef __UCLIBC__ #undef HAVE_SHMEM #undef HAVE_PASSWD #endif /* Haiku */ #ifdef __HAIKU__ #undef HAVE_SHMEM #undef HAVE_PASSWD #endif /* OpenBSD */ #ifdef __OpenBSD__ #define HAVE_STRLCPY #endif /* MacOS */ #if defined(__APPLE__) #if __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_10 #define HAVE_STRLCPY #endif #endif /* AIX */ #if defined(_AIX) #define _LARGE_FILES 1 #endif /* Add other OS-specific defines here */ /* * Include headers */ #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_SENDFILE #include #include #endif #ifdef HAVE_LOCALES #include #endif #ifdef HAVE_SHMEM #include #include #else #define shm_state void #endif #if defined(HAVE_IPv4) || defined(HAVE_IPv6) #include #include #include #endif #ifdef HAVE_UNAME #include #endif #if !defined(HAVE_STRLCPY) size_t strlcpy(char *dst, const char *src, size_t siz); size_t strlcat(char *dst, const char *src, size_t siz); #endif #ifdef HAVE_LIBWRAP #include #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 #define WRAP_DENIED 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_TLS_PORT 0 #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_WIDTH 67 #define DEFAULT_CHARSET UTF_8 #define MIN_WIDTH 33 #define MAX_WIDTH 200 #define UNKNOWN_ADDR "unknown" /* 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 PROGNAME "gophernicus" #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", users[i].user /* See man 3 getpwent */ #define VHOST_FORMAT "gopher://%s/" /* ISO-8859-1 to US-ASCII look-alike conversion table */ #define ASCII \ "E?,f..++^%S??zY" \ " !c_*Y|$\"C?????" \ "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 1024 /* 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 */ #define MAX_USERS 1024 /* Maximum number of users for the ~ option */ /* 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_search[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; int server_tls_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; #ifdef __OpenBSD__ char *extra_unveil_paths; #endif /* Session */ int session_timeout; int session_max_kbytes; int session_max_hits; int session_id; /* 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_status; char opt_shm; char opt_root; char opt_proxy; char opt_exec; char opt_personal_spaces; char opt_http_requests; char debug; } state; /* Shared memory for session & accounting data */ #ifdef HAVE_SHMEM #define SHM_KEY 0xbeeb0008 /* 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; int session_id; 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; /* Struct for the userlist with date */ typedef struct { char user[32]; /* Maximum in most systems */ time_t mtime; } user_date; /* * 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 "files.h" #include "filetypes.h" /* gophernicus.c */ void info(state *st, char *str, char type); void footer(state *st); void die(state *st, const char *message, const char *description); void log_combined(state *st, int status); /* file.c */ void send_binary_file(state *st); void send_text_file(state *st); void url_redirect(state *st); void server_status(state *st, shm_state *shm, int shmid); void caps_txt(state *st, shm_state *shm); void setenv_cgi(state *st, char *script); void gopher_file(state *st); /* menu.c */ char gopher_filetype(state *st, char *file, char magic); void gopher_menu(state *st); /* string.c */ void strrepeat(char *dest, char c, size_t num); void strreplace(char *str, char from, char to); size_t strcut(char *str, size_t width); char *strkey(char *header, char *key); char strlast(char *str); void chomp(char *str); char *strcharset(int charset); void strniconv(int charset, char *out, char *in, size_t outsize); void strnencode(char *out, const char *in, size_t outsize); void strndecode(char *out, char *in, size_t outsize); void strfsize(char *out, off_t size, size_t outsize); /* platform.c */ void platform(state *st); float loadavg(void); /* session.c */ void get_shm_session(state *st, shm_state *shm); void update_shm_session(state *st, shm_state *shm); /* options.c */ void add_ftype_mapping(state *st, char *suffix); void parse_args(state *st, int argc, char *argv[]); /* log.c */ void log_init(int enable, int debug); void log_fatal(const char *fmt, ...); void log_warning(const char *fmt, ...); void log_info(const char *fmt, ...); void log_debug(const char *fmt, ...); #endif gophernicus-3.1.1/src/log.c000066400000000000000000000046121377422061400155670ustar00rootroot00000000000000#include #include #include "gophernicus.h" #if defined(LOG_UPTO) #define _LOG_UPTO LOG_UPTO #else #define _LOG_UPTO(pri) \ ( \ ((pri) == LOG_EMERG) ? (LOG_MASK(LOG_EMERG)) : \ ((pri) == LOG_ALERT) ? (LOG_MASK(LOG_EMERG) | LOG_MASK(LOG_ALERT)) : \ ((pri) == LOG_CRIT) ? (LOG_MASK(LOG_EMERG) | LOG_MASK(LOG_ALERT) | LOG_MASK(LOG_CRIT)) : \ ((pri) == LOG_ERR) ? (LOG_MASK(LOG_EMERG) | LOG_MASK(LOG_ALERT) | LOG_MASK(LOG_CRIT) | LOG_MASK(LOG_ERR)) : \ ((pri) == LOG_WARNING) ? (LOG_MASK(LOG_EMERG) | LOG_MASK(LOG_ALERT) | LOG_MASK(LOG_CRIT) | LOG_MASK(LOG_ERR) | LOG_MASK(LOG_WARNING)) : \ ((pri) == LOG_NOTICE) ? (LOG_MASK(LOG_EMERG) | LOG_MASK(LOG_ALERT) | LOG_MASK(LOG_CRIT) | LOG_MASK(LOG_ERR) | LOG_MASK(LOG_WARNING) | LOG_MASK(LOG_NOTICE)) : \ ((pri) == LOG_INFO) ? (LOG_MASK(LOG_EMERG) | LOG_MASK(LOG_ALERT) | LOG_MASK(LOG_CRIT) | LOG_MASK(LOG_ERR) | LOG_MASK(LOG_WARNING) | LOG_MASK(LOG_NOTICE) | LOG_MASK(LOG_INFO)) : \ ((pri) == LOG_DEBUG) ? (LOG_MASK(LOG_EMERG) | LOG_MASK(LOG_ALERT) | LOG_MASK(LOG_CRIT) | LOG_MASK(LOG_ERR) | LOG_MASK(LOG_WARNING) | LOG_MASK(LOG_NOTICE) | LOG_MASK(LOG_INFO) | LOG_MASK(LOG_DEBUG)) : \ 0) #endif static int _enable = 0; void log_init(int enable, int debug) { if (!enable) return; _enable = enable; openlog(PROGNAME, LOG_PID | (debug ? LOG_PERROR : 0), LOG_DAEMON); setlogmask(_LOG_UPTO(debug ? LOG_DEBUG : LOG_INFO)); } static void _vlog(int priority, const char *fmt, va_list ap) { char buf[BUFSIZE]; if (!_enable) return; vsnprintf(buf, sizeof(buf), fmt, ap); syslog(priority, "%s", buf); } static void _log(int priority, const char *fmt, ...) { va_list ap; va_start(ap, fmt); _vlog(priority, fmt, ap); va_end(ap); } void log_fatal(const char *fmt, ...) { va_list ap; va_start(ap, fmt); if (errno) { int en = errno; char buf[BUFSIZE]; vsnprintf(buf, sizeof(buf), fmt, ap); _log(LOG_CRIT, "%s (%s)", buf, strerror(en)); } else { _vlog(LOG_CRIT, fmt, ap); } va_end(ap); } #define _GOPHERNICUS_MK_LOG_FUNCTION(FCT_LVL, LOG_LVL) \ void FCT_LVL(const char *fmt, ...) \ { \ va_list ap; \ \ va_start(ap, fmt); \ _vlog(LOG_LVL, fmt, ap); \ va_end(ap); \ } _GOPHERNICUS_MK_LOG_FUNCTION(log_warning, LOG_WARNING) _GOPHERNICUS_MK_LOG_FUNCTION(log_info, LOG_INFO) _GOPHERNICUS_MK_LOG_FUNCTION(log_debug, LOG_DEBUG) #undef _GOPHERNICUS_MK_LOG_FUNCTION gophernicus-3.1.1/src/menu.c000066400000000000000000000411611377422061400157520ustar00rootroot00000000000000/* * Gophernicus * * Copyright (c) 2009-2018 Kim Holviala * Copyright (c) 2019 Gophernicus Developers * * 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() */ static 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); } /* * Date sort for userlist() */ int datesort(const void *a, const void *b) { if (((user_date *)a)->mtime > ((user_date *)b)->mtime) return -1; else return 1; } /* * Scan, stat and sort a directory folders first (scandir replacement) */ static 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 static void userlist(state *st) { struct passwd *pwd; struct stat dir; char buf[BUFSIZE]; user_date users[MAX_USERS]; 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(); int i = 0; 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 */ strcpy(users[i].user, pwd->pw_name); users[i].mtime = dir.st_mtime; i++; } /* Sort by date */ int true_length = 0; while((users[true_length].user[0] != '\0') && (true_length < MAX_USERS)) true_length++; qsort((void*)users, true_length, sizeof(users[0]), datesort); /* Loop over the found users */ for( i = 0; ((i < MAX_USERS) && (users[i].user[0] != '\0')); i++) { /* Format the user string */ snprintf(buf, sizeof(buf), USERDIR_FORMAT); /* Output */ if (st->opt_date) { ltime = localtime(&users[i].mtime); strftime(timestr, sizeof(timestr), DATE_FORMAT, ltime); printf("1%-*.*s %s - \t/~%s/\t%s\t%i" CRLF, width, width, buf, timestr, users[i].user, st->server_host, st->server_port); } else { printf("1%.*s\t/~%s/\t%s\t%i" CRLF, st->out_width, buf, users[i].user, st->server_host_default, st->server_port); } } endpwent(); } #endif /* * Print a list of available virtual hosts */ static 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, "default_filetype; } /* * Handle gophermaps */ static int gophermap(state *st, char *mapfile, int depth) { FILE *fp; struct stat file; char line[BUFSIZE]; #ifdef HAVE_POPEN char command[BUFSIZE]; #endif char *selector; char *name; char *host; char *c; char type; int port; int exe; int return_val = QUIT; /* 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)) { #ifdef HAVE_POPEN /* Quote the command in case path has spaces */ snprintf(command, sizeof(command), "'%s'", mapfile); #endif exe = TRUE; } else exe = FALSE; } /* This must be a shell include */ else { #ifdef HAVE_POPEN /* Let's assume the shell command runs as is without quoting */ sstrlcpy(command, mapfile); #endif exe = TRUE; } log_debug("parsing %s gophermap \"%s\"%s", exe ? "executable" : "static", mapfile, exe && !st->opt_exec ? ": forbidden by `-nx'" : ""); /* Try to execute or open the mapfile */ if (exe & st->opt_exec) { #ifdef HAVE_POPEN setenv_cgi(st, mapfile); if ((fp = popen(command, "r")) == NULL) return OK; #else return OK; #endif } else 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_val = OK; goto CLOSE_FP; } if (type == '.') goto CLOSE_FP; /* Print a list of users with public_gopher */ if (type == '~' && st->opt_personal_spaces) { #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 } } CLOSE_FP: #ifdef HAVE_POPEN if (exe & st->opt_exec) pclose(fp); else #endif fclose(fp); return return_val; } /* * 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"))) { if (fgets(buf, sizeof(buf), fp) == NULL) strclear(buf); 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"))) { if (fgets(buf, sizeof(buf), fp) == NULL) strclear(buf); 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); } gophernicus-3.1.1/src/options.c000066400000000000000000000160231377422061400165000ustar00rootroot00000000000000/* * Gophernicus * * Copyright (c) 2009-2018 Kim Holviala * Copyright (c) 2019 Gophernicus Developers * * 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 */ static 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, #ifdef __OpenBSD__ "U:" /* extra unveil(2) paths are OpenBSD only */ #endif "h:p:T:r:t:g:a:c:u:m:l:w:o:s:i:k:f:e:R:D:L:A:P:n:dbv?-")) != ERROR) { switch(opt) { case 'h': sstrlcpy(st->server_host, optarg); break; case 'p': st->server_port = atoi(optarg); break; case 'T': st->server_tls_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, "US-ASCII") == MATCH) st->out_charset = US_ASCII; 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; #ifdef __OpenBSD__ case 'U': st->extra_unveil_paths = optarg; break; #endif 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 == 't') { st->opt_status = FALSE; break; } if (*optarg == 'm') { st->opt_shm = FALSE; break; } if (*optarg == 'r') { st->opt_root = FALSE; break; } if (*optarg == 'p') { st->opt_proxy = FALSE; break; } if (*optarg == 'x') { st->opt_exec = FALSE; break; } if (*optarg == 'u') { st->opt_personal_spaces = FALSE; break; } if (*optarg == 'H') { st->opt_http_requests = FALSE; break; } break; case 'd': st->debug = TRUE; break; case 'b': puts(license); exit(EXIT_SUCCESS); case 'v': printf("%s/%s \"%s\" (built %s)\n", SERVER_SOFTWARE, VERSION, CODENAME, __DATE__); 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"))) { if (fgets(st->server_description, sizeof(st->server_description), fp) == NULL) strclear(st->server_description); 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"))) { if (fgets(st->server_location, sizeof(st->server_location), fp) == NULL) strclear(st->server_description); chomp(st->server_location); fclose(fp); } else strclear(st->server_location); } } gophernicus-3.1.1/src/platform.c000066400000000000000000000213771377422061400166410ustar00rootroot00000000000000/* * Gophernicus * * Copyright (c) 2009-2018 Kim Holviala * Copyright (c) 2019 Gophernicus Developers * * 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(__linux) || 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"))) { if (fgets(machine, sizeof(machine), fp) != NULL) strreplace(machine, ' ', '_'); pclose(fp); } /* Get hardware name using shell uname */ if (!*st->server_description && (fp = popen("/usr/bin/uname -M", "r"))) { if (fgets(st->server_description, sizeof(st->server_description), fp) != NULL) { strreplace(st->server_description, ',', ' '); chomp(st->server_description); } pclose(fp); } #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"))) { if (fgets(release, sizeof(release), fp) == NULL) strclear(release); pclose(fp); } /* Get hardware name */ if (!*st->server_description && (fp = popen("/usr/sbin/sysctl -n hw.model", "r"))) { /* Read hardware name */ if (fgets(buf, sizeof(buf), fp) != NULL) { /* 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; } } pclose(fp); } #endif /* Linux uname() just says Linux/kernelversion - 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 /* Get hardware type from DMI data */ if (!*st->server_description && (fp = fopen("/sys/class/dmi/id/board_vendor" , "r"))) { if (fgets(buf, sizeof(buf), fp) != NULL) { sstrlcpy(st->server_description, buf); chomp(st->server_description); } fclose(fp); if ((fp = fopen("/sys/class/dmi/id/board_name" , "r"))) { if (fgets(buf, sizeof(buf), fp) != NULL) { if (*st->server_description) sstrlcat(st->server_description, " "); sstrlcat(st->server_description, buf); chomp(st->server_description); } fclose(fp); } } /* No DMI? Get possible hypervisor name */ if (!*st->server_description && (fp = fopen("/sys/hypervisor/type" , "r"))) { if (fgets(buf, sizeof(buf), fp) != NULL) { chomp(buf); if (*buf) snprintf(st->server_description, sizeof(st->server_description), "%s virtual machine", buf); } fclose(fp); } /* Identify Gentoo */ if (!*sysname && (fp = fopen("/etc/gentoo-release", "r"))) { if (fgets(sysname, sizeof(sysname), fp) != NULL) { if ((c = strstr(sysname, "release "))) sstrlcpy(release, c + 8); if ((c = strchr(release, ' '))) *c = '\0'; if ((c = strchr(sysname, ' '))) *c = '\0'; } fclose(fp); } /* Identify RedHat */ if (!*sysname && (fp = fopen("/etc/redhat-release", "r"))) { if (fgets(sysname, sizeof(sysname), fp) != NULL) { 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"); } fclose(fp); } /* Identify Slackware */ if (!*sysname && (fp = fopen("/etc/slackware-version", "r"))) { if (fgets(sysname, sizeof(sysname), fp) != NULL) { if ((c = strchr(sysname, ' '))) { sstrlcpy(release, c + 1); *c = '\0'; } if ((c = strchr(sysname, '-'))) *c = '\0'; } fclose(fp); } /* Identify CRUX */ if (!*sysname && stat("/usr/bin/crux", &file) == OK && (file.st_mode & S_IXOTH)) { sstrlcpy(sysname, "CRUX"); if ((fp = popen("/usr/bin/crux", "r"))) { if (fgets(buf, sizeof(buf), fp) != NULL && (c = strchr(buf, ' ')) && (c = strchr(c + 1, ' '))) sstrlcpy(release, c + 1); pclose(fp); } } /* 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"))) { if (fgets(sysname, sizeof(sysname), fp) == NULL) strclear(sysname); pclose(fp); } if (!*release && (fp = popen("/usr/bin/lsb_release -r -s", "r"))) { if (fgets(release, sizeof(release), fp) == NULL) strclear(release); pclose(fp); } } /* OK, nothing worked - let's try /etc/issue for sysname */ if (!*sysname && (fp = fopen("/etc/issue", "r"))) { if (fgets(sysname, sizeof(sysname), fp) != NULL) { if ((c = strchr(sysname, ' '))) *c = '\0'; if ((c = strchr(sysname, '\\'))) *c = '\0'; } fclose(fp); } /* Debian version should be in /etc/debian_version */ if (!*release && (fp = fopen("/etc/debian_version", "r"))) { if (fgets (release, sizeof(release), fp) != NULL) if ((c = strchr(release, '/'))) *c = '\0'; fclose(fp); } #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); /* I always liked weird Perl-only functions */ chomp(sysname); chomp(release); chomp(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 log_debug("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 if ((fp = fopen("/proc/loadavg" , "r")) == NULL) return 0; if (fgets(buf, sizeof(buf), fp) == NULL) strclear(buf); 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"))) { if (fgets(buf, sizeof(buf), fp) == NULL) strclear(buf); pclose(fp); if ((c = strstr(buf, "average: ")) || (c = strstr(buf, "averages: "))) return (float) atof(c + 9); } #endif /* Fallback reply */ return 0; #endif } gophernicus-3.1.1/src/session.c000066400000000000000000000104351377422061400164710ustar00rootroot00000000000000/* * Gophernicus * * Copyright (c) 2009-2018 Kim Holviala * Copyright (c) 2019 Gophernicus Developers * * 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 static 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); } } #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; shm->session[i].session_id = rand(); 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://%s:%i/%c%s", (shm->session[i].server_port == st->server_tls_port ? "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); } /* Get public session id */ st->session_id = shm->session[i].session_id; /* 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 */ log_info("throttling user from %s for %i seconds", st->req_remote_addr, delay); sleep(delay); } } #endif gophernicus-3.1.1/src/string.c000066400000000000000000000217061377422061400163170ustar00rootroot00000000000000/* * Gophernicus * * Copyright (c) 2009-2018 Kim Holviala * Copyright (c) 2019 Gophernicus Developers * * 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) */ size_t strcut(char *str, size_t width) { unsigned char c = '\0'; 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++; } if (c) *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 * * 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 #include /* * 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