Directory listing for: tech/gopher/gopher_tor_server
DIR Parent directory (..)
TEXT README.txt (3.6KB, 2026-05-23):
Same gopherhole served two ways at once: clearnet Gopher on port 70 and a Tor
hidden service. The catch is that a Tor visitor must get *.onion* links back ---
otherwise the first click dumps them onto the clearnet host. So the onion side
runs through a tiny shim that rewrites menu links to the `.onion`.
This descends from my earlier [reverse-proxy setup](/tech/gopher/gopher-routing).
The change: Venusia now binds :70 directly for clearnet, and the rewriting shim
sits only in front of Tor.
## Shape
```
clearnet: client ──────────────────────────────► Venusia :70 (xinetd not involved)
onion: Tor ─► :7072 (xinetd) ─► gopher_router.sh ─► nc :70 Venusia
rewrites menu-link host -> .onion
```
## Venusia binds :70 itself
Nothing to do here: the current Venusia package ships a systemd unit that
already binds privileged port 70 as the unprivileged `venusia` user. The trick
is one line --- `AmbientCapabilities=CAP_NET_BIND_SERVICE` --- which lets the
process bind a port < 1024 without root and, unlike `setcap` on the binary,
survives package upgrades:
```
User=venusia
AmbientCapabilities=CAP_NET_BIND_SERVICE
ExecStart=/usr/bin/venusia watch /var/gopher gopher.example.com 70
```
The only thing you change is the `ExecStart` host --- point it at your own
hostname (it's what Venusia prints in menu links). Check it with
`ss -ltnp 'sport = :70'`.
## Tor side
Clearnet bypasses everything below --- it hits Venusia on :70 directly. xinetd
exists *only* to wrap the onion.
torrc:
```
HiddenServiceDir /var/lib/tor/someodd_gopher
HiddenServicePort 70 127.0.0.1:7072
```
The onion's port 70 forwards to `127.0.0.1:7072` --- *not* straight to Venusia
on :70, because :7072 is where the rewriter lives. (Point it at :70 and Tor
visitors get clearnet links.) Your address is in `.../someodd_gopher/hostname`.
xinetd answers :7072 and runs the shim (file `gopher_onion`):
```
service gopher_onion
{
type = UNLISTED
port = 7072
bind = 127.0.0.1
socket_type = stream
protocol = tcp
wait = no
user = root
server = /bin/bash
server_args = /usr/local/bin/gopher_router.sh
disable = no
}
```
Apply with `sudo systemctl restart xinetd`.
The shim (`gopher_router.sh` --- copy to `/usr/local/bin`, `chmod +x`, and set
`HOST`/`ONION` to your own) forwards the request to Venusia with `nc localhost
70` and pipes the reply
through `sed --unbuffered`, rewriting the `gopher.someodd.zip` host to the
`.onion`. `--unbuffered` keeps responses streaming instead of buffering until
the backend closes.
## Tor erases the client IP
Every onion request reaches xinetd from Tor's local forward, so `$REMOTE_IP` is
always `127.0.0.1`. Anything keyed on the client address --- geolocation, per-IP
limits, bans --- sees only loopback over Tor and won't work. You also can't use
the peer IP to *detect* Tor --- local clearnet tools are loopback too.
## Testing
```
printf '\r\n' | nc localhost 70 | head # clearnet -> links say gopher.someodd.zip
printf '\r\n' | nc localhost 7072 | head # onion path -> links say .onion
torsocks gopher $(sudo cat /var/lib/tor/someodd_gopher/hostname) # end to end
```
## Caveat
The shim runs every byte through `sed`, so it can mangle *binary* downloads over
the onion (clearnet is a direct connection and unaffected). A cleaner rewriter
would touch only menu (type 1) responses.
## Files here
* torrc --- the hidden-service mapping
* gopher_onion --- xinetd service for the onion side (:7072)
* gopher_router.sh --- the link-rewriting shim
BIN gopher_onion 587B 2026-05-23
BIN gopher_router.sh 932B 2026-05-23
BIN torrc 379B 2026-05-23