Skip to content

Commit

Permalink
net/smtp: set ServerName in StartTLS, as now required by crypto/tls
Browse files Browse the repository at this point in the history
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
  • Loading branch information
xoba authored and bradfitz committed Mar 4, 2014
1 parent 3b01561 commit a18bfb8
Show file tree
Hide file tree
Showing 2 changed files with 151 additions and 1 deletion.
8 changes: 7 additions & 1 deletion src/pkg/net/smtp/smtp.go
Expand Up @@ -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
Expand All @@ -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
}
}
Expand Down
144 changes: 144 additions & 0 deletions src/pkg/net/smtp/smtp_test.go
Expand Up @@ -7,6 +7,8 @@ package smtp
import (
"bufio"
"bytes"
"crypto/tls"
"crypto/x509"
"io"
"net"
"net/textproto"
Expand Down Expand Up @@ -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-----`)

0 comments on commit a18bfb8

Please sign in to comment.