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