github.com/roboticscm/goman@v0.0.0-20210203095141-87c07b4a0a55/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 package smtp 12 13 import ( 14 "crypto/tls" 15 "encoding/base64" 16 "errors" 17 "io" 18 "net" 19 "net/textproto" 20 "strings" 21 ) 22 23 // A Client represents a client connection to an SMTP server. 24 type Client struct { 25 // Text is the textproto.Conn used by the Client. It is exported to allow for 26 // clients to add extensions. 27 Text *textproto.Conn 28 // keep a reference to the connection so it can be used to create a TLS 29 // connection later 30 conn net.Conn 31 // whether the Client is using TLS 32 tls bool 33 serverName string 34 // map of supported extensions 35 ext map[string]string 36 // supported auth mechanisms 37 auth []string 38 localName string // the name to use in HELO/EHLO 39 didHello bool // whether we've said HELO/EHLO 40 helloError error // the error from the hello 41 } 42 43 // Dial returns a new Client connected to an SMTP server at addr. 44 // The addr must include a port number. 45 func Dial(addr string) (*Client, error) { 46 conn, err := net.Dial("tcp", addr) 47 if err != nil { 48 return nil, err 49 } 50 host, _, _ := net.SplitHostPort(addr) 51 return NewClient(conn, host) 52 } 53 54 // NewClient returns a new Client using an existing connection and host as a 55 // server name to be used when authenticating. 56 func NewClient(conn net.Conn, host string) (*Client, error) { 57 text := textproto.NewConn(conn) 58 _, _, err := text.ReadResponse(220) 59 if err != nil { 60 text.Close() 61 return nil, err 62 } 63 c := &Client{Text: text, conn: conn, serverName: host, localName: "localhost"} 64 return c, nil 65 } 66 67 // Close closes the connection. 68 func (c *Client) Close() error { 69 return c.Text.Close() 70 } 71 72 // hello runs a hello exchange if needed. 73 func (c *Client) hello() error { 74 if !c.didHello { 75 c.didHello = true 76 err := c.ehlo() 77 if err != nil { 78 c.helloError = c.helo() 79 } 80 } 81 return c.helloError 82 } 83 84 // Hello sends a HELO or EHLO to the server as the given host name. 85 // Calling this method is only necessary if the client needs control 86 // over the host name used. The client will introduce itself as "localhost" 87 // automatically otherwise. If Hello is called, it must be called before 88 // any of the other methods. 89 func (c *Client) Hello(localName string) error { 90 if c.didHello { 91 return errors.New("smtp: Hello called after other methods") 92 } 93 c.localName = localName 94 return c.hello() 95 } 96 97 // cmd is a convenience function that sends a command and returns the response 98 func (c *Client) cmd(expectCode int, format string, args ...interface{}) (int, string, error) { 99 id, err := c.Text.Cmd(format, args...) 100 if err != nil { 101 return 0, "", err 102 } 103 c.Text.StartResponse(id) 104 defer c.Text.EndResponse(id) 105 code, msg, err := c.Text.ReadResponse(expectCode) 106 return code, msg, err 107 } 108 109 // helo sends the HELO greeting to the server. It should be used only when the 110 // server does not support ehlo. 111 func (c *Client) helo() error { 112 c.ext = nil 113 _, _, err := c.cmd(250, "HELO %s", c.localName) 114 return err 115 } 116 117 // ehlo sends the EHLO (extended hello) greeting to the server. It 118 // should be the preferred greeting for servers that support it. 119 func (c *Client) ehlo() error { 120 _, msg, err := c.cmd(250, "EHLO %s", c.localName) 121 if err != nil { 122 return err 123 } 124 ext := make(map[string]string) 125 extList := strings.Split(msg, "\n") 126 if len(extList) > 1 { 127 extList = extList[1:] 128 for _, line := range extList { 129 args := strings.SplitN(line, " ", 2) 130 if len(args) > 1 { 131 ext[args[0]] = args[1] 132 } else { 133 ext[args[0]] = "" 134 } 135 } 136 } 137 if mechs, ok := ext["AUTH"]; ok { 138 c.auth = strings.Split(mechs, " ") 139 } 140 c.ext = ext 141 return err 142 } 143 144 // StartTLS sends the STARTTLS command and encrypts all further communication. 145 // Only servers that advertise the STARTTLS extension support this function. 146 func (c *Client) StartTLS(config *tls.Config) error { 147 if err := c.hello(); err != nil { 148 return err 149 } 150 _, _, err := c.cmd(220, "STARTTLS") 151 if err != nil { 152 return err 153 } 154 c.conn = tls.Client(c.conn, config) 155 c.Text = textproto.NewConn(c.conn) 156 c.tls = true 157 return c.ehlo() 158 } 159 160 // Verify checks the validity of an email address on the server. 161 // If Verify returns nil, the address is valid. A non-nil return 162 // does not necessarily indicate an invalid address. Many servers 163 // will not verify addresses for security reasons. 164 func (c *Client) Verify(addr string) error { 165 if err := c.hello(); err != nil { 166 return err 167 } 168 _, _, err := c.cmd(250, "VRFY %s", addr) 169 return err 170 } 171 172 // Auth authenticates a client using the provided authentication mechanism. 173 // A failed authentication closes the connection. 174 // Only servers that advertise the AUTH extension support this function. 175 func (c *Client) Auth(a Auth) error { 176 if err := c.hello(); err != nil { 177 return err 178 } 179 encoding := base64.StdEncoding 180 mech, resp, err := a.Start(&ServerInfo{c.serverName, c.tls, c.auth}) 181 if err != nil { 182 c.Quit() 183 return err 184 } 185 resp64 := make([]byte, encoding.EncodedLen(len(resp))) 186 encoding.Encode(resp64, resp) 187 code, msg64, err := c.cmd(0, "AUTH %s %s", mech, resp64) 188 for err == nil { 189 var msg []byte 190 switch code { 191 case 334: 192 msg, err = encoding.DecodeString(msg64) 193 case 235: 194 // the last message isn't base64 because it isn't a challenge 195 msg = []byte(msg64) 196 default: 197 err = &textproto.Error{Code: code, Msg: msg64} 198 } 199 if err == nil { 200 resp, err = a.Next(msg, code == 334) 201 } 202 if err != nil { 203 // abort the AUTH 204 c.cmd(501, "*") 205 c.Quit() 206 break 207 } 208 if resp == nil { 209 break 210 } 211 resp64 = make([]byte, encoding.EncodedLen(len(resp))) 212 encoding.Encode(resp64, resp) 213 code, msg64, err = c.cmd(0, string(resp64)) 214 } 215 return err 216 } 217 218 // Mail issues a MAIL command to the server using the provided email address. 219 // If the server supports the 8BITMIME extension, Mail adds the BODY=8BITMIME 220 // parameter. 221 // This initiates a mail transaction and is followed by one or more Rcpt calls. 222 func (c *Client) Mail(from string) error { 223 if err := c.hello(); err != nil { 224 return err 225 } 226 cmdStr := "MAIL FROM:<%s>" 227 if c.ext != nil { 228 if _, ok := c.ext["8BITMIME"]; ok { 229 cmdStr += " BODY=8BITMIME" 230 } 231 } 232 _, _, err := c.cmd(250, cmdStr, from) 233 return err 234 } 235 236 // Rcpt issues a RCPT command to the server using the provided email address. 237 // A call to Rcpt must be preceded by a call to Mail and may be followed by 238 // a Data call or another Rcpt call. 239 func (c *Client) Rcpt(to string) error { 240 _, _, err := c.cmd(25, "RCPT TO:<%s>", to) 241 return err 242 } 243 244 type dataCloser struct { 245 c *Client 246 io.WriteCloser 247 } 248 249 func (d *dataCloser) Close() error { 250 d.WriteCloser.Close() 251 _, _, err := d.c.Text.ReadResponse(250) 252 return err 253 } 254 255 // Data issues a DATA command to the server and returns a writer that 256 // can be used to write the data. The caller should close the writer 257 // before calling any more methods on c. 258 // A call to Data must be preceded by one or more calls to Rcpt. 259 func (c *Client) Data() (io.WriteCloser, error) { 260 _, _, err := c.cmd(354, "DATA") 261 if err != nil { 262 return nil, err 263 } 264 return &dataCloser{c, c.Text.DotWriter()}, nil 265 } 266 267 var testHookStartTLS func(*tls.Config) // nil, except for tests 268 269 // SendMail connects to the server at addr, switches to TLS if 270 // possible, authenticates with the optional mechanism a if possible, 271 // and then sends an email from address from, to addresses to, with 272 // message msg. 273 func SendMail(addr string, a Auth, from string, to []string, msg []byte) error { 274 c, err := Dial(addr) 275 if err != nil { 276 return err 277 } 278 defer c.Close() 279 if err = c.hello(); err != nil { 280 return err 281 } 282 if ok, _ := c.Extension("STARTTLS"); ok { 283 config := &tls.Config{ServerName: c.serverName} 284 if testHookStartTLS != nil { 285 testHookStartTLS(config) 286 } 287 if err = c.StartTLS(config); err != nil { 288 return err 289 } 290 } 291 if a != nil && c.ext != nil { 292 if _, ok := c.ext["AUTH"]; ok { 293 if err = c.Auth(a); err != nil { 294 return err 295 } 296 } 297 } 298 if err = c.Mail(from); err != nil { 299 return err 300 } 301 for _, addr := range to { 302 if err = c.Rcpt(addr); err != nil { 303 return err 304 } 305 } 306 w, err := c.Data() 307 if err != nil { 308 return err 309 } 310 _, err = w.Write(msg) 311 if err != nil { 312 return err 313 } 314 err = w.Close() 315 if err != nil { 316 return err 317 } 318 return c.Quit() 319 } 320 321 // Extension reports whether an extension is support by the server. 322 // The extension name is case-insensitive. If the extension is supported, 323 // Extension also returns a string that contains any parameters the 324 // server specifies for the extension. 325 func (c *Client) Extension(ext string) (bool, string) { 326 if err := c.hello(); err != nil { 327 return false, "" 328 } 329 if c.ext == nil { 330 return false, "" 331 } 332 ext = strings.ToUpper(ext) 333 param, ok := c.ext[ext] 334 return ok, param 335 } 336 337 // Reset sends the RSET command to the server, aborting the current mail 338 // transaction. 339 func (c *Client) Reset() error { 340 if err := c.hello(); err != nil { 341 return err 342 } 343 _, _, err := c.cmd(250, "RSET") 344 return err 345 } 346 347 // Quit sends the QUIT command and closes the connection to the server. 348 func (c *Client) Quit() error { 349 if err := c.hello(); err != nil { 350 return err 351 } 352 _, _, err := c.cmd(221, "QUIT") 353 if err != nil { 354 return err 355 } 356 return c.Text.Close() 357 }