smtp

package
v0.30.0 Latest Latest
Warning

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

Go to latest
Published: Oct 4, 2021 License: BSD-3-Clause Imports: 25 Imported by: 1

Documentation

Overview

Package smtp provide a library for building SMTP server and client.

Server

By default, server will listen on port 25 and 465.

Port 25 is only used to receive message relay from other mail server. Any command that require authentication will be rejected by this port.

Port 465 is used to receive message submission from SMTP accounts with authentication.

Server Environment

The server require one primary domain with one primary account called "postmaster". Domain can have two or more accounts. Domain can have their own DKIM certificate.

Limitations

The server favor implicit TLS over STARTTLS (RFC 8314) on port 465 for message submission.

Index

Examples

Constants

View Source
const (
	CommandZERO CommandKind = 0
	CommandHELO             = 1 << iota
	CommandEHLO
	CommandAUTH
	CommandMAIL
	CommandRCPT
	CommandDATA
	CommandRSET
	CommandVRFY
	CommandEXPN
	CommandHELP
	CommandNOOP
	CommandQUIT
)

List of SMTP commands.

View Source
const (
	//
	// 2yz  Positive Completion reply
	//
	// The requested action has been successfully completed.  A new
	// request may be initiated.
	//
	StatusSystem        = 211
	StatusHelp          = 214
	StatusReady         = 220
	StatusClosing       = 221
	StatusAuthenticated = 235 // RFC 4954
	StatusOK            = 250
	StatusAddressChange = 251 // RFC 5321, section 3.4.
	StatusVerifyFailed  = 252 // RFC 5321, section 3.5.3.

	//
	// 3xx Positive Intermediate reply.
	//
	// The command has been accepted, but the requested action is being
	// held in abeyance, pending receipt of further information.  The
	// SMTP client should send another command specifying this
	// information.  This reply is used in command DATA.
	//
	StatusAuthReady = 334
	StatusDataReady = 354

	//
	// 4xx Transient Negative Completion reply
	//
	// The command was not accepted, and the requested action did not
	// occur.  However, the error condition is temporary, and the action
	// may be requested again.  The sender should return to the beginning
	// of the command sequence (if any).  It is difficult to assign a
	// meaning to "transient" when two different sites (receiver- and
	// sender-SMTP agents) must agree on the interpretation.  Each reply
	// in this category might have a different time value, but the SMTP
	// client SHOULD try again.  A rule of thumb to determine whether a
	// reply fits into the 4yz or the 5yz category (see below) is that
	// replies are 4yz if they can be successful if repeated without any
	// change in command form or in properties of the sender or receiver
	// (that is, the command is repeated identically and the receiver
	// does not put up a new implementation).
	//
	StatusShuttingDown           = 421
	StatusMailboxUnavailable     = 450
	StatusLocalError             = 451
	StatusNoStorage              = 452
	StatusParameterUnprocessable = 455

	//
	// 5xx indicate permanent failure.
	//
	// The command was not accepted and the requested action did not
	// occur.  The SMTP client SHOULD NOT repeat the exact request (in
	// the same sequence).  Even some "permanent" error conditions can be
	// corrected, so the human user may want to direct the SMTP client to
	// reinitiate the command sequence by direct action at some point in
	// the future (e.g., after the spelling has been changed, or the user
	// has altered the account status).
	//
	StatusCmdUnknown           = 500 // RFC 5321, section 4.2.4.
	StatusCmdTooLong           = 500 // RFC 5321, section 4.3.2.
	StatusCmdSyntaxError       = 501
	StatusCmdNotImplemented    = 502 // RFC 5321, section 4.2.4.
	StatusCmdBadSequence       = 503
	StatusParamUnimplemented   = 504
	StatusNotAuthenticated     = 530
	StatusInvalidCredential    = 535
	StatusMailboxNotFound      = 550
	StatusAddressChangeAborted = 551 // RFC 5321, section 3.4.
	StatusMailNoStorage        = 552
	StatusMailboxIncorrect     = 553
	StatusTransactionFailed    = 554
	StatusMailRcptParamUnknown = 555
)

List of SMTP status codes.

Variables

