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
- 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(m Modifier)
- 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) NewConnection(m Modifier) 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 ErrVersionTooLow = errors.New("milter: action not allowed in this milter protocol version")
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 }
Action represents the action that the milter wants to take on the current message. The client can call StopProcessing on it to check if the milter wants to abort the connection/message.
func (Action) StopProcessing ¶
StopProcessing returns true when the milter wants to immediately stop this SMTP connection or reject this recipient. (a.Type is one of ActionReject, ActionTempFail or ActionRejectWithCode). 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() var rcptTos []string 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()) } } currentRcpt := "" handleMilterRcptToResponse := 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() { rcptTos = append(rcptTos, currentRcpt) } 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") currentRcpt = "<other-spammer2@example.com>" handleMilterRcptToResponse(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") currentRcpt = "<other-spammer2@example.com>" handleMilterRcptToResponse(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 afterward 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/next hop for the current RCPT TO address. E.g. smtp, local. 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 )
type Milter ¶
type Milter interface { // NewConnection gets called when a new SMTP connection was opened. // You can use this method to initialize your milter. // Normally an MTA will use one milter connection only for one SMTP connection so this method will only be called // one time. The MTA can also re-use a milter connection for another SMTP connection (wire.CodeQuitNewConn). // // NewConnection might be called without ever actually calling the other // methods of the [Milter] interface (Connect, Helo). Then Cleanup gets called immediately after NewConnection. // // You can use m to examine the milter protocol options/actions that were negotiated with the MTA. // If you return an error, the milter connection will break. If you want to send a response to the SMTP client // from this command (including calling Modifier.Progress), you need to defer the response to // the next command (Connect, Helo, MailFrom) that gets called. NewConnection(m Modifier) error // Connect is called to provide SMTP connection data for incoming message. // Suppress with OptNoConnect. // // m is read-only+progress. You can call Modifier.Progress on it but no // other modifier actions (e.g. Modifier.AddHeader). // // 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]. // // name is the hostname that the STMP client provided. // // m is read-only+progress. You can call Modifier.Progress on it but no // other modifier actions (e.g. Modifier.AddHeader). // // You can get multiple Helo calls in one connection. // This normally means that the SMTP client issued a STARTTLS command. // // 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]. // // from is the from address/envelope sender of the message (without <>). // esmtpArgs are the ESMTP arguments that were passed in the MAIL FROM command. // // m is read-only+progress. You can call Modifier.Progress on it but no // other modifier actions (e.g. Modifier.AddHeader). // // 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 addresses. Suppress with [OptNoRcptTo]. // It may be called multiple times. // // rcptTo is the current recipient address (without <>). // esmtpArgs are the ESMTP arguments that were passed in the RCPT TO command. // // m is read-only+progress. You can call Modifier.Progress on it but no // other modifier actions (e.g. Modifier.AddHeader). // // The Response you return from this method determines the action the MTA will take for this recipient only. // - RespAccept: accept this recipient // - RespReject: reject this recipient (5xx error code, permanent error) // - RespTempFail: reject this recipient (4xx error code, temporary error) // - RejectWithCodeAndReason: reject this recipient (4xx or 5xx error code) with a custom error message // - RespContinue: accept this recipient (the same as RespAccept) // - RespSkip: accept this recipient but do not send more RCPT TO events to the milter. // This response is only available in version 6 Milter protocol connections. // - RespDiscard: discards the whole message/transaction for all recipients. // This is the only Response that will affect the whole message, not only this recipient. // // 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]. // // m is read-only+progress. You can call Modifier.Progress on it but no // other modifier actions (e.g. Modifier.AddHeader). // // 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]. // // name is the header name (without ":"). // value is the header value (everything after ":", without the terminating "\r\n"). // Depending on OptHeaderLeadingSpace the MTA might have swallowed the first space after the colon. // // m is read-only+progress. You can call Modifier.Progress on it but no // other modifier actions (e.g. Modifier.AddHeader). // // If you return [RespSkip] the MTA will stop sending more Header events. // This response is only available in version 6 Milter protocol connections. // // 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]. // // m is read-only+progress. You can call Modifier.Progress on it but no // other modifier actions (e.g. Modifier.AddHeader). // // 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]. // // m is read-only+progress. You can call Modifier.Progress on it but no // other modifier actions (e.g. Modifier.AddHeader). // // If you return [RespSkip] the MTA will stop sending more BodyChunk events. // This response is only available in version 6 Milter protocol connections. // // 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. // // m is read-write. You can call modifiers (e.g. Modifier.AddHeader) and Modifier.Progress on it. // // 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 to the state prior to the [Milter.MailFrom] callback. Connection data should be // preserved. [Milter.Cleanup] is not called before or after Abort. // // It is very likely that the next callback will be [Milter.MailFrom] again – // the MTA will start over with a new message. // Some MTAs also call Abort (possibly multiple times) after EndOfMessage. // // m is read-only. You cannot call any modifiers on it (including Modifier.Progress). 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. // A Milter gets discarded when the network connection to the MTA was closed. // // m is read-only. You cannot call any modifiers on it (including Modifier.Progress). Cleanup(m Modifier) }
Milter is an interface for milter callback handlers. You need to implement this interface to create a milter. You embed the NoOpMilter struct in your own milter implementation to only implement the methods you need. One Milter will handle one MTA milter connection. Normally an MTA will create one milter connection for one SMTP connection. Your Milter implementation needs to be able to handle getting multiple messages in one connection. I.e. after you send a terminating response to the MTA (RespAccept, RespReject, RespDiscard, RespTempFail, RejectWithCodeAndReason) you need to be able to handle getting (a) a new MailFrom command from the MTA for the next message in the same SMTP connection, or (b) a call to NewConnection when the MTA re-uses the milter connection for a new SMTP connection.
type Modifier ¶
type Modifier interface { Macros // Version returns the negotiated milter protocol version. Version() uint32 // Protocol returns the negotiated milter protocol flags. Protocol() OptProtocol // Actions returns the negotiated milter actions flags. Actions() OptAction // MaxDataSize returns the maximum data size that the MTA will accept. // This is the value that was negotiated with the MTA. MaxDataSize() DataSize // MilterId returns an identifier of this Milter instance. // This is a unique, incrementing identifier in the realm of a single Server. MilterId() uint64 // 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. AddRecipient(r string, esmtpArgs string) error // DeleteRecipient removes an envelope recipient address from message DeleteRecipient(r string) error // 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. ReplaceBodyRawChunk(chunk []byte) error // 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 the milterutil package to wrap your reader. // // t := transform.Chain(&milterutil.CrLfCanonicalizationTransformer{}, &milterutil.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. ReplaceBody(r io.Reader) error // Quarantine a message by giving a reason to hold it. Only makes sense when you RespAccept the message. Quarantine(reason string) error // 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. // // The header name must be valid. It can only contain printable ASCII characters without SP and colon. // // value can include newlines. They will be canonicalized to LF. // If the value includes newlines, it should also have the continuation character (SP or HT) at the beginning of the lines. // The continuation character is not mandatory, but it is recommended to use it. // NUL characters get converted to SP. AddHeader(name, value string) error // ChangeHeader replaces the header at the specified position with a new one. // The index is per canonical header 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). // // The header name must be valid. It can only contain printable ASCII characters without SP and colon. // // value can include newlines. They will be canonicalized to LF. // If the value includes newlines, it should also have the continuation character (SP or HT) at the beginning of the lines. // The continuation character is not mandatory, but it is recommended to use it. // NUL characters get converted to SP. ChangeHeader(index int, name, value string) error // InsertHeader inserts the header at the specified position. // index is one-based. The index 0 means at the very beginning. // If the index is bigger than the number of headers, then the header will be added at the end. // Unlike ChangeHeader, index is not per canonical header name but the index in the list of all headers. // // The header name must be valid. It can only contain printable ASCII characters without SP and colon. // // value can include newlines. They will be canonicalized to LF. // If the value includes newlines, it should also have the continuation character (SP or HT) at the beginning of the lines. // The continuation character is not mandatory, but it is recommended to use it. // NUL characters get converted to SP. // // 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. InsertHeader(index int, name, value string) error // 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. ChangeFrom(value string, esmtpArgs string) error // Progress tells the client that there is progress in a long operation // and that the client should not time out the milter connection. // // This function is only available when the negotiated milter protocol version is >= 6. // // This function can be called in any callback handler (unlike all other functions of [Modifier]). // It will send a progress notification packet to the MTA. // When it returns an error besides ErrVersionTooLow, the connection to the MTA is broken. Progress() error }
Modifier provides access to Macros to the 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.
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" means "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. // To be consistent, you should delete headers in reverse order. 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 }
func (ModifyAction) String ¶ added in v0.10.0
func (ma ModifyAction) String() 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. You can embed this milter in your own Milter implementation, when you only want/need to implement some methods of the interface.
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(m Modifier)
func (NoOpMilter) EndOfMessage ¶
func (NoOpMilter) EndOfMessage(m Modifier) (*Response, error)
func (NoOpMilter) NewConnection ¶ added in v0.10.0
func (NoOpMilter) NewConnection(m Modifier) 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 )
func (OptProtocol) String ¶ added in v0.10.0
func (o OptProtocol) String() string
String returns a string representation of the OptProtocol. It is used for debugging purposes.
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. For Client the default is a read-timeout of 10 seconds. For Server the default is a read-timeout of 0 seconds (no timeout). This is because the MTA sends milter messages when the SMTP client sends data. To not artificially break the MTA/SMTP client connection, we do not set a read timeout.
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. See milterutil.FormatResponse for the rules on the reason string.
func (*Response) Continue ¶
Continue returns false if the MTA should stop sending events for this transaction, true otherwise. If the Response is for a RCPT TO event, this function will return true if the MTA should accept this recipient. 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 ( "context" "log" "net" "os" "os/signal" "sync" "syscall" "time" "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 recipient when it goes to other-spammer@example.com and is a local delivery if rcptTo == "other-spammer@example.com" && m.Get(milter.MacroRcptMailer) == "local" { return milter.RejectWithCodeAndReason(550, "5.7.1 We do not like you\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.Println(err) } wgDone.Done() }(socket) log.Printf("Started milter on %s:%s", socket.Addr().Network(), socket.Addr().String()) // wait for SIGINT or SIGTERM sig := make(chan os.Signal, 1) signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) go func() { <-sig log.Printf("Gracefully shutting down milter…") ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() server.Shutdown(ctx) }() // 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.
func (*Server) MilterCount ¶ added in v0.10.0
MilterCount returns the number of milter backends that this server created in total. A Milter instance gets created for each new connection from the MTA (after successful negotiation). Use this function for logging purposes.
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. |