github.com/varialus/godfly@v0.0.0-20130904042352-1934f9f095ab/src/pkg/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 "io" 11 "net" 12 "net/textproto" 13 "strings" 14 "testing" 15 "time" 16 ) 17 18 type authTest struct { 19 auth Auth 20 challenges []string 21 name string 22 responses []string 23 } 24 25 var authTests = []authTest{ 26 {PlainAuth("", "user", "pass", "testserver"), []string{}, "PLAIN", []string{"\x00user\x00pass"}}, 27 {PlainAuth("foo", "bar", "baz", "testserver"), []string{}, "PLAIN", []string{"foo\x00bar\x00baz"}}, 28 {CRAMMD5Auth("user", "pass"), []string{"<123456.1322876914@testserver>"}, "CRAM-MD5", []string{"", "user 287eb355114cf5c471c26a875f1ca4ae"}}, 29 } 30 31 func TestAuth(t *testing.T) { 32 testLoop: 33 for i, test := range authTests { 34 name, resp, err := test.auth.Start(&ServerInfo{"testserver", true, nil}) 35 if name != test.name { 36 t.Errorf("#%d got name %s, expected %s", i, name, test.name) 37 } 38 if !bytes.Equal(resp, []byte(test.responses[0])) { 39 t.Errorf("#%d got response %s, expected %s", i, resp, test.responses[0]) 40 } 41 if err != nil { 42 t.Errorf("#%d error: %s", i, err) 43 } 44 for j := range test.challenges { 45 challenge := []byte(test.challenges[j]) 46 expected := []byte(test.responses[j+1]) 47 resp, err := test.auth.Next(challenge, true) 48 if err != nil { 49 t.Errorf("#%d error: %s", i, err) 50 continue testLoop 51 } 52 if !bytes.Equal(resp, expected) { 53 t.Errorf("#%d got %s, expected %s", i, resp, expected) 54 continue testLoop 55 } 56 } 57 } 58 } 59 60 func TestAuthPlain(t *testing.T) { 61 auth := PlainAuth("foo", "bar", "baz", "servername") 62 63 tests := []struct { 64 server *ServerInfo 65 err string 66 }{ 67 { 68 server: &ServerInfo{Name: "servername", TLS: true}, 69 }, 70 { 71 // Okay; explicitly advertised by server. 72 server: &ServerInfo{Name: "servername", Auth: []string{"PLAIN"}}, 73 }, 74 { 75 server: &ServerInfo{Name: "servername", Auth: []string{"CRAM-MD5"}}, 76 err: "unencrypted connection", 77 }, 78 { 79 server: &ServerInfo{Name: "attacker", TLS: true}, 80 err: "wrong host name", 81 }, 82 } 83 for i, tt := range tests { 84 _, _, err := auth.Start(tt.server) 85 got := "" 86 if err != nil { 87 got = err.Error() 88 } 89 if got != tt.err { 90 t.Errorf("%d. got error = %q; want %q", i, got, tt.err) 91 } 92 } 93 } 94 95 type faker struct { 96 io.ReadWriter 97 } 98 99 func (f faker) Close() error { return nil } 100 func (f faker) LocalAddr() net.Addr { return nil } 101 func (f faker) RemoteAddr() net.Addr { return nil } 102 func (f faker) SetDeadline(time.Time) error { return nil } 103 func (f faker) SetReadDeadline(time.Time) error { return nil } 104 func (f faker) SetWriteDeadline(time.Time) error { return nil } 105 106 func TestBasic(t *testing.T) { 107 server := strings.Join(strings.Split(basicServer, "\n"), "\r\n") 108 client := strings.Join(strings.Split(basicClient, "\n"), "\r\n") 109 110 var cmdbuf bytes.Buffer 111 bcmdbuf := bufio.NewWriter(&cmdbuf) 112 var fake faker 113 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf) 114 c := &Client{Text: textproto.NewConn(fake), localName: "localhost"} 115 116 if err := c.helo(); err != nil { 117 t.Fatalf("HELO failed: %s", err) 118 } 119 if err := c.ehlo(); err == nil { 120 t.Fatalf("Expected first EHLO to fail") 121 } 122 if err := c.ehlo(); err != nil { 123 t.Fatalf("Second EHLO failed: %s", err) 124 } 125 126 c.didHello = true 127 if ok, args := c.Extension("aUtH"); !ok || args != "LOGIN PLAIN" { 128 t.Fatalf("Expected AUTH supported") 129 } 130 if ok, _ := c.Extension("DSN"); ok { 131 t.Fatalf("Shouldn't support DSN") 132 } 133 134 if err := c.Mail("user@gmail.com"); err == nil { 135 t.Fatalf("MAIL should require authentication") 136 } 137 138 if err := c.Verify("user1@gmail.com"); err == nil { 139 t.Fatalf("First VRFY: expected no verification") 140 } 141 if err := c.Verify("user2@gmail.com"); err != nil { 142 t.Fatalf("Second VRFY: expected verification, got %s", err) 143 } 144 145 // fake TLS so authentication won't complain 146 c.tls = true 147 c.serverName = "smtp.google.com" 148 if err := c.Auth(PlainAuth("", "user", "pass", "smtp.google.com")); err != nil { 149 t.Fatalf("AUTH failed: %s", err) 150 } 151 152 if err := c.Mail("user@gmail.com"); err != nil { 153 t.Fatalf("MAIL failed: %s", err) 154 } 155 if err := c.Rcpt("golang-nuts@googlegroups.com"); err != nil { 156 t.Fatalf("RCPT failed: %s", err) 157 } 158 msg := `From: user@gmail.com 159 To: golang-nuts@googlegroups.com 160 Subject: Hooray for Go 161 162 Line 1 163 .Leading dot line . 164 Goodbye.` 165 w, err := c.Data() 166 if err != nil { 167 t.Fatalf("DATA failed: %s", err) 168 } 169 if _, err := w.Write([]byte(msg)); err != nil { 170 t.Fatalf("Data write failed: %s", err) 171 } 172 if err := w.Close(); err != nil { 173 t.Fatalf("Bad data response: %s", err) 174 } 175 176 if err := c.Quit(); err != nil { 177 t.Fatalf("QUIT failed: %s", err) 178 } 179 180 bcmdbuf.Flush() 181 actualcmds := cmdbuf.String() 182 if client != actualcmds { 183 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client) 184 } 185 } 186 187 var basicServer = `250 mx.google.com at your service 188 502 Unrecognized command. 189 250-mx.google.com at your service 190 250-SIZE 35651584 191 250-AUTH LOGIN PLAIN 192 250 8BITMIME 193 530 Authentication required 194 252 Send some mail, I'll try my best 195 250 User is valid 196 235 Accepted 197 250 Sender OK 198 250 Receiver OK 199 354 Go ahead 200 250 Data OK 201 221 OK 202 ` 203 204 var basicClient = `HELO localhost 205 EHLO localhost 206 EHLO localhost 207 MAIL FROM:<user@gmail.com> BODY=8BITMIME 208 VRFY user1@gmail.com 209 VRFY user2@gmail.com 210 AUTH PLAIN AHVzZXIAcGFzcw== 211 MAIL FROM:<user@gmail.com> BODY=8BITMIME 212 RCPT TO:<golang-nuts@googlegroups.com> 213 DATA 214 From: user@gmail.com 215 To: golang-nuts@googlegroups.com 216 Subject: Hooray for Go 217 218 Line 1 219 ..Leading dot line . 220 Goodbye. 221 . 222 QUIT 223 ` 224 225 func TestNewClient(t *testing.T) { 226 server := strings.Join(strings.Split(newClientServer, "\n"), "\r\n") 227 client := strings.Join(strings.Split(newClientClient, "\n"), "\r\n") 228 229 var cmdbuf bytes.Buffer 230 bcmdbuf := bufio.NewWriter(&cmdbuf) 231 out := func() string { 232 bcmdbuf.Flush() 233 return cmdbuf.String() 234 } 235 var fake faker 236 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf) 237 c, err := NewClient(fake, "fake.host") 238 if err != nil { 239 t.Fatalf("NewClient: %v\n(after %v)", err, out()) 240 } 241 defer c.Close() 242 if ok, args := c.Extension("aUtH"); !ok || args != "LOGIN PLAIN" { 243 t.Fatalf("Expected AUTH supported") 244 } 245 if ok, _ := c.Extension("DSN"); ok { 246 t.Fatalf("Shouldn't support DSN") 247 } 248 if err := c.Quit(); err != nil { 249 t.Fatalf("QUIT failed: %s", err) 250 } 251 252 actualcmds := out() 253 if client != actualcmds { 254 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client) 255 } 256 } 257 258 var newClientServer = `220 hello world 259 250-mx.google.com at your service 260 250-SIZE 35651584 261 250-AUTH LOGIN PLAIN 262 250 8BITMIME 263 221 OK 264 ` 265 266 var newClientClient = `EHLO localhost 267 QUIT 268 ` 269 270 func TestNewClient2(t *testing.T) { 271 server := strings.Join(strings.Split(newClient2Server, "\n"), "\r\n") 272 client := strings.Join(strings.Split(newClient2Client, "\n"), "\r\n") 273 274 var cmdbuf bytes.Buffer 275 bcmdbuf := bufio.NewWriter(&cmdbuf) 276 var fake faker 277 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf) 278 c, err := NewClient(fake, "fake.host") 279 if err != nil { 280 t.Fatalf("NewClient: %v", err) 281 } 282 defer c.Close() 283 if ok, _ := c.Extension("DSN"); ok { 284 t.Fatalf("Shouldn't support DSN") 285 } 286 if err := c.Quit(); err != nil { 287 t.Fatalf("QUIT failed: %s", err) 288 } 289 290 bcmdbuf.Flush() 291 actualcmds := cmdbuf.String() 292 if client != actualcmds { 293 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client) 294 } 295 } 296 297 var newClient2Server = `220 hello world 298 502 EH? 299 250-mx.google.com at your service 300 250-SIZE 35651584 301 250-AUTH LOGIN PLAIN 302 250 8BITMIME 303 221 OK 304 ` 305 306 var newClient2Client = `EHLO localhost 307 HELO localhost 308 QUIT 309 ` 310 311 func TestHello(t *testing.T) { 312 313 if len(helloServer) != len(helloClient) { 314 t.Fatalf("Hello server and client size mismatch") 315 } 316 317 for i := 0; i < len(helloServer); i++ { 318 server := strings.Join(strings.Split(baseHelloServer+helloServer[i], "\n"), "\r\n") 319 client := strings.Join(strings.Split(baseHelloClient+helloClient[i], "\n"), "\r\n") 320 var cmdbuf bytes.Buffer 321 bcmdbuf := bufio.NewWriter(&cmdbuf) 322 var fake faker 323 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf) 324 c, err := NewClient(fake, "fake.host") 325 if err != nil { 326 t.Fatalf("NewClient: %v", err) 327 } 328 defer c.Close() 329 c.localName = "customhost" 330 err = nil 331 332 switch i { 333 case 0: 334 err = c.Hello("customhost") 335 case 1: 336 err = c.StartTLS(nil) 337 if err.Error() == "502 Not implemented" { 338 err = nil 339 } 340 case 2: 341 err = c.Verify("test@example.com") 342 case 3: 343 c.tls = true 344 c.serverName = "smtp.google.com" 345 err = c.Auth(PlainAuth("", "user", "pass", "smtp.google.com")) 346 case 4: 347 err = c.Mail("test@example.com") 348 case 5: 349 ok, _ := c.Extension("feature") 350 if ok { 351 t.Errorf("Expected FEATURE not to be supported") 352 } 353 case 6: 354 err = c.Reset() 355 case 7: 356 err = c.Quit() 357 case 8: 358 err = c.Verify("test@example.com") 359 if err != nil { 360 err = c.Hello("customhost") 361 if err != nil { 362 t.Errorf("Want error, got none") 363 } 364 } 365 default: 366 t.Fatalf("Unhandled command") 367 } 368 369 if err != nil { 370 t.Errorf("Command %d failed: %v", i, err) 371 } 372 373 bcmdbuf.Flush() 374 actualcmds := cmdbuf.String() 375 if client != actualcmds { 376 t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client) 377 } 378 } 379 } 380 381 var baseHelloServer = `220 hello world 382 502 EH? 383 250-mx.google.com at your service 384 250 FEATURE 385 ` 386 387 var helloServer = []string{ 388 "", 389 "502 Not implemented\n", 390 "250 User is valid\n", 391 "235 Accepted\n", 392 "250 Sender ok\n", 393 "", 394 "250 Reset ok\n", 395 "221 Goodbye\n", 396 "250 Sender ok\n", 397 } 398 399 var baseHelloClient = `EHLO customhost 400 HELO customhost 401 ` 402 403 var helloClient = []string{ 404 "", 405 "STARTTLS\n", 406 "VRFY test@example.com\n", 407 "AUTH PLAIN AHVzZXIAcGFzcw==\n", 408 "MAIL FROM:<test@example.com>\n", 409 "", 410 "RSET\n", 411 "QUIT\n", 412 "VRFY test@example.com\n", 413 } 414 415 func TestSendMail(t *testing.T) { 416 server := strings.Join(strings.Split(sendMailServer, "\n"), "\r\n") 417 client := strings.Join(strings.Split(sendMailClient, "\n"), "\r\n") 418 var cmdbuf bytes.Buffer 419 bcmdbuf := bufio.NewWriter(&cmdbuf) 420 l, err := net.Listen("tcp", "127.0.0.1:0") 421 if err != nil { 422 t.Fatalf("Unable to to create listener: %v", err) 423 } 424 defer l.Close() 425 426 // prevent data race on bcmdbuf 427 var done = make(chan struct{}) 428 go func(data []string) { 429 430 defer close(done) 431 432 conn, err := l.Accept() 433 if err != nil { 434 t.Errorf("Accept error: %v", err) 435 return 436 } 437 defer conn.Close() 438 439 tc := textproto.NewConn(conn) 440 for i := 0; i < len(data) && data[i] != ""; i++ { 441 tc.PrintfLine(data[i]) 442 for len(data[i]) >= 4 && data[i][3] == '-' { 443 i++ 444 tc.PrintfLine(data[i]) 445 } 446 if data[i] == "221 Goodbye" { 447 return 448 } 449 read := false 450 for !read || data[i] == "354 Go ahead" { 451 msg, err := tc.ReadLine() 452 bcmdbuf.Write([]byte(msg + "\r\n")) 453 read = true 454 if err != nil { 455 t.Errorf("Read error: %v", err) 456 return 457 } 458 if data[i] == "354 Go ahead" && msg == "." { 459 break 460 } 461 } 462 } 463 }(strings.Split(server, "\r\n")) 464 465 err = SendMail(l.Addr().String(), nil, "test@example.com", []string{"other@example.com"}, []byte(strings.Replace(`From: test@example.com 466 To: other@example.com 467 Subject: SendMail test 468 469 SendMail is working for me. 470 `, "\n", "\r\n", -1))) 471 472 if err != nil { 473 t.Errorf("%v", err) 474 } 475 476 <-done 477 bcmdbuf.Flush() 478 actualcmds := cmdbuf.String() 479 if client != actualcmds { 480 t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client) 481 } 482 } 483 484 var sendMailServer = `220 hello world 485 502 EH? 486 250 mx.google.com at your service 487 250 Sender ok 488 250 Receiver ok 489 354 Go ahead 490 250 Data ok 491 221 Goodbye 492 ` 493 494 var sendMailClient = `EHLO localhost 495 HELO localhost 496 MAIL FROM:<test@example.com> 497 RCPT TO:<other@example.com> 498 DATA 499 From: test@example.com 500 To: other@example.com 501 Subject: SendMail test 502 503 SendMail is working for me. 504 . 505 QUIT 506 ` 507 508 func TestAuthFailed(t *testing.T) { 509 server := strings.Join(strings.Split(authFailedServer, "\n"), "\r\n") 510 client := strings.Join(strings.Split(authFailedClient, "\n"), "\r\n") 511 var cmdbuf bytes.Buffer 512 bcmdbuf := bufio.NewWriter(&cmdbuf) 513 var fake faker 514 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf) 515 c, err := NewClient(fake, "fake.host") 516 if err != nil { 517 t.Fatalf("NewClient: %v", err) 518 } 519 defer c.Close() 520 521 c.tls = true 522 c.serverName = "smtp.google.com" 523 err = c.Auth(PlainAuth("", "user", "pass", "smtp.google.com")) 524 525 if err == nil { 526 t.Error("Auth: expected error; got none") 527 } else if err.Error() != "535 Invalid credentials\nplease see www.example.com" { 528 t.Errorf("Auth: got error: %v, want: %s", err, "535 Invalid credentials\nplease see www.example.com") 529 } 530 531 bcmdbuf.Flush() 532 actualcmds := cmdbuf.String() 533 if client != actualcmds { 534 t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client) 535 } 536 } 537 538 var authFailedServer = `220 hello world 539 250-mx.google.com at your service 540 250 AUTH LOGIN PLAIN 541 535-Invalid credentials 542 535 please see www.example.com 543 221 Goodbye 544 ` 545 546 var authFailedClient = `EHLO localhost 547 AUTH PLAIN AHVzZXIAcGFzcw== 548 * 549 QUIT 550 `