package smtp
import (
+ "crypto/rand"
"fmt"
"net"
"net/mail"
"net/textproto"
"strings"
+ "time"
)
type state int
tp *textproto.Conn
remoteAddr net.Addr
+ esmtp bool
+ tls bool
+
state
line string
conn.tp.Close()
break
case "HELO":
+ conn.esmtp = false
fallthrough
case "EHLO":
+ conn.esmtp = true
conn.doEHLO()
case "MAIL":
conn.doMAIL()
return
}
+ received := time.Now()
env := Envelope{
RemoteAddr: conn.remoteAddr,
EHLO: conn.ehlo,
MailFrom: *conn.mailFrom,
RcptTo: conn.rcptTo,
- Data: data,
+ Received: received,
+ ID: conn.envelopeID(received),
}
+ trace := conn.getReceivedInfo(env)
+
+ env.Data = append(trace, data...)
+
if reply := conn.server.OnMessageDelivered(env); reply != nil {
conn.reply(*reply)
return
conn.reply(ReplyOK)
}
+func (conn *connection) envelopeID(t time.Time) string {
+ var idBytes [4]byte
+ rand.Read(idBytes[:])
+ return fmt.Sprintf("m.%d.%x", t.UnixNano(), idBytes)
+}
+
+func (conn *connection) getReceivedInfo(envelope Envelope) []byte {
+ rhost, _, err := net.SplitHostPort(conn.remoteAddr.String())
+ if err != nil {
+ rhost = conn.remoteAddr.String()
+ }
+
+ rhosts, err := net.LookupAddr(rhost)
+ if err == nil {
+ rhost = fmt.Sprintf("%s [%s]", rhosts[0], rhost)
+ }
+
+ base := fmt.Sprintf("Received: from %s (%s)\r\n ", conn.ehlo, rhost)
+
+ with := "SMTP"
+ if conn.esmtp {
+ with = "E" + with
+ }
+ if conn.tls {
+ with += "S"
+ }
+ base += fmt.Sprintf("by %s (mailpopbox) with %s id %s\r\n ", conn.server.Name(), with, envelope.ID)
+
+ base += fmt.Sprintf("for <%s>\r\n ", envelope.RcptTo[0].Address)
+
+ transport := "PLAINTEXT"
+ if conn.tls {
+ // TODO: TLS version, cipher, bits
+ }
+ date := envelope.Received.Format(time.RFC1123Z) // Same as RFC 5322 ยง 3.3
+ base += fmt.Sprintf("(using %s);\r\n %s\r\n", transport, date)
+
+ return []byte(base)
+}
+
func (conn *connection) doRSET() {
conn.state = stateInitial
conn.resetBuffers()
"runtime"
"strings"
"testing"
+ "time"
)
func _fl(depth int) string {
{"QUiT", 221, nil},
})
}
+
+func TestGetReceivedInfo(t *testing.T) {
+ conn := connection{
+ server: &testServer{},
+ remoteAddr: &net.IPAddr{net.IPv4(127, 0, 0, 1), ""},
+ }
+
+ now := time.Now()
+
+ const crlf = "\r\n"
+ const line1 = "Received: from remote.test. (localhost [127.0.0.1])" + crlf
+ const line2 = "by Test-Server (mailpopbox) with "
+ const msgId = "abcdef.hijk"
+ lineLast := now.Format(time.RFC1123Z) + crlf
+
+ type params struct {
+ ehlo string
+ esmtp bool
+ tls bool
+ address string
+ }
+
+ tests := []struct {
+ params params
+
+ expect []string
+ }{
+ {params{"remote.test.", true, false, "foo@bar.com"},
+ []string{line1,
+ line2 + "ESMTP id " + msgId + crlf,
+ "for <foo@bar.com>" + crlf,
+ "(using PLAINTEXT);" + crlf,
+ lineLast, ""}},
+ }
+
+ for _, test := range tests {
+ t.Logf("%#v", test.params)
+
+ conn.ehlo = test.params.ehlo
+ conn.esmtp = test.params.esmtp
+ conn.tls = test.params.tls
+
+ envelope := Envelope{
+ RcptTo: []mail.Address{{"", test.params.address}},
+ Received: now,
+ ID: msgId,
+ }
+
+ actual := conn.getReceivedInfo(envelope)
+ actualLines := strings.SplitAfter(string(actual), crlf)
+
+ if len(actualLines) != len(test.expect) {
+ t.Errorf("wrong numbber of lines, expected %d, got %d", len(test.expect), len(actualLines))
+ continue
+ }
+
+ for i, line := range actualLines {
+ expect := test.expect[i]
+ if expect != strings.TrimLeft(line, " ") {
+ t.Errorf("Expected equal string %q, got %q", expect, line)
+ }
+ }
+ }
+
+}