View Source
var (
	ErrInvalidCredential = &errors.E{
		Code:    StatusInvalidCredential,
		Message: "5.7.8 Authentication credentials invalid",
	}
)

List of errors.

Functions

func ParseMailbox

func ParseMailbox(data []byte) (mailbox []byte)

ParseMailbox parse the mailbox, remove comment or any escaped characters insided quoted-string.

func ParsePath

func ParsePath(path []byte) (mailbox []byte, err error)

ParsePath parse the Reverse-path or Forward-path as in argument of MAIL and RCPT commands. This function ignore the source route and only return the mailbox. Empty mailbox without an error is equal to Null Reverse-Path "<>".

Example
mb, _ := ParsePath([]byte(`<@domain.com,@domain.net:local.part@domain.com>`))
fmt.Printf("%s\n", mb)
mb, _ = ParsePath([]byte(`<local.part@domain.com>`))
fmt.Printf("%s\n", mb)
mb, _ = ParsePath([]byte(`<local>`))
fmt.Printf("%s\n", mb)
Output:

local.part@domain.com
local.part@domain.com
local

Types

type Account

type Account struct {
	Mailbox
	// HashPass user password that has been hashed using bcrypt.
	HashPass string
}

Account represent an SMTP account in the server that can send and receive email.

func NewAccount

func NewAccount(name, local, domain, pass string) (acc *Account, err error)

NewAccount create new account. Password will be hashed using bcrypt. An account with empty password is system account, which mean it will not allowed in SMTP AUTH.

func (*Account) Authenticate

func (acc *Account) Authenticate(pass string) (err error)

Authenticate a user using plain text password. It will return an error if password does not match.

func (*Account) Short

func (acc *Account) Short() (out string)

Short return the account email address without Name, "local@domain".

func (*Account) String

func (acc *Account) String() (out string)

String representation of account in the format of "Name <local@domain>" if Name is not empty, or "local@domain" is Name is empty.

type Client

type Client struct {
	// ServerInfo contains the server information, from the response of
	// EHLO command.
	ServerInfo *ServerInfo
	// contains filtered or unexported fields
}

Client for SMTP.

func NewClient

func NewClient(localName, remoteURL string, insecure bool) (cl *Client, err error)

NewClient create and initialize connection to remote SMTP server.

The localName define the client domain address, used when issuing EHLO command to server. If its empty, it will set to current operating system's hostname.

The remoteURL use the following format,

remoteURL = [ scheme "://" ](domain | IP-address [":" port])
scheme    = "smtp" / "smtps" / "smtp+starttls"

If scheme is "smtp" and no port is given, client will connect to remote address at port 25. If scheme is "smtps" and no port is given, client will connect to remote address at port 465 (implicit TLS). If scheme is "smtp+starttls" and no port is given, client will connect to remote address at port 587.

The "insecure" parameter, if set to true, will disable verifying remote certificate when connecting with TLS or STARTTLS.

On success, it will return connected client, with implicit EHLO command issued to server immediately. If scheme is "smtp+starttls", the connection also automatically upgraded to TLS after EHLO command success.

On fail, it will return nil client with an error.

func (*Client) Authenticate

func (cl *Client) Authenticate(mech Mechanism, username, password string) (
	res *Response, err error,
)

Authenticate to server using one of SASL mechanism. Currently, the only mechanism available is PLAIN.

func (*Client) Expand

func (cl *Client) Expand(mlist string) (res *Response, err error)

Expand get members of mailing-list.

func (*Client) Help

func (cl *Client) Help(cmdName string) (res *Response, err error)

Help get information on specific command from server.

func (*Client) MailTx

func (cl *Client) MailTx(mail *MailTx) (res *Response, err error)

MailTx send the mail to server. This function is implementation of mail transaction (MAIL, RCPT, and DATA commands as described in RFC 5321, section 3.3). The MailTx.Data must be internet message format which contains headers and content as defined by RFC 5322.

On success, it will return the last response, which is the success status of data transaction (250).

On fail, it will return response from the failed command with error is string combination of command, response code and message.

func (*Client) Quit

func (cl *Client) Quit() (res *Response, err error)

Quit signal the server that the client will close the connection.

func (*Client) SendCommand

func (cl *Client) SendCommand(cmd []byte) (res *Response, err error)

SendCommand send any custom command to server.

func (*Client) StartTLS added in v0.5.0

func (cl *Client) StartTLS() (res *Response, err error)

StartTLS upgrade the underlying connection to TLS. This method only works if client connected to remote URL using scheme "smtp+starttls" or on port 587, and on server that support STARTTLS extension.

func (*Client) Verify

func (cl *Client) Verify(mailbox string) (res *Response, err error)

Verify send the VRFY command to server to check if mailbox is exist.

type Command

type Command struct {
	Kind   CommandKind
	Arg    string
	Param  string
	Params map[string]string
}

Command represent a single SMTP command with its parsed argument and parameters.

type CommandKind

type CommandKind int

CommandKind represent the numeric value of SMTP command.

type DKIMOptions added in v0.6.0

type DKIMOptions struct {
	Signature  *dkim.Signature
	PrivateKey *rsa.PrivateKey
}

DKIMOptions contains the DKIM signature fields and private key to sign the incoming message.

type Domain

type Domain struct {
	Name     string
	Accounts map[string]*Account
	// contains filtered or unexported fields
}

Domain contains a host name and list of accounts in domain, with optional DKIM feature.

func NewDomain

func NewDomain(name string, dkimOpts *DKIMOptions) (domain *Domain)

NewDomain create new domain with single main user, "postmaster".

type Environment

type Environment struct {
	// PrimaryDomain of the SMTP server.
	// This field is required.
	PrimaryDomain *Domain

	// VirtualDomains contains list of virtual domain handled by server.
	// This field is optional.
	VirtualDomains map[string]*Domain
}

Environment contains SMTP server environment.

type Extension

type Extension interface {
	//
	// Name return the SMTP extension name to be used on reply of EHLO.
	//
	Name() string

	//
	// Params return the SMTP extension parameters.
	//
	Params() string

	//
	// ValidateCommand validate the command parameters, if the extension
	// provide custom parameters.
	//
	ValidateCommand(cmd *Command) error
}

Extension is an interface to implement extension for SMTP server.

type Handler

type Handler interface {
	// ServeAuth handle SMTP AUTH parameter username and password.
	ServeAuth(username, password string) (*Response, error)

	// ServeBounce handle email transaction that with unknown or invalid
	// recipent.
	ServeBounce(mail *MailTx) (*Response, error)

	// ServeExpand handle SMTP EXPN command.
	ServeExpand(mailingList string) (*Response, error)

	// ServeMailTx handle termination on email transaction.
	ServeMailTx(mail *MailTx) (*Response, error)

	// ServeVerify handle SMTP VRFY command.
	ServeVerify(username string) (*Response, error)
}

Handler define an interface to handle bouncing and incoming mail message, and handling EXPN and VRFY commands.

func NewLocalHandler

func NewLocalHandler(env *Environment) Handler

NewLocalHandler create an handler using local environment.

type LocalHandler

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

LocalHandler is an handler using local environment.

func (*LocalHandler) ServeAuth

func (lh *LocalHandler) ServeAuth(username, password string) (
	res *Response, err error,
)

ServeAuth handle SMTP AUTH parameter username and password.

func (*LocalHandler) ServeBounce

func (lh *LocalHandler) ServeBounce(mail *MailTx) (res *Response, err error)

ServeBounce handle email transaction with unknown or invalid recipent.

func (*LocalHandler) ServeExpand

func (lh *LocalHandler) ServeExpand(mailingList string) (res *Response, err error)

ServeExpand handle SMTP EXPN command.

BUG: The group feature currently is not supported.

func (*LocalHandler) ServeMailTx

func (lh *LocalHandler) ServeMailTx(mail *MailTx) (res *Response, err error)

ServeMailTx handle processing the final delivery of incoming mail.

func (*LocalHandler) ServeVerify

func (lh *LocalHandler) ServeVerify(username string) (res *Response, err error)

ServeVerify handle SMTP VRFY command. The username must be in the format of mailbox, "local@domain".

type LocalStorage

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

LocalStorage implement the Storage interface where mail object is save and retrieved in file system inside a directory.

func (*LocalStorage) MailBounce

func (fs *LocalStorage) MailBounce(id string) error

MailBounce move the incoming mail to bounced state. In this storage service, the mail file is moved to "{dir}/bounce".

func (*LocalStorage) MailDelete

func (fs *LocalStorage) MailDelete(id string) (err error)

MailDelete the mail object on file system by ID.

func (*LocalStorage) MailLoad

func (fs *LocalStorage) MailLoad(id string) (mail *MailTx, err error)

MailLoad read the mail object from file system by ID.

func (*LocalStorage) MailLoadAll

func (fs *LocalStorage) MailLoadAll() (mails []*MailTx, err error)

MailLoadAll mail objects from file system.

func (*LocalStorage) MailSave

func (fs *LocalStorage) MailSave(mail *MailTx) (err error)

MailSave save the mail object into file system.

type MailTx

type MailTx struct {
	// ID of message.
	// This field is ignored in Client.Send().
	ID string

	// From contains originator address.
	// This field is required in Client.Send().
	From string

	// Recipients contains list of the destination address.
	// This field is required in Client.Send().
	Recipients []string

	// Data contains content of message.
	// This field is optional in Client.Send().
	Data []byte

	Postpone time.Time

	// Received contains the time when the message arrived on server.
	// This field is ignored in Client.Send().
	Received time.Time
	Retry    int
}

MailTx define a mail transaction.

func NewMailTx

func NewMailTx(from string, to []string, data []byte) (mail *MailTx)

NewMailTx create and return new mail object.

func (*MailTx) Reset

func (mail *MailTx) Reset()

Reset all mail attributes to their zero value.

type Mailbox

type Mailbox struct {
	Name   string // Name of user in system.
	Local  string // Local part.
	Domain string // Domain part.
}

Mailbox represent a mailbox format.

type Mechanism

type Mechanism int

Mechanism represent Simple Authentication and Security Layer (SASL) mechanism (RFC 4422).

const (
	MechanismPLAIN Mechanism = 1
)

List of available SASL mechanism.

type Response

type Response struct {
	Code    int
	Message string
	Body    []string
}

Response represent a generic single or multilines response from server.

func NewResponse

func NewResponse(raw []byte) (res *Response, err error)

NewResponse create and initialize new Response from parsing the raw response text.

type Server

type Server struct {

	// TLSCert the server certificate for TLS or nil if no certificate.
	// This field is optional, if its non nil, the server will also listen
	// on address defined in TLSAddress.
	TLSCert *tls.Certificate

	// Env contains server environment.
	Env *Environment

	//
	// Exts define list of custom extensions that the server will provide.
	//
	Exts []Extension

	//
	// Handler define an interface that will process the bouncing email,
	// incoming email, EXPN command, and VRFY command.
	// This field is optional, if not set, it will default to
	// LocalHandler.
	//
	Handler Handler
	// contains filtered or unexported fields
}

Server defines parameters for running an SMTP server.

func (*Server) LoadCertificate

func (srv *Server) LoadCertificate(certFile, keyFile string) (err error)

LoadCertificate load TLS certificate and its private key from file.

func (*Server) Start

func (srv *Server) Start() (err error)

Start listening for SMTP connections. Each client connection will be handled in a single routine.

func (*Server) Stop

func (srv *Server) Stop()

Stop the server.

type ServerInfo

type ServerInfo struct {
	Domain string
	Info   string
	Exts   map[string][]string
}

ServerInfo provide information about server from response of EHLO or HELO command.

func NewServerInfo

func NewServerInfo(res *Response) (srvInfo *ServerInfo)

NewServerInfo create and initialize ServerInfo from EHLO/HELO response.

type Storage

type Storage interface {
	MailBounce(id string) error
	MailDelete(id string) error
	MailLoad(id string) (mail *MailTx, err error)
	MailLoadAll() (mail []*MailTx, err error)
	MailSave(mail *MailTx) error
}

Storage define an interface for storing and retrieving mail object into permanent storage (for example, file system or database).

func NewLocalStorage

func NewLocalStorage(dir string) (storage Storage, err error)

NewLocalStorage create and initialize new file storage. If directory is empty, the default storage is located at "/var/spool/smtp/".

Jump to

Keyboard shortcuts

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