Documentation
¶
Overview ¶
Package milter provides an interface to implement milter mail filters and MTAs that can talk to milter programs.
Index ¶
- Constants
- Variables
- func AddAngle(str string) string
- func RemoveAngle(str string) string
- type Action
- type ActionType
- type Client
- type ClientSession
- func (s *ClientSession) Abort(macros map[MacroName]string) error
- func (s *ClientSession) ActionOption(opt OptAction) bool
- func (s *ClientSession) BodyChunk(chunk []byte) (*Action, error)
- func (s *ClientSession) BodyReadFrom(r io.Reader) ([]ModifyAction, *Action, error)
- func (s *ClientSession) Close() error
- func (s *ClientSession) Conn(hostname string, family ProtoFamily, port uint16, addr string) (*Action, error)
- func (s *ClientSession) DataStart() (*Action, error)
- func (s *ClientSession) End() ([]ModifyAction, *Action, error)
- func (s *ClientSession) Header(hdr textproto.Header) (*Action, error)
- func (s *ClientSession) HeaderEnd() (*Action, error)
- func (s *ClientSession) HeaderField(key, value string, macros map[MacroName]string) (*Action, error)
- func (s *ClientSession) Helo(helo string) (*Action, error)
- func (s *ClientSession) Mail(sender string, esmtpArgs string) (*Action, error)
- func (s *ClientSession) ProtocolOption(opt OptProtocol) bool
- func (s *ClientSession) Rcpt(rcpt string, esmtpArgs string) (*Action, error)
- func (s *ClientSession) Reset(macros Macros) error
- func (s *ClientSession) Skip() bool
- func (s *ClientSession) Unknown(cmd string, macros map[MacroName]string) (*Action, error)
- type DataSize
- type Dialer
- type MacroBag
- type MacroName
- type MacroStage
- type Macros
- type Milter
- type Modifier
- func (m *Modifier) AddHeader(name, value string) error
- func (m *Modifier) AddRecipient(r string, esmtpArgs string) error
- func (m *Modifier) ChangeFrom(value string, esmtpArgs string) error
- func (m *Modifier) ChangeHeader(index int, name, value string) error
- func (m *Modifier) DeleteRecipient(r string) error
- func (m *Modifier) InsertHeader(index int, name, value string) error
- func (m *Modifier) Progress() error
- func (m *Modifier) Quarantine(reason string) error
- func (m *Modifier) ReplaceBody(r io.Reader) error
- func (m *Modifier) ReplaceBodyRawChunk(chunk []byte) error
- type ModifyAction
- type ModifyActionType
- type NegotiationCallbackFunc
- type NewMilterFunc
- type NoOpMilter
- func (NoOpMilter) Abort(_ *Modifier) error
- func (NoOpMilter) BodyChunk(chunk []byte, m *Modifier) (*Response, error)
- func (NoOpMilter) Cleanup()
- func (NoOpMilter) Connect(host string, family string, port uint16, addr string, m *Modifier) (*Response, error)
- func (NoOpMilter) Data(m *Modifier) (*Response, error)
- func (NoOpMilter) EndOfMessage(m *Modifier) (*Response, error)
- func (NoOpMilter) Header(name string, value string, m *Modifier) (*Response, error)
- func (NoOpMilter) Headers(m *Modifier) (*Response, error)
- func (NoOpMilter) Helo(name string, m *Modifier) (*Response, error)
- func (NoOpMilter) MailFrom(from string, esmtpArgs string, m *Modifier) (*Response, error)
- func (NoOpMilter) RcptTo(rcptTo string, esmtpArgs string, m *Modifier) (*Response, error)
- func (NoOpMilter) Unknown(cmd string, m *Modifier) (*Response, error)
- type OptAction
- type OptProtocol
- type Option
- func WithAction(action OptAction) Option
- func WithActions(actions OptAction) Option
- func WithDialer(dialer Dialer) Option
- func WithDynamicMilter(newMilter NewMilterFunc) Option
- func WithMacroRequest(stage MacroStage, macros []MacroName) Option
- func WithMaximumVersion(version uint32) Option
- func WithMilter(newMilter func() Milter) Option
- func WithNegotiationCallback(negotiationCallback NegotiationCallbackFunc) Option
- func WithOfferedMaxData(offeredMaxData DataSize) Option
- func WithProtocol(protocol OptProtocol) Option
- func WithProtocols(protocol OptProtocol) Option
- func WithReadTimeout(timeout time.Duration) Option
- func WithUsedMaxData(usedMaxData DataSize) Option
- func WithWriteTimeout(timeout time.Duration) Option
- func WithoutAction(action OptAction) Option
- func WithoutDefaultMacros() Option
- func WithoutProtocol(protocol OptProtocol) Option
- type ProtoFamily
- type Response
- type Server
Examples ¶
Constants ¶
const AllClientSupportedActionMasks = OptAddHeader | OptChangeBody | OptAddRcpt | OptRemoveRcpt | OptChangeHeader | OptQuarantine | OptChangeFrom | OptAddRcptWithArgs | OptSetMacros
const MaxClientProtocolVersion uint32 = 6
MaxClientProtocolVersion is the maximum Milter protocol version implemented by the client.
const MaxServerProtocolVersion uint32 = 6
MaxServerProtocolVersion is the maximum Milter protocol version implemented by the server.
Variables ¶
var ( // RespAccept signals to the MTA that the current transaction should be accepted. // No more events get send to the milter after this response. RespAccept = &Response{code: wire.Code(wire.ActAccept)} // RespContinue signals to the MTA that the current transaction should continue RespContinue = &Response{code: wire.Code(wire.ActContinue)} // RespDiscard signals to the MTA that the current transaction should be silently discarded. // No more events get send to the milter after this response. RespDiscard = &Response{code: wire.Code(wire.ActDiscard)} // RespReject signals to the MTA that the current transaction should be rejected with a hard rejection. // No more events get send to the milter after this response. RespReject = &Response{code: wire.Code(wire.ActReject)} // RespTempFail signals to the MTA that the current transaction should be rejected with a temporary error code. // The sending MTA might try to deliver the same message again at a later time. // No more events get send to the milter after this response. RespTempFail = &Response{code: wire.Code(wire.ActTempFail)} // RespSkip signals to the MTA that transaction should continue and that the MTA // does not need to send more events of the same type. This response one makes sense/is possible as // return value of [Milter.RcptTo], [Milter.Header] and [Milter.BodyChunk]. // No more events get send to the milter after this response. RespSkip = &Response{code: wire.Code(wire.ActSkip)} )
Define standard responses with no data
var ErrModificationNotAllowed = errors.New("milter: modification not allowed via milter protocol negotiation")
var ErrServerClosed = errors.New("milter: server closed")
ErrServerClosed is returned by the Server's Server.Serve method after a call to Server.Close.
var LogWarning = logWarning
LogWarning is called by this library when it wants to output a warning. Warnings can happen even when the library user did everything right (because the other end did something wrong)
The default implementation uses log.Print to output the warning. You can re-assign LogWarning to something more suitable for your application. But do not assign nil to it.
Functions ¶
func AddAngle ¶ added in v0.6.6
AddAngle adds <> to an address. If str already has <>, then str is returned unchanged.
func RemoveAngle ¶ added in v0.6.6
RemoveAngle removes <> from an address. If str does not have <>, then str is returned unchanged.
Types ¶
type Action ¶
type Action struct { Type ActionType // SMTP code if milter wants to abort the connection/message. Zero otherwise. SMTPCode uint16 // Properly formatted reply text if milter wants to abort the connection/message. Empty string otherwise. SMTPReply string }
func (Action) StopProcessing ¶
StopProcessing returns true when the milter wants to immediately stop this SMTP connection. You can use [Action.SMTPReply] to send as reply to the current SMTP command.
type ActionType ¶
type ActionType int
const ( ActionAccept ActionType = iota + 1 ActionContinue ActionDiscard ActionReject ActionTempFail ActionSkip ActionRejectWithCode )
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is a wrapper for managing milter connections to one milter.
You need to call Session to actually open a connection to the milter.
Example ¶
// create milter definition once client := milter.NewClient("tcp", "127.0.0.1:1234") globalMacros := milter.NewMacroBag() globalMacros.Set(milter.MacroMTAFQDN, "localhost.local") globalMacros.Set(milter.MacroMTAPid, "123") // on each SMTP connection macros := globalMacros.Copy() session, err := client.Session(macros) if err != nil { panic(err) } defer session.Close() handleMilterResponse := func(act *milter.Action, err error) { if err != nil { // you should disable this milter for this connection or close the SMTP transaction panic(err) } if act.StopProcessing() { // abort SMTP transaction, you can use act.SMTPReply to send to the SMTP client panic(act.SMTPReply) } if act.Type == milter.ActionDiscard { // close the milter connection (do not send more SMTP events of this SMTP transaction) // but keep SMTP connection open and after DATA, silently discard the message panic(session.Close()) } } // for each received SMTP command set relevant macros and send it to the milter macros.Set(milter.MacroIfAddr, "127.0.0.1") macros.Set(milter.MacroIfName, "eth0") handleMilterResponse(session.Conn("spammer.example.com", milter.FamilyInet, 0, "127.0.0.15")) macros.Set(milter.MacroSenderHostName, "spammer.example.com") macros.Set(milter.MacroTlsVersion, "SSLv3") handleMilterResponse(session.Helo("spammer.example.com")) macros.Set(milter.MacroMailMailer, "esmtp") macros.Set(milter.MacroMailHost, "example.com") macros.Set(milter.MacroMailAddr, "spammer@example.com") handleMilterResponse(session.Mail("<spammer@example.com>", "")) macros.Set(milter.MacroRcptMailer, "local") macros.Set(milter.MacroRcptHost, "example.com") macros.Set(milter.MacroRcptAddr, "other-spammer@example.com") handleMilterResponse(session.Rcpt("<other-spammer@example.com>", "")) macros.Set(milter.MacroRcptMailer, "local") macros.Set(milter.MacroRcptHost, "example.com") macros.Set(milter.MacroRcptAddr, "other-spammer2@example.com") handleMilterResponse(session.Rcpt("<other-spammer2@example.com>", "")) // After DataStart you should send the initial SMTP data to the first milter, accept and apply its modifications // and then send this modified data to the next milter. Before this point all milters could be queried in parallel. handleMilterResponse(session.DataStart()) handleMilterResponse(session.HeaderField("From", "Your Bank <spammer@example.com>", nil)) handleMilterResponse(session.HeaderField("To", "Your <spammer@example.com>", nil)) handleMilterResponse(session.HeaderField("Subject", "Your money", nil)) macros.SetHeaderDate(time.Date(2023, time.January, 1, 1, 1, 1, 0, time.UTC)) handleMilterResponse(session.HeaderField("Date", "Sun, 1 Jan 2023 00:00:00 +0000", nil)) handleMilterResponse(session.HeaderEnd()) mActs, act, err := session.BodyReadFrom(strings.NewReader("Hello You,\r\ndo you want money?\r\nYour bank\r\n")) if err != nil { panic(err) } if act.StopProcessing() { panic(act.SMTPReply) } for _, mAct := range mActs { // process mAct log.Print(mAct) }
Output:
func NewClient ¶
NewClient creates a new Client object connection to a miter at network / address. If you do not specify any opts the defaults are:
It uses 10 seconds for connection/read/write timeouts and allows milter to send any actions supported by library.
You generally want to use WithAction to advertise to the milter what modification options your MTA supports. A value of 0 is a valid value –then your MTA only supports accepting or rejecting an SMTP transaction.
If WithDialer is not used, a net.Dialer with 10 seconds connection timeout will be used. If WithMaximumVersion is not used, MaxClientProtocolVersion will be used. If WithProtocol or WithProtocols is not set, it defaults to all protocol features the library can handle for the specified maximum milter version. If WithOfferedMaxData is not used, DataSize64K will be used. If WithoutDefaultMacros or WithMacroRequest are not used the following default macro stages are used:
WithMacroRequest(StageConnect, []MacroName{MacroMTAFQDN, MacroDaemonName, MacroIfName, MacroIfAddr}) WithMacroRequest(StageHelo, []MacroName{MacroTlsVersion, MacroCipher, MacroCipherBits, MacroCertSubject, MacroCertIssuer}) WithMacroRequest(StageMail, []MacroName{MacroAuthType, MacroAuthAuthen, MacroAuthSsf, MacroAuthAuthor, MacroMailMailer, MacroMailHost, MacroMailAddr}) WithMacroRequest(StageRcpt, []MacroName{MacroRcptMailer, MacroRcptHost, MacroRcptAddr}) WithMacroRequest(StageEOM, []MacroName{MacroQueueId})
This function will panic when you provide invalid options.
func (*Client) Session ¶
func (c *Client) Session(macros Macros) (*ClientSession, error)
Session opens a new connection to this milter and negotiates protocol features with it.
The macros parameter defines the Macros this ClientSession will use to send to the milter. It can be nil then this session will not send any macros to the milter. Set macro values as soon as you know them (e.g. the MacroMTAFQDN macro can be set before calling Session). It is your responsibility to clear command specific macros like MacroRcptMailer after the command got executed (on all milters in a list of milters).
This method is go-routine save.
type ClientSession ¶
type ClientSession struct {
// contains filtered or unexported fields
}
ClientSession is a connection to one Client for one SMTP connection.
func (*ClientSession) Abort ¶
func (s *ClientSession) Abort(macros map[MacroName]string) error
Abort sends Abort to the milter. You can call Mail in this same session after a successful call to Abort.
This should be called for a premature but valid end of the SMTP session. That is when the SMTP client issues a RSET or QUIT command after at least Helo was called.
You can send macros to the milter with macros. They only get send to the milter when it wants unknown commands.
func (*ClientSession) ActionOption ¶
func (s *ClientSession) ActionOption(opt OptAction) bool
ActionOption checks whether the option is set in negotiated options.
func (*ClientSession) BodyChunk ¶
func (s *ClientSession) BodyChunk(chunk []byte) (*Action, error)
BodyChunk sends a single body chunk to the milter.
It is callers responsibility to ensure every chunk is not bigger than defined in WithUsedMaxData.
BodyChunk can be called even after the milter responded with ActSkip. This method translates a ActSkip milter response into a ActContinue response but after a successful ActSkip response Skip will return true.
func (*ClientSession) BodyReadFrom ¶
func (s *ClientSession) BodyReadFrom(r io.Reader) ([]ModifyAction, *Action, error)
BodyReadFrom is a helper function that calls BodyChunk repeatedly to transmit entire body from io.Reader and then calls End.
See documentation for these functions for details.
You may first call BodyChunk and then call BodyReadFrom but after BodyReadFrom the End method gets called automatically.
func (*ClientSession) Close ¶
func (s *ClientSession) Close() error
Close releases resources associated with the session and closes the connection to the milter.
If there is a milter sequence in progress the CodeQuit command is called to signal closure to the milter.
You can call Close at any time in the session, and you can call Close multiple times without harm.
func (*ClientSession) Conn ¶
func (s *ClientSession) Conn(hostname string, family ProtoFamily, port uint16, addr string) (*Action, error)
Conn sends the connection information to the milter.
It should be called once per milter session (from Session to Close). Exception: After you called Reset you need to call Conn again.
func (*ClientSession) DataStart ¶
func (s *ClientSession) DataStart() (*Action, error)
DataStart sends the start of the DATA command to the milter. DataStart can be automatically called from Header, but you should normally call it explicitly.
When your MTA can handle multiple milter in a chain, DataStart is the last event that is called individually for each milter in the chain. After DataStart you need to call the HeaderField/Header and BodyChunk&End/BodyReadFrom calls for the whole message serially to each milter. The first milter may alter the message and the next milter should receive the altered message, not the original message.
func (*ClientSession) End ¶
func (s *ClientSession) End() ([]ModifyAction, *Action, error)
End sends the EOB message and resets session back to the state before Mail call. The same ClientSession can be used to check another message arrived within the same SMTP connection (Helo and Conn information is preserved).
Close should be called to conclude session.
func (*ClientSession) Header ¶
func (s *ClientSession) Header(hdr textproto.Header) (*Action, error)
Header sends each field from textproto.Header followed by EOH unless header messages are disabled during negotiation.
You may call HeaderField before calling this method but since it calls HeaderEnd afterwards you should call BodyChunk or BodyReadFrom.
func (*ClientSession) HeaderEnd ¶
func (s *ClientSession) HeaderEnd() (*Action, error)
HeaderEnd send the EOH (End-Of-Header) message to the milter.
No HeaderField calls are allowed after this point.
func (*ClientSession) HeaderField ¶
func (s *ClientSession) HeaderField(key, value string, macros map[MacroName]string) (*Action, error)
HeaderField sends a single header field to the milter.
Value should be the original field value without any unfolding applied. value may contain the last CR LF that ist the end marker of this header.
HeaderEnd() must be called after the last field.
You can send macros to the milter with macros. They only get send to the milter when it wants header values and it did not send a skip response. Thus, the macros you send here should be relevant to this header only.
func (*ClientSession) Helo ¶
func (s *ClientSession) Helo(helo string) (*Action, error)
Helo sends the HELO hostname to the milter.
It should be called once per milter session (from Client.Session to Close).
func (*ClientSession) Mail ¶
func (s *ClientSession) Mail(sender string, esmtpArgs string) (*Action, error)
Mail sends the sender (with optional esmtpArgs) to the milter.
func (*ClientSession) ProtocolOption ¶
func (s *ClientSession) ProtocolOption(opt OptProtocol) bool
ProtocolOption checks whether the option is set in negotiated options.
func (*ClientSession) Rcpt ¶
func (s *ClientSession) Rcpt(rcpt string, esmtpArgs string) (*Action, error)
Rcpt sends the RCPT TO rcpt (with optional esmtpArgs) to the milter. If s.ProtocolOption(OptRcptRej) is true the milter wants rejected recipients. The default is to only send valid recipients to the milter.
func (*ClientSession) Reset ¶
func (s *ClientSession) Reset(macros Macros) error
Reset sends CodeQuitNewConn to the milter so this session can be used for another connection.
You can use this to do connection pooling - but that could be quite flaky since not all milters can handle CodeQuitNewConn sendmail or postfix do not use CodeQuitNewConn and never re-use a connection. Existing milters might not expect the MTA to use this feature.
func (*ClientSession) Skip ¶
func (s *ClientSession) Skip() bool
Skip can be used after a BodyChunk, HeaderField or Rcpt call to check if the milter indicated to not need any more of these events. You can directly skip to the next event class. It is not an error to ignore this and just keep sending the same events since ClientSession will handle skipping internally.
func (*ClientSession) Unknown ¶
Unknown sends an unknown command to the milter. This can happen at any time in the connection. Although you should probably do not call it after DataStart until End was called.
You can send macros to the milter with macros. They only get send to the milter when it wants unknown commands.
type DataSize ¶
type DataSize uint32
DataSize defines the maximum data size for milter or MTA to use.
The DataSize does not include the one byte for the command byte. Only three sizes are defined in the milter protocol.
type MacroBag ¶
type MacroBag struct {
// contains filtered or unexported fields
}
MacroBag is a default implementation of the Macros interface. A MacroBag is safe for concurrent use by multiple goroutines. It has special handling for the date related macros and can be copied.
The zero value of MacroBag is invalid. Use NewMacroBag to create an empty MacroBag.
func NewMacroBag ¶
func NewMacroBag() *MacroBag
func (*MacroBag) Copy ¶
Copy copies the macros to a new MacroBag. The time.Time values set by MacroBag.SetCurrentDate and MacroBag.SetHeaderDate do not get copied.
func (*MacroBag) SetCurrentDate ¶
func (*MacroBag) SetHeaderDate ¶
type MacroName ¶
type MacroName = string
const ( MacroMTAVersion MacroName = "v" // MTA Version (and MTA name in case of Postfix) MacroMTAFQDN MacroName = "j" // MTA fully qualified domain name MacroDaemonName MacroName = "{daemon_name}" // name of the daemon of the MTA. E.g. MTA-v4 or smtpd or anything the user configured. MacroDaemonAddr MacroName = "{daemon_addr}" // Local server IP address MacroDaemonPort MacroName = "{daemon_port}" // Local server TCP port MacroIfName MacroName = "{if_name}" // Interface name of the interface the MTA is accepting the SMTP connection MacroIfAddr MacroName = "{if_addr}" // IP address of the interface the MTA is accepting the SMTP connection MacroTlsVersion MacroName = "{tls_version}" // TLS version in use (set after STARTTLS or when SMTPS is used) MacroCipher MacroName = "{cipher}" // Cipher suite used (set after STARTTLS or when SMTPS is used) MacroCipherBits MacroName = "{cipher_bits}" // Strength of the cipher suite in bits (set after STARTTLS or when SMTPS is used) MacroCertSubject MacroName = "{cert_subject}" // Validated client cert's subject information (only when mutual TLS is in use) MacroCertIssuer MacroName = "{cert_issuer}" // Validated client cert's issuer information (only when mutual TLS is in use) MacroClientAddr MacroName = "{client_addr}" // Remote client IP address MacroClientPort MacroName = "{client_port}" // Remote client TCP port MacroClientPTR MacroName = "{client_ptr}" // Client name from address → name lookup MacroClientName MacroName = "{client_name}" // Remote client hostname MacroClientConnections MacroName = "{client_connections}" // Connection concurrency for this client MacroQueueId MacroName = "i" // The queue ID for this message. Some MTAs only assign a Queue ID after the DATA command (Postfix) MacroAuthType MacroName = "{auth_type}" // The used authentication method (LOGIN, DIGEST-MD5, etc) MacroAuthAuthen MacroName = "{auth_authen}" // The username of the authenticated user MacroAuthSsf MacroName = "{auth_ssf}" // The key length (in bits) of the used encryption layer (TLS) – if any MacroAuthAuthor MacroName = "{auth_author}" // The optional overwrite username for this message MacroMailMailer MacroName = "{mail_mailer}" // the delivery agent for this MAIL FROM (e.g. esmtp, lmtp) MacroMailHost MacroName = "{mail_host}" // the domain part of the MAIL FROM address MacroMailAddr MacroName = "{mail_addr}" // the MAIL FROM address (only the address without <>) MacroRcptMailer MacroName = "{rcpt_mailer}" // MacroRcptMailer holds the delivery agent for the current RCPT TO address MacroRcptHost MacroName = "{rcpt_host}" // The domain part of the RCPT TO address MacroRcptAddr MacroName = "{rcpt_addr}" // the RCPT TO address (only the address without <>) )
Macros that have good support between MTAs like sendmail and Postfix
const ( MacroRFC1413AuthInfo MacroName = "_" MacroHopCount MacroName = "c" MacroSenderHostName MacroName = "s" MacroProtocolUsed MacroName = "r" MacroMTAPid MacroName = "p" MacroDateRFC822Origin MacroName = "a" MacroDateRFC822Current MacroName = "b" MacroDateANSICCurrent MacroName = "d" MacroDateSecondsCurrent MacroName = "t" )
Macros that do not have good cross-MTA support. Only usable with sendmail as MTA.
type MacroStage ¶
type MacroStage = byte
const ( StageConnect MacroStage = iota // SMFIM_CONNECT StageHelo // SMFIM_HELO StageMail // SMFIM_ENVFROM StageRcpt // SMFIM_ENVRCPT StageData // SMFIM_DATA StageEOM // SMFIM_EOM StageEOH // SMFIM_EOH StageEndMarker // is used for command level macros for Abort, Unknown and Header commands StageNotFoundMarker // identifies that a macro was not found )
type Milter ¶
type Milter interface { // Connect is called to provide SMTP connection data for incoming message. // Suppress with OptNoConnect. // // If this method returns an error the error will be logged and the connection will be closed. // If there is a [Response] (and we did not negotiate [OptNoConnReply]) this response will be sent before closing the connection. Connect(host string, family string, port uint16, addr string, m *Modifier) (*Response, error) // Helo is called to process any HELO/EHLO related filters. Suppress with [OptNoHelo]. // // If this method returns an error the error will be logged and the connection will be closed. // If there is a [Response] (and we did not negotiate [OptNoHeloReply]) this response will be sent before closing the connection. Helo(name string, m *Modifier) (*Response, error) // MailFrom is called to process filters on envelope FROM address. Suppress with [OptNoMailFrom]. // // If this method returns an error the error will be logged and the connection will be closed. // If there is a [Response] (and we did not negotiate [OptNoMailReply]) this response will be sent before closing the connection. MailFrom(from string, esmtpArgs string, m *Modifier) (*Response, error) // RcptTo is called to process filters on envelope TO address. Suppress with [OptNoRcptTo]. // // If this method returns an error the error will be logged and the connection will be closed. // If there is a [Response] (and we did not negotiate [OptNoRcptReply]) this response will be sent before closing the connection. RcptTo(rcptTo string, esmtpArgs string, m *Modifier) (*Response, error) // Data is called at the beginning of the DATA command (after all RCPT TO commands). Suppress with [OptNoData]. // // If this method returns an error the error will be logged and the connection will be closed. // If there is a [Response] (and we did not negotiate [OptNoDataReply]) this response will be sent before closing the connection. Data(m *Modifier) (*Response, error) // Header is called once for each header in incoming message. Suppress with [OptNoHeaders]. // // If this method returns an error the error will be logged and the connection will be closed. // If there is a [Response] (and we did not negotiate [OptNoHeaderReply]) this response will be sent before closing the connection. Header(name string, value string, m *Modifier) (*Response, error) // Headers gets called when all message headers have been processed. Suppress with [OptNoEOH]. // // If this method returns an error the error will be logged and the connection will be closed. // If there is a [Response] (and we did not negotiate [OptNoEOHReply]) this response will be sent before closing the connection. Headers(m *Modifier) (*Response, error) // BodyChunk is called to process next message body chunk data (up to 64KB // in size). Suppress with [OptNoBody]. If you return [RespSkip] the MTA will stop // sending more body chunks. But older MTAs do not support this and in this case there are more calls to BodyChunk. // Your code should be able to handle this. // // If this method returns an error the error will be logged and the connection will be closed. // If there is a [Response] (and we did not negotiate [OptNoBodyReply]) this response will be sent before closing the connection. BodyChunk(chunk []byte, m *Modifier) (*Response, error) // EndOfMessage is called at the end of each message. All changes to message's // content & attributes must be done here. // The MTA can start over with another message in the same connection but that is handled in a new Milter instance. // // If this method returns an error the error will be logged and the connection will be closed. // If there is a [Response] this response will be sent before closing the connection. EndOfMessage(m *Modifier) (*Response, error) // Abort is called if the current message has been aborted. All message data // should be reset prior to the [Milter.MailFrom] callback. Connection data should be // preserved. [Milter.Cleanup] is not called before or after Abort. Abort(m *Modifier) error // Unknown is called when the MTA got an unknown command in the SMTP connection. // // If this method returns an error the error will be logged and the connection will be closed. // If there is a [Response] (and we did not negotiate [OptNoUnknownReply]) this response will be sent before closing the connection. Unknown(cmd string, m *Modifier) (*Response, error) // Cleanup always gets called when the [Milter] is about to be discarded. // E.g. because the MTA closed the connection, one SMTP message was successful or there was an error. // May be called more than once for a single [Milter]. Cleanup() }
Milter is an interface for milter callback handlers.
type Modifier ¶
type Modifier struct { Macros Macros // contains filtered or unexported fields }
Modifier provides access to Macros to callback handlers. It also defines a number of functions that can be used by callback handlers to modify processing of the email message. Besides Modifier.Progress they can only be called in the EndOfMessage callback.
func NewTestModifier ¶ added in v0.6.0
func NewTestModifier(macros Macros, writePacket, writeProgress func(msg *wire.Message) error, actions OptAction, maxDataSize DataSize) *Modifier
NewTestModifier is only exported for unit-tests. It can only be use internally since it uses the internal package wire.
func (*Modifier) AddHeader ¶
AddHeader appends a new email message header to the message
Unfortunately when interacting with Sendmail it is not guaranteed that the header will be added at the end. If Sendmail has a (maybe deleted) header of the same name in the list of headers, this header will be altered/re-used instead of adding a new header at the end.
If you always want to add the header at the very end you need to use InsertHeader with a very high index.
func (*Modifier) AddRecipient ¶
AddRecipient appends a new envelope recipient for current message. You can optionally specify esmtpArgs to pass along. You need to negotiate this via OptAddRcptWithArgs with the MTA.
Sendmail will validate the provided esmtpArgs and if it deems them invalid it will error out.
func (*Modifier) ChangeFrom ¶
ChangeFrom replaces the FROM envelope header with value. You can also define ESMTP arguments. But beware of the following Sendmail comment:
Even though all ESMTP arguments could be set via this call, it does not make sense to do so for many of them, e.g., SIZE and BODY. Setting those may cause problems, proper care must be taken. Moreover, there is no feedback from the MTA to the milter whether the call was successful.
func (*Modifier) ChangeHeader ¶
ChangeHeader replaces the header at the specified position with a new one. The index is per canonical name and one-based. To delete a header pass an empty value. If the index is bigger than there are headers with that name, then ChangeHeader will actually add a new header at the end of the header list (With the same semantic as AddHeader).
func (*Modifier) DeleteRecipient ¶
DeleteRecipient removes an envelope recipient address from message
func (*Modifier) InsertHeader ¶
InsertHeader inserts the header at the specified position. index is one-based. The index 0 means at the very beginning.
Unfortunately when interacting with Sendmail the index is used to find the position in Sendmail's internal list of headers. Not all of those internal headers get send to the milter. Thus, you cannot really add a header at a specific position when the milter client is Sendmail.
func (*Modifier) Quarantine ¶
Quarantine a message by giving a reason to hold it
func (*Modifier) ReplaceBody ¶
ReplaceBody reads from r and send its contents in the least amount of chunks to the MTA.
This function does not do any CR LF line ending canonicalization or maximum line length enforcements. If you need that you can use the various transform.Transformers of this package to wrap your reader.
t := transform.Chain(&milter.CrLfCanonicalizationTransformer{}, &milter.MaximumLineLengthTransformer{}) wrappedR := transform.NewReader(r, t) m.ReplaceBody(wrappedR)
This function tries to use as few calls to Modifier.ReplaceBodyRawChunk as possible.
You can call ReplaceBody multiple times. The MTA will combine all those calls into one message.
You should do the ReplaceBody calls all in one go without intersecting it with other modification actions. MTAs like Postfix do not allow that.
func (*Modifier) ReplaceBodyRawChunk ¶
ReplaceBodyRawChunk sends one chunk of the body replacement.
The chunk get send as-is. Caller needs to ensure that the chunk does not exceed the maximum configured data size (defaults to DataSize64K)
You should do the ReplaceBodyRawChunk calls all in one go without intersecting it with other modification actions. MTAs like Postfix do not allow that.
type ModifyAction ¶
type ModifyAction struct { Type ModifyActionType // Recipient to add/remove if Type == ActionAddRcpt or ActionDelRcpt. // This value already includes the necessary <>. Rcpt string // ESMTP arguments for recipient address if Type = ActionAddRcpt. RcptArgs string // New envelope sender if Type = ActionChangeFrom. // This value already includes the necessary <>. From string // ESMTP arguments for envelope sender if Type = ActionChangeFrom. FromArgs string // Portion of body to be replaced if Type == ActionReplaceBody. Body []byte // Index of the header field to be changed if Type = ActionChangeHeader or Type = ActionInsertHeader. // Index is 1-based. // // If Type = ActionChangeHeader the index is per canonical value of HdrName. // E.g. HeaderIndex = 3 and HdrName = "DKIM-Signature" mean "change third field with the canonical header name Dkim-Signature". // Order is the same as of HeaderField calls. // // If Type = ActionInsertHeader the index is global to all headers, 1-based and means "insert after the HeaderIndex header". // A HeaderIndex of 0 has the special meaning "at the very beginning". // // Deleted headers (Type = ActionChangeHeader and HeaderValue == "") may change the indexes of the other headers. // Postfix MTA removes the header from the linked list (and thus change the indexes of headers coming after the deleted header). // Sendmail on the other hand will only mark the header as deleted. HeaderIndex uint32 // Header field name to be added/changed if Type == ActionAddHeader or // ActionChangeHeader or ActionInsertHeader. HeaderName string // Header field value to be added/changed if Type == ActionAddHeader or // ActionChangeHeader or ActionInsertHeader. If set to empty string - the field // should be removed. HeaderValue string // Quarantine reason if Type == ActionQuarantine. Reason string }
type ModifyActionType ¶
type ModifyActionType int
const ( ActionAddRcpt ModifyActionType = iota + 1 ActionDelRcpt ActionQuarantine ActionReplaceBody ActionChangeFrom ActionAddHeader ActionChangeHeader ActionInsertHeader )
type NegotiationCallbackFunc ¶
type NegotiationCallbackFunc func(mtaVersion, milterVersion uint32, mtaActions, milterActions OptAction, mtaProtocol, milterProtocol OptProtocol, offeredDataSize DataSize) (version uint32, actions OptAction, protocol OptProtocol, maxDataSize DataSize, err error)
NegotiationCallbackFunc is the signature of a WithNegotiationCallback function. With this callback function you can override the negotiation process.
type NewMilterFunc ¶
type NewMilterFunc func(version uint32, action OptAction, protocol OptProtocol, maxData DataSize) Milter
NewMilterFunc is the signature of a function that can be used with WithDynamicMilter to configure the Milter backend. The parameters version, action, protocol and maxData are the negotiated values.
type NoOpMilter ¶
type NoOpMilter struct{}
NoOpMilter is a dummy Milter implementation that does nothing.
func (NoOpMilter) Abort ¶
func (NoOpMilter) Abort(_ *Modifier) error
func (NoOpMilter) BodyChunk ¶
func (NoOpMilter) BodyChunk(chunk []byte, m *Modifier) (*Response, error)
func (NoOpMilter) Cleanup ¶
func (NoOpMilter) Cleanup()
func (NoOpMilter) EndOfMessage ¶
func (NoOpMilter) EndOfMessage(m *Modifier) (*Response, error)
type OptAction ¶
type OptAction uint32
OptAction sets which actions the milter wants to perform. Multiple options can be set using a bitmask.
const ( OptAddHeader OptAction = 1 << 0 // SMFIF_ADDHDRS OptChangeBody OptAction = 1 << 1 // SMFIF_CHGBODY / SMFIF_MODBODY OptAddRcpt OptAction = 1 << 2 // SMFIF_ADDRCPT OptRemoveRcpt OptAction = 1 << 3 // SMFIF_DELRCPT OptChangeHeader OptAction = 1 << 4 // SMFIF_CHGHDRS OptQuarantine OptAction = 1 << 5 // SMFIF_QUARANTINE OptChangeFrom OptAction = 1 << 6 // SMFIF_CHGFROM [v6] OptAddRcptWithArgs OptAction = 1 << 7 // SMFIF_ADDRCPT_PAR [v6] OptSetMacros OptAction = 1 << 8 // SMFIF_SETSYMLIST [v6] )
Set which actions the milter wants to perform.
type OptProtocol ¶
type OptProtocol uint32
OptProtocol masks out unwanted parts of the SMTP transaction. Multiple options can be set using a bitmask.
const ( OptNoConnect OptProtocol = 1 << 0 // MTA does not send connect events. SMFIP_NOCONNECT OptNoHelo OptProtocol = 1 << 1 // MTA does not send HELO/EHLO events. SMFIP_NOHELO OptNoMailFrom OptProtocol = 1 << 2 // MTA does not send MAIL FROM events. SMFIP_NOMAIL OptNoRcptTo OptProtocol = 1 << 3 // MTA does not send RCPT TO events. SMFIP_NORCPT OptNoBody OptProtocol = 1 << 4 // MTA does not send message body data. SMFIP_NOBODY OptNoHeaders OptProtocol = 1 << 5 // MTA does not send message header data. SMFIP_NOHDRS OptNoEOH OptProtocol = 1 << 6 // MTA does not send end of header indication event. SMFIP_NOEOH OptNoHeaderReply OptProtocol = 1 << 7 // Milter does not send a reply to header data. SMFIP_NR_HDR, SMFIP_NOHREPL OptNoUnknown OptProtocol = 1 << 8 // MTA does not send unknown SMTP command events. SMFIP_NOUNKNOWN OptNoData OptProtocol = 1 << 9 // MTA does not send the DATA start event. SMFIP_NODATA OptSkip OptProtocol = 1 << 10 // MTA supports ActSkip. SMFIP_SKIP [v6] OptRcptRej OptProtocol = 1 << 11 // Filter wants rejected RCPTs. SMFIP_RCPT_REJ [v6] OptNoConnReply OptProtocol = 1 << 12 // Milter does not send a reply to connection event. SMFIP_NR_CONN [v6] OptNoHeloReply OptProtocol = 1 << 13 // Milter does not send a reply to HELO/EHLO event. SMFIP_NR_HELO [v6] OptNoMailReply OptProtocol = 1 << 14 // Milter does not send a reply to MAIL FROM event. SMFIP_NR_MAIL [v6] OptNoRcptReply OptProtocol = 1 << 15 // Milter does not send a reply to RCPT TO event. SMFIP_NR_RCPT [v6] OptNoDataReply OptProtocol = 1 << 16 // Milter does not send a reply to DATA start event. SMFIP_NR_DATA [v6] OptNoUnknownReply OptProtocol = 1 << 17 // Milter does not send a reply to unknown command event. SMFIP_NR_UNKN [v6] OptNoEOHReply OptProtocol = 1 << 18 // Milter does not send a reply to end of header event. SMFIP_NR_EOH [v6] OptNoBodyReply OptProtocol = 1 << 19 // Milter does not send a reply to body chunk event. SMFIP_NR_BODY [v6] // OptHeaderLeadingSpace lets the [Milter] request that the MTA does not swallow a leading space // when passing the header value to the milter. // Sendmail by default eats one space (not tab) after the colon. So the header line (spaces replaced with _): // Subject:__Test // gets transferred as HeaderName "Subject" and HeaderValue "_Test". If the milter // sends OptHeaderLeadingSpace to the MTA it requests that it wants the header value as is. // So the MTA should send HeaderName "Subject" and HeaderValue "__Test". // // [Milter] that do e.g. DKIM signing may need the additional space to create valid DKIM signatures. // // The [Client] and [ClientSession] does not handle this option. It is the responsibility of the MTA to check if the milter // asked for this and obey this request. In the simplest case just never swallow the space. // // SMFIP_HDR_LEADSPC [v6] OptHeaderLeadingSpace OptProtocol = 1 << 20 )
The options that the milter can send to the MTA during negotiation to tailor the communication.
const ( // OptNoReplies combines all protocol flags that define that your milter does not send a reply // to the MTA. Use this if your [Milter] only decides at the [Milter.EndOfMessage] handler if the // email is acceptable or needs to be rejected. OptNoReplies OptProtocol = OptNoHeaderReply | OptNoConnReply | OptNoHeloReply | OptNoMailReply | OptNoRcptReply | OptNoDataReply | OptNoUnknownReply | OptNoEOHReply | OptNoBodyReply )
type Option ¶
type Option func(*options)
Option can be used to configure Client and Server.
func WithAction ¶
WithAction adds action to the actions your MTA supports or your Milter needs. You need to specify this since this library cannot guess what your MTA can handle or your milter needs. 0 is a valid value when your MTA does not support any message modification (only rejection) or your milter does not need any message modifications.
func WithActions ¶
WithActions sets the actions your MTA supports or your Milter needs. You need to specify this since this library cannot guess what your MTA can handle or your milter needs. 0 is a valid value when your MTA does not support any message modification (only rejection) or your milter does not need any message modifications.
func WithDialer ¶
WithDialer sets the net.Dialer this Client will use. You can use this to e.g. set the connection timeout of the client. The default is to use a net.Dialer with a connection timeout of 10 seconds.
func WithDynamicMilter ¶
func WithDynamicMilter(newMilter NewMilterFunc) Option
WithDynamicMilter sets the Milter backend this Server uses. This Option sets the milter with the negotiated version, action and protocol. You can use this to dynamically configure the Milter backend.
func WithMacroRequest ¶
func WithMacroRequest(stage MacroStage, macros []MacroName) Option
WithMacroRequest defines the macros that your Client intends to send at stage, or it instructs the Server to ask for these macros at this stage.
For Client: The milter can request other macros at protocol negotiation but if it does not do this (most do not) it will receive these macros at these stages.
For Server: MTAs like sendmail and Postfix honor your macro requests and only send you the macros you requested (even if other macros were configured in their configuration). If it is possible your milter should gracefully handle the case that the MTA does not honor your macro requests. This function automatically sets the action OptSetMacros
func WithMaximumVersion ¶
WithMaximumVersion sets the maximum milter version your MTA or milter filter accepts. The default is to use the maximum supported version.
func WithMilter ¶
func WithNegotiationCallback ¶
func WithNegotiationCallback(negotiationCallback NegotiationCallbackFunc) Option
WithNegotiationCallback is an expert Option with which you can overwrite the negotiation process.
You should not need to use this. You might easily break things. You are responsible to adhere to the milter protocol negotiation rules (they unfortunately only exist in sendmail & libmilter source code).
func WithOfferedMaxData ¶
WithOfferedMaxData sets the DataSize that your MTA wants to offer to milters. The milter needs to accept this offer in protocol negotiation for it to become effective. This is just an indication to the milter that it can send bigger packages. This library does not care what value was negotiated and always accept packages of up to 512 MB.
func WithProtocol ¶
func WithProtocol(protocol OptProtocol) Option
WithProtocol adds protocol to the protocol features your MTA should be able to handle or your Milter needs. For MTAs you can normally skip setting this option since we then just default to all protocol feature that this library supports. Milter should specify this option to instruct the MTA to not send any events that your Milter does not need or to not expect any response from events that you are not using to accept or reject an SMTP transaction.
func WithProtocols ¶
func WithProtocols(protocol OptProtocol) Option
WithProtocols sets the protocol features your MTA should be able to handle or your Milter needs. For MTAs you can normally skip setting this option since we then just default to all protocol feature that this library supports. Milter should specify this option to instruct the MTA to not send any events that your Milter does not need or to not expect any response from events that you are not using to accept or reject an SMTP transaction.
func WithReadTimeout ¶
WithReadTimeout sets the read-timeout for all read operations of this Client or Server. The default is a read-timeout of 10 seconds.
func WithUsedMaxData ¶
WithUsedMaxData sets the DataSize that your MTA or milter uses to send packages to the other party. The default value is DataSize64K for maximum compatibility. If you set this to 0 the Client will use the value of WithOfferedMaxData and the Server will use the dataSize that it negotiated with the MTA.
Setting the maximum used data size to something different might trigger the other party to an error. MTAs like Postfix/sendmail and newer libmilter versions can handle bigger values without negotiation. E.g. Postfix will accept packets of up to 2 GB. This library has a hard maximum packet size of 512 MB.
func WithWriteTimeout ¶
WithWriteTimeout sets the write-timeout for all read operations of this Client or Server. The default is a write-timeout of 10 seconds.
func WithoutAction ¶
WithoutAction removes action from the list of actions this MTA supports/Milter needs.
func WithoutDefaultMacros ¶
func WithoutDefaultMacros() Option
WithoutDefaultMacros deletes all macro stage definitions that were made before this Option. Use it in NewClient do not use the default. Since NewServer does not have a default, it is a no-op in NewServer.
func WithoutProtocol ¶
func WithoutProtocol(protocol OptProtocol) Option
WithoutProtocol removes protocol from the list of protocol features this MTA supports/Milter requests.
type ProtoFamily ¶
type ProtoFamily byte
const ( FamilyUnknown ProtoFamily = 'U' // SMFIA_UNKNOWN FamilyUnix ProtoFamily = 'L' // SMFIA_UNIX FamilyInet ProtoFamily = '4' // SMFIA_INET FamilyInet6 ProtoFamily = '6' // SMFIA_INET6 )
type Response ¶
type Response struct {
// contains filtered or unexported fields
}
Response represents a response structure returned by callback handlers to indicate how the milter server should proceed
func RejectWithCodeAndReason ¶
RejectWithCodeAndReason stops processing and tells client the error code and reason to sent
smtpCode must be between 400 and 599, otherwise this method will return an error.
The reason can contain new-lines. Line ending canonicalization is done automatically. This function returns an error when the resulting SMTP text has a length of more than DataSize64K - 1
func (*Response) Continue ¶
Continue returns false if the MTA should stop sending events for this transaction, true otherwise. A RespDiscard Response will return false because the MTA should end sending events for the current SMTP transaction to this milter.
func (*Response) String ¶ added in v0.8.3
String returns a string representation of this response. Can be used for logging purposes. This method will always return a logfmt compatible string. We try to not alter the output of this method arbitrarily – but we do not make any guaranties.
It sometimes internally examines the bytes that will be sent over the wire with the parsing code of the client part of this library. This is not the most performant implementation, so you might opt to not use this method when your code needs to be performant.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is a milter server.
Example ¶
package main import ( "log" "net" "sync" "github.com/d--j/go-milter" ) type ExampleBackend struct { milter.NoOpMilter } func (b *ExampleBackend) RcptTo(rcptTo string, esmtpArgs string, m *milter.Modifier) (*milter.Response, error) { // reject the mail when it goes to other-spammer@example.com and is a local delivery if rcptTo == "other-spammer@example.com" && m.Macros.Get(milter.MacroRcptMailer) == "local" { return milter.RejectWithCodeAndReason(550, "We do not like you\r\nvery much, please go away") } return milter.RespContinue, nil } func main() { // create socket to listen on socket, err := net.Listen("tcp4", "127.0.0.1:6785") if err != nil { log.Fatal(err) } defer socket.Close() // define the backend, required actions, protocol options and macros we want server := milter.NewServer( milter.WithMilter(func() milter.Milter { return &ExampleBackend{} }), milter.WithProtocol(milter.OptNoConnect|milter.OptNoHelo|milter.OptNoMailFrom|milter.OptNoBody|milter.OptNoHeaders|milter.OptNoEOH|milter.OptNoUnknown|milter.OptNoData), milter.WithAction(milter.OptChangeFrom|milter.OptAddRcpt|milter.OptRemoveRcpt), milter.WithMacroRequest(milter.StageRcpt, []milter.MacroName{milter.MacroRcptMailer}), ) defer server.Close() // start the milter var wgDone sync.WaitGroup wgDone.Add(1) go func(socket net.Listener) { if err := server.Serve(socket); err != nil { log.Fatal(err) } wgDone.Done() }(socket) log.Printf("Started milter on %s:%s", socket.Addr().Network(), socket.Addr().String()) // quit when milter quits wgDone.Wait() }
Output:
func NewServer ¶
NewServer creates a new milter server.
You need to at least specify the used Milter with the option WithMilter. You should also specify the actions your Milter will do. Otherwise, you cannot do any message modifications. For performance reasons you should disable protocol stages that you do not need with WithProtocol.
This function will panic when you provide invalid options.
Source Files
¶
Directories
¶
Path | Synopsis |
---|---|
cmd
|
|
log-milter
Command log-milter is a no-op milter that logs all milter communication
|
Command log-milter is a no-op milter that logs all milter communication |
milter-check
Command milter-check can be used to send test data to milters.
|
Command milter-check can be used to send test data to milters. |
integration
module
|
|
internal
|
|
body
Package body implements a write-once read-multiple io.ReadSeekCloser that is backed by a temporary file when too much data gets written into it.
|
Package body implements a write-once read-multiple io.ReadSeekCloser that is backed by a temporary file when too much data gets written into it. |
header
Package header has structs and functions handling with mail header and their modifications
|
Package header has structs and functions handling with mail header and their modifications |
rcptto
Package rcptto includes utility functions for handling lists of recipients
|
Package rcptto includes utility functions for handling lists of recipients |
wire
Package wire includes constants and functions for the raw libmilter protocol
|
Package wire includes constants and functions for the raw libmilter protocol |
Package mailfilter allows you to write milter filters without boilerplate code
|
Package mailfilter allows you to write milter filters without boilerplate code |
addr
Package addr includes IDNA aware address structs
|
Package addr includes IDNA aware address structs |
header
Package header includes interfaces to access and modify email headers
|
Package header includes interfaces to access and modify email headers |
testtrx
Package testtrx can be used to test mailfilter based filter functions
|
Package testtrx can be used to test mailfilter based filter functions |
Package milterutil includes utility functions and types that might be useful for writing milters or MTAs.
|
Package milterutil includes utility functions and types that might be useful for writing milters or MTAs. |