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