smtp

package
v0.0.0-...-534e721 Latest Latest
Warning

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

Go to latest
Published: May 8, 2021 License: GPL-3.0 Imports: 26 Imported by: 0

README

pkg/SMTP

The vast majority of this code is based off of go-guerrilla (as of commit aa54b3ac4a0b4b34232fd29239422d024ad9395e), with slight modifications here and there to better accommodate Chameleons needs.

Thank you to those who have worked so hard to make go-guerrilla possible!

Todos

Notes

go-guerrilla implemented workers and processors as part of handling mail. I don't think we will require such a system, but if we do at some point in the future, then we will copy/paste the implementation or just import go-guerrilla directly.

Documentation

Index

Constants

View Source
const (
	// The client has connected, and is awaiting our first response
	ClientGreeting = iota
	// We have responded to the client's connection and are awaiting a command
	ClientCmd
	// We have received the sender and recipient information
	ClientData
	// We have agreed with the client to secure the connection over TLS
	ClientStartTLS
	// Server will shutdown, client to shutdown on next command turn
	ClientShutdown
)
View Source
const (
	// ClassSuccess specifies that the DSN is reporting a positive delivery
	// action.  Detail sub-codes may provide notification of
	// transformations required for delivery.
	ClassSuccess = 2
	// ClassTransientFailure - a persistent transient failure is one in which the message as
	// sent is valid, but persistence of some temporary condition has
	// caused abandonment or delay of attempts to send the message.
	// If this code accompanies a delivery failure report, sending in
	// the future may be successful.
	ClassTransientFailure = 4
	// ClassPermanentFailure - a permanent failure is one which is not likely to be resolved
	// by resending the message in the current form.  Some change to
	// the message or the destination must be made for successful
	// delivery.
	ClassPermanentFailure = 5
)
View Source
const (
	OtherStatus                             = ".0.0"
	OtherAddressStatus                      = ".1.0"
	BadDestinationMailboxAddress            = ".1.1"
	BadDestinationSystemAddress             = ".1.2"
	BadDestinationMailboxAddressSyntax      = ".1.3"
	DestinationMailboxAddressAmbiguous      = ".1.4"
	DestinationMailboxAddressValid          = ".1.5"
	MailboxHasMoved                         = ".1.6"
	BadSendersMailboxAddressSyntax          = ".1.7"
	BadSendersSystemAddress                 = ".1.8"
	OtherOrUndefinedMailboxStatus           = ".2.0"
	MailboxDisabled                         = ".2.1"
	MailboxFull                             = ".2.2"
	MessageLengthExceedsAdministrativeLimit = ".2.3"
	MailingListExpansionProblem             = ".2.4"
	OtherOrUndefinedMailSystemStatus        = ".3.0"
	MailSystemFull                          = ".3.1"
	SystemNotAcceptingNetworkMessages       = ".3.2"
	SystemNotCapableOfSelectedFeatures      = ".3.3"
	MessageTooBigForSystem                  = ".3.4"
	OtherOrUndefinedNetworkOrRoutingStatus  = ".4.0"
	NoAnswerFromHost                        = ".4.1"
	BadConnection                           = ".4.2"
	RoutingServerFailure                    = ".4.3"
	UnableToRoute                           = ".4.4"
	NetworkCongestion                       = ".4.5"
	RoutingLoopDetected                     = ".4.6"
	DeliveryTimeExpired                     = ".4.7"
	OtherOrUndefinedProtocolStatus          = ".5.0"
	InvalidCommand                          = ".5.1"
	SyntaxError                             = ".5.2"
	TooManyRecipients                       = ".5.3"
	InvalidCommandArguments                 = ".5.4"
	WrongProtocolVersion                    = ".5.5"
	OtherOrUndefinedMediaError              = ".6.0"
	MediaNotSupported                       = ".6.1"
	ConversionRequiredAndProhibited         = ".6.2"
	ConversionRequiredButNotSupported       = ".6.3"
	ConversionWithLossPerformed             = ".6.4"
	ConversionFailed                        = ".6.5"
)

