github.com/twelsh-aw/go/src@v0.0.0-20230516233729-a56fe86a7c81/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 strings.Builder 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 strings.Builder 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 TestExtensions(t *testing.T) { 292 fake := func(server string) (c *Client, bcmdbuf *bufio.Writer, cmdbuf *strings.Builder) { 293 server = strings.Join(strings.Split(server, "\n"), "\r\n") 294 295 cmdbuf = &strings.Builder{} 296 bcmdbuf = bufio.NewWriter(cmdbuf) 297 var fake faker 298 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf) 299 c = &Client{Text: textproto.NewConn(fake), localName: "localhost"} 300 301 return c, bcmdbuf, cmdbuf 302 } 303 304 t.Run("helo", func(t *testing.T) { 305 const ( 306 basicServer = `250 mx.google.com at your service 307 250 Sender OK 308 221 Goodbye 309 ` 310 311 basicClient = `HELO localhost 312 MAIL FROM:<user@gmail.com> 313 QUIT 314 ` 315 ) 316 317 c, bcmdbuf, cmdbuf := fake(basicServer) 318 319 if err := c.helo(); err != nil { 320 t.Fatalf("HELO failed: %s", err) 321 } 322 c.didHello = true 323 if err := c.Mail("user@gmail.com"); err != nil { 324 t.Fatalf("MAIL FROM failed: %s", err) 325 } 326 if err := c.Quit(); err != nil { 327 t.Fatalf("QUIT failed: %s", err) 328 } 329 330 bcmdbuf.Flush() 331 actualcmds := cmdbuf.String() 332 client := strings.Join(strings.Split(basicClient, "\n"), "\r\n") 333 if client != actualcmds { 334 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client) 335 } 336 }) 337 338 t.Run("ehlo", func(t *testing.T) { 339 const ( 340 basicServer = `250-mx.google.com at your service 341 250 SIZE 35651584 342 250 Sender OK 343 221 Goodbye 344 ` 345 346 basicClient = `EHLO localhost 347 MAIL FROM:<user@gmail.com> 348 QUIT 349 ` 350 ) 351 352 c, bcmdbuf, cmdbuf := fake(basicServer) 353 354 if err := c.Hello("localhost"); err != nil { 355 t.Fatalf("EHLO failed: %s", err) 356 } 357 if ok, _ := c.Extension("8BITMIME"); ok { 358 t.Fatalf("Shouldn't support 8BITMIME") 359 } 360 if ok, _ := c.Extension("SMTPUTF8"); ok { 361 t.Fatalf("Shouldn't support SMTPUTF8") 362 } 363 if err := c.Mail("user@gmail.com"); err != nil { 364 t.Fatalf("MAIL FROM failed: %s", err) 365 } 366 if err := c.Quit(); err != nil { 367 t.Fatalf("QUIT failed: %s", err) 368 } 369 370 bcmdbuf.Flush() 371 actualcmds := cmdbuf.String() 372 client := strings.Join(strings.Split(basicClient, "\n"), "\r\n") 373 if client != actualcmds { 374 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client) 375 } 376 }) 377 378 t.Run("ehlo 8bitmime", func(t *testing.T) { 379 const ( 380 basicServer = `250-mx.google.com at your service 381 250-SIZE 35651584 382 250 8BITMIME 383 250 Sender OK 384 221 Goodbye 385 ` 386 387 basicClient = `EHLO localhost 388 MAIL FROM:<user@gmail.com> BODY=8BITMIME 389 QUIT 390 ` 391 ) 392 393 c, bcmdbuf, cmdbuf := fake(basicServer) 394 395 if err := c.Hello("localhost"); err != nil { 396 t.Fatalf("EHLO failed: %s", err) 397 } 398 if ok, _ := c.Extension("8BITMIME"); !ok { 399 t.Fatalf("Should support 8BITMIME") 400 } 401 if ok, _ := c.Extension("SMTPUTF8"); ok { 402 t.Fatalf("Shouldn't support SMTPUTF8") 403 } 404 if err := c.Mail("user@gmail.com"); err != nil { 405 t.Fatalf("MAIL FROM failed: %s", err) 406 } 407 if err := c.Quit(); err != nil { 408 t.Fatalf("QUIT failed: %s", err) 409 } 410 411 bcmdbuf.Flush() 412 actualcmds := cmdbuf.String() 413 client := strings.Join(strings.Split(basicClient, "\n"), "\r\n") 414 if client != actualcmds { 415 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client) 416 } 417 }) 418 419 t.Run("ehlo smtputf8", func(t *testing.T) { 420 const ( 421 basicServer = `250-mx.google.com at your service 422 250-SIZE 35651584 423 250 SMTPUTF8 424 250 Sender OK 425 221 Goodbye 426 ` 427 428 basicClient = `EHLO localhost 429 MAIL FROM:<user+📧@gmail.com> SMTPUTF8 430 QUIT 431 ` 432 ) 433 434 c, bcmdbuf, cmdbuf := fake(basicServer) 435 436 if err := c.Hello("localhost"); err != nil { 437 t.Fatalf("EHLO failed: %s", err) 438 } 439 if ok, _ := c.Extension("8BITMIME"); ok { 440 t.Fatalf("Shouldn't support 8BITMIME") 441 } 442 if ok, _ := c.Extension("SMTPUTF8"); !ok { 443 t.Fatalf("Should support SMTPUTF8") 444 } 445 if err := c.Mail("user+📧@gmail.com"); err != nil { 446 t.Fatalf("MAIL FROM failed: %s", err) 447 } 448 if err := c.Quit(); err != nil { 449 t.Fatalf("QUIT failed: %s", err) 450 } 451 452 bcmdbuf.Flush() 453 actualcmds := cmdbuf.String() 454 client := strings.Join(strings.Split(basicClient, "\n"), "\r\n") 455 if client != actualcmds { 456 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client) 457 } 458 }) 459 460 t.Run("ehlo 8bitmime smtputf8", func(t *testing.T) { 461 const ( 462 basicServer = `250-mx.google.com at your service 463 250-SIZE 35651584 464 250-8BITMIME 465 250 SMTPUTF8 466 250 Sender OK 467 221 Goodbye 468 ` 469 470 basicClient = `EHLO localhost 471 MAIL FROM:<user+📧@gmail.com> BODY=8BITMIME SMTPUTF8 472 QUIT 473 ` 474 ) 475 476 c, bcmdbuf, cmdbuf := fake(basicServer) 477 478 if err := c.Hello("localhost"); err != nil { 479 t.Fatalf("EHLO failed: %s", err) 480 } 481 c.didHello = true 482 if ok, _ := c.Extension("8BITMIME"); !ok { 483 t.Fatalf("Should support 8BITMIME") 484 } 485 if ok, _ := c.Extension("SMTPUTF8"); !ok { 486 t.Fatalf("Should support SMTPUTF8") 487 } 488 if err := c.Mail("user+📧@gmail.com"); err != nil { 489 t.Fatalf("MAIL FROM failed: %s", err) 490 } 491 if err := c.Quit(); err != nil { 492 t.Fatalf("QUIT failed: %s", err) 493 } 494 495 bcmdbuf.Flush() 496 actualcmds := cmdbuf.String() 497 client := strings.Join(strings.Split(basicClient, "\n"), "\r\n") 498 if client != actualcmds { 499 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client) 500 } 501 }) 502 } 503 504 func TestNewClient(t *testing.T) { 505 server := strings.Join(strings.Split(newClientServer, "\n"), "\r\n") 506 client := strings.Join(strings.Split(newClientClient, "\n"), "\r\n") 507 508 var cmdbuf strings.Builder 509 bcmdbuf := bufio.NewWriter(&cmdbuf) 510 out := func() string { 511 bcmdbuf.Flush() 512 return cmdbuf.String() 513 } 514 var fake faker 515 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf) 516 c, err := NewClient(fake, "fake.host") 517 if err != nil { 518 t.Fatalf("NewClient: %v\n(after %v)", err, out()) 519 } 520 defer c.Close() 521 if ok, args := c.Extension("aUtH"); !ok || args != "LOGIN PLAIN" { 522 t.Fatalf("Expected AUTH supported") 523 } 524 if ok, _ := c.Extension("DSN"); ok { 525 t.Fatalf("Shouldn't support DSN") 526 } 527 if err := c.Quit(); err != nil { 528 t.Fatalf("QUIT failed: %s", err) 529 } 530 531 actualcmds := out() 532 if client != actualcmds { 533 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client) 534 } 535 } 536 537 var newClientServer = `220 hello world 538 250-mx.google.com at your service 539 250-SIZE 35651584 540 250-AUTH LOGIN PLAIN 541 250 8BITMIME 542 221 OK 543 ` 544 545 var newClientClient = `EHLO localhost 546 QUIT 547 ` 548 549 func TestNewClient2(t *testing.T) { 550 server := strings.Join(strings.Split(newClient2Server, "\n"), "\r\n") 551 client := strings.Join(strings.Split(newClient2Client, "\n"), "\r\n") 552 553 var cmdbuf strings.Builder 554 bcmdbuf := bufio.NewWriter(&cmdbuf) 555 var fake faker 556 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf) 557 c, err := NewClient(fake, "fake.host") 558 if err != nil { 559 t.Fatalf("NewClient: %v", err) 560 } 561 defer c.Close() 562 if ok, _ := c.Extension("DSN"); ok { 563 t.Fatalf("Shouldn't support DSN") 564 } 565 if err := c.Quit(); err != nil { 566 t.Fatalf("QUIT failed: %s", err) 567 } 568 569 bcmdbuf.Flush() 570 actualcmds := cmdbuf.String() 571 if client != actualcmds { 572 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client) 573 } 574 } 575 576 var newClient2Server = `220 hello world 577 502 EH? 578 250-mx.google.com at your service 579 250-SIZE 35651584 580 250-AUTH LOGIN PLAIN 581 250 8BITMIME 582 221 OK 583 ` 584 585 var newClient2Client = `EHLO localhost 586 HELO localhost 587 QUIT 588 ` 589 590 func TestNewClientWithTLS(t *testing.T) { 591 cert, err := tls.X509KeyPair(localhostCert, localhostKey) 592 if err != nil { 593 t.Fatalf("loadcert: %v", err) 594 } 595 596 config := tls.Config{Certificates: []tls.Certificate{cert}} 597 598 ln, err := tls.Listen("tcp", "127.0.0.1:0", &config) 599 if err != nil { 600 ln, err = tls.Listen("tcp", "[::1]:0", &config) 601 if err != nil { 602 t.Fatalf("server: listen: %v", err) 603 } 604 } 605 606 go func() { 607 conn, err := ln.Accept() 608 if err != nil { 609 t.Errorf("server: accept: %v", err) 610 return 611 } 612 defer conn.Close() 613 614 _, err = conn.Write([]byte("220 SIGNS\r\n")) 615 if err != nil { 616 t.Errorf("server: write: %v", err) 617 return 618 } 619 }() 620 621 config.InsecureSkipVerify = true 622 conn, err := tls.Dial("tcp", ln.Addr().String(), &config) 623 if err != nil { 624 t.Fatalf("client: dial: %v", err) 625 } 626 defer conn.Close() 627 628 client, err := NewClient(conn, ln.Addr().String()) 629 if err != nil { 630 t.Fatalf("smtp: newclient: %v", err) 631 } 632 if !client.tls { 633 t.Errorf("client.tls Got: %t Expected: %t", client.tls, true) 634 } 635 } 636 637 func TestHello(t *testing.T) { 638 639 if len(helloServer) != len(helloClient) { 640 t.Fatalf("Hello server and client size mismatch") 641 } 642 643 for i := 0; i < len(helloServer); i++ { 644 server := strings.Join(strings.Split(baseHelloServer+helloServer[i], "\n"), "\r\n") 645 client := strings.Join(strings.Split(baseHelloClient+helloClient[i], "\n"), "\r\n") 646 var cmdbuf strings.Builder 647 bcmdbuf := bufio.NewWriter(&cmdbuf) 648 var fake faker 649 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf) 650 c, err := NewClient(fake, "fake.host") 651 if err != nil { 652 t.Fatalf("NewClient: %v", err) 653 } 654 defer c.Close() 655 c.localName = "customhost" 656 err = nil 657 658 switch i { 659 case 0: 660 err = c.Hello("hostinjection>\n\rDATA\r\nInjected message body\r\n.\r\nQUIT\r\n") 661 if err == nil { 662 t.Errorf("Expected Hello to be rejected due to a message injection attempt") 663 } 664 err = c.Hello("customhost") 665 case 1: 666 err = c.StartTLS(nil) 667 if err.Error() == "502 Not implemented" { 668 err = nil 669 } 670 case 2: 671 err = c.Verify("test@example.com") 672 case 3: 673 c.tls = true 674 c.serverName = "smtp.google.com" 675 err = c.Auth(PlainAuth("", "user", "pass", "smtp.google.com")) 676 case 4: 677 err = c.Mail("test@example.com") 678 case 5: 679 ok, _ := c.Extension("feature") 680 if ok { 681 t.Errorf("Expected FEATURE not to be supported") 682 } 683 case 6: 684 err = c.Reset() 685 case 7: 686 err = c.Quit() 687 case 8: 688 err = c.Verify("test@example.com") 689 if err != nil { 690 err = c.Hello("customhost") 691 if err != nil { 692 t.Errorf("Want error, got none") 693 } 694 } 695 case 9: 696 err = c.Noop() 697 default: 698 t.Fatalf("Unhandled command") 699 } 700 701 if err != nil { 702 t.Errorf("Command %d failed: %v", i, err) 703 } 704 705 bcmdbuf.Flush() 706 actualcmds := cmdbuf.String() 707 if client != actualcmds { 708 t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client) 709 } 710 } 711 } 712 713 var baseHelloServer = `220 hello world 714 502 EH? 715 250-mx.google.com at your service 716 250 FEATURE 717 ` 718 719 var helloServer = []string{ 720 "", 721 "502 Not implemented\n", 722 "250 User is valid\n", 723 "235 Accepted\n", 724 "250 Sender ok\n", 725 "", 726 "250 Reset ok\n", 727 "221 Goodbye\n", 728 "250 Sender ok\n", 729 "250 ok\n", 730 } 731 732 var baseHelloClient = `EHLO customhost 733 HELO customhost 734 ` 735 736 var helloClient = []string{ 737 "", 738 "STARTTLS\n", 739 "VRFY test@example.com\n", 740 "AUTH PLAIN AHVzZXIAcGFzcw==\n", 741 "MAIL FROM:<test@example.com>\n", 742 "", 743 "RSET\n", 744 "QUIT\n", 745 "VRFY test@example.com\n", 746 "NOOP\n", 747 } 748 749 func TestSendMail(t *testing.T) { 750 server := strings.Join(strings.Split(sendMailServer, "\n"), "\r\n") 751 client := strings.Join(strings.Split(sendMailClient, "\n"), "\r\n") 752 var cmdbuf strings.Builder 753 bcmdbuf := bufio.NewWriter(&cmdbuf) 754 l, err := net.Listen("tcp", "127.0.0.1:0") 755 if err != nil { 756 t.Fatalf("Unable to create listener: %v", err) 757 } 758 defer l.Close() 759 760 // prevent data race on bcmdbuf 761 var done = make(chan struct{}) 762 go func(data []string) { 763 764 defer close(done) 765 766 conn, err := l.Accept() 767 if err != nil { 768 t.Errorf("Accept error: %v", err) 769 return 770 } 771 defer conn.Close() 772 773 tc := textproto.NewConn(conn) 774 for i := 0; i < len(data) && data[i] != ""; i++ { 775 tc.PrintfLine(data[i]) 776 for len(data[i]) >= 4 && data[i][3] == '-' { 777 i++ 778 tc.PrintfLine(data[i]) 779 } 780 if data[i] == "221 Goodbye" { 781 return 782 } 783 read := false 784 for !read || data[i] == "354 Go ahead" { 785 msg, err := tc.ReadLine() 786 bcmdbuf.Write([]byte(msg + "\r\n")) 787 read = true 788 if err != nil { 789 t.Errorf("Read error: %v", err) 790 return 791 } 792 if data[i] == "354 Go ahead" && msg == "." { 793 break 794 } 795 } 796 } 797 }(strings.Split(server, "\r\n")) 798 799 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 800 To: other@example.com 801 Subject: SendMail test 802 803 SendMail is working for me. 804 `, "\n", "\r\n", -1))) 805 if err == nil { 806 t.Errorf("Expected SendMail to be rejected due to a message injection attempt") 807 } 808 809 err = SendMail(l.Addr().String(), nil, "test@example.com", []string{"other@example.com"}, []byte(strings.Replace(`From: test@example.com 810 To: other@example.com 811 Subject: SendMail test 812 813 SendMail is working for me. 814 `, "\n", "\r\n", -1))) 815 816 if err != nil { 817 t.Errorf("%v", err) 818 } 819 820 <-done 821 bcmdbuf.Flush() 822 actualcmds := cmdbuf.String() 823 if client != actualcmds { 824 t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client) 825 } 826 } 827 828 var sendMailServer = `220 hello world 829 502 EH? 830 250 mx.google.com at your service 831 250 Sender ok 832 250 Receiver ok 833 354 Go ahead 834 250 Data ok 835 221 Goodbye 836 ` 837 838 var sendMailClient = `EHLO localhost 839 HELO localhost 840 MAIL FROM:<test@example.com> 841 RCPT TO:<other@example.com> 842 DATA 843 From: test@example.com 844 To: other@example.com 845 Subject: SendMail test 846 847 SendMail is working for me. 848 . 849 QUIT 850 ` 851 852 func TestSendMailWithAuth(t *testing.T) { 853 l, err := net.Listen("tcp", "127.0.0.1:0") 854 if err != nil { 855 t.Fatalf("Unable to create listener: %v", err) 856 } 857 defer l.Close() 858 859 errCh := make(chan error) 860 go func() { 861 defer close(errCh) 862 conn, err := l.Accept() 863 if err != nil { 864 errCh <- fmt.Errorf("Accept: %v", err) 865 return 866 } 867 defer conn.Close() 868 869 tc := textproto.NewConn(conn) 870 tc.PrintfLine("220 hello world") 871 msg, err := tc.ReadLine() 872 if err != nil { 873 errCh <- fmt.Errorf("ReadLine error: %v", err) 874 return 875 } 876 const wantMsg = "EHLO localhost" 877 if msg != wantMsg { 878 errCh <- fmt.Errorf("unexpected response %q; want %q", msg, wantMsg) 879 return 880 } 881 err = tc.PrintfLine("250 mx.google.com at your service") 882 if err != nil { 883 errCh <- fmt.Errorf("PrintfLine: %v", err) 884 return 885 } 886 }() 887 888 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 889 To: other@example.com 890 Subject: SendMail test 891 892 SendMail is working for me. 893 `, "\n", "\r\n", -1))) 894 if err == nil { 895 t.Error("SendMail: Server doesn't support AUTH, expected to get an error, but got none ") 896 } 897 if err.Error() != "smtp: server doesn't support AUTH" { 898 t.Errorf("Expected: smtp: server doesn't support AUTH, got: %s", err) 899 } 900 err = <-errCh 901 if err != nil { 902 t.Fatalf("server error: %v", err) 903 } 904 } 905 906 func TestAuthFailed(t *testing.T) { 907 server := strings.Join(strings.Split(authFailedServer, "\n"), "\r\n") 908 client := strings.Join(strings.Split(authFailedClient, "\n"), "\r\n") 909 var cmdbuf strings.Builder 910 bcmdbuf := bufio.NewWriter(&cmdbuf) 911 var fake faker 912 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf) 913 c, err := NewClient(fake, "fake.host") 914 if err != nil { 915 t.Fatalf("NewClient: %v", err) 916 } 917 defer c.Close() 918 919 c.tls = true 920 c.serverName = "smtp.google.com" 921 err = c.Auth(PlainAuth("", "user", "pass", "smtp.google.com")) 922 923 if err == nil { 924 t.Error("Auth: expected error; got none") 925 } else if err.Error() != "535 Invalid credentials\nplease see www.example.com" { 926 t.Errorf("Auth: got error: %v, want: %s", err, "535 Invalid credentials\nplease see www.example.com") 927 } 928 929 bcmdbuf.Flush() 930 actualcmds := cmdbuf.String() 931 if client != actualcmds { 932 t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client) 933 } 934 } 935 936 var authFailedServer = `220 hello world 937 250-mx.google.com at your service 938 250 AUTH LOGIN PLAIN 939 535-Invalid credentials 940 535 please see www.example.com 941 221 Goodbye 942 ` 943 944 var authFailedClient = `EHLO localhost 945 AUTH PLAIN AHVzZXIAcGFzcw== 946 * 947 QUIT 948 ` 949 950 func TestTLSClient(t *testing.T) { 951 if runtime.GOOS == "freebsd" || runtime.GOOS == "js" || runtime.GOOS == "wasip1" { 952 testenv.SkipFlaky(t, 19229) 953 } 954 ln := newLocalListener(t) 955 defer ln.Close() 956 errc := make(chan error) 957 go func() { 958 errc <- sendMail(ln.Addr().String()) 959 }() 960 conn, err := ln.Accept() 961 if err != nil { 962 t.Fatalf("failed to accept connection: %v", err) 963 } 964 defer conn.Close() 965 if err := serverHandle(conn, t); err != nil { 966 t.Fatalf("failed to handle connection: %v", err) 967 } 968 if err := <-errc; err != nil { 969 t.Fatalf("client error: %v", err) 970 } 971 } 972 973 func TestTLSConnState(t *testing.T) { 974 ln := newLocalListener(t) 975 defer ln.Close() 976 clientDone := make(chan bool) 977 serverDone := make(chan bool) 978 go func() { 979 defer close(serverDone) 980 c, err := ln.Accept() 981 if err != nil { 982 t.Errorf("Server accept: %v", err) 983 return 984 } 985 defer c.Close() 986 if err := serverHandle(c, t); err != nil { 987 t.Errorf("server error: %v", err) 988 } 989 }() 990 go func() { 991 defer close(clientDone) 992 c, err := Dial(ln.Addr().String()) 993 if err != nil { 994 t.Errorf("Client dial: %v", err) 995 return 996 } 997 defer c.Quit() 998 cfg := &tls.Config{ServerName: "example.com"} 999 testHookStartTLS(cfg) // set the RootCAs 1000 if err := c.StartTLS(cfg); err != nil { 1001 t.Errorf("StartTLS: %v", err) 1002 return 1003 } 1004 cs, ok := c.TLSConnectionState() 1005 if !ok { 1006 t.Errorf("TLSConnectionState returned ok == false; want true") 1007 return 1008 } 1009 if cs.Version == 0 || !cs.HandshakeComplete { 1010 t.Errorf("ConnectionState = %#v; expect non-zero Version and HandshakeComplete", cs) 1011 } 1012 }() 1013 <-clientDone 1014 <-serverDone 1015 } 1016 1017 func newLocalListener(t *testing.T) net.Listener { 1018 ln, err := net.Listen("tcp", "127.0.0.1:0") 1019 if err != nil { 1020 ln, err = net.Listen("tcp6", "[::1]:0") 1021 } 1022 if err != nil { 1023 t.Fatal(err) 1024 } 1025 return ln 1026 } 1027 1028 type smtpSender struct { 1029 w io.Writer 1030 } 1031 1032 func (s smtpSender) send(f string) { 1033 s.w.Write([]byte(f + "\r\n")) 1034 } 1035 1036 // smtp server, finely tailored to deal with our own client only! 1037 func serverHandle(c net.Conn, t *testing.T) error { 1038 send := smtpSender{c}.send 1039 send("220 127.0.0.1 ESMTP service ready") 1040 s := bufio.NewScanner(c) 1041 for s.Scan() { 1042 switch s.Text() { 1043 case "EHLO localhost": 1044 send("250-127.0.0.1 ESMTP offers a warm hug of welcome") 1045 send("250-STARTTLS") 1046 send("250 Ok") 1047 case "STARTTLS": 1048 send("220 Go ahead") 1049 keypair, err := tls.X509KeyPair(localhostCert, localhostKey) 1050 if err != nil { 1051 return err 1052 } 1053 config := &tls.Config{Certificates: []tls.Certificate{keypair}} 1054 c = tls.Server(c, config) 1055 defer c.Close() 1056 return serverHandleTLS(c, t) 1057 default: 1058 t.Fatalf("unrecognized command: %q", s.Text()) 1059 } 1060 } 1061 return s.Err() 1062 } 1063 1064 func serverHandleTLS(c net.Conn, t *testing.T) error { 1065 send := smtpSender{c}.send 1066 s := bufio.NewScanner(c) 1067 for s.Scan() { 1068 switch s.Text() { 1069 case "EHLO localhost": 1070 send("250 Ok") 1071 case "MAIL FROM:<joe1@example.com>": 1072 send("250 Ok") 1073 case "RCPT TO:<joe2@example.com>": 1074 send("250 Ok") 1075 case "DATA": 1076 send("354 send the mail data, end with .") 1077 send("250 Ok") 1078 case "Subject: test": 1079 case "": 1080 case "howdy!": 1081 case ".": 1082 case "QUIT": 1083 send("221 127.0.0.1 Service closing transmission channel") 1084 return nil 1085 default: 1086 t.Fatalf("unrecognized command during TLS: %q", s.Text()) 1087 } 1088 } 1089 return s.Err() 1090 } 1091 1092 func init() { 1093 testRootCAs := x509.NewCertPool() 1094 testRootCAs.AppendCertsFromPEM(localhostCert) 1095 testHookStartTLS = func(config *tls.Config) { 1096 config.RootCAs = testRootCAs 1097 } 1098 } 1099 1100 func sendMail(hostPort string) error { 1101 from := "joe1@example.com" 1102 to := []string{"joe2@example.com"} 1103 return SendMail(hostPort, nil, from, to, []byte("Subject: test\n\nhowdy!")) 1104 } 1105 1106 // localhostCert is a PEM-encoded TLS cert generated from src/crypto/tls: 1107 // 1108 // go run generate_cert.go --rsa-bits 1024 --host 127.0.0.1,::1,example.com \ 1109 // --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h 1110 var localhostCert = []byte(` 1111 -----BEGIN CERTIFICATE----- 1112 MIICFDCCAX2gAwIBAgIRAK0xjnaPuNDSreeXb+z+0u4wDQYJKoZIhvcNAQELBQAw 1113 EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2 1114 MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzCBnzANBgkqhkiG9w0BAQEFAAOBjQAw 1115 gYkCgYEA0nFbQQuOWsjbGtejcpWz153OlziZM4bVjJ9jYruNw5n2Ry6uYQAffhqa 1116 JOInCmmcVe2siJglsyH9aRh6vKiobBbIUXXUU1ABd56ebAzlt0LobLlx7pZEMy30 1117 LqIi9E6zmL3YvdGzpYlkFRnRrqwEtWYbGBf3znO250S56CCWH2UCAwEAAaNoMGYw 1118 DgYDVR0PAQH/BAQDAgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQF 1119 MAMBAf8wLgYDVR0RBCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAAAAAAAAAA 1120 AAAAAAEwDQYJKoZIhvcNAQELBQADgYEAbZtDS2dVuBYvb+MnolWnCNqvw1w5Gtgi 1121 NmvQQPOMgM3m+oQSCPRTNGSg25e1Qbo7bgQDv8ZTnq8FgOJ/rbkyERw2JckkHpD4 1122 n4qcK27WkEDBtQFlPihIM8hLIuzWoi/9wygiElTy/tVL3y7fGCvY2/k1KBthtZGF 1123 tN8URjVmyEo= 1124 -----END CERTIFICATE-----`) 1125 1126 // localhostKey is the private key for localhostCert. 1127 var localhostKey = []byte(testingKey(` 1128 -----BEGIN RSA TESTING KEY----- 1129 MIICXgIBAAKBgQDScVtBC45ayNsa16NylbPXnc6XOJkzhtWMn2Niu43DmfZHLq5h 1130 AB9+Gpok4icKaZxV7ayImCWzIf1pGHq8qKhsFshRddRTUAF3np5sDOW3QuhsuXHu 1131 lkQzLfQuoiL0TrOYvdi90bOliWQVGdGurAS1ZhsYF/fOc7bnRLnoIJYfZQIDAQAB 1132 AoGBAMst7OgpKyFV6c3JwyI/jWqxDySL3caU+RuTTBaodKAUx2ZEmNJIlx9eudLA 1133 kucHvoxsM/eRxlxkhdFxdBcwU6J+zqooTnhu/FE3jhrT1lPrbhfGhyKnUrB0KKMM 1134 VY3IQZyiehpxaeXAwoAou6TbWoTpl9t8ImAqAMY8hlULCUqlAkEA+9+Ry5FSYK/m 1135 542LujIcCaIGoG1/Te6Sxr3hsPagKC2rH20rDLqXwEedSFOpSS0vpzlPAzy/6Rbb 1136 PHTJUhNdwwJBANXkA+TkMdbJI5do9/mn//U0LfrCR9NkcoYohxfKz8JuhgRQxzF2 1137 6jpo3q7CdTuuRixLWVfeJzcrAyNrVcBq87cCQFkTCtOMNC7fZnCTPUv+9q1tcJyB 1138 vNjJu3yvoEZeIeuzouX9TJE21/33FaeDdsXbRhQEj23cqR38qFHsF1qAYNMCQQDP 1139 QXLEiJoClkR2orAmqjPLVhR3t2oB3INcnEjLNSq8LHyQEfXyaFfu4U9l5+fRPL2i 1140 jiC0k/9L5dHUsF0XZothAkEA23ddgRs+Id/HxtojqqUT27B8MT/IGNrYsp4DvS/c 1141 qgkeluku4GjxRlDMBuXk94xOBEinUs+p/hwP1Alll80Tpg== 1142 -----END RSA TESTING KEY-----`)) 1143 1144 func testingKey(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") }