letterbox

package
v0.1.12 Latest Latest
Warning

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

Go to latest
Published: Apr 3, 2021 License: AGPL-3.0 Imports: 18 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrNotFound            = errors.New("not found")
	ErrBackendError        = errors.New("couldn't complete database operation")
	ErrValidationError     = errors.New("validation failed")
	ErrAlreadyExists       = errors.New("already exists")
	ErrInitError           = errors.New("could not initialize service")
	ErrMissingDependency   = errors.New("missing dependency")
	ErrInvalidDestination  = errors.New("invalid destination")
	ErrParsingError        = errors.New("parsing error")
	ErrDomainMisalignment  = errors.New("domains not aligned")
	ErrHashFail            = errors.New("error while hashing password")
	ErrSMTPClientInitFail  = errors.New("could not connect to outbound SMTP server")
	ErrSMTPClientCloseFail = errors.New("could not close connection to outbound SMTP server")
	ErrSMTPClientSendFail  = errors.New("could not send SMTP message")
	ErrMaxSizeExceeded     = errors.New("maximum message size exceeded")
	ErrMissingID           = errors.New("missing ID")
	ErrPasswordInvalid     = errors.New("password invalid")
	ErrInvalidValue        = errors.New("invalid value")
	ErrCatchAllExists      = errors.New("catchall mailbox already exists")
)

Functions

func AddDomain added in v0.1.12

func AddDomain(params *AddDomainParams, userID uint, log logger.Logger, domainDB DomainDatastore) error

func AddInitialAdminUser

func AddInitialAdminUser(db Datastore, hasher ArgonHasher, user *User) error

AddInitialAdminUser checks (1) that there are no other users present, and (2) that the source request is from private IP space. If both are true, the given user is added as the initial admin account.

TODO change this to use the HTTP API instead of interacting direcctly with the database.

func AddMessage

func AddMessage(
	message *Message,
	mailbox *Mailbox,
	log logger.Logger,
	userDB UserDatastore,
	keyDB UserKeyDatastore,
	messageDB MessageDatastore,
) error

func AddUser

func AddUser(u *User, hasher ArgonHasher, db Datastore) error

AddUser adds a user to the datastore.

func BoolP added in v0.1.11

func BoolP(b bool) *bool

func CanAddInitialAdminUser

func CanAddInitialAdminUser(db Datastore) error

CanAddInitialAdminUser determines whether it's allowed to add the initial admin user. It is only allowed if there are no other users created.

func DecryptObject

func DecryptObject(key *UserKey, password, ciphertext string, v interface{}) error

DecryptObject decrypts an encrypted message using the given key and password, and marshals it into the interface{} provided in v.

func DeleteDomain

func DeleteDomain(domain string, db DomainDatastore) error

DeleteDomain deletes the domain with the given name.

func DeleteExpiredMailboxes

func DeleteExpiredMailboxes(db Datastore, log logger.Logger) error

DeleteExpiredMailboxes deletes all mailboxes where the current time is past their expiry time.

func DeleteMailbox

func DeleteMailbox(domain, name string, db Datastore) error

DeleteMailbox deletes the mailbox with the given name and domain.

func DisableMailboxByExternalID added in v0.1.6

func DisableMailboxByExternalID(id string, getter MailboxExternalIDGetter, updater MailboxUpdater) error

DisableMailboxByExternalID disables a particular mailbox given its external identifier. This may be called from unauthenticated contexts.

func EncryptObject

func EncryptObject(key *UserKey, v interface{}) (string, error)

EncryptObject serializes a given object to JSON, then encrypts the string.

func GetRandomString

func GetRandomString(n int) (string, error)

GetRandomString returns a random alphanumeric string of length n.

func IncrementOrInsertStat

func IncrementOrInsertStat(mailbox *Mailbox, domain string, db Datastore) error

IncrementOrInsertStat upserts a new statistic to indicate that another email from the given source domain has be received.

func PatchMailbox added in v0.1.11

func PatchMailbox(domain, name string, params *MailboxRequestParams, db Datastore) error

PatchMailbox updates the values of an existing mailbox.

func RotateMessageKeys

func RotateMessageKeys(user *User, password string, oldKey, newKey *UserKey, db Datastore) error

RotateMessageKeys rotates the keys used to encrypt all messages owned by a particular user. It attempts to re-encrypt every message it can; however, if there is a message encrypted with an unknown key, it will be left alone.

func SetMailbox added in v0.1.11

func SetMailbox(domain, name string, user *User, params *MailboxRequestParams, domainDB DomainDatastore, mailboxDB MailboxDatastore) error

