Added TLS param to Peer. Added option to prepend Received header to envelope data.
This commit is contained in:
parent
73d3eb24c5
commit
4ba7fea939
6 changed files with 170 additions and 13 deletions
54
envelope.go
Normal file
54
envelope.go
Normal 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)
|
||||||
|
|
||||||
|
}
|
|
@ -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()
|
||||||
|
|
||||||
|
|
8
smtpd.go
8
smtpd.go
|
@ -63,13 +63,7 @@ type Peer struct {
|
||||||
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.
|
||||||
|
|
|
@ -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
22
wrap.go
Normal 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
24
wrap_test.go
Normal 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.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue