github.com/kdevb0x/go@v0.0.0-20180115030120-39687051e9e7/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. 245 // This initiates a mail transaction and is followed by one or more Rcpt calls. 246 func (c *Client) Mail(from string) error { 247 if err := validateLine(from); err != nil { 248 return err 249 } 250 if err := c.hello(); err != nil { 251 return err 252 } 253 cmdStr := "MAIL FROM:<%s>" 254 if c.ext != nil { 255 if _, ok := c.ext["8BITMIME"]; ok { 256 cmdStr += " BODY=8BITMIME" 257 } 258 } 259 _, _, err := c.cmd(250, cmdStr, from) 260 return err 261 } 262 263 // Rcpt issues a RCPT command to the server using the provided email address. 264 // A call to Rcpt must be preceded by a call to Mail and may be followed by 265 // a Data call or another Rcpt call. 266 func (c *Client) Rcpt(to string) error { 267 if err := validateLine(to); err != nil { 268 return err 269 } 270 _, _, err := c.cmd(25, "RCPT TO:<%s>", to) 271 return err 272 } 273 274 type dataCloser struct { 275 c *Client 276 io.WriteCloser 277 } 278 279 func (d *dataCloser) Close() error { 280 d.WriteCloser.Close() 281 _, _, err := d.c.Text.ReadResponse(250) 282 return err 283 } 284 285 // Data issues a DATA command to the server and returns a writer that 286 // can be used to write the mail headers and body. The caller should 287 // close the writer before calling any more methods on c. A call to 288 // Data must be preceded by one or more calls to Rcpt. 289 func (c *Client) Data() (io.WriteCloser, error) { 290 _, _, err := c.cmd(354, "DATA") 291 if err != nil { 292 return nil, err 293 } 294 return &dataCloser{c, c.Text.DotWriter()}, nil 295 } 296 297 var testHookStartTLS func(*tls.Config) // nil, except for tests 298 299 // SendMail connects to the server at addr, switches to TLS if 300 // possible, authenticates with the optional mechanism a if possible, 301 // and then sends an email from address from, to addresses to, with 302 // message msg. 303 // The addr must include a port, as in "mail.example.com:smtp". 304 // 305 // The addresses in the to parameter are the SMTP RCPT addresses. 306 // 307 // The msg parameter should be an RFC 822-style email with headers 308 // first, a blank line, and then the message body. The lines of msg 309 // should be CRLF terminated. The msg headers should usually include 310 // fields such as "From", "To", "Subject", and "Cc". Sending "Bcc" 311 // messages is accomplished by including an email address in the to 312 // parameter but not including it in the msg headers. 313 // 314 // The SendMail function and the net/smtp package are low-level 315 // mechanisms and provide no support for DKIM signing, MIME 316 // attachments (see the mime/multipart package), or other mail 317 // functionality. Higher-level packages exist outside of the standard 318 // library. 319 func SendMail(addr string, a Auth, from string, to []string, msg []byte) error { 320 if err := validateLine(from); err != nil { 321 return err 322 } 323 for _, recp := range to { 324 if err := validateLine(recp); err != nil { 325 return err 326 } 327 } 328 c, err := Dial(addr) 329 if err != nil { 330 return err 331 } 332 defer c.Close() 333 if err = c.hello(); err != nil { 334 return err 335 } 336 if ok, _ := c.Extension("STARTTLS"); ok { 337 config := &tls.Config{ServerName: c.serverName} 338 if testHookStartTLS != nil { 339 testHookStartTLS(config) 340 } 341 if err = c.StartTLS(config); err != nil { 342 return err 343 } 344 } 345 if a != nil && c.ext != nil { 346 if _, ok := c.ext["AUTH"]; ok { 347 if err = c.Auth(a); err != nil { 348 return err 349 } 350 } 351 } 352 if err = c.Mail(from); err != nil { 353 return err 354 } 355 for _, addr := range to { 356 if err = c.Rcpt(addr); err != nil { 357 return err 358 } 359 } 360 w, err := c.Data() 361 if err != nil { 362 return err 363 } 364 _, err = w.Write(msg) 365 if err != nil { 366 return err 367 } 368 err = w.Close() 369 if err != nil { 370 return err 371 } 372 return c.Quit() 373 } 374 375 // Extension reports whether an extension is support by the server. 376 // The extension name is case-insensitive. If the extension is supported, 377 // Extension also returns a string that contains any parameters the 378 // server specifies for the extension. 379 func (c *Client) Extension(ext string) (bool, string) { 380 if err := c.hello(); err != nil { 381 return false, "" 382 } 383 if c.ext == nil { 384 return false, "" 385 } 386 ext = strings.ToUpper(ext) 387 param, ok := c.ext[ext] 388 return ok, param 389 } 390 391 // Reset sends the RSET command to the server, aborting the current mail 392 // transaction. 393 func (c *Client) Reset() error { 394 if err := c.hello(); err != nil { 395 return err 396 } 397 _, _, err := c.cmd(250, "RSET") 398 return err 399 } 400 401 // Noop sends the NOOP command to the server. It does nothing but check 402 // that the connection to the server is okay. 403 func (c *Client) Noop() error { 404 if err := c.hello(); err != nil { 405 return err 406 } 407 _, _, err := c.cmd(250, "NOOP") 408 return err 409 } 410 411 // Quit sends the QUIT command and closes the connection to the server. 412 func (c *Client) Quit() error { 413 if err := c.hello(); err != nil { 414 return err 415 } 416 _, _, err := c.cmd(221, "QUIT") 417 if err != nil { 418 return err 419 } 420 return c.Text.Close() 421 } 422 423 // validateLine checks to see if a line has CR or LF as per RFC 5321 424 func validateLine(line string) error { 425 if strings.ContainsAny(line, "\n\r") { 426 return errors.New("smtp: A line must not contain CR or LF") 427 } 428 return nil 429 }