module

package
v0.4.3 Latest Latest
Warning

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

Go to latest
Published: Dec 13, 2020 License: GPL-3.0 Imports: 17 Imported by: 3

Documentation

Overview

Package module contains modules registry and interfaces implemented by modules.

Interfaces are placed here to prevent circular dependencies.

Each interface required by maddy for operation is provided by some object called "module". This includes authentication, storage backends, DKIM, email filters, etc. Each module may serve multiple functions. I.e. it can be IMAP storage backend, SMTP downstream and authentication provider at the same moment.

Each module gets its own unique name (sql for go-imap-sql, proxy for proxy module, local for local delivery perhaps, etc). Each module instance also can have its own unique name can be used to refer to it in configuration.

Index

Constants

View Source
const (
	AuthDisabled     = "off"
	AuthMTASTS       = "mtasts"
	AuthDNSSEC       = "dnssec"
	AuthCommonDomain = "common_domain"
)
View Source
const (
	TLSNone TLSLevel = iota
	TLSEncrypted
	TLSAuthenticated

	MXNone MXLevel = iota
	MX_MTASTS
	MX_DNSSEC
)

Variables

View Source
var (
	// ErrUnknownCredentials should be returned by auth. provider if supplied
	// credentials are valid for it but are not recognized (e.g. not found in
	// used DB).
	ErrUnknownCredentials = errors.New("unknown credentials")
)
View Source
var (
	Initialized = make(map[string]bool)
)

Functions

func GenerateMsgID

func GenerateMsgID() (string, error)

GenerateMsgID generates a string usable as MsgID field in module.MsgMeta.

func HasInstance

func HasInstance(name string) bool

func Register

func Register(name string, factory FuncNewModule)

Register adds module factory function to global registry.

name must be unique. Register will panic if module with specified name already exists in registry.

You probably want to call this function from func init() of module package.

func RegisterAlias

func RegisterAlias(aliasName, instName string)

RegisterAlias creates an association between a certain name and instance name.

After RegisterAlias, module.GetInstance(aliasName) will return the same result as module.GetInstance(instName).

func RegisterDeprecated

func RegisterDeprecated(name, newName string, factory FuncNewModule)

RegisterDeprecated adds module factory function to global registry.

It prints warning to the log about name being deprecated and suggests using a new name.

func RegisterEndpoint

func RegisterEndpoint(name string, factory FuncNewEndpoint)

RegisterEndpoint registers an endpoint module.

See FuncNewEndpoint for information about differences of endpoint modules from regular modules.

func RegisterInstance

func RegisterInstance(inst Module, cfg *config.Map)

RegisterInstance adds module instance to the global registry.

Instance name must be unique. Second RegisterInstance with same instance name will replace previous.

Types

type Check

type Check interface {
	// CheckStateForMsg initializes the "internal" check state required for
	// processing of the new message.
	//
	// NOTE: Returned CheckState object must be hashable (usable as a map key).
	// This is used to deduplicate Check* calls, the easiest way to achieve
	// this is to have CheckState as a pointer to some struct, all pointers
	// are hashable.
	CheckStateForMsg(ctx context.Context, msgMeta *MsgMetadata) (CheckState, error)
}

Check is the module interface that is meant for read-only (with the exception of the message header modifications) (meta-)data checking.

Modules implementing this interface should be registered with "check." prefix in name.

type CheckResult

type CheckResult struct {
	// Reason is the error that is reported to the message source
	// if check decided that the message should be rejected.
	Reason error

	// Reject is the flag that specifies that the message
	// should be rejected.
	Reject bool

	// Quarantine is the flag that specifies that the message
	// is considered "possibly malicious" and should be
	// put into Junk mailbox.
	//
	// This value is copied into MsgMetadata by the msgpipeline.
	Quarantine bool

	// AuthResult is the information that is supposed to
	// be included in Authentication-Results header.
	AuthResult []authres.Result

	// Header is the header fields that should be
	// added to the header after all checks.
	Header textproto.Header
}

type CheckState

