github.com/zebozhuang/go@v0.0.0-20200207033046-f8a98f6f5c5d/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 c.didHello { 98 return errors.New("smtp: Hello called after other methods") 99 } 100 c.localName = localName 101 return c.hello() 102 } 103 104 // cmd is a convenience function that sends a command and returns the response 105 func (c *Client) cmd(expectCode int, format string, args ...interface{}) (int, string, error) { 106 id, err := c.Text.Cmd(format, args...) 107 if err != nil { 108 return 0, "", err 109 } 110 c.Text.StartResponse(id) 111 defer c.Text.EndResponse(id) 112 code, msg, err := c.Text.ReadResponse(expectCode) 113 return code, msg, err 114 } 115 116 // helo sends the HELO greeting to the server. It should be used only when the 117 // server does not support ehlo. 118 func (c *Client) helo() error { 119 c.ext = nil 120 _, _, err := c.cmd(250, "HELO %s", c.localName) 121 return err 122 } 123 124 // ehlo sends the EHLO (extended hello) greeting to the server. It 125 // should be the preferred greeting for servers that support it. 126 func (c *Client) ehlo() error { 127 _, msg, err := c.cmd(250, "EHLO %s", c.localName) 128 if err != nil { 129 return err 130 } 131 ext := make(map[string]string) 132 extList := strings.Split(msg, "\n") 133 if len(extList) > 1 { 134 extList = extList[1:] 135 for _, line := range extList { 136 args := strings.SplitN(line, " ", 2) 137 if len(args) > 1 { 138 ext[args[0]] = args[1] 139 } else { 140 ext[args[0]] = "" 141 } 142 } 143 } 144 if mechs, ok := ext["AUTH"]; ok { 145 c.auth = strings.Split(mechs, " ") 146 } 147 c.ext = ext 148 return err 149 } 150 151 // StartTLS sends the STARTTLS command and encrypts all further communication. 152 // Only servers that advertise the STARTTLS extension support this function. 153 func (c *Client) StartTLS(config *tls.Config) error { 154 if err := c.hello(); err != nil { 155 return err 156 } 157 _, _, err := c.cmd(220, "STARTTLS") 158 if err != nil { 159 return err 160 } 161 c.conn = tls.Client(c.conn, config) 162 c.Text = textproto.NewConn(c.conn) 163 c.tls = true 164 return c.ehlo() 165 } 166 167 // TLSConnectionState returns the client's TLS connection state. 168 // The return values are their zero values if StartTLS did 169 // not succeed. 170 func (c *Client) TLSConnectionState() (state tls.ConnectionState, ok bool) { 171 tc, ok := c.conn.(*tls.Conn) 172 if !ok { 173 return 174 } 175 return tc.ConnectionState(), true 176 } 177 178 // Verify checks the validity of an email address on the server. 179 // If Verify returns nil, the address is valid. A non-nil return 180 // does not necessarily indicate an invalid address. Many servers 181 // will not verify addresses for security reasons. 182 func (c *Client) Verify(addr string) error { 183 if err := c.hello(); err != nil { 184 return err 185 } 186 _, _, err := c.cmd(250, "VRFY %s", addr) 187 return err 188 } 189 190 // Auth authenticates a client using the provided authentication mechanism. 191 // A failed authentication closes the connection. 192 // Only servers that advertise the AUTH extension support this function. 193 func (c *Client) Auth(a Auth) error { 194 if err := c.hello(); err != nil { 195 return err 196 } 197 encoding := base64.StdEncoding 198 mech, resp, err := a.Start(&ServerInfo{c.serverName, c.tls, c.auth}) 199 if err != nil { 200 c.Quit() 201 return err 202 } 203 resp64 := make([]byte, encoding.EncodedLen(len(resp))) 204 encoding.Encode(resp64, resp) 205 code, msg64, err := c.cmd(0, strings.TrimSpace(fmt.Sprintf("AUTH %s %s", mech, resp64))) 206 for err == nil { 207 var msg []byte 208 switch code { 209 case 334: 210 msg, err = encoding.DecodeString(msg64) 211 case 235: 212 // the last message isn't base64 because it isn't a challenge 213 msg = []byte(msg64) 214 default: 215 err = &textproto.Error{Code: code, Msg: msg64} 216 } 217 if err == nil { 218 resp, err = a.Next(msg, code == 334) 219 } 220 if err != nil { 221 // abort the AUTH 222 c.cmd(501, "*") 223 c.Quit() 224 break 225 } 226 if resp == nil { 227 break 228 } 229 resp64 = make([]byte, encoding.EncodedLen(len(resp))) 230 encoding.Encode(resp64, resp) 231 code, msg64, err = c.cmd(0, string(resp64)) 232 } 233 return err 234 } 235 236 // Mail issues a MAIL command to the server using the provided email address. 237 // If the server supports the 8BITMIME extension, Mail adds the BODY=8BITMIME 238 // parameter. 239 // This initiates a mail transaction and is followed by one or more Rcpt calls. 240 func (c *Client) Mail(from string) error { 241 if err := c.hello(); err != nil { 242 return err 243 } 244 cmdStr := "MAIL FROM:<%s>" 245 if c.ext != nil { 246 if _, ok := c.ext["8BITMIME"]; ok { 247 cmdStr += " BODY=8BITMIME" 248 } 249 } 250 _, _, err := c.cmd(250, cmdStr, from) 251 return err 252 } 253 254 // Rcpt issues a RCPT command to the server using the provided email address. 255 // A call to Rcpt must be preceded by a call to Mail and may be followed by 256 // a Data call or another Rcpt call. 257 func (c *Client) Rcpt(to string) error { 258 _, _, err := c.cmd(25, "RCPT TO:<%s>", to) 259 return err 260 } 261 262 type dataCloser struct { 263 c *Client 264 io.WriteCloser 265 } 266 267 func (d *dataCloser) Close() error { 268 d.WriteCloser.Close() 269 _, _, err := d.c.Text.ReadResponse(250) 270 return err 271 } 272 273 // Data issues a DATA command to the server and returns a writer that 274 // can be used to write the mail headers and body. The caller should 275 // close the writer before calling any more methods on c. A call to 276 // Data must be preceded by one or more calls to Rcpt. 277 func (c *Client) Data() (io.WriteCloser, error) { 278 _, _, err := c.cmd(354, "DATA") 279 if err != nil { 280 return nil, err 281 } 282 return &dataCloser{c, c.Text.DotWriter()}, nil 283 } 284 285 var testHookStartTLS func(*tls.Config) // nil, except for tests 286 287 // SendMail connects to the server at addr, switches to TLS if 288 // possible, authenticates with the optional mechanism a if possible, 289 // and then sends an email from address from, to addresses to, with 290 // message msg. 291 // The addr must include a port, as in "mail.example.com:smtp". 292 // 293 // The addresses in the to parameter are the SMTP RCPT addresses. 294 // 295 // The msg parameter should be an RFC 822-style email with headers 296 // first, a blank line, and then the message body. The lines of msg 297 // should be CRLF terminated. The msg headers should usually include 298 // fields such as "From", "To", "Subject", and "Cc". Sending "Bcc" 299 // messages is accomplished by including an email address in the to 300 // parameter but not including it in the msg headers. 301 // 302 // The SendMail function and the net/smtp package are low-level 303 // mechanisms and provide no support for DKIM signing, MIME 304 // attachments (see the mime/multipart package), or other mail 305 // functionality. Higher-level packages exist outside of the standard 306 // library. 307 func SendMail(addr string, a Auth, from string, to []string, msg []byte) error { 308 c, err := Dial(addr) 309 if err != nil { 310 return err 311 } 312 defer c.Close() 313 if err = c.hello(); err != nil { 314 return err 315 } 316 if ok, _ := c.Extension("STARTTLS"); ok { 317 config := &tls.Config{ServerName: c.serverName} 318 if testHookStartTLS != nil { 319 testHookStartTLS(config) 320 } 321 if err = c.StartTLS(config); err != nil { 322 return err 323 } 324 } 325 if a != nil && c.ext != nil { 326 if _, ok := c.ext["AUTH"]; ok { 327 if err = c.Auth(a); err != nil { 328 return err 329 } 330 } 331 } 332 if err = c.Mail(from); err != nil { 333 return err 334 } 335 for _, addr := range to { 336 if err = c.Rcpt(addr); err != nil { 337 return err 338 } 339 } 340 w, err := c.Data() 341 if err != nil { 342 return err 343 } 344 _, err = w.Write(msg) 345 if err != nil { 346 return err 347 } 348 err = w.Close() 349 if err != nil { 350 return err 351 } 352 return c.Quit() 353 } 354 355 // Extension reports whether an extension is support by the server. 356 // The extension name is case-insensitive. If the extension is supported, 357 // Extension also returns a string that contains any parameters the 358 // server specifies for the extension. 359 func (c *Client) Extension(ext string) (bool, string) { 360 if err := c.hello(); err != nil { 361 return false, "" 362 } 363 if c.ext == nil { 364 return false, "" 365 } 366 ext = strings.ToUpper(ext) 367 param, ok := c.ext[ext] 368 return ok, param 369 } 370 371 // Reset sends the RSET command to the server, aborting the current mail 372 // transaction. 373 func (c *Client) Reset() error { 374 if err := c.hello(); err != nil { 375 return err 376 } 377 _, _, err := c.cmd(250, "RSET") 378 return err 379 } 380 381 // Quit sends the QUIT command and closes the connection to the server. 382 func (c *Client) Quit() error { 383 if err := c.hello(); err != nil { 384 return err 385 } 386 _, _, err := c.cmd(221, "QUIT") 387 if err != nil { 388 return err 389 } 390 return c.Text.Close() 391 }