Documentation
¶
Overview ¶
Package skymailbridge pkg/skymailbridge/server.go c4-app-mail bridge: a minimal SMTP server-side state machine that parses inbound envelopes, extracts a peer PubKey from the recipient domain's pre-suffix DNS label, dials the peer via a transport- agnostic Dialer, and relays the envelope using net/smtp.
Two callers exist:
cmd/apps/skymail-bridge — registered visor app. Implements Dialer via app.Client.Dial over skywire's routing layer (TypeSkynet). Reuses the visor's running dmsg session.
cmd/smb — standalone binary. Implements Dialer via dmsg.Client.Dial directly, with its own keypair. Useful for headless deployments that don't run a full visor.
Both binaries use exactly the same SMTP server loop, recipient parser, and relay logic; only the Dialer differs. The address shape supported is documented under ParseRecipient.
Package skymailbridge pkg/skymailbridge/session.go c4-app-mail
Index ¶
- Constants
- func ParseRecipient(addr, suffix, mode string) (cipher.PubKey, string, bool, error)
- func Relay(ctx context.Context, dialer Dialer, cfg Config, from string, rcpts []Recipient, ...) error
- func Serve(ctx context.Context, lis net.Listener, dialer Dialer, cfg Config, ...) error
- func ServeHandler(ctx context.Context, lis net.Listener, h Handler, heloName string, ...) error
- type Config
- type Dialer
- type Envelope
- type Handler
- type Recipient
- type Reply
- type SizeLimiter
Constants ¶
const ( // DefaultSuffix is the TLD the bridge treats as a skywire-routed // recipient. Matches the resolver's pkg/skynetweb default so a // single mental model covers HTTP, TLS-MITM, and SMTP over skywire. DefaultSuffix = ".skynet" // DefaultHeloName is the EHLO/HELO greeting the bridge sends to // the peer's Postfix. Operators can override; the value mostly // shows up in Received: trace headers on the receiver side. DefaultHeloName = "skymail-bridge.local" )
Variables ¶
This section is empty.
Functions ¶
func ParseRecipient ¶
ParseRecipient inspects an envelope recipient and returns (peerPK, forward, isSkynet, err). When the address doesn't end in the configured suffix, returns (zero, "", false, nil) — caller treats that as a 550 reject rather than an error.
Address layouts:
host-prefixed: local@<host>.<base32-pk><suffix> bare: local@<base32-pk><suffix>
Both shapes require a single base32-pk DNS label (cipher.PubKeyDNSLabelLen chars) immediately before the suffix. In the host-prefixed shape the host portion may itself contain dots (e.g. "magnetosphere.net.<pk>.skynet").
Mode selects the RCPT TO rewrite rule:
"a" — verbatim: forward the recipient unchanged. Receiver's Postfix must accept the full domain (host-prefixed form requires the entire dotted domain in virtual_alias_domains; bare form requires <pk><suffix> in mydestination).
"b" — strip-when-prefixed: when the address is host-prefixed, strip ".<pk><suffix>" before forwarding so the receiver's existing virtual_alias chain handles delivery natively. When the address is bare, fall through to verbatim — receiver's Postfix accepts <pk><suffix> directly via mydestination / virtual_alias_domains. This makes mode "b" a strict superset of mode "a" while preserving the strip behavior that motivates it; operators can deploy a single bridge that accepts both address shapes and let the receiver's Postfix decide which domain forms it serves.
func Relay ¶
func Relay(ctx context.Context, dialer Dialer, cfg Config, from string, rcpts []Recipient, body []byte, log logrus.FieldLogger) error
Relay dials the peer via dialer and forwards an SMTP envelope. Errors are surfaced verbatim so the calling SMTP session can translate them to an appropriate 4xx/5xx for the upstream Postfix.
func Serve ¶
func Serve(ctx context.Context, lis net.Listener, dialer Dialer, cfg Config, log logrus.FieldLogger) error
Serve runs the bridge until ctx is canceled or lis is closed: every envelope accepted from the local MTA is relayed to the peer named by its recipients' PK label, over the network its suffix names. With cfg.Dialers set, each suffix there is accepted and dialed with its own dialer; otherwise cfg.Suffix is, with dialer.
Types ¶
type Config ¶
type Config struct {
// Suffix is the TLD the bridge treats as skywire-routed.
// Defaults to DefaultSuffix when empty.
Suffix string
// Mode selects the RCPT TO rewrite rule. Both modes accept both
// address shapes (host-prefixed `user@<host>.<pk><Suffix>` and
// bare `user@<pk><Suffix>`); they differ only in what's done
// with the host-prefixed shape:
// "b" (default) — strip-when-prefixed: ".<pk><Suffix>" is
// stripped from host-prefixed envelopes before
// forwarding; bare envelopes pass through
// verbatim. Strict superset of mode "a".
// "a" — verbatim: forward both shapes unchanged.
// Receiver's Postfix must accept the full
// ".<pk><Suffix>" domain in every envelope.
Mode string
// HeloName is the EHLO/HELO name sent to the peer. Defaults to
// DefaultHeloName when empty.
HeloName string
// Dialers, when set, maps each accepted suffix to the dialer for
// its network (".skynet" to a skywire route, ".dmsg" to dmsg); Serve's
// dialer and Suffix are then unused.
Dialers map[string]Dialer
// RemotePort is the peer routing port to dial. Defaults to 25
// (matching the SMTP convention) when zero.
RemotePort uint16
}
Config holds the runtime knobs of a bridge server. Zero-value fields fall through to documented defaults.
type Dialer ¶
type Dialer interface {
Dial(ctx context.Context, peer cipher.PubKey, port uint16) (net.Conn, error)
}
Dialer abstracts the peer-dial step so the same SMTP server loop can sit on top of either a skywire-routing transport (visor app flavor) or a direct dmsg.Client (standalone flavor).
Implementations must return a usable net.Conn or an error; returning (nil, nil) is a contract violation.
type Envelope ¶
type Envelope struct {
Conn net.Conn // the session's connection; RemoteAddr names the peer
From string
Rcpts []string
Body []byte
}
Envelope is one complete SMTP transaction: the reverse path, every accepted forward path, and the un-dot-stuffed message as received.
type Handler ¶
type Handler interface {
// Rcpt accepts or rejects one RCPT TO. accepted holds the recipients
// already taken for this envelope. Return a *Reply to choose the
// SMTP code; any other error is answered 550 5.1.1.
Rcpt(c net.Conn, from, rcpt string, accepted []string) error
// Deliver takes a complete envelope. The returned text follows
// "250 2.0.0" on success. Return a *Reply to choose the failure
// code; any other error is answered 451 4.3.0.
Deliver(ctx context.Context, env Envelope) (string, error)
}
Handler decides what an SMTP session does with the mail it receives. The session loop owns the protocol; a Handler owns policy (Rcpt) and delivery (Deliver). The bridge relays to a peer; a mailbox stores.
type Recipient ¶
type Recipient struct {
Original string // wire-format as accepted by the bridge
Forward string // address forwarded to the peer, per Mode
PeerPK cipher.PubKey // skywire dial target
}
Recipient is one parsed RCPT TO entry.
type Reply ¶
Reply is an SMTP reply a Handler returns as an error to pick the code the session sends.
type SizeLimiter ¶
type SizeLimiter interface {
MaxMessageSize() int64
}
SizeLimiter is implemented by a Handler that takes smaller messages than the session ceiling (dataSizeLimit). It is asked per session, so a limit changed at runtime applies to the next connection.