2014-11-22 19:15:50 +00:00
|
|
|
package server
|
2014-11-22 16:11:04 +00:00
|
|
|
|
|
|
|
// http://www.rfc-editor.org/rfc/rfc5321.txt
|
|
|
|
|
|
|
|
import (
|
2014-11-23 04:21:53 +00:00
|
|
|
"io"
|
2014-11-22 16:11:04 +00:00
|
|
|
"log"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/ian-kent/Go-MailHog/mailhog/data"
|
2014-11-22 19:15:50 +00:00
|
|
|
"github.com/ian-kent/Go-MailHog/mailhog/smtp/protocol"
|
2014-11-23 12:43:33 +00:00
|
|
|
"github.com/ian-kent/Go-MailHog/mailhog/storage"
|
2014-11-22 16:11:04 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Session represents a SMTP session using net.TCPConn
|
|
|
|
type Session struct {
|
2014-11-23 04:21:53 +00:00
|
|
|
conn io.ReadWriteCloser
|
|
|
|
proto *protocol.Protocol
|
2014-11-23 12:43:33 +00:00
|
|
|
storage storage.Storage
|
|
|
|
messageChan chan *data.Message
|
2014-11-23 04:21:53 +00:00
|
|
|
remoteAddress string
|
|
|
|
isTLS bool
|
|
|
|
line string
|
2014-11-22 16:11:04 +00:00
|
|
|
}
|
|
|
|
|
2014-11-23 04:21:53 +00:00
|
|
|
// Accept starts a new SMTP session using io.ReadWriteCloser
|
2014-11-23 12:43:33 +00:00
|
|
|
func Accept(remoteAddress string, conn io.ReadWriteCloser, storage storage.Storage, messageChan chan *data.Message, hostname string) {
|
2014-11-22 19:15:50 +00:00
|
|
|
proto := protocol.NewProtocol()
|
2014-11-23 12:43:33 +00:00
|
|
|
proto.Hostname = hostname
|
|
|
|
session := &Session{conn, proto, storage, messageChan, remoteAddress, false, ""}
|
2014-11-22 16:11:04 +00:00
|
|
|
proto.LogHandler = session.logf
|
2014-11-22 19:26:11 +00:00
|
|
|
proto.MessageReceivedHandler = session.acceptMessage
|
2014-11-22 19:20:47 +00:00
|
|
|
proto.ValidateSenderHandler = session.validateSender
|
|
|
|
proto.ValidateRecipientHandler = session.validateRecipient
|
2014-11-22 19:26:11 +00:00
|
|
|
proto.ValidateAuthenticationHandler = session.validateAuthentication
|
2014-11-22 16:11:04 +00:00
|
|
|
|
|
|
|
session.logf("Starting session")
|
2014-11-23 00:42:15 +00:00
|
|
|
session.Write(proto.Start())
|
2014-11-22 16:11:04 +00:00
|
|
|
for session.Read() == true {
|
|
|
|
}
|
|
|
|
session.logf("Session ended")
|
|
|
|
}
|
|
|
|
|
2014-11-23 00:36:32 +00:00
|
|
|
func (c *Session) validateAuthentication(mechanism string, args ...string) (errorReply *protocol.Reply, ok bool) {
|
|
|
|
return nil, true
|
2014-11-22 19:26:11 +00:00
|
|
|
}
|
2014-11-23 00:23:39 +00:00
|
|
|
|
2014-11-22 19:20:47 +00:00
|
|
|
func (c *Session) validateRecipient(to string) bool {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Session) validateSender(from string) bool {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2014-11-22 19:26:11 +00:00
|
|
|
func (c *Session) acceptMessage(msg *data.Message) (id string, err error) {
|
2014-11-23 00:23:39 +00:00
|
|
|
c.logf("Storing message %s", msg.ID)
|
2014-11-23 12:43:33 +00:00
|
|
|
id, err = c.storage.Store(msg)
|
|
|
|
c.messageChan <- msg
|
2014-11-22 16:11:04 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Session) logf(message string, args ...interface{}) {
|
|
|
|
message = strings.Join([]string{"[SMTP %s]", message}, " ")
|
2014-11-23 04:21:53 +00:00
|
|
|
args = append([]interface{}{c.remoteAddress}, args...)
|
2014-11-22 16:11:04 +00:00
|
|
|
log.Printf(message, args...)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read reads from the underlying net.TCPConn
|
|
|
|
func (c *Session) Read() bool {
|
|
|
|
buf := make([]byte, 1024)
|
2014-11-23 04:21:53 +00:00
|
|
|
n, err := io.Reader(c.conn).Read(buf)
|
2014-11-22 16:11:04 +00:00
|
|
|
|
|
|
|
if n == 0 {
|
|
|
|
c.logf("Connection closed by remote host\n")
|
2014-11-23 12:43:33 +00:00
|
|
|
io.Closer(c.conn).Close() // not sure this is necessary?
|
2014-11-22 16:11:04 +00:00
|
|
|
return false
|
|
|
|
}
|
2014-11-23 12:43:33 +00:00
|
|
|
|
2014-11-22 16:11:04 +00:00
|
|
|
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
|
|
|
|
|
2014-11-23 12:43:33 +00:00
|
|
|
for strings.Contains(c.line, "\n") {
|
|
|
|
line, reply := c.proto.Parse(c.line)
|
|
|
|
c.line = line
|
2014-11-22 16:11:04 +00:00
|
|
|
|
2014-11-23 12:43:33 +00:00
|
|
|
if reply != nil {
|
|
|
|
c.Write(reply)
|
|
|
|
if reply.Status == 221 {
|
|
|
|
io.Closer(c.conn).Close()
|
|
|
|
}
|
2014-11-22 16:11:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write writes a reply to the underlying net.TCPConn
|
2014-11-22 19:15:50 +00:00
|
|
|
func (c *Session) Write(reply *protocol.Reply) {
|
|
|
|
lines := reply.Lines()
|
|
|
|
for _, l := range lines {
|
2014-11-22 16:11:04 +00:00
|
|
|
logText := strings.Replace(l, "\n", "\\n", -1)
|
|
|
|
logText = strings.Replace(logText, "\r", "\\r", -1)
|
|
|
|
c.logf("Sent %d bytes: '%s'", len(l), logText)
|
2014-11-23 04:21:53 +00:00
|
|
|
io.Writer(c.conn).Write([]byte(l))
|
2014-11-22 16:11:04 +00:00
|
|
|
}
|
|
|
|
}
|