mirror of
https://gitlab.com/ric_harvey/MailHog.git
synced 2024-11-28 08:44:03 +00:00
164 lines
4 KiB
Go
164 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))
|
||
|
}
|
||
|
}
|