milter

package module
v0.0.0-...-6006968 Latest Latest
Warning

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

Go to latest
Published: Apr 5, 2022 License: BSD-2-Clause Imports: 13 Imported by: 0

README

GoDoc

milter

This is a heavily fork of github.com/phalaaxx/milter, added the following pull requests:

and also _test cases using my milterclient library.

Added the functions in the interface:
  • Init() is called on begin of a new Mail, before Connect() and before MailFrom() and also on RSET (abort command), so you can cleanup and init.
  • Init now has a sessionID and a mailID
  • Disconnect() which is called when the client disconnects (if you have a concurrent session counter you can decrease the counter there, this was not possible before)
  • Logger interface to inject a custom logger
  • Errors exported
  • Changed: EnvFrom and RcptTo addresses are now always converted to lowercase
  • Added all Protocol Options and Actions from libmilter (session.go)
  • Added SymListFactory to Set the list of macros that the milter wants to receive from the MTA for a protocol stage
  • Refactored to common consts names for Milter Commands
Breaking Changes for v2:
  • Breaking API Changes.
  • Changed to functional options
  • New Interface names
  • Added RequestMacros (SetSymlist) to MilterFactory
  • See example_test.go and server_test.go howto use this Library.
  • Added DefaultSession as basic implementation (can be used or not.)

Documentation

Overview

A Go library for milter support

Example
package main

import (
	"io/ioutil"
	"log"

	"github.com/mschneider82/milter"
)

// A Session embetted the SessionHandler Interface
type Session struct {
	milter.DefaultSession
}

// Body in this case we just want to show some chars of the mail
// All other Interactions are done by DefaultSession implementation
func (s *Session) Body(m *milter.Modifier) (milter.Response, error) {
	b, _ := ioutil.ReadAll(s.Message)
	log.Printf("Mail's first 100 chars: %s", string(b[0:100]))
	return milter.RespAccept, nil
}

func main() {
	panichandler := func(e error) {
		log.Printf("Panic happend: %s\n", e.Error())
	}

	setsymlist := make(milter.RequestMacros)
	setsymlist[milter.SMFIM_CONNECT] = []milter.Macro{milter.MACRO_DAEMON_NAME, milter.Macro("{client_addr}")}

	milterfactory := func() (milter.SessionHandler, milter.OptAction, milter.OptProtocol, milter.RequestMacros) {
		return &Session{},
			milter.OptAllActions, // BitMask for wanted Actions
			0, // BitMask for unwanted SMTP Parts; 0 = nothing to opt out
			setsymlist // optional: can be nil
	}

	m := milter.New(milterfactory,
		milter.WithTCPListener("127.0.0.1:12349"),
		milter.WithLogger(milter.StdOutLogger),
		milter.WithPanicHandler(panichandler),
	)
	err := m.Run()
	if err != nil {
		log.Fatalf("Error: %s", err.Error())
	}

	defer m.Close()
}
Output:

Index

Examples

Constants