type CheckState interface {
	// CheckConnection is executed once when client sends a new message.
	//
	// Result may be cached for the whole client connection so this function
	// may not be called sometimes.
	CheckConnection(ctx context.Context) CheckResult

	// CheckSender is executed once when client sends the message sender
	// information (e.g. on the MAIL FROM command).
	CheckSender(ctx context.Context, mailFrom string) CheckResult

	// CheckRcpt is executed for each recipient when its address is received
	// from the client (e.g. on the RCPT TO command).
	CheckRcpt(ctx context.Context, rcptTo string) CheckResult

	// CheckBody is executed once after the message body is received and
	// buffered in memory or on disk.
	//
	// Check code should use passed mutex when working with the message header.
	// Body can be read without locking it since it is read-only.
	CheckBody(ctx context.Context, header textproto.Header, body buffer.Buffer) CheckResult

	// Close is called after the message processing ends, even if any of the
	// Check* functions return an error.
	Close() error
}

type ConnState

type ConnState struct {
	// IANA name (ESMTP, ESMTPS, etc) of the protocol message was received
	// over. If the message was generated locally, this field is empty.
	Proto string

	// Information about the SMTP connection, including HELO hostname and
	// source IP. Valid only if Proto refers the SMTP protocol or its variant
	// (e.g. LMTP).
	smtp.ConnectionState

	// The RDNSName field contains the result of Reverse DNS lookup on the
	// client IP.
	//
	// The underlying type is the string or untyped nil value. It is the
	// message source responsibility to populate this field.
	//
	// Valid values of this field consumers need to be aware of:
	// RDNSName = nil
	//   The reverse DNS lookup is not applicable for that message source.
	//   Typically the case for messages generated locally.
	// RDNSName != nil, but Get returns nil
	//   The reverse DNS lookup was attempted, but resulted in an error.
	//   Consumers should assume that the PTR record doesn't exist.
	RDNSName *future.Future

	// If the client successfully authenticated using a username/password pair.
	// This field contains the username.
	AuthUser string

	// If the client successfully authenticated using a username/password pair.
	// This field should be cleaned if the ConnState object is serialized
	AuthPassword string
}

ConnState structure holds the state information of the protocol used to accept this message.

type Delivery

type Delivery interface {
	// AddRcpt adds the target address for the message.
	//
	// The domain part of the address is assumed to be U-labels with NFC normalization
	// and case-folding applied. The message source should ensure that by
	// calling address.CleanDomain if necessary.
	//
	// Implementation should assume that no case-folding or deduplication was
	// done by caller code. Its implementation responsibility to do so if it is
	// necessary. It is not recommended to reject duplicated recipients,
	// however. They should be silently ignored.
	//
	// Implementation should do as much checks as possible here and reject
	// recipients that can't be used.  Note: MsgMetadata object passed to Start
	// contains BodyLength field. If it is non-zero, it can be used to check
	// storage quota for the user before Body.
	AddRcpt(ctx context.Context, rcptTo string) error

	// Body sets the body and header contents for the message.
	// If this method fails, message is assumed to be undeliverable
	// to all recipients.
	//
	// Implementation should avoid doing any persistent changes to the
	// underlying storage until Commit is called. If that is not possible,
	// Abort should (attempt to) rollback any such changes.
	//
	// If Body can't be implemented without per-recipient failures,
	// then delivery object should also implement PartialDelivery interface
	// for use by message sources that are able to make sense of per-recipient
	// errors.
	//
	// Here is the example of possible implementation for maildir-based
	// storage:
	// Calling Body creates a file in tmp/ directory.
	// Commit moves the created file to new/ directory.
	// Abort removes the created file.
	Body(ctx context.Context, header textproto.Header, body buffer.Buffer) error

	// Abort cancels message delivery.
	//
	// All changes made to the underlying storage should be aborted at this
	// point, if possible.
	Abort(ctx context.Context) error

	// Commit completes message delivery.
	//
	// It generally should never fail, since failures here jeopardize
	// atomicity of the delivery if multiple targets are used.
	Commit(ctx context.Context) error
}

type DeliveryMXAuthPolicy

type DeliveryMXAuthPolicy interface {
	// PrepareDomain is called before DNS MX lookup and may asynchronously
	// start additional lookups necessary for policy application in CheckMX
	// or CheckConn.
	//
	// If there any errors - they should be deferred to the CheckMX or
	// CheckConn call.
	PrepareDomain(ctx context.Context, domain string)

	// PrepareDomain is called before connection and may asynchronously
	// start additional lookups necessary for policy application in
	// CheckConn.
	//
	// If there any errors - they should be deferred to the CheckConn
	// call.
	PrepareConn(ctx context.Context, mx string)

	// CheckMX is called to check whether the policy permits to use a MX.
	//
	// mxLevel contains the MX security level estabilished by checks
	// executed before.
	//
	// domain is passed to the CheckMX to allow simpler implementation
	// of stateless policy objects.
	//
	// dnssec is true if the MX lookup was performed using DNSSEC-enabled
	// resolver and the zone is signed and its signature is valid.
	CheckMX(ctx context.Context, mxLevel MXLevel, domain, mx string, dnssec bool) (MXLevel, error)

	// CheckConn is called to check whether the policy permits to use this
	// connection.
	//
	// tlsLevel and mxLevel contain the TLS security level estabilished by
	// checks executed before.
	//
	// domain is passed to the CheckConn to allow simpler implementation
	// of stateless policy objects.
	//
	// If tlsState.HandshakeCompleted is false, TLS is not used. If
	// tlsState.VerifiedChains is nil, InsecureSkipVerify was used (no
	// ServerName or PKI check was done).
	CheckConn(ctx context.Context, mxLevel MXLevel, tlsLevel TLSLevel, domain, mx string, tlsState tls.ConnectionState) (TLSLevel, error)

	// Reset cleans the internal object state for use with another message.
	// newMsg may be nil if object is not needed anymore.
	Reset(newMsg *MsgMetadata)
}

DeliveryMXAuthPolicy is an interface of per-delivery object that estabilishes and verifies required and effective security for MX records and TLS connections.

type DeliveryTarget

type DeliveryTarget interface {
	// Start starts the delivery of a new message.
	//
	// The domain part of the MAIL FROM address is assumed to be U-labels with
	// NFC normalization and case-folding applied. The message source should
	// ensure that by calling address.CleanDomain if necessary.
	Start(ctx context.Context, msgMeta *MsgMetadata, mailFrom string) (Delivery, error)
}

DeliveryTarget interface represents abstract storage for the message data (typically persistent) or other kind of component that can be used as a final destination for the message.

Modules implementing this interface should be registered with "target." prefix in name.

type Dummy

type Dummy struct {
	// contains filtered or unexported fields
}

Dummy is a struct that implements PlainAuth and DeliveryTarget interfaces but does nothing. Useful for testing.

It is always registered under the 'dummy' name and can be used in both tests and the actual server code (but the latter is kinda pointless).

func (*Dummy) AuthPlain

func (d *Dummy) AuthPlain(username, _ string) error

func (*Dummy) Init

func (d *Dummy) Init(_ *config.Map) error

func (*Dummy) InstanceName

func (d *Dummy) InstanceName() string

func (*Dummy) Lookup

func (d *Dummy) Lookup(_ string) (string, bool, error)

func (*Dummy) Name

func (d *Dummy) Name() string

func (*Dummy) Start

func (d *Dummy) Start(ctx context.Context, msgMeta *MsgMetadata, mailFrom string) (Delivery, error)

type EarlyCheck

type EarlyCheck interface {
	CheckConnection(ctx context.Context, state *smtp.ConnectionState) error
}

EarlyCheck is an optional module interface that can be implemented by module implementing Check.

It is used as an optimization to reject obviously malicious connections before allocating resources for SMTP session.

The Status of this check is accept (no error) or reject (error) only, no advanced handling is available (such as 'quarantine' action and headers prepending).

type FuncNewEndpoint

type FuncNewEndpoint func(modName string, addrs []string) (Module, error)

FuncNewEndpoint is a function that creates new instance of endpoint module.

