feat: answer unknown senders, gated on proving they hold their address

process now splits by whether the sender owns a feed. The owner path runs
first, so a rotated owner is never mistaken for a registration, and behaves
exactly as before for ordinary mail.

The stranger path is the new one, and every branch of it is gated. No signed
Reply-To means no address to verify or answer, so it stays a silent drop. The
blocked check runs before anything touches the network, so Twt-Block cannot be
sidestepped by re-registering. Silent mode stays silent, which is how every
version before this behaved — except for an admin, who would otherwise have no
way to turn registration back on.

Then the gate the whole change rests on: before any outbound mail, the claimed
address is resolved on its own server and the resolved key must equal the key
that signed the request. Without it a signed Reply-To naming someone else's
address would be enough to take their feed, and replying to the claimed
smol:// URI would pin the sender's key as verified under that address — a
permanent denial of service against whoever really owns it. Every reply is
queued against the short form for the same reason; nothing hands a
self-certifying URI to send.

Registration attempts are counted before the dial rather than after, since one
that fails its key check costs the same connection. A per-cycle dial budget
bounds how long the state lock is held, and a mail over budget keeps its row
instead of being dropped.

Admin commands are recognised only from an admin address, and only Admit,
Block and Registration count as admin commands — an admin's Twt-Help means
Twt-Help. A non-admin sending one gets the ordinary answer and is never told
admin commands exist.

Rig still passes, so the owner path is unchanged in practice as well as in
intent.
This commit is contained in:
randogoth 2026-10-14 20:21:11 +03:00
parent 77d822cebe
commit f1084c1056
3 changed files with 519 additions and 26 deletions

View file

@ -33,8 +33,8 @@ welcome-body =
Your smolboid feed is live: { $url }
Post by sending mail to this address with the twt as the body. Manage it
with Twt-Nick, Twt-Delete, Twt-Repost and Twt-Prune frontmatter fields
(PLAN.md).
with the Twt-Nick, Twt-Delete, Twt-Repost and Twt-Prune frontmatter
fields, and send Twt-Help to get this list again.
## Rejection reasons — one per `reason::Reason` variant, rendered into the
## reply subject and body above. Each is a sentence fragment completing
@ -80,3 +80,75 @@ reason-one-command-per-mail =
reason-bad-address = `{ $got }` is not a smolmail address
reason-registration-mode-unknown =
unknown registration mode `{ $got }`, want open, closed or silent
## First contact — an unknown sender who may register
info-subject = twtxt: how to get a feed
info-body =
You can publish a twtxt feed here by sending mail.
Two steps. First accept this address in your client, so our replies reach
your inbox instead of your requests tier:
fumi accept { $mailbox }
Then ask for a feed, optionally naming the nick you want:
fumi send { $mailbox } --header "Twt-Register: yournick"
We will reply with your feed's address and the commands for managing it.
Nothing is published until you ask for a feed, and this mail is the only
one you get until you do.
## First contact while registration is closed
info-closed-subject = twtxt: registration is closed
info-closed-body =
Thanks for writing. This instance is not taking new feeds at the moment,
so there is nothing for you to do here yet.
Nothing was published and nothing was kept.
## Twt-Help — the command reference
help-subject = twtxt: your commands
help-body =
Your feed: { $url }
Post by sending mail to this address with the twt as the body. The body is
published byte for byte, at most 160 characters.
Management fields, one per mail:
Twt-Nick: yournick set the displayed nick
Twt-Delete: <timestamp> remove one post or repost
Twt-Repost: <mention> <#id> publish a repost
Twt-Prune: fifo | off what a full feed does at its size cap
Twt-Help: send this list again
help-admin-section =
Admin, from your address:
Twt-Admit: user@host give someone a feed
Twt-Block: user@host withdraw someone's token
Twt-Registration: open | closed | silent
## Admin notifications
admin-notify-subject = twtxt: new feed { $slug }
admin-notify-body =
A feed was created.
Address: { $address }
Feed: { $url }
Fingerprint: { $fingerprint }
Verify that fingerprint out of band if you do not already know this
author. Twt-Block withdraws their token.
admin-ack-subject = twtxt: { $what }
admin-ack-body =
{ $what }
Sent because your address is listed as an admin of this instance.