16 "github.com/uber-go/zap"
19 func _fl(depth int) string {
20 _, file, line, _ := runtime.Caller(depth + 1)
21 return fmt.Sprintf("[%s:%d]", filepath.Base(file), line)
24 func ok(t testing.TB, err error) {
26 t.Errorf("%s unexpected error: %v", _fl(1), err)
30 func readCodeLine(t testing.TB, conn *textproto.Conn, code int) string {
31 _, message, err := conn.ReadCodeLine(code)
33 t.Errorf("%s ReadCodeLine error: %v", _fl(1), err)
38 // runServer creates a TCP socket, runs a listening server, and returns the connection.
39 // The server exits when the Conn is closed.
40 func runServer(t *testing.T, server Server) net.Listener {
41 l, err := net.Listen("tcp", "localhost:0")
49 conn, err := l.Accept()
53 go AcceptConnection(conn, server, zap.New(zap.NullEncoder()))
60 type userAuth struct {
61 authz, authc, passwd string
64 type testServer struct {
71 func (s *testServer) Name() string {
75 func (s *testServer) TLSConfig() *tls.Config {
79 func (s *testServer) VerifyAddress(addr mail.Address) ReplyLine {
80 for _, block := range s.blockList {
81 if strings.ToLower(block) == addr.Address {
82 return ReplyBadMailbox
88 func (s *testServer) Authenticate(authz, authc, passwd string) bool {
89 return s.userAuth.authz == authz &&
90 s.userAuth.authc == authc &&
91 s.userAuth.passwd == passwd
94 func createClient(t *testing.T, addr net.Addr) *textproto.Conn {
95 conn, err := textproto.Dial(addr.Network(), addr.String())
103 type requestResponse struct {
106 handler func(testing.TB, *textproto.Conn)
109 func runTableTest(t testing.TB, conn *textproto.Conn, seq []requestResponse) {
110 for i, rr := range seq {
111 t.Logf("%s case %d", _fl(1), i)
112 ok(t, conn.PrintfLine(rr.request))
113 if rr.handler != nil {
116 readCodeLine(t, conn, rr.responseCode)
122 func TestScenarioTypical(t *testing.T) {
124 blockList: []string{"Green@foo.com"},
126 l := runServer(t, &s)
129 conn := createClient(t, l.Addr())
131 message := readCodeLine(t, conn, 220)
132 if !strings.HasPrefix(message, s.Name()) {
133 t.Errorf("Greeting does not have server name, got %q", message)
136 greet := "greeting.TestScenarioTypical"
137 ok(t, conn.PrintfLine("EHLO "+greet))
139 _, message, err := conn.ReadResponse(250)
141 if !strings.Contains(message, greet) {
142 t.Errorf("EHLO response does not contain greeting, got %q", message)
145 ok(t, conn.PrintfLine("MAIL FROM:<Smith@bar.com>"))
146 readCodeLine(t, conn, 250)
148 ok(t, conn.PrintfLine("RCPT TO:<Jones@foo.com>"))
149 readCodeLine(t, conn, 250)
151 ok(t, conn.PrintfLine("RCPT TO:<Green@foo.com>"))
152 readCodeLine(t, conn, 550)
154 ok(t, conn.PrintfLine("RCPT TO:<Brown@foo.com>"))
155 readCodeLine(t, conn, 250)
157 ok(t, conn.PrintfLine("DATA"))
158 readCodeLine(t, conn, 354)
160 ok(t, conn.PrintfLine("Blah blah blah..."))
161 ok(t, conn.PrintfLine("...etc. etc. etc."))
162 ok(t, conn.PrintfLine("."))
163 readCodeLine(t, conn, 250)
165 ok(t, conn.PrintfLine("QUIT"))
166 readCodeLine(t, conn, 221)
169 func TestVerifyAddress(t *testing.T) {
171 blockList: []string{"banned@test.mail"},
173 l := runServer(t, &s)
176 conn := createClient(t, l.Addr())
177 readCodeLine(t, conn, 220)
179 runTableTest(t, conn, []requestResponse{
180 {"EHLO test", 0, func(t testing.TB, conn *textproto.Conn) { conn.ReadResponse(250) }},
181 {"VRFY banned@test.mail", 252, nil},
182 {"VRFY allowed@test.mail", 252, nil},
183 {"MAIL FROM:<sender@example.com>", 250, nil},
184 {"RCPT TO:<banned@test.mail>", 550, nil},
189 func TestBadAddress(t *testing.T) {
190 l := runServer(t, &testServer{})
193 conn := createClient(t, l.Addr())
194 readCodeLine(t, conn, 220)
196 runTableTest(t, conn, []requestResponse{
197 {"EHLO test", 0, func(t testing.TB, conn *textproto.Conn) { conn.ReadResponse(250) }},
198 {"MAIL FROM:<sender>", 501, nil},
199 {"MAIL FROM:<sender@foo.com> SIZE=2163", 250, nil},
200 {"RCPT TO:<banned.net>", 501, nil},
205 func TestCaseSensitivty(t *testing.T) {
207 s.blockList = []string{"reject@mail.com"}
211 conn := createClient(t, l.Addr())
212 readCodeLine(t, conn, 220)
214 runTableTest(t, conn, []requestResponse{
216 {"ehLO test.TEST", 0, func(t testing.TB, conn *textproto.Conn) { conn.ReadResponse(250) }},
217 {"mail FROM:<sender@example.com>", 250, nil},
218 {"RcPT tO:<receive@mail.com>", 250, nil},
219 {"RCPT TO:<reject@MAIL.com>", 550, nil},
220 {"RCPT TO:<reject@mail.com>", 550, nil},
221 {"DATa", 0, func(t testing.TB, conn *textproto.Conn) {
222 readCodeLine(t, conn, 354)
224 ok(t, conn.PrintfLine("."))
225 readCodeLine(t, conn, 250)
227 {"MAIL FR:", 501, nil},
232 func TestGetReceivedInfo(t *testing.T) {
234 server: &testServer{},
235 remoteAddr: &net.IPAddr{net.IPv4(127, 0, 0, 1), ""},
241 const line1 = "Received: from remote.test. (localhost [127.0.0.1])" + crlf
242 const line2 = "by Test-Server (mailpopbox) with "
243 const msgId = "abcdef.hijk"
244 lineLast := now.Format(time.RFC1123Z) + crlf
258 {params{"remote.test.", true, false, "foo@bar.com"},
260 line2 + "ESMTP id " + msgId + crlf,
261 "for <foo@bar.com>" + crlf,
262 "(using PLAINTEXT);" + crlf,
266 for _, test := range tests {
267 t.Logf("%#v", test.params)
269 conn.ehlo = test.params.ehlo
270 conn.esmtp = test.params.esmtp
271 //conn.tls = test.params.tls
273 envelope := Envelope{
274 RcptTo: []mail.Address{{"", test.params.address}},
279 actual := conn.getReceivedInfo(envelope)
280 actualLines := strings.SplitAfter(string(actual), crlf)
282 if len(actualLines) != len(test.expect) {
283 t.Errorf("wrong numbber of lines, expected %d, got %d", len(test.expect), len(actualLines))
287 for i, line := range actualLines {
288 expect := test.expect[i]
289 if expect != strings.TrimLeft(line, " ") {
290 t.Errorf("Expected equal string %q, got %q", expect, line)
297 func getTLSConfig(t *testing.T) *tls.Config {
298 cert, err := tls.LoadX509KeyPair("../testtls/domain.crt", "../testtls/domain.key")
304 ServerName: "localhost",
305 Certificates: []tls.Certificate{cert},
306 InsecureSkipVerify: true,
310 func setupTLSClient(t *testing.T, addr net.Addr) *textproto.Conn {
311 nc, err := net.Dial(addr.Network(), addr.String())
314 conn := textproto.NewConn(nc)
315 readCodeLine(t, conn, 220)
317 ok(t, conn.PrintfLine("EHLO test-tls"))
318 _, resp, err := conn.ReadResponse(250)
320 if !strings.Contains(resp, "STARTTLS\n") {
321 t.Errorf("STARTTLS not advertised")
324 ok(t, conn.PrintfLine("STARTTLS"))
325 readCodeLine(t, conn, 220)
327 tc := tls.Client(nc, getTLSConfig(t))
331 conn = textproto.NewConn(tc)
333 ok(t, conn.PrintfLine("EHLO test-tls-started"))
334 _, resp, err = conn.ReadResponse(250)
336 if strings.Contains(resp, "STARTTLS\n") {
337 t.Errorf("STARTTLS advertised when already started")
343 func TestTLS(t *testing.T) {
344 l := runServer(t, &testServer{tlsConfig: getTLSConfig(t)})
347 setupTLSClient(t, l.Addr())
350 func TestAuthWithoutTLS(t *testing.T) {
351 l := runServer(t, &testServer{})
354 conn := createClient(t, l.Addr())
355 readCodeLine(t, conn, 220)
357 ok(t, conn.PrintfLine("EHLO test"))
358 _, resp, err := conn.ReadResponse(250)
361 if strings.Contains(resp, "AUTH") {
362 t.Errorf("AUTH should not be advertised over plaintext")
366 func TestAuth(t *testing.T) {
367 l := runServer(t, &testServer{
368 tlsConfig: getTLSConfig(t),
377 conn := setupTLSClient(t, l.Addr())
379 b64enc := func(s string) string {
380 return string(base64.StdEncoding.EncodeToString([]byte(s)))
383 runTableTest(t, conn, []requestResponse{
385 {"AUTH OAUTHBEARER", 504, nil},
386 {"AUTH PLAIN", 334, nil},
387 {b64enc("abc\x00def\x00ghf"), 535, nil},
388 {"AUTH PLAIN", 334, nil},
389 {b64enc("\x00"), 501, nil},
390 {"AUTH PLAIN", 334, nil},
391 {"this isn't base 64", 501, nil},
392 {"AUTH PLAIN", 334, nil},
393 {b64enc("-authz-\x00-authc-\x00goats"), 250, nil},
394 {"AUTH PLAIN", 503, nil}, // already authenticated