]>
src.bluestatic.org Git - mailpopbox.git/blob - smtp/conn_test.go
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 {
73 func (s
*testServer
) Name() string {
77 func (s
*testServer
) TLSConfig() *tls
.Config
{
81 func (s
*testServer
) VerifyAddress(addr mail
.Address
) ReplyLine
{
82 if DomainForAddress(addr
) != s
.domain
{
83 return ReplyBadMailbox
85 for _
, block
:= range s
.blockList
{
86 if strings
.ToLower(block
) == addr
.Address
{
87 return ReplyBadMailbox
93 func (s
*testServer
) Authenticate(authz
, authc
, passwd
string) bool {
94 return s
.userAuth
.authz
== authz
&&
95 s
.userAuth
.authc
== authc
&&
96 s
.userAuth
.passwd
== passwd
99 func (s
*testServer
) RelayMessage(en Envelope
) {
100 s
.relayed
= append(s
.relayed
, en
)
103 func createClient(t
*testing
.T
, addr net
.Addr
) *textproto
.Conn
{
104 conn
, err
:= textproto
.Dial(addr
.Network(), addr
.String())
112 type requestResponse
struct {
115 handler
func(testing
.TB
, *textproto
.Conn
)
118 func runTableTest(t testing
.TB
, conn
*textproto
.Conn
, seq
[]requestResponse
) {
119 for i
, rr
:= range seq
{
120 t
.Logf("%s case %d", _fl(1), i
)
121 ok(t
, conn
.PrintfLine(rr
.request
))
122 if rr
.handler
!= nil {
125 readCodeLine(t
, conn
, rr
.responseCode
)
131 func TestScenarioTypical(t
*testing
.T
) {
134 blockList
: []string{"Green@foo.com"},
136 l
:= runServer(t
, &s
)
139 conn
:= createClient(t
, l
.Addr())
141 message
:= readCodeLine(t
, conn
, 220)
142 if !strings
.HasPrefix(message
, s
.Name()) {
143 t
.Errorf("Greeting does not have server name, got %q", message
)
146 greet
:= "greeting.TestScenarioTypical"
147 ok(t
, conn
.PrintfLine("EHLO "+greet
))
149 _
, message
, err
:= conn
.ReadResponse(250)
151 if !strings
.Contains(message
, greet
) {
152 t
.Errorf("EHLO response does not contain greeting, got %q", message
)
155 ok(t
, conn
.PrintfLine("MAIL FROM:<Smith@bar.com>"))
156 readCodeLine(t
, conn
, 250)
158 ok(t
, conn
.PrintfLine("RCPT TO:<Jones@foo.com>"))
159 readCodeLine(t
, conn
, 250)
161 ok(t
, conn
.PrintfLine("RCPT TO:<Green@foo.com>"))
162 readCodeLine(t
, conn
, 550)
164 ok(t
, conn
.PrintfLine("RCPT TO:<Brown@foo.com>"))
165 readCodeLine(t
, conn
, 250)
167 ok(t
, conn
.PrintfLine("DATA"))
168 readCodeLine(t
, conn
, 354)
170 ok(t
, conn
.PrintfLine("Blah blah blah..."))
171 ok(t
, conn
.PrintfLine("...etc. etc. etc."))
172 ok(t
, conn
.PrintfLine("."))
173 readCodeLine(t
, conn
, 250)
175 ok(t
, conn
.PrintfLine("QUIT"))
176 readCodeLine(t
, conn
, 221)
179 func TestVerifyAddress(t
*testing
.T
) {
182 blockList
: []string{"banned@test.mail"},
184 l
:= runServer(t
, &s
)
187 conn
:= createClient(t
, l
.Addr())
188 readCodeLine(t
, conn
, 220)
190 runTableTest(t
, conn
, []requestResponse
{
191 {"EHLO test", 0, func(t testing
.TB
, conn
*textproto
.Conn
) { conn
.ReadResponse(250) }},
192 {"VRFY banned@test.mail", 252, nil},
193 {"VRFY allowed@test.mail", 252, nil},
194 {"MAIL FROM:<sender@example.com>", 250, nil},
195 {"RCPT TO:<banned@test.mail>", 550, nil},
200 func TestBadAddress(t
*testing
.T
) {
201 l
:= runServer(t
, &testServer
{})
204 conn
:= createClient(t
, l
.Addr())
205 readCodeLine(t
, conn
, 220)
207 runTableTest(t
, conn
, []requestResponse
{
208 {"EHLO test", 0, func(t testing
.TB
, conn
*textproto
.Conn
) { conn
.ReadResponse(250) }},
209 {"MAIL FROM:<sender>", 501, nil},
210 {"MAIL FROM:<sender@foo.com> SIZE=2163", 250, nil},
211 {"RCPT TO:<banned.net>", 501, nil},
216 func TestCaseSensitivty(t
*testing
.T
) {
219 blockList
: []string{"reject@mail.com"},
224 conn
:= createClient(t
, l
.Addr())
225 readCodeLine(t
, conn
, 220)
227 runTableTest(t
, conn
, []requestResponse
{
229 {"ehLO test.TEST", 0, func(t testing
.TB
, conn
*textproto
.Conn
) { conn
.ReadResponse(250) }},
230 {"mail FROM:<sender@example.com>", 250, nil},
231 {"RcPT tO:<receive@mail.com>", 250, nil},
232 {"RCPT TO:<reject@MAIL.com>", 550, nil},
233 {"RCPT TO:<reject@mail.com>", 550, nil},
234 {"DATa", 0, func(t testing
.TB
, conn
*textproto
.Conn
) {
235 readCodeLine(t
, conn
, 354)
237 ok(t
, conn
.PrintfLine("."))
238 readCodeLine(t
, conn
, 250)
240 {"MAIL FR:", 501, nil},
245 func TestGetReceivedInfo(t
*testing
.T
) {
247 server
: &testServer
{},
248 remoteAddr
: &net
.IPAddr
{net
.IPv4(127, 0, 0, 1), ""},
254 const line1
= "Received: from remote.test. (localhost [127.0.0.1])" + crlf
255 const line2
= "by Test-Server (mailpopbox) with "
256 const msgId
= "abcdef.hijk"
257 lineLast
:= now
.Format(time
.RFC1123Z
) + crlf
271 {params
{"remote.test.", true, false, "foo@bar.com"},
273 line2
+ "ESMTP id " + msgId
+ crlf
,
274 "for <foo@bar.com>" + crlf
,
275 "(using PLAINTEXT);" + crlf
,
279 for _
, test
:= range tests
{
280 t
.Logf("%#v", test
.params
)
282 conn
.ehlo
= test
.params
.ehlo
283 conn
.esmtp
= test
.params
.esmtp
284 //conn.tls = test.params.tls
286 envelope
:= Envelope
{
287 RcptTo
: []mail
.Address
{{"", test
.params
.address
}},
292 actual
:= conn
.getReceivedInfo(envelope
)
293 actualLines
:= strings
.SplitAfter(string(actual
), crlf
)
295 if len(actualLines
) != len(test
.expect
) {
296 t
.Errorf("wrong numbber of lines, expected %d, got %d", len(test
.expect
), len(actualLines
))
300 for i
, line
:= range actualLines
{
301 expect
:= test
.expect
[i
]
302 if expect
!= strings
.TrimLeft(line
, " ") {
303 t
.Errorf("Expected equal string %q, got %q", expect
, line
)
310 func getTLSConfig(t
*testing
.T
) *tls
.Config
{
311 cert
, err
:= tls
.LoadX509KeyPair("../testtls/domain.crt", "../testtls/domain.key")
317 ServerName
: "localhost",
318 Certificates
: []tls
.Certificate
{cert
},
319 InsecureSkipVerify
: true,
323 func setupTLSClient(t
*testing
.T
, addr net
.Addr
) *textproto
.Conn
{
324 nc
, err
:= net
.Dial(addr
.Network(), addr
.String())
327 conn
:= textproto
.NewConn(nc
)
328 readCodeLine(t
, conn
, 220)
330 ok(t
, conn
.PrintfLine("EHLO test-tls"))
331 _
, resp
, err
:= conn
.ReadResponse(250)
333 if !strings
.Contains(resp
, "STARTTLS\n") {
334 t
.Errorf("STARTTLS not advertised")
337 ok(t
, conn
.PrintfLine("STARTTLS"))
338 readCodeLine(t
, conn
, 220)
340 tc
:= tls
.Client(nc
, getTLSConfig(t
))
344 conn
= textproto
.NewConn(tc
)
346 ok(t
, conn
.PrintfLine("EHLO test-tls-started"))
347 _
, resp
, err
= conn
.ReadResponse(250)
349 if strings
.Contains(resp
, "STARTTLS\n") {
350 t
.Errorf("STARTTLS advertised when already started")
356 func b64enc(s
string) string {
357 return string(base64
.StdEncoding
.EncodeToString([]byte(s
)))
360 func TestTLS(t
*testing
.T
) {
361 l
:= runServer(t
, &testServer
{tlsConfig
: getTLSConfig(t
)})
364 setupTLSClient(t
, l
.Addr())
367 func TestAuthWithoutTLS(t
*testing
.T
) {
368 l
:= runServer(t
, &testServer
{})
371 conn
:= createClient(t
, l
.Addr())
372 readCodeLine(t
, conn
, 220)
374 ok(t
, conn
.PrintfLine("EHLO test"))
375 _
, resp
, err
:= conn
.ReadResponse(250)
378 if strings
.Contains(resp
, "AUTH") {
379 t
.Errorf("AUTH should not be advertised over plaintext")
383 func TestAuth(t
*testing
.T
) {
384 l
:= runServer(t
, &testServer
{
385 tlsConfig
: getTLSConfig(t
),
394 conn
:= setupTLSClient(t
, l
.Addr())
396 runTableTest(t
, conn
, []requestResponse
{
398 {"AUTH OAUTHBEARER", 504, nil},
399 {"AUTH PLAIN", 334, nil},
400 {b64enc("abc\x00def\x00ghf"), 535, nil},
401 {"AUTH PLAIN", 334, nil},
402 {b64enc("\x00"), 501, nil},
403 {"AUTH PLAIN", 334, nil},
404 {"this isn't base 64", 501, nil},
405 {"AUTH PLAIN", 334, nil},
406 {b64enc("-authz-\x00-authc-\x00goats"), 250, nil},
407 {"AUTH PLAIN", 503, nil}, // already authenticated
412 func TestRelayRequiresAuth(t
*testing
.T
) {
413 l
:= runServer(t
, &testServer
{
414 domain
: "example.com",
415 tlsConfig
: getTLSConfig(t
),
418 authc
: "mailbox@example.com",
424 conn
:= setupTLSClient(t
, l
.Addr())
426 runTableTest(t
, conn
, []requestResponse
{
427 {"MAIL FROM:<apples@example.com>", 550, nil},
428 {"MAIL FROM:<mailbox@example.com>", 550, nil},
429 {"AUTH PLAIN", 334, nil},
430 {b64enc("\x00mailbox@example.com\x00test"), 250, nil},
431 {"MAIL FROM:<mailbox@example.com>", 250, nil},
435 func setupRelayTest(t
*testing
.T
) (server
*testServer
, l net
.Listener
, conn
*textproto
.Conn
) {
436 server
= &testServer
{
437 domain
: "example.com",
438 tlsConfig
: getTLSConfig(t
),
441 authc
: "mailbox@example.com",
445 l
= runServer(t
, server
)
446 conn
= setupTLSClient(t
, l
.Addr())
447 runTableTest(t
, conn
, []requestResponse
{
448 {"AUTH PLAIN", 334, nil},
449 {b64enc("\x00mailbox@example.com\x00test"), 250, nil},
454 func TestBasicRelay(t
*testing
.T
) {
455 server
, l
, conn
:= setupRelayTest(t
)
458 runTableTest(t
, conn
, []requestResponse
{
459 {"MAIL FROM:<mailbox@example.com>", 250, nil},
460 {"RCPT TO:<dest@another.net>", 250, nil},
461 {"DATA", 354, func(t testing
.TB
, conn
*textproto
.Conn
) {
462 readCodeLine(t
, conn
, 354)
464 ok(t
, conn
.PrintfLine("From: <mailbox@example.com>"))
465 ok(t
, conn
.PrintfLine("To: <dest@example.com>"))
466 ok(t
, conn
.PrintfLine("Subject: Basic relay\n"))
467 ok(t
, conn
.PrintfLine("This is a basic relay message"))
468 ok(t
, conn
.PrintfLine("."))
469 readCodeLine(t
, conn
, 250)
473 if len(server
.relayed
) != 1 {
474 t
.Errorf("Expected 1 relayed message, got %d", len(server
.relayed
))
478 func TestNoInternalRelays(t
*testing
.T
) {
479 _
, l
, conn
:= setupRelayTest(t
)
482 runTableTest(t
, conn
, []requestResponse
{
483 {"MAIL FROM:<mailbox@example.com>", 250, nil},
484 {"RCPT TO:<valid@dest.xyz>", 250, nil},
485 {"RCPT TO:<dest@example.com>", 550, nil},
486 {"RCPT TO:<mailbox@example.com>", 550, nil},
490 func TestSendAsRelay(t
*testing
.T
) {
491 server
, l
, conn
:= setupRelayTest(t
)
494 runTableTest(t
, conn
, []requestResponse
{
495 {"MAIL FROM:<mailbox@example.com>", 250, nil},
496 {"RCPT TO:<valid@dest.xyz>", 250, nil},
497 {"RCPT TO:<sendas+source@example.com>", 250, nil},
498 {"RCPT TO:<mailbox@example.com>", 550, nil},
499 {"DATA", 354, func(t testing
.TB
, conn
*textproto
.Conn
) {
500 readCodeLine(t
, conn
, 354)
502 ok(t
, conn
.PrintfLine("From: <mailbox@example.com>"))
503 ok(t
, conn
.PrintfLine("To: <valid@dest.xyz>"))
504 ok(t
, conn
.PrintfLine("Subject: Send-as relay\n"))
505 ok(t
, conn
.PrintfLine("We've switched the senders!"))
506 ok(t
, conn
.PrintfLine("."))
507 readCodeLine(t
, conn
, 250)
511 if len(server
.relayed
) != 1 {
512 t
.Errorf("Expected 1 relayed message, got %d", len(server
.relayed
))
515 replaced
:= "source@example.com"
516 original
:= "mailbox@example.com"
518 en
:= server
.relayed
[0]
519 if en
.MailFrom
.Address
!= replaced
{
520 t
.Errorf("Expected mail to be from %q, got %q", replaced
, en
.MailFrom
.Address
)
523 if len(en
.RcptTo
) != 1 {
524 t
.Errorf("Expected 1 recipient, got %d", len(en
.RcptTo
))
526 if en
.RcptTo
[0].Address
!= "valid@dest.xyz" {
527 t
.Errorf("Unexpected RcptTo %q", en
.RcptTo
[0].Address
)
530 msg
:= string(en
.Data
)
532 if strings
.Index(msg
, original
) != -1 {
533 t
.Errorf("Should not find %q in message %q", original
, msg
)
536 if strings
.Index(msg
, "\nFrom: <source@example.com>\n") == -1 {
537 t
.Errorf("Could not find From: header in message %q", msg
)
541 func TestSendMultipleRelay(t
*testing
.T
) {
542 server
, l
, conn
:= setupRelayTest(t
)
545 runTableTest(t
, conn
, []requestResponse
{
546 {"MAIL FROM:<mailbox@example.com>", 250, nil},
547 {"RCPT TO:<valid@dest.xyz>", 250, nil},
548 {"RCPT TO:<sendas+source@example.com>", 250, nil},
549 {"RCPT TO:<another@dest.org>", 250, nil},
550 {"DATA", 354, func(t testing
.TB
, conn
*textproto
.Conn
) {
551 readCodeLine(t
, conn
, 354)
553 ok(t
, conn
.PrintfLine("To: Cindy <valid@dest.xyz>, Sam <another@dest.org>"))
554 ok(t
, conn
.PrintfLine("From: <mailbox@example.com>"))
555 ok(t
, conn
.PrintfLine("Subject: Two destinations\n"))
556 ok(t
, conn
.PrintfLine("And we've switched the senders!"))
557 ok(t
, conn
.PrintfLine("."))
558 readCodeLine(t
, conn
, 250)
562 if len(server
.relayed
) != 1 {
563 t
.Errorf("Expected 1 relayed message, got %d", len(server
.relayed
))
566 replaced
:= "source@example.com"
567 original
:= "mailbox@example.com"
569 en
:= server
.relayed
[0]
570 if en
.MailFrom
.Address
!= replaced
{
571 t
.Errorf("Expected mail to be from %q, got %q", replaced
, en
.MailFrom
.Address
)
574 if len(en
.RcptTo
) != 2 {
575 t
.Errorf("Expected 2 recipient, got %d", len(en
.RcptTo
))
577 if en
.RcptTo
[0].Address
!= "valid@dest.xyz" {
578 t
.Errorf("Unexpected RcptTo %q", en
.RcptTo
[0].Address
)
581 msg
:= string(en
.Data
)
583 if strings
.Index(msg
, original
) != -1 {
584 t
.Errorf("Should not find %q in message %q", original
, msg
)
587 if strings
.Index(msg
, "\nFrom: <source@example.com>\n") == -1 {
588 t
.Errorf("Could not find From: header in message %q", msg
)