MailHog/smtp/server/session.go
2014-11-23 14:01:36 +00:00

116 lines
3 KiB
Go

package server
// http://www.rfc-editor.org/rfc/rfc5321.txt
import (
"io"
"log"
"strings"
"github.com/ian-kent/Go-MailHog/data"
"github.com/ian-kent/Go-MailHog/smtp/protocol"
"github.com/ian-kent/Go-MailHog/storage"
)
// Session represents a SMTP session using net.TCPConn
type Session struct {
conn io.ReadWriteCloser
proto *protocol.Protocol
storage storage.Storage
messageChan chan *data.Message
remoteAddress string
isTLS bool
line string
}
// 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) {
proto := protocol.NewProtocol()
proto.Hostname = hostname
session := &Session{conn, proto, storage, messageChan, remoteAddress, false, ""}
proto.LogHandler = session.logf
proto.MessageReceivedHandler = session.acceptMessage
proto.ValidateSenderHandler = session.validateSender
proto.ValidateRecipientHandler = session.validateRecipient
proto.ValidateAuthenticationHandler = session.validateAuthentication
session.logf("Starting session")
session.Write(proto.Start())
for session.Read() == true {
}
session.logf("Session ended")
}
func (c *Session) validateAuthentication(mechanism string, args ...string) (errorReply *protocol.Reply, ok bool) {
return nil, true
}
func (c *Session) validateRecipient(to string) bool {
return true
}
func (c *Session) validateSender(from string) bool {
return true
}
func (c *Session) acceptMessage(msg *data.Message) (id string, err error) {
c.logf("Storing message %s", msg.ID)
id, err = c.storage.Store(msg)
c.messageChan <- msg
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 := io.Reader(c.conn).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, "\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 true
}
// Write writes a reply to the underlying net.TCPConn
func (c *Session) Write(reply *protocol.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)
io.Writer(c.conn).Write([]byte(l))
}
}