diff --git a/mailhog/config/config.go b/mailhog/config/config.go index a94a372..51401a3 100644 --- a/mailhog/config/config.go +++ b/mailhog/config/config.go @@ -1,5 +1,9 @@ package config +import ( + "github.com/ian-kent/Go-MailHog/mailhog/storage" +) + func DefaultConfig() *Config { return &Config{ SMTPBindAddr: "0.0.0.0:1025", @@ -19,6 +23,6 @@ type Config struct { MongoDb string MongoColl string MessageChan chan interface{} - Storage interface{} + Storage storage.Storage Assets func(asset string) ([]byte, error) } diff --git a/mailhog/smtp/protocol/protocol.go b/mailhog/smtp/protocol/protocol.go index 980c8ce..7152713 100644 --- a/mailhog/smtp/protocol/protocol.go +++ b/mailhog/smtp/protocol/protocol.go @@ -23,10 +23,22 @@ type Protocol struct { message *data.SMTPMessage hostname string - LogHandler func(message string, args ...interface{}) - MessageReceivedHandler func(*data.Message) (string, error) - ValidateSenderHandler func(from string) bool - ValidateRecipientHandler func(to string) bool + // LogHandler is called for each log message. If nil, log messages will + // be output using fmt.Printf instead. + LogHandler func(message string, args ...interface{}) + // MessageReceivedHandler is called for each message accepted by the + // SMTP protocol. It must return a MessageID or error. If nil, messages + // will be rejected with an error. + MessageReceivedHandler func(*data.Message) (string, error) + // ValidateSenderHandler should return true if the sender is valid, + // otherwise false. If nil, all senders will be accepted. + ValidateSenderHandler func(from string) bool + // ValidateRecipientHandler should return true if the recipient is valid, + // otherwise false. If nil, all recipients will be accepted. + ValidateRecipientHandler func(to string) bool + // ValidateAuthenticationhandler should return true if the authentication + // parameters are valid, otherwise false. If nil, all authentication + // attempts will be accepted. ValidateAuthenticationHandler func(mechanism string, args ...string) bool } @@ -50,7 +62,8 @@ func (proto *Protocol) logf(message string, args ...interface{}) { } } -// Start begins an SMTP conversation with a 220 reply +// Start begins an SMTP conversation with a 220 reply, placing the state +// machine in ESTABLISH state. func (proto *Protocol) Start(hostname string) *Reply { proto.state = ESTABLISH proto.hostname = hostname @@ -60,7 +73,8 @@ func (proto *Protocol) Start(hostname string) *Reply { // Parse parses a line string and returns any remaining line string // and a reply, if a command was found. Parse does nothing until a // new line is found. -// - TODO move this to a buffer inside proto? +// - TODO decide whether to move this to a buffer inside Protocol +// sort of like it this way, since it gives control back to the caller func (proto *Protocol) Parse(line string) (string, *Reply) { var reply *Reply @@ -266,7 +280,7 @@ func ParseMAIL(mail string) (string, error) { r := regexp.MustCompile("(?i:From):<([^>]+)>") match := r.FindStringSubmatch(mail) if len(match) != 2 { - return "", errors.New("Invalid sender") + return "", errors.New("Invalid sender " + mail) } return match[1], nil } @@ -276,7 +290,7 @@ func ParseRCPT(rcpt string) (string, error) { r := regexp.MustCompile("(?i:To):<([^>]+)>") match := r.FindStringSubmatch(rcpt) if len(match) != 2 { - return "", errors.New("Invalid recipient") + return "", errors.New("Invalid recipient " + rcpt) } return match[1], nil } diff --git a/mailhog/smtp/protocol/state.go b/mailhog/smtp/protocol/state.go index cbb98e7..8c7dc0e 100644 --- a/mailhog/smtp/protocol/state.go +++ b/mailhog/smtp/protocol/state.go @@ -1,7 +1,5 @@ package protocol -// http://www.rfc-editor.org/rfc/rfc5321.txt - // State represents the state of an SMTP conversation type State int diff --git a/mailhog/smtp/server/session.go b/mailhog/smtp/server/session.go index 72e49c0..6418102 100644 --- a/mailhog/smtp/server/session.go +++ b/mailhog/smtp/server/session.go @@ -3,7 +3,6 @@ package server // http://www.rfc-editor.org/rfc/rfc5321.txt import ( - "errors" "log" "net" "strings" @@ -11,7 +10,6 @@ import ( "github.com/ian-kent/Go-MailHog/mailhog/config" "github.com/ian-kent/Go-MailHog/mailhog/data" "github.com/ian-kent/Go-MailHog/mailhog/smtp/protocol" - "github.com/ian-kent/Go-MailHog/mailhog/storage" ) // Session represents a SMTP session using net.TCPConn @@ -43,6 +41,7 @@ func Accept(conn *net.TCPConn, conf *config.Config) { func (c *Session) validateAuthentication(mechanism string, args ...string) bool { return true } + func (c *Session) validateRecipient(to string) bool { return true } @@ -52,16 +51,8 @@ func (c *Session) validateSender(from string) bool { } func (c *Session) acceptMessage(msg *data.Message) (id string, err error) { - switch c.conf.Storage.(type) { - case *storage.MongoDB: - c.logf("Storing message using MongoDB") - id, err = c.conf.Storage.(*storage.MongoDB).Store(msg) - case *storage.InMemory: - c.logf("Storing message using Memory") - id, err = c.conf.Storage.(*storage.InMemory).Store(msg) - default: - err = errors.New("Unknown storage stype") - } + c.logf("Storing message %s", msg.ID) + id, err = c.conf.Storage.Store(msg) c.conf.MessageChan <- msg return } diff --git a/mailhog/storage/memory.go b/mailhog/storage/memory.go index 6af3c39..eb297d8 100644 --- a/mailhog/storage/memory.go +++ b/mailhog/storage/memory.go @@ -1,22 +1,17 @@ package storage -import ( - "github.com/ian-kent/Go-MailHog/mailhog/config" - "github.com/ian-kent/Go-MailHog/mailhog/data" -) +import "github.com/ian-kent/Go-MailHog/mailhog/data" // InMemory is an in memory storage backend type InMemory struct { - Config *config.Config Messages map[string]*data.Message MessageIndex []string MessageRIndex map[string]int } // CreateInMemory creates a new in memory storage backend -func CreateInMemory(c *config.Config) *InMemory { +func CreateInMemory() *InMemory { return &InMemory{ - Config: c, Messages: make(map[string]*data.Message, 0), MessageIndex: make([]string, 0), MessageRIndex: make(map[string]int, 0), @@ -32,15 +27,16 @@ func (memory *InMemory) Store(m *data.Message) (string, error) { } // List lists stored messages by index -func (memory *InMemory) List(start int, limit int) ([]*data.Message, error) { +func (memory *InMemory) List(start int, limit int) (*data.Messages, error) { if limit > len(memory.MessageIndex) { limit = len(memory.MessageIndex) } - var messages []*data.Message + var messages []data.Message for _, m := range memory.MessageIndex[start:limit] { - messages = append(messages, memory.Messages[m]) + messages = append(messages, *memory.Messages[m]) } - return messages, nil + msgs := data.Messages(messages) + return &msgs, nil } // DeleteOne deletes an individual message by storage ID diff --git a/mailhog/storage/mongodb.go b/mailhog/storage/mongodb.go index fe3fe71..3198342 100644 --- a/mailhog/storage/mongodb.go +++ b/mailhog/storage/mongodb.go @@ -3,7 +3,6 @@ package storage import ( "log" - "github.com/ian-kent/Go-MailHog/mailhog/config" "github.com/ian-kent/Go-MailHog/mailhog/data" "labix.org/v2/mgo" "labix.org/v2/mgo/bson" @@ -12,22 +11,20 @@ import ( // MongoDB represents MongoDB backed storage backend type MongoDB struct { Session *mgo.Session - Config *config.Config Collection *mgo.Collection } // CreateMongoDB creates a MongoDB backed storage backend -func CreateMongoDB(c *config.Config) *MongoDB { - log.Printf("Connecting to MongoDB: %s\n", c.MongoUri) - session, err := mgo.Dial(c.MongoUri) +func CreateMongoDB(uri, db, coll string) *MongoDB { + log.Printf("Connecting to MongoDB: %s\n", uri) + session, err := mgo.Dial(uri) if err != nil { log.Printf("Error connecting to MongoDB: %s", err) return nil } return &MongoDB{ Session: session, - Config: c, - Collection: session.DB(c.MongoDb).C(c.MongoColl), + Collection: session.DB(db).C(coll), } } diff --git a/mailhog/storage/storage.go b/mailhog/storage/storage.go new file mode 100644 index 0000000..abb422b --- /dev/null +++ b/mailhog/storage/storage.go @@ -0,0 +1,12 @@ +package storage + +import "github.com/ian-kent/Go-MailHog/mailhog/data" + +// Storage represents a storage backend +type Storage interface { + Store(m *data.Message) (string, error) + List(start int, limit int) (*data.Messages, error) + DeleteOne(id string) error + DeleteAll() error + Load(id string) (*data.Message, error) +} diff --git a/main.go b/main.go index dcb5e6e..c42d772 100644 --- a/main.go +++ b/main.go @@ -46,17 +46,17 @@ func configure() { if storage_type == "mongodb" { log.Println("Using MongoDB message storage") - s := storage.CreateMongoDB(conf) + s := storage.CreateMongoDB(conf.MongoUri, conf.MongoDb, conf.MongoColl) if s == nil { log.Println("MongoDB storage unavailable, reverting to in-memory storage") - conf.Storage = storage.CreateInMemory(conf) + conf.Storage = storage.CreateInMemory() } else { log.Println("Connected to MongoDB") conf.Storage = s } } else if storage_type == "memory" { log.Println("Using in-memory message storage") - conf.Storage = storage.CreateInMemory(conf) + conf.Storage = storage.CreateInMemory() } else { log.Fatalf("Invalid storage type %s", storage_type) }