Do not implement SMTP VRFY.
[mailpopbox.git] / smtp / conn_test.go
1 package smtp
2
3 import (
4 "fmt"
5 "net"
6 "net/mail"
7 "net/textproto"
8 "path/filepath"
9 "runtime"
10 "strings"
11 "testing"
12 )
13
14 func _fl(depth int) string {
15 _, file, line, _ := runtime.Caller(depth + 1)
16 return fmt.Sprintf("[%s:%d]", filepath.Base(file), line)
17 }
18
19 func ok(t testing.TB, err error) {
20 if err != nil {
21 t.Errorf("%s unexpected error: %v", _fl(1), err)
22 }
23 }
24
25 func readCodeLine(t testing.TB, conn *textproto.Conn, code int) string {
26 _, message, err := conn.ReadCodeLine(code)
27 if err != nil {
28 t.Errorf("%s ReadCodeLine error: %v", _fl(1), err)
29 }
30 return message
31 }
32
33 // runServer creates a TCP socket, runs a listening server, and returns the connection.
34 // The server exits when the Conn is closed.
35 func runServer(t *testing.T, server Server) net.Listener {
36 l, err := net.Listen("tcp", "localhost:0")
37 if err != nil {
38 t.Fatal(err)
39 return nil
40 }
41
42 go func() {
43 for {
44 conn, err := l.Accept()
45 if err != nil {
46 return
47 }
48 go AcceptConnection(conn, server)
49 }
50 }()
51
52 return l
53 }
54
55 type testServer struct {
56 EmptyServerCallbacks
57 blockList []string
58 }
59
60 func (s *testServer) Name() string {
61 return "Test-Server"
62 }
63
64 func (s *testServer) VerifyAddress(addr mail.Address) ReplyLine {
65 for _, block := range s.blockList {
66 if block == addr.Address {
67 return ReplyBadMailbox
68 }
69 }
70 return ReplyOK
71 }
72
73 func createClient(t *testing.T, addr net.Addr) *textproto.Conn {
74 conn, err := textproto.Dial(addr.Network(), addr.String())
75 if err != nil {
76 t.Fatal(err)
77 return nil
78 }
79 return conn
80 }
81
82 type requestResponse struct {
83 request string
84 responseCode int
85 handler func(testing.TB, *textproto.Conn)
86 }
87
88 func runTableTest(t testing.TB, conn *textproto.Conn, seq []requestResponse) {
89 for i, rr := range seq {
90 t.Logf("%s case %d", _fl(1), i)
91 ok(t, conn.PrintfLine(rr.request))
92 if rr.handler != nil {
93 rr.handler(t, conn)
94 } else {
95 readCodeLine(t, conn, rr.responseCode)
96 }
97 }
98 }
99
100 // RFC 5321 ยง D.1
101 func TestScenarioTypical(t *testing.T) {
102 s := testServer{
103 blockList: []string{"Green@foo.com"},
104 }
105 l := runServer(t, &s)
106 defer l.Close()
107
108 conn := createClient(t, l.Addr())
109
110 message := readCodeLine(t, conn, 220)
111 if !strings.HasPrefix(message, s.Name()) {
112 t.Errorf("Greeting does not have server name, got %q", message)
113 }
114
115 greet := "greeting.TestScenarioTypical"
116 ok(t, conn.PrintfLine("EHLO "+greet))
117
118 _, message, err := conn.ReadResponse(250)
119 ok(t, err)
120 if !strings.Contains(message, greet) {
121 t.Errorf("EHLO response does not contain greeting, got %q", message)
122 }
123
124 ok(t, conn.PrintfLine("MAIL FROM:<Smith@bar.com>"))
125 readCodeLine(t, conn, 250)
126
127 ok(t, conn.PrintfLine("RCPT TO:<Jones@foo.com>"))
128 readCodeLine(t, conn, 250)
129
130 ok(t, conn.PrintfLine("RCPT TO:<Green@foo.com>"))
131 readCodeLine(t, conn, 550)
132
133 ok(t, conn.PrintfLine("RCPT TO:<Brown@foo.com>"))
134 readCodeLine(t, conn, 250)
135
136 ok(t, conn.PrintfLine("DATA"))
137 readCodeLine(t, conn, 354)
138
139 ok(t, conn.PrintfLine("Blah blah blah..."))
140 ok(t, conn.PrintfLine("...etc. etc. etc."))
141 ok(t, conn.PrintfLine("."))
142 readCodeLine(t, conn, 250)
143
144 ok(t, conn.PrintfLine("QUIT"))
145 readCodeLine(t, conn, 221)
146 }
147
148 func TestVerifyAddress(t *testing.T) {
149 s := testServer{
150 blockList: []string{"banned@test.mail"},
151 }
152 l := runServer(t, &s)
153 defer l.Close()
154
155 conn := createClient(t, l.Addr())
156 readCodeLine(t, conn, 220)
157
158 runTableTest(t, conn, []requestResponse{
159 {"EHLO test", 0, func(t testing.TB, conn *textproto.Conn) { conn.ReadResponse(250) }},
160 {"VRFY banned@test.mail", 252, nil},
161 {"VRFY allowed@test.mail", 252, nil},
162 {"MAIL FROM:<sender@example.com>", 250, nil},
163 {"RCPT TO:<banned@test.mail>", 550, nil},
164 {"QUIT", 221, nil},
165 })
166 }