]>
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 {
72 func (s
*testServer
) Name() string {
76 func (s
*testServer
) TLSConfig() *tls
.Config
{
80 func (s
*testServer
) VerifyAddress(addr mail
.Address
) ReplyLine
{
81 if DomainForAddress(addr
) != s
.domain
{
82 return ReplyBadMailbox
84 for _
, block
:= range s
.blockList
{
85 if strings
.ToLower(block
) == addr
.Address
{
86 return ReplyBadMailbox
92 func (s
*testServer
) Authenticate(authz
, authc
, passwd
string) bool {
93 return s
.userAuth
.authz
== authz
&&
94 s
.userAuth
.authc
== authc
&&
95 s
.userAuth
.passwd
== passwd
98 func createClient(t
*testing
.T
, addr net
.Addr
) *textproto
.Conn
{
99 conn
, err
:= textproto
.Dial(addr
.Network(), addr
.String())
107 type requestResponse
struct {
110 handler
func(testing
.TB
, *textproto
.Conn
)
113 func runTableTest(t testing
.TB
, conn
*textproto
.Conn
, seq
[]requestResponse
) {
114 for i
, rr
:= range seq
{
115 t
.Logf("%s case %d", _fl(1), i
)
116 ok(t
, conn
.PrintfLine(rr
.request
))
117 if rr
.handler
!= nil {
120 readCodeLine(t
, conn
, rr
.responseCode
)
126 func TestScenarioTypical(t
*testing
.T
) {
129 blockList
: []string{"Green@foo.com"},
131 l
:= runServer(t
, &s
)
134 conn
:= createClient(t
, l
.Addr())
136 message
:= readCodeLine(t
, conn
, 220)
137 if !strings
.HasPrefix(message
, s
.Name()) {
138 t
.Errorf("Greeting does not have server name, got %q", message
)
141 greet
:= "greeting.TestScenarioTypical"
142 ok(t
, conn
.PrintfLine("EHLO "+greet
))
144 _
, message
, err
:= conn
.ReadResponse(250)
146 if !strings
.Contains(message
, greet
) {
147 t
.Errorf("EHLO response does not contain greeting, got %q", message
)
150 ok(t
, conn
.PrintfLine("MAIL FROM:<Smith@bar.com>"))
151 readCodeLine(t
, conn
, 250)
153 ok(t
, conn
.PrintfLine("RCPT TO:<Jones@foo.com>"))
154 readCodeLine(t
, conn
, 250)
156 ok(t
, conn
.PrintfLine("RCPT TO:<Green@foo.com>"))
157 readCodeLine(t
, conn
, 550)
159 ok(t
, conn
.PrintfLine("RCPT TO:<Brown@foo.com>"))
160 readCodeLine(t
, conn
, 250)
162 ok(t
, conn
.PrintfLine("DATA"))
163 readCodeLine(t
, conn
, 354)
165 ok(t
, conn
.PrintfLine("Blah blah blah..."))
166 ok(t
, conn
.PrintfLine("...etc. etc. etc."))
167 ok(t
, conn
.PrintfLine("."))
168 readCodeLine(t
, conn
, 250)
170 ok(t
, conn
.PrintfLine("QUIT"))
171 readCodeLine(t
, conn
, 221)
174 func TestVerifyAddress(t
*testing
.T
) {
177 blockList
: []string{"banned@test.mail"},
179 l
:= runServer(t
, &s
)
182 conn
:= createClient(t
, l
.Addr())
183 readCodeLine(t
, conn
, 220)
185 runTableTest(t
, conn
, []requestResponse
{
186 {"EHLO test", 0, func(t testing
.TB
, conn
*textproto
.Conn
) { conn
.ReadResponse(250) }},
187 {"VRFY banned@test.mail", 252, nil},
188 {"VRFY allowed@test.mail", 252, nil},
189 {"MAIL FROM:<sender@example.com>", 250, nil},
190 {"RCPT TO:<banned@test.mail>", 550, nil},
195 func TestBadAddress(t
*testing
.T
) {
196 l
:= runServer(t
, &testServer
{})
199 conn
:= createClient(t
, l
.Addr())
200 readCodeLine(t
, conn
, 220)
202 runTableTest(t
, conn
, []requestResponse
{
203 {"EHLO test", 0, func(t testing
.TB
, conn
*textproto
.Conn
) { conn
.ReadResponse(250) }},
204 {"MAIL FROM:<sender>", 501, nil},
205 {"MAIL FROM:<sender@foo.com> SIZE=2163", 250, nil},
206 {"RCPT TO:<banned.net>", 501, nil},
211 func TestCaseSensitivty(t
*testing
.T
) {
214 blockList
: []string{"reject@mail.com"},
219 conn
:= createClient(t
, l
.Addr())
220 readCodeLine(t
, conn
, 220)
222 runTableTest(t
, conn
, []requestResponse
{
224 {"ehLO test.TEST", 0, func(t testing
.TB
, conn
*textproto
.Conn
) { conn
.ReadResponse(250) }},
225 {"mail FROM:<sender@example.com>", 250, nil},
226 {"RcPT tO:<receive@mail.com>", 250, nil},
227 {"RCPT TO:<reject@MAIL.com>", 550, nil},
228 {"RCPT TO:<reject@mail.com>", 550, nil},
229 {"DATa", 0, func(t testing
.TB
, conn
*textproto
.Conn
) {
230 readCodeLine(t
, conn
, 354)
232 ok(t
, conn
.PrintfLine("."))
233 readCodeLine(t
, conn
, 250)
235 {"MAIL FR:", 501, nil},
240 func TestGetReceivedInfo(t
*testing
.T
) {
242 server
: &testServer
{},
243 remoteAddr
: &net
.IPAddr
{net
.IPv4(127, 0, 0, 1), ""},
249 const line1
= "Received: from remote.test. (localhost [127.0.0.1])" + crlf
250 const line2
= "by Test-Server (mailpopbox) with "
251 const msgId
= "abcdef.hijk"
252 lineLast
:= now
.Format(time
.RFC1123Z
) + crlf
266 {params
{"remote.test.", true, false, "foo@bar.com"},
268 line2
+ "ESMTP id " + msgId
+ crlf
,
269 "for <foo@bar.com>" + crlf
,
270 "(using PLAINTEXT);" + crlf
,
274 for _
, test
:= range tests
{
275 t
.Logf("%#v", test
.params
)
277 conn
.ehlo
= test
.params
.ehlo
278 conn
.esmtp
= test
.params
.esmtp
279 //conn.tls = test.params.tls
281 envelope
:= Envelope
{
282 RcptTo
: []mail
.Address
{{"", test
.params
.address
}},
287 actual
:= conn
.getReceivedInfo(envelope
)
288 actualLines
:= strings
.SplitAfter(string(actual
), crlf
)
290 if len(actualLines
) != len(test
.expect
) {
291 t
.Errorf("wrong numbber of lines, expected %d, got %d", len(test
.expect
), len(actualLines
))
295 for i
, line
:= range actualLines
{
296 expect
:= test
.expect
[i
]
297 if expect
!= strings
.TrimLeft(line
, " ") {
298 t
.Errorf("Expected equal string %q, got %q", expect
, line
)
305 func getTLSConfig(t
*testing
.T
) *tls
.Config
{
306 cert
, err
:= tls
.LoadX509KeyPair("../testtls/domain.crt", "../testtls/domain.key")
312 ServerName
: "localhost",
313 Certificates
: []tls
.Certificate
{cert
},
314 InsecureSkipVerify
: true,
318 func setupTLSClient(t
*testing
.T
, addr net
.Addr
) *textproto
.Conn
{
319 nc
, err
:= net
.Dial(addr
.Network(), addr
.String())
322 conn
:= textproto
.NewConn(nc
)
323 readCodeLine(t
, conn
, 220)
325 ok(t
, conn
.PrintfLine("EHLO test-tls"))
326 _
, resp
, err
:= conn
.ReadResponse(250)
328 if !strings
.Contains(resp
, "STARTTLS\n") {
329 t
.Errorf("STARTTLS not advertised")
332 ok(t
, conn
.PrintfLine("STARTTLS"))
333 readCodeLine(t
, conn
, 220)
335 tc
:= tls
.Client(nc
, getTLSConfig(t
))
339 conn
= textproto
.NewConn(tc
)
341 ok(t
, conn
.PrintfLine("EHLO test-tls-started"))
342 _
, resp
, err
= conn
.ReadResponse(250)
344 if strings
.Contains(resp
, "STARTTLS\n") {
345 t
.Errorf("STARTTLS advertised when already started")
351 func TestTLS(t
*testing
.T
) {
352 l
:= runServer(t
, &testServer
{tlsConfig
: getTLSConfig(t
)})
355 setupTLSClient(t
, l
.Addr())
358 func TestAuthWithoutTLS(t
*testing
.T
) {
359 l
:= runServer(t
, &testServer
{})
362 conn
:= createClient(t
, l
.Addr())
363 readCodeLine(t
, conn
, 220)
365 ok(t
, conn
.PrintfLine("EHLO test"))
366 _
, resp
, err
:= conn
.ReadResponse(250)
369 if strings
.Contains(resp
, "AUTH") {
370 t
.Errorf("AUTH should not be advertised over plaintext")
374 func TestAuth(t
*testing
.T
) {
375 l
:= runServer(t
, &testServer
{
376 tlsConfig
: getTLSConfig(t
),
385 conn
:= setupTLSClient(t
, l
.Addr())
387 b64enc
:= func(s
string) string {
388 return string(base64
.StdEncoding
.EncodeToString([]byte(s
)))
391 runTableTest(t
, conn
, []requestResponse
{
393 {"AUTH OAUTHBEARER", 504, nil},
394 {"AUTH PLAIN", 334, nil},
395 {b64enc("abc\x00def\x00ghf"), 535, nil},
396 {"AUTH PLAIN", 334, nil},
397 {b64enc("\x00"), 501, nil},
398 {"AUTH PLAIN", 334, nil},
399 {"this isn't base 64", 501, nil},
400 {"AUTH PLAIN", 334, nil},
401 {b64enc("-authz-\x00-authc-\x00goats"), 250, nil},
402 {"AUTH PLAIN", 503, nil}, // already authenticated