github.com/mattn/go@v0.0.0-20171011075504-07f7db3ea99f/src/net/smtp/smtp_test.go (about) 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. 4 5 package smtp 6 7 import ( 8 "bufio" 9 "bytes" 10 "crypto/tls" 11 "crypto/x509" 12 "internal/testenv" 13 "io" 14 "net" 15 "net/textproto" 16 "runtime" 17 "strings" 18 "testing" 19 "time" 20 ) 21 22 type authTest struct { 23 auth Auth 24 challenges []string 25 name string 26 responses []string 27 } 28 29 var authTests = []authTest{ 30 {PlainAuth("", "user", "pass", "testserver"), []string{}, "PLAIN", []string{"\x00user\x00pass"}}, 31 {PlainAuth("foo", "bar", "baz", "testserver"), []string{}, "PLAIN", []string{"foo\x00bar\x00baz"}}, 32 {CRAMMD5Auth("user", "pass"), []string{"<123456.1322876914@testserver>"}, "CRAM-MD5", []string{"", "user 287eb355114cf5c471c26a875f1ca4ae"}}, 33 } 34 35 func TestAuth(t *testing.T) { 36 testLoop: 37 for i, test := range authTests { 38 name, resp, err := test.auth.Start(&ServerInfo{"testserver", true, nil}) 39 if name != test.name { 40 t.Errorf("#%d got name %s, expected %s", i, name, test.name) 41 } 42 if !bytes.Equal(resp, []byte(test.responses[0])) { 43 t.Errorf("#%d got response %s, expected %s", i, resp, test.responses[0]) 44 } 45 if err != nil { 46 t.Errorf("#%d error: %s", i, err) 47 } 48 for j := range test.challenges { 49 challenge := []byte(test.challenges[j]) 50 expected := []byte(test.responses[j+1]) 51 resp, err := test.auth.Next(challenge, true) 52 if err != nil { 53 t.Errorf("#%d error: %s", i, err) 54 continue testLoop 55 } 56 if !bytes.Equal(resp, expected) { 57 t.Errorf("#%d got %s, expected %s", i, resp, expected) 58 continue testLoop 59 } 60 } 61 } 62 } 63 64 func TestAuthPlain(t *testing.T) { 65 66 tests := []struct { 67 authName string 68 server *ServerInfo 69 err string 70 }{ 71 { 72 authName: "servername", 73 server: &ServerInfo{Name: "servername", TLS: true}, 74 }, 75 { 76 // OK to use PlainAuth on localhost without TLS 77 authName: "localhost", 78 server: &ServerInfo{Name: "localhost", TLS: false}, 79 }, 80 { 81 // NOT OK on non-localhost, even if server says PLAIN is OK. 82 // (We don't know that the server is the real server.) 83 authName: "servername", 84 server: &ServerInfo{Name: "servername", Auth: []string{"PLAIN"}}, 85 err: "unencrypted connection", 86 }, 87 { 88 authName: "servername", 89 server: &ServerInfo{Name: "servername", Auth: []string{"CRAM-MD5"}}, 90 err: "unencrypted connection", 91 }, 92 { 93 authName: "servername", 94 server: &ServerInfo{Name: "attacker", TLS: true}, 95 err: "wrong host name", 96 }, 97 } 98 for i, tt := range tests { 99 auth := PlainAuth("foo", "bar", "baz", tt.authName) 100 _, _, err := auth.Start(tt.server) 101 got := "" 102 if err != nil { 103 got = err.Error() 104 } 105 if got != tt.err { 106 t.Errorf("%d. got error = %q; want %q", i, got, tt.err) 107 } 108 } 109 } 110 111 // Issue 17794: don't send a trailing space on AUTH command when there's no password. 112 func TestClientAuthTrimSpace(t *testing.T) { 113 server := "220 hello world\r\n" + 114 "200 some more" 115 var wrote bytes.Buffer 116 var fake faker 117 fake.ReadWriter = struct { 118 io.Reader 119 io.Writer 120 }{ 121 strings.NewReader(server), 122 &wrote, 123 } 124 c, err := NewClient(fake, "fake.host") 125 if err != nil { 126 t.Fatalf("NewClient: %v", err) 127 } 128 c.tls = true 129 c.didHello = true 130 c.Auth(toServerEmptyAuth{}) 131 c.Close() 132 if got, want := wrote.String(), "AUTH FOOAUTH\r\n*\r\nQUIT\r\n"; got != want { 133 t.Errorf("wrote %q; want %q", got, want) 134 } 135 } 136 137 // toServerEmptyAuth is an implementation of Auth that only implements 138 // the Start method, and returns "FOOAUTH", nil, nil. Notably, it returns 139 // zero bytes for "toServer" so we can test that we don't send spaces at 140 // the end of the line. See TestClientAuthTrimSpace. 141 type toServerEmptyAuth struct{} 142 143 func (toServerEmptyAuth) Start(server *ServerInfo) (proto string, toServer []byte, err error) { 144 return "FOOAUTH", nil, nil 145 } 146 147 func (toServerEmptyAuth) Next(fromServer []byte, more bool) (toServer []byte, err error) { 148 panic("unexpected call") 149 } 150 151 type faker struct { 152 io.ReadWriter 153 } 154 155 func (f faker) Close() error { return nil } 156 func (f faker) LocalAddr() net.Addr { return nil } 157 func (f faker) RemoteAddr() net.Addr { return nil } 158 func (f faker) SetDeadline(time.Time) error { return nil } 159 func (f faker) SetReadDeadline(time.Time) error { return nil } 160 func (f faker) SetWriteDeadline(time.Time) error { return nil } 161 162 func TestBasic(t *testing.T) { 163 server := strings.Join(strings.Split(basicServer, "\n"), "\r\n") 164 client := strings.Join(strings.Split(basicClient, "\n"), "\r\n") 165 166 var cmdbuf bytes.Buffer 167 bcmdbuf := bufio.NewWriter(&cmdbuf) 168 var fake faker 169 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf) 170 c := &Client{Text: textproto.NewConn(fake), localName: "localhost"} 171 172 if err := c.helo(); err != nil { 173 t.Fatalf("HELO failed: %s", err) 174 } 175 if err := c.ehlo(); err == nil { 176 t.Fatalf("Expected first EHLO to fail") 177 } 178 if err := c.ehlo(); err != nil { 179 t.Fatalf("Second EHLO failed: %s", err) 180 } 181 182 c.didHello = true 183 if ok, args := c.Extension("aUtH"); !ok || args != "LOGIN PLAIN" { 184 t.Fatalf("Expected AUTH supported") 185 } 186 if ok, _ := c.Extension("DSN"); ok { 187 t.Fatalf("Shouldn't support DSN") 188 } 189 190 if err := c.Mail("user@gmail.com"); err == nil { 191 t.Fatalf("MAIL should require authentication") 192 } 193 194 if err := c.Verify("user1@gmail.com"); err == nil { 195 t.Fatalf("First VRFY: expected no verification") 196 } 197 if err := c.Verify("user2@gmail.com>\r\nDATA\r\nAnother injected message body\r\n.\r\nQUIT\r\n"); err == nil { 198 t.Fatalf("VRFY should have failed due to a message injection attempt") 199 } 200 if err := c.Verify("user2@gmail.com"); err != nil { 201 t.Fatalf("Second VRFY: expected verification, got %s", err) 202 } 203 204 // fake TLS so authentication won't complain 205 c.tls = true 206 c.serverName = "smtp.google.com" 207 if err := c.Auth(PlainAuth("", "user", "pass", "smtp.google.com")); err != nil { 208 t.Fatalf("AUTH failed: %s", err) 209 } 210 211 if err := c.Rcpt("golang-nuts@googlegroups.com>\r\nDATA\r\nInjected message body\r\n.\r\nQUIT\r\n"); err == nil { 212 t.Fatalf("RCPT should have failed due to a message injection attempt") 213 } 214 if err := c.Mail("user@gmail.com>\r\nDATA\r\nAnother injected message body\r\n.\r\nQUIT\r\n"); err == nil { 215 t.Fatalf("MAIL should have failed due to a message injection attempt") 216 } 217 if err := c.Mail("user@gmail.com"); err != nil { 218 t.Fatalf("MAIL failed: %s", err) 219 } 220 if err := c.Rcpt("golang-nuts@googlegroups.com"); err != nil { 221 t.Fatalf("RCPT failed: %s", err) 222 } 223 msg := `From: user@gmail.com 224 To: golang-nuts@googlegroups.com 225 Subject: Hooray for Go 226 227 Line 1 228 .Leading dot line . 229 Goodbye.` 230 w, err := c.Data() 231 if err != nil { 232 t.Fatalf("DATA failed: %s", err) 233 } 234 if _, err := w.Write([]byte(msg)); err != nil { 235 t.Fatalf("Data write failed: %s", err) 236 } 237 if err := w.Close(); err != nil { 238 t.Fatalf("Bad data response: %s", err) 239 } 240 241 if err := c.Quit(); err != nil { 242 t.Fatalf("QUIT failed: %s", err) 243 } 244 245 bcmdbuf.Flush() 246 actualcmds := cmdbuf.String() 247 if client != actualcmds { 248 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client) 249 } 250 } 251 252 var basicServer = `250 mx.google.com at your service 253 502 Unrecognized command. 254 250-mx.google.com at your service 255 250-SIZE 35651584 256 250-AUTH LOGIN PLAIN 257 250 8BITMIME 258 530 Authentication required 259 252 Send some mail, I'll try my best 260 250 User is valid 261 235 Accepted 262 250 Sender OK 263 250 Receiver OK 264 354 Go ahead 265 250 Data OK 266 221 OK 267 ` 268 269 var basicClient = `HELO localhost 270 EHLO localhost 271 EHLO localhost 272 MAIL FROM:<user@gmail.com> BODY=8BITMIME 273 VRFY user1@gmail.com 274 VRFY user2@gmail.com 275 AUTH PLAIN AHVzZXIAcGFzcw== 276 MAIL FROM:<user@gmail.com> BODY=8BITMIME 277 RCPT TO:<golang-nuts@googlegroups.com> 278 DATA 279 From: user@gmail.com 280 To: golang-nuts@googlegroups.com 281 Subject: Hooray for Go 282 283 Line 1 284 ..Leading dot line . 285 Goodbye. 286 . 287 QUIT 288 ` 289 290 func TestNewClient(t *testing.T) { 291 server := strings.Join(strings.Split(newClientServer, "\n"), "\r\n") 292 client := strings.Join(strings.Split(newClientClient, "\n"), "\r\n") 293 294 var cmdbuf bytes.Buffer 295 bcmdbuf := bufio.NewWriter(&cmdbuf) 296 out := func() string { 297 bcmdbuf.Flush() 298 return cmdbuf.String() 299 } 300 var fake faker 301 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf) 302 c, err := NewClient(fake, "fake.host") 303 if err != nil { 304 t.Fatalf("NewClient: %v\n(after %v)", err, out()) 305 } 306 defer c.Close() 307 if ok, args := c.Extension("aUtH"); !ok || args != "LOGIN PLAIN" { 308 t.Fatalf("Expected AUTH supported") 309 } 310 if ok, _ := c.Extension("DSN"); ok { 311 t.Fatalf("Shouldn't support DSN") 312 } 313 if err := c.Quit(); err != nil { 314 t.Fatalf("QUIT failed: %s", err) 315 } 316 317 actualcmds := out() 318 if client != actualcmds { 319 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client) 320 } 321 } 322 323 var newClientServer = `220 hello world 324 250-mx.google.com at your service 325 250-SIZE 35651584 326 250-AUTH LOGIN PLAIN 327 250 8BITMIME 328 221 OK 329 ` 330 331 var newClientClient = `EHLO localhost 332 QUIT 333 ` 334 335 func TestNewClient2(t *testing.T) { 336 server := strings.Join(strings.Split(newClient2Server, "\n"), "\r\n") 337 client := strings.Join(strings.Split(newClient2Client, "\n"), "\r\n") 338 339 var cmdbuf bytes.Buffer 340 bcmdbuf := bufio.NewWriter(&cmdbuf) 341 var fake faker 342 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf) 343 c, err := NewClient(fake, "fake.host") 344 if err != nil { 345 t.Fatalf("NewClient: %v", err) 346 } 347 defer c.Close() 348 if ok, _ := c.Extension("DSN"); ok { 349 t.Fatalf("Shouldn't support DSN") 350 } 351 if err := c.Quit(); err != nil { 352 t.Fatalf("QUIT failed: %s", err) 353 } 354 355 bcmdbuf.Flush() 356 actualcmds := cmdbuf.String() 357 if client != actualcmds { 358 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client) 359 } 360 } 361 362 var newClient2Server = `220 hello world 363 502 EH? 364 250-mx.google.com at your service 365 250-SIZE 35651584 366 250-AUTH LOGIN PLAIN 367 250 8BITMIME 368 221 OK 369 ` 370 371 var newClient2Client = `EHLO localhost 372 HELO localhost 373 QUIT 374 ` 375 376 func TestNewClientWithTLS(t *testing.T) { 377 cert, err := tls.X509KeyPair(localhostCert, localhostKey) 378 if err != nil { 379 t.Fatalf("loadcert: %v", err) 380 } 381 382 config := tls.Config{Certificates: []tls.Certificate{cert}} 383 384 ln, err := tls.Listen("tcp", "127.0.0.1:0", &config) 385 if err != nil { 386 ln, err = tls.Listen("tcp", "[::1]:0", &config) 387 if err != nil { 388 t.Fatalf("server: listen: %s", err) 389 } 390 } 391 392 go func() { 393 conn, err := ln.Accept() 394 if err != nil { 395 t.Fatalf("server: accept: %s", err) 396 return 397 } 398 defer conn.Close() 399 400 _, err = conn.Write([]byte("220 SIGNS\r\n")) 401 if err != nil { 402 t.Fatalf("server: write: %s", err) 403 return 404 } 405 }() 406 407 config.InsecureSkipVerify = true 408 conn, err := tls.Dial("tcp", ln.Addr().String(), &config) 409 if err != nil { 410 t.Fatalf("client: dial: %s", err) 411 } 412 defer conn.Close() 413 414 client, err := NewClient(conn, ln.Addr().String()) 415 if err != nil { 416 t.Fatalf("smtp: newclient: %s", err) 417 } 418 if !client.tls { 419 t.Errorf("client.tls Got: %t Expected: %t", client.tls, true) 420 } 421 } 422 423 func TestHello(t *testing.T) { 424 425 if len(helloServer) != len(helloClient) { 426 t.Fatalf("Hello server and client size mismatch") 427 } 428 429 for i := 0; i < len(helloServer); i++ { 430 server := strings.Join(strings.Split(baseHelloServer+helloServer[i], "\n"), "\r\n") 431 client := strings.Join(strings.Split(baseHelloClient+helloClient[i], "\n"), "\r\n") 432 var cmdbuf bytes.Buffer 433 bcmdbuf := bufio.NewWriter(&cmdbuf) 434 var fake faker 435 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf) 436 c, err := NewClient(fake, "fake.host") 437 if err != nil { 438 t.Fatalf("NewClient: %v", err) 439 } 440 defer c.Close() 441 c.localName = "customhost" 442 err = nil 443 444 switch i { 445 case 0: 446 err = c.Hello("hostinjection>\n\rDATA\r\nInjected message body\r\n.\r\nQUIT\r\n") 447 if err == nil { 448 t.Errorf("Expected Hello to be rejected due to a message injection attempt") 449 } 450 err = c.Hello("customhost") 451 case 1: 452 err = c.StartTLS(nil) 453 if err.Error() == "502 Not implemented" { 454 err = nil 455 } 456 case 2: 457 err = c.Verify("test@example.com") 458 case 3: 459 c.tls = true 460 c.serverName = "smtp.google.com" 461 err = c.Auth(PlainAuth("", "user", "pass", "smtp.google.com")) 462 case 4: 463 err = c.Mail("test@example.com") 464 case 5: 465 ok, _ := c.Extension("feature") 466 if ok { 467 t.Errorf("Expected FEATURE not to be supported") 468 } 469 case 6: 470 err = c.Reset() 471 case 7: 472 err = c.Quit() 473 case 8: 474 err = c.Verify("test@example.com") 475 if err != nil { 476 err = c.Hello("customhost") 477 if err != nil { 478 t.Errorf("Want error, got none") 479 } 480 } 481 default: 482 t.Fatalf("Unhandled command") 483 } 484 485 if err != nil { 486 t.Errorf("Command %d failed: %v", i, err) 487 } 488 489 bcmdbuf.Flush() 490 actualcmds := cmdbuf.String() 491 if client != actualcmds { 492 t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client) 493 } 494 } 495 } 496 497 var baseHelloServer = `220 hello world 498 502 EH? 499 250-mx.google.com at your service 500 250 FEATURE 501 ` 502 503 var helloServer = []string{ 504 "", 505 "502 Not implemented\n", 506 "250 User is valid\n", 507 "235 Accepted\n", 508 "250 Sender ok\n", 509 "", 510 "250 Reset ok\n", 511 "221 Goodbye\n", 512 "250 Sender ok\n", 513 } 514 515 var baseHelloClient = `EHLO customhost 516 HELO customhost 517 ` 518 519 var helloClient = []string{ 520 "", 521 "STARTTLS\n", 522 "VRFY test@example.com\n", 523 "AUTH PLAIN AHVzZXIAcGFzcw==\n", 524 "MAIL FROM:<test@example.com>\n", 525 "", 526 "RSET\n", 527 "QUIT\n", 528 "VRFY test@example.com\n", 529 } 530 531 func TestSendMail(t *testing.T) { 532 server := strings.Join(strings.Split(sendMailServer, "\n"), "\r\n") 533 client := strings.Join(strings.Split(sendMailClient, "\n"), "\r\n") 534 var cmdbuf bytes.Buffer 535 bcmdbuf := bufio.NewWriter(&cmdbuf) 536 l, err := net.Listen("tcp", "127.0.0.1:0") 537 if err != nil { 538 t.Fatalf("Unable to to create listener: %v", err) 539 } 540 defer l.Close() 541 542 // prevent data race on bcmdbuf 543 var done = make(chan struct{}) 544 go func(data []string) { 545 546 defer close(done) 547 548 conn, err := l.Accept() 549 if err != nil { 550 t.Errorf("Accept error: %v", err) 551 return 552 } 553 defer conn.Close() 554 555 tc := textproto.NewConn(conn) 556 for i := 0; i < len(data) && data[i] != ""; i++ { 557 tc.PrintfLine(data[i]) 558 for len(data[i]) >= 4 && data[i][3] == '-' { 559 i++ 560 tc.PrintfLine(data[i]) 561 } 562 if data[i] == "221 Goodbye" { 563 return 564 } 565 read := false 566 for !read || data[i] == "354 Go ahead" { 567 msg, err := tc.ReadLine() 568 bcmdbuf.Write([]byte(msg + "\r\n")) 569 read = true 570 if err != nil { 571 t.Errorf("Read error: %v", err) 572 return 573 } 574 if data[i] == "354 Go ahead" && msg == "." { 575 break 576 } 577 } 578 } 579 }(strings.Split(server, "\r\n")) 580 581 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 582 To: other@example.com 583 Subject: SendMail test 584 585 SendMail is working for me. 586 `, "\n", "\r\n", -1))) 587 if err == nil { 588 t.Errorf("Expected SendMail to be rejected due to a message injection attempt") 589 } 590 591 err = SendMail(l.Addr().String(), nil, "test@example.com", []string{"other@example.com"}, []byte(strings.Replace(`From: test@example.com 592 To: other@example.com 593 Subject: SendMail test 594 595 SendMail is working for me. 596 `, "\n", "\r\n", -1))) 597 598 if err != nil { 599 t.Errorf("%v", err) 600 } 601 602 <-done 603 bcmdbuf.Flush() 604 actualcmds := cmdbuf.String() 605 if client != actualcmds { 606 t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client) 607 } 608 } 609 610 var sendMailServer = `220 hello world 611 502 EH? 612 250 mx.google.com at your service 613 250 Sender ok 614 250 Receiver ok 615 354 Go ahead 616 250 Data ok 617 221 Goodbye 618 ` 619 620 var sendMailClient = `EHLO localhost 621 HELO localhost 622 MAIL FROM:<test@example.com> 623 RCPT TO:<other@example.com> 624 DATA 625 From: test@example.com 626 To: other@example.com 627 Subject: SendMail test 628 629 SendMail is working for me. 630 . 631 QUIT 632 ` 633 634 func TestAuthFailed(t *testing.T) { 635 server := strings.Join(strings.Split(authFailedServer, "\n"), "\r\n") 636 client := strings.Join(strings.Split(authFailedClient, "\n"), "\r\n") 637 var cmdbuf bytes.Buffer 638 bcmdbuf := bufio.NewWriter(&cmdbuf) 639 var fake faker 640 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf) 641 c, err := NewClient(fake, "fake.host") 642 if err != nil { 643 t.Fatalf("NewClient: %v", err) 644 } 645 defer c.Close() 646 647 c.tls = true 648 c.serverName = "smtp.google.com" 649 err = c.Auth(PlainAuth("", "user", "pass", "smtp.google.com")) 650 651 if err == nil { 652 t.Error("Auth: expected error; got none") 653 } else if err.Error() != "535 Invalid credentials\nplease see www.example.com" { 654 t.Errorf("Auth: got error: %v, want: %s", err, "535 Invalid credentials\nplease see www.example.com") 655 } 656 657 bcmdbuf.Flush() 658 actualcmds := cmdbuf.String() 659 if client != actualcmds { 660 t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client) 661 } 662 } 663 664 var authFailedServer = `220 hello world 665 250-mx.google.com at your service 666 250 AUTH LOGIN PLAIN 667 535-Invalid credentials 668 535 please see www.example.com 669 221 Goodbye 670 ` 671 672 var authFailedClient = `EHLO localhost 673 AUTH PLAIN AHVzZXIAcGFzcw== 674 * 675 QUIT 676 ` 677 678 func TestTLSClient(t *testing.T) { 679 if runtime.GOOS == "freebsd" && runtime.GOARCH == "amd64" { 680 testenv.SkipFlaky(t, 19229) 681 } 682 ln := newLocalListener(t) 683 defer ln.Close() 684 errc := make(chan error) 685 go func() { 686 errc <- sendMail(ln.Addr().String()) 687 }() 688 conn, err := ln.Accept() 689 if err != nil { 690 t.Fatalf("failed to accept connection: %v", err) 691 } 692 defer conn.Close() 693 if err := serverHandle(conn, t); err != nil { 694 t.Fatalf("failed to handle connection: %v", err) 695 } 696 if err := <-errc; err != nil { 697 t.Fatalf("client error: %v", err) 698 } 699 } 700 701 func TestTLSConnState(t *testing.T) { 702 ln := newLocalListener(t) 703 defer ln.Close() 704 clientDone := make(chan bool) 705 serverDone := make(chan bool) 706 go func() { 707 defer close(serverDone) 708 c, err := ln.Accept() 709 if err != nil { 710 t.Errorf("Server accept: %v", err) 711 return 712 } 713 defer c.Close() 714 if err := serverHandle(c, t); err != nil { 715 t.Errorf("server error: %v", err) 716 } 717 }() 718 go func() { 719 defer close(clientDone) 720 c, err := Dial(ln.Addr().String()) 721 if err != nil { 722 t.Errorf("Client dial: %v", err) 723 return 724 } 725 defer c.Quit() 726 cfg := &tls.Config{ServerName: "example.com"} 727 testHookStartTLS(cfg) // set the RootCAs 728 if err := c.StartTLS(cfg); err != nil { 729 t.Errorf("StartTLS: %v", err) 730 return 731 } 732 cs, ok := c.TLSConnectionState() 733 if !ok { 734 t.Errorf("TLSConnectionState returned ok == false; want true") 735 return 736 } 737 if cs.Version == 0 || !cs.HandshakeComplete { 738 t.Errorf("ConnectionState = %#v; expect non-zero Version and HandshakeComplete", cs) 739 } 740 }() 741 <-clientDone 742 <-serverDone 743 } 744 745 func newLocalListener(t *testing.T) net.Listener { 746 ln, err := net.Listen("tcp", "127.0.0.1:0") 747 if err != nil { 748 ln, err = net.Listen("tcp6", "[::1]:0") 749 } 750 if err != nil { 751 t.Fatal(err) 752 } 753 return ln 754 } 755 756 type smtpSender struct { 757 w io.Writer 758 } 759 760 func (s smtpSender) send(f string) { 761 s.w.Write([]byte(f + "\r\n")) 762 } 763 764 // smtp server, finely tailored to deal with our own client only! 765 func serverHandle(c net.Conn, t *testing.T) error { 766 send := smtpSender{c}.send 767 send("220 127.0.0.1 ESMTP service ready") 768 s := bufio.NewScanner(c) 769 for s.Scan() { 770 switch s.Text() { 771 case "EHLO localhost": 772 send("250-127.0.0.1 ESMTP offers a warm hug of welcome") 773 send("250-STARTTLS") 774 send("250 Ok") 775 case "STARTTLS": 776 send("220 Go ahead") 777 keypair, err := tls.X509KeyPair(localhostCert, localhostKey) 778 if err != nil { 779 return err 780 } 781 config := &tls.Config{Certificates: []tls.Certificate{keypair}} 782 c = tls.Server(c, config) 783 defer c.Close() 784 return serverHandleTLS(c, t) 785 default: 786 t.Fatalf("unrecognized command: %q", s.Text()) 787 } 788 } 789 return s.Err() 790 } 791 792 func serverHandleTLS(c net.Conn, t *testing.T) error { 793 send := smtpSender{c}.send 794 s := bufio.NewScanner(c) 795 for s.Scan() { 796 switch s.Text() { 797 case "EHLO localhost": 798 send("250 Ok") 799 case "MAIL FROM:<joe1@example.com>": 800 send("250 Ok") 801 case "RCPT TO:<joe2@example.com>": 802 send("250 Ok") 803 case "DATA": 804 send("354 send the mail data, end with .") 805 send("250 Ok") 806 case "Subject: test": 807 case "": 808 case "howdy!": 809 case ".": 810 case "QUIT": 811 send("221 127.0.0.1 Service closing transmission channel") 812 return nil 813 default: 814 t.Fatalf("unrecognized command during TLS: %q", s.Text()) 815 } 816 } 817 return s.Err() 818 } 819 820 func init() { 821 testRootCAs := x509.NewCertPool() 822 testRootCAs.AppendCertsFromPEM(localhostCert) 823 testHookStartTLS = func(config *tls.Config) { 824 config.RootCAs = testRootCAs 825 } 826 } 827 828 func sendMail(hostPort string) error { 829 host, _, err := net.SplitHostPort(hostPort) 830 if err != nil { 831 return err 832 } 833 auth := PlainAuth("", "", "", host) 834 from := "joe1@example.com" 835 to := []string{"joe2@example.com"} 836 return SendMail(hostPort, auth, from, to, []byte("Subject: test\n\nhowdy!")) 837 } 838 839 // (copied from net/http/httptest) 840 // localhostCert is a PEM-encoded TLS cert with SAN IPs 841 // "127.0.0.1" and "[::1]", expiring at the last second of 2049 (the end 842 // of ASN.1 time). 843 // generated from src/crypto/tls: 844 // 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 845 var localhostCert = []byte(`-----BEGIN CERTIFICATE----- 846 MIIBjjCCATigAwIBAgIQMon9v0s3pDFXvAMnPgelpzANBgkqhkiG9w0BAQsFADAS 847 MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw 848 MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB 849 AM0u/mNXKkhAzNsFkwKZPSpC4lZZaePQ55IyaJv3ovMM2smvthnlqaUfVKVmz7FF 850 wLP9csX6vGtvkZg1uWAtvfkCAwEAAaNoMGYwDgYDVR0PAQH/BAQDAgKkMBMGA1Ud 851 JQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8wLgYDVR0RBCcwJYILZXhh 852 bXBsZS5jb22HBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQELBQAD 853 QQBOZsFVC7IwX+qibmSbt2IPHkUgXhfbq0a9MYhD6tHcj4gbDcTXh4kZCbgHCz22 854 gfSj2/G2wxzopoISVDucuncj 855 -----END CERTIFICATE-----`) 856 857 // localhostKey is the private key for localhostCert. 858 var localhostKey = []byte(`-----BEGIN RSA PRIVATE KEY----- 859 MIIBOwIBAAJBAM0u/mNXKkhAzNsFkwKZPSpC4lZZaePQ55IyaJv3ovMM2smvthnl 860 qaUfVKVmz7FFwLP9csX6vGtvkZg1uWAtvfkCAwEAAQJART2qkxODLUbQ2siSx7m2 861 rmBLyR/7X+nLe8aPDrMOxj3heDNl4YlaAYLexbcY8d7VDfCRBKYoAOP0UCP1Vhuf 862 UQIhAO6PEI55K3SpNIdc2k5f0xz+9rodJCYzu51EwWX7r8ufAiEA3C9EkLiU2NuK 863 3L3DHCN5IlUSN1Nr/lw8NIt50Yorj2cCIQCDw1VbvCV6bDLtSSXzAA51B4ZzScE7 864 sHtB5EYF9Dwm9QIhAJuCquuH4mDzVjUntXjXOQPdj7sRqVGCNWdrJwOukat7AiAy 865 LXLEwb77DIPoI5ZuaXQC+MnyyJj1ExC9RFcGz+bexA== 866 -----END RSA PRIVATE KEY-----`)