Compared to regular modules, endpoint module instances are: - Not registered in the global registry. - Can't be defined inline. - Don't have an unique name - All config arguments are always passed as an 'addrs' slice and not used as names.

As a consequence of having no per-instance name, InstanceName of the module object always returns the same value as Name.

func GetEndpoint

func GetEndpoint(name string) FuncNewEndpoint

GetEndpoints returns an endpoint module from global registry.

Nil is returned if no module with specified name is registered.

type FuncNewModule

type FuncNewModule func(modName, instName string, aliases, inlineArgs []string) (Module, error)

FuncNewModule is function that creates new instance of module with specified name.

Module.InstanceName() of the returned module object should return instName. aliases slice contains other names that can be used to reference created module instance.

If module is defined inline, instName will be empty and all values specified after module name in configuration will be in inlineArgs.

func Get

func Get(name string) FuncNewModule

Get returns module from global registry.

This function does not return endpoint-type modules, use GetEndpoint for that. Nil is returned if no module with specified name is registered.

type IMAPFilter

type IMAPFilter interface {
	// IMAPFilter is called when message is about to be stored in IMAP-compatible
	// storage. It is called only for messages delivered over SMTP, hdr and body
	// contain the message exactly how it will be stored.
	//
	// Filter can change the target directory by returning non-empty folder value.
	// Additionally it can add additional IMAP flags to the message by returning
	// them.
	//
	// Errors returned by IMAPFilter will be just logged and will not cause delivery
	// to fail.
	IMAPFilter(accountName string, meta *MsgMetadata, hdr textproto.Header, body buffer.Buffer) (folder string, flags []string, err error)
}

IMAPFilter is interface used by modules that want to modify IMAP-specific message attributes on delivery.

Modules implementing this interface should be registered with namespace prefix "imap.filter".

type MXAuthPolicy

type MXAuthPolicy interface {
	Start(*MsgMetadata) DeliveryMXAuthPolicy

	// Weight is an integer in range 0-1000 that represents relative
	// ordering of policy application.
	Weight() int
}

MXAuthPolicy is an object that provides security check for outbound connections. It can do one of the following:

- Check effective TLS level or MX level against some configured or discovered value. E.g. local policy.

- Raise the security level if certain condition about used MX or connection is met. E.g. DANE MXAuthPolicy raises TLS level to Authenticated if a matching TLSA record is discovered.

- Reject the connection if certain condition about used MX or connection is _not_ met. E.g. An enforced MTA-STS MXAuthPolicy rejects MX records not matching it.

It is not recommended to mix different types of behavior described above in the same implementation. Specifically, the first type is used mostly for local policies and is not really practical.

Modules implementing this interface should be registered with "mx_auth." prefix in name.

type MXLevel

type MXLevel int

func (MXLevel) String

func (l MXLevel) String() string

type ManageableStorage

type ManageableStorage interface {
	Storage

	ListIMAPAccts() ([]string, error)
	CreateIMAPAcct(username string) error
	DeleteIMAPAcct(username string) error
}

ManageableStorage is an extended Storage interface that allows to list existing accounts, create and delete them.

type Modifier

type Modifier interface {
	// ModStateForMsg initializes modifier "internal" state
	// required for processing of the message.
	ModStateForMsg(ctx context.Context, msgMeta *MsgMetadata) (ModifierState, error)
}

Modifier is the module interface for modules that can mutate the processed message or its meta-data.

Currently, the message body can't be mutated for efficiency and correctness reasons: It would require "rebuffering" (see buffer.Buffer doc), can invalidate assertions made on the body contents before modification and will break DKIM signatures.

Only message header can be modified. Furthermore, it is highly discouraged for modifiers to remove or change existing fields to prevent issues outlined above.

Calls on ModifierState are always strictly ordered. RewriteRcpt is newer called before RewriteSender and RewriteBody is never called before RewriteRcpts. This allows modificator code to save values passed to previous calls for use in later operations.

Modules implementing this interface should be registered with "modify." prefix in name.

type ModifierState

