Documentation
¶
Overview ¶
Package skymail pkg/skymail/helpers.go c4-app-mail
Package skymail pkg/skymail/limits.go c4-app-mail
Package skymail pkg/skymail/mailbox.go c4-app-mail
Package skymail pkg/skymail/maildir.go c4-app-mail
Package skymail is a mailbox a visor hosts for its own PK: mail to <anything>@<base32-pk>.skynet (or .dmsg) arrives over plain SMTP on port 25, is stamped with the sender's authenticated PK, and is kept verbatim in a Maildir. Nothing about it needs a certificate, a domain or a public address, so it runs as well in a browser tab as on a server. The SMTP protocol loop and the relay are shared with pkg/skymailbridge, which is what a host already running Postfix uses.
Package skymail pkg/skymail/outbox.go c4-app-mail
Package skymail pkg/skymail/parked.go c4-app-mail
Parking: mail for a visor the Outbox cannot reach is also left, sealed for that visor, on its dmsg-async inbox hosts, so it arrives when that visor comes back even if this one is offline by then. The visor fetches parked mail and files it through the same checks as mail over SMTP.
Package skymail pkg/skymail/render.go c4-app-mail
Package skymail pkg/skymail/sealed.go c4-app-mail
Package skymail pkg/skymail/seen.go c4-app-mail
Package skymail pkg/skymail/send.go c4-app-mail
Package skymail pkg/skymail/units.go c4-app-mail
Index ¶
- Constants
- Variables
- func FormatAge(d time.Duration) string
- func FormatSize(n int64) string
- func IsSealed(body []byte) bool
- func OpenSealed(body []byte, sk cipher.SecKey) ([]byte, cipher.PubKey, error)
- func ParseAge(s string) (time.Duration, error)
- func ParseSize(s string) (int64, error)
- func Seal(msg []byte, sk cipher.SecKey, rcpt cipher.PubKey) ([]byte, error)
- func SealedRecipient(body []byte) (cipher.PubKey, bool)
- func SealedSize(n int64) int64
- type Age
- type Attachment
- type AttachmentData
- type Config
- type Control
- func (c Control) Attachment(folder, id string, n int) (*AttachmentData, error)
- func (c Control) Delete(folder, id string) error
- func (c Control) List(folder string) ([]Summary, error)
- func (c Control) Raw(folder, id string) ([]byte, error)
- func (c Control) Read(folder, id string) (*Rendered, error)
- func (c Control) Reload() error
- func (c Control) Retry(id string) error
- func (c Control) Send(out Outgoing) (*SendResult, error)
- func (c Control) SetWhitelist(pks []cipher.PubKey) error
- func (c Control) Status() (Status, error)
- type Delivered
- type Host
- type KeySealer
- type Limits
- type Mailbox
- func (mb *Mailbox) Address(local, suffix string) string
- func (mb *Mailbox) Attachment(folder, id string, n int) (*AttachmentData, error)
- func (mb *Mailbox) Delete(folder, id string) error
- func (mb *Mailbox) Expire(now time.Time) (int, error)
- func (mb *Mailbox) HasOutbox() bool
- func (mb *Mailbox) Limits() Limits
- func (mb *Mailbox) List(folder string) ([]Summary, error)
- func (mb *Mailbox) MarkSeen(folder, id string) error
- func (mb *Mailbox) MaxMessageSize() int64
- func (mb *Mailbox) Queued() int
- func (mb *Mailbox) Raw(folder, id string) ([]byte, error)
- func (mb *Mailbox) Retry(id string) error
- func (mb *Mailbox) RunExpiry(ctx context.Context)
- func (mb *Mailbox) RunOutbox(ctx context.Context)
- func (mb *Mailbox) RunParked(ctx context.Context)
- func (mb *Mailbox) Send(ctx context.Context, out Outgoing) (*SendResult, error)
- func (mb *Mailbox) Serve(ctx context.Context, lis net.Listener) error
- func (mb *Mailbox) SetLimits(l Limits)
- func (mb *Mailbox) SetWhitelist(pks []cipher.PubKey) error
- func (mb *Mailbox) Unpark(ctx context.Context) int
- func (mb *Mailbox) Usage() (int64, error)
- func (mb *Mailbox) Whitelist() []cipher.PubKey
- type Outgoing
- type OutgoingAttachment
- type Parked
- type Parker
- type Queued
- type RcptResult
- type Relay
- type Rendered
- type Sealer
- type SendReply
- type SendResult
- type Service
- type ServiceConfig
- type Settings
- type Status
- type Summary
Constants ¶
const ( TokenFile = "control-token" TokenHeader = "X-Skymail-Token" //nolint:gosec // a header name, not a credential )
TokenFile holds the token a control request over TCP must carry in TokenHeader. The app writes it beside the mail when it binds a TCP address, so only who can read the mail can use it.
const ( DefaultMaxMessageSize int64 = 10 << 20 // 10 MiB, room for a few phone photos DefaultMaxTotalSize int64 = 64 << 20 // 64 MiB, every folder together DefaultMaxAge = 7 * 24 * time.Hour )
Default limits. The mailbox is on by default and open to every PK, so they are low enough that nobody can fill a browser's storage with it: a full mailbox refuses mail until expiry or a delete frees room.
const ( FolderInbox = "INBOX" FolderSent = "Sent" FolderOutbox = "Outbox" )
Folder names. Inbox is the Maildir root; others are Maildir++ subfolders ("." + name), which is what Dovecot or mutt would open.
const ( NetDmsg = "dmsg" NetSkynet = "skynet" )
Networks a Service listens on, in the order it opens them.
const PeerHeader = "X-Skymail-Peer"
PeerHeader carries the sender PK the receiving visor authenticated. The mailbox writes it at the top of every message it accepts, so the first occurrence is always its own; one a sender forged sits below.
const Port = 25
Port is SMTP's port, on dmsg and skynet alike.
const RelayHeader = "X-Skymail-Relay"
RelayHeader names the visor that delivered a sealed envelope for its signer. The mailbox writes it on the line after PeerHeader, and only a header in that place counts.
const SealedContentType = "application/vnd.skywire.skymail-sealed"
SealedContentType marks a sealed envelope.
const SealedExtension = "SKYMAIL-SEALED"
SealedExtension is the EHLO keyword of a mailbox that opens envelopes.
const SettingsFile = "settings.json"
SettingsFile keeps the settings changed at runtime beside the mail, like the whitelist. It wins over the baseline the mailbox was started with, so a change survives a restart and, in a browser tab, a reload that regenerates the visor config.
Variables ¶
var ( ErrNotSealed = errors.New("skymail: not a sealed envelope") ErrNotForUs = errors.New("skymail: sealed for another visor") ErrSealBroken = errors.New("skymail: sealed envelope does not open") ErrBadSealSign = errors.New("skymail: sealed envelope signature does not verify") )
Errors an envelope can fail to open with.
var ErrMailboxFull = errors.New("skymail: mailbox full")
ErrMailboxFull is returned when a message does not fit the quota.
var ErrNoOutbox = errors.New("skymail: this mailbox has no outbox")
ErrNoOutbox answers Retry on a mailbox opened without an Outbox.
var ErrNotFound = errors.New("skymail: no such message")
ErrNotFound is returned for a message id that is not in the folder.
var Suffixes = []string{".skynet", ".dmsg"}
Suffixes are the address TLDs a mailbox answers to. Both name the same PK; they differ only in which network a sender dials.
Functions ¶
func FormatSize ¶
FormatSize writes a size the way ParseSize reads it.
func OpenSealed ¶
OpenSealed opens an envelope sealed to the holder of sk and returns the message and the PK that signed it.
func ParseAge ¶
ParseAge reads a Go duration, also taking days ("7d"), "default" (0) or "none" (-1).
func ParseSize ¶
ParseSize reads a limit written "16MiB", "500KB" or "1048576", or "default" (0) or "none" (-1), the values Limits takes.
func SealedRecipient ¶
SealedRecipient reads the recipient PK an envelope is sealed for, which a carrier can see without opening it.
func SealedSize ¶
SealedSize is the size of the envelope Seal makes for an n byte message, for a size limit that has to admit it.
Types ¶
type Age ¶
Age is a duration written as text, such as "168h0m0s". It also reads the nanosecond number the wasm visor once wrote.
func (Age) MarshalJSON ¶
MarshalJSON implements json.Marshaler.
func (*Age) UnmarshalJSON ¶
UnmarshalJSON implements json.Unmarshaler.
type Attachment ¶
type Attachment struct {
Name string `json:"name"`
ContentType string `json:"content_type"`
Size int `json:"size"`
}
Attachment names a non-text part of a message.
type AttachmentData ¶
type AttachmentData struct {
Name string `json:"name"`
ContentType string `json:"content_type"`
Data []byte `json:"data"`
}
AttachmentData is one attachment, decoded.
func ExtractAttachment ¶
func ExtractAttachment(raw []byte, n int) (*AttachmentData, error)
ExtractAttachment decodes attachment n, numbered as Render lists them.
type Config ¶
type Config struct {
// Dir is the Maildir root. Inbox lives here, Sent in Dir/.Sent.
Dir string
// PK is the mailbox owner: mail is accepted only for <x>@<PK>.
PK cipher.PubKey
// PeerPK names the authenticated remote PK of an inbound
// connection. The visor supplies it (dmsg and skynet addresses
// differ); ok=false means the caller cannot be identified.
PeerPK func(net.Conn) (cipher.PubKey, bool)
// Dialers reach a peer's port 25, keyed by address suffix. A
// suffix with no dialer cannot be sent to.
Dialers map[string]skymailbridge.Dialer
// Limits bound what the mailbox keeps; zero fields take defaults.
Limits Limits
// Outbox queues mail for a recipient that cannot take it yet and
// retries it (RunOutbox). Off, such a send fails at once.
Outbox bool
// Sealer, when set, seals and opens envelopes as PK. The mailbox then
// opens sealed envelopes (sealed.go) that any visor delivers, and files
// them under the PK that signed them.
Sealer Sealer
// Helpers, with Sealer and the Outbox, names the visors that get a
// sealed copy of queued mail to deliver too (helpers.go).
Helpers func() []cipher.PubKey
// Parker, with Sealer and the Outbox, parks queued mail on the recipient's
// dmsg-async inbox hosts and fetches the mail parked for this visor
// (parked.go).
Parker Parker
// RelayFor, with Sealer and the Outbox, reports whether this mailbox
// carries sealed mail that pk hands over for someone else.
RelayFor func(pk cipher.PubKey) bool
// OnDeliver, when set, is told of each message the network files in the
// inbox, once it is stored. It must not block.
OnDeliver func(Delivered)
Log logrus.FieldLogger
}
Config is what a Mailbox needs from its host.
type Control ¶
Control calls a mailbox's control surface. Do sends one request and is where the caller decides how to reach the app: its in-process handler, or its TCP address with the token.
func (Control) Attachment ¶
func (c Control) Attachment(folder, id string, n int) (*AttachmentData, error)
Attachment decodes attachment n of a message.
func (Control) Send ¶
func (c Control) Send(out Outgoing) (*SendResult, error)
Send delivers out, as Mailbox.Send does.
func (Control) SetWhitelist ¶
SetWhitelist replaces the PKs allowed to deliver.
type Host ¶
type Host struct {
PK cipher.PubKey
// Listen opens Port on one of the Networks.
Listen func(network string) (net.Listener, error)
// Dialers reach a peer's Port, keyed by address suffix.
Dialers map[string]skymailbridge.Dialer
// PeerPK names the authenticated remote PK of an inbound connection.
PeerPK func(net.Conn) (cipher.PubKey, bool)
// Sealer seals and opens envelopes as PK. Nil turns sealed mail off.
Sealer Sealer
// Peers are the visors this one manages as a hypervisor and its own
// hypervisors, the default helpers and relay-for lists.
Peers func() (managed, hypervisors []cipher.PubKey)
// Parker reaches the visor's dmsg-async inbox. Nil turns parking off.
Parker Parker
OnDeliver func(Delivered)
Log logrus.FieldLogger
}
Host is what a running mailbox needs from the visor it serves.
type KeySealer ¶
KeySealer is a Sealer that holds the secret key itself.
type Limits ¶
type Limits struct {
MaxMessageSize int64 `json:"max_message_size"`
MaxTotalSize int64 `json:"max_total_size"`
MaxAge time.Duration `json:"max_age"`
}
Limits bound what a mailbox keeps. A zero field means its default; a negative one removes that bound.
func (Limits) WithDefaults ¶
WithDefaults resolves zero fields to the defaults.
type Mailbox ¶
type Mailbox struct {
// contains filtered or unexported fields
}
Mailbox is one PK's mail: a Maildir, an SMTP receiver policy, and a sender. Safe for concurrent use.
func (*Mailbox) Address ¶
Address returns the mailbox's address for local part and suffix, e.g. "me@<base32-pk>.skynet".
func (*Mailbox) Attachment ¶
func (mb *Mailbox) Attachment(folder, id string, n int) (*AttachmentData, error)
Attachment decodes attachment n of a message.
func (*Mailbox) Expire ¶
Expire deletes every Inbox and Sent message older than MaxAge and reports how many. The Outbox gives up on its own, with a bounce.
func (*Mailbox) MaxMessageSize ¶
MaxMessageSize is what the SMTP session advertises and enforces.
func (*Mailbox) RunOutbox ¶
RunOutbox delivers queued mail until ctx ends: each message when its next attempt is due, and at once when Retry asks or its recipient has just delivered mail here, which shows that visor is back.
func (*Mailbox) RunParked ¶
RunParked fetches the mail parked for this visor when it starts and every unparkInterval until ctx ends.
func (*Mailbox) Send ¶
Send delivers out to every recipient's visor now, over the network its address names, and files a copy in Sent if any delivery worked. Recipients at this mailbox's own PK are filed directly. A recipient that cannot take it yet is queued in the Outbox, and Send succeeds when every recipient was either delivered or queued.
func (*Mailbox) SetLimits ¶
SetLimits replaces the limits; zero fields take their defaults. It applies at once: the next session advertises the new size, and a lowered MaxAge expires what it now excludes on the next sweep.
func (*Mailbox) SetWhitelist ¶
SetWhitelist replaces the set of PKs allowed to deliver and saves it. An empty set accepts mail from any PK, as an unrestricted `serve` port does.
func (*Mailbox) Unpark ¶
Unpark files the mail parked for this visor and removes what it filed or can never file. Mail that may file later, such as into a full mailbox, stays parked. It reports how many messages it filed.
type Outgoing ¶
type Outgoing struct {
// From is a local part ("me") or a full address of this mailbox.
// Empty means "mail".
From string `json:"from,omitempty"`
To []string `json:"to"`
Cc []string `json:"cc,omitempty"`
Subject string `json:"subject"`
Body string `json:"body"`
InReplyTo string `json:"in_reply_to,omitempty"`
// Attachments go after the text as a multipart/mixed message.
Attachments []OutgoingAttachment `json:"attachments,omitempty"`
}
Outgoing is a plain-text message to send.
type OutgoingAttachment ¶
type OutgoingAttachment struct {
Name string `json:"name"`
ContentType string `json:"content_type,omitempty"` // default application/octet-stream
Data []byte `json:"data"`
}
OutgoingAttachment is one file to send.
type Parker ¶
type Parker interface {
// Park leaves env, sealed for rcpt, on rcpt's inbox hosts and returns the
// host that took it. ref names the message across retries.
Park(ctx context.Context, rcpt cipher.PubKey, env []byte, ref string) (cipher.PubKey, error)
// Unpark returns the mail parked for this visor without removing it.
Unpark(ctx context.Context) ([]Parked, error)
// Ack removes parked mail.
Ack(ctx context.Context, done []Parked) error
}
Parker reaches the visor's dmsg-async inbox (pkg/dmsg/dmsgasync).
type Queued ¶
type Queued struct {
// Pending are the recipients still to reach.
Pending []string `json:"pending"`
Attempts int `json:"attempts"`
LastError string `json:"last_error,omitempty"`
NextAttempt time.Time `json:"next_attempt"`
// GiveUp is when the Outbox stops trying. Zero means never.
GiveUp time.Time `json:"give_up,omitzero"`
// Handed are the helpers that hold a sealed copy to deliver too.
Handed []string `json:"handed,omitempty"`
// CarriedFor is set on sealed mail this visor delivers for the
// visor that signed it. This visor cannot read it.
CarriedFor string `json:"carried_for,omitempty"`
// Parked are the recipient visors whose inbox hosts hold a sealed copy.
Parked []string `json:"parked,omitempty"`
}
Queued is where an Outbox message stands.
type RcptResult ¶
type RcptResult struct {
Rcpt string `json:"rcpt"`
Err string `json:"error,omitempty"`
Queued bool `json:"queued,omitempty"`
// Via is the network that carried it: "skynet", "dmsg", or "local"
// for this mailbox's own PK.
Via string `json:"via,omitempty"`
}
RcptResult is the outcome for one recipient. Err says why it was not delivered. With Queued set that is only for now: the Outbox retries.
type Relay ¶
type Relay struct {
Helpers *[]cipher.PubKey `json:"helpers,omitempty"`
RelayFor *[]cipher.PubKey `json:"relay_for,omitempty"`
}
Relay is who carries this mailbox's queued mail and whose mail it carries. nil is the default, empty is none.
type Rendered ¶
type Rendered struct {
From string `json:"from"`
To string `json:"to"`
Cc string `json:"cc,omitempty"`
Subject string `json:"subject"`
Date string `json:"date"`
MessageID string `json:"message_id,omitempty"`
PeerPK string `json:"peer_pk,omitempty"`
Verified bool `json:"from_verified"`
RelayPK string `json:"relay_pk,omitempty"`
Text string `json:"text"`
FromHTML bool `json:"from_html,omitempty"` // Text was reduced from an HTML-only part
Attachments []Attachment `json:"attachments,omitempty"`
}
Rendered is a message reduced to what a reader shows.
type Sealer ¶
type Sealer interface {
Seal(msg []byte, rcpt cipher.PubKey) ([]byte, error)
Open(body []byte) ([]byte, cipher.PubKey, error)
}
Sealer seals and opens envelopes as the mailbox's owner. The owner's secret key stays with whoever implements it, which for the skymail app is the visor.
type SendReply ¶
type SendReply struct {
Result *SendResult `json:"result,omitempty"`
Error string `json:"error,omitempty"`
}
SendReply is the control surface's answer to a send. Send can fail with a result, which says why each recipient was refused.
type SendResult ¶
type SendResult struct {
ID string `json:"id,omitempty"` // the Sent copy, when anything was delivered
OutboxID string `json:"outbox_id,omitempty"` // the Outbox copy, when anything was queued
MessageID string `json:"message_id"`
Recipients []RcptResult `json:"recipients"`
}
SendResult is what Send reports.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service is a mailbox that serves Port: the receiving and sending side of the skymail app, apart from how the app reaches its visor.
func StartService ¶
func StartService(ctx context.Context, cfg ServiceConfig) (*Service, error)
StartService opens the mailbox and serves it on every network the host can listen on. dmsg is required. A visor without skynet still takes mail over dmsg, as a .skynet address cannot reach it anyway.
func (*Service) Close ¶
func (s *Service) Close()
Close stops serving and waits for the mailbox's loops, keeping its mail. Port is free again when it returns.
func (*Service) Handler ¶
Handler is the control surface of s, which the visor's mail API calls. token, when set, must arrive in TokenHeader.
func (*Service) Helpers ¶
Helpers is who gets this mailbox's queued mail, and whether that is the default: the visors it manages as a hypervisor, which trust it already.
func (*Service) RelayFor ¶
RelayFor is whose sealed mail this mailbox carries, and whether that is the default: its hypervisors, which can already do anything on it.
type ServiceConfig ¶
type ServiceConfig struct {
Host
// Dir is the Maildir root, which also holds SettingsFile.
Dir string
// Base are the limits SettingsFile overrides.
Base Limits
// Outbox queues mail for a visor that cannot take it yet.
Outbox bool
}
ServiceConfig starts a Service.
type Settings ¶
type Settings struct {
Enable *bool `json:"enable,omitempty"`
MaxMessageSize *int64 `json:"max_message_size,omitempty"`
MaxTotalSize *int64 `json:"max_total_size,omitempty"`
MaxAge *Age `json:"max_age,omitempty"`
Relay
}
Settings is what SettingsFile holds. A nil field is unset.
func LoadSettings ¶
LoadSettings reads dir's SettingsFile. A missing file is no settings.
type Status ¶
type Status struct {
Address string `json:"address"`
AddressDmsg string `json:"address_dmsg"`
Dir string `json:"dir"`
Limits Limits `json:"limits"`
Usage int64 `json:"usage"`
Whitelist []cipher.PubKey `json:"whitelist"`
Unread int `json:"unread"`
Total int `json:"total"`
HasOutbox bool `json:"has_outbox"`
Outbox int `json:"outbox"`
Sealing bool `json:"sealing"`
Helpers []cipher.PubKey `json:"helpers"`
HelpersDefault bool `json:"helpers_default"`
RelayFor []cipher.PubKey `json:"relay_for"`
RelayForDefault bool `json:"relay_for_default"`
}
Status is the state of a running mailbox.
type Summary ¶
type Summary struct {
ID string `json:"id"`
Folder string `json:"folder"`
From string `json:"from"`
To string `json:"to"`
Subject string `json:"subject"`
Date time.Time `json:"date"`
Seen bool `json:"seen"`
Size int64 `json:"size"`
// PeerPK is the sender PK the transport authenticated (inbox only).
PeerPK string `json:"peer_pk,omitempty"`
// FromVerified is true when the From address names PeerPK itself.
FromVerified bool `json:"from_verified"`
// RelayPK is the visor that delivered a sealed envelope for PeerPK.
RelayPK string `json:"relay_pk,omitempty"`
// Queue is where an Outbox message stands.
Queue *Queued `json:"queue,omitempty"`
}
Summary is one message as a list shows it.