SetMailbox idempotently upserts a mailbox to a specific domain.

func UpdateKey

func UpdateKey(key *UserKey, newPassword, oldPassword string) error

UpdateKey rotates a user key's passphrase (if a private key is stored on the server).

func UpdateUser

func UpdateUser(username, oldPassword string, newValue *User, hasher ArgonHasher, db Datastore) error

UpdateUser updates a user to the new values given. If the field of newValue is a zero value, it is ignored. If a new password is specified, the user's GPG key is re-encrypted under the new password.

func UpdateUserKey

func UpdateUserKey(user *User, newKey *UserKey, password string, db Datastore) error

UpdateUserKey updates the user key for the given user.

Types

type AddDomainParams added in v0.1.12

type AddDomainParams struct {
	Domain          string `json:"domain,omitempty"`
	CatchallEnabled *bool  `json:"catchall_enabled"`
}

func (*AddDomainParams) Validate added in v0.1.12

func (d *AddDomainParams) Validate() error

type Address added in v0.1.2

type Address struct {
	// A human-readable name for the address
	Name string
	// The full email address (e.g. "foo@example.com")
	Full string
	// The username portion of the email address (e.g. "foo")
	Username string
	// The domain portion of the email address (e.g. "example.com")
	Domain string
}

Address contains information about a parsed email address.

func SplitAddr added in v0.1.2

func SplitAddr(address string) (*Address, error)

SplitAddr parses a given RFC822 email address into its component parts.

type AllMessages

type AllMessages struct {
	Messages          []Message          `json:"messages,omitempty"`
	EncryptedMessages []EncryptedMessage `json:"encrypted_messages,omitempty"`
}

AllMessages is a struct to bind together both encrypted and decrypted messages.

func GetMessages

func GetMessages(mailbox *Mailbox, user *User, password string, db Datastore) (*AllMessages, error)

