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