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