View Source
const (
	SMFIC_ABORT   = 'A' // Abort current filter checks
	SMFIC_BODY    = 'B' // Body chunk
	SMFIC_CONNECT = 'C' // SMTP connection information
	SMFIC_MACRO   = 'D' // Define macros
	SMFIC_BODYEOB = 'E' // End of body marker
	SMFIC_HELO    = 'H' // HELO/EHLO name
	SMFIC_QUIT_NC = 'K' // QUIT but new connection follows
	SMFIC_HEADER  = 'L' // Mail header
	SMFIC_MAIL    = 'M' // MAIL FROM: information
	SMFIC_EOH     = 'N' // End of headers marker
	SMFIC_OPTNEG  = 'O' // Option negotiation
	SMFIC_QUIT    = 'Q' // Quit milter communication
	SMFIC_RCPT    = 'R' // RCPT TO: information
	SMFIC_DATA    = 'T' // DATA
	SMFIC_UNKNOWN = 'U' // Any unknown command

	SMFIR_ADDRCPT     = '+' // Add recipient (modification action)
	SMFIR_DELRCPT     = '-' // Remove recipient (modification action)
	SMFIR_ADDRCPT_PAR = '2' // Add recipient (incl. ESMTP args)
	SMFIR_SHUTDOWN    = '4' // 421: shutdown (internal to MTA)
	SMFIR_ACCEPT      = 'a' // Accept message completely (accept/reject action)
	SMFIR_REPLBODY    = 'b' // Replace body (modification action)
	SMFIR_CONTINUE    = 'c' // Accept and keep processing (accept/reject action)
	SMFIR_DISCARD     = 'd' // Set discard flag for entire message (accept/reject action)
	SMFIR_CHGFROM     = 'e' // Change envelope sender (from)
	SMFIR_CONN_FAIL   = 'f' // Cause a connection failure
	SMFIR_ADDHEADER   = 'h' // Add header (modification action)
	SMFIR_INSHEADER   = 'i' // Insert header
	SMFIR_SETSYMLIST  = 'l' // Set list of symbols (macros)
	SMFIR_CHGHEADER   = 'm' // Change header (modification action)
	SMFIR_PROGRESS    = 'p' // Progress (asynchronous action)
	SMFIR_QUARANTINE  = 'q' // Quarantine message (modification action)
	SMFIR_REJECT      = 'r' // Reject command/recipient with a 5xx (accept/reject action)
	SMFIR_SKIP        = 's' // Skip
	SMFIR_TEMPFAIL    = 't' // Reject command/recipient with a 4xx (accept/reject action)
	SMFIR_REPLYCODE   = 'y' // Send specific Nxx reply message (accept/reject action)

	SMFIA_INET    = '4'
	SMFIA_INET6   = '6'
	SMFIA_UNIX    = 'L'
	SMFIA_UNKNOWN = 'U'
)
View Source
const (
	SMFIS_KEEP    = uint32(20)
	SMFIS_ABORT   = uint32(21)
	SMFIS_OPTIONS = uint32(22)
	SMFIS_NOREPLY = uint32(7)
)

Define standard responses with no data

Variables

View Source
var (
	ErrCloseSession = errors.New("Stop current milter processing")
	ErrMacroNoData  = errors.New("Macro definition with no data")
	ErrNoListenAddr = errors.New("no listen addr specified")
)

pre-defined errors

View Source
var NopLogger = CustomLogger(nopLogger{})

NopLogger can be used to discard all logs caused by milter library

View Source
var StdOutLogger = CustomLogger(stdoutLogger{})

StdOutLogger is the default logger used if no Logger was supplied

Functions

func Close

func Close()

Close server listener and wait worked process

Types

type CustomLogger

type CustomLogger interface {
	Printf(format string, v ...interface{})
}

CustomLogger is a interface to inject a custom logger

type CustomResponse

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

CustomResponse is a response instance used by callback handlers to indicate how the milter should continue processing of current message

func NewResponse

func NewResponse(code byte, data []byte) *CustomResponse

NewResponse generates a new CustomResponse suitable for WritePacket

func NewResponseStr

func NewResponseStr(code byte, data string) *CustomResponse

NewResponseStr generates a new CustomResponse with string payload code should be SMFIR_REPLYCODE == 'y' data can be "550 5.2.0 mailbox unavailable."

func (*CustomResponse) Continue

func (c *CustomResponse) Continue() bool

Continue returns false if milter chain should be stopped, true otherwise

func (*CustomResponse) Response

func (c *CustomResponse) Response() *Message

Response returns message instance with data

type DefaultSession

type DefaultSession struct {
	SID            string // Session ID
	MID            string // Mail ID
	From           string
	ClientName     string
	HeloName       string
	ClientIP       net.IP
	Rcpts          []string
	MessageHeaders textproto.MIMEHeader
	Message        *bytes.Buffer
}

