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 implements the Simple Mail Transfer Protocol as defined in RFC 5321. 6// It also implements the following extensions: 7// 8// 8BITMIME RFC 1652 9// AUTH RFC 2554 10// STARTTLS RFC 3207 11// 12// Additional extensions may be handled by clients. 13// 14// The smtp package is frozen and is not accepting new features. 15// Some external packages provide more functionality. See: 16// 17// https://godoc.org/?q=smtp 18package smtp 19 20import ( 21 "crypto/tls" 22 "encoding/base64" 23 "errors" 24 "fmt" 25 "io" 26 "net" 27 "net/textproto" 28 "strings" 29) 30 31// A Client represents a client connection to an SMTP server. 32type Client struct { 33 // Text is the textproto.Conn used by the Client. It is exported to allow for 34 // clients to add extensions. 35 Text *textproto.Conn 36 // keep a reference to the connection so it can be used to create a TLS 37 // connection later 38 conn net.Conn 39 // whether the Client is using TLS 40 tls bool 41 serverName string 42 // map of supported extensions 43 ext map[string]string 44 // supported auth mechanisms 45 auth []string 46 localName string // the name to use in HELO/EHLO 47 didHello bool // whether we've said HELO/EHLO 48 helloError error // the error from the hello 49} 50 51// Dial returns a new [Client] connected to an SMTP server at addr. 52// The addr must include a port, as in "mail.example.com:smtp". 53func Dial(addr string) (*Client, error) { 54 conn, err := net.Dial("tcp", addr) 55 if err != nil { 56 return nil, err 57 } 58 host, _, _ := net.SplitHostPort(addr) 59 return NewClient(conn, host) 60} 61 62// NewClient returns a new [Client] using an existing connection and host as a 63// server name to be used when authenticating. 64func NewClient(conn net.Conn, host string) (*Client, error) { 65 text := textproto.NewConn(conn) 66 _, _, err := text.ReadResponse(220) 67 if err != nil { 68 text.Close() 69 return nil, err 70 } 71 c := &Client{Text: text, conn: conn, serverName: host, localName: "localhost"} 72 _, c.tls = conn.(*tls.Conn) 73 return c, nil 74} 75 76// Close closes the connection. 77func (c *Client) Close() error { 78 return c.Text.Close() 79} 80 81// hello runs a hello exchange if needed. 82func (c *Client) hello() error { 83 if !c.didHello { 84 c.didHello = true 85 err := c.ehlo() 86 if err != nil { 87 c.helloError = c.helo() 88 } 89 } 90 return c.helloError 91} 92 93// Hello sends a HELO or EHLO to the server as the given host name. 94// Calling this method is only necessary if the client needs control 95// over the host name used. The client will introduce itself as "localhost" 96// automatically otherwise. If Hello is called, it must be called before 97// any of the other methods. 98func (c *Client) Hello(localName string) error { 99 if err := validateLine(localName); err != nil { 100 return err 101 } 102 if c.didHello { 103 return errors.New("smtp: Hello called after other methods") 104 } 105 c.localName = localName 106 return c.hello() 107} 108 109// cmd is a convenience function that sends a command and returns the response 110func (c *Client) cmd(expectCode int, format string, args ...any) (int, string, error) { 111 id, err := c.Text.Cmd(format, args...) 112 if err != nil { 113 return 0, "", err 114 } 115 c.Text.StartResponse(id) 116 defer c.Text.EndResponse(id) 117 code, msg, err := c.Text.ReadResponse(expectCode) 118 return code, msg, err 119} 120 121// helo sends the HELO greeting to the server. It should be used only when the 122// server does not support ehlo. 123func (c *Client) helo() error { 124 c.ext = nil 125 _, _, err := c.cmd(250, "HELO %s", c.localName) 126 return err 127} 128 129// ehlo sends the EHLO (extended hello) greeting to the server. It 130// should be the preferred greeting for servers that support it. 131func (c *Client) ehlo() error { 132 _, msg, err := c.cmd(250, "EHLO %s", c.localName) 133 if err != nil { 134 return err 135 } 136 ext := make(map[string]string) 137 extList := strings.Split(msg, "\n") 138 if len(extList) > 1 { 139 extList = extList[1:] 140 for _, line := range extList { 141 k, v, _ := strings.Cut(line, " ") 142 ext[k] = v 143 } 144 } 145 if mechs, ok := ext["AUTH"]; ok { 146 c.auth = strings.Split(mechs, " ") 147 } 148 c.ext = ext 149 return err 150} 151 152// StartTLS sends the STARTTLS command and encrypts all further communication. 153// Only servers that advertise the STARTTLS extension support this function. 154func (c *Client) StartTLS(config *tls.Config) error { 155 if err := c.hello(); err != nil { 156 return err 157 } 158 _, _, err := c.cmd(220, "STARTTLS") 159 if err != nil { 160 return err 161 } 162 c.conn = tls.Client(c.conn, config) 163 c.Text = textproto.NewConn(c.conn) 164 c.tls = true 165 return c.ehlo() 166} 167 168// TLSConnectionState returns the client's TLS connection state. 169// The return values are their zero values if [Client.StartTLS] did 170// not succeed. 171func (c *Client) TLSConnectionState() (state tls.ConnectionState, ok bool) { 172 tc, ok := c.conn.(*tls.Conn) 173 if !ok { 174 return 175 } 176 return tc.ConnectionState(), true 177} 178 179// Verify checks the validity of an email address on the server. 180// If Verify returns nil, the address is valid. A non-nil return 181// does not necessarily indicate an invalid address. Many servers 182// will not verify addresses for security reasons. 183func (c *Client) Verify(addr string) error { 184 if err := validateLine(addr); err != nil { 185 return err 186 } 187 if err := c.hello(); err != nil { 188 return err 189 } 190 _, _, err := c.cmd(250, "VRFY %s", addr) 191 return err 192} 193 194// Auth authenticates a client using the provided authentication mechanism. 195// A failed authentication closes the connection. 196// Only servers that advertise the AUTH extension support this function. 197func (c *Client) Auth(a Auth) error { 198 if err := c.hello(); err != nil { 199 return err 200 } 201 encoding := base64.StdEncoding 202 mech, resp, err := a.Start(&ServerInfo{c.serverName, c.tls, c.auth}) 203 if err != nil { 204 c.Quit() 205 return err 206 } 207 resp64 := make([]byte, encoding.EncodedLen(len(resp))) 208 encoding.Encode(resp64, resp) 209 code, msg64, err := c.cmd(0, "%s", strings.TrimSpace(fmt.Sprintf("AUTH %s %s", mech, resp64))) 210 for err == nil { 211 var msg []byte 212 switch code { 213 case 334: 214 msg, err = encoding.DecodeString(msg64) 215 case 235: 216 // the last message isn't base64 because it isn't a challenge 217 msg = []byte(msg64) 218 default: 219 err = &textproto.Error{Code: code, Msg: msg64} 220 } 221 if err == nil { 222 resp, err = a.Next(msg, code == 334) 223 } 224 if err != nil { 225 // abort the AUTH 226 c.cmd(501, "*") 227 c.Quit() 228 break 229 } 230 if resp == nil { 231 break 232 } 233 resp64 = make([]byte, encoding.EncodedLen(len(resp))) 234 encoding.Encode(resp64, resp) 235 code, msg64, err = c.cmd(0, "%s", resp64) 236 } 237 return err 238} 239 240// Mail issues a MAIL command to the server using the provided email address. 241// If the server supports the 8BITMIME extension, Mail adds the BODY=8BITMIME 242// parameter. If the server supports the SMTPUTF8 extension, Mail adds the 243// SMTPUTF8 parameter. 244// This initiates a mail transaction and is followed by one or more [Client.Rcpt] calls. 245func (c *Client) Mail(from string) error { 246 if err := validateLine(from); err != nil { 247 return err 248 } 249 if err := c.hello(); err != nil { 250 return err 251 } 252 cmdStr := "MAIL FROM:<%s>" 253 if c.ext != nil { 254 if _, ok := c.ext["8BITMIME"]; ok { 255 cmdStr += " BODY=8BITMIME" 256 } 257 if _, ok := c.ext["SMTPUTF8"]; ok { 258 cmdStr += " SMTPUTF8" 259 } 260 } 261 _, _, err := c.cmd(250, cmdStr, from) 262 return err 263} 264 265// Rcpt issues a RCPT command to the server using the provided email address. 266// A call to Rcpt must be preceded by a call to [Client.Mail] and may be followed by 267// a [Client.Data] call or another Rcpt call. 268func (c *Client) Rcpt(to string) error { 269 if err := validateLine(to); err != nil { 270 return err 271 } 272 _, _, err := c.cmd(25, "RCPT TO:<%s>", to) 273 return err 274} 275 276type dataCloser struct { 277 c *Client 278 io.WriteCloser 279} 280 281func (d *dataCloser) Close() error { 282 d.WriteCloser.Close() 283 _, _, err := d.c.Text.ReadResponse(250) 284 return err 285} 286 287// Data issues a DATA command to the server and returns a writer that 288// can be used to write the mail headers and body. The caller should 289// close the writer before calling any more methods on c. A call to 290// Data must be preceded by one or more calls to [Client.Rcpt]. 291func (c *Client) Data() (io.WriteCloser, error) { 292 _, _, err := c.cmd(354, "DATA") 293 if err != nil { 294 return nil, err 295 } 296 return &dataCloser{c, c.Text.DotWriter()}, nil 297} 298 299var testHookStartTLS func(*tls.Config) // nil, except for tests 300 301// SendMail connects to the server at addr, switches to TLS if 302// possible, authenticates with the optional mechanism a if possible, 303// and then sends an email from address from, to addresses to, with 304// message msg. 305// The addr must include a port, as in "mail.example.com:smtp". 306// 307// The addresses in the to parameter are the SMTP RCPT addresses. 308// 309// The msg parameter should be an RFC 822-style email with headers 310// first, a blank line, and then the message body. The lines of msg 311// should be CRLF terminated. The msg headers should usually include 312// fields such as "From", "To", "Subject", and "Cc". Sending "Bcc" 313// messages is accomplished by including an email address in the to 314// parameter but not including it in the msg headers. 315// 316// The SendMail function and the net/smtp package are low-level 317// mechanisms and provide no support for DKIM signing, MIME 318// attachments (see the mime/multipart package), or other mail 319// functionality. Higher-level packages exist outside of the standard 320// library. 321func SendMail(addr string, a Auth, from string, to []string, msg []byte) error { 322 if err := validateLine(from); err != nil { 323 return err 324 } 325 for _, recp := range to { 326 if err := validateLine(recp); err != nil { 327 return err 328 } 329 } 330 c, err := Dial(addr) 331 if err != nil { 332 return err 333 } 334 defer c.Close() 335 if err = c.hello(); err != nil { 336 return err 337 } 338 if ok, _ := c.Extension("STARTTLS"); ok { 339 config := &tls.Config{ServerName: c.serverName} 340 if testHookStartTLS != nil { 341 testHookStartTLS(config) 342 } 343 if err = c.StartTLS(config); err != nil { 344 return err 345 } 346 } 347 if a != nil && c.ext != nil { 348 if _, ok := c.ext["AUTH"]; !ok { 349 return errors.New("smtp: server doesn't support AUTH") 350 } 351 if err = c.Auth(a); err != nil { 352 return err 353 } 354 } 355 if err = c.Mail(from); err != nil { 356 return err 357 } 358 for _, addr := range to { 359 if err = c.Rcpt(addr); err != nil { 360 return err 361 } 362 } 363 w, err := c.Data() 364 if err != nil { 365 return err 366 } 367 _, err = w.Write(msg) 368 if err != nil { 369 return err 370 } 371 err = w.Close() 372 if err != nil { 373 return err 374 } 375 return c.Quit() 376} 377 378// Extension reports whether an extension is support by the server. 379// The extension name is case-insensitive. If the extension is supported, 380// Extension also returns a string that contains any parameters the 381// server specifies for the extension. 382func (c *Client) Extension(ext string) (bool, string) { 383 if err := c.hello(); err != nil { 384 return false, "" 385 } 386 if c.ext == nil { 387 return false, "" 388 } 389 ext = strings.ToUpper(ext) 390 param, ok := c.ext[ext] 391 return ok, param 392} 393 394// Reset sends the RSET command to the server, aborting the current mail 395// transaction. 396func (c *Client) Reset() error { 397 if err := c.hello(); err != nil { 398 return err 399 } 400 _, _, err := c.cmd(250, "RSET") 401 return err 402} 403 404// Noop sends the NOOP command to the server. It does nothing but check 405// that the connection to the server is okay. 406func (c *Client) Noop() error { 407 if err := c.hello(); err != nil { 408 return err 409 } 410 _, _, err := c.cmd(250, "NOOP") 411 return err 412} 413 414// Quit sends the QUIT command and closes the connection to the server. 415func (c *Client) Quit() error { 416 if err := c.hello(); err != nil { 417 return err 418 } 419 _, _, err := c.cmd(221, "QUIT") 420 if err != nil { 421 return err 422 } 423 return c.Text.Close() 424} 425 426// validateLine checks to see if a line has CR or LF as per RFC 5321. 427func validateLine(line string) error { 428 if strings.ContainsAny(line, "\n\r") { 429 return errors.New("smtp: A line must not contain CR or LF") 430 } 431 return nil 432} 433