Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

gopherc

gopherc is a small Gopher server for Unix-like systems written in C11 with POSIX sockets and no external runtime dependencies.

It serves content from a document root, supports directories, text files, and binary files, and is intentionally conservative about filesystem access and protocol handling.

Features

  • Gopher menus for directories
  • Text file serving with CRLF normalization and dot-stuffing
  • Binary file serving (with sendfile optimization on Linux/FreeBSD)
  • Empty selector maps to the document root
  • Hidden files denied by default
  • Path traversal rejected
  • Symlink traversal refused with O_NOFOLLOW
  • Optional gophermap override for directory menus
  • Fork-per-connection concurrency
  • Optional privilege drop after bind/listen
  • Optional child-process limit for basic backpressure
  • IPv6 dual-stack support
  • Connection read timeout (configurable)
  • Access logging in Common Log Format
  • Daemonization (fork to background)
  • Optional chroot jail after privilege drop
  • Configuration file support
  • Verbose/debug logging
  • Graceful shutdown with child reaping

Build

Requirements:

  • cc, gcc, or clang
  • make
  • Python 3 for the regression test script

Build the server:

make

Run the regression tests:

make test

Run the C unit tests:

make unit-test

Clean build outputs:

make clean

Run

Serve the bundled example tree on localhost port 7070:

./gopherd -r example-root -a 127.0.0.1 -p 7070 -n localhost

Fetch the root menu with a simple client:

python3 - <<'PY'
import socket
s = socket.create_connection(("127.0.0.1", 7070))
s.sendall(b"\r\n")
print(s.recv(4096).decode("utf-8", "replace"))
PY

Command-Line Options

-r DOCROOT        document root to serve (required)
-a ADDRESS        bind address, default: 0.0.0.0
-p PORT           TCP port, default: 7070
-n HOSTNAME       hostname advertised in generated menus
-u USER           drop privileges to this user after binding
-g GROUP          drop privileges to this group after binding
-m COUNT          max concurrent child workers, 0 means unlimited
-t SECONDS        connection read timeout, default: 30, 0=off
-c DIR            chroot to this directory after privilege drop
-l FILE           path to access log file (appended)
-f FILE           read configuration from file
-A                allow hidden files and directories
-6                enable IPv6 (dual-stack by default, single-stack with -a)
-d                daemonize (fork to background after bind)
-v                verbose/debug logging
-V                show version
-h                show help

Notes:

  • -r is required.
  • If -n is omitted, generated menus use the bind address when possible, otherwise localhost.
  • If -u is set, supplementary groups are dropped with initgroups() before setgid() and setuid().
  • If -m is reached, new connections are refused by the parent instead of spawning more workers.
  • When using -c (chroot), the document root path (-r) should be relative to the chroot directory.
  • IPv6 dual-stack is used by default. Pass -6 with -a ::1 for IPv6-only. Without -6, the bind address determines the family.
  • Configuration file format is key=value (one per line). Lines starting with # or ; are comments.

Document Root Behavior

Selectors map to paths under the configured root.

By default:

  • names beginning with . are ignored in menus and cannot be requested
  • . and .. path components are rejected
  • symlinks are refused during traversal
  • only regular files and directories are served

Item type inference is intentionally simple:

  • directories are type 1
  • known text-like extensions are type 0
  • everything else defaults to type 9

gophermap

If a directory contains a file named gophermap, it overrides the generated listing for that directory.

Supported line forms:

  • no tabs: emitted as an informational i item using the server hostname and port
  • three or more tabs: emitted verbatim as a menu line

Any other line form is treated as invalid and the request fails.

Example:

Subdirectory banner
0Read the nested file	/subdir/readme.md	localhost	7070

Example Content

The repository includes a sample tree under example-root/:

example-root/
├── docs/
│   └── welcome.txt
├── files/
│   └── blob.bin
├── index.txt
└── subdir/
    ├── gophermap
    └── readme.md

Configuration File Format

The config file uses simple key=value pairs, one per line. Lines starting with # or ; are ignored as comments.

# gopherd configuration
docroot=/var/gopher
bind_addr=0.0.0.0
port=70
hostname=gopher.example.com
user=nobody
max_children=20
timeout=30
allow_hidden=false
ipv6_only=false
daemonize=true
verbose=false
access_log=/var/log/gopherd/access.log
chroot_dir=/var/gopher

CLI flags override config file values.

Architecture

Source layout:

  • src/main.c: CLI parsing, config file loading, access log setup
  • src/server.c: socket setup, signals, fork-per-connection loop, privilege drop, chroot, daemonization
  • src/request.c: bounded selector parsing and secure path resolution
  • src/response.c: menus, gophermap, text and binary responses (with sendfile)
  • src/util.c: logging (error, debug, access log) and small helpers
  • src/config.c: configuration file parser
  • tests/regression.py: end-to-end smoke tests
  • tests/test_unit.c: C-level unit tests for pure functions

Security Properties

This server is designed to preserve a small set of explicit invariants:

  • selector input is bounded and treated as untrusted
  • accesses stay inside the configured document root
  • partial writes are handled correctly
  • file descriptors are closed on all main paths
  • text responses use valid Gopher line termination
  • menus terminate with .\r\n
  • optional chroot jail after privilege drop
  • connection read timeout prevents resource exhaustion

Operational Notes

  • The server uses fork() per connection.
  • Children are reaped with SIGCHLD and waitpid(..., WNOHANG).
  • SIGINT and SIGTERM trigger a graceful shutdown: the listen socket is closed, remaining children are waited for.
  • SIGPIPE is ignored.
  • SO_REUSEADDR is enabled on the listening socket.
  • SO_RCVTIMEO is set on client sockets to enforce the read timeout.
  • Binary files use sendfile() on Linux and FreeBSD for zero-copy delivery.

Limitations

  • No authentication or authorization
  • No seccomp or pledge-style sandbox
  • No MIME detection beyond filename-extension heuristics
  • No advanced Gopher+ features

Development

Typical workflow:

make
make unit-test
make test
./gopherd -r example-root -a 127.0.0.1 -p 7070 -n localhost

About

A small Gopher server for Unix-like systems written in C11 with POSIX sockets and no external runtime dependencies.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages