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.
23 type authTest
struct {
30 var authTests
= []authTest
{
31 {PlainAuth("", "user", "pass", "testserver"), []string{}, "PLAIN", []string{"\x00user\x00pass"}},
32 {PlainAuth("foo", "bar", "baz", "testserver"), []string{}, "PLAIN", []string{"foo\x00bar\x00baz"}},
33 {CRAMMD5Auth("user", "pass"), []string{"<123456.1322876914@testserver>"}, "CRAM-MD5", []string{"", "user 287eb355114cf5c471c26a875f1ca4ae"}},
36 func TestAuth(t
*testing
.T
) {
38 for i
, test
:= range authTests
{
39 name
, resp
, err
:= test
.auth
.Start(&ServerInfo
{"testserver", true, nil})
40 if name
!= test
.name
{
41 t
.Errorf("#%d got name %s, expected %s", i
, name
, test
.name
)
43 if !bytes
.Equal(resp
, []byte(test
.responses
[0])) {
44 t
.Errorf("#%d got response %s, expected %s", i
, resp
, test
.responses
[0])
47 t
.Errorf("#%d error: %s", i
, err
)
49 for j
:= range test
.challenges
{
50 challenge
:= []byte(test
.challenges
[j
])
51 expected
:= []byte(test
.responses
[j
+1])
52 resp
, err
:= test
.auth
.Next(challenge
, true)
54 t
.Errorf("#%d error: %s", i
, err
)
57 if !bytes
.Equal(resp
, expected
) {
58 t
.Errorf("#%d got %s, expected %s", i
, resp
, expected
)
65 func TestAuthPlain(t
*testing
.T
) {
73 authName
: "servername",
74 server
: &ServerInfo
{Name
: "servername", TLS
: true},
77 // OK to use PlainAuth on localhost without TLS
78 authName
: "localhost",
79 server
: &ServerInfo
{Name
: "localhost", TLS
: false},
82 // NOT OK on non-localhost, even if server says PLAIN is OK.
83 // (We don't know that the server is the real server.)
84 authName
: "servername",
85 server
: &ServerInfo
{Name
: "servername", Auth
: []string{"PLAIN"}},
86 err
: "unencrypted connection",
89 authName
: "servername",
90 server
: &ServerInfo
{Name
: "servername", Auth
: []string{"CRAM-MD5"}},
91 err
: "unencrypted connection",
94 authName
: "servername",
95 server
: &ServerInfo
{Name
: "attacker", TLS
: true},
96 err
: "wrong host name",
99 for i
, tt
:= range tests
{
100 auth
:= PlainAuth("foo", "bar", "baz", tt
.authName
)
101 _
, _
, err
:= auth
.Start(tt
.server
)
107 t
.Errorf("%d. got error = %q; want %q", i
, got
, tt
.err
)
112 // Issue 17794: don't send a trailing space on AUTH command when there's no password.
113 func TestClientAuthTrimSpace(t
*testing
.T
) {
114 server
:= "220 hello world\r\n" +
116 var wrote bytes
.Buffer
118 fake
.ReadWriter
= struct {
122 strings
.NewReader(server
),
125 c
, err
:= NewClient(fake
, "fake.host")
127 t
.Fatalf("NewClient: %v", err
)
131 c
.Auth(toServerEmptyAuth
{})
133 if got
, want
:= wrote
.String(), "AUTH FOOAUTH\r\n*\r\nQUIT\r\n"; got
!= want
{
134 t
.Errorf("wrote %q; want %q", got
, want
)
138 // toServerEmptyAuth is an implementation of Auth that only implements
139 // the Start method, and returns "FOOAUTH", nil, nil. Notably, it returns
140 // zero bytes for "toServer" so we can test that we don't send spaces at
141 // the end of the line. See TestClientAuthTrimSpace.
142 type toServerEmptyAuth
struct{}
144 func (toServerEmptyAuth
) Start(server
*ServerInfo
) (proto
string, toServer
[]byte, err error
) {
145 return "FOOAUTH", nil, nil
148 func (toServerEmptyAuth
) Next(fromServer
[]byte, more
bool) (toServer
[]byte, err error
) {
149 panic("unexpected call")
156 func (f faker
) Close() error
{ return nil }
157 func (f faker
) LocalAddr() net
.Addr
{ return nil }
158 func (f faker
) RemoteAddr() net
.Addr
{ return nil }
159 func (f faker
) SetDeadline(time
.Time
) error
{ return nil }
160 func (f faker
) SetReadDeadline(time
.Time
) error
{ return nil }
161 func (f faker
) SetWriteDeadline(time
.Time
) error
{ return nil }
163 func TestBasic(t
*testing
.T
) {
164 server
:= strings
.Join(strings
.Split(basicServer
, "\n"), "\r\n")
165 client
:= strings
.Join(strings
.Split(basicClient
, "\n"), "\r\n")
167 var cmdbuf bytes
.Buffer
168 bcmdbuf
:= bufio
.NewWriter(&cmdbuf
)
170 fake
.ReadWriter
= bufio
.NewReadWriter(bufio
.NewReader(strings
.NewReader(server
)), bcmdbuf
)
171 c
:= &Client
{Text
: textproto
.NewConn(fake
), localName
: "localhost"}
173 if err
:= c
.helo(); err
!= nil {
174 t
.Fatalf("HELO failed: %s", err
)
176 if err
:= c
.ehlo(); err
== nil {
177 t
.Fatalf("Expected first EHLO to fail")
179 if err
:= c
.ehlo(); err
!= nil {
180 t
.Fatalf("Second EHLO failed: %s", err
)
184 if ok
, args
:= c
.Extension("aUtH"); !ok || args
!= "LOGIN PLAIN" {
185 t
.Fatalf("Expected AUTH supported")
187 if ok
, _
:= c
.Extension("DSN"); ok
{
188 t
.Fatalf("Shouldn't support DSN")
191 if err
:= c
.Mail("user@gmail.com"); err
== nil {
192 t
.Fatalf("MAIL should require authentication")
195 if err
:= c
.Verify("user1@gmail.com"); err
== nil {
196 t
.Fatalf("First VRFY: expected no verification")
198 if err
:= c
.Verify("user2@gmail.com>\r\nDATA\r\nAnother injected message body\r\n.\r\nQUIT\r\n"); err
== nil {
199 t
.Fatalf("VRFY should have failed due to a message injection attempt")
201 if err
:= c
.Verify("user2@gmail.com"); err
!= nil {
202 t
.Fatalf("Second VRFY: expected verification, got %s", err
)
205 // fake TLS so authentication won't complain
207 c
.serverName
= "smtp.google.com"
208 if err
:= c
.Auth(PlainAuth("", "user", "pass", "smtp.google.com")); err
!= nil {
209 t
.Fatalf("AUTH failed: %s", err
)
212 if err
:= c
.Rcpt("golang-nuts@googlegroups.com>\r\nDATA\r\nInjected message body\r\n.\r\nQUIT\r\n"); err
== nil {
213 t
.Fatalf("RCPT should have failed due to a message injection attempt")
215 if err
:= c
.Mail("user@gmail.com>\r\nDATA\r\nAnother injected message body\r\n.\r\nQUIT\r\n"); err
== nil {
216 t
.Fatalf("MAIL should have failed due to a message injection attempt")
218 if err
:= c
.Mail("user@gmail.com"); err
!= nil {
219 t
.Fatalf("MAIL failed: %s", err
)
221 if err
:= c
.Rcpt("golang-nuts@googlegroups.com"); err
!= nil {
222 t
.Fatalf("RCPT failed: %s", err
)
224 msg
:= `From: user@gmail.com
225 To: golang-nuts@googlegroups.com
226 Subject: Hooray for Go
233 t
.Fatalf("DATA failed: %s", err
)
235 if _
, err
:= w
.Write([]byte(msg
)); err
!= nil {
236 t
.Fatalf("Data write failed: %s", err
)
238 if err
:= w
.Close(); err
!= nil {
239 t
.Fatalf("Bad data response: %s", err
)
242 if err
:= c
.Quit(); err
!= nil {
243 t
.Fatalf("QUIT failed: %s", err
)
247 actualcmds
:= cmdbuf
.String()
248 if client
!= actualcmds
{
249 t
.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds
, client
)
253 var basicServer
= `250 mx.google.com at your service
254 502 Unrecognized command.
255 250-mx.google.com at your service
259 530 Authentication required
260 252 Send some mail, I'll try my best
270 var basicClient
= `HELO localhost
273 MAIL FROM:<user@gmail.com> BODY=8BITMIME
276 AUTH PLAIN AHVzZXIAcGFzcw==
277 MAIL FROM:<user@gmail.com> BODY=8BITMIME
278 RCPT TO:<golang-nuts@googlegroups.com>
281 To: golang-nuts@googlegroups.com
282 Subject: Hooray for Go
291 func TestExtensions(t
*testing
.T
) {
292 fake
:= func(server
string) (c
*Client
, bcmdbuf
*bufio
.Writer
, cmdbuf
*strings
.Builder
) {
293 server
= strings
.Join(strings
.Split(server
, "\n"), "\r\n")
295 cmdbuf
= &strings
.Builder
{}
296 bcmdbuf
= bufio
.NewWriter(cmdbuf
)
298 fake
.ReadWriter
= bufio
.NewReadWriter(bufio
.NewReader(strings
.NewReader(server
)), bcmdbuf
)
299 c
= &Client
{Text
: textproto
.NewConn(fake
), localName
: "localhost"}
301 return c
, bcmdbuf
, cmdbuf
304 t
.Run("helo", func(t
*testing
.T
) {
306 basicServer
= `250 mx.google.com at your service
311 basicClient
= `HELO localhost
312 MAIL FROM:<user@gmail.com>
317 c
, bcmdbuf
, cmdbuf
:= fake(basicServer
)
319 if err
:= c
.helo(); err
!= nil {
320 t
.Fatalf("HELO failed: %s", err
)
323 if err
:= c
.Mail("user@gmail.com"); err
!= nil {
324 t
.Fatalf("MAIL FROM failed: %s", err
)
326 if err
:= c
.Quit(); err
!= nil {
327 t
.Fatalf("QUIT failed: %s", err
)
331 actualcmds
:= cmdbuf
.String()
332 client
:= strings
.Join(strings
.Split(basicClient
, "\n"), "\r\n")
333 if client
!= actualcmds
{
334 t
.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds
, client
)
338 t
.Run("ehlo", func(t
*testing
.T
) {
340 basicServer
= `250-mx.google.com at your service
346 basicClient
= `EHLO localhost
347 MAIL FROM:<user@gmail.com>
352 c
, bcmdbuf
, cmdbuf
:= fake(basicServer
)
354 if err
:= c
.Hello("localhost"); err
!= nil {
355 t
.Fatalf("EHLO failed: %s", err
)
357 if ok
, _
:= c
.Extension("8BITMIME"); ok
{
358 t
.Fatalf("Shouldn't support 8BITMIME")
360 if ok
, _
:= c
.Extension("SMTPUTF8"); ok
{
361 t
.Fatalf("Shouldn't support SMTPUTF8")
363 if err
:= c
.Mail("user@gmail.com"); err
!= nil {
364 t
.Fatalf("MAIL FROM failed: %s", err
)
366 if err
:= c
.Quit(); err
!= nil {
367 t
.Fatalf("QUIT failed: %s", err
)
371 actualcmds
:= cmdbuf
.String()
372 client
:= strings
.Join(strings
.Split(basicClient
, "\n"), "\r\n")
373 if client
!= actualcmds
{
374 t
.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds
, client
)
378 t
.Run("ehlo 8bitmime", func(t
*testing
.T
) {
380 basicServer
= `250-mx.google.com at your service
387 basicClient
= `EHLO localhost
388 MAIL FROM:<user@gmail.com> BODY=8BITMIME
393 c
, bcmdbuf
, cmdbuf
:= fake(basicServer
)
395 if err
:= c
.Hello("localhost"); err
!= nil {
396 t
.Fatalf("EHLO failed: %s", err
)
398 if ok
, _
:= c
.Extension("8BITMIME"); !ok
{
399 t
.Fatalf("Should support 8BITMIME")
401 if ok
, _
:= c
.Extension("SMTPUTF8"); ok
{
402 t
.Fatalf("Shouldn't support SMTPUTF8")
404 if err
:= c
.Mail("user@gmail.com"); err
!= nil {
405 t
.Fatalf("MAIL FROM failed: %s", err
)
407 if err
:= c
.Quit(); err
!= nil {
408 t
.Fatalf("QUIT failed: %s", err
)
412 actualcmds
:= cmdbuf
.String()
413 client
:= strings
.Join(strings
.Split(basicClient
, "\n"), "\r\n")
414 if client
!= actualcmds
{
415 t
.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds
, client
)
419 t
.Run("ehlo smtputf8", func(t
*testing
.T
) {
421 basicServer
= `250-mx.google.com at your service
428 basicClient
= `EHLO localhost
429 MAIL FROM:<user+📧@gmail.com> SMTPUTF8
434 c
, bcmdbuf
, cmdbuf
:= fake(basicServer
)
436 if err
:= c
.Hello("localhost"); err
!= nil {
437 t
.Fatalf("EHLO failed: %s", err
)
439 if ok
, _
:= c
.Extension("8BITMIME"); ok
{
440 t
.Fatalf("Shouldn't support 8BITMIME")
442 if ok
, _
:= c
.Extension("SMTPUTF8"); !ok
{
443 t
.Fatalf("Should support SMTPUTF8")
445 if err
:= c
.Mail("user+📧@gmail.com"); err
!= nil {
446 t
.Fatalf("MAIL FROM failed: %s", err
)
448 if err
:= c
.Quit(); err
!= nil {
449 t
.Fatalf("QUIT failed: %s", err
)
453 actualcmds
:= cmdbuf
.String()
454 client
:= strings
.Join(strings
.Split(basicClient
, "\n"), "\r\n")
455 if client
!= actualcmds
{
456 t
.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds
, client
)
460 t
.Run("ehlo 8bitmime smtputf8", func(t
*testing
.T
) {
462 basicServer
= `250-mx.google.com at your service
470 basicClient
= `EHLO localhost
471 MAIL FROM:<user+📧@gmail.com> BODY=8BITMIME SMTPUTF8
476 c
, bcmdbuf
, cmdbuf
:= fake(basicServer
)
478 if err
:= c
.Hello("localhost"); err
!= nil {
479 t
.Fatalf("EHLO failed: %s", err
)
482 if ok
, _
:= c
.Extension("8BITMIME"); !ok
{
483 t
.Fatalf("Should support 8BITMIME")
485 if ok
, _
:= c
.Extension("SMTPUTF8"); !ok
{
486 t
.Fatalf("Should support SMTPUTF8")
488 if err
:= c
.Mail("user+📧@gmail.com"); err
!= nil {
489 t
.Fatalf("MAIL FROM failed: %s", err
)
491 if err
:= c
.Quit(); err
!= nil {
492 t
.Fatalf("QUIT failed: %s", err
)
496 actualcmds
:= cmdbuf
.String()
497 client
:= strings
.Join(strings
.Split(basicClient
, "\n"), "\r\n")
498 if client
!= actualcmds
{
499 t
.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds
, client
)
504 func TestNewClient(t
*testing
.T
) {
505 server
:= strings
.Join(strings
.Split(newClientServer
, "\n"), "\r\n")
506 client
:= strings
.Join(strings
.Split(newClientClient
, "\n"), "\r\n")
508 var cmdbuf bytes
.Buffer
509 bcmdbuf
:= bufio
.NewWriter(&cmdbuf
)
510 out
:= func() string {
512 return cmdbuf
.String()
515 fake
.ReadWriter
= bufio
.NewReadWriter(bufio
.NewReader(strings
.NewReader(server
)), bcmdbuf
)
516 c
, err
:= NewClient(fake
, "fake.host")
518 t
.Fatalf("NewClient: %v\n(after %v)", err
, out())
521 if ok
, args
:= c
.Extension("aUtH"); !ok || args
!= "LOGIN PLAIN" {
522 t
.Fatalf("Expected AUTH supported")
524 if ok
, _
:= c
.Extension("DSN"); ok
{
525 t
.Fatalf("Shouldn't support DSN")
527 if err
:= c
.Quit(); err
!= nil {
528 t
.Fatalf("QUIT failed: %s", err
)
532 if client
!= actualcmds
{
533 t
.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds
, client
)
537 var newClientServer
= `220 hello world
538 250-mx.google.com at your service
545 var newClientClient
= `EHLO localhost
549 func TestNewClient2(t
*testing
.T
) {
550 server
:= strings
.Join(strings
.Split(newClient2Server
, "\n"), "\r\n")
551 client
:= strings
.Join(strings
.Split(newClient2Client
, "\n"), "\r\n")
553 var cmdbuf bytes
.Buffer
554 bcmdbuf
:= bufio
.NewWriter(&cmdbuf
)
556 fake
.ReadWriter
= bufio
.NewReadWriter(bufio
.NewReader(strings
.NewReader(server
)), bcmdbuf
)
557 c
, err
:= NewClient(fake
, "fake.host")
559 t
.Fatalf("NewClient: %v", err
)
562 if ok
, _
:= c
.Extension("DSN"); ok
{
563 t
.Fatalf("Shouldn't support DSN")
565 if err
:= c
.Quit(); err
!= nil {
566 t
.Fatalf("QUIT failed: %s", err
)
570 actualcmds
:= cmdbuf
.String()
571 if client
!= actualcmds
{
572 t
.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds
, client
)
576 var newClient2Server
= `220 hello world
578 250-mx.google.com at your service
585 var newClient2Client
= `EHLO localhost
590 func TestNewClientWithTLS(t
*testing
.T
) {
591 cert
, err
:= tls
.X509KeyPair(localhostCert
, localhostKey
)
593 t
.Fatalf("loadcert: %v", err
)
596 config
:= tls
.Config
{Certificates
: []tls
.Certificate
{cert
}}
598 ln
, err
:= tls
.Listen("tcp", "127.0.0.1:0", &config
)
600 ln
, err
= tls
.Listen("tcp", "[::1]:0", &config
)
602 t
.Fatalf("server: listen: %v", err
)
607 conn
, err
:= ln
.Accept()
609 t
.Errorf("server: accept: %v", err
)
614 _
, err
= conn
.Write([]byte("220 SIGNS\r\n"))
616 t
.Errorf("server: write: %v", err
)
621 config
.InsecureSkipVerify
= true
622 conn
, err
:= tls
.Dial("tcp", ln
.Addr().String(), &config
)
624 t
.Fatalf("client: dial: %v", err
)
628 client
, err
:= NewClient(conn
, ln
.Addr().String())
630 t
.Fatalf("smtp: newclient: %v", err
)
633 t
.Errorf("client.tls Got: %t Expected: %t", client
.tls
, true)
637 func TestHello(t
*testing
.T
) {
639 if len(helloServer
) != len(helloClient
) {
640 t
.Fatalf("Hello server and client size mismatch")
643 for i
:= 0; i
< len(helloServer
); i
++ {
644 server
:= strings
.Join(strings
.Split(baseHelloServer
+helloServer
[i
], "\n"), "\r\n")
645 client
:= strings
.Join(strings
.Split(baseHelloClient
+helloClient
[i
], "\n"), "\r\n")
646 var cmdbuf bytes
.Buffer
647 bcmdbuf
:= bufio
.NewWriter(&cmdbuf
)
649 fake
.ReadWriter
= bufio
.NewReadWriter(bufio
.NewReader(strings
.NewReader(server
)), bcmdbuf
)
650 c
, err
:= NewClient(fake
, "fake.host")
652 t
.Fatalf("NewClient: %v", err
)
655 c
.localName
= "customhost"
660 err
= c
.Hello("hostinjection>\n\rDATA\r\nInjected message body\r\n.\r\nQUIT\r\n")
662 t
.Errorf("Expected Hello to be rejected due to a message injection attempt")
664 err
= c
.Hello("customhost")
666 err
= c
.StartTLS(nil)
667 if err
.Error() == "502 Not implemented" {
671 err
= c
.Verify("test@example.com")
674 c
.serverName
= "smtp.google.com"
675 err
= c
.Auth(PlainAuth("", "user", "pass", "smtp.google.com"))
677 err
= c
.Mail("test@example.com")
679 ok
, _
:= c
.Extension("feature")
681 t
.Errorf("Expected FEATURE not to be supported")
688 err
= c
.Verify("test@example.com")
690 err
= c
.Hello("customhost")
692 t
.Errorf("Want error, got none")
698 t
.Fatalf("Unhandled command")
702 t
.Errorf("Command %d failed: %v", i
, err
)
706 actualcmds
:= cmdbuf
.String()
707 if client
!= actualcmds
{
708 t
.Errorf("Got:\n%s\nExpected:\n%s", actualcmds
, client
)
713 var baseHelloServer
= `220 hello world
715 250-mx.google.com at your service
719 var helloServer
= []string{
721 "502 Not implemented\n",
722 "250 User is valid\n",
732 var baseHelloClient
= `EHLO customhost
736 var helloClient
= []string{
739 "VRFY test@example.com\n",
740 "AUTH PLAIN AHVzZXIAcGFzcw==\n",
741 "MAIL FROM:<test@example.com>\n",
745 "VRFY test@example.com\n",
749 func TestSendMail(t
*testing
.T
) {
750 server
:= strings
.Join(strings
.Split(sendMailServer
, "\n"), "\r\n")
751 client
:= strings
.Join(strings
.Split(sendMailClient
, "\n"), "\r\n")
752 var cmdbuf bytes
.Buffer
753 bcmdbuf
:= bufio
.NewWriter(&cmdbuf
)
754 l
, err
:= net
.Listen("tcp", "127.0.0.1:0")
756 t
.Fatalf("Unable to create listener: %v", err
)
760 // prevent data race on bcmdbuf
761 var done
= make(chan struct{})
762 go func(data
[]string) {
766 conn
, err
:= l
.Accept()
768 t
.Errorf("Accept error: %v", err
)
773 tc
:= textproto
.NewConn(conn
)
774 for i
:= 0; i
< len(data
) && data
[i
] != ""; i
++ {
775 tc
.PrintfLine(data
[i
])
776 for len(data
[i
]) >= 4 && data
[i
][3] == '-' {
778 tc
.PrintfLine(data
[i
])
780 if data
[i
] == "221 Goodbye" {
784 for !read || data
[i
] == "354 Go ahead" {
785 msg
, err
:= tc
.ReadLine()
786 bcmdbuf
.Write([]byte(msg
+ "\r\n"))
789 t
.Errorf("Read error: %v", err
)
792 if data
[i
] == "354 Go ahead" && msg
== "." {
797 }(strings
.Split(server
, "\r\n"))
799 err
= SendMail(l
.Addr().String(), nil, "test@example.com", []string{"other@example.com>\n\rDATA\r\nInjected message body\r\n.\r\nQUIT\r\n"}, []byte(strings
.Replace(`From: test@example.com
800 To: other@example.com
801 Subject: SendMail test
803 SendMail is working for me.
804 `, "\n", "\r\n", -1)))
806 t
.Errorf("Expected SendMail to be rejected due to a message injection attempt")
809 err
= SendMail(l
.Addr().String(), nil, "test@example.com", []string{"other@example.com"}, []byte(strings
.Replace(`From: test@example.com
810 To: other@example.com
811 Subject: SendMail test
813 SendMail is working for me.
814 `, "\n", "\r\n", -1)))
822 actualcmds
:= cmdbuf
.String()
823 if client
!= actualcmds
{
824 t
.Errorf("Got:\n%s\nExpected:\n%s", actualcmds
, client
)
828 var sendMailServer
= `220 hello world
830 250 mx.google.com at your service
838 var sendMailClient
= `EHLO localhost
840 MAIL FROM:<test@example.com>
841 RCPT TO:<other@example.com>
843 From: test@example.com
844 To: other@example.com
845 Subject: SendMail test
847 SendMail is working for me.
852 func TestSendMailWithAuth(t
*testing
.T
) {
853 l
, err
:= net
.Listen("tcp", "127.0.0.1:0")
855 t
.Fatalf("Unable to create listener: %v", err
)
859 errCh
:= make(chan error
)
862 conn
, err
:= l
.Accept()
864 errCh
<- fmt
.Errorf("Accept: %v", err
)
869 tc
:= textproto
.NewConn(conn
)
870 tc
.PrintfLine("220 hello world")
871 msg
, err
:= tc
.ReadLine()
873 errCh
<- fmt
.Errorf("ReadLine error: %v", err
)
876 const wantMsg
= "EHLO localhost"
878 errCh
<- fmt
.Errorf("unexpected response %q; want %q", msg
, wantMsg
)
881 err
= tc
.PrintfLine("250 mx.google.com at your service")
883 errCh
<- fmt
.Errorf("PrintfLine: %v", err
)
888 err
= SendMail(l
.Addr().String(), PlainAuth("", "user", "pass", "smtp.google.com"), "test@example.com", []string{"other@example.com"}, []byte(strings
.Replace(`From: test@example.com
889 To: other@example.com
890 Subject: SendMail test
892 SendMail is working for me.
893 `, "\n", "\r\n", -1)))
895 t
.Error("SendMail: Server doesn't support AUTH, expected to get an error, but got none ")
897 if err
.Error() != "smtp: server doesn't support AUTH" {
898 t
.Errorf("Expected: smtp: server doesn't support AUTH, got: %s", err
)
902 t
.Fatalf("server error: %v", err
)
906 func TestAuthFailed(t
*testing
.T
) {
907 server
:= strings
.Join(strings
.Split(authFailedServer
, "\n"), "\r\n")
908 client
:= strings
.Join(strings
.Split(authFailedClient
, "\n"), "\r\n")
909 var cmdbuf bytes
.Buffer
910 bcmdbuf
:= bufio
.NewWriter(&cmdbuf
)
912 fake
.ReadWriter
= bufio
.NewReadWriter(bufio
.NewReader(strings
.NewReader(server
)), bcmdbuf
)
913 c
, err
:= NewClient(fake
, "fake.host")
915 t
.Fatalf("NewClient: %v", err
)
920 c
.serverName
= "smtp.google.com"
921 err
= c
.Auth(PlainAuth("", "user", "pass", "smtp.google.com"))
924 t
.Error("Auth: expected error; got none")
925 } else if err
.Error() != "535 Invalid credentials\nplease see www.example.com" {
926 t
.Errorf("Auth: got error: %v, want: %s", err
, "535 Invalid credentials\nplease see www.example.com")
930 actualcmds
:= cmdbuf
.String()
931 if client
!= actualcmds
{
932 t
.Errorf("Got:\n%s\nExpected:\n%s", actualcmds
, client
)
936 var authFailedServer
= `220 hello world
937 250-mx.google.com at your service
939 535-Invalid credentials
940 535 please see www.example.com
944 var authFailedClient
= `EHLO localhost
945 AUTH PLAIN AHVzZXIAcGFzcw==
950 func TestTLSClient(t
*testing
.T
) {
951 if runtime
.GOOS
== "freebsd" || runtime
.GOOS
== "js" {
952 testenv
.SkipFlaky(t
, 19229)
954 ln
:= newLocalListener(t
)
956 errc
:= make(chan error
)
958 errc
<- sendMail(ln
.Addr().String())
960 conn
, err
:= ln
.Accept()
962 t
.Fatalf("failed to accept connection: %v", err
)
965 if err
:= serverHandle(conn
, t
); err
!= nil {
966 t
.Fatalf("failed to handle connection: %v", err
)
968 if err
:= <-errc
; err
!= nil {
969 t
.Fatalf("client error: %v", err
)
973 func TestTLSConnState(t
*testing
.T
) {
974 ln
:= newLocalListener(t
)
976 clientDone
:= make(chan bool)
977 serverDone
:= make(chan bool)
979 defer close(serverDone
)
980 c
, err
:= ln
.Accept()
982 t
.Errorf("Server accept: %v", err
)
986 if err
:= serverHandle(c
, t
); err
!= nil {
987 t
.Errorf("server error: %v", err
)
991 defer close(clientDone
)
992 c
, err
:= Dial(ln
.Addr().String())
994 t
.Errorf("Client dial: %v", err
)
998 cfg
:= &tls
.Config
{ServerName
: "example.com"}
999 testHookStartTLS(cfg
) // set the RootCAs
1000 if err
:= c
.StartTLS(cfg
); err
!= nil {
1001 t
.Errorf("StartTLS: %v", err
)
1004 cs
, ok
:= c
.TLSConnectionState()
1006 t
.Errorf("TLSConnectionState returned ok == false; want true")
1009 if cs
.Version
== 0 ||
!cs
.HandshakeComplete
{
1010 t
.Errorf("ConnectionState = %#v; expect non-zero Version and HandshakeComplete", cs
)
1017 func newLocalListener(t
*testing
.T
) net
.Listener
{
1018 ln
, err
:= net
.Listen("tcp", "127.0.0.1:0")
1020 ln
, err
= net
.Listen("tcp6", "[::1]:0")
1028 type smtpSender
struct {
1032 func (s smtpSender
) send(f
string) {
1033 s
.w
.Write([]byte(f
+ "\r\n"))
1036 // smtp server, finely tailored to deal with our own client only!
1037 func serverHandle(c net
.Conn
, t
*testing
.T
) error
{
1038 send
:= smtpSender
{c
}.send
1039 send("220 127.0.0.1 ESMTP service ready")
1040 s
:= bufio
.NewScanner(c
)
1043 case "EHLO localhost":
1044 send("250-127.0.0.1 ESMTP offers a warm hug of welcome")
1045 send("250-STARTTLS")
1048 send("220 Go ahead")
1049 keypair
, err
:= tls
.X509KeyPair(localhostCert
, localhostKey
)
1053 config
:= &tls
.Config
{Certificates
: []tls
.Certificate
{keypair
}}
1054 c
= tls
.Server(c
, config
)
1056 return serverHandleTLS(c
, t
)
1058 t
.Fatalf("unrecognized command: %q", s
.Text())
1064 func serverHandleTLS(c net
.Conn
, t
*testing
.T
) error
{
1065 send
:= smtpSender
{c
}.send
1066 s
:= bufio
.NewScanner(c
)
1069 case "EHLO localhost":
1071 case "MAIL FROM:<joe1@example.com>":
1073 case "RCPT TO:<joe2@example.com>":
1076 send("354 send the mail data, end with .")
1078 case "Subject: test":
1083 send("221 127.0.0.1 Service closing transmission channel")
1086 t
.Fatalf("unrecognized command during TLS: %q", s
.Text())
1093 testRootCAs
:= x509
.NewCertPool()
1094 testRootCAs
.AppendCertsFromPEM(localhostCert
)
1095 testHookStartTLS
= func(config
*tls
.Config
) {
1096 config
.RootCAs
= testRootCAs
1100 func sendMail(hostPort
string) error
{
1101 from
:= "joe1@example.com"
1102 to
:= []string{"joe2@example.com"}
1103 return SendMail(hostPort
, nil, from
, to
, []byte("Subject: test\n\nhowdy!"))
1106 // localhostCert is a PEM-encoded TLS cert generated from src/crypto/tls:
1107 // go run generate_cert.go --rsa-bits 1024 --host 127.0.0.1,::1,example.com \
1108 // --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
1109 var localhostCert
= []byte(`
1110 -----BEGIN CERTIFICATE-----
1111 MIICFDCCAX2gAwIBAgIRAK0xjnaPuNDSreeXb+z+0u4wDQYJKoZIhvcNAQELBQAw
1112 EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2
1113 MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
1114 gYkCgYEA0nFbQQuOWsjbGtejcpWz153OlziZM4bVjJ9jYruNw5n2Ry6uYQAffhqa
1115 JOInCmmcVe2siJglsyH9aRh6vKiobBbIUXXUU1ABd56ebAzlt0LobLlx7pZEMy30
1116 LqIi9E6zmL3YvdGzpYlkFRnRrqwEtWYbGBf3znO250S56CCWH2UCAwEAAaNoMGYw
1117 DgYDVR0PAQH/BAQDAgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQF
1118 MAMBAf8wLgYDVR0RBCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAAAAAAAAAA
1119 AAAAAAEwDQYJKoZIhvcNAQELBQADgYEAbZtDS2dVuBYvb+MnolWnCNqvw1w5Gtgi
1120 NmvQQPOMgM3m+oQSCPRTNGSg25e1Qbo7bgQDv8ZTnq8FgOJ/rbkyERw2JckkHpD4
1121 n4qcK27WkEDBtQFlPihIM8hLIuzWoi/9wygiElTy/tVL3y7fGCvY2/k1KBthtZGF
1123 -----END CERTIFICATE-----`)
1125 // localhostKey is the private key for localhostCert.
1126 var localhostKey
= []byte(testingKey(`
1127 -----BEGIN RSA TESTING KEY-----
1128 MIICXgIBAAKBgQDScVtBC45ayNsa16NylbPXnc6XOJkzhtWMn2Niu43DmfZHLq5h
1129 AB9+Gpok4icKaZxV7ayImCWzIf1pGHq8qKhsFshRddRTUAF3np5sDOW3QuhsuXHu
1130 lkQzLfQuoiL0TrOYvdi90bOliWQVGdGurAS1ZhsYF/fOc7bnRLnoIJYfZQIDAQAB
1131 AoGBAMst7OgpKyFV6c3JwyI/jWqxDySL3caU+RuTTBaodKAUx2ZEmNJIlx9eudLA
1132 kucHvoxsM/eRxlxkhdFxdBcwU6J+zqooTnhu/FE3jhrT1lPrbhfGhyKnUrB0KKMM
1133 VY3IQZyiehpxaeXAwoAou6TbWoTpl9t8ImAqAMY8hlULCUqlAkEA+9+Ry5FSYK/m
1134 542LujIcCaIGoG1/Te6Sxr3hsPagKC2rH20rDLqXwEedSFOpSS0vpzlPAzy/6Rbb
1135 PHTJUhNdwwJBANXkA+TkMdbJI5do9/mn//U0LfrCR9NkcoYohxfKz8JuhgRQxzF2
1136 6jpo3q7CdTuuRixLWVfeJzcrAyNrVcBq87cCQFkTCtOMNC7fZnCTPUv+9q1tcJyB
1137 vNjJu3yvoEZeIeuzouX9TJE21/33FaeDdsXbRhQEj23cqR38qFHsF1qAYNMCQQDP
1138 QXLEiJoClkR2orAmqjPLVhR3t2oB3INcnEjLNSq8LHyQEfXyaFfu4U9l5+fRPL2i
1139 jiC0k/9L5dHUsF0XZothAkEA23ddgRs+Id/HxtojqqUT27B8MT/IGNrYsp4DvS/c
1140 qgkeluku4GjxRlDMBuXk94xOBEinUs+p/hwP1Alll80Tpg==
1141 -----END RSA TESTING KEY-----`))
1143 func testingKey(s
string) string { return strings
.ReplaceAll(s
, "TESTING KEY", "PRIVATE KEY") }