github.com/guyezi/gofrontend@v0.0.0-20200228202240-7a62a49e62c0/libgo/go/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 "fmt" 13 "internal/testenv" 14 "io" 15 "net" 16 "net/textproto" 17 "runtime" 18 "strings" 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 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 create listener: %v", err) 643 } 644 defer l.Close() 645 646 errCh := make(chan error) 647 go func() { 648 defer close(errCh) 649 conn, err := l.Accept() 650 if err != nil { 651 errCh <- fmt.Errorf("Accept: %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 err != nil { 660 errCh <- fmt.Errorf("ReadLine error: %v", err) 661 return 662 } 663 const wantMsg = "EHLO localhost" 664 if msg != wantMsg { 665 errCh <- fmt.Errorf("unexpected response %q; want %q", msg, wantMsg) 666 return 667 } 668 err = tc.PrintfLine("250 mx.google.com at your service") 669 if err != nil { 670 errCh <- fmt.Errorf("PrintfLine: %v", err) 671 return 672 } 673 }() 674 675 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 676 To: other@example.com 677 Subject: SendMail test 678 679 SendMail is working for me. 680 `, "\n", "\r\n", -1))) 681 if err == nil { 682 t.Error("SendMail: Server doesn't support AUTH, expected to get an error, but got none ") 683 } 684 if err.Error() != "smtp: server doesn't support AUTH" { 685 t.Errorf("Expected: smtp: server doesn't support AUTH, got: %s", err) 686 } 687 err = <-errCh 688 if err != nil { 689 t.Fatalf("server error: %v", err) 690 } 691 } 692 693 func TestAuthFailed(t *testing.T) { 694 server := strings.Join(strings.Split(authFailedServer, "\n"), "\r\n") 695 client := strings.Join(strings.Split(authFailedClient, "\n"), "\r\n") 696 var cmdbuf bytes.Buffer 697 bcmdbuf := bufio.NewWriter(&cmdbuf) 698 var fake faker 699 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf) 700 c, err := NewClient(fake, "fake.host") 701 if err != nil { 702 t.Fatalf("NewClient: %v", err) 703 } 704 defer c.Close() 705 706 c.tls = true 707 c.serverName = "smtp.google.com" 708 err = c.Auth(PlainAuth("", "user", "pass", "smtp.google.com")) 709 710 if err == nil { 711 t.Error("Auth: expected error; got none") 712 } else if err.Error() != "535 Invalid credentials\nplease see www.example.com" { 713 t.Errorf("Auth: got error: %v, want: %s", err, "535 Invalid credentials\nplease see www.example.com") 714 } 715 716 bcmdbuf.Flush() 717 actualcmds := cmdbuf.String() 718 if client != actualcmds { 719 t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client) 720 } 721 } 722 723 var authFailedServer = `220 hello world 724 250-mx.google.com at your service 725 250 AUTH LOGIN PLAIN 726 535-Invalid credentials 727 535 please see www.example.com 728 221 Goodbye 729 ` 730 731 var authFailedClient = `EHLO localhost 732 AUTH PLAIN AHVzZXIAcGFzcw== 733 * 734 QUIT 735 ` 736 737 func TestTLSClient(t *testing.T) { 738 if (runtime.GOOS == "freebsd" && runtime.GOARCH == "amd64") || runtime.GOOS == "js" { 739 testenv.SkipFlaky(t, 19229) 740 } 741 ln := newLocalListener(t) 742 defer ln.Close() 743 errc := make(chan error) 744 go func() { 745 errc <- sendMail(ln.Addr().String()) 746 }() 747 conn, err := ln.Accept() 748 if err != nil { 749 t.Fatalf("failed to accept connection: %v", err) 750 } 751 defer conn.Close() 752 if err := serverHandle(conn, t); err != nil { 753 t.Fatalf("failed to handle connection: %v", err) 754 } 755 if err := <-errc; err != nil { 756 t.Fatalf("client error: %v", err) 757 } 758 } 759 760 func TestTLSConnState(t *testing.T) { 761 ln := newLocalListener(t) 762 defer ln.Close() 763 clientDone := make(chan bool) 764 serverDone := make(chan bool) 765 go func() { 766 defer close(serverDone) 767 c, err := ln.Accept() 768 if err != nil { 769 t.Errorf("Server accept: %v", err) 770 return 771 } 772 defer c.Close() 773 if err := serverHandle(c, t); err != nil { 774 t.Errorf("server error: %v", err) 775 } 776 }() 777 go func() { 778 defer close(clientDone) 779 c, err := Dial(ln.Addr().String()) 780 if err != nil { 781 t.Errorf("Client dial: %v", err) 782 return 783 } 784 defer c.Quit() 785 cfg := &tls.Config{ServerName: "example.com"} 786 testHookStartTLS(cfg) // set the RootCAs 787 if err := c.StartTLS(cfg); err != nil { 788 t.Errorf("StartTLS: %v", err) 789 return 790 } 791 cs, ok := c.TLSConnectionState() 792 if !ok { 793 t.Errorf("TLSConnectionState returned ok == false; want true") 794 return 795 } 796 if cs.Version == 0 || !cs.HandshakeComplete { 797 t.Errorf("ConnectionState = %#v; expect non-zero Version and HandshakeComplete", cs) 798 } 799 }() 800 <-clientDone 801 <-serverDone 802 } 803 804 func newLocalListener(t *testing.T) net.Listener { 805 ln, err := net.Listen("tcp", "127.0.0.1:0") 806 if err != nil { 807 ln, err = net.Listen("tcp6", "[::1]:0") 808 } 809 if err != nil { 810 t.Fatal(err) 811 } 812 return ln 813 } 814 815 type smtpSender struct { 816 w io.Writer 817 } 818 819 func (s smtpSender) send(f string) { 820 s.w.Write([]byte(f + "\r\n")) 821 } 822 823 // smtp server, finely tailored to deal with our own client only! 824 func serverHandle(c net.Conn, t *testing.T) error { 825 send := smtpSender{c}.send 826 send("220 127.0.0.1 ESMTP service ready") 827 s := bufio.NewScanner(c) 828 for s.Scan() { 829 switch s.Text() { 830 case "EHLO localhost": 831 send("250-127.0.0.1 ESMTP offers a warm hug of welcome") 832 send("250-STARTTLS") 833 send("250 Ok") 834 case "STARTTLS": 835 send("220 Go ahead") 836 keypair, err := tls.X509KeyPair(localhostCert, localhostKey) 837 if err != nil { 838 return err 839 } 840 config := &tls.Config{Certificates: []tls.Certificate{keypair}} 841 c = tls.Server(c, config) 842 defer c.Close() 843 return serverHandleTLS(c, t) 844 default: 845 t.Fatalf("unrecognized command: %q", s.Text()) 846 } 847 } 848 return s.Err() 849 } 850 851 func serverHandleTLS(c net.Conn, t *testing.T) error { 852 send := smtpSender{c}.send 853 s := bufio.NewScanner(c) 854 for s.Scan() { 855 switch s.Text() { 856 case "EHLO localhost": 857 send("250 Ok") 858 case "MAIL FROM:<joe1@example.com>": 859 send("250 Ok") 860 case "RCPT TO:<joe2@example.com>": 861 send("250 Ok") 862 case "DATA": 863 send("354 send the mail data, end with .") 864 send("250 Ok") 865 case "Subject: test": 866 case "": 867 case "howdy!": 868 case ".": 869 case "QUIT": 870 send("221 127.0.0.1 Service closing transmission channel") 871 return nil 872 default: 873 t.Fatalf("unrecognized command during TLS: %q", s.Text()) 874 } 875 } 876 return s.Err() 877 } 878 879 func init() { 880 testRootCAs := x509.NewCertPool() 881 testRootCAs.AppendCertsFromPEM(localhostCert) 882 testHookStartTLS = func(config *tls.Config) { 883 config.RootCAs = testRootCAs 884 } 885 } 886 887 func sendMail(hostPort string) error { 888 from := "joe1@example.com" 889 to := []string{"joe2@example.com"} 890 return SendMail(hostPort, nil, from, to, []byte("Subject: test\n\nhowdy!")) 891 } 892 893 // localhostCert is a PEM-encoded TLS cert generated from src/crypto/tls: 894 // go run generate_cert.go --rsa-bits 1024 --host 127.0.0.1,::1,example.com \ 895 // --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h 896 var localhostCert = []byte(` 897 -----BEGIN CERTIFICATE----- 898 MIICFDCCAX2gAwIBAgIRAK0xjnaPuNDSreeXb+z+0u4wDQYJKoZIhvcNAQELBQAw 899 EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2 900 MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzCBnzANBgkqhkiG9w0BAQEFAAOBjQAw 901 gYkCgYEA0nFbQQuOWsjbGtejcpWz153OlziZM4bVjJ9jYruNw5n2Ry6uYQAffhqa 902 JOInCmmcVe2siJglsyH9aRh6vKiobBbIUXXUU1ABd56ebAzlt0LobLlx7pZEMy30 903 LqIi9E6zmL3YvdGzpYlkFRnRrqwEtWYbGBf3znO250S56CCWH2UCAwEAAaNoMGYw 904 DgYDVR0PAQH/BAQDAgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQF 905 MAMBAf8wLgYDVR0RBCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAAAAAAAAAA 906 AAAAAAEwDQYJKoZIhvcNAQELBQADgYEAbZtDS2dVuBYvb+MnolWnCNqvw1w5Gtgi 907 NmvQQPOMgM3m+oQSCPRTNGSg25e1Qbo7bgQDv8ZTnq8FgOJ/rbkyERw2JckkHpD4 908 n4qcK27WkEDBtQFlPihIM8hLIuzWoi/9wygiElTy/tVL3y7fGCvY2/k1KBthtZGF 909 tN8URjVmyEo= 910 -----END CERTIFICATE-----`) 911 912 // localhostKey is the private key for localhostCert. 913 var localhostKey = []byte(testingKey(` 914 -----BEGIN RSA TESTING KEY----- 915 MIICXgIBAAKBgQDScVtBC45ayNsa16NylbPXnc6XOJkzhtWMn2Niu43DmfZHLq5h 916 AB9+Gpok4icKaZxV7ayImCWzIf1pGHq8qKhsFshRddRTUAF3np5sDOW3QuhsuXHu 917 lkQzLfQuoiL0TrOYvdi90bOliWQVGdGurAS1ZhsYF/fOc7bnRLnoIJYfZQIDAQAB 918 AoGBAMst7OgpKyFV6c3JwyI/jWqxDySL3caU+RuTTBaodKAUx2ZEmNJIlx9eudLA 919 kucHvoxsM/eRxlxkhdFxdBcwU6J+zqooTnhu/FE3jhrT1lPrbhfGhyKnUrB0KKMM 920 VY3IQZyiehpxaeXAwoAou6TbWoTpl9t8ImAqAMY8hlULCUqlAkEA+9+Ry5FSYK/m 921 542LujIcCaIGoG1/Te6Sxr3hsPagKC2rH20rDLqXwEedSFOpSS0vpzlPAzy/6Rbb 922 PHTJUhNdwwJBANXkA+TkMdbJI5do9/mn//U0LfrCR9NkcoYohxfKz8JuhgRQxzF2 923 6jpo3q7CdTuuRixLWVfeJzcrAyNrVcBq87cCQFkTCtOMNC7fZnCTPUv+9q1tcJyB 924 vNjJu3yvoEZeIeuzouX9TJE21/33FaeDdsXbRhQEj23cqR38qFHsF1qAYNMCQQDP 925 QXLEiJoClkR2orAmqjPLVhR3t2oB3INcnEjLNSq8LHyQEfXyaFfu4U9l5+fRPL2i 926 jiC0k/9L5dHUsF0XZothAkEA23ddgRs+Id/HxtojqqUT27B8MT/IGNrYsp4DvS/c 927 qgkeluku4GjxRlDMBuXk94xOBEinUs+p/hwP1Alll80Tpg== 928 -----END RSA TESTING KEY-----`)) 929 930 func testingKey(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") }