diff --git a/smtp/smtp_test.go b/smtp/smtp_test.go index 401a6ac..11cc2a0 100644 --- a/smtp/smtp_test.go +++ b/smtp/smtp_test.go @@ -3117,1343 +3117,6 @@ func TestClient_Noop(t *testing.T) { }) } -/* - - -func TestExtensions(t *testing.T) { - fake := func(server string) (c *Client, bcmdbuf *bufio.Writer, cmdbuf *strings.Builder) { - server = strings.Join(strings.Split(server, "\n"), "\r\n") - - cmdbuf = &strings.Builder{} - bcmdbuf = bufio.NewWriter(cmdbuf) - var fake faker - fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf) - c = &Client{Text: textproto.NewConn(fake), localName: "localhost"} - - return c, bcmdbuf, cmdbuf - } - - t.Run("helo", func(t *testing.T) { - const ( - basicServer = `250 mx.google.com at your service -250 Sender OK -221 Goodbye -` - - basicClient = `HELO localhost -MAIL FROM: -QUIT -` - ) - - c, bcmdbuf, cmdbuf := fake(basicServer) - - if err := c.helo(); err != nil { - t.Fatalf("HELO failed: %s", err) - } - c.didHello = true - if err := c.Mail("user@gmail.com"); err != nil { - t.Fatalf("MAIL FROM failed: %s", err) - } - if err := c.Quit(); err != nil { - t.Fatalf("QUIT failed: %s", err) - } - - if err := bcmdbuf.Flush(); err != nil { - t.Errorf("flush failed: %s", err) - } - actualcmds := cmdbuf.String() - client := strings.Join(strings.Split(basicClient, "\n"), "\r\n") - if client != actualcmds { - t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client) - } - }) - - t.Run("ehlo", func(t *testing.T) { - const ( - basicServer = `250-mx.google.com at your service -250 SIZE 35651584 -250 Sender OK -221 Goodbye -` - - basicClient = `EHLO localhost -MAIL FROM: -QUIT -` - ) - - c, bcmdbuf, cmdbuf := fake(basicServer) - - if err := c.Hello("localhost"); err != nil { - t.Fatalf("EHLO failed: %s", err) - } - if ok, _ := c.Extension("8BITMIME"); ok { - t.Fatalf("Shouldn't support 8BITMIME") - } - if ok, _ := c.Extension("SMTPUTF8"); ok { - t.Fatalf("Shouldn't support SMTPUTF8") - } - if err := c.Mail("user@gmail.com"); err != nil { - t.Fatalf("MAIL FROM failed: %s", err) - } - if err := c.Quit(); err != nil { - t.Fatalf("QUIT failed: %s", err) - } - - if err := bcmdbuf.Flush(); err != nil { - t.Errorf("flush failed: %s", err) - } - actualcmds := cmdbuf.String() - client := strings.Join(strings.Split(basicClient, "\n"), "\r\n") - if client != actualcmds { - t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client) - } - }) - - t.Run("ehlo 8bitmime", func(t *testing.T) { - const ( - basicServer = `250-mx.google.com at your service -250-SIZE 35651584 -250 8BITMIME -250 Sender OK -221 Goodbye -` - - basicClient = `EHLO localhost -MAIL FROM: BODY=8BITMIME -QUIT -` - ) - - c, bcmdbuf, cmdbuf := fake(basicServer) - - if err := c.Hello("localhost"); err != nil { - t.Fatalf("EHLO failed: %s", err) - } - if ok, _ := c.Extension("8BITMIME"); !ok { - t.Fatalf("Should support 8BITMIME") - } - if ok, _ := c.Extension("SMTPUTF8"); ok { - t.Fatalf("Shouldn't support SMTPUTF8") - } - if err := c.Mail("user@gmail.com"); err != nil { - t.Fatalf("MAIL FROM failed: %s", err) - } - if err := c.Quit(); err != nil { - t.Fatalf("QUIT failed: %s", err) - } - - if err := bcmdbuf.Flush(); err != nil { - t.Errorf("failed to flush: %s", err) - } - actualcmds := cmdbuf.String() - client := strings.Join(strings.Split(basicClient, "\n"), "\r\n") - if client != actualcmds { - t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client) - } - }) - - t.Run("ehlo smtputf8", func(t *testing.T) { - const ( - basicServer = `250-mx.google.com at your service -250-SIZE 35651584 -250 SMTPUTF8 -250 Sender OK -221 Goodbye -` - - basicClient = `EHLO localhost -MAIL FROM: SMTPUTF8 -QUIT -` - ) - - c, bcmdbuf, cmdbuf := fake(basicServer) - - if err := c.Hello("localhost"); err != nil { - t.Fatalf("EHLO failed: %s", err) - } - if ok, _ := c.Extension("8BITMIME"); ok { - t.Fatalf("Shouldn't support 8BITMIME") - } - if ok, _ := c.Extension("SMTPUTF8"); !ok { - t.Fatalf("Should support SMTPUTF8") - } - if err := c.Mail("user+📧@gmail.com"); err != nil { - t.Fatalf("MAIL FROM failed: %s", err) - } - if err := c.Quit(); err != nil { - t.Fatalf("QUIT failed: %s", err) - } - - if err := bcmdbuf.Flush(); err != nil { - t.Errorf("failed to flush: %s", err) - } - actualcmds := cmdbuf.String() - client := strings.Join(strings.Split(basicClient, "\n"), "\r\n") - if client != actualcmds { - t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client) - } - }) - - t.Run("ehlo 8bitmime smtputf8", func(t *testing.T) { - const ( - basicServer = `250-mx.google.com at your service -250-SIZE 35651584 -250-8BITMIME -250 SMTPUTF8 -250 Sender OK -221 Goodbye - ` - - basicClient = `EHLO localhost -MAIL FROM: BODY=8BITMIME SMTPUTF8 -QUIT -` - ) - - c, bcmdbuf, cmdbuf := fake(basicServer) - - if err := c.Hello("localhost"); err != nil { - t.Fatalf("EHLO failed: %s", err) - } - c.didHello = true - if ok, _ := c.Extension("8BITMIME"); !ok { - t.Fatalf("Should support 8BITMIME") - } - if ok, _ := c.Extension("SMTPUTF8"); !ok { - t.Fatalf("Should support SMTPUTF8") - } - if err := c.Mail("user+📧@gmail.com"); err != nil { - t.Fatalf("MAIL FROM failed: %s", err) - } - if err := c.Quit(); err != nil { - t.Fatalf("QUIT failed: %s", err) - } - - if err := bcmdbuf.Flush(); err != nil { - t.Errorf("failed to flush: %s", err) - } - actualcmds := cmdbuf.String() - client := strings.Join(strings.Split(basicClient, "\n"), "\r\n") - if client != actualcmds { - t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client) - } - }) -} - -func TestNewClient(t *testing.T) { - server := strings.Join(strings.Split(newClientServer, "\n"), "\r\n") - client := strings.Join(strings.Split(newClientClient, "\n"), "\r\n") - - var cmdbuf strings.Builder - bcmdbuf := bufio.NewWriter(&cmdbuf) - out := func() string { - if err := bcmdbuf.Flush(); err != nil { - t.Errorf("failed to flush: %s", err) - } - return cmdbuf.String() - } - var fake faker - fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf) - c, err := NewClient(fake, "fake.host") - if err != nil { - t.Fatalf("NewClient: %v\n(after %v)", err, out()) - } - defer func() { - _ = c.Close() - }() - if ok, args := c.Extension("aUtH"); !ok || args != "LOGIN PLAIN" { - t.Fatalf("Expected AUTH supported") - } - if ok, _ := c.Extension("DSN"); ok { - t.Fatalf("Shouldn't support DSN") - } - if err := c.Quit(); err != nil { - t.Fatalf("QUIT failed: %s", err) - } - - actualcmds := out() - if client != actualcmds { - t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client) - } -} - -// TestClient_SetDebugLog tests the Client method with the Client.SetDebugLog method -// to enable debug logging -func TestClient_SetDebugLog(t *testing.T) { - server := strings.Join(strings.Split(newClientServer, "\n"), "\r\n") - - var cmdbuf strings.Builder - bcmdbuf := bufio.NewWriter(&cmdbuf) - out := func() string { - if err := bcmdbuf.Flush(); err != nil { - t.Errorf("failed to flush: %s", err) - } - return cmdbuf.String() - } - var fake faker - fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf) - c, err := NewClient(fake, "fake.host") - if err != nil { - t.Fatalf("NewClient: %v\n(after %v)", err, out()) - } - defer func() { - _ = c.Close() - }() - c.SetDebugLog(true) - if !c.debug { - t.Errorf("Expected DebugLog flag to be true but received false") - } -} - -// TestClient_SetLogger tests the Client method with the Client.SetLogger method -// to provide a custom logger -func TestClient_SetLogger(t *testing.T) { - server := strings.Join(strings.Split(newClientServer, "\n"), "\r\n") - - var cmdbuf strings.Builder - bcmdbuf := bufio.NewWriter(&cmdbuf) - out := func() string { - if err := bcmdbuf.Flush(); err != nil { - t.Errorf("failed to flush: %s", err) - } - return cmdbuf.String() - } - var fake faker - fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf) - c, err := NewClient(fake, "fake.host") - if err != nil { - t.Fatalf("NewClient: %v\n(after %v)", err, out()) - } - defer func() { - _ = c.Close() - }() - c.SetLogger(log.New(os.Stderr, log.LevelDebug)) - if c.logger == nil { - t.Errorf("Expected Logger to be set but received nil") - } - c.logger.Debugf(log.Log{Direction: log.DirServerToClient, Format: "%s", Messages: []interface{}{"test"}}) - c.SetLogger(nil) - c.logger.Debugf(log.Log{Direction: log.DirServerToClient, Format: "%s", Messages: []interface{}{"test"}}) -} - -func TestClient_SetLogAuthData(t *testing.T) { - server := strings.Join(strings.Split(newClientServer, "\n"), "\r\n") - - var cmdbuf strings.Builder - bcmdbuf := bufio.NewWriter(&cmdbuf) - out := func() string { - if err := bcmdbuf.Flush(); err != nil { - t.Errorf("failed to flush: %s", err) - } - return cmdbuf.String() - } - var fake faker - fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf) - c, err := NewClient(fake, "fake.host") - if err != nil { - t.Fatalf("NewClient: %v\n(after %v)", err, out()) - } - defer func() { - _ = c.Close() - }() - c.SetLogAuthData() - if !c.logAuthData { - t.Error("Expected logAuthData to be true but received false") - } -} - -var newClientServer = `220 hello world -250-mx.google.com at your service -250-SIZE 35651584 -250-AUTH LOGIN PLAIN -250 8BITMIME -221 OK -` - -var newClientClient = `EHLO localhost -QUIT -` - -func TestNewClient2(t *testing.T) { - server := strings.Join(strings.Split(newClient2Server, "\n"), "\r\n") - client := strings.Join(strings.Split(newClient2Client, "\n"), "\r\n") - - var cmdbuf strings.Builder - bcmdbuf := bufio.NewWriter(&cmdbuf) - var fake faker - fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf) - c, err := NewClient(fake, "fake.host") - if err != nil { - t.Fatalf("NewClient: %v", err) - } - defer func() { - _ = c.Close() - }() - if ok, _ := c.Extension("DSN"); ok { - t.Fatalf("Shouldn't support DSN") - } - if err := c.Quit(); err != nil { - t.Fatalf("QUIT failed: %s", err) - } - - if err := bcmdbuf.Flush(); err != nil { - t.Errorf("flush failed: %s", err) - } - actualcmds := cmdbuf.String() - if client != actualcmds { - t.Fatalf("Got:\n%s\nExpected:\n%s", actualcmds, client) - } -} - -var newClient2Server = `220 hello world -502 EH? -250-mx.google.com at your service -250-SIZE 35651584 -250-AUTH LOGIN PLAIN -250 8BITMIME -221 OK -` - -var newClient2Client = `EHLO localhost -HELO localhost -QUIT -` - -func TestNewClientWithTLS(t *testing.T) { - cert, err := tls.X509KeyPair(localhostCert, localhostKey) - if err != nil { - t.Fatalf("loadcert: %v", err) - } - - config := tls.Config{Certificates: []tls.Certificate{cert}} - - ln, err := tls.Listen("tcp", "127.0.0.1:0", &config) - if err != nil { - ln, err = tls.Listen("tcp", "[::1]:0", &config) - if err != nil { - t.Fatalf("server: listen: %v", err) - } - } - - go func() { - conn, err := ln.Accept() - if err != nil { - t.Errorf("server: accept: %v", err) - return - } - defer func() { - _ = conn.Close() - }() - - _, err = conn.Write([]byte("220 SIGNS\r\n")) - if err != nil { - t.Errorf("server: write: %v", err) - return - } - }() - - config.InsecureSkipVerify = true - conn, err := tls.Dial("tcp", ln.Addr().String(), &config) - if err != nil { - t.Fatalf("client: dial: %v", err) - } - defer func() { - _ = conn.Close() - }() - - client, err := NewClient(conn, ln.Addr().String()) - if err != nil { - t.Fatalf("smtp: newclient: %v", err) - } - if !client.tls { - t.Errorf("client.tls Got: %t Expected: %t", client.tls, true) - } -} - -func TestHello(t *testing.T) { - if len(helloServer) != len(helloClient) { - t.Fatalf("Hello server and client size mismatch") - } - - tf := func(fake faker, i int) error { - c, err := NewClient(fake, "fake.host") - if err != nil { - t.Fatalf("NewClient: %v", err) - } - defer func() { - _ = c.Close() - }() - c.localName = "customhost" - err = nil - - switch i { - case 0: - err = c.Hello("hostinjection>\n\rDATA\r\nInjected message body\r\n.\r\nQUIT\r\n") - if err == nil { - t.Errorf("Expected Hello to be rejected due to a message injection attempt") - } - err = c.Hello("customhost") - case 1: - err = c.StartTLS(nil) - if err.Error() == "502 Not implemented" { - err = nil - } - case 2: - err = c.Verify("test@example.com") - case 3: - c.tls = true - c.serverName = "smtp.google.com" - err = c.Auth(PlainAuth("", "user", "pass", "smtp.google.com", false)) - case 4: - err = c.Mail("test@example.com") - case 5: - ok, _ := c.Extension("feature") - if ok { - t.Errorf("Expected FEATURE not to be supported") - } - case 6: - err = c.Reset() - case 7: - err = c.Quit() - case 8: - err = c.Verify("test@example.com") - if err != nil { - err = c.Hello("customhost") - if err != nil { - t.Errorf("Want error, got none") - } - } - case 9: - err = c.Noop() - default: - t.Fatalf("Unhandled command") - } - - if err != nil { - t.Errorf("Command %d failed: %v", i, err) - } - return nil - } - - for i := 0; i < len(helloServer); i++ { - server := strings.Join(strings.Split(baseHelloServer+helloServer[i], "\n"), "\r\n") - client := strings.Join(strings.Split(baseHelloClient+helloClient[i], "\n"), "\r\n") - var cmdbuf strings.Builder - bcmdbuf := bufio.NewWriter(&cmdbuf) - var fake faker - fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf) - - if err := tf(fake, i); err != nil { - t.Error(err) - } - - if err := bcmdbuf.Flush(); err != nil { - t.Errorf("flush failed: %s", err) - } - actualcmds := cmdbuf.String() - if client != actualcmds { - t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client) - } - } -} - -var baseHelloServer = `220 hello world -502 EH? -250-mx.google.com at your service -250 FEATURE -` - -var helloServer = []string{ - "", - "502 Not implemented\n", - "250 User is valid\n", - "235 Accepted\n", - "250 Sender ok\n", - "", - "250 Reset ok\n", - "221 Goodbye\n", - "250 Sender ok\n", - "250 ok\n", -} - -var baseHelloClient = `EHLO customhost -HELO customhost -` - -var helloClient = []string{ - "", - "STARTTLS\n", - "VRFY test@example.com\n", - "AUTH PLAIN AHVzZXIAcGFzcw==\n", - "MAIL FROM:\n", - "", - "RSET\n", - "QUIT\n", - "VRFY test@example.com\n", - "NOOP\n", -} - -func TestSendMail(t *testing.T) { - server := strings.Join(strings.Split(sendMailServer, "\n"), "\r\n") - client := strings.Join(strings.Split(sendMailClient, "\n"), "\r\n") - var cmdbuf strings.Builder - bcmdbuf := bufio.NewWriter(&cmdbuf) - l, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatalf("Unable to create listener: %v", err) - } - defer func() { - _ = l.Close() - }() - - // prevent data race on bcmdbuf - done := make(chan struct{}) - go func(data []string) { - defer close(done) - - conn, err := l.Accept() - if err != nil { - t.Errorf("Accept error: %v", err) - return - } - defer func() { - _ = conn.Close() - }() - - tc := textproto.NewConn(conn) - for i := 0; i < len(data) && data[i] != ""; i++ { - if err := tc.PrintfLine("%s", data[i]); err != nil { - t.Errorf("printing to textproto failed: %s", err) - } - for len(data[i]) >= 4 && data[i][3] == '-' { - i++ - if err := tc.PrintfLine("%s", data[i]); err != nil { - t.Errorf("printing to textproto failed: %s", err) - } - } - if data[i] == "221 Goodbye" { - return - } - read := false - for !read || data[i] == "354 Go ahead" { - msg, err := tc.ReadLine() - if _, err := bcmdbuf.Write([]byte(msg + "\r\n")); err != nil { - t.Errorf("write failed: %s", err) - } - read = true - if err != nil { - t.Errorf("Read error: %v", err) - return - } - if data[i] == "354 Go ahead" && msg == "." { - break - } - } - } - }(strings.Split(server, "\r\n")) - - 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 -To: other@example.com -Subject: SendMail test - -SendMail is working for me. -`, "\n", "\r\n", -1))) - if err == nil { - t.Errorf("Expected SendMail to be rejected due to a message injection attempt") - } - - err = SendMail(l.Addr().String(), nil, "test@example.com", []string{"other@example.com"}, []byte(strings.Replace(`From: test@example.com -To: other@example.com -Subject: SendMail test - -SendMail is working for me. -`, "\n", "\r\n", -1))) - if err != nil { - t.Errorf("%v", err) - } - - <-done - if err := bcmdbuf.Flush(); err != nil { - t.Errorf("flush failed: %s", err) - } - actualcmds := cmdbuf.String() - if client != actualcmds { - t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client) - } -} - -var sendMailServer = `220 hello world -502 EH? -250 mx.google.com at your service -250 Sender ok -250 Receiver ok -354 Go ahead -250 Data ok -221 Goodbye -` - -var sendMailClient = `EHLO localhost -HELO localhost -MAIL FROM: -RCPT TO: -DATA -From: test@example.com -To: other@example.com -Subject: SendMail test - -SendMail is working for me. -. -QUIT -` - -func TestSendMailWithAuth(t *testing.T) { - l, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatalf("Unable to create listener: %v", err) - } - defer func() { - _ = l.Close() - }() - - errCh := make(chan error) - go func() { - defer close(errCh) - conn, err := l.Accept() - if err != nil { - errCh <- fmt.Errorf("listener Accept: %w", err) - return - } - defer func() { - _ = conn.Close() - }() - - tc := textproto.NewConn(conn) - if err := tc.PrintfLine("220 hello world"); err != nil { - t.Errorf("textproto connetion print failed: %s", err) - } - msg, err := tc.ReadLine() - if err != nil { - errCh <- fmt.Errorf("textproto connection ReadLine error: %w", err) - return - } - const wantMsg = "EHLO localhost" - if msg != wantMsg { - errCh <- fmt.Errorf("unexpected response %q; want %q", msg, wantMsg) - return - } - err = tc.PrintfLine("250 mx.google.com at your service") - if err != nil { - errCh <- fmt.Errorf("textproto connection PrintfLine: %w", err) - return - } - }() - - err = SendMail(l.Addr().String(), PlainAuth("", "user", "pass", "smtp.google.com", false), "test@example.com", []string{"other@example.com"}, []byte(strings.Replace(`From: test@example.com -To: other@example.com -Subject: SendMail test - -SendMail is working for me. -`, "\n", "\r\n", -1))) - if err == nil { - t.Error("SendMail: Server doesn't support AUTH, expected to get an error, but got none ") - return - } - if err.Error() != "smtp: server doesn't support AUTH" { - t.Errorf("Expected: smtp: server doesn't support AUTH, got: %s", err) - } - err = <-errCh - if err != nil { - t.Fatalf("server error: %v", err) - } -} - -func TestAuthFailed(t *testing.T) { - server := strings.Join(strings.Split(authFailedServer, "\n"), "\r\n") - client := strings.Join(strings.Split(authFailedClient, "\n"), "\r\n") - var cmdbuf strings.Builder - bcmdbuf := bufio.NewWriter(&cmdbuf) - var fake faker - fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf) - c, err := NewClient(fake, "fake.host") - if err != nil { - t.Fatalf("NewClient: %v", err) - } - defer func() { - _ = c.Close() - }() - - c.tls = true - c.serverName = "smtp.google.com" - err = c.Auth(PlainAuth("", "user", "pass", "smtp.google.com", false)) - - if err == nil { - t.Error("Auth: expected error; got none") - } else if err.Error() != "535 Invalid credentials\nplease see www.example.com" { - t.Errorf("Auth: got error: %v, want: %s", err, "535 Invalid credentials\nplease see www.example.com") - } - - if err := bcmdbuf.Flush(); err != nil { - t.Errorf("flush failed: %s", err) - } - actualcmds := cmdbuf.String() - if client != actualcmds { - t.Errorf("Got:\n%s\nExpected:\n%s", actualcmds, client) - } -} - -var authFailedServer = `220 hello world -250-mx.google.com at your service -250 AUTH LOGIN PLAIN -535-Invalid credentials -535 please see www.example.com -221 Goodbye -` - -var authFailedClient = `EHLO localhost -AUTH PLAIN AHVzZXIAcGFzcw== -* -QUIT -` - -func TestTLSClient(t *testing.T) { - if runtime.GOOS == "freebsd" || runtime.GOOS == "js" || runtime.GOOS == "wasip1" { - SkipFlaky(t, 19229) - } - ln := newLocalListener(t) - defer func() { - _ = ln.Close() - }() - errc := make(chan error) - go func() { - errc <- sendMail(ln.Addr().String()) - }() - conn, err := ln.Accept() - if err != nil { - t.Fatalf("failed to accept connection: %v", err) - } - defer func() { - _ = conn.Close() - }() - if err := serverHandle(conn, t); err != nil { - t.Fatalf("failed to handle connection: %v", err) - } - if err := <-errc; err != nil { - t.Fatalf("client error: %v", err) - } -} - -func TestTLSConnState(t *testing.T) { - ln := newLocalListener(t) - defer func() { - _ = ln.Close() - }() - clientDone := make(chan bool) - serverDone := make(chan bool) - go func() { - defer close(serverDone) - c, err := ln.Accept() - if err != nil { - t.Errorf("Server accept: %v", err) - return - } - defer func() { - _ = c.Close() - }() - if err := serverHandle(c, t); err != nil { - t.Errorf("server error: %v", err) - } - }() - go func() { - defer close(clientDone) - c, err := Dial(ln.Addr().String()) - if err != nil { - t.Errorf("Client dial: %v", err) - return - } - defer func() { - _ = c.Quit() - }() - cfg := &tls.Config{ServerName: "example.com"} - testHookStartTLS(cfg) // set the RootCAs - if err := c.StartTLS(cfg); err != nil { - t.Errorf("StartTLS: %v", err) - return - } - cs, ok := c.TLSConnectionState() - if !ok { - t.Errorf("TLSConnectionState returned ok == false; want true") - return - } - if cs.Version == 0 || !cs.HandshakeComplete { - t.Errorf("ConnectionState = %#v; expect non-zero Version and HandshakeComplete", cs) - } - }() - <-clientDone - <-serverDone -} - -func TestClient_GetTLSConnectionState(t *testing.T) { - ln := newLocalListener(t) - defer func() { - _ = ln.Close() - }() - clientDone := make(chan bool) - serverDone := make(chan bool) - go func() { - defer close(serverDone) - c, err := ln.Accept() - if err != nil { - t.Errorf("Server accept: %v", err) - return - } - defer func() { - _ = c.Close() - }() - if err := serverHandle(c, t); err != nil { - t.Errorf("server error: %v", err) - } - }() - go func() { - defer close(clientDone) - c, err := Dial(ln.Addr().String()) - if err != nil { - t.Errorf("Client dial: %v", err) - return - } - defer func() { - _ = c.Quit() - }() - cfg := &tls.Config{ServerName: "example.com"} - testHookStartTLS(cfg) // set the RootCAs - if err := c.StartTLS(cfg); err != nil { - t.Errorf("StartTLS: %v", err) - return - } - cs, err := c.GetTLSConnectionState() - if err != nil { - t.Errorf("failed to get TLSConnectionState: %s", err) - return - } - if cs.Version == 0 || !cs.HandshakeComplete { - t.Errorf("ConnectionState = %#v; expect non-zero Version and HandshakeComplete", cs) - } - }() - <-clientDone - <-serverDone -} - -func TestClient_GetTLSConnectionState_noTLS(t *testing.T) { - ln := newLocalListener(t) - defer func() { - _ = ln.Close() - }() - clientDone := make(chan bool) - serverDone := make(chan bool) - go func() { - defer close(serverDone) - c, err := ln.Accept() - if err != nil { - t.Errorf("Server accept: %v", err) - return - } - defer func() { - _ = c.Close() - }() - if err := serverHandle(c, t); err != nil { - t.Errorf("server error: %v", err) - } - }() - go func() { - defer close(clientDone) - c, err := Dial(ln.Addr().String()) - if err != nil { - t.Errorf("Client dial: %v", err) - return - } - defer func() { - _ = c.Quit() - }() - _, err = c.GetTLSConnectionState() - if err == nil { - t.Error("GetTLSConnectionState: expected error; got nil") - return - } - }() - <-clientDone - <-serverDone -} - -func TestClient_GetTLSConnectionState_noConn(t *testing.T) { - ln := newLocalListener(t) - defer func() { - _ = ln.Close() - }() - clientDone := make(chan bool) - serverDone := make(chan bool) - go func() { - defer close(serverDone) - c, err := ln.Accept() - if err != nil { - t.Errorf("Server accept: %v", err) - return - } - defer func() { - _ = c.Close() - }() - if err := serverHandle(c, t); err != nil { - t.Errorf("server error: %v", err) - } - }() - go func() { - defer close(clientDone) - c, err := Dial(ln.Addr().String()) - if err != nil { - t.Errorf("Client dial: %v", err) - return - } - _ = c.Close() - _, err = c.GetTLSConnectionState() - if err == nil { - t.Error("GetTLSConnectionState: expected error; got nil") - return - } - }() - <-clientDone - <-serverDone -} - -func TestClient_GetTLSConnectionState_unableErr(t *testing.T) { - ln := newLocalListener(t) - defer func() { - _ = ln.Close() - }() - clientDone := make(chan bool) - serverDone := make(chan bool) - go func() { - defer close(serverDone) - c, err := ln.Accept() - if err != nil { - t.Errorf("Server accept: %v", err) - return - } - defer func() { - _ = c.Close() - }() - if err := serverHandle(c, t); err != nil { - t.Errorf("server error: %v", err) - } - }() - go func() { - defer close(clientDone) - c, err := Dial(ln.Addr().String()) - if err != nil { - t.Errorf("Client dial: %v", err) - return - } - defer func() { - _ = c.Quit() - }() - c.tls = true - _, err = c.GetTLSConnectionState() - if err == nil { - t.Error("GetTLSConnectionState: expected error; got nil") - return - } - }() - <-clientDone - <-serverDone -} - -func TestClient_HasConnection(t *testing.T) { - ln := newLocalListener(t) - defer func() { - _ = ln.Close() - }() - clientDone := make(chan bool) - serverDone := make(chan bool) - go func() { - defer close(serverDone) - c, err := ln.Accept() - if err != nil { - t.Errorf("Server accept: %v", err) - return - } - defer func() { - _ = c.Close() - }() - if err := serverHandle(c, t); err != nil { - t.Errorf("server error: %v", err) - } - }() - go func() { - defer close(clientDone) - c, err := Dial(ln.Addr().String()) - if err != nil { - t.Errorf("Client dial: %v", err) - return - } - cfg := &tls.Config{ServerName: "example.com"} - testHookStartTLS(cfg) // set the RootCAs - if err := c.StartTLS(cfg); err != nil { - t.Errorf("StartTLS: %v", err) - return - } - if !c.HasConnection() { - t.Error("HasConnection: expected true; got false") - return - } - if err = c.Quit(); err != nil { - t.Errorf("closing connection failed: %s", err) - return - } - if c.HasConnection() { - t.Error("HasConnection: expected false; got true") - } - }() - <-clientDone - <-serverDone -} - -func TestClient_SetDSNMailReturnOption(t *testing.T) { - ln := newLocalListener(t) - defer func() { - _ = ln.Close() - }() - clientDone := make(chan bool) - serverDone := make(chan bool) - go func() { - defer close(serverDone) - c, err := ln.Accept() - if err != nil { - t.Errorf("Server accept: %v", err) - return - } - defer func() { - _ = c.Close() - }() - if err := serverHandle(c, t); err != nil { - t.Errorf("server error: %v", err) - } - }() - go func() { - defer close(clientDone) - c, err := Dial(ln.Addr().String()) - if err != nil { - t.Errorf("Client dial: %v", err) - return - } - defer func() { - _ = c.Quit() - }() - c.SetDSNMailReturnOption("foo") - if c.dsnmrtype != "foo" { - t.Errorf("SetDSNMailReturnOption: expected %s; got %s", "foo", c.dsnrntype) - } - }() - <-clientDone - <-serverDone -} - -func TestClient_SetDSNRcptNotifyOption(t *testing.T) { - ln := newLocalListener(t) - defer func() { - _ = ln.Close() - }() - clientDone := make(chan bool) - serverDone := make(chan bool) - go func() { - defer close(serverDone) - c, err := ln.Accept() - if err != nil { - t.Errorf("Server accept: %v", err) - return - } - defer func() { - _ = c.Close() - }() - if err := serverHandle(c, t); err != nil { - t.Errorf("server error: %v", err) - } - }() - go func() { - defer close(clientDone) - c, err := Dial(ln.Addr().String()) - if err != nil { - t.Errorf("Client dial: %v", err) - return - } - defer func() { - _ = c.Quit() - }() - c.SetDSNRcptNotifyOption("foo") - if c.dsnrntype != "foo" { - t.Errorf("SetDSNMailReturnOption: expected %s; got %s", "foo", c.dsnrntype) - } - }() - <-clientDone - <-serverDone -} - -func TestClient_UpdateDeadline(t *testing.T) { - ln := newLocalListener(t) - defer func() { - _ = ln.Close() - }() - clientDone := make(chan bool) - serverDone := make(chan bool) - go func() { - defer close(serverDone) - c, err := ln.Accept() - if err != nil { - t.Errorf("Server accept: %v", err) - return - } - defer func() { - _ = c.Close() - }() - if err = serverHandle(c, t); err != nil { - t.Errorf("server error: %v", err) - } - }() - go func() { - defer close(clientDone) - c, err := Dial(ln.Addr().String()) - if err != nil { - t.Errorf("Client dial: %v", err) - return - } - defer func() { - _ = c.Close() - }() - if !c.HasConnection() { - t.Error("HasConnection: expected true; got false") - return - } - if err = c.UpdateDeadline(time.Millisecond * 20); err != nil { - t.Errorf("failed to update deadline: %s", err) - return - } - time.Sleep(time.Millisecond * 50) - if !c.HasConnection() { - t.Error("HasConnection: expected true; got false") - return - } - }() - <-clientDone - <-serverDone -} - -func newLocalListener(t *testing.T) net.Listener { - ln, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - ln, err = net.Listen("tcp6", "[::1]:0") - } - if err != nil { - t.Fatal(err) - } - return ln -} - -type smtpSender struct { - w io.Writer -} - -func (s smtpSender) send(f string) { - _, _ = s.w.Write([]byte(f + "\r\n")) -} - -// smtp server, finely tailored to deal with our own client only! -func serverHandle(c net.Conn, t *testing.T) error { - send := smtpSender{c}.send - send("220 127.0.0.1 ESMTP service ready") - s := bufio.NewScanner(c) - tf := func(config *tls.Config) error { - c = tls.Server(c, config) - defer func() { - _ = c.Close() - }() - return serverHandleTLS(c, t) - } - for s.Scan() { - switch s.Text() { - case "EHLO localhost": - send("250-127.0.0.1 ESMTP offers a warm hug of welcome") - send("250-STARTTLS") - send("250 Ok") - case "STARTTLS": - send("220 Go ahead") - keypair, err := tls.X509KeyPair(localhostCert, localhostKey) - if err != nil { - return err - } - config := &tls.Config{Certificates: []tls.Certificate{keypair}} - return tf(config) - case "QUIT": - return nil - default: - t.Fatalf("unrecognized command: %q", s.Text()) - } - } - return s.Err() -} - -func serverHandleTLS(c net.Conn, t *testing.T) error { - send := smtpSender{c}.send - s := bufio.NewScanner(c) - for s.Scan() { - switch s.Text() { - case "EHLO localhost": - send("250 Ok") - case "MAIL FROM:": - send("250 Ok") - case "RCPT TO:": - send("250 Ok") - case "DATA": - send("354 send the mail data, end with .") - send("250 Ok") - case "Subject: test": - case "": - case "howdy!": - case ".": - case "QUIT": - send("221 127.0.0.1 Service closing transmission channel") - return nil - default: - t.Fatalf("unrecognized command during TLS: %q", s.Text()) - } - } - return s.Err() -} - -func init() { - testRootCAs := x509.NewCertPool() - testRootCAs.AppendCertsFromPEM(localhostCert) - testHookStartTLS = func(config *tls.Config) { - config.RootCAs = testRootCAs - } -} - -func sendMail(hostPort string) error { - from := "joe1@example.com" - to := []string{"joe2@example.com"} - return SendMail(hostPort, nil, from, to, []byte("Subject: test\n\nhowdy!")) -} - -var flaky = flag.Bool("flaky", false, "run known-flaky tests too") - -func SkipFlaky(t testing.TB, issue int) { - t.Helper() - if !*flaky { - t.Skipf("skipping known flaky test without the -flaky flag; see golang.org/issue/%d", issue) - } -} - - -*/ - // faker is a struct embedding io.ReadWriter to simulate network connections for testing purposes. type faker struct { io.ReadWriter