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