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 }