1 // Copyright 2010 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
20 type authTest
struct {
27 var authTests
= []authTest
{
28 {PlainAuth("", "user", "pass", "testserver"), []string{}, "PLAIN", []string{"\x00user\x00pass"}},
29 {PlainAuth("foo", "bar", "baz", "testserver"), []string{}, "PLAIN", []string{"foo\x00bar\x00baz"}},
30 {CRAMMD5Auth("user", "pass"), []string{"<123456.1322876914@testserver>"}, "CRAM-MD5", []string{"", "user 287eb355114cf5c471c26a875f1ca4ae"}},
33 func TestAuth(t
*testing
.T
) {
35 for i
, test
:= range authTests
{
36 name
, resp
, err
:= test
.auth
.Start(&ServerInfo
{"testserver", true, nil})
37 if name
!= test
.name
{
38 t
.Errorf("#%d got name %s, expected %s", i
, name
, test
.name
)
40 if !bytes
.Equal(resp
, []byte(test
.responses
[0])) {
41 t
.Errorf("#%d got response %s, expected %s", i
, resp
, test
.responses
[0])
44 t
.Errorf("#%d error: %s", i
, err
)
46 for j
:= range test
.challenges
{
47 challenge
:= []byte(test
.challenges
[j
])
48 expected
:= []byte(test
.responses
[j
+1])
49 resp
, err
:= test
.auth
.Next(challenge
, true)
51 t
.Errorf("#%d error: %s", i
, err
)
54 if !bytes
.Equal(resp
, expected
) {
55 t
.Errorf("#%d got %s, expected %s", i
, resp
, expected
)
62 func TestAuthPlain(t
*testing
.T
) {
63 auth
:= PlainAuth("foo", "bar", "baz", "servername")
70 server
: &ServerInfo
{Name
: "servername", TLS
: true},
73 // Okay; explicitly advertised by server.
74 server
: &ServerInfo
{Name
: "servername", Auth
: []string{"PLAIN"}},
77 server
: &ServerInfo
{Name
: "servername", Auth
: []string{"CRAM-MD5"}},
78 err
: "unencrypted connection",
81 server
: &ServerInfo
{Name
: "attacker", TLS
: true},
82 err
: "wrong host name",
85 for i
, tt
:= range tests
{
86 _
, _
, err
:= auth
.Start(tt
.server
)
92 t
.Errorf("%d. got error = %q; want %q", i
, got
, tt
.err
)
101 func (f faker
) Close() error
{ return nil }
102 func (f faker
) LocalAddr() net
.Addr
{ return nil }
103 func (f faker
) RemoteAddr() net
.Addr
{ return nil }
104 func (f faker
) SetDeadline(time
.Time
) error
{ return nil }
105 func (f faker
) SetReadDeadline(time
.Time
) error
{ return nil }
106 func (f faker
) SetWriteDeadline(time
.Time
) error
{ return nil }
108 func TestBasic(t
*testing
.T
) {
109 server
:= strings
.Join(strings
.Split(basicServer
, "\n"), "\r\n")
110 client
:= strings
.Join(strings
.Split(basicClient
, "\n"), "\r\n")
112 var cmdbuf bytes
.Buffer
113 bcmdbuf
:= bufio
.NewWriter(&cmdbuf
)
115 fake
.ReadWriter
= bufio
.NewReadWriter(bufio
.NewReader(strings
.NewReader(server
)), bcmdbuf
)
116 c
:= &Client
{Text
: textproto
.NewConn(fake
), localName
: "localhost"}
118 if err
:= c
.helo(); err
!= nil {
119 t
.Fatalf("HELO failed: %s", err
)
121 if err
:= c
.ehlo(); err
== nil {
122 t
.Fatalf("Expected first EHLO to fail")
124 if err
:= c
.ehlo(); err
!= nil {
125 t
.Fatalf("Second EHLO failed: %s", err
)
129 if ok
, args
:= c
.Extension("aUtH"); !ok || args
!= "LOGIN PLAIN" {
130 t
.Fatalf("Expected AUTH supported")
132 if ok
, _
:= c
.Extension("DSN"); ok
{
133 t
.Fatalf("Shouldn't support DSN")
136 if err
:= c
.Mail("user@gmail.com"); err
== nil {
137 t
.Fatalf("MAIL should require authentication")
140 if err
:= c
.Verify("user1@gmail.com"); err
== nil {
141 t
.Fatalf("First VRFY: expected no verification")
143 if err
:= c
.Verify("user2@gmail.com"); err
!= nil {
144 t
.Fatalf("Second VRFY: expected verification, got %s", err
)
147 // fake TLS so authentication won't complain
149 c
.serverName
= "smtp.google.com"
150 if err
:= c
.Auth(PlainAuth("", "user", "pass", "smtp.google.com")); err
!= nil {
151 t
.Fatalf("AUTH failed: %s", err
)
154 if err
:= c
.Mail("user@gmail.com"); err
!= nil {
155 t
.Fatalf("MAIL failed: %s", err
)
157 if err
:= c
.Rcpt("golang-nuts@googlegroups.com"); err
!= nil {
158 t
.Fatalf("RCPT failed: %s", err
)
160 msg
:= `From: user@gmail.com
161 To: golang-nuts@googlegroups.com
162 Subject: Hooray for Go
169 t
.Fatalf("DATA failed: %s", err
)
171 if _
, err
:= w
.Write([]byte(msg
)); err
!= nil {
172 t
.Fatalf("Data write failed: %s", err
)
174 if err
:= w
.Close(); err
!= nil {
175 t
.Fatalf("Bad data response: %s", err
)
178 if err
:= c
.Quit(); err
!= nil {
179 t
.Fatalf("QUIT failed: %s", err
)
183 actualcmds
:= cmdbuf
.String()
184 if client
!= actualcmds
{
185 t
.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds
, client
)
189 var basicServer
= `250 mx.google.com at your service
190 502 Unrecognized command.
191 250-mx.google.com at your service
195 530 Authentication required
196 252 Send some mail, I'll try my best
206 var basicClient
= `HELO localhost
209 MAIL FROM:<user@gmail.com> BODY=8BITMIME
212 AUTH PLAIN AHVzZXIAcGFzcw==
213 MAIL FROM:<user@gmail.com> BODY=8BITMIME
214 RCPT TO:<golang-nuts@googlegroups.com>
217 To: golang-nuts@googlegroups.com
218 Subject: Hooray for Go
227 func TestNewClient(t
*testing
.T
) {
228 server
:= strings
.Join(strings
.Split(newClientServer
, "\n"), "\r\n")
229 client
:= strings
.Join(strings
.Split(newClientClient
, "\n"), "\r\n")
231 var cmdbuf bytes
.Buffer
232 bcmdbuf
:= bufio
.NewWriter(&cmdbuf
)
233 out
:= func() string {
235 return cmdbuf
.String()
238 fake
.ReadWriter
= bufio
.NewReadWriter(bufio
.NewReader(strings
.NewReader(server
)), bcmdbuf
)
239 c
, err
:= NewClient(fake
, "fake.host")
241 t
.Fatalf("NewClient: %v\n(after %v)", err
, out())
244 if ok
, args
:= c
.Extension("aUtH"); !ok || args
!= "LOGIN PLAIN" {
245 t
.Fatalf("Expected AUTH supported")
247 if ok
, _
:= c
.Extension("DSN"); ok
{
248 t
.Fatalf("Shouldn't support DSN")
250 if err
:= c
.Quit(); err
!= nil {
251 t
.Fatalf("QUIT failed: %s", err
)
255 if client
!= actualcmds
{
256 t
.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds
, client
)
260 var newClientServer
= `220 hello world
261 250-mx.google.com at your service
268 var newClientClient
= `EHLO localhost
272 func TestNewClient2(t
*testing
.T
) {
273 server
:= strings
.Join(strings
.Split(newClient2Server
, "\n"), "\r\n")
274 client
:= strings
.Join(strings
.Split(newClient2Client
, "\n"), "\r\n")
276 var cmdbuf bytes
.Buffer
277 bcmdbuf
:= bufio
.NewWriter(&cmdbuf
)
279 fake
.ReadWriter
= bufio
.NewReadWriter(bufio
.NewReader(strings
.NewReader(server
)), bcmdbuf
)
280 c
, err
:= NewClient(fake
, "fake.host")
282 t
.Fatalf("NewClient: %v", err
)
285 if ok
, _
:= c
.Extension("DSN"); ok
{
286 t
.Fatalf("Shouldn't support DSN")
288 if err
:= c
.Quit(); err
!= nil {
289 t
.Fatalf("QUIT failed: %s", err
)
293 actualcmds
:= cmdbuf
.String()
294 if client
!= actualcmds
{
295 t
.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds
, client
)
299 var newClient2Server
= `220 hello world
301 250-mx.google.com at your service
308 var newClient2Client
= `EHLO localhost
313 func TestHello(t
*testing
.T
) {
315 if len(helloServer
) != len(helloClient
) {
316 t
.Fatalf("Hello server and client size mismatch")
319 for i
:= 0; i
< len(helloServer
); i
++ {
320 server
:= strings
.Join(strings
.Split(baseHelloServer
+helloServer
[i
], "\n"), "\r\n")
321 client
:= strings
.Join(strings
.Split(baseHelloClient
+helloClient
[i
], "\n"), "\r\n")
322 var cmdbuf bytes
.Buffer
323 bcmdbuf
:= bufio
.NewWriter(&cmdbuf
)
325 fake
.ReadWriter
= bufio
.NewReadWriter(bufio
.NewReader(strings
.NewReader(server
)), bcmdbuf
)
326 c
, err
:= NewClient(fake
, "fake.host")
328 t
.Fatalf("NewClient: %v", err
)
331 c
.localName
= "customhost"
336 err
= c
.Hello("customhost")
338 err
= c
.StartTLS(nil)
339 if err
.Error() == "502 Not implemented" {
343 err
= c
.Verify("test@example.com")
346 c
.serverName
= "smtp.google.com"
347 err
= c
.Auth(PlainAuth("", "user", "pass", "smtp.google.com"))
349 err
= c
.Mail("test@example.com")
351 ok
, _
:= c
.Extension("feature")
353 t
.Errorf("Expected FEATURE not to be supported")
360 err
= c
.Verify("test@example.com")
362 err
= c
.Hello("customhost")
364 t
.Errorf("Want error, got none")
368 t
.Fatalf("Unhandled command")
372 t
.Errorf("Command %d failed: %v", i
, err
)
376 actualcmds
:= cmdbuf
.String()
377 if client
!= actualcmds
{
378 t
.Errorf("Got:\n%s\nExpected:\n%s", actualcmds
, client
)
383 var baseHelloServer
= `220 hello world
385 250-mx.google.com at your service
389 var helloServer
= []string{
391 "502 Not implemented\n",
392 "250 User is valid\n",
401 var baseHelloClient
= `EHLO customhost
405 var helloClient
= []string{
408 "VRFY test@example.com\n",
409 "AUTH PLAIN AHVzZXIAcGFzcw==\n",
410 "MAIL FROM:<test@example.com>\n",
414 "VRFY test@example.com\n",
417 func TestSendMail(t
*testing
.T
) {
418 server
:= strings
.Join(strings
.Split(sendMailServer
, "\n"), "\r\n")
419 client
:= strings
.Join(strings
.Split(sendMailClient
, "\n"), "\r\n")
420 var cmdbuf bytes
.Buffer
421 bcmdbuf
:= bufio
.NewWriter(&cmdbuf
)
422 l
, err
:= net
.Listen("tcp", "127.0.0.1:0")
424 t
.Fatalf("Unable to to create listener: %v", err
)
428 // prevent data race on bcmdbuf
429 var done
= make(chan struct{})
430 go func(data
[]string) {
434 conn
, err
:= l
.Accept()
436 t
.Errorf("Accept error: %v", err
)
441 tc
:= textproto
.NewConn(conn
)
442 for i
:= 0; i
< len(data
) && data
[i
] != ""; i
++ {
443 tc
.PrintfLine(data
[i
])
444 for len(data
[i
]) >= 4 && data
[i
][3] == '-' {
446 tc
.PrintfLine(data
[i
])
448 if data
[i
] == "221 Goodbye" {
452 for !read || data
[i
] == "354 Go ahead" {
453 msg
, err
:= tc
.ReadLine()
454 bcmdbuf
.Write([]byte(msg
+ "\r\n"))
457 t
.Errorf("Read error: %v", err
)
460 if data
[i
] == "354 Go ahead" && msg
== "." {
465 }(strings
.Split(server
, "\r\n"))
467 err
= SendMail(l
.Addr().String(), nil, "test@example.com", []string{"other@example.com"}, []byte(strings
.Replace(`From: test@example.com
468 To: other@example.com
469 Subject: SendMail test
471 SendMail is working for me.
472 `, "\n", "\r\n", -1)))
480 actualcmds
:= cmdbuf
.String()
481 if client
!= actualcmds
{
482 t
.Errorf("Got:\n%s\nExpected:\n%s", actualcmds
, client
)
486 var sendMailServer
= `220 hello world
488 250 mx.google.com at your service
496 var sendMailClient
= `EHLO localhost
498 MAIL FROM:<test@example.com>
499 RCPT TO:<other@example.com>
501 From: test@example.com
502 To: other@example.com
503 Subject: SendMail test
505 SendMail is working for me.
510 func TestAuthFailed(t
*testing
.T
) {
511 server
:= strings
.Join(strings
.Split(authFailedServer
, "\n"), "\r\n")
512 client
:= strings
.Join(strings
.Split(authFailedClient
, "\n"), "\r\n")
513 var cmdbuf bytes
.Buffer
514 bcmdbuf
:= bufio
.NewWriter(&cmdbuf
)
516 fake
.ReadWriter
= bufio
.NewReadWriter(bufio
.NewReader(strings
.NewReader(server
)), bcmdbuf
)
517 c
, err
:= NewClient(fake
, "fake.host")
519 t
.Fatalf("NewClient: %v", err
)
524 c
.serverName
= "smtp.google.com"
525 err
= c
.Auth(PlainAuth("", "user", "pass", "smtp.google.com"))
528 t
.Error("Auth: expected error; got none")
529 } else if err
.Error() != "535 Invalid credentials\nplease see www.example.com" {
530 t
.Errorf("Auth: got error: %v, want: %s", err
, "535 Invalid credentials\nplease see www.example.com")
534 actualcmds
:= cmdbuf
.String()
535 if client
!= actualcmds
{
536 t
.Errorf("Got:\n%s\nExpected:\n%s", actualcmds
, client
)
540 var authFailedServer
= `220 hello world
541 250-mx.google.com at your service
543 535-Invalid credentials
544 535 please see www.example.com
548 var authFailedClient
= `EHLO localhost
549 AUTH PLAIN AHVzZXIAcGFzcw==
554 func TestTLSClient(t
*testing
.T
) {
555 ln
:= newLocalListener(t
)
557 errc
:= make(chan error
)
559 errc
<- sendMail(ln
.Addr().String())
561 conn
, err
:= ln
.Accept()
563 t
.Fatalf("failed to accept connection: %v", err
)
566 if err
:= serverHandle(conn
, t
); err
!= nil {
567 t
.Fatalf("failed to handle connection: %v", err
)
569 if err
:= <-errc
; err
!= nil {
570 t
.Fatalf("client error: %v", err
)
574 func newLocalListener(t
*testing
.T
) net
.Listener
{
575 ln
, err
:= net
.Listen("tcp", "127.0.0.1:0")
577 ln
, err
= net
.Listen("tcp6", "[::1]:0")
585 type smtpSender
struct {
589 func (s smtpSender
) send(f
string) {
590 s
.w
.Write([]byte(f
+ "\r\n"))
593 // smtp server, finely tailored to deal with our own client only!
594 func serverHandle(c net
.Conn
, t
*testing
.T
) error
{
595 send
:= smtpSender
{c
}.send
596 send("220 127.0.0.1 ESMTP service ready")
597 s
:= bufio
.NewScanner(c
)
600 case "EHLO localhost":
601 send("250-127.0.0.1 ESMTP offers a warm hug of welcome")
606 keypair
, err
:= tls
.X509KeyPair(localhostCert
, localhostKey
)
610 config
:= &tls
.Config
{Certificates
: []tls
.Certificate
{keypair
}}
611 c
= tls
.Server(c
, config
)
613 return serverHandleTLS(c
, t
)
615 t
.Fatalf("unrecognized command: %q", s
.Text())
621 func serverHandleTLS(c net
.Conn
, t
*testing
.T
) error
{
622 send
:= smtpSender
{c
}.send
623 s
:= bufio
.NewScanner(c
)
626 case "EHLO localhost":
628 case "MAIL FROM:<joe1@example.com>":
630 case "RCPT TO:<joe2@example.com>":
633 send("354 send the mail data, end with .")
635 case "Subject: test":
640 send("221 127.0.0.1 Service closing transmission channel")
643 t
.Fatalf("unrecognized command during TLS: %q", s
.Text())
650 testRootCAs
:= x509
.NewCertPool()
651 testRootCAs
.AppendCertsFromPEM(localhostCert
)
652 testHookStartTLS
= func(config
*tls
.Config
) {
653 config
.RootCAs
= testRootCAs
657 func sendMail(hostPort
string) error
{
658 host
, _
, err
:= net
.SplitHostPort(hostPort
)
662 auth
:= PlainAuth("", "", "", host
)
663 from
:= "joe1@example.com"
664 to
:= []string{"joe2@example.com"}
665 return SendMail(hostPort
, auth
, from
, to
, []byte("Subject: test\n\nhowdy!"))
668 // (copied from net/http/httptest)
669 // localhostCert is a PEM-encoded TLS cert with SAN IPs
670 // "127.0.0.1" and "[::1]", expiring at the last second of 2049 (the end
672 // generated from src/crypto/tls:
673 // go run generate_cert.go --rsa-bits 512 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
674 var localhostCert
= []byte(`-----BEGIN CERTIFICATE-----
675 MIIBdzCCASOgAwIBAgIBADALBgkqhkiG9w0BAQUwEjEQMA4GA1UEChMHQWNtZSBD
676 bzAeFw03MDAxMDEwMDAwMDBaFw00OTEyMzEyMzU5NTlaMBIxEDAOBgNVBAoTB0Fj
677 bWUgQ28wWjALBgkqhkiG9w0BAQEDSwAwSAJBAN55NcYKZeInyTuhcCwFMhDHCmwa
678 IUSdtXdcbItRB/yfXGBhiex00IaLXQnSU+QZPRZWYqeTEbFSgihqi1PUDy8CAwEA
679 AaNoMGYwDgYDVR0PAQH/BAQDAgCkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1Ud
680 EwEB/wQFMAMBAf8wLgYDVR0RBCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAA
681 AAAAAAAAAAAAAAEwCwYJKoZIhvcNAQEFA0EAAoQn/ytgqpiLcZu9XKbCJsJcvkgk
682 Se6AbGXgSlq+ZCEVo0qIwSgeBqmsJxUu7NCSOwVJLYNEBO2DtIxoYVk+MA==
683 -----END CERTIFICATE-----`)
685 // localhostKey is the private key for localhostCert.
686 var localhostKey
= []byte(`-----BEGIN RSA PRIVATE KEY-----
687 MIIBPAIBAAJBAN55NcYKZeInyTuhcCwFMhDHCmwaIUSdtXdcbItRB/yfXGBhiex0
688 0IaLXQnSU+QZPRZWYqeTEbFSgihqi1PUDy8CAwEAAQJBAQdUx66rfh8sYsgfdcvV
689 NoafYpnEcB5s4m/vSVe6SU7dCK6eYec9f9wpT353ljhDUHq3EbmE4foNzJngh35d
690 AekCIQDhRQG5Li0Wj8TM4obOnnXUXf1jRv0UkzE9AHWLG5q3AwIhAPzSjpYUDjVW
691 MCUXgckTpKCuGwbJk7424Nb8bLzf3kllAiA5mUBgjfr/WtFSJdWcPQ4Zt9KTMNKD
692 EUO0ukpTwEIl6wIhAMbGqZK3zAAFdq8DD2jPx+UJXnh0rnOkZBzDtJ6/iN69AiEA
693 1Aq8MJgTaYsDQWyU/hDq5YkDJc9e9DSCvUIzqxQWMQE=
694 -----END RSA PRIVATE KEY-----`)