DefaultMap contains defined default codes (RfC 3463)

View Source
const (
	CommandVerbMaxLength = 16
	CommandLineMaxLength = 1024
	// Number of allowed unrecognized commands before we terminate the connection
	MaxUnrecognizedCommands = 5
)
View Source
const (
	// server has just been created
	ServerStateNew = iota
	// Server has just been stopped
	ServerStateStopped
	// Server has been started and is running
	ServerStateRunning
	// Server could not start due to an error
	ServerStateStartError
)
View Source
const SP = " "

space char

Variables

View Source
var (
	LineLimitExceeded   = errors.New("maximum line length exceeded")
	MessageSizeExceeded = errors.New("maximum message size exceeded")
)

A WordDecoder decodes MIME headers containing RFC 2047 encoded-words. Used by the MimeHeaderDecode function. It's exposed public so that an alternative decoder can be set, eg Gnu iconv by importing the mail/inconv package. Another alternative would be to use https://godoc.org/golang.org/x/text/encoding

View Source
var (
	ErrPoolShuttingDown = errors.New("server pool: shutting down")
)
View Source
var TLSCiphers = map[string]uint16{

	"TLS_RSA_WITH_3DES_EDE_CBC_SHA":        tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
	"TLS_RSA_WITH_AES_128_CBC_SHA":         tls.TLS_RSA_WITH_AES_128_CBC_SHA,
	"TLS_RSA_WITH_AES_256_CBC_SHA":         tls.TLS_RSA_WITH_AES_256_CBC_SHA,
	"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
	"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
	"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA":  tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
	"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA":   tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
	"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA":   tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,

	"TLS_RSA_WITH_RC4_128_SHA":        tls.TLS_RSA_WITH_RC4_128_SHA,
	"TLS_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
	"TLS_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_RSA_WITH_AES_256_GCM_SHA384,

	"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA":        tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
	"TLS_ECDHE_RSA_WITH_RC4_128_SHA":          tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
	"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
	"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384":   tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
	"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
}

https://golang.org/pkg/crypto/tls/#pkg-constants Ciphers introduced before Go 1.7 are listed here, ciphers since Go 1.8, see tls_go1.8.go ....... since Go 1.13, see tls_go1.13.go

View Source
var TLSClientAuthTypes = map[string]tls.ClientAuthType{
	"NoClientCert":               tls.NoClientCert,
	"RequestClientCert":          tls.RequestClientCert,
	"RequireAnyClientCert":       tls.RequireAnyClientCert,
	"VerifyClientCertIfGiven":    tls.VerifyClientCertIfGiven,
	"RequireAndVerifyClientCert": tls.RequireAndVerifyClientCert,
}

https://golang.org/pkg/crypto/tls/#ClientAuthType

View Source
var TLSCurves = map[string]tls.CurveID{
	"P256": tls.CurveP256,
	"P384": tls.CurveP384,
	"P521": tls.CurveP521,
}

https://golang.org/pkg/crypto/tls/#CurveID

View Source
var TLSProtocols = map[string]uint16{
	"tls1.0": tls.VersionTLS10,
	"tls1.1": tls.VersionTLS11,
	"tls1.2": tls.VersionTLS12,
}

https://golang.org/pkg/crypto/tls/#pkg-constants

Functions

func MimeHeaderDecode

func MimeHeaderDecode(str string) string

MimeHeaderDecode converts 7 bit encoded mime header strings to UTF-8

func NewClient

func NewClient(conn net.Conn, clientID uint64, logger log.ChameleonLogger, envelope *EnvelopePool) *client

NewClient allocates a new client.

Types

type Address

type Address struct {
	// User is local part
	User string
	// Host is the domain
	Host string
	// ADL is at-domain list if matched
	ADL []string
	// PathParams contains any ESTMP parameters that were matched
	PathParams [][]string
	// NullPath is true if <> was received
	NullPath bool
	// Quoted indicates if the local-part needs quotes
	Quoted bool
	// IP stores the IP Address, if the Host is an IP
	IP net.IP
	// DisplayName is a label before the address (RFC5322)
	DisplayName string
	// DisplayNameQuoted is true when DisplayName was quoted
	DisplayNameQuoted bool
}

