From 75177984b27f158dff6838972d06aff1acf2bcec Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Mon, 12 Dec 2016 21:31:41 -0500 Subject: [PATCH] Add the first test for SMTP connections, based on the exchange in RFC 5321. This fixes a few bugs caught by the test. --- smtp/conn.go | 3 +- smtp/conn_test.go | 108 ++++++++++++++++++++++++++++++++++++++++++++++ smtp/server.go | 14 ++++++ 3 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 smtp/conn_test.go diff --git a/smtp/conn.go b/smtp/conn.go index f29b198..ac07595 100644 --- a/smtp/conn.go +++ b/smtp/conn.go @@ -41,7 +41,7 @@ func AcceptConnection(netConn net.Conn, server Server) error { var err error - conn.writeReply(250, fmt.Sprintf("%s ESMTP [%s] (mailpopbox)", server.Name(), netConn.LocalAddr())) + conn.writeReply(220, fmt.Sprintf("%s ESMTP [%s] (mailpopbox)", server.Name(), netConn.LocalAddr())) for { conn.line, err = conn.tp.ReadLine() @@ -58,6 +58,7 @@ func AcceptConnection(netConn net.Conn, server Server) error { switch cmd { case "QUIT": + conn.writeReply(221, "Goodbye") conn.tp.Close() break case "HELO": diff --git a/smtp/conn_test.go b/smtp/conn_test.go new file mode 100644 index 0000000..d0998cd --- /dev/null +++ b/smtp/conn_test.go @@ -0,0 +1,108 @@ +package smtp + +import ( + "net" + "net/textproto" + "path/filepath" + "runtime" + "strings" + "testing" +) + +func ok(t testing.TB, err error) { + if err != nil { + _, file, line, _ := runtime.Caller(1) + t.Errorf("[%s:%d] unexpected error: %v", filepath.Base(file), line, err) + } +} + +func readCodeLine(t testing.TB, conn *textproto.Conn, code int) string { + _, message, err := conn.ReadCodeLine(code) + ok(t, err) + return message +} + +// runServer creates a TCP socket, runs a listening server, and returns the connection. +// The server exits when the Conn is closed. +func runServer(t *testing.T, server Server) net.Listener { + l, err := net.Listen("tcp", "localhost:0") + if err != nil { + t.Fatal(err) + return nil + } + + go func() { + for { + conn, err := l.Accept() + if err != nil { + return + } + go AcceptConnection(conn, server) + } + }() + + return l +} + +type testServer struct { + EmptyServerCallbacks +} + +func (s *testServer) Name() string { + return "Test-Server" +} + +func createClient(t *testing.T, addr net.Addr) *textproto.Conn { + conn, err := textproto.Dial(addr.Network(), addr.String()) + if err != nil { + t.Fatal(err) + return nil + } + return conn +} + +// RFC 5321 § D.1 +func TestScenarioTypical(t *testing.T) { + s := testServer{} + l := runServer(t, &s) + defer l.Close() + + conn := createClient(t, l.Addr()) + + message := readCodeLine(t, conn, 220) + if !strings.HasPrefix(message, s.Name()) { + t.Errorf("Greeting does not have server name, got %q", message) + } + + greet := "greeting.TestScenarioTypical" + ok(t, conn.PrintfLine("EHLO "+greet)) + + _, message, err := conn.ReadResponse(250) + ok(t, err) + if !strings.Contains(message, greet) { + t.Errorf("EHLO response does not contain greeting, got %q", message) + } + + ok(t, conn.PrintfLine("MAIL FROM:")) + readCodeLine(t, conn, 250) + + ok(t, conn.PrintfLine("RCPT TO:")) + readCodeLine(t, conn, 250) + + ok(t, conn.PrintfLine("RCPT TO:")) + readCodeLine(t, conn, 250) // TODO: make this 55o by rejecting Green + + ok(t, conn.PrintfLine("RCPT TO:")) + readCodeLine(t, conn, 250) + + ok(t, conn.PrintfLine("DATA")) + readCodeLine(t, conn, 354) + + ok(t, conn.PrintfLine("Blah blah blah...")) + ok(t, conn.PrintfLine("...etc. etc. etc.")) + ok(t, conn.PrintfLine(".")) + readCodeLine(t, conn, 250) + + ok(t, conn.PrintfLine("QUIT")) + readCodeLine(t, conn, 221) +} diff --git a/smtp/server.go b/smtp/server.go index 271e172..3ecb24a 100644 --- a/smtp/server.go +++ b/smtp/server.go @@ -31,3 +31,17 @@ type Server interface { OnEHLO() *ReplyLine OnMessageDelivered(Envelope) *ReplyLine } + +type EmptyServerCallbacks struct {} + +func (*EmptyServerCallbacks) TLSConfig() *tls.Config { + return nil +} + +func (*EmptyServerCallbacks) OnEHLO() *ReplyLine { + return nil +} + +func (*EmptyServerCallbacks) OnMessageDelivered(Envelope) *ReplyLine { + return nil +} -- 2.22.5