type ModifierState interface {
	// RewriteSender allows modifier to replace MAIL FROM value.
	// If no changes are required, this method returns its
	// argument, otherwise it returns a new value.
	//
	// Note that per-source/per-destination modifiers are executed
	// after routing decision is made so changed value will have no
	// effect on it.
	//
	// Also note that MsgMeta.OriginalFrom will still contain the original value
	// for purposes of tracing. It should not be modified by this method.
	RewriteSender(ctx context.Context, mailFrom string) (string, error)

	// RewriteRcpt replaces RCPT TO value.
	// If no changed are required, this method returns its argument, otherwise
	// it returns a new value.
	//
	// MsgPipeline will take of populating MsgMeta.OriginalRcpts. RewriteRcpt
	// doesn't do it.
	RewriteRcpt(ctx context.Context, rcptTo string) (string, error)

	// RewriteBody modifies passed Header argument and may optionally
	// inspect the passed body buffer to make a decision on new header field values.
	//
	// There is no way to modify the body and RewriteBody should avoid
	// removing existing header fields and changing their values.
	RewriteBody(ctx context.Context, h *textproto.Header, body buffer.Buffer) error

	// Close is called after the message processing ends, even if any of the
	// Rewrite* functions return an error.
	Close() error
}

type Module

type Module interface {
	// Init performs actual initialization of the module.
	//
	// It is not done in FuncNewModule so all module instances are
	// registered at time of initialization, thus initialization does not
	// depends on ordering of configuration blocks and modules can reference
	// each other without any problems.
	//
	// Module can use passed config.Map to read its configuration variables.
	Init(*config.Map) error

	// Name method reports module name.
	//
	// It is used to reference module in the configuration and in logs.
	Name() string

	// InstanceName method reports unique name of this module instance or empty
	// string if module instance is unnamed.
	InstanceName() string
}

Module is the interface implemented by all maddy module instances.

It defines basic methods used to identify instances.

Additionally, module can implement io.Closer if it needs to perform clean-up on shutdown. If module starts long-lived goroutines - they should be stopped *before* Close method returns to ensure graceful shutdown.

func GetInstance

func GetInstance(name string) (Module, error)

GetInstance returns module instance from global registry, initializing it if necessary.

Error is returned if module initialization fails or module instance does not exists.

type MsgMetadata

type MsgMetadata struct {
	// Unique identifier for this message. Randomly generated by the
	// message source module.
	ID string

	// Original message sender address as it was received by the message source.
	//
	// Note that this field is meant for use for tracing purposes.
	// All routing and other decisions should be made based on the sender address
	// passed separately (for example, mailFrom argument for CheckSender function)
	// Note that addresses may contain unescaped Unicode characters.
	OriginalFrom string

	// If set - no SrcHostname and SrcAddr will be added to Received
	// header. These fields are still written to the server log.
	DontTraceSender bool

	// Quarantine is a message flag that is should be set if message is
	// considered "suspicious" and should be put into "Junk" folder
	// in the storage.
	//
	// This field should not be modified by the checks that verify
	// the message. It is set only by the message pipeline.
	Quarantine bool

	// OriginalRcpts contains the mapping from the final recipient to the
	// recipient that was presented by the client.
	//
	// MsgPipeline will update that field when recipient modifiers
	// are executed.
	//
	// It should be used when reporting information back to client (via DSN,
	// for example) to prevent disclosing information about aliases
	// which is usually unwanted.
	OriginalRcpts map[string]string

	// SMTPOpts contains the SMTP MAIL FROM command arguments, if the message
	// was accepted over SMTP or SMTP-like protocol (such as LMTP).
	//
	// Note that the Size field should not be used as source of information about
	// the body size. Especially since it counts the header too whereas
	// Buffer.Len does not.
	SMTPOpts smtp.MailOptions

	// Conn contains the information about the underlying protocol connection
	// that was used to accept this message. The referenced instance may be shared
	// between multiple messages.
	//
	// It can be nil for locally generated messages.
	Conn *ConnState

	// This is set by endpoint/smtp to indicate that body contains "TLS-Required: No"
	// header. It is only meaningful if server has seen the body at least once
	// (e.g. the message was passed via queue).
	TLSRequireOverride bool
}