Address encodes an email address of the form `<user@host>`

func NewAddress

func NewAddress(str string) (*Address, error)

NewAddress takes a string of an RFC 5322 address of the form "Gogh Fir <gf@example.com>" or "foo@example.com".

func (*Address) IsEmpty

func (a *Address) IsEmpty() bool

func (*Address) IsPostmaster

func (a *Address) IsPostmaster() bool

func (*Address) String

func (a *Address) String() string

type ClientState

type ClientState int

ClientState indicates which part of the SMTP transaction a given client is in.

type EnhancedStatusCode

type EnhancedStatusCode struct {
	Class             class
	SubjectDetailCode subjectDetail
}

EnhancedStatus are the ones that look like 2.1.0

func (EnhancedStatusCode) String

func (e EnhancedStatusCode) String() string

String returns a string representation of EnhancedStatus

type Envelope

type Envelope struct {
	// Remote IP address
	RemoteIP string
	// Message sent in EHLO command
	Helo string
	// Sender
	MailFrom Address
	// Recipients
	RcptTo []Address
	// Data stores the header and message body
	Data bytes.Buffer
	// Subject stores the subject of the email, extracted and decoded after calling ParseHeaders()
	Subject string
	// TLS is true if the email was received using a TLS connection
	TLS bool
	// Header stores the results from ParseHeaders()
	Header textproto.MIMEHeader
	// Values hold the values generated when processing the envelope by the backend
	Values map[string]interface{}
	// Hashes of each email on the rcpt
	Hashes []string
	// additional delivery header that may be added
	DeliveryHeader string
	// Email(s) will be queued with this id
	QueuedId string
	// ESMTP: true if EHLO was used
	ESMTP bool
	// When locked, it means that the envelope is being processed by the backend
	sync.Mutex
}

Envelope of Email represents a single SMTP message.

func NewEnvelope

func NewEnvelope(remoteAddr string, clientID uint64) *Envelope

func (*Envelope) Len

func (e *Envelope) Len() int

Len returns the number of bytes that would be in the reader returned by NewReader()

func (*Envelope) NewReader

func (e *Envelope) NewReader() io.Reader

NewReader returns a new reader for reading the email contents, including the delivery headers

func (*Envelope) ParseHeaders

func (e *Envelope) ParseHeaders() error

ParseHeaders parses the headers into Header field of the Envelope struct. Data buffer must be full before calling. It assumes that at most 30kb of email data can be a header Decoding of encoding to UTF is only done on the Subject, where the result is assigned to the Subject field

func (*Envelope) PopRcpt

func (e *Envelope) PopRcpt() Address

PopRcpt removes the last email address that was pushed to the envelope

func (*Envelope) PushRcpt

func (e *Envelope) PushRcpt(addr Address)

PushRcpt adds a recipient email address to the envelope

func (*Envelope) Reseed

func (e *Envelope) Reseed(remoteIP string, clientID uint64)

Reseed is called when used with a new connection, once it's accepted

func (*Envelope) ResetTransaction

func (e *Envelope) ResetTransaction()

ResetTransaction is called when the transaction is reset (keeping the connection open)

func (*Envelope) String

func (e *Envelope) String() string

String converts the email to string. Typically, you would want to use the compressor guerrilla.Processor for more efficiency, or use NewReader

type EnvelopePool

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

func NewEnvelopePool

func NewEnvelopePool(poolSize int) *EnvelopePool

func (*EnvelopePool) Borrow

func (p *EnvelopePool) Borrow(remoteAddr string, clientID uint64) *Envelope

func (*EnvelopePool) Return

func (p *EnvelopePool) Return(e *Envelope)

Return returns an envelope back to the envelope pool Make sure that envelope finished processing before calling this

type Errors

type Errors []error

func (Errors) Error

func (e Errors) Error() string

implement the Error interface

type Handler