A DefaultSession can be used as a basic implementation for SessionHandler Interface It has already a From and Rcpts[] field, mostly used to embett in your own Session struct.

func (*DefaultSession) Body

func (e *DefaultSession) Body(m *Modifier) (Response, error)

Body is called when email message body has been sent

func (*DefaultSession) BodyChunk

func (e *DefaultSession) BodyChunk(chunk []byte, m *Modifier) (Response, error)

accept body chunk

func (*DefaultSession) Connect

func (e *DefaultSession) Connect(name, family string, port uint16, ip net.IP, m *Modifier) (Response, error)

func (*DefaultSession) Disconnect

func (e *DefaultSession) Disconnect()

func (*DefaultSession) Header

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

handle headers one by one

func (*DefaultSession) Headers

func (e *DefaultSession) Headers(headers textproto.MIMEHeader, m *Modifier) (Response, error)

at end of headers initialize message buffer and add headers to it

func (*DefaultSession) Helo

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

func (*DefaultSession) MailFrom

func (e *DefaultSession) MailFrom(from string, m *Modifier) (Response, error)

func (*DefaultSession) RcptTo

func (e *DefaultSession) RcptTo(rcptTo string, m *Modifier) (Response, error)

type ListenerOption

type ListenerOption interface {
	// contains filtered or unexported methods
}

An ListenerOption configures a Server using the functional options paradigm popularized by Rob Pike.

func WithListener

func WithListener(listener net.Listener) ListenerOption

WithListener adds an Listener

func WithTCPListener

func WithTCPListener(address string) ListenerOption

WithTCPListener e.g. "127.0.0.1:12349"

func WithUnixSocket

func WithUnixSocket(file string) ListenerOption

WithUnixSocket e.g. "/var/spool/postfix/var/run/milter/milter.sock" make sure that the file does not exist!

type Macro

type Macro string

Macro http://www.postfix.org/MILTER_README.html#macros

