]> Cypherpunks repositories - gostls13.git/commitdiff
net/smtp: set ServerName in StartTLS, as now required by crypto/tls
authorMike Andrews <mra@xoba.com>
Tue, 4 Mar 2014 21:43:26 +0000 (13:43 -0800)
committerBrad Fitzpatrick <bradfitz@golang.org>
Tue, 4 Mar 2014 21:43:26 +0000 (13:43 -0800)
the crypto/tls revision d3d43f270632 (CL 67010043, requiring ServerName or InsecureSkipVerify) breaks net/smtp,
since it seems impossible to do SMTP via TLS anymore. i've tried to fix this by simply using a tls.Config with
ServerName, instead of a nil *tls.Config. without this fix, doing SMTP with TLS results in error "tls: either
ServerName or InsecureSkipVerify must be specified in the tls.Config".

testing: the new method TestTlsClient(...) sets up a skeletal smtp server with tls capability, and test client
injects a "fake" certificate allowing tls to work on localhost; thus, the modification to SendMail(...) enabling
this.

Fixes #7437.

LGTM=bradfitz
R=golang-codereviews, josharian, bradfitz
CC=golang-codereviews
https://golang.org/cl/70380043

src/pkg/net/smtp/smtp.go
src/pkg/net/smtp/smtp_test.go

index a0a478a85246460d5240bde853455bc7a667746b..87dea442c46e1f7ae841290074aaa64f4e721874 100644 (file)
@@ -264,6 +264,8 @@ func (c *Client) Data() (io.WriteCloser, error) {
        return &dataCloser{c, c.Text.DotWriter()}, nil
 }
 
+var testHookStartTLS func(*tls.Config) // nil, except for tests
+
 // SendMail connects to the server at addr, switches to TLS if
 // possible, authenticates with the optional mechanism a if possible,
 // and then sends an email from address from, to addresses to, with
@@ -278,7 +280,11 @@ func SendMail(addr string, a Auth, from string, to []string, msg []byte) error {
                return err
        }
        if ok, _ := c.Extension("STARTTLS"); ok {
-               if err = c.StartTLS(nil); err != nil {
+               config := &tls.Config{ServerName: c.serverName}
+               if testHookStartTLS != nil {
+                       testHookStartTLS(config)
+               }
+               if err = c.StartTLS(config); err != nil {
                        return err
                }
        }
index 2133dc7c7baeccdce64274854bd91583487dcaa8..3fba1ea5ae35ebf80f97f1354b7712ae09e7316f 100644 (file)
@@ -7,6 +7,8 @@ package smtp
 import (
        "bufio"
        "bytes"
+       "crypto/tls"
+       "crypto/x509"
        "io"
        "net"
        "net/textproto"
@@ -548,3 +550,145 @@ AUTH PLAIN AHVzZXIAcGFzcw==
 *
 QUIT
 `
+
+func TestTLSClient(t *testing.T) {
+       ln := newLocalListener(t)
+       defer 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 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 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)
+       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}}
+                       c = tls.Server(c, config)
+                       defer c.Close()
+                       return serverHandleTLS(c, t)
+               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:<joe1@example.com>":
+                       send("250 Ok")
+               case "RCPT TO:<joe2@example.com>":
+                       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 {
+       host, _, err := net.SplitHostPort(hostPort)
+       if err != nil {
+               return err
+       }
+       auth := PlainAuth("", "", "", host)
+       from := "joe1@example.com"
+       to := []string{"joe2@example.com"}
+       return SendMail(hostPort, auth, from, to, []byte("Subject: test\n\nhowdy!"))
+}
+
+// (copied from net/http/httptest)
+// localhostCert is a PEM-encoded TLS cert with SAN IPs
+// "127.0.0.1" and "[::1]", expiring at the last second of 2049 (the end
+// of ASN.1 time).
+// generated from src/pkg/crypto/tls:
+// 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
+var localhostCert = []byte(`-----BEGIN CERTIFICATE-----
+MIIBdzCCASOgAwIBAgIBADALBgkqhkiG9w0BAQUwEjEQMA4GA1UEChMHQWNtZSBD
+bzAeFw03MDAxMDEwMDAwMDBaFw00OTEyMzEyMzU5NTlaMBIxEDAOBgNVBAoTB0Fj
+bWUgQ28wWjALBgkqhkiG9w0BAQEDSwAwSAJBAN55NcYKZeInyTuhcCwFMhDHCmwa
+IUSdtXdcbItRB/yfXGBhiex00IaLXQnSU+QZPRZWYqeTEbFSgihqi1PUDy8CAwEA
+AaNoMGYwDgYDVR0PAQH/BAQDAgCkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1Ud
+EwEB/wQFMAMBAf8wLgYDVR0RBCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAA
+AAAAAAAAAAAAAAEwCwYJKoZIhvcNAQEFA0EAAoQn/ytgqpiLcZu9XKbCJsJcvkgk
+Se6AbGXgSlq+ZCEVo0qIwSgeBqmsJxUu7NCSOwVJLYNEBO2DtIxoYVk+MA==
+-----END CERTIFICATE-----`)
+
+// localhostKey is the private key for localhostCert.
+var localhostKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
+MIIBPAIBAAJBAN55NcYKZeInyTuhcCwFMhDHCmwaIUSdtXdcbItRB/yfXGBhiex0
+0IaLXQnSU+QZPRZWYqeTEbFSgihqi1PUDy8CAwEAAQJBAQdUx66rfh8sYsgfdcvV
+NoafYpnEcB5s4m/vSVe6SU7dCK6eYec9f9wpT353ljhDUHq3EbmE4foNzJngh35d
+AekCIQDhRQG5Li0Wj8TM4obOnnXUXf1jRv0UkzE9AHWLG5q3AwIhAPzSjpYUDjVW
+MCUXgckTpKCuGwbJk7424Nb8bLzf3kllAiA5mUBgjfr/WtFSJdWcPQ4Zt9KTMNKD
+EUO0ukpTwEIl6wIhAMbGqZK3zAAFdq8DD2jPx+UJXnh0rnOkZBzDtJ6/iN69AiEA
+1Aq8MJgTaYsDQWyU/hDq5YkDJc9e9DSCvUIzqxQWMQE=
+-----END RSA PRIVATE KEY-----`)