~john-koepi/ubuntu/trusty/golang/default

« back to all changes in this revision

Viewing changes to src/pkg/smtp/smtp.go

  • Committer: Bazaar Package Importer
  • Author(s): Ondřej Surý
  • Date: 2011-04-20 17:36:48 UTC
  • Revision ID: james.westby@ubuntu.com-20110420173648-ifergoxyrm832trd
Tags: upstream-2011.03.07.1
Import upstream version 2011.03.07.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
}