MailHog/vendor/github.com/mailhog/MailHog-Server/smtp/session.go
2017-04-17 00:42:21 +01:00

163 lines
4 KiB
Go

package smtp
// http://www.rfc-editor.org/rfc/rfc5321.txt
import (
"io"
"log"
"strings"
"github.com/ian-kent/linkio"
"github.com/mailhog/MailHog-Server/monkey"
"github.com/mailhog/data"
"github.com/mailhog/smtp"
"github.com/mailhog/storage"
)
// Session represents a SMTP session using net.TCPConn
type Session struct {
conn io.ReadWriteCloser
proto *smtp.Protocol
storage storage.Storage
messageChan chan *data.Message
remoteAddress string
isTLS bool
line string
link *linkio.Link
reader io.Reader
writer io.Writer
monkey monkey.ChaosMonkey
}
// Accept starts a new SMTP session using io.ReadWriteCloser
func Accept(remoteAddress string, conn io.ReadWriteCloser, storage storage.Storage, messageChan chan *data.Message, hostname string, monkey monkey.ChaosMonkey) {
defer conn.Close()
proto := smtp.NewProtocol()
proto.Hostname = hostname
var link *linkio.Link
reader := io.Reader(conn)
writer := io.Writer(conn)
if monkey != nil {
linkSpeed := monkey.LinkSpeed()
if linkSpeed != nil {
link = linkio.NewLink(*linkSpeed * linkio.BytePerSecond)
reader = link.NewLinkReader(io.Reader(conn))
writer = link.NewLinkWriter(io.Writer(conn))
}
}
session := &Session{conn, proto, storage, messageChan, remoteAddress, false, "", link, reader, writer, monkey}
proto.LogHandler = session.logf
proto.MessageReceivedHandler = session.acceptMessage
proto.ValidateSenderHandler = session.validateSender
proto.ValidateRecipientHandler = session.validateRecipient
proto.ValidateAuthenticationHandler = session.validateAuthentication
proto.GetAuthenticationMechanismsHandler = func() []string { return []string{"PLAIN"} }
session.logf("Starting session")
session.Write(proto.Start())
for session.Read() == true {
if monkey != nil && monkey.Disconnect != nil && monkey.Disconnect() {
session.conn.Close()
break
}
}
session.logf("Session ended")
}
func (c *Session) validateAuthentication(mechanism string, args ...string) (errorReply *smtp.Reply, ok bool) {
if c.monkey != nil {
ok := c.monkey.ValidAUTH(mechanism, args...)
if !ok {
// FIXME better error?
return smtp.ReplyUnrecognisedCommand(), false
}
}
return nil, true
}
func (c *Session) validateRecipient(to string) bool {
if c.monkey != nil {
ok := c.monkey.ValidRCPT(to)
if !ok {
return false
}
}
return true
}
func (c *Session) validateSender(from string) bool {
if c.monkey != nil {
ok := c.monkey.ValidMAIL(from)
if !ok {
return false
}
}
return true
}
func (c *Session) acceptMessage(msg *data.SMTPMessage) (id string, err error) {
m := msg.Parse(c.proto.Hostname)
c.logf("Storing message %s", m.ID)
id, err = c.storage.Store(m)
c.messageChan <- m
return
}
func (c *Session) logf(message string, args ...interface{}) {
message = strings.Join([]string{"[SMTP %s]", message}, " ")
args = append([]interface{}{c.remoteAddress}, args...)
log.Printf(message, args...)
}
// Read reads from the underlying net.TCPConn
func (c *Session) Read() bool {
buf := make([]byte, 1024)
n, err := c.reader.Read(buf)
if n == 0 {
c.logf("Connection closed by remote host\n")
io.Closer(c.conn).Close() // not sure this is necessary?
return false
}
if err != nil {
c.logf("Error reading from socket: %s\n", err)
return false
}
text := string(buf[0:n])
logText := strings.Replace(text, "\n", "\\n", -1)
logText = strings.Replace(logText, "\r", "\\r", -1)
c.logf("Received %d bytes: '%s'\n", n, logText)
c.line += text
for strings.Contains(c.line, "\r\n") {
line, reply := c.proto.Parse(c.line)
c.line = line
if reply != nil {
c.Write(reply)
if reply.Status == 221 {
io.Closer(c.conn).Close()
return false
}
}
}
return true
}
// Write writes a reply to the underlying net.TCPConn
func (c *Session) Write(reply *smtp.Reply) {
lines := reply.Lines()
for _, l := range lines {
logText := strings.Replace(l, "\n", "\\n", -1)
logText = strings.Replace(logText, "\r", "\\r", -1)
c.logf("Sent %d bytes: '%s'", len(l), logText)
c.writer.Write([]byte(l))
}
}