OSDN Git Service

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