mirror of
https://github.com/golang/go
synced 2024-11-13 16:50:23 -07:00
net/smtp: set ServerName in StartTLS, as now required by crypto/tls
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
This commit is contained in:
parent
3b015616f7
commit
a18bfb8c67
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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-----`)
|
||||
|
Loading…
Reference in New Issue
Block a user