type Handler interface {
	ValidateRcpt(e *Envelope, logger log.ChameleonLogger) error
	Handle(e *Envelope, logger log.ChameleonLogger) Result
}

type Pool

type Pool struct {
	ShutdownChan chan int
	// contains filtered or unexported fields
}

Pool holds Clients.

func NewPool

func NewPool(poolSize int) *Pool

NewPool creates a new pool of Clients.

func (*Pool) Borrow

func (p *Pool) Borrow(conn net.Conn, clientID uint64, logger log.ChameleonLogger, ep *EnvelopePool) (Poolable, error)

Borrow a Client from the pool. Will block if len(activeClients) > maxClients

func (*Pool) GetActiveClientsCount

func (p *Pool) GetActiveClientsCount() int

Gets the number of active clients that are currently out of the pool and busy serving

func (*Pool) IsShuttingDown

func (p *Pool) IsShuttingDown() bool

returns true if the pool is shutting down

func (*Pool) Return

func (p *Pool) Return(c Poolable)

Return returns a Client back to the pool.

func (*Pool) SetTimeout

func (p *Pool) SetTimeout(duration time.Duration)

set a timeout for all lent clients

func (*Pool) ShutdownState

func (p *Pool) ShutdownState()

Lock the pool from borrowing then remove all active clients each active client's timeout is lowered to 1 sec and notified to stop accepting commands

func (*Pool) ShutdownWait

func (p *Pool) ShutdownWait()

func (*Pool) Start

func (p *Pool) Start()

type Poolable

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

a struct can be pooled if it has the following interface

type Response

type Response struct {
	EnhancedCode subjectDetail
	BasicCode    int
	Class        class
	// Comment is optional
	Comment string
	// contains filtered or unexported fields
}

Response type for Stringer interface

func (*Response) String

func (r *Response) String() string

String returns a custom Response as a string

type Responses

type Responses struct {

	// The 500's
	FailLineTooLong              *Response
	FailNestedMailCmd            *Response
	FailNoSenderDataCmd          *Response
	FailNoRecipientsDataCmd      *Response
	FailUnrecognizedCmd          *Response
	FailMaxUnrecognizedCmd       *Response
	FailSyntaxError              *Response
	FailReadLimitExceededDataCmd *Response
	FailMessageSizeExceeded      *Response
	FailReadErrorDataCmd         *Response
	FailPathTooLong              *Response
	FailInvalidAddress           *Response
	FailLocalPartTooLong         *Response
	FailDomainTooLong            *Response
	FailBackendNotRunning        *Response
	FailBackendTransaction       *Response
	FailBackendTimeout           *Response
	FailRcptCmd                  *Response

	// The 400's
	ErrorTooManyRecipients *Response
	ErrorRelayDenied       *Response
	ErrorShutdown          *Response

	// The 200's
	SuccessMailCmd       *Response
	SuccessRcptCmd       *Response
	SuccessResetCmd      *Response
	SuccessVerifyCmd     *Response
	SuccessNoopCmd       *Response
	SuccessQuitCmd       *Response
	SuccessDataCmd       *Response
	SuccessStartTLSCmd   *Response
	SuccessMessageQueued *Response
}

Responses has some already pre-constructed responses

var (
	// Canned is to be read-only, except in the init() function
	Canned Responses
)

type Result

type Result interface {
	fmt.Stringer
	// Code should return the SMTP code associated with this response, ie. `250`
	Code() int
}

Result represents a response to an SMTP client after receiving DATA. The String method should return an SMTP message ready to send back to the client, for example `250 OK: Message received`.

func NewResult

func NewResult(r ...interface{}) Result

type Server

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

Server listens for SMTP clients on the port specified in its config

func NewServer

func NewServer(sc *ServerConfig, handler Handler, log log.ChameleonLogger) (*Server, error)

Creates and returns a new ready-to-run Server from a ServerConfig configuration

func (*Server) GetActiveClientsCount

func (s *Server) GetActiveClientsCount() int

func (*Server) SetAllowedHosts

