Added TLS param to Peer. Added option to prepend Received header to envelope data.

This commit is contained in:
Christian Joergensen 2014-07-21 00:06:56 +02:00
parent 73d3eb24c5
commit 4ba7fea939
6 changed files with 170 additions and 13 deletions

54
envelope.go Normal file
View file

@ -0,0 +1,54 @@
package smtpd
import (
"crypto/tls"
"fmt"
"strings"
"time"
)
// Envelope holds a message
type Envelope struct {
Sender string
Recipients []string
Data []byte
}
// AddReceivedLine prepends a Received header to the Data
func (env *Envelope) AddReceivedLine(peer Peer, serverName string) {
tlsDetails := ""
tlsVersions := map[uint16]string{
tls.VersionSSL30: "SSL3.0",
tls.VersionTLS10: "TLS1.0",
tls.VersionTLS11: "TLS1.1",
tls.VersionTLS12: "TLS1.2",
}
if peer.TLS != nil {
tlsDetails = fmt.Sprintf(
"\r\n\t(version=%s cipher=0x%x);",
tlsVersions[peer.TLS.Version],
peer.TLS.CipherSuite,
)
}
line := wrap([]byte(fmt.Sprintf(
"Received: from %s [%s] by %s with %s;%s\r\n\t%s\r\n",
peer.HeloName,
strings.Split(peer.Addr.String(), ":")[0],
serverName,
peer.Protocol,
tlsDetails,
time.Now().Format("Mon Jan 2 15:04:05 -0700 2006"),
)))
env.Data = append(env.Data, line...)
// Move the new Received line up front
copy(env.Data[len(line):], env.Data[0:len(env.Data)-len(line)])
copy(env.Data, line)
}

View file

@ -272,6 +272,10 @@ func (session *session) handleSTARTTLS(cmd command) {
session.scanner = bufio.NewScanner(session.reader) session.scanner = bufio.NewScanner(session.reader)
session.tls = true session.tls = true
// Save connection state on peer
state := tlsConn.ConnectionState()
session.peer.TLS = &state
// Flush the connection to set new timeout deadlines // Flush the connection to set new timeout deadlines
session.flush() session.flush()

View file

@ -58,18 +58,12 @@ const (
// Peer represents the client connecting to the server // Peer represents the client connecting to the server
type Peer struct { type Peer struct {
HeloName string // Server name used in HELO/EHLO command HeloName string // Server name used in HELO/EHLO command
Username string // Username from authentication, if authenticated Username string // Username from authentication, if authenticated
Password string // Password from authentication, if authenticated Password string // Password from authentication, if authenticated
Protocol Protocol // Protocol used, SMTP or ESMTP Protocol Protocol // Protocol used, SMTP or ESMTP
Addr net.Addr // Network address Addr net.Addr // Network address
} TLS *tls.ConnectionState // TLS Connection details, if on TLS
// Envelope holds a message
type Envelope struct {
Sender string
Recipients []string
Data []byte
} }
// Error represents an Error reported in the SMTP session. // Error represents an Error reported in the SMTP session.

View file

@ -1,7 +1,7 @@
package smtpd_test package smtpd_test
import ( import (
"bitbucket.org/chrj/smtpd" "bytes"
"crypto/tls" "crypto/tls"
"errors" "errors"
"fmt" "fmt"
@ -10,6 +10,8 @@ import (
"strings" "strings"
"testing" "testing"
"time" "time"
"bitbucket.org/chrj/smtpd"
) )
var localhostCert = []byte(`-----BEGIN CERTIFICATE----- var localhostCert = []byte(`-----BEGIN CERTIFICATE-----
@ -1145,3 +1147,60 @@ func TestXCLIENT(t *testing.T) {
} }
} }
func TestEnvelopeReceived(t *testing.T) {
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("Listen failed: %v", err)
}
defer ln.Close()
server := &smtpd.Server{
Handler: func(peer smtpd.Peer, env smtpd.Envelope) error {
env.AddReceivedLine(peer, "foobar.example.net")
if !bytes.HasPrefix(env.Data, []byte("Received: from localhost [127.0.0.1] by foobar.example.net with ESMTP;")) {
t.Fatal("Wrong received line.")
}
return nil
},
}
go func() {
server.Serve(ln)
}()
c, err := smtp.Dial(ln.Addr().String())
if err != nil {
t.Fatalf("Dial failed: %v", err)
}
if err := c.Mail("sender@example.org"); err != nil {
t.Fatalf("MAIL failed: %v", err)
}
if err := c.Rcpt("recipient@example.net"); err != nil {
t.Fatalf("RCPT failed: %v", err)
}
wc, err := c.Data()
if err != nil {
t.Fatalf("Data failed: %v", err)
}
_, err = fmt.Fprintf(wc, "This is the email body")
if err != nil {
t.Fatalf("Data body failed: %v", err)
}
err = wc.Close()
if err != nil {
t.Fatalf("Data close failed: %v", err)
}
if err := c.Quit(); err != nil {
t.Fatalf("QUIT failed: %v", err)
}
}

22
wrap.go Normal file
View file

@ -0,0 +1,22 @@
package smtpd
// Wrap a byte slice paragraph for use in SMTP header
func wrap(sl []byte) []byte {
length := 0
for i := 0; i < len(sl); i++ {
if length > 76 && sl[i] == ' ' {
sl = append(sl, 0, 0)
copy(sl[i+2:], sl[i:])
sl[i] = '\r'
sl[i+1] = '\n'
sl[i+2] = '\t'
i += 2
length = 0
}
if sl[i] == '\n' {
length = 0
}
length++
}
return sl
}

24
wrap_test.go Normal file
View file

@ -0,0 +1,24 @@
package smtpd
import (
"testing"
)
func TestWrap(t *testing.T) {
cases := map[string]string{
"foobar": "foobar",
"foobar quux": "foobar quux",
"foobar\r\n": "foobar\r\n",
"foobar\r\nquux": "foobar\r\nquux",
"foobar quux foobar quux foobar quux foobar quux foobar quux foobar quux foobar quux foobar quux": "foobar quux foobar quux foobar quux foobar quux foobar quux foobar quux foobar\r\n\tquux foobar quux",
"foobar quux foobar quux foobar quux foobar quux foobar quux foobar\r\n\tquux foobar quux foobar quux": "foobar quux foobar quux foobar quux foobar quux foobar quux foobar\r\n\tquux foobar quux foobar quux",
}
for k, v := range cases {
if string(wrap([]byte(k))) != v {
t.Fatal("Didn't wrap correctly.")
}
}
}