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 (a] gmail.com"); err == nil { 191 t.Fatalf("MAIL should require authentication") 192 } 193 194 if err := c.Verify("user1 (a] gmail.com"); err == nil { 195 t.Fatalf("First VRFY: expected no verification") 196 } 197 if err := c.Verify("user2 (a] 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 (a] 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 (a] 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 (a] 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 (a] gmail.com"); err != nil { 218 t.Fatalf("MAIL failed: %s", err) 219 } 220 if err := c.Rcpt("golang-nuts (a] googlegroups.com"); err != nil { 221 t.Fatalf("RCPT failed: %s", err) 222 } 223 msg := `From: user (a] gmail.com 224 To: golang-nuts (a] 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 (a] gmail.com> BODY=8BITMIME 273 VRFY user1 (a] gmail.com 274 VRFY user2 (a] gmail.com 275 AUTH PLAIN AHVzZXIAcGFzcw== 276 MAIL FROM:<user (a] gmail.com> BODY=8BITMIME 277 RCPT TO:<golang-nuts (a] googlegroups.com> 278 DATA 279 From: user (a] gmail.com 280 To: golang-nuts (a] 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: %v", err) 389 } 390 } 391 392 go func() { 393 conn, err := ln.Accept() 394 if err != nil { 395 t.Errorf("server: accept: %v", err) 396 return 397 } 398 defer conn.Close() 399 400 _, err = conn.Write([]byte("220 SIGNS\r\n")) 401 if err != nil { 402 t.Errorf("server: write: %v", 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: %v", err) 411 } 412 defer conn.Close() 413 414 client, err := NewClient(conn, ln.Addr().String()) 415 if err != nil { 416 t.Fatalf("smtp: newclient: %v", 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 (a] 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 (a] 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 (a] 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 case 9: 482 err = c.Noop() 483 default: 484 t.Fatalf("Unhandled command") 485 } 486 487 if err != nil { 488 t.Errorf("Command %d failed: %v", i, err) 489 } 490 491 bcmdbuf.Flush() 492 actualcmds := cmdbuf.String() 493 if client != actualcmds { 494 t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client) 495 } 496 } 497 } 498 499 var baseHelloServer = `220 hello world 500 502 EH? 501 250-mx.google.com at your service 502 250 FEATURE 503 ` 504 505 var helloServer = []string{ 506 "", 507 "502 Not implemented\n", 508 "250 User is valid\n", 509 "235 Accepted\n", 510 "250 Sender ok\n", 511 "", 512 "250 Reset ok\n", 513 "221 Goodbye\n", 514 "250 Sender ok\n", 515 "250 ok\n", 516 } 517 518 var baseHelloClient = `EHLO customhost 519 HELO customhost 520 ` 521 522 var helloClient = []string{ 523 "", 524 "STARTTLS\n", 525 "VRFY test (a] example.com\n", 526 "AUTH PLAIN AHVzZXIAcGFzcw==\n", 527 "MAIL FROM:<test (a] example.com>\n", 528 "", 529 "RSET\n", 530 "QUIT\n", 531 "VRFY test (a] example.com\n", 532 "NOOP\n", 533 } 534 535 func TestSendMail(t *testing.T) { 536 server := strings.Join(strings.Split(sendMailServer, "\n"), "\r\n") 537 client := strings.Join(strings.Split(sendMailClient, "\n"), "\r\n") 538 var cmdbuf bytes.Buffer 539 bcmdbuf := bufio.NewWriter(&cmdbuf) 540 l, err := net.Listen("tcp", "127.0.0.1:0") 541 if err != nil { 542 t.Fatalf("Unable to to create listener: %v", err) 543 } 544 defer l.Close() 545 546 // prevent data race on bcmdbuf 547 var done = make(chan struct{}) 548 go func(data []string) { 549 550 defer close(done) 551 552 conn, err := l.Accept() 553 if err != nil { 554 t.Errorf("Accept error: %v", err) 555 return 556 } 557 defer conn.Close() 558 559 tc := textproto.NewConn(conn) 560 for i := 0; i < len(data) && data[i] != ""; i++ { 561 tc.PrintfLine(data[i]) 562 for len(data[i]) >= 4 && data[i][3] == '-' { 563 i++ 564 tc.PrintfLine(data[i]) 565 } 566 if data[i] == "221 Goodbye" { 567 return 568 } 569 read := false 570 for !read || data[i] == "354 Go ahead" { 571 msg, err := tc.ReadLine() 572 bcmdbuf.Write([]byte(msg + "\r\n")) 573 read = true 574 if err != nil { 575 t.Errorf("Read error: %v", err) 576 return 577 } 578 if data[i] == "354 Go ahead" && msg == "." { 579 break 580 } 581 } 582 } 583 }(strings.Split(server, "\r\n")) 584 585 err = SendMail(l.Addr().String(), nil, "test (a] example.com", []string{"other (a] example.com>\n\rDATA\r\nInjected message body\r\n.\r\nQUIT\r\n"}, []byte(strings.Replace(`From: test (a] example.com 586 To: other (a] example.com 587 Subject: SendMail test 588 589 SendMail is working for me. 590 `, "\n", "\r\n", -1))) 591 if err == nil { 592 t.Errorf("Expected SendMail to be rejected due to a message injection attempt") 593 } 594 595 err = SendMail(l.Addr().String(), nil, "test (a] example.com", []string{"other (a] example.com"}, []byte(strings.Replace(`From: test (a] example.com 596 To: other (a] example.com 597 Subject: SendMail test 598 599 SendMail is working for me. 600 `, "\n", "\r\n", -1))) 601 602 if err != nil { 603 t.Errorf("%v", err) 604 } 605 606 <-done 607 bcmdbuf.Flush() 608 actualcmds := cmdbuf.String() 609 if client != actualcmds { 610 t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client) 611 } 612 } 613 614 var sendMailServer = `220 hello world 615 502 EH? 616 250 mx.google.com at your service 617 250 Sender ok 618 250 Receiver ok 619 354 Go ahead 620 250 Data ok 621 221 Goodbye 622 ` 623 624 var sendMailClient = `EHLO localhost 625 HELO localhost 626 MAIL FROM:<test (a] example.com> 627 RCPT TO:<other (a] example.com> 628 DATA 629 From: test (a] example.com 630 To: other (a] example.com 631 Subject: SendMail test 632 633 SendMail is working for me. 634 . 635 QUIT 636 ` 637 638 func TestAuthFailed(t *testing.T) { 639 server := strings.Join(strings.Split(authFailedServer, "\n"), "\r\n") 640 client := strings.Join(strings.Split(authFailedClient, "\n"), "\r\n") 641 var cmdbuf bytes.Buffer 642 bcmdbuf := bufio.NewWriter(&cmdbuf) 643 var fake faker 644 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf) 645 c, err := NewClient(fake, "fake.host") 646 if err != nil { 647 t.Fatalf("NewClient: %v", err) 648 } 649 defer c.Close() 650 651 c.tls = true 652 c.serverName = "smtp.google.com" 653 err = c.Auth(PlainAuth("", "user", "pass", "smtp.google.com")) 654 655 if err == nil { 656 t.Error("Auth: expected error; got none") 657 } else if err.Error() != "535 Invalid credentials\nplease see www.example.com" { 658 t.Errorf("Auth: got error: %v, want: %s", err, "535 Invalid credentials\nplease see www.example.com") 659 } 660 661 bcmdbuf.Flush() 662 actualcmds := cmdbuf.String() 663 if client != actualcmds { 664 t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client) 665 } 666 } 667 668 var authFailedServer = `220 hello world 669 250-mx.google.com at your service 670 250 AUTH LOGIN PLAIN 671 535-Invalid credentials 672 535 please see www.example.com 673 221 Goodbye 674 ` 675 676 var authFailedClient = `EHLO localhost 677 AUTH PLAIN AHVzZXIAcGFzcw== 678 * 679 QUIT 680 ` 681 682 func TestTLSClient(t *testing.T) { 683 if runtime.GOOS == "freebsd" && runtime.GOARCH == "amd64" { 684 testenv.SkipFlaky(t, 19229) 685 } 686 ln := newLocalListener(t) 687 defer ln.Close() 688 errc := make(chan error) 689 go func() { 690 errc <- sendMail(ln.Addr().String()) 691 }() 692 conn, err := ln.Accept() 693 if err != nil { 694 t.Fatalf("failed to accept connection: %v", err) 695 } 696 defer conn.Close() 697 if err := serverHandle(conn, t); err != nil { 698 t.Fatalf("failed to handle connection: %v", err) 699 } 700 if err := <-errc; err != nil { 701 t.Fatalf("client error: %v", err) 702 } 703 } 704 705 func TestTLSConnState(t *testing.T) { 706 ln := newLocalListener(t) 707 defer ln.Close() 708 clientDone := make(chan bool) 709 serverDone := make(chan bool) 710 go func() { 711 defer close(serverDone) 712 c, err := ln.Accept() 713 if err != nil { 714 t.Errorf("Server accept: %v", err) 715 return 716 } 717 defer c.Close() 718 if err := serverHandle(c, t); err != nil { 719 t.Errorf("server error: %v", err) 720 } 721 }() 722 go func() { 723 defer close(clientDone) 724 c, err := Dial(ln.Addr().String()) 725 if err != nil { 726 t.Errorf("Client dial: %v", err) 727 return 728 } 729 defer c.Quit() 730 cfg := &tls.Config{ServerName: "example.com"} 731 testHookStartTLS(cfg) // set the RootCAs 732 if err := c.StartTLS(cfg); err != nil { 733 t.Errorf("StartTLS: %v", err) 734 return 735 } 736 cs, ok := c.TLSConnectionState() 737 if !ok { 738 t.Errorf("TLSConnectionState returned ok == false; want true") 739 return 740 } 741 if cs.Version == 0 || !cs.HandshakeComplete { 742 t.Errorf("ConnectionState = %#v; expect non-zero Version and HandshakeComplete", cs) 743 } 744 }() 745 <-clientDone 746 <-serverDone 747 } 748 749 func newLocalListener(t *testing.T) net.Listener { 750 ln, err := net.Listen("tcp", "127.0.0.1:0") 751 if err != nil { 752 ln, err = net.Listen("tcp6", "[::1]:0") 753 } 754 if err != nil { 755 t.Fatal(err) 756 } 757 return ln 758 } 759 760 type smtpSender struct { 761 w io.Writer 762 } 763 764 func (s smtpSender) send(f string) { 765 s.w.Write([]byte(f + "\r\n")) 766 } 767 768 // smtp server, finely tailored to deal with our own client only! 769 func serverHandle(c net.Conn, t *testing.T) error { 770 send := smtpSender{c}.send 771 send("220 127.0.0.1 ESMTP service ready") 772 s := bufio.NewScanner(c) 773 for s.Scan() { 774 switch s.Text() { 775 case "EHLO localhost": 776 send("250-127.0.0.1 ESMTP offers a warm hug of welcome") 777 send("250-STARTTLS") 778 send("250 Ok") 779 case "STARTTLS": 780 send("220 Go ahead") 781 keypair, err := tls.X509KeyPair(localhostCert, localhostKey) 782 if err != nil { 783 return err 784 } 785 config := &tls.Config{Certificates: []tls.Certificate{keypair}} 786 c = tls.Server(c, config) 787 defer c.Close() 788 return serverHandleTLS(c, t) 789 default: 790 t.Fatalf("unrecognized command: %q", s.Text()) 791 } 792 } 793 return s.Err() 794 } 795 796 func serverHandleTLS(c net.Conn, t *testing.T) error { 797 send := smtpSender{c}.send 798 s := bufio.NewScanner(c) 799 for s.Scan() { 800 switch s.Text() { 801 case "EHLO localhost": 802 send("250 Ok") 803 case "MAIL FROM:<joe1 (a] example.com>": 804 send("250 Ok") 805 case "RCPT TO:<joe2 (a] example.com>": 806 send("250 Ok") 807 case "DATA": 808 send("354 send the mail data, end with .") 809 send("250 Ok") 810 case "Subject: test": 811 case "": 812 case "howdy!": 813 case ".": 814 case "QUIT": 815 send("221 127.0.0.1 Service closing transmission channel") 816 return nil 817 default: 818 t.Fatalf("unrecognized command during TLS: %q", s.Text()) 819 } 820 } 821 return s.Err() 822 } 823 824 func init() { 825 testRootCAs := x509.NewCertPool() 826 testRootCAs.AppendCertsFromPEM(localhostCert) 827 testHookStartTLS = func(config *tls.Config) { 828 config.RootCAs = testRootCAs 829 } 830 } 831 832 func sendMail(hostPort string) error { 833 host, _, err := net.SplitHostPort(hostPort) 834 if err != nil { 835 return err 836 } 837 auth := PlainAuth("", "", "", host) 838 from := "joe1 (a] example.com" 839 to := []string{"joe2 (a] example.com"} 840 return SendMail(hostPort, auth, from, to, []byte("Subject: test\n\nhowdy!")) 841 } 842 843 // (copied from net/http/httptest) 844 // localhostCert is a PEM-encoded TLS cert with SAN IPs 845 // "127.0.0.1" and "[::1]", expiring at the last second of 2049 (the end 846 // of ASN.1 time). 847 // generated from src/crypto/tls: 848 // 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 849 var localhostCert = []byte(`-----BEGIN CERTIFICATE----- 850 MIIBjjCCATigAwIBAgIQMon9v0s3pDFXvAMnPgelpzANBgkqhkiG9w0BAQsFADAS 851 MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw 852 MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB 853 AM0u/mNXKkhAzNsFkwKZPSpC4lZZaePQ55IyaJv3ovMM2smvthnlqaUfVKVmz7FF 854 wLP9csX6vGtvkZg1uWAtvfkCAwEAAaNoMGYwDgYDVR0PAQH/BAQDAgKkMBMGA1Ud 855 JQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8wLgYDVR0RBCcwJYILZXhh 856 bXBsZS5jb22HBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQELBQAD 857 QQBOZsFVC7IwX+qibmSbt2IPHkUgXhfbq0a9MYhD6tHcj4gbDcTXh4kZCbgHCz22 858 gfSj2/G2wxzopoISVDucuncj 859 -----END CERTIFICATE-----`) 860 861 // localhostKey is the private key for localhostCert. 862 var localhostKey = []byte(`-----BEGIN RSA PRIVATE KEY----- 863 MIIBOwIBAAJBAM0u/mNXKkhAzNsFkwKZPSpC4lZZaePQ55IyaJv3ovMM2smvthnl 864 qaUfVKVmz7FFwLP9csX6vGtvkZg1uWAtvfkCAwEAAQJART2qkxODLUbQ2siSx7m2 865 rmBLyR/7X+nLe8aPDrMOxj3heDNl4YlaAYLexbcY8d7VDfCRBKYoAOP0UCP1Vhuf 866 UQIhAO6PEI55K3SpNIdc2k5f0xz+9rodJCYzu51EwWX7r8ufAiEA3C9EkLiU2NuK 867 3L3DHCN5IlUSN1Nr/lw8NIt50Yorj2cCIQCDw1VbvCV6bDLtSSXzAA51B4ZzScE7 868 sHtB5EYF9Dwm9QIhAJuCquuH4mDzVjUntXjXOQPdj7sRqVGCNWdrJwOukat7AiAy 869 LXLEwb77DIPoI5ZuaXQC+MnyyJj1ExC9RFcGz+bexA== 870 -----END RSA PRIVATE KEY-----`) 871