milter

package module
v0.8.4 Latest Latest
Warning

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

Go to latest
Published: Dec 28, 2023 License: BSD-2-Clause Imports: 17 Imported by: 1

README

go-milter

GoDoc Build status Coverage Status

A Go library to write mail filters.

Features

  • With this library you can write both the client (MTA/SMTP-Server) and server (milter filter) in pure Go without sendmail's libmilter.
  • Easy wrapper of the milter protocol that abstracts away many milter protocol quirks and lets you write mail filters with little effort.
  • UTF-8 support
  • IDNA support
  • Client & Server support milter protocol version 6 with all features. E.g.:
    • all milter events including DATA, UNKNOWN, ABORT and QUIT NEW CONNECTION
    • milter can skip e.g. body chunks when it does not need all chunks
    • milter can send progress notifications when response can take some time
    • milter can automatically instruct the MTA which macros it needs.
  • Automatic integration tests that test the compatibility with Postfix and Sendmail.

Installation

go get -u github.com/d--j/go-milter

Usage

The following example is a milter filter that adds [⚠️EXTERNAL] to the subject of all messages of unauthenticated users.

See GoDoc for more documentation and an example for a milter client or a raw milter server.

package main

import (
  "context"
  "flag"
  "log"
  "strings"

  "github.com/d--j/go-milter/mailfilter"
)

func main() {
  // parse commandline arguments
  var protocol, address string
  flag.StringVar(&protocol, "proto", "tcp", "Protocol family (unix or tcp)")
  flag.StringVar(&address, "addr", "127.0.0.1:10003", "Bind to address or unix domain socket")
  flag.Parse()

  // create and start the mail filter
  mailFilter, err := mailfilter.New(protocol, address,
    func(_ context.Context, trx mailfilter.Trx) (mailfilter.Decision, error) {
      // Quarantine mail when it is addressed to our SPAM trap
      if trx.HasRcptTo("spam-trap@スパム.example.com") {
        return mailfilter.QuarantineResponse("train as spam"), nil
      }
      // Prefix subject with [⚠️EXTERNAL] when user is not logged in
      if trx.MailFrom().AuthenticatedUser() == "" {
        subject, _ := trx.Headers().Subject()
        if !strings.HasPrefix(subject, "[⚠️EXTERNAL] ") {
          subject = "[⚠️EXTERNAL] " + subject
          trx.Headers().SetSubject(subject)
        }
      }
      return mailfilter.Accept, nil
    },
    // optimization: call the decision function when all headers were sent to us
    mailfilter.WithDecisionAt(mailfilter.DecisionAtEndOfHeaders),
  )
  if err != nil {
    log.Fatal(err)
  }
  log.Printf("Started milter on %s:%s", mailFilter.Addr().Network(), mailFilter.Addr().String())

  // wait for the mail filter to end
  mailFilter.Wait()
}

License

BSD 2-Clause

Credits

Based on https://github.com/emersion/go-milter by Simon Ser which is based on https://github.com/phalaaxx/milter by Bozhin Zafirov. Max Mazurov made major contributions to this code as well.

Documentation

Overview

Package milter provides an interface to implement milter mail filters and MTAs that can talk to milter programs.

Index

Examples

Constants

View Source
const MaxClientProtocolVersion uint32 = 6

MaxClientProtocolVersion is the maximum Milter protocol version implemented by the client.

View Source
const MaxServerProtocolVersion uint32 = 6

MaxServerProtocolVersion is the maximum Milter protocol version implemented by the server.

Variables

View Source
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

View Source
var ErrModificationNotAllowed = errors.New("milter: modification not allowed via milter protocol negotiation")
View Source
var ErrServerClosed = errors.New("milter: server closed")

ErrServerClosed is returned by the Server's Server.Serve method after a call to Server.Close.

View Source
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

func AddAngle(str string) string

AddAngle adds <> to an address. If str already has <>, then str is returned unchanged.

func RemoveAngle added in v0.6.6

func RemoveAngle(str string) string

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

func (a Action) StopProcessing() bool

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

func NewClient(network, address string, opts ...Option) *Client

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.

func (*Client) String

func (c *Client) String() string

String returns the network and address that his Client is configured to connect to. 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

func (s *ClientSession) Unknown(cmd string, macros map[MacroName]string) (*Action, error)

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.

const (
	// DataSize64K is 64KB - 1 byte (command-byte). This is the default buffer size.
	DataSize64K DataSize = 1024*64 - 1
	// DataSize256K is 256KB - 1 byte (command-byte)
	DataSize256K DataSize = 1024*256 - 1
	// DataSize1M is 1MB - 1 byte (command-byte)
	DataSize1M DataSize = 1024*1024 - 1
)