const (
	MACRO_QUEUEID                 Macro = "i"                    // DATA, EOH, EOM	- Queue ID, also Postfix queue file name
	MACRO_MYHOSTNAME              Macro = "j"                    // Always - Value of myhostname
	MACRO_VALIDCLIENTNAME         Macro = "_"                    // Always	- The validated client name and address
	MACRO_AUTH_SASL_LOGINNAME     Macro = "{auth_authen}"        // MAIL, DATA, EOH, EOM - SASL login name
	MACRO_AUTH_SASL_SENDER        Macro = "{auth_author}"        // MAIL, DATA, EOH, EOM - SASL sender
	MACRO_AUTH_SASL_LOGINMETHOD   Macro = "{auth_type}"          // MAIL, DATA, EOH, EOM - SASL login method
	MACRO_REMOTECLIENTIP          Macro = "{client_addr}"        // Always - Remote client IP address
	MACRO_CLIENT_CONNECTIONS      Macro = "{client_connections}" // CONNECT - Connection concurrency for this client (zero if the client is excluded from all smtpd_client_* limits).
	MACRO_CLIENT_NAME             Macro = "{client_name}"        // Always	- Remote client hostname address → name lookup or name → address verification fails: "unknown"
	MACRO_CLIENT_TCPPORT          Macro = "{client_port}"        // Always (Postfix ≥2.5) - Remote client TCP port
	MACRO_CLIENT_PTR              Macro = "{client_ptr}"         // CONNECT, HELO, MAIL, DATA - Client name from address → name lookup address → name lookup fails: "unknown"
	MACRO_CLIENT_TLS_CERT_ISSUER  Macro = "{cert_issuer}"        // HELO, MAIL, DATA, EOH, EOM	- TLS client certificate issuer
	MACRO_CLIENT_TLS_CERT_SUBJECT Macro = "{cert_subject}"       // HELO, MAIL, DATA, EOH, EOM	- TLS client certificate subject
	MACRO_CLIENT_TLS_CIPHER_BITS  Macro = "{cipher_bits}"        // HELO, MAIL, DATA, EOH, EOM	- TLS session key size
	MACRO_CLIENT_TLS_CIPHER       Macro = "{cipher}"             // HELO, MAIL, DATA, EOH, EOM	- TLS cipher
	MACRO_DAEMON_ADDR             Macro = "{daemon_addr}"        // Always (Postfix ≥3.2) - Local server IP address
	MACRO_DAEMON_NAME             Macro = "{daemon_name}"        // Always	- value of milter_macro_daemon_name
	MACRO_DAEMON_PORT             Macro = "{daemon_port}"        // Always (Postfix ≥3.2) -Local server TCP port
	MACRO_MAIL_ADDR               Macro = "{mail_addr}"          // MAIL - Sender address
	MACRO_MAIL_HOST               Macro = "{mail_host}"          // MAIL (Postfix ≥ 2.6, only with smtpd_milters) - Sender next-hop destination
	MACRO_MAIL_MAILER             Macro = "{mail_mailer}"        // MAIL (Postfix ≥ 2.6, only with smtpd_milters) - Sender mail delivery transport
	MACRO_RCPT_ADDR               Macro = "{rcpt_addr}"          // RCPT - Recipient address with rejected recipient: descriptive text
	MACRO_RCPT_HOST               Macro = "{rcpt_host}"          // RCPT (Postfix ≥ 2.6, only with smtpd_milters) - Recipient next-hop destination with rejected recipient: enhanced status code
	MACRO_RCPT_MAILER             Macro = "{rcpt_mailer}"        // RCPT (Postfix ≥ 2.6, only with smtpd_milters) - Recipient mail delivery transport With Protocol Stage: rejected recipient: "error"
	MACRO_TLS_VERSION             Macro = "{tls_version}"        // HELO, MAIL, DATA, EOH, EOM	- TLS protocol version
	MACRO_V                       Macro = "v"                    // Always - value of milter_macro_v (default: $mail_name $mail_version)
)

type Message

type Message struct {
	Code byte
	Data []byte
}

Message represents a command sent from milter client

type MilterFactory

type MilterFactory func() (SessionHandler, OptAction, OptProtocol, RequestMacros)

MilterFactory initializes milter options multiple options can be set using a bitmask

type Modifier

type Modifier struct {
	Macros  map[string]string
	Headers textproto.MIMEHeader
	// contains filtered or unexported fields
}

Modifier provides access to Macros, Headers and Body data to callback handlers. It also defines a number of functions that can be used by callback handlers to modify processing of the email message

func (*Modifier) AddHeader

func (m *Modifier) AddHeader(name, value string) error

AddHeader appends a new email message header the message

func (*Modifier) AddRecipient

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

AddRecipient appends a new envelope recipient for current message

func (*Modifier) ChangeFrom

func (m *Modifier) ChangeFrom(value string) error

ChangeFrom replaces the FROM envelope header with a new one

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

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 pecified position

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(body []byte) error

ReplaceBody substitutes message body with provided body

type OptAction

type OptAction uint32

OptAction sets which actions the milter wants to perform. Multiple options can be set using a bitmask.

