github.com/zebozhuang/go@v0.0.0-20200207033046-f8a98f6f5c5d/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"); err != nil { 198 t.Fatalf("Second VRFY: expected verification, got %s", err) 199 } 200 201 // fake TLS so authentication won't complain 202 c.tls = true 203 c.serverName = "smtp.google.com" 204 if err := c.Auth(PlainAuth("", "user", "pass", "smtp.google.com")); err != nil { 205 t.Fatalf("AUTH failed: %s", err) 206 } 207 208 if err := c.Mail("user@gmail.com"); err != nil { 209 t.Fatalf("MAIL failed: %s", err) 210 } 211 if err := c.Rcpt("golang-nuts@googlegroups.com"); err != nil { 212 t.Fatalf("RCPT failed: %s", err) 213 } 214 msg := `From: user@gmail.com 215 To: golang-nuts@googlegroups.com 216 Subject: Hooray for Go 217 218 Line 1 219 .Leading dot line . 220 Goodbye.` 221 w, err := c.Data() 222 if err != nil { 223 t.Fatalf("DATA failed: %s", err) 224 } 225 if _, err := w.Write([]byte(msg)); err != nil { 226 t.Fatalf("Data write failed: %s", err) 227 } 228 if err := w.Close(); err != nil { 229 t.Fatalf("Bad data response: %s", err) 230 } 231 232 if err := c.Quit(); err != nil { 233 t.Fatalf("QUIT failed: %s", err) 234 } 235 236 bcmdbuf.Flush() 237 actualcmds := cmdbuf.String() 238 if client != actualcmds { 239 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client) 240 } 241 } 242 243 var basicServer = `250 mx.google.com at your service 244 502 Unrecognized command. 245 250-mx.google.com at your service 246 250-SIZE 35651584 247 250-AUTH LOGIN PLAIN 248 250 8BITMIME 249 530 Authentication required 250 252 Send some mail, I'll try my best 251 250 User is valid 252 235 Accepted 253 250 Sender OK 254 250 Receiver OK 255 354 Go ahead 256 250 Data OK 257 221 OK 258 ` 259 260 var basicClient = `HELO localhost 261 EHLO localhost 262 EHLO localhost 263 MAIL FROM:<user@gmail.com> BODY=8BITMIME 264 VRFY user1@gmail.com 265 VRFY user2@gmail.com 266 AUTH PLAIN AHVzZXIAcGFzcw== 267 MAIL FROM:<user@gmail.com> BODY=8BITMIME 268 RCPT TO:<golang-nuts@googlegroups.com> 269 DATA 270 From: user@gmail.com 271 To: golang-nuts@googlegroups.com 272 Subject: Hooray for Go 273 274 Line 1 275 ..Leading dot line . 276 Goodbye. 277 . 278 QUIT 279 ` 280 281 func TestNewClient(t *testing.T) { 282 server := strings.Join(strings.Split(newClientServer, "\n"), "\r\n") 283 client := strings.Join(strings.Split(newClientClient, "\n"), "\r\n") 284 285 var cmdbuf bytes.Buffer 286 bcmdbuf := bufio.NewWriter(&cmdbuf) 287 out := func() string { 288 bcmdbuf.Flush() 289 return cmdbuf.String() 290 } 291 var fake faker 292 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf) 293 c, err := NewClient(fake, "fake.host") 294 if err != nil { 295 t.Fatalf("NewClient: %v\n(after %v)", err, out()) 296 } 297 defer c.Close() 298 if ok, args := c.Extension("aUtH"); !ok || args != "LOGIN PLAIN" { 299 t.Fatalf("Expected AUTH supported") 300 } 301 if ok, _ := c.Extension("DSN"); ok { 302 t.Fatalf("Shouldn't support DSN") 303 } 304 if err := c.Quit(); err != nil { 305 t.Fatalf("QUIT failed: %s", err) 306 } 307 308 actualcmds := out() 309 if client != actualcmds { 310 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client) 311 } 312 } 313 314 var newClientServer = `220 hello world 315 250-mx.google.com at your service 316 250-SIZE 35651584 317 250-AUTH LOGIN PLAIN 318 250 8BITMIME 319 221 OK 320 ` 321 322 var newClientClient = `EHLO localhost 323 QUIT 324 ` 325 326 func TestNewClient2(t *testing.T) { 327 server := strings.Join(strings.Split(newClient2Server, "\n"), "\r\n") 328 client := strings.Join(strings.Split(newClient2Client, "\n"), "\r\n") 329 330 var cmdbuf bytes.Buffer 331 bcmdbuf := bufio.NewWriter(&cmdbuf) 332 var fake faker 333 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf) 334 c, err := NewClient(fake, "fake.host") 335 if err != nil { 336 t.Fatalf("NewClient: %v", err) 337 } 338 defer c.Close() 339 if ok, _ := c.Extension("DSN"); ok { 340 t.Fatalf("Shouldn't support DSN") 341 } 342 if err := c.Quit(); err != nil { 343 t.Fatalf("QUIT failed: %s", err) 344 } 345 346 bcmdbuf.Flush() 347 actualcmds := cmdbuf.String() 348 if client != actualcmds { 349 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client) 350 } 351 } 352 353 var newClient2Server = `220 hello world 354 502 EH? 355 250-mx.google.com at your service 356 250-SIZE 35651584 357 250-AUTH LOGIN PLAIN 358 250 8BITMIME 359 221 OK 360 ` 361 362 var newClient2Client = `EHLO localhost 363 HELO localhost 364 QUIT 365 ` 366 367 func TestNewClientWithTLS(t *testing.T) { 368 cert, err := tls.X509KeyPair(localhostCert, localhostKey) 369 if err != nil { 370 t.Fatalf("loadcert: %v", err) 371 } 372 373 config := tls.Config{Certificates: []tls.Certificate{cert}} 374 375 ln, err := tls.Listen("tcp", "127.0.0.1:0", &config) 376 if err != nil { 377 ln, err = tls.Listen("tcp", "[::1]:0", &config) 378 if err != nil { 379 t.Fatalf("server: listen: %s", err) 380 } 381 } 382 383 go func() { 384 conn, err := ln.Accept() 385 if err != nil { 386 t.Fatalf("server: accept: %s", err) 387 return 388 } 389 defer conn.Close() 390 391 _, err = conn.Write([]byte("220 SIGNS\r\n")) 392 if err != nil { 393 t.Fatalf("server: write: %s", err) 394 return 395 } 396 }() 397 398 config.InsecureSkipVerify = true 399 conn, err := tls.Dial("tcp", ln.Addr().String(), &config) 400 if err != nil { 401 t.Fatalf("client: dial: %s", err) 402 } 403 defer conn.Close() 404 405 client, err := NewClient(conn, ln.Addr().String()) 406 if err != nil { 407 t.Fatalf("smtp: newclient: %s", err) 408 } 409 if !client.tls { 410 t.Errorf("client.tls Got: %t Expected: %t", client.tls, true) 411 } 412 } 413 414 func TestHello(t *testing.T) { 415 416 if len(helloServer) != len(helloClient) { 417 t.Fatalf("Hello server and client size mismatch") 418 } 419 420 for i := 0; i < len(helloServer); i++ { 421 server := strings.Join(strings.Split(baseHelloServer+helloServer[i], "\n"), "\r\n") 422 client := strings.Join(strings.Split(baseHelloClient+helloClient[i], "\n"), "\r\n") 423 var cmdbuf bytes.Buffer 424 bcmdbuf := bufio.NewWriter(&cmdbuf) 425 var fake faker 426 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf) 427 c, err := NewClient(fake, "fake.host") 428 if err != nil { 429 t.Fatalf("NewClient: %v", err) 430 } 431 defer c.Close() 432 c.localName = "customhost" 433 err = nil 434 435 switch i { 436 case 0: 437 err = c.Hello("customhost") 438 case 1: 439 err = c.StartTLS(nil) 440 if err.Error() == "502 Not implemented" { 441 err = nil 442 } 443 case 2: 444 err = c.Verify("test@example.com") 445 case 3: 446 c.tls = true 447 c.serverName = "smtp.google.com" 448 err = c.Auth(PlainAuth("", "user", "pass", "smtp.google.com")) 449 case 4: 450 err = c.Mail("test@example.com") 451 case 5: 452 ok, _ := c.Extension("feature") 453 if ok { 454 t.Errorf("Expected FEATURE not to be supported") 455 } 456 case 6: 457 err = c.Reset() 458 case 7: 459 err = c.Quit() 460 case 8: 461 err = c.Verify("test@example.com") 462 if err != nil { 463 err = c.Hello("customhost") 464 if err != nil { 465 t.Errorf("Want error, got none") 466 } 467 } 468 default: 469 t.Fatalf("Unhandled command") 470 } 471 472 if err != nil { 473 t.Errorf("Command %d failed: %v", i, err) 474 } 475 476 bcmdbuf.Flush() 477 actualcmds := cmdbuf.String() 478 if client != actualcmds { 479 t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client) 480 } 481 } 482 } 483 484 var baseHelloServer = `220 hello world 485 502 EH? 486 250-mx.google.com at your service 487 250 FEATURE 488 ` 489 490 var helloServer = []string{ 491 "", 492 "502 Not implemented\n", 493 "250 User is valid\n", 494 "235 Accepted\n", 495 "250 Sender ok\n", 496 "", 497 "250 Reset ok\n", 498 "221 Goodbye\n", 499 "250 Sender ok\n", 500 } 501 502 var baseHelloClient = `EHLO customhost 503 HELO customhost 504 ` 505 506 var helloClient = []string{ 507 "", 508 "STARTTLS\n", 509 "VRFY test@example.com\n", 510 "AUTH PLAIN AHVzZXIAcGFzcw==\n", 511 "MAIL FROM:<test@example.com>\n", 512 "", 513 "RSET\n", 514 "QUIT\n", 515 "VRFY test@example.com\n", 516 } 517 518 func TestSendMail(t *testing.T) { 519 server := strings.Join(strings.Split(sendMailServer, "\n"), "\r\n") 520 client := strings.Join(strings.Split(sendMailClient, "\n"), "\r\n") 521 var cmdbuf bytes.Buffer 522 bcmdbuf := bufio.NewWriter(&cmdbuf) 523 l, err := net.Listen("tcp", "127.0.0.1:0") 524 if err != nil { 525 t.Fatalf("Unable to to create listener: %v", err) 526 } 527 defer l.Close() 528 529 // prevent data race on bcmdbuf 530 var done = make(chan struct{}) 531 go func(data []string) { 532 533 defer close(done) 534 535 conn, err := l.Accept() 536 if err != nil { 537 t.Errorf("Accept error: %v", err) 538 return 539 } 540 defer conn.Close() 541 542 tc := textproto.NewConn(conn) 543 for i := 0; i < len(data) && data[i] != ""; i++ { 544 tc.PrintfLine(data[i]) 545 for len(data[i]) >= 4 && data[i][3] == '-' { 546 i++ 547 tc.PrintfLine(data[i]) 548 } 549 if data[i] == "221 Goodbye" { 550 return 551 } 552 read := false 553 for !read || data[i] == "354 Go ahead" { 554 msg, err := tc.ReadLine() 555 bcmdbuf.Write([]byte(msg + "\r\n")) 556 read = true 557 if err != nil { 558 t.Errorf("Read error: %v", err) 559 return 560 } 561 if data[i] == "354 Go ahead" && msg == "." { 562 break 563 } 564 } 565 } 566 }(strings.Split(server, "\r\n")) 567 568 err = SendMail(l.Addr().String(), nil, "test@example.com", []string{"other@example.com"}, []byte(strings.Replace(`From: test@example.com 569 To: other@example.com 570 Subject: SendMail test 571 572 SendMail is working for me. 573 `, "\n", "\r\n", -1))) 574 575 if err != nil { 576 t.Errorf("%v", err) 577 } 578 579 <-done 580 bcmdbuf.Flush() 581 actualcmds := cmdbuf.String() 582 if client != actualcmds { 583 t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client) 584 } 585 } 586 587 var sendMailServer = `220 hello world 588 502 EH? 589 250 mx.google.com at your service 590 250 Sender ok 591 250 Receiver ok 592 354 Go ahead 593 250 Data ok 594 221 Goodbye 595 ` 596 597 var sendMailClient = `EHLO localhost 598 HELO localhost 599 MAIL FROM:<test@example.com> 600 RCPT TO:<other@example.com> 601 DATA 602 From: test@example.com 603 To: other@example.com 604 Subject: SendMail test 605 606 SendMail is working for me. 607 . 608 QUIT 609 ` 610 611 func TestAuthFailed(t *testing.T) { 612 server := strings.Join(strings.Split(authFailedServer, "\n"), "\r\n") 613 client := strings.Join(strings.Split(authFailedClient, "\n"), "\r\n") 614 var cmdbuf bytes.Buffer 615 bcmdbuf := bufio.NewWriter(&cmdbuf) 616 var fake faker 617 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf) 618 c, err := NewClient(fake, "fake.host") 619 if err != nil { 620 t.Fatalf("NewClient: %v", err) 621 } 622 defer c.Close() 623 624 c.tls = true 625 c.serverName = "smtp.google.com" 626 err = c.Auth(PlainAuth("", "user", "pass", "smtp.google.com")) 627 628 if err == nil { 629 t.Error("Auth: expected error; got none") 630 } else if err.Error() != "535 Invalid credentials\nplease see www.example.com" { 631 t.Errorf("Auth: got error: %v, want: %s", err, "535 Invalid credentials\nplease see www.example.com") 632 } 633 634 bcmdbuf.Flush() 635 actualcmds := cmdbuf.String() 636 if client != actualcmds { 637 t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client) 638 } 639 } 640 641 var authFailedServer = `220 hello world 642 250-mx.google.com at your service 643 250 AUTH LOGIN PLAIN 644 535-Invalid credentials 645 535 please see www.example.com 646 221 Goodbye 647 ` 648 649 var authFailedClient = `EHLO localhost 650 AUTH PLAIN AHVzZXIAcGFzcw== 651 * 652 QUIT 653 ` 654 655 func TestTLSClient(t *testing.T) { 656 if runtime.GOOS == "freebsd" && runtime.GOARCH == "amd64" { 657 testenv.SkipFlaky(t, 19229) 658 } 659 ln := newLocalListener(t) 660 defer ln.Close() 661 errc := make(chan error) 662 go func() { 663 errc <- sendMail(ln.Addr().String()) 664 }() 665 conn, err := ln.Accept() 666 if err != nil { 667 t.Fatalf("failed to accept connection: %v", err) 668 } 669 defer conn.Close() 670 if err := serverHandle(conn, t); err != nil { 671 t.Fatalf("failed to handle connection: %v", err) 672 } 673 if err := <-errc; err != nil { 674 t.Fatalf("client error: %v", err) 675 } 676 } 677 678 func TestTLSConnState(t *testing.T) { 679 ln := newLocalListener(t) 680 defer ln.Close() 681 clientDone := make(chan bool) 682 serverDone := make(chan bool) 683 go func() { 684 defer close(serverDone) 685 c, err := ln.Accept() 686 if err != nil { 687 t.Errorf("Server accept: %v", err) 688 return 689 } 690 defer c.Close() 691 if err := serverHandle(c, t); err != nil { 692 t.Errorf("server error: %v", err) 693 } 694 }() 695 go func() { 696 defer close(clientDone) 697 c, err := Dial(ln.Addr().String()) 698 if err != nil { 699 t.Errorf("Client dial: %v", err) 700 return 701 } 702 defer c.Quit() 703 cfg := &tls.Config{ServerName: "example.com"} 704 testHookStartTLS(cfg) // set the RootCAs 705 if err := c.StartTLS(cfg); err != nil { 706 t.Errorf("StartTLS: %v", err) 707 return 708 } 709 cs, ok := c.TLSConnectionState() 710 if !ok { 711 t.Errorf("TLSConnectionState returned ok == false; want true") 712 return 713 } 714 if cs.Version == 0 || !cs.HandshakeComplete { 715 t.Errorf("ConnectionState = %#v; expect non-zero Version and HandshakeComplete", cs) 716 } 717 }() 718 <-clientDone 719 <-serverDone 720 } 721 722 func newLocalListener(t *testing.T) net.Listener { 723 ln, err := net.Listen("tcp", "127.0.0.1:0") 724 if err != nil { 725 ln, err = net.Listen("tcp6", "[::1]:0") 726 } 727 if err != nil { 728 t.Fatal(err) 729 } 730 return ln 731 } 732 733 type smtpSender struct { 734 w io.Writer 735 } 736 737 func (s smtpSender) send(f string) { 738 s.w.Write([]byte(f + "\r\n")) 739 } 740 741 // smtp server, finely tailored to deal with our own client only! 742 func serverHandle(c net.Conn, t *testing.T) error { 743 send := smtpSender{c}.send 744 send("220 127.0.0.1 ESMTP service ready") 745 s := bufio.NewScanner(c) 746 for s.Scan() { 747 switch s.Text() { 748 case "EHLO localhost": 749 send("250-127.0.0.1 ESMTP offers a warm hug of welcome") 750 send("250-STARTTLS") 751 send("250 Ok") 752 case "STARTTLS": 753 send("220 Go ahead") 754 keypair, err := tls.X509KeyPair(localhostCert, localhostKey) 755 if err != nil { 756 return err 757 } 758 config := &tls.Config{Certificates: []tls.Certificate{keypair}} 759 c = tls.Server(c, config) 760 defer c.Close() 761 return serverHandleTLS(c, t) 762 default: 763 t.Fatalf("unrecognized command: %q", s.Text()) 764 } 765 } 766 return s.Err() 767 } 768 769 func serverHandleTLS(c net.Conn, t *testing.T) error { 770 send := smtpSender{c}.send 771 s := bufio.NewScanner(c) 772 for s.Scan() { 773 switch s.Text() { 774 case "EHLO localhost": 775 send("250 Ok") 776 case "MAIL FROM:<joe1@example.com>": 777 send("250 Ok") 778 case "RCPT TO:<joe2@example.com>": 779 send("250 Ok") 780 case "DATA": 781 send("354 send the mail data, end with .") 782 send("250 Ok") 783 case "Subject: test": 784 case "": 785 case "howdy!": 786 case ".": 787 case "QUIT": 788 send("221 127.0.0.1 Service closing transmission channel") 789 return nil 790 default: 791 t.Fatalf("unrecognized command during TLS: %q", s.Text()) 792 } 793 } 794 return s.Err() 795 } 796 797 func init() { 798 testRootCAs := x509.NewCertPool() 799 testRootCAs.AppendCertsFromPEM(localhostCert) 800 testHookStartTLS = func(config *tls.Config) { 801 config.RootCAs = testRootCAs 802 } 803 } 804 805 func sendMail(hostPort string) error { 806 host, _, err := net.SplitHostPort(hostPort) 807 if err != nil { 808 return err 809 } 810 auth := PlainAuth("", "", "", host) 811 from := "joe1@example.com" 812 to := []string{"joe2@example.com"} 813 return SendMail(hostPort, auth, from, to, []byte("Subject: test\n\nhowdy!")) 814 } 815 816 // (copied from net/http/httptest) 817 // localhostCert is a PEM-encoded TLS cert with SAN IPs 818 // "127.0.0.1" and "[::1]", expiring at the last second of 2049 (the end 819 // of ASN.1 time). 820 // generated from src/crypto/tls: 821 // 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 822 var localhostCert = []byte(`-----BEGIN CERTIFICATE----- 823 MIIBjjCCATigAwIBAgIQMon9v0s3pDFXvAMnPgelpzANBgkqhkiG9w0BAQsFADAS 824 MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw 825 MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB 826 AM0u/mNXKkhAzNsFkwKZPSpC4lZZaePQ55IyaJv3ovMM2smvthnlqaUfVKVmz7FF 827 wLP9csX6vGtvkZg1uWAtvfkCAwEAAaNoMGYwDgYDVR0PAQH/BAQDAgKkMBMGA1Ud 828 JQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8wLgYDVR0RBCcwJYILZXhh 829 bXBsZS5jb22HBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQELBQAD 830 QQBOZsFVC7IwX+qibmSbt2IPHkUgXhfbq0a9MYhD6tHcj4gbDcTXh4kZCbgHCz22 831 gfSj2/G2wxzopoISVDucuncj 832 -----END CERTIFICATE-----`) 833 834 // localhostKey is the private key for localhostCert. 835 var localhostKey = []byte(`-----BEGIN RSA PRIVATE KEY----- 836 MIIBOwIBAAJBAM0u/mNXKkhAzNsFkwKZPSpC4lZZaePQ55IyaJv3ovMM2smvthnl 837 qaUfVKVmz7FFwLP9csX6vGtvkZg1uWAtvfkCAwEAAQJART2qkxODLUbQ2siSx7m2 838 rmBLyR/7X+nLe8aPDrMOxj3heDNl4YlaAYLexbcY8d7VDfCRBKYoAOP0UCP1Vhuf 839 UQIhAO6PEI55K3SpNIdc2k5f0xz+9rodJCYzu51EwWX7r8ufAiEA3C9EkLiU2NuK 840 3L3DHCN5IlUSN1Nr/lw8NIt50Yorj2cCIQCDw1VbvCV6bDLtSSXzAA51B4ZzScE7 841 sHtB5EYF9Dwm9QIhAJuCquuH4mDzVjUntXjXOQPdj7sRqVGCNWdrJwOukat7AiAy 842 LXLEwb77DIPoI5ZuaXQC+MnyyJj1ExC9RFcGz+bexA== 843 -----END RSA PRIVATE KEY-----`)