Help us shape the future of Go! Take the 2026 Go Developer Survey

skymailbridge

package
v1.3.104 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 31, 2026 License: AGPL-3.0 Imports: 15 Imported by: 0

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

View Source
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

func ParseRecipient(addr, suffix, mode string) (cipher.PubKey, string, bool, error)

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.

func ServeHandler

func ServeHandler(ctx context.Context, lis net.Listener, h Handler, heloName string, log logrus.FieldLogger) error

ServeHandler runs an SMTP server on lis until ctx is canceled or lis is closed, handing every connection to its own session with h.

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.

func (Config) Validate

func (c Config) Validate() error

Validate returns nil iff cfg has a usable Mode. Other fields use defaults silently; Mode is gated because a typo is more likely than not to mean "I wanted the other mode".

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

type Reply struct {
	Code     int
	Enhanced string // RFC 3463 status, e.g. "5.7.1"
	Text     string
}

Reply is an SMTP reply a Handler returns as an error to pick the code the session sends.

func (*Reply) Error

func (r *Reply) Error() string

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL