Added TLS param to Peer. Added option to prepend Received header to envelope data.
This commit is contained in:
parent
c6fe39d4dc
commit
d28767953f
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.tls = true
|
||||
|
||||
// Save connection state on peer
|
||||
state := tlsConn.ConnectionState()
|
||||
session.peer.TLS = &state
|
||||
|
||||
// Flush the connection to set new timeout deadlines
|
||||
session.flush()
|
||||
|
||||
|
|
18
smtpd.go
18
smtpd.go
|
@ -58,18 +58,12 @@ const (
|
|||
|
||||
// Peer represents the client connecting to the server
|
||||
type Peer struct {
|
||||
HeloName string // Server name used in HELO/EHLO command
|
||||
Username string // Username from authentication, if authenticated
|
||||
Password string // Password from authentication, if authenticated
|
||||
Protocol Protocol // Protocol used, SMTP or ESMTP
|
||||
Addr net.Addr // Network address
|
||||
}
|
||||
|
||||
// Envelope holds a message
|
||||
type Envelope struct {
|
||||
Sender string
|
||||
Recipients []string
|
||||
Data []byte
|
||||
HeloName string // Server name used in HELO/EHLO command
|
||||
Username string // Username from authentication, if authenticated
|
||||
Password string // Password from authentication, if authenticated
|
||||
Protocol Protocol // Protocol used, SMTP or ESMTP
|
||||
Addr net.Addr // Network address
|
||||
TLS *tls.ConnectionState // TLS Connection details, if on TLS
|
||||
}
|
||||
|
||||
// Error represents an Error reported in the SMTP session.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package smtpd_test
|
||||
|
||||
import (
|
||||
"bitbucket.org/chrj/smtpd"
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
@ -10,6 +10,8 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"bitbucket.org/chrj/smtpd"
|
||||
)
|
||||
|
||||
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