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