smtp

package
v0.0.0-...-e47413e Latest Latest
Warning

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

Go to latest
Published: Nov 4, 2015 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package smtp provides structures and functions to listen for and handle SMTP client connections, as well as parsing SMTP socket communications.

Index

Constants

View Source
const (
	DATA int = iota
	RCPT int = iota
	MAIL int = iota
	HELO int = iota
	RSET int = iota
	QUIT int = iota
)

Constants representing the commands that an SMTP client will send during the course of communicating with our server.

View Source
const (
	STATE_START       int = iota
	STATE_HEADER      int = iota
	STATE_DATA_HEADER int = iota
	STATE_BODY        int = iota
	STATE_QUIT        int = iota
	STATE_ERROR       int = iota
)

Constants for the various states the parser can be in. The parser always starts with STATE_START and will end in either STATE_QUIT if the transmission was successful, or STATE_ERROR if something bad happened along the way.

View Source
const (
	RECEIVE_BUFFER_LEN        = 1024
	CONN_TIMEOUT_MILLISECONDS = 5
	COMMAND_TIMEOUT_SECONDS   = 5
)

Constants for parser buffer sizes and timeouts. CONN_TIMEOUT_MILLISECONDS is how many milliseconds to wait before attempting to read from the socket again. COMMAND_TIMEOUT_SETTINGS is how long to hold the socket open without recieving commands before closing with an error.

View Source
const (
	ENGINE_SQLITE int = 1
	ENGINE_MYSQL  int = 2
	ENGINE_MSSQL  int = 3
)

Variables

View Source
var Commands = map[string]int{
	"helo":      HELO,
	"ehlo":      HELO,
	"rcpt to":   RCPT,
	"mail from": MAIL,
	"send":      MAIL,
	"rset":      RSET,
	"quit":      QUIT,
	"data":      DATA,
}

This is a command 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.

View Source
var WebsocketConnections map[*WebsocketConnection]bool = make(map[*WebsocketConnection]bool)

Functions

func BroadcastMessageToWebsockets

func BroadcastMessageToWebsockets(message MailItemStruct)

This function takes a MailItemStruct and sends it to all open websockets.

func ConnectMSSQL

func ConnectMSSQL(host string, port string, database string, userName string, password string) (*sql.DB, error)

func ConnectMySQL

func ConnectMySQL(host string, port string, database string, userName string, password string) (*sql.DB, error)

func ConnectSqlite

func ConnectSqlite() (*sql.DB, error)

func CreateMSSQLDatabase

func CreateMSSQLDatabase(db *sql.DB) error

func CreateMySQLDatabase

func CreateMySQLDatabase(db *sql.DB) error

func CreateSqlliteDatabase

func CreateSqlliteDatabase(db *sql.DB) error

func WebsocketHandler

func WebsocketHandler(writer http.ResponseWriter, request *http.Request)

This function handles the handshake for our websocket connection. It sets up a goroutine to handle sending MailItemStructs to the other side.

Types

type Attachment

type Attachment struct {
	Headers  *AttachmentHeader
	Contents string
}

type AttachmentHeader

type AttachmentHeader struct {
	ContentType             string
	MIMEVersion             string
	ContentTransferEncoding string
	ContentDisposition      string
	FileName                string
	Body                    string
}

func (*AttachmentHeader) Parse

func (this *AttachmentHeader) Parse(contents string)

Parses a set of attachment headers. Splits lines up and figures out what header data goes into what structure key. Most headers follow this format:

Header-Name: Some value here\r\n

type MailBody

type MailBody struct {
	TextBody    string
	HTMLBody    string
	Attachments []*Attachment
}

func (*MailBody) Parse

func (this *MailBody) Parse(contents string, boundary string)

Parses a mail's DATA section. This will attempt to figure out what this mail contains. At the simples level it will contain a text message. A more complex example would be a multipart message with mixed text and HTML. It will also parse any attachments and retrieve their contents into an attachments array.

type MailHeader

type MailHeader struct {
	ContentType string
	Boundary    string
	MIMEVersion string
	Subject     string
	Date        string
	XMailer     string
}

func (*MailHeader) Parse

func (this *MailHeader) Parse(contents string)

Given an entire mail transmission this method parses a set of mail headers. It will split lines up and figures out what header data goes into what structure key. Most headers follow this format:

Header-Name: Some value here\r\n

However some headers, such as Content-Type, may have additiona information, especially when the content type is a multipart and there are attachments. Then it can look like this:

Content-Type: multipart/mixed; boundary="==abcsdfdfd=="\r\n

type MailItemStruct

type MailItemStruct struct {
	Id          int           `json:"id"`
	DateSent    string        `json:"dateSent"`
	FromAddress string        `json:"fromAddress"`
	ToAddresses []string      `json:"toAddresses"`
	Subject     string        `json:"subject"`
	XMailer     string        `json:"xmailer"`
	Body        string        `json:"body"`
	ContentType string        `json:"contentType"`
	Boundary    string        `json:"boundary"`
	Attachments []*Attachment `json:"attachments"`
}

