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
- Variables
- func GenerateMsgID() (string, error)
- func HasInstance(name string) bool
- func Register(name string, factory FuncNewModule)
- func RegisterAlias(aliasName, instName string)
- func RegisterDeprecated(name, newName string, factory FuncNewModule)
- func RegisterEndpoint(name string, factory FuncNewEndpoint)
- func RegisterInstance(inst Module, cfg *config.Map)
- type Blob
- type BlobStore
- type Check
- type CheckResult
- type CheckState
- type ConnState
- type Delivery
- type DeliveryMXAuthPolicy
- type DeliveryTarget
- type Dummy
- func (d *Dummy) AuthPlain(username, _ string) error
- func (d *Dummy) Init(_ *config.Map) error
- func (d *Dummy) InstanceName() string
- func (d *Dummy) Lookup(_ context.Context, _ string) (string, bool, error)
- func (d *Dummy) Name() string
- func (d *Dummy) Start(ctx context.Context, msgMeta *MsgMetadata, mailFrom string) (Delivery, error)
- type EarlyCheck
- type FuncNewEndpoint
- type FuncNewModule
- type IMAPFilter
- type MXAuthPolicy
- type MXLevel
- type ManageableStorage
- type Modifier
- type ModifierState
- type Module
- type MsgMetadata
- type MultiTable
- type MutableTable
- type PartialDelivery
- type PlainAuth
- type PlainUserDB
- type StatusCollector
- type Storage
- type TLSLevel
- type TLSLoader
- type Table
Constants ¶
const ( AuthDisabled = "off" AuthMTASTS = "mtasts" AuthDNSSEC = "dnssec" AuthCommonDomain = "common_domain" )
const ( TLSNone TLSLevel = iota TLSEncrypted TLSAuthenticated MXNone MXLevel = iota MX_MTASTS MX_DNSSEC )
const UnknownBlobSize int64 = -1
Variables ¶
var ErrNoSuchBlob = errors.New("blob_store: no such object")
var ErrUnknownCredentials = errors.New("unknown credentials")
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).
var (
Initialized = make(map[string]bool)
)
var ( // NoRun makes sure modules do not start any bacground tests. // // If it set - modules should not perform any actual work and should stop // once the configuration is read and verified to be correct. // TODO: Replace it with separation of Init and Run at interface level. NoRun = false )
Functions ¶
func GenerateMsgID ¶
GenerateMsgID generates a string usable as MsgID field in module.MsgMeta.
func HasInstance ¶
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 ¶
RegisterInstance adds module instance to the global registry.
Instance name must be unique. Second RegisterInstance with same instance name will replace previous.
Types ¶
type BlobStore ¶ added in v0.5.0
type BlobStore interface { // Create creates a new blob for writing. // // Sync will be called on the returned Blob object after -all- data has // been successfully written. // // Close without Sync can be assumed to happen due to an unrelated error // and stored data can be discarded. // // blobSize indicates the exact amount of bytes that will be written // If -1 is passed - it is unknown and implementation will not make // any assumptions about the blob size. Error can be returned by any // Blob method if more than than blobSize bytes get written. // // Passed context will cover the entire blob write operation. Create(ctx context.Context, key string, blobSize int64) (Blob, error) // Open returns the reader for the object specified by // passed key. // // If no such object exists - ErrNoSuchBlob is returned. Open(ctx context.Context, key string) (io.ReadCloser, error) // Delete removes a set of keys from store. Non-existent keys are ignored. Delete(ctx context.Context, keys []string) error }
BlobStore is the interface used by modules providing large binary object storage.
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) InstanceName ¶
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 ¶
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 ¶
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 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 ¶
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 MultiTable ¶ added in v0.5.0
MultiTable is the interface that module can implement in addition to Table if it can provide multiple values as a lookup result.
type MutableTable ¶
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 ¶
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 TLSLoader ¶
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.