GetMessages returns all messages for a user and attempts to transparently decrypt them (if they're encrypted with a private key stored in the server).

type ArgonHasher added in v0.1.2

type ArgonHasher struct {
	Params argon2id.Params
}

func (*ArgonHasher) Hash added in v0.1.2

func (a *ArgonHasher) Hash(password string) (string, error)

func (*ArgonHasher) Validate added in v0.1.2

func (a *ArgonHasher) Validate(password, hash string) (bool, error)

type Datastore

Datastore embeds the datastores for each domain object.

type Domain

type Domain struct {
	gorm.Model      `json:"-"`
	Domain          string `gorm:"type:varchar(255)" json:"domain,omitempty"`
	CatchallEnabled bool   `json:"catchall_enabled"`
	UserID          uint   `json:"-"`
}

Domain represents an RFC1034 domain name.

func GetDomain

func GetDomain(domain string, db DomainDatastore) (*Domain, error)

GetDomain retreives the domain object for a specific domain name.

func (*Domain) Validate

func (d *Domain) Validate() error

Validate validates that the given domain is a valid RFC1034 domain.

type DomainDatastore

type DomainDatastore interface {
	AddDomain(d *Domain) error
	GetDomain(name string) (*Domain, error)
	GetAllDomains() ([]Domain, error)
	DeleteDomain(d *Domain) error
}

DomainDatastore represents the persistence and retrieval mechanisms for Domain objects.

type DomainStat

type DomainStat struct {
	gorm.Model `json:"-"`
	MailboxID  uint   `json:"-"`
	Domain     string `json:"domain,omitempty"`
	Count      uint   `json:"count,omitempty"`
}

DomainStat represents a count of unique sources we've seen send mail to this mailbox.

type EncryptedMessage

type EncryptedMessage struct {
	gorm.Model `json:"-"`

	// MailboxID contains the parent mailbox this message is attached to.
	MailboxID uint `json:"-"`

	// Content holds an object of type MailMessage after it's been
	// encrypted.
	Content string `json:"encrypted_message"`

	// Fingerprint holds the SHA256 fingerprint of the public key that this
	// message was encrypted with
	Fingerprint string `json:"fingerprint"`
}

EncryptedMessage represents a Message that has been encrypted using a GPG UserKey.

func EncryptMessage

func EncryptMessage(message *Message, key *UserKey) (*EncryptedMessage, error)

EncryptMessage encrypts an email message under the given key.

type FlexibleDomainCheck added in v0.1.2

type FlexibleDomainCheck struct{}

FlexibleDomainCheck checks whether the given domains fall within the same effective top-level-domain + 1. Is uses the public suffix list to determine whether the given domains are organizationally equivalent.

func (*FlexibleDomainCheck) ValidateDomainAlignment added in v0.1.2

func (*FlexibleDomainCheck) ValidateDomainAlignment(a, b string) error

type ForwardingMessage added in v0.1.2

type ForwardingMessage struct {
	FromHeader string
	ToHeader   string
	Subject    string
	TextBody   string
	HTMLBody   string
}

ForwardingMessage is a stripped-down version of Message that contains only enough information for an SMTP client.

func NewProxyMessage added in v0.1.2

func NewProxyMessage(
	message *Message,
	mailbox *Mailbox,
	log logger.Logger,
	userDB UserDatastore,
	bb *banner.Builder,
) (*ForwardingMessage, error)

NewProxyMessage creates a new message to be sent via an SMTP client. The proxy email sets the "To" header as the mailbox user's configured email. If no forwarding email configuration is found, then both returned items are nil.

type Info

type Info struct {
	Version string
	Commit  string
	Date    string
	BuiltBy string
}

Info contains some metadata about the server. This information is typically baked in at compile time, so in most development cases these fields will be empty.

type Mailbox

type Mailbox struct {
	gorm.Model `json:"-"`
	// DomainID is the reference to the domain of this address
	DomainID uint `json:"-"`

	// UserID is the reference to the user that owns this mailbox
	UserID uint `json:"-"`

	// Name is the name of this mailbox. This is the first part of the associated email address
	Name string `gorm:"type:varchar(100)" json:"name,omitempty"`

	// Address holds the full email address of this mailbox
	Address string `json:"address,omitempty"`

	// ProxyAddress is the return address used in forwarded emails
	ProxyAddress string `json:"-"`

	// Enabled indicates whether this mailbox is enabled or not. If not, then
	// all mails for this address are rejected similarly to if this mailbox
	// didn't exist.
	Enabled bool `json:"enabled"`

	// Expires indicates an absolute (RFC3339) time for this mailbox to expire. If this is set,
	// the mailbox will be deleted after the designated time.
	Expires *time.Time `json:"expires,omitempty"`

	// DomainWhitelist contains an array of domains to match against. If this is
	// set, the "from" header for all mail destined for this mailbox must match
	// one of the domains listed in this whitelist. Whitelist domains may also
	// be wildcards, with '*' matching multiple characters and '?' matching
	// single characters.
	DomainWhitelist pq.StringArray `gorm:"type:varchar(100)[]" json:"domainWhitelist,omitempty"`

	// ExternalID is a randomly-generated ID which can be used to reference this
	// mailbox from unauthenticated contexts, such as the "disable mailbox"
	// endpoint.
	ExternalID string `json:"-"`
}

Mailbox represents all the relevant information about a particular mailbox.

func GetMailbox

func GetMailbox(domain, name string, db Datastore) (*Mailbox, error)

GetMailbox gets the mailbox with the given domain and name.

func GetOrCreateMailboxForAddress added in v0.1.12

func GetOrCreateMailboxForAddress(to string, domainDB DomainDatastore, mailboxDB MailboxDatastore) (*Mailbox, error)

GetOrCreateMailboxForAddress retreives a Mailbox object given an RFC822-formated email address.

func (*Mailbox) Validate

func (m *Mailbox) Validate() error

Validate validates whether this mailbox contains valid values.

type MailboxDatastore

type MailboxDatastore interface {
	MailboxExternalIDGetter
	MailboxUpdater
	GetMailboxesByDomain(domain *Domain) ([]Mailbox, error)
	GetMailboxByDomain(domain *Domain, name string) (*Mailbox, error)
	GetAllMailboxes() ([]Mailbox, error)
	GetExpiredMailboxes() ([]Mailbox, error)
	DeleteMailbox(m *Mailbox) error
}

MailboxDatastore defines the persistence and retrieval mechanisms for mailboxes.

type MailboxExternalIDGetter added in v0.1.6

type MailboxExternalIDGetter interface {
	GetMailboxByExternalID(id string) (*Mailbox, error)
}

type MailboxRequestParams added in v0.1.11

type MailboxRequestParams struct {
	// Enabled indicates whether this mailbox is enabled or not. If not, then
	// all mail for this address is rejected similarly to if this mailbox
	// didn't exist.
	Enabled *bool `json:"enabled,omitempty"`

	// Expires indicates an absolute (RFC3339) time for this mailbox to expire. If this is set,
	// the mailbox will be deleted after the designated time.
	Expires *time.Time `json:"expires,omitempty"`

	// DomainWhitelist contains an array of domains to match against. If this is
	// set, the "from" header for all mail destined for this mailbox must match
	// one of the domains listed in this whitelist. Whitelist domains may also
	// be wildcards, with '*' matching multiple characters and '?' matching
	// single characters.
	DomainWhitelist []string `json:"domainWhitelist,omitempty"`
}

type MailboxStatsDatastore

type MailboxStatsDatastore interface {
	GetStats(mailbox *Mailbox) ([]DomainStat, error)
	GetStat(mailbox *Mailbox, domain string) (*DomainStat, error)
	UpdateStat(stat *DomainStat) error
	CreateStat(mailbox *Mailbox, stat *DomainStat) error
}

MailboxStatsDatastore defines our persistence and retrieval mechaisms for DomainsStat.

type MailboxUpdater added in v0.1.6

type MailboxUpdater interface {
	UpdateMailbox(m *Mailbox) error
}

type Message

type Message struct {
	From          string    `json:"from,omitempty"`
	To            string    `json:"to,omitempty"`
	HTMLBody      string    `json:"html_body,omitempty"`
	TextBody      string    `json:"text_body,omitempty"`
	Raw           string    `json:"raw,omitempty"`
	ReceivedTime  time.Time `json:"received_time,omitempty"`
	FromHeader    string    `json:"from_header,omitempty"`
	ToHeader      string    `json:"to_header,omitempty"`
	DateHeader    time.Time `json:"date_header,omitempty"`
	Subject       string    `json:"subject,omitempty"`
	DKIMStatus    string    `json:"dkim_status,omitempty"`
	SPFResult     string    `json:"spf_result,omitempty"`
	DKIMAlignment string    `json:"dkim_alignment,omitempty"`
	SPFAlignment  string    `json:"spf_alignment,omitempty"`
}

Message defines all the relevant fields of an email message.

type MessageDatastore

type MessageDatastore interface {
	AddMessage(mb *Mailbox, m *EncryptedMessage) error
	GetMessages(mb *Mailbox) ([]EncryptedMessage, error)
	UpdateMessage(m *EncryptedMessage) error
	DeleteMessage(m *EncryptedMessage) error
	GetMessagesForUser(u *User) ([]EncryptedMessage, error)
}

MessageDatastore defines our persistence and retrieval mechaisms for messages.

type StrictDomainCheck added in v0.1.2

type StrictDomainCheck struct{}

StrictDomainCheck checks whether the domains are exactly equivalent. This is normally less useful, since there are many cases where a sending domain will be slighlly differenct than the domain in the FROM header.

func (*StrictDomainCheck) ValidateDomainAlignment added in v0.1.2

func (*StrictDomainCheck) ValidateDomainAlignment(a, b string) error

type User

type User struct {
	gorm.Model `json:"-"`
	Username   string `gorm:"unique_index;type:varchar(100)" json:"username,omitempty"`
	Email      string `gorm:"type:varchar(100)" json:"email,omitempty"`
	Password   string `gorm:"type:varchar(255)" json:"password,omitempty"`
}

User stores information about a user.

func (*User) Scrub

func (u *User) Scrub()

Scrub empties the password hash from a user object.

func (*User) Validate

func (u *User) Validate() error

Validate validates whether the values of the User are valid.

type UserDatastore

type UserDatastore interface {
	AddUser(u *User) error
	GetUser(name string) (*User, error)
	UpdateUser(u *User) error
	GetAllUsers() ([]User, error)
	GetMailboxUser(mailbox *Mailbox) (*User, error)
}

UserDatastore defines all our persistence and retrieval mechanisms for a particular user.

type UserKey

type UserKey struct {
	gorm.Model `json:"-"`
	UserID     uint   `json:"-"`
	PrivateKey string `json:"-"`
	PublicKey  string `json:"public_key,omitempty"`
}

UserKey defines a GPG key stored in our server. If this contains a private key, it will be used for decryption as well as encryption.

func NewKeyPair

func NewKeyPair(username, password string) (*UserKey, error)

NewKeyPair generates a new PGP key for the given user. The password in the user object MUST be unhashed.

func (*UserKey) Validate

func (u *UserKey) Validate() error

Validate validates that the given UserKey is valid. TODO validate that the contents look like public/private keys.

type UserKeyDatastore

type UserKeyDatastore interface {
	AddUserKey(user *User, key *UserKey) error
	GetUserKey(user *User) (*UserKey, error)
	UpdateUserKey(key *UserKey) error
}

UserKeyDatastore defines our persistence and retrieval mechanisms for UserKeys.

Directories

Path Synopsis
Code generated by counterfeiter.
Code generated by counterfeiter.

Jump to

Keyboard shortcuts

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