Allow additional parameters after the address in smtp.connection.parasePath.
[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 "time"
13
14 "github.com/uber-go/zap"
15 )
16
17 func _fl(depth int) string {
18 _, file, line, _ := runtime.Caller(depth + 1)
19 return fmt.Sprintf("[%s:%d]", filepath.Base(file), line)
20 }
21
22 func ok(t testing.TB, err error) {
23 if err != nil {
24 t.Errorf("%s unexpected error: %v", _fl(1), err)
25 }
26 }
27
28 func readCodeLine(t testing.TB, conn *textproto.Conn, code int) string {
29 _, message, err := conn.ReadCodeLine(code)
30 if err != nil {
31 t.Errorf("%s ReadCodeLine error: %v", _fl(1), err)
32 }
33 return message
34 }
35
36 // runServer creates a TCP socket, runs a listening server, and returns the connection.
37 // The server exits when the Conn is closed.
38 func runServer(t *testing.T, server Server) net.Listener {
39 l, err := net.Listen("tcp", "localhost:0")
40 if err != nil {
41 t.Fatal(err)
42 return nil
43 }
44
45 go func() {
46 for {
47 conn, err := l.Accept()
48 if err != nil {
49 return
50 }
51 go AcceptConnection(conn, server, zap.New(zap.NullEncoder()))
52 }
53 }()
54
55 return l
56 }
57
58 type testServer struct {
59 EmptyServerCallbacks
60 blockList []string
61 }
62
63 func (s *testServer) Name() string {
64 return "Test-Server"
65 }
66
67 func (s *testServer) VerifyAddress(addr mail.Address) ReplyLine {
68 for _, block := range s.blockList {
69 if block == addr.Address {
70 return ReplyBadMailbox
71 }
72 }
73 return ReplyOK
74 }
75
76 func createClient(t *testing.T, addr net.Addr) *textproto.Conn {
77 conn, err := textproto.Dial(addr.Network(), addr.String())
78 if err != nil {
79 t.Fatal(err)
80 return nil
81 }
82 return conn
83 }
84
85 type requestResponse struct {
86 request string
87 responseCode int
88 handler func(testing.TB, *textproto.Conn)
89 }
90
91 func runTableTest(t testing.TB, conn *textproto.Conn, seq []requestResponse) {
92 for i, rr := range seq {
93 t.Logf("%s case %d", _fl(1), i)
94 ok(t, conn.PrintfLine(rr.request))
95 if rr.handler != nil {
96 rr.handler(t, conn)
97 } else {
98 readCodeLine(t, conn, rr.responseCode)
99 }
100 }
101 }
102
103 // RFC 5321 ยง D.1
104 func TestScenarioTypical(t *testing.T) {
105 s := testServer{
106 blockList: []string{"Green@foo.com"},
107 }
108 l := runServer(t, &s)
109 defer l.Close()
110
111 conn := createClient(t, l.Addr())
112
113 message := readCodeLine(t, conn, 220)
114 if !strings.HasPrefix(message, s.Name()) {
115 t.Errorf("Greeting does not have server name, got %q", message)
116 }
117
118 greet := "greeting.TestScenarioTypical"
119 ok(t, conn.PrintfLine("EHLO "+greet))
120
121 _, message, err := conn.ReadResponse(250)
122 ok(t, err)
123 if !strings.Contains(message, greet) {
124 t.Errorf("EHLO response does not contain greeting, got %q", message)
125 }
126
127 ok(t, conn.PrintfLine("MAIL FROM:<Smith@bar.com>"))
128 readCodeLine(t, conn, 250)
129
130 ok(t, conn.PrintfLine("RCPT TO:<Jones@foo.com>"))
131 readCodeLine(t, conn, 250)
132
133 ok(t, conn.PrintfLine("RCPT TO:<Green@foo.com>"))
134 readCodeLine(t, conn, 550)
135
136 ok(t, conn.PrintfLine("RCPT TO:<Brown@foo.com>"))
137 readCodeLine(t, conn, 250)
138
139 ok(t, conn.PrintfLine("DATA"))
140 readCodeLine(t, conn, 354)
141
142 ok(t, conn.PrintfLine("Blah blah blah..."))
143 ok(t, conn.PrintfLine("...etc. etc. etc."))
144 ok(t, conn.PrintfLine("."))
145 readCodeLine(t, conn, 250)
146
147 ok(t, conn.PrintfLine("QUIT"))
148 readCodeLine(t, conn, 221)
149 }
150
151 func TestVerifyAddress(t *testing.T) {
152 s := testServer{
153 blockList: []string{"banned@test.mail"},
154 }
155 l := runServer(t, &s)
156 defer l.Close()
157
158 conn := createClient(t, l.Addr())
159 readCodeLine(t, conn, 220)
160
161 runTableTest(t, conn, []requestResponse{
162 {"EHLO test", 0, func(t testing.TB, conn *textproto.Conn) { conn.ReadResponse(250) }},
163 {"VRFY banned@test.mail", 252, nil},
164 {"VRFY allowed@test.mail", 252, nil},
165 {"MAIL FROM:<sender@example.com>", 250, nil},
166 {"RCPT TO:<banned@test.mail>", 550, nil},
167 {"QUIT", 221, nil},
168 })
169 }
170
171 func TestBadAddress(t *testing.T) {
172 l := runServer(t, &testServer{})
173 defer l.Close()
174
175 conn := createClient(t, l.Addr())
176 readCodeLine(t, conn, 220)
177
178 runTableTest(t, conn, []requestResponse{
179 {"EHLO test", 0, func(t testing.TB, conn *textproto.Conn) { conn.ReadResponse(250) }},
180 {"MAIL FROM:<sender>", 501, nil},
181 {"MAIL FROM:<sender@foo.com> SIZE=2163", 250, nil},
182 {"RCPT TO:<banned.net>", 501, nil},
183 {"QUIT", 221, nil},
184 })
185 }
186
187 func TestCaseSensitivty(t *testing.T) {
188 s := &testServer{}
189 l := runServer(t, s)
190 defer l.Close()
191
192 conn := createClient(t, l.Addr())
193 readCodeLine(t, conn, 220)
194
195 runTableTest(t, conn, []requestResponse{
196 {"nOoP", 250, nil},
197 {"ehLO test.TEST", 0, func(t testing.TB, conn *textproto.Conn) { conn.ReadResponse(250) }},
198 {"mail FROM:<sender@example.com>", 250, nil},
199 {"RcPT tO:<receive@mail.com>", 250, nil},
200 {"DATa", 0, func(t testing.TB, conn *textproto.Conn) {
201 readCodeLine(t, conn, 354)
202
203 ok(t, conn.PrintfLine("."))
204 readCodeLine(t, conn, 250)
205 }},
206 {"MAIL FR:", 501, nil},
207 {"QUiT", 221, nil},
208 })
209 }
210
211 func TestGetReceivedInfo(t *testing.T) {
212 conn := connection{
213 server: &testServer{},
214 remoteAddr: &net.IPAddr{net.IPv4(127, 0, 0, 1), ""},
215 }
216
217 now := time.Now()
218
219 const crlf = "\r\n"
220 const line1 = "Received: from remote.test. (localhost [127.0.0.1])" + crlf
221 const line2 = "by Test-Server (mailpopbox) with "
222 const msgId = "abcdef.hijk"
223 lineLast := now.Format(time.RFC1123Z) + crlf
224
225 type params struct {
226 ehlo string
227 esmtp bool
228 tls bool
229 address string
230 }
231
232 tests := []struct {
233 params params
234
235 expect []string
236 }{
237 {params{"remote.test.", true, false, "foo@bar.com"},
238 []string{line1,
239 line2 + "ESMTP id " + msgId + crlf,
240 "for <foo@bar.com>" + crlf,
241 "(using PLAINTEXT);" + crlf,
242 lineLast, ""}},
243 }
244
245 for _, test := range tests {
246 t.Logf("%#v", test.params)
247
248 conn.ehlo = test.params.ehlo
249 conn.esmtp = test.params.esmtp
250 //conn.tls = test.params.tls
251
252 envelope := Envelope{
253 RcptTo: []mail.Address{{"", test.params.address}},
254 Received: now,
255 ID: msgId,
256 }
257
258 actual := conn.getReceivedInfo(envelope)
259 actualLines := strings.SplitAfter(string(actual), crlf)
260
261 if len(actualLines) != len(test.expect) {
262 t.Errorf("wrong numbber of lines, expected %d, got %d", len(test.expect), len(actualLines))
263 continue
264 }
265
266 for i, line := range actualLines {
267 expect := test.expect[i]
268 if expect != strings.TrimLeft(line, " ") {
269 t.Errorf("Expected equal string %q, got %q", expect, line)
270 }
271 }
272 }
273
274 }