MsgMetadata structure contains all information about the origin of the message and all associated flags indicating how it should be handled by components.

All fields should be considered read-only except when otherwise is noted. Module instances should avoid keeping reference to the instance passed to it and copy the structure using DeepCopy method instead.

Compatibility with older values should be considered when changing this structure since it is serialized to the disk by the queue module using JSON. Modules should correctly handle missing or invalid values.

func (*MsgMetadata) DeepCopy

func (msgMeta *MsgMetadata) DeepCopy() *MsgMetadata

DeepCopy creates a copy of the MsgMetadata structure, also copying contents of the maps and slices.

There are a few exceptions, however: - SrcAddr is not copied and copy field references original value.

type MutableTable

type MutableTable interface {
	Table
	Keys() ([]string, error)
	RemoveKey(k string) error
	SetKey(k, v string) error
}

type PartialDelivery

type PartialDelivery interface {
	// BodyNonAtomic is similar to Body method of the regular Delivery interface
	// with the except that it allows target to reject the body only for some
	// recipients by setting statuses using passed collector object.
	//
	// This interface is preferred by the LMTP endpoint and queue implementation
	// to ensure correct handling of partial failures.
	BodyNonAtomic(ctx context.Context, c StatusCollector, header textproto.Header, body buffer.Buffer)
}

PartialDelivery is an optional interface that may be implemented by the object returned by DeliveryTarget.Start. See PartialDelivery.BodyNonAtomic documentation for details.

type PlainAuth

type PlainAuth interface {
	AuthPlain(username, password string) error
}

PlainAuth is the interface implemented by modules providing authentication using username:password pairs.

Modules implementing this interface should be registered with "auth." prefix in name.

type PlainUserDB

type PlainUserDB interface {
	PlainAuth
	ListUsers() ([]string, error)
	CreateUser(username, password string) error
	SetUserPassword(username, password string) error
	DeleteUser(username string) error
}

PlainUserDB is a local credentials store that can be managed using maddyctl utility.

type StatusCollector

type StatusCollector interface {
	// SetStatus sets the error associated with the recipient.
	//
	// rcptTo should match exactly the value that was passed to the
	// AddRcpt, i.e. if any translations was made by the target,
	// they should not affect the rcptTo argument here.
	//
	// It should not be called multiple times for the same
	// value of rcptTo. It also should not be called
	// after BodyNonAtomic returns.
	//
	// SetStatus is goroutine-safe. Implementations
	// provide necessary serialization.
	SetStatus(rcptTo string, err error)
}

StatusCollector is an object that is passed by message source that is interested in intermediate status reports about partial delivery failures.

type Storage

type Storage interface {
	// GetOrCreateIMAPAcct returns User associated with storage account specified by
	// the name.
	//
	// If it doesn't exists - it should be created.
	GetOrCreateIMAPAcct(username string) (imapbackend.User, error)
	GetIMAPAcct(username string) (imapbackend.User, error)

	// Extensions returns list of IMAP extensions supported by backend.
	IMAPExtensions() []string
}

Storage interface is a slightly modified go-imap's Backend interface (authentication is removed).

Modules implementing this interface should be registered with prefix "storage." in name.

type TLSLevel

type TLSLevel int

func (TLSLevel) String

func (l TLSLevel) String() string

type TLSLoader

type TLSLoader interface {
	LoadCerts() ([]tls.Certificate, error)
}

TLSLoader interface is module interface that can be used to supply TLS certificates to TLS-enabled endpoints.

The interface is intentionally kept simple, all configuration and parameters necessary are to be provided using conventional module configuration.

If loader returns multiple certificate chains - endpoint will serve them based on SNI matching.

Note that loading function will be called for each connections - it is highly recommended to cache parsed form.

Modules implementing this interface should be registered with prefix "tls.loader." in name.

type Table

type Table interface {
	Lookup(s string) (string, bool, error)
}

Tabele is the interface implemented by module that implementation string-to-string translation.

Modules implementing this interface should be registered with prefix "table." in name.

Jump to

Keyboard shortcuts

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