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