github.com/twelsh-aw/go/src@v0.0.0-20230516233729-a56fe86a7c81/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 // 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 18 package smtp 19 20 import ( 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. 32 type 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". 53 func 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. 64 func 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. 77 func (c *Client) Close() error { 78 return c.Text.Close() 79 } 80 81 // hello runs a hello exchange if needed. 82 func (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. 98 func (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 110 func (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. 123 func (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. 131 func (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. 154 func (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 StartTLS did 170 // not succeed. 171 func (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. 183 func (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. 197 func (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, 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, string(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 Rcpt calls. 245 func (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 Mail and may be followed by 267 // a Data call or another Rcpt call. 268 func (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 276 type dataCloser struct { 277 c *Client 278 io.WriteCloser 279 } 280 281 func (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 Rcpt. 291 func (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 299 var 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. 321 func 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. 382 func (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. 396 func (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. 406 func (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. 415 func (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. 427 func 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 }