const (
	// set which actions the milter wants to perform
	OptNone           OptAction = 0x00  /* SMFIF_NONE no flags */
	OptAddHeader      OptAction = 0x01  /* SMFIF_ADDHDRS filter may add headers */
	OptChangeBody     OptAction = 0x02  /* SMFIF_CHGBODY filter may replace body */
	OptAddRcpt        OptAction = 0x04  /* SMFIF_ADDRCPT filter may add recipients */
	OptRemoveRcpt     OptAction = 0x08  /* SMFIF_DELRCPT filter may delete recipients */
	OptChangeHeader   OptAction = 0x10  /* SMFIF_CHGHDRS filter may change/delete headers */
	OptQuarantine     OptAction = 0x20  /* SMFIF_QUARANTINE filter may quarantine envelope */
	OptChangeFrom     OptAction = 0x40  /* SMFIF_CHGFROM filter may change "from" (envelope sender) */
	OptAddRcptPartial OptAction = 0x80  /* SMFIF_ADDRCPT_PAR filter may add recipients, including ESMTP parameter to the envelope.*/
	OptSetSymList     OptAction = 0x100 /* SMFIF_SETSYMLIST filter can send set of symbols (macros) that it wants */
	// OptAllActions SMFI_CURR_ACTS Set of all actions in the current milter version */
	OptAllActions OptAction = OptAddHeader | OptChangeBody | OptAddRcpt | OptRemoveRcpt | OptChangeHeader | OptQuarantine | OptChangeFrom | OptAddRcptPartial | OptSetSymList
)

type OptProtocol

type OptProtocol uint32

OptProtocol masks out unwanted parts of the SMTP transaction. Multiple options can be set using a bitmask.

const (
	// mask out unwanted parts of the SMTP transaction
	OptNoConnect    OptProtocol = 0x01       /* SMFIP_NOCONNECT MTA should not send connect info */
	OptNoHelo       OptProtocol = 0x02       /* SMFIP_NOHELO MTA should not send HELO info */
	OptNoMailFrom   OptProtocol = 0x04       /* SMFIP_NOMAIL MTA should not send MAIL info */
	OptNoRcptTo     OptProtocol = 0x08       /* SMFIP_NORCPT MTA should not send RCPT info */
	OptNoBody       OptProtocol = 0x10       /* SMFIP_NOBODY MTA should not send body (chunk) */
	OptNoHeaders    OptProtocol = 0x20       /* SMFIP_NOHDRS MTA should not send headers */
	OptNoEOH        OptProtocol = 0x40       /* SMFIP_NOEOH MTA should not send EOH */
	OptNrHdr        OptProtocol = 0x80       /* SMFIP_NR_HDR SMFIP_NOHREPL No reply for headers */
	OptNoUnknown    OptProtocol = 0x100      /* SMFIP_NOUNKNOWN MTA should not send unknown commands */
	OptNoData       OptProtocol = 0x200      /* SMFIP_NODATA MTA should not send DATA */
	OptSkip         OptProtocol = 0x400      /* SMFIP_SKIP MTA understands SMFIS_SKIP */
	OptRcptRej      OptProtocol = 0x800      /* SMFIP_RCPT_REJ MTA should also send rejected RCPTs */
	OptNrConn       OptProtocol = 0x1000     /* SMFIP_NR_CONN No reply for connect */
	OptNrHelo       OptProtocol = 0x2000     /* SMFIP_NR_HELO No reply for HELO */
	OptNrMailFrom   OptProtocol = 0x4000     /* SMFIP_NR_MAIL No reply for MAIL */
	OptNrRcptTo     OptProtocol = 0x8000     /* SMFIP_NR_RCPT No reply for RCPT */
	OptNrData       OptProtocol = 0x10000    /* SMFIP_NR_DATA No reply for DATA */
	OptNrUnknown    OptProtocol = 0x20000    /* SMFIP_NR_UNKN No reply for UNKNOWN */
	OptNrEOH        OptProtocol = 0x40000    /* SMFIP_NR_EOH No reply for eoh */
	OptNrBody       OptProtocol = 0x80000    /* SMFIP_NR_BODY No reply for body chunk */
	OptHdrLeadSpace OptProtocol = 0x100000   /* SMFIP_HDR_LEADSPC header value leading space */
	OptMDS256K      OptProtocol = 0x10000000 /* SMFIP_MDS_256K MILTER_MAX_DATA_SIZE=256K */
	OptMDS1M        OptProtocol = 0x20000000 /* SMFIP_MDS_1M MILTER_MAX_DATA_SIZE=1M */
)

