github.com/xushiwei/go@v0.0.0-20130601165731-2b9d83f45bc9/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 if ok, args := c.Extension("aUtH"); !ok || args != "LOGIN PLAIN" { 242 t.Fatalf("Expected AUTH supported") 243 } 244 if ok, _ := c.Extension("DSN"); ok { 245 t.Fatalf("Shouldn't support DSN") 246 } 247 if err := c.Quit(); err != nil { 248 t.Fatalf("QUIT failed: %s", err) 249 } 250 251 actualcmds := out() 252 if client != actualcmds { 253 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client) 254 } 255 } 256 257 var newClientServer = `220 hello world 258 250-mx.google.com at your service 259 250-SIZE 35651584 260 250-AUTH LOGIN PLAIN 261 250 8BITMIME 262 221 OK 263 ` 264 265 var newClientClient = `EHLO localhost 266 QUIT 267 ` 268 269 func TestNewClient2(t *testing.T) { 270 server := strings.Join(strings.Split(newClient2Server, "\n"), "\r\n") 271 client := strings.Join(strings.Split(newClient2Client, "\n"), "\r\n") 272 273 var cmdbuf bytes.Buffer 274 bcmdbuf := bufio.NewWriter(&cmdbuf) 275 var fake faker 276 fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf) 277 c, err := NewClient(fake, "fake.host") 278 if err != nil { 279 t.Fatalf("NewClient: %v", err) 280 } 281 if ok, _ := c.Extension("DSN"); ok { 282 t.Fatalf("Shouldn't support DSN") 283 } 284 if err := c.Quit(); err != nil { 285 t.Fatalf("QUIT failed: %s", err) 286 } 287 288 bcmdbuf.Flush() 289 actualcmds := cmdbuf.String() 290 if client != actualcmds { 291 t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client) 292 } 293 } 294 295 var newClient2Server = `220 hello world 296 502 EH? 297 250-mx.google.com at your service 298 250-SIZE 35651584 299 250-AUTH LOGIN PLAIN 300 250 8BITMIME 301 221 OK 302 ` 303 304 var newClient2Client = `EHLO localhost 305 HELO localhost 306 QUIT 307 ` 308 309 func TestHello(t *testing.T) { 310 311 if len(helloServer) != len(helloClient) { 312 t.Fatalf("Hello server and client size mismatch") 313 } 314 315 for i := 0; i < len(helloServer); i++ { 316 server := strings.Join(strings.Split(baseHelloServer+helloServer[i], "\n"), "\r\n") 317 client := strings.Join(strings.Split(baseHelloClient+helloClient[i], "\n"), "\r\n") 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 c.localName = "customhost" 327 err = nil 328 329 switch i { 330 case 0: 331 err = c.Hello("customhost") 332 case 1: 333 err = c.StartTLS(nil) 334 if err.Error() == "502 Not implemented" { 335 err = nil 336 } 337 case 2: 338 err = c.Verify("test@example.com") 339 case 3: 340 c.tls = true 341 c.serverName = "smtp.google.com" 342 err = c.Auth(PlainAuth("", "user", "pass", "smtp.google.com")) 343 case 4: 344 err = c.Mail("test@example.com") 345 case 5: 346 ok, _ := c.Extension("feature") 347 if ok { 348 t.Errorf("Expected FEATURE not to be supported") 349 } 350 case 6: 351 err = c.Reset() 352 case 7: 353 err = c.Quit() 354 case 8: 355 err = c.Verify("test@example.com") 356 if err != nil { 357 err = c.Hello("customhost") 358 if err != nil { 359 t.Errorf("Want error, got none") 360 } 361 } 362 default: 363 t.Fatalf("Unhandled command") 364 } 365 366 if err != nil { 367 t.Errorf("Command %d failed: %v", i, err) 368 } 369 370 bcmdbuf.Flush() 371 actualcmds := cmdbuf.String() 372 if client != actualcmds { 373 t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client) 374 } 375 } 376 } 377 378 var baseHelloServer = `220 hello world 379 502 EH? 380 250-mx.google.com at your service 381 250 FEATURE 382 ` 383 384 var helloServer = []string{ 385 "", 386 "502 Not implemented\n", 387 "250 User is valid\n", 388 "235 Accepted\n", 389 "250 Sender ok\n", 390 "", 391 "250 Reset ok\n", 392 "221 Goodbye\n", 393 "250 Sender ok\n", 394 } 395 396 var baseHelloClient = `EHLO customhost 397 HELO customhost 398 ` 399 400 var helloClient = []string{ 401 "", 402 "STARTTLS\n", 403 "VRFY test@example.com\n", 404 "AUTH PLAIN AHVzZXIAcGFzcw==\n", 405 "MAIL FROM:<test@example.com>\n", 406 "", 407 "RSET\n", 408 "QUIT\n", 409 "VRFY test@example.com\n", 410 } 411 412 func TestSendMail(t *testing.T) { 413 server := strings.Join(strings.Split(sendMailServer, "\n"), "\r\n") 414 client := strings.Join(strings.Split(sendMailClient, "\n"), "\r\n") 415 var cmdbuf bytes.Buffer 416 bcmdbuf := bufio.NewWriter(&cmdbuf) 417 l, err := net.Listen("tcp", "127.0.0.1:0") 418 if err != nil { 419 t.Fatalf("Unable to to create listener: %v", err) 420 } 421 defer l.Close() 422 423 // prevent data race on bcmdbuf 424 var done = make(chan struct{}) 425 go func(data []string) { 426 427 defer close(done) 428 429 conn, err := l.Accept() 430 if err != nil { 431 t.Errorf("Accept error: %v", err) 432 return 433 } 434 defer conn.Close() 435 436 tc := textproto.NewConn(conn) 437 for i := 0; i < len(data) && data[i] != ""; i++ { 438 tc.PrintfLine(data[i]) 439 for len(data[i]) >= 4 && data[i][3] == '-' { 440 i++ 441 tc.PrintfLine(data[i]) 442 } 443 if data[i] == "221 Goodbye" { 444 return 445 } 446 read := false 447 for !read || data[i] == "354 Go ahead" { 448 msg, err := tc.ReadLine() 449 bcmdbuf.Write([]byte(msg + "\r\n")) 450 read = true 451 if err != nil { 452 t.Errorf("Read error: %v", err) 453 return 454 } 455 if data[i] == "354 Go ahead" && msg == "." { 456 break 457 } 458 } 459 } 460 }(strings.Split(server, "\r\n")) 461 462 err = SendMail(l.Addr().String(), nil, "test@example.com", []string{"other@example.com"}, []byte(strings.Replace(`From: test@example.com 463 To: other@example.com 464 Subject: SendMail test 465 466 SendMail is working for me. 467 `, "\n", "\r\n", -1))) 468 469 if err != nil { 470 t.Errorf("%v", err) 471 } 472 473 <-done 474 bcmdbuf.Flush() 475 actualcmds := cmdbuf.String() 476 if client != actualcmds { 477 t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client) 478 } 479 } 480 481 var sendMailServer = `220 hello world 482 502 EH? 483 250 mx.google.com at your service 484 250 Sender ok 485 250 Receiver ok 486 354 Go ahead 487 250 Data ok 488 221 Goodbye 489 ` 490 491 var sendMailClient = `EHLO localhost 492 HELO localhost 493 MAIL FROM:<test@example.com> 494 RCPT TO:<other@example.com> 495 DATA 496 From: test@example.com 497 To: other@example.com 498 Subject: SendMail test 499 500 SendMail is working for me. 501 . 502 QUIT 503 `