func (s *Server) SetAllowedHosts(allowedHosts []string)

Set the allowed hosts for the server

func (*Server) Shutdown

func (s *Server) Shutdown()

func (*Server) Start

func (s *Server) Start() error

Begin accepting SMTP clients. Will block unless there is an error or server.Shutdown() is called

type ServerConfig

type ServerConfig struct {
	// TLS Configuration
	TLS *ServerTLSConfig `yaml:"tls,omitempty"`

	// Hostname will be used in the server's reply to HELO/EHLO
	// If TLS enabled, make sure that the Hostname matches the cert
	// Defaults to os.Hostname()
	// Hostname will also be used to fill the 'Host' property when the "RCPT TO" address is addressed to just <postmaster>
	Hostname string `yaml:"hostname"`

	// Listen interface specified in <ip>:<port> - defaults to 127.0.0.1:2525
	ListenInterface string `yaml:"listen-interface"`

	// MaxSize is the maximum size of an email that will be accepted for delivery
	// Defaults to 10 Mebibytes
	MaxSize int64 `yaml:"max-mail-size"`

	// Timeout specifies the connection timeout in seconds. Defaults to 30
	Timeout int `yaml:"timeout"`

	// MaxClients controls how many maximum clients we can handle at once
	// Defaults to defaultMaxClients
	MaxClients int `yaml:"max-clients"`

	// AllowedHosts lists which hosts to accept email for. Defaults to os.Hostname
	AllowedHosts []string `yaml:"allowed-hosts"`

	// XClientOn when using a proxy such as Nginx, XCLIENT command is used to pass the
	// original client's IP address & client's HELO
	XClientOn bool `yaml:"xclient-on,omitempty"`
}

ServerConfig specifies config options for a single server

func (*ServerConfig) SetDefaults

func (c *ServerConfig) SetDefaults() error

SetDefaults fills in default server settings for values that were not configured The defaults are: * Server listening to 127.0.0.1:2525 * use your hostname to determine your which hosts to accept email for * 100 maximum clients * 10MB max message size * log to Stderr, * log level set to "`debug`" * timeout to 30 sec * Backend configured with the following processors: `HeadersParser|Header|Debugger` where it will log the received emails.

type ServerTLSConfig

type ServerTLSConfig struct {

	// TLS Protocols to use. [0] = min, [1]max
	// Use Go's default if empty
	Protocols []string `yaml:"protocols,omitempty"`

	// TLS Ciphers to use.
	// Use Go's default if empty
	Ciphers []string `yaml:"ciphers,omitempty"`

	// TLS Curves to use.
	// Use Go's default if empty
	Curves []string `yaml:"curves,omitempty"`

	// PrivateKeyFile path to cert private key in PEM format.
	PrivateKeyFile string `yaml:"private-key-file"`

	// PublicKeyFile path to cert (public key) chain in PEM format.
	PublicKeyFile string `yaml:"public-key-file"`

	// TLS Root cert authorities to use. "A PEM encoded CA's certificate file.
	// Defaults to system's root CA file if empty
	RootCAs string `yaml:"root-cas-file,omitempty"`

	// declares the policy the server will follow for TLS Client Authentication.
	// Use Go's default if empty
	ClientAuthType string `yaml:"client-auth-type,omitempty"`

	// controls whether the server selects the
	// client's most preferred cipher suite
	PreferServerCipherSuites bool `yaml:"prefer-server-cipher-suites,omitempty"`

	// StartTLSOn should we offer STARTTLS command. Cert must be valid.
	// False by default
	StartTLSOn bool `yaml:"start-tls-on,omitempty"`

	// AlwaysOn run this server as a pure TLS server, i.e. SMTPS
	AlwaysOn bool `yaml:"always-on,omitempty"`
	// contains filtered or unexported fields
}

func (*ServerTLSConfig) SetDefaults

func (stc *ServerTLSConfig) SetDefaults() error

func (*ServerTLSConfig) Validate

func (stc *ServerTLSConfig) Validate() error

Jump to

Keyboard shortcuts

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