MailItemStruct is a struct describing a parsed mail item. This is populated after an incoming client connection has finished sending mail data to this server.

type MailStorage

type MailStorage struct {
	Engine   int
	Host     string
	Port     string
	Database string
	UserName string
	Password string

	Db *sql.DB
}

Structure for holding a persistent database connection.

var Storage MailStorage

Global variable for our server's database connection

func (*MailStorage) Connect

func (ms *MailStorage) Connect() error

Open a connection to a SQLite database. This will attempt to delete any existing database file and create a new one with a blank table for holding mail data.

func (*MailStorage) Disconnect

func (ms *MailStorage) Disconnect()

Close a SQLite database connection.

func (*MailStorage) GetAttachment

func (ms *MailStorage) GetAttachment(id int) map[string]string

func (*MailStorage) GetMail

func (ms *MailStorage) GetMail(id int) model.JSONMailItem

Retrieves a single mail item and its attachments.

func (*MailStorage) GetMails

func (ms *MailStorage) GetMails() []model.JSONMailItem

Retrieves all stored mail items as an array of MailItemStruct items.

func (*MailStorage) StartWriteListener

func (ms *MailStorage) StartWriteListener(dbWriteChannel chan MailItemStruct)

Listens for messages on a channel for mail messages to be written to disk. This channel takes in MailItemStruct mail items.

type Parser

type Parser struct {
	State      int
	Connection net.Conn
	MailItem   MailItemStruct
}

SMTP parser. The parser type keeps the current state of a parsing session, the socket connection handle, and finally collects all information into a MailItemStruct.

func (*Parser) CommandRouter

func (parser *Parser) CommandRouter(command int, input string) bool

This function takes a command and the raw data read from the socket connection and executes the correct handler function to process the data and potentially respond to the client to continue SMTP negotiations.

func (*Parser) ParseCommand

func (parser *Parser) ParseCommand(line string) int

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.

func (*Parser) Process_DATA

func (parser *Parser) Process_DATA(line string) (bool, string, *MailHeader, *MailBody)

Function to process 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. True/false for success
  2. Error or success message
  3. Headers
  4. Body breakdown

func (*Parser) Process_HELO

func (parser *Parser) Process_HELO(line string) (bool, string)

Function to process the HELO and EHLO SMTP commands. This command responds to clients with a 250 greeting code and returns success or false and an error message (if any).

func (*Parser) Process_MAIL

func (parser *Parser) Process_MAIL(line string) (bool, string)

Function to process the MAIL FROM command (constant MAIL). This command will respond to clients with 250 Ok response and returns true/false for success and a string containing the sender's address.

func (*Parser) Process_RCPT

func (parser *Parser) Process_RCPT(line string) (bool, string)

Function to process the RCPT TO command (constant RCPT). This command will respond to clients with a 250 Ok response and returns true/false for success and a string containing the recipients address. Note that a client can send one or more RCPT TO commands.

func (*Parser) ReadChunk

func (parser *Parser) ReadChunk() string

This function 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 (*Parser) Run

func (parser *Parser) Run()

This is the main entry function called when a new client connection is established. It begins by sending a 220 welcome message to the client to indicate we are ready to communicate. From here we initialize a parser and blank MailItemStruct to hold the data we recieve. Once we recieve the quit command we close out.

A parsing session can end with either a STATE_QUIT if all was successful, or a STATE_ERROR if there was a problem.

func (*Parser) SendClosingResponse

func (parser *Parser) SendClosingResponse() (bool, string)

Function to tell 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 (*Parser) SendOkResponse

func (parser *Parser) SendOkResponse() (bool, string)

Function to tell a client that we recieved the last communication successfully and are ready to get our next command. This sends a 250 response. It returns true/false for success and a string with any response.

func (*Parser) SendResponse

func (parser *Parser) SendResponse(resp string) (bool, string)

Function to send a response to a client connection. It returns true/false for success and a string with any response.

type Server

type Server struct {
	Address          string
	ConnectionHandle net.Listener
}

Represents an SMTP server with an address and connection handle.

func (*Server) Close

func (s *Server) Close()

Closes a socket connection in an Server object. Most likely used in a defer call.

Example:

smtp := Server.Server { Address: "127.0.0.1:8000" }
defer smtp.Close()

func (*Server) Connect

func (s *Server) Connect()

Establishes a listening connection to a socket on an address. This will set the connection handle on our Server struct.

func (*Server) ProcessRequests

func (s *Server) ProcessRequests()

This function 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 SQLite 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 the database writing channel.

type WebsocketConnection

type WebsocketConnection struct {
	// Websocket connection handle
	WS *websocket.Conn

	// Buffered channel for outbound messages
	SendChannel chan MailItemStruct
}

Structure for tracking and working with websockets

Jump to

Keyboard shortcuts

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