type Dialer

type Dialer interface {
	Dial(network string, addr string) (net.Conn, error)
}

Dialer is the interface of the only method we use of a net.Dialer.

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

func (m *MacroBag) Copy() *MacroBag

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) Get

func (m *MacroBag) Get(name MacroName) string

func (*MacroBag) GetEx

func (m *MacroBag) GetEx(name MacroName) (value string, ok bool)

func (*MacroBag) Set

func (m *MacroBag) Set(name MacroName, value string)

func (*MacroBag) SetCurrentDate

func (m *MacroBag) SetCurrentDate(date time.Time)

func (*MacroBag) SetHeaderDate

func (m *MacroBag) SetHeaderDate(date time.Time)

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 Macros

type Macros interface {
	Get(name MacroName) string
	GetEx(name MacroName) (value string, ok bool)
}

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

func (m *Modifier) AddHeader(name, value 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.

func (*Modifier) AddRecipient

func (m *Modifier) AddRecipient(r string, esmtpArgs string) error

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

func (m *Modifier) ChangeFrom(value string, esmtpArgs 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.

func (*Modifier) ChangeHeader

func (m *Modifier) ChangeHeader(index int, name, value string) error

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

func (m *Modifier) DeleteRecipient(r string) error

DeleteRecipient removes an envelope recipient address from message

func (*Modifier) InsertHeader

func (m *Modifier) InsertHeader(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.

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) Progress

func (m *Modifier) Progress() error

Progress tells the client that there is progress in a long operation

func (*Modifier) Quarantine

func (m *Modifier) Quarantine(reason string) error

Quarantine a message by giving a reason to hold it

func (*Modifier) ReplaceBody

func (m *Modifier) ReplaceBody(r io.Reader) 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 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

func (m *Modifier) ReplaceBodyRawChunk(chunk []byte) 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.

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) Connect

func (NoOpMilter) Connect(host string, family string, port uint16, addr string, m *Modifier) (*Response, error)

func (NoOpMilter) Data

func (NoOpMilter) Data(m *Modifier) (*Response, error)

func (NoOpMilter) EndOfMessage

func (NoOpMilter) EndOfMessage(m *Modifier) (*Response, error)

func (NoOpMilter) Header

func (NoOpMilter) Header(name string, value string, m *Modifier) (*Response, error)

func (NoOpMilter) Headers

func (NoOpMilter) Headers(m *Modifier) (*Response, error)

func (NoOpMilter) Helo

func (NoOpMilter) Helo(name string, m *Modifier) (*Response, error)

func (NoOpMilter) MailFrom

func (NoOpMilter) MailFrom(from string, esmtpArgs string, m *Modifier) (*Response, error)

func (NoOpMilter) RcptTo

func (NoOpMilter) RcptTo(rcptTo string, esmtpArgs string, m *Modifier) (*Response, error)

func (NoOpMilter) Unknown

func (NoOpMilter) Unknown(cmd string, 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

func WithAction(action OptAction) Option

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

func WithActions(actions OptAction) Option

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

func WithDialer(dialer Dialer) Option

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.

This is a Server only Option.

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

func WithMaximumVersion(version uint32) Option

WithMaximumVersion sets the maximum milter version your MTA or milter filter accepts. The default is to use the maximum supported version.

func WithMilter

func WithMilter(newMilter func() Milter) Option

WithMilter sets the Milter backend this Server uses.

This is a Server only Option.

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).

This is a Server only Option.

func WithOfferedMaxData

func WithOfferedMaxData(offeredMaxData DataSize) Option

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.

This is a Client only Option.

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

func WithReadTimeout(timeout time.Duration) Option

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

func WithUsedMaxData(usedMaxData DataSize) Option

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

func WithWriteTimeout(timeout time.Duration) Option

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

func WithoutAction(action OptAction) Option

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

func RejectWithCodeAndReason(smtpCode uint16, reason string) (*Response, error)

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

func (r *Response) Continue() bool

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) Response

func (r *Response) Response() *wire.Message

Response returns message instance with data

func (*Response) String added in v0.8.3

func (r *Response) String() string

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

func NewServer(opts ...Option) *Server

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) Close

func (s *Server) Close() error

func (*Server) Serve

func (s *Server) Serve(ln net.Listener) error

Serve starts the server.

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.

Jump to

Keyboard shortcuts

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