Skip to content

DNS server for callers, shared ports by name, and the BBS's own CA - #56

Merged
Neo-Desktop merged 6 commits into
mainfrom
claude/project-thread-lrsbkp
Nov 11, 2026
Merged

Neo-Desktop merged 6 commits into
mainfrom
claude/project-thread-lrsbkp

Conversation

@Neo-Desktop

@Neo-Desktop Neo-Desktop commented Nov 11, 2026 •

Copy link
Copy Markdown
Collaborator

Requested by Sydney · project thread

What

A new [dns] section makes the BBS a DNS server for its callers (dial-up users above all, via pppd's ms-dns, which dmodem.conf.sample already sets to 192.168.77.1).

  • Legacy names answered with the BBS's own address, for the front ends set up here (services = auto):
    • MSN Messenger: messenger.hotmail.com, nexus.passport.com, login.passport.com, loginnet.passport.com, login.live.com
    • AIM/ICQ: login.oscar.aol.com, slogin.oscar.aol.com, login.messaging.aol.com, kdc.uas.aol.com, api.screenname.aol.com, api.oscar.aol.com, login.icq.com, slogin.icq.com
    • AOL: AmericaOnline.aol.com
    • Yahoo! Messenger: scs/scsa/scsb/scsc.msg.yahoo.com, cs.yahoo.com, vcs1/vcs2, insider, relay, filetransfer.msg.yahoo.com, relay.yahoo.com, login.yahoo.com
    • MSN Classic: gateway.moswest.msn.net; CompuServe: compuserve.com, cis.compuserve.com
    • plus the sysop's own names (with *.domain wildcards).
      The address given defaults to the one the caller reached the BBS at (the PPP side for a dial-up user), or answer/answer6. This replaces the per-client hosts-file entries MSN: serve MSN Messenger 5.0–7.5 (MSNP8–12) with a Passport server #50 needed.
  • An IP per service: a service whose HTTPS (TLS) server is on an IP of its own, with a certificate of its own, gets that IP for its names: taken from its https_address (or [oscar] tls_address, or address), or set as [dns] msn_answer, ymsg_answer, oscar_answer and so on. A port that is on every address for one front end and on a specific IP for another can't bind both, so bbs check refuses that mix, and the config editor refuses an edit that makes it (putting the old value back and showing why).
  • Everything else is resolved and cached: forwarded to the host's DNS servers (/etc/resolv.conf, or systemd-resolved's real list when it only has the 127.0.0.53 stub; the BBS's own address is never used as upstream) or the sysop's primary and backup. Answers are cached by TTL (LRU, cache_size, max_ttl), negative ones by the SOA (negative_ttl), with identical in-flight queries coalesced. Recursion is limited to allow (loopback and private ranges by default) so it isn't an open resolver, and UDP is rate limited per address.

Routes: shared ports by name

For a setup on one address, MSN's Passport, Yahoo!'s login and AIM's TLS logins all land on port 443 (and the web halves on 80). New internal/vhost lets front ends given the same address share it: TLS is terminated with the certificate for the SNI name (or the first front end's for Windows XP clients that send none), then requests are routed by Host, or by path for a client that came by IP (/RST.srf → MSN, /config/pwtoken_get → Yahoo!, /auth/clientLogin and / → AIM). Non-HTTP TLS connections (OSCAR over TLS, AIM 6.2's SSL 2.0-format hello) go to AIM. msn, ymsg and oscar skip listening on a claimed port; oscar gains HandleConn and WebHandler. Front ends on different IPs aren't shared and listen on their own as before.

Also: the chat front ends' ports and DNS (UDP and TCP) are now bound as root before the BBS drops to [general] user, so 53, 80, 443 and 569 work. bbs check prints the DNS settings and shared ports, warns when a front end isn't on its old service's port, and warns when front ends sharing a TLS port use different certificates. A port that's TLS for one front end and plain for another is refused.

The BBS's own certificate authority

tls_cert = ca ([msn], [ymsg], [oscar]) and cert_file = ca ([gemini]) have the BBS's CA (new internal/bbsca) sign the server's certificate for the names its clients ask for. No key file is needed. bbs serve makes the CA in ca/ beside the database if there is none, signs at start and checks daily (397-day leaves renewed 30 days before their end, keys kept; Gemini's leaf lasts as long as the CA, since its clients pin). Keys are RSA 2048 for AIM 6.2+ and XP; bbs ca init -sha1 signs with SHA-1 for Windows before XP SP3.

The CA is a root with sub-CAs, each with its own key. The root (ca/ca.crt, pathlen 1) is the only one callers install, and it signs only sub-CAs. The services sub-CA (ca/services-ca.crt) signs the BBS's own servers. A program beside the BBS gets a sub-CA of its own: bbs ca subca proxy makes ca/proxy-ca.crt/.key for a TLS proxy (WebOne's Go port), so the proxy never holds the root's or the services' key. Leaves are sent with their sub-CA's certificate after them, and sub-CAs are pathlen 0.

  • bbs ca [show|init|subca NAME|issue|package]: show the CA's fingerprint, its sub-CAs and what it signs; make it; make or show a sub-CA; sign a certificate with a sub-CA (bbs ca issue [-ca proxy] -out DIR NAME..., services by default); write the client package.
  • ca/install-ca.zip for callers' machines (the root only): NAME-CA.cer, an install-ca.inf and install-ca.reg that write the machine's trusted roots in the registry (Windows 95 on), remove-ca.reg, install-ca.bat (certutil, else the .reg), a macOS/iOS .mobileconfig, the PEM, and a README with the fingerprint.
  • [DNS for callers] added to the config editor's Internet services.

Also fixed: PTP client on a busy machine

TestPTP failed in CI here, and it fails on main as well when run on one busy CPU. The cause: a two-step Sync and its Follow_Up come in on two sockets, each read by its own goroutine, and the Follow_Up was dropped whenever it was handled first. No sync ever completed. A Follow_Up that arrives first is now kept until its Sync comes, and a Delay_Req is recorded before it is sent, so a quick Delay_Resp isn't lost.

Docs

New wiki page DNS.md (settings, dial-up setup, the port table, shared ports, an IP per HTTPS service, running beside systemd-resolved) and a CA section in Certificates.md (the root and its sub-CAs, what it signs, the client package, SHA-1, TLS proxies, commands). The MSN, Yahoo!, AIM, AOL, MSN Classic and CompuServe pages point to DNS; the sample config and the generated configuration reference include [dns], the *_answer keys and tls_cert = ca.

Testing

  • go test ./... and go vet ./... pass.
  • New tests: internal/dnsd (legacy and per-service answers, forwarding, cache TTL countdown and expiry, NXDOMAIN caching, backup failover, refused outsiders, resolv.conf and systemd stub handling, UDP and TCP serving), internal/vhost (SNI, Host, path and no-SNI routing, default certificate, OSCAR handoff), internal/server ([dns] parsing, per-service answers, shared ports and the every-address/specific-IP conflict), internal/confui (the editor refusing and reverting a mixing edit), internal/bbsca (root and sub-CAs, chains verified from the root alone, the root refusing to sign servers, renewal keeping keys, SHA-1, the package's files and the registry blob's layout).
  • TestPTP on one CPU with 8 busy loops: failed every run before the fix, 8 of 8 passed after.
  • Smoke tests against a running bbs serve: legacy names resolved to the BBS and example.com forwarded and cached; one shared TLS port answered MSN, Yahoo! and AIM by name and by path; with separate IPs, each service's names resolved to its own IP; with tls_cert = ca, the CA was made and MSN, Yahoo! and Gemini served certificates that verify against it. MSN's is issued by the services sub-CA, and a bbs ca issue -ca proxy certificate verifies through the proxy sub-CA but not through the services one (openssl).
  • Not yet tried with the real clients on a dial-up line, or the .inf/.reg on real Windows 9x/XP.

🤖 Generated with Claude Code

https://claude.ai/code/session_01B82eRTuaG98f8wiWHbNhPo

… name

[dns] answers the old services' login names (MSN Messenger and Passport,
AIM/ICQ, AOL, Yahoo! Messenger, MSN Classic, CompuServe) with the BBS's
own address, for the front ends set up here, so clients find the BBS
without hosts-file edits. Every other name is resolved through the host's
DNS servers (resolv.conf, or systemd-resolved's own list) or a sysop's
primary and backup, cached by TTL (negative answers by SOA), with
recursion limited to private networks and a per-address UDP rate limit.

Since those names all lead to one address, MSN's Passport, Yahoo!'s login
and AIM's TLS can now share port 443 (and the web halves port 80):
internal/vhost takes the connections and routes each by SNI, then Host,
then path, with non-HTTP TLS (OSCAR, SSL 2.0 hellos) going to AIM.
The chat front ends' ports and DNS's are bound as root before dropping
privileges.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B82eRTuaG98f8wiWHbNhPo
@Neo-Desktop Neo-Desktop self-assigned this Nov 11, 2026
A front end whose HTTPS (TLS) server is on an IP of its own, with a
self-signed certificate of its own, now has its names answered with that
IP: taken from its https_address (or [oscar] tls_address, or address),
or set as [dns] <service>_answer (an IPv4 and an IPv6 address at most).
One port on every address for one front end and on an IP of its own for
another can't both bind, so that mix is refused at check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B82eRTuaG98f8wiWHbNhPo
With the chat's HTTPS services each on an IP of their own, a port on
every address for another can't be listened on beside them. The editor
now refuses such an edit at once (the old value put back, the reason
shown) instead of only at save. A file already mixed can still be put
right an edit at a time. The DNS server joins Internet services.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B82eRTuaG98f8wiWHbNhPo
tls_cert = ca ([msn], [ymsg], [oscar]) and cert_file = ca ([gemini]) have
the BBS's CA sign the server's certificate for the names its clients ask
for: bbs serve makes the CA if there is none, signs at start and looks
daily, keeping keys across renewals (Gemini's good as long as the CA, as
its clients pin). RSA 2048 throughout; bbs ca init -sha1 for Windows
before XP SP3.

bbs ca issue signs a certificate for a TLS proxy; bbs ca package (and
serve, into ca/install-ca.zip) writes what puts the CA on callers'
machines: the .cer, an .inf and .reg writing the machine's trusted roots
(Windows 95 on), remove-ca.reg, an install .bat (certutil), a macOS/iOS
.mobileconfig, the PEM and a README with the fingerprint.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B82eRTuaG98f8wiWHbNhPo
@Neo-Desktop Neo-Desktop changed the title DNS server for callers: legacy login names to the BBS, everything else resolved and cached DNS server for callers, shared ports by name, and the BBS's own CA Nov 11, 2026
…others for programs beside it

The root (ca/ca.crt), the one callers install, now signs only sub-CAs
(pathlen 1). The services sub-CA signs tls_cert = ca servers' certificates;
bbs ca subca NAME makes another (proxy, for a TLS proxy such as WebOne's
port) with its own key. Leaves carry their sub-CA's certificate after them.
bbs ca issue takes -ca NAME (default services). A root from before, which
could sign no sub-CAs, is refused with how to replace it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B82eRTuaG98f8wiWHbNhPo

Neo-Desktop commented Nov 11, 2026 •

Copy link
Copy Markdown
Collaborator Author

CI build failed twice on 64fc48e in internal/timesync TestPTP, first with a wrong stratum and then with "no measurements". This PR didn't touch that package, but the failure isn't a flake. main fails the same way when run on one CPU under load (3 of 3 runs). The extra parallel tests in this PR make CI busy enough to show it.

Root cause, now fixed in this PR (a346bfc):

  • Main bug: a two-step Sync arrives on the event socket and its Follow_Up on the general socket, and each socket has its own reader goroutine. On a busy machine the Follow_Up is handled first. ptpUpstream.handle used to drop it as unknown, so no sync ever completed. A Follow_Up that arrives first is now kept until its Sync comes.
  • Same kind of race: delayReq recorded the Delay_Req's send time only after sendEvent returned, so a Delay_Resp handled before that was dropped. The request is now recorded before it is sent.

Verification: under the same load (taskset -c 0 with 8 busy loops), TestPTP went from failing every run to 8 of 8 passing. go vet ./... and go test ./... pass.


Generated by Claude Code

… it answers

Sync and Follow_Up come on two sockets, each read by its own goroutine, so
on a busy machine the Follow_Up was handled first, dropped as unknown, and
no two-step sync ever measured (TestPTP failing in CI with "no
measurements"; on one CPU under load, every time). A Follow_Up before its
Sync is now kept till the Sync comes. Likewise a Delay_Req is recorded
before it is sent, so a Delay_Resp handled before sendEvent returns is kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B82eRTuaG98f8wiWHbNhPo
@Neo-Desktop
Neo-Desktop merged commit cc1c89c into main Nov 11, 2026
4 checks passed
@Neo-Desktop
Neo-Desktop deleted the claude/project-thread-lrsbkp branch November 11, 2026 12:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants