Documentation
¶
Index ¶
- Constants
- Variables
- func GetCommandValue(streamInput, command, delimiter string) (string, error)
- func IsValidCommand(streamInput, expectedCommand string) error
- type Command
- type ConnectionExistsError
- type ConnectionManager
- type ConnectionNotExistsError
- type ConnectionPool
- type ConnectionPoolItem
- type DataCommandExecutor
- type HelloCommandExecutor
- type InvalidCommandError
- type InvalidCommandFormatError
- type Listener
- type MailCommandExecutor
- type NoWorkerAvailableError
- type NoopCommandExecutor
- type RcptCommandExecutor
- type Reader
- type ResetCommandExecutor
- type SMTPWorkerState
- type ServerPool
- type Worker
- type Writer
Constants ¶
const ( SMTP_CRLF string = "\r\n" SMTP_DATA_TERMINATOR string = "\r\n.\r\n" SMTP_WELCOME_MESSAGE string = "220 Welcome to MailSlurper!" SMTP_CLOSING_MESSAGE string = "221 Bye" SMTP_OK_MESSAGE string = "250 Ok" SMTP_DATA_RESPONSE_MESSAGE string = "354 End data with <CR><LF>.<CR><LF>" SMTP_HELLO_RESPONSE_MESSAGE string = "250 Hello. How very nice to meet you!" SMTP_ERROR_TRANSACTION_FAILED string = "554 Transaction failed" )
Responses that are sent to SMTP clients.
const ( SMTP_WORKER_IDLE SMTPWorkerState = 0 SMTP_WORKER_WORKING SMTPWorkerState = 1 SMTP_WORKER_DONE SMTPWorkerState = 100 SMTP_WORKER_ERROR SMTPWorkerState = 101 RECEIVE_BUFFER_LEN = 1024 CONNECTION_TIMEOUT_MINUTES = 10 COMMAND_TIMEOUT_SECONDS = 5 )
Variables ¶
var Commands = map[string]Command{ "helo": HELO, "ehlo": HELO, "rcpt to": RCPT, "mail from": MAIL, "send": MAIL, "rset": RSET, "quit": QUIT, "data": DATA, "noop": NOOP, }
Commands is a map of SMTP command strings to their int representation. This is primarily used because there can be more than one command to do the same things. For example, a client can send "helo" or "ehlo" to initiate the handshake.
var CommandsToStrings = map[Command]string{ HELO: "HELO", RCPT: "RCPT TO", MAIL: "SEND", RSET: "RSET", QUIT: "QUIT", DATA: "DATA", NOOP: "NOOP", }
CommandsToStrings is a friendly string representations of commands. Useful in error reporting.
var (
ErrServerClosed = errors.New("server closed")
)
Functions ¶
func GetCommandValue ¶
GetCommandValue splits an input by colon (:) and returns the right hand side. If there isn't a split, or a missing colon, an InvalidCommandFormatError is returned.
func IsValidCommand ¶
IsValidCommand returns an error if the input stream does not contain the expected command. The input and expected commands are lower cased, as we do not care about case when comparing.
Types ¶
type Command ¶
type Command int
Command represents a command issued over a TCP connection.
func GetCommandFromString ¶
GetCommandFromString takes a string and returns the integer command representation. For example if the string contains "DATA" then the value 1 (the constant DATA) will be returned.
type ConnectionExistsError ¶
type ConnectionExistsError struct {
Address string
}
An ConnectionExistsError is used to alert a client that there is already a connection by this address cached
func ConnectionExists ¶
func ConnectionExists(address string) *ConnectionExistsError
ConnectionExists returns a new error object
func (*ConnectionExistsError) Error ¶
func (err *ConnectionExistsError) Error() string
type ConnectionManager ¶
type ConnectionManager struct {
// contains filtered or unexported fields
}
ConnectionManager is responsible for maintaining, closing, and cleaning client connections. For every connection there is a worker. After an idle timeout period the manager will forceably close a client connection.
func NewConnectionManager ¶
func NewConnectionManager( logger *slog.Logger, config *slurperio.Config, chStop chan struct{}, mailItemChannel chan *model.MailItem, serverPool *ServerPool, ) *ConnectionManager
NewConnectionManager creates a new struct.
type ConnectionNotExistsError ¶
type ConnectionNotExistsError struct {
Address string
}
An ConnectionNotExistsError is used to alert a client that the specified connection is not in the ConnectionManager pool
func ConnectionNotExists ¶
func ConnectionNotExists(address string) *ConnectionNotExistsError
ConnectionNotExists returns a new error object
func (*ConnectionNotExistsError) Error ¶
func (err *ConnectionNotExistsError) Error() string
type ConnectionPool ¶
type ConnectionPool map[string]*ConnectionPoolItem
ConnectionPool is a map of remote address to TCP connections and their workers.
func NewConnectionPool ¶
func NewConnectionPool() ConnectionPool
NewConnectionPool creates a new empty map.
type ConnectionPoolItem ¶
ConnectionPoolItem is a single item in the pool. This tracks a connection to its worker.
func NewConnectionPoolItem ¶
func NewConnectionPoolItem(connection net.Conn, worker *Worker) *ConnectionPoolItem
NewConnectionPoolItem create a new object.
type DataCommandExecutor ¶
type DataCommandExecutor struct {
// contains filtered or unexported fields
}
DataCommandExecutor process the Data TO command.
func NewDataCommandExecutor ¶
func NewDataCommandExecutor( logger *slog.Logger, reader *Reader, writer *Writer, emailValidationService mailslurper.EmailValidationProvider, xssService sanitizer.IXSSServiceProvider, ) *DataCommandExecutor
NewDataCommandExecutor creates a new struct.
func (*DataCommandExecutor) Process ¶
func (e *DataCommandExecutor) Process(streamInput string, mailItem *model.MailItem) error
Process processes the DATA command (constant DATA). When a client sends the DATA command there are three parts to the transmission content. Before this data can be processed this function will tell the client how to terminate the DATA block. We are asking clients to terminate with "\r\n.\r\n".
The first part is a set of header lines. Each header line is a header key (name), followed by a colon, followed by the value for that header key. For example a header key might be "Subject" with a value of "Testing Mail!".
After the header section there should be two sets of carriage return/line feed characters. This signals the end of the header block and the start of the message body.
Finally when the client sends the "\r\n.\r\n" the DATA transmission portion is complete. This function will return the following items.
1. Headers (MailHeader) 2. Body breakdown (MailBody) 3. error structure
type HelloCommandExecutor ¶
type HelloCommandExecutor struct {
// contains filtered or unexported fields
}
HelloCommandExecutor process the commands EHLO, HELO.
func NewHelloCommandExecutor ¶
func NewHelloCommandExecutor(logger *slog.Logger, reader *Reader, writer *Writer) *HelloCommandExecutor
NewHelloCommandExecutor creates a new struct.
type InvalidCommandError ¶
type InvalidCommandError struct {
InvalidCommand string
}
An InvalidCommandError is used to alert a client that the command passed in is invalid.
func InvalidCommand ¶
func InvalidCommand(command string) *InvalidCommandError
InvalidCommand returns a new error object
func (*InvalidCommandError) Error ¶
func (err *InvalidCommandError) Error() string
type InvalidCommandFormatError ¶
type InvalidCommandFormatError struct {
InvalidCommand string
}
An InvalidCommandFormatError is used to alert a client that the command passed in has an invalid format
func InvalidCommandFormat ¶
func InvalidCommandFormat(command string) *InvalidCommandFormatError
InvalidCommandFormat returns a new error object
func (*InvalidCommandFormatError) Error ¶
func (err *InvalidCommandFormatError) Error() string
type Listener ¶
type Listener struct {
// contains filtered or unexported fields
}
Listener sets up a server that listens on a TCP socket for connections. When a connection is received a worker is created to handle processing the mail on this connection.
func NewListener ¶
func NewListener( logger *slog.Logger, config slurperio.ListenConfig, mailItemChannel chan *model.MailItem, serverPool *ServerPool, receivers []mailslurper.IMailItemReceiver, connectionManager mailslurper.IConnectionManager, ) (*Listener, error)
NewListener creates an Listener struct.
func (*Listener) Dispatch
deprecated
Deprecated:
Dispatch starts the process of handling SMTP client connections. The first order of business is to setup a channel for writing parsed mails, in the form of MailItemStruct variables, to our database. A goroutine is setup to listen on that channel and handles storage.
Meanwhile this method will loop forever and wait for client connections (blocking). When a connection is recieved a goroutine is started to create a new MailItemStruct and parser and the parser process is started. If the parsing is successful the MailItemStruct is added to a channel. An receivers passed in will be listening on that channel and may do with the mail item as they wish.
func (*Listener) ListenAndServe ¶
ListenAndServe starts the process of handling SMTP client connections. The first order of business is to setup a channel for writing parsed mails, in the form of MailItemStruct variables, to our database. A goroutine is setup to listen on that channel and handles storage.
Meanwhile this method will loop forever and wait for client connections (blocking). When a connection is recieved a goroutine is started to create a new MailItemStruct and parser and the parser process is started. If the parsing is successful the MailItemStruct is added to a channel. An receivers passed in will be listening on that channel and may do with the mail item as they wish.
ListenAndServe always returns a non-nil error.
type MailCommandExecutor ¶
type MailCommandExecutor struct {
// contains filtered or unexported fields
}
MailCommandExecutor process the MAIL FROM.
func NewMailCommandExecutor ¶
func NewMailCommandExecutor( logger *slog.Logger, reader *Reader, writer *Writer, emailValidationService mailslurper.EmailValidationProvider, xssService sanitizer.IXSSServiceProvider, ) *MailCommandExecutor
NewMailCommandExecutor creates a new struct.
type NoWorkerAvailableError ¶
type NoWorkerAvailableError struct{}
NoWorkerAvailableError is an error used when no worker is available to service a SMTP connection request.
func NoWorkerAvailable ¶
func NoWorkerAvailable() NoWorkerAvailableError
NoWorkerAvailable returns a new instance of the No Worker Available error
func (NoWorkerAvailableError) Error ¶
func (err NoWorkerAvailableError) Error() string
type NoopCommandExecutor ¶
type NoopCommandExecutor struct {
// contains filtered or unexported fields
}
NoopCommandExecutor process the command NOOP.
func NewNoopCommandExecutor ¶
func NewNoopCommandExecutor(logger *slog.Logger, writer *Writer) *NoopCommandExecutor
NewNoopCommandExecutor creates a new struct.
type RcptCommandExecutor ¶
type RcptCommandExecutor struct {
// contains filtered or unexported fields
}
RcptCommandExecutor process the RCPT TO command.
func NewRcptCommandExecutor ¶
func NewRcptCommandExecutor( logger *slog.Logger, reader *Reader, writer *Writer, emailValidationService mailslurper.EmailValidationProvider, xssService sanitizer.IXSSServiceProvider, ) *RcptCommandExecutor
NewRcptCommandExecutor creates a new struct.
type Reader ¶
Reader is a simple object for reading commands and responses from a connected TCP client.
func (*Reader) Read ¶
Read reads the raw data from the socket connection to our client. This will read on the socket until there is nothing left to read and an error is generated. This method blocks the socket for the number of milliseconds defined in CONN_TIMEOUT_MILLISECONDS. It then records what has been read in that time, then blocks again until there is nothing left on the socket to read. The final value is stored and returned as a string.
func (*Reader) ReadDataBlock ¶
ReadDataBlock is used by the SMTP DATA command. It will read data from the connection until the terminator is sent.
type ResetCommandExecutor ¶
type ResetCommandExecutor struct {
// contains filtered or unexported fields
}
ResetCommandExecutor process the command RSET.
func NewResetCommandExecutor ¶
func NewResetCommandExecutor(logger *slog.Logger, writer *Writer) *ResetCommandExecutor
NewResetCommandExecutor creates a new struct.
type SMTPWorkerState ¶
type SMTPWorkerState int
SMTPWorkerState defines states that a worker may be in. Typically a worker starts IDLE, the moves to WORKING, finally going to either DONE or ERROR.
type ServerPool ¶
type ServerPool struct {
// contains filtered or unexported fields
}
ServerPool represents a pool of SMTP workers. This will manage how many workers may respond to SMTP client requests and allocation of those workers.
func NewServerPool ¶
func NewServerPool(maxWorkers int, xss sanitizer.IXSSServiceProvider, logger *slog.Logger) *ServerPool
NewServerPool creates a new server pool with a maximum number of SMTP workers. An array of workers is initialized with an ID and an initial state of SMTP_WORKER_IDLE.
func (*ServerPool) JoinQueue ¶
func (pool *ServerPool) JoinQueue(worker *Worker)
JoinQueue adds a worker to the queue.
type Worker ¶
type Worker struct {
Connection net.Conn
EmailValidationService mailslurper.EmailValidationProvider
Error error
Reader *Reader
Receiver chan *model.MailItem
State SMTPWorkerState
WorkerID int
Writer *Writer
XSSService sanitizer.IXSSServiceProvider
// contains filtered or unexported fields
}
Worker is responsible for executing, parsing, and processing a single TCP connection's email.
func NewWorker ¶
func NewWorker( workerID int, pool *ServerPool, emailValidationService mailslurper.EmailValidationProvider, xssService sanitizer.IXSSServiceProvider, logger *slog.Logger, ) *Worker
NewWorker creates a new SMTP worker. An SMTP worker is responsible for parsing and working with SMTP mail data.
func (*Worker) Prepare ¶
func (w *Worker) Prepare( connection net.Conn, receiver chan *model.MailItem, reader *Reader, writer *Writer, chStop chan struct{}, connectionCloseChannel chan net.Conn, )
Prepare tells a worker about the TCP connection they will work with, the IO handlers, and sets their state.
func (*Worker) TimeoutHasExpired ¶
TimeoutHasExpired determines if the time elapsed since a start time has exceeded the command timeout.
type Writer ¶
Writer is a simple object for writing commands and responses to a client connected on a TCP socket.
func (*Writer) SayGoodbye ¶
SayGoodbye tells a client that we are done communicating. This sends a 221 response. It returns true/false for success and a string with any response.
func (*Writer) SayHello ¶
SayHello sends a hello message to a new client. The SMTP protocol dictates that you must be polite. :)
func (*Writer) SendDataResponse ¶
SendDataResponse is a function to send a DATA response message.
func (*Writer) SendHELOResponse ¶
SendHELOResponse sends a HELO message to a client.
func (*Writer) SendOkResponse ¶
SendOkResponse sends an OK to a client.
func (*Writer) SendResponse ¶
SendResponse sends a response to a client connection. It returns true/false for success and a string with any response.