type Option

type Option interface {
	// contains filtered or unexported methods
}

An Option configures a Server using the functional options paradigm popularized by Rob Pike.

func WithLogger

func WithLogger(l CustomLogger) Option

WithLogger adds an Logger

func WithPanicHandler

func WithPanicHandler(handler func(error)) Option

WithPanicHandler Adds the error panic handler Multiple panic handlers are supported

type RequestMacros

type RequestMacros map[Stage][]Macro

RequestMacros - Also known as SetSymList: the list of macros that the milter wants to receive from the MTA for a protocol Stage (stages has the prefix SMFIM_). if nil, then there are not Macros requested and the default macros from MTA are used.

type Response

type Response interface {
	Response() *Message
	Continue() bool
}

Response represents a response structure returned by callback handlers to indicate how the milter server should proceed

type Server

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

Server Milter for handling and processing incoming connections support panic handling via ErrHandler couple of func(error) could be provided for handling error

func New

func New(milterfactory MilterFactory, lopt ListenerOption, opts ...Option) *Server

New generates a new Server

func (*Server) Close

func (s *Server) Close() error

Close for graceful shutdown Stop accepting new connections And wait until processing connections ends

func (*Server) Run

func (s *Server) Run() error

Run starts milter server via provided listener

type SessionHandler

type SessionHandler interface {
	// Init is called on begin of a new Mail, before Connect() and before MailFrom()
	// Can be used to Reset session state
	// On MailFrom mailID is available
	Init(sessionID, mailID string)

	// Connect is called to provide SMTP connection data for incoming message
	//   supress with NoConnect
	Connect(host string, family string, port uint16, addr net.IP, m *Modifier) (Response, error)

	// Helo is called to process any HELO/EHLO related filters
	//   supress with NoHelo
	Helo(name string, m *Modifier) (Response, error)

	// MailFrom is called to process filters on envelope FROM address
	//   supress with NoMailForm
	MailFrom(from string, m *Modifier) (Response, error)

	// RcptTo is called to process filters on envelope TO address
	//   supress with NoRcptTo
	RcptTo(rcptTo string, m *Modifier) (Response, error)

	// Header is called once for each header in incoming message
	//   supress with NoHeaders
	Header(name string, value string, m *Modifier) (Response, error)

	// Headers is called when all message headers have been processed
	//   supress with NoHeaders
	Headers(h textproto.MIMEHeader, m *Modifier) (Response, error)

	// BodyChunk is called to process next message body chunk data (up to 64KB in size)
	//   supress with NoBody
	BodyChunk(chunk []byte, m *Modifier) (Response, error)

	// Body is called at the end of each message
	//   all changes to message's content & attributes must be done here
	Body(m *Modifier) (Response, error)

	// Disconnect is called at the end of the message Handling loop
	Disconnect()
}

SessionHandler is an interface for milter callback handlers

type SimpleResponse

type SimpleResponse byte

SimpleResponse type to define list of pre-defined responses

func (SimpleResponse) Continue

func (r SimpleResponse) Continue() bool

Continue to process milter messages only if current code is Continue

func (SimpleResponse) Response

func (r SimpleResponse) Response() *Message

Response returns a Message object reference

type Stage

type Stage uint32

Stage is for SetSymList, to tell the MTA what Macros in what Stage we need

const (
	SMFIM_CONNECT Stage = iota /* 0 connect */
	SMFIM_HELO                 /* 1 HELO/EHLO */
	SMFIM_ENVFROM              /* 2 MAIL From */
	SMFIM_ENVRCPT              /* 3 RCPT To */
	SMFIM_DATA                 /* 4 DATA */
	SMFIM_EOM                  /* 5 = end of message (final dot) */
	SMFIM_EOH                  /* 6 = end of header */
)

Jump to

Keyboard shortcuts

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