Source file
src/net/smtp/smtp.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package smtp
17
18 import (
19 "crypto/tls"
20 "encoding/base64"
21 "errors"
22 "fmt"
23 "io"
24 "net"
25 "net/textproto"
26 "strings"
27 )
28
29
30 type Client struct {
31
32
33 Text *textproto.Conn
34
35
36 conn net.Conn
37
38 tls bool
39 serverName string
40
41 ext map[string]string
42
43 auth []string
44 localName string
45 didHello bool
46 helloError error
47 }
48
49
50
51 func Dial(addr string) (*Client, error) {
52 conn, err := net.Dial("tcp", addr)
53 if err != nil {
54 return nil, err
55 }
56 host, _, _ := net.SplitHostPort(addr)
57 return NewClient(conn, host)
58 }
59
60
61
62 func NewClient(conn net.Conn, host string) (*Client, error) {
63 text := textproto.NewConn(conn)
64 _, _, err := text.ReadResponse(220)
65 if err != nil {
66 text.Close()
67 return nil, err
68 }
69 c := &Client{Text: text, conn: conn, serverName: host, localName: "localhost"}
70 _, c.tls = conn.(*tls.Conn)
71 return c, nil
72 }
73
74
75 func (c *Client) Close() error {
76 return c.Text.Close()
77 }
78
79
80 func (c *Client) hello() error {
81 if !c.didHello {
82 c.didHello = true
83 err := c.ehlo()
84 if err != nil {
85 c.helloError = c.helo()
86 }
87 }
88 return c.helloError
89 }
90
91
92
93
94
95
96 func (c *Client) Hello(localName string) error {
97 if err := validateLine(localName); err != nil {
98 return err
99 }
100 if c.didHello {
101 return errors.New("smtp: Hello called after other methods")
102 }
103 c.localName = localName
104 return c.hello()
105 }
106
107
108 func (c *Client) cmd(expectCode int, format string, args ...interface{}) (int, string, error) {
109 id, err := c.Text.Cmd(format, args...)
110 if err != nil {
111 return 0, "", err
112 }
113 c.Text.StartResponse(id)
114 defer c.Text.EndResponse(id)
115 code, msg, err := c.Text.ReadResponse(expectCode)
116 return code, msg, err
117 }
118
119
120
121 func (c *Client) helo() error {
122 c.ext = nil
123 _, _, err := c.cmd(250, "HELO %s", c.localName)
124 return err
125 }
126
127
128
129 func (c *Client) ehlo() error {
130 _, msg, err := c.cmd(250, "EHLO %s", c.localName)
131 if err != nil {
132 return err
133 }
134 ext := make(map[string]string)
135 extList := strings.Split(msg, "\n")
136 if len(extList) > 1 {
137 extList = extList[1:]
138 for _, line := range extList {
139 args := strings.SplitN(line, " ", 2)
140 if len(args) > 1 {
141 ext[args[0]] = args[1]
142 } else {
143 ext[args[0]] = ""
144 }
145 }
146 }
147 if mechs, ok := ext["AUTH"]; ok {
148 c.auth = strings.Split(mechs, " ")
149 }
150 c.ext = ext
151 return err
152 }
153
154
155
156 func (c *Client) StartTLS(config *tls.Config) error {
157 if err := c.hello(); err != nil {
158 return err
159 }
160 _, _, err := c.cmd(220, "STARTTLS")
161 if err != nil {
162 return err
163 }
164 c.conn = tls.Client(c.conn, config)
165 c.Text = textproto.NewConn(c.conn)
166 c.tls = true
167 return c.ehlo()
168 }
169
170
171
172
173 func (c *Client) TLSConnectionState() (state tls.ConnectionState, ok bool) {
174 tc, ok := c.conn.(*tls.Conn)
175 if !ok {
176 return
177 }
178 return tc.ConnectionState(), true
179 }
180
181
182
183
184
185 func (c *Client) Verify(addr string) error {
186 if err := validateLine(addr); err != nil {
187 return err
188 }
189 if err := c.hello(); err != nil {
190 return err
191 }
192 _, _, err := c.cmd(250, "VRFY %s", addr)
193 return err
194 }
195
196
197
198
199 func (c *Client) Auth(a Auth) error {
200 if err := c.hello(); err != nil {
201 return err
202 }
203 encoding := base64.StdEncoding
204 mech, resp, err := a.Start(&ServerInfo{c.serverName, c.tls, c.auth})
205 if err != nil {
206 c.Quit()
207 return err
208 }
209 resp64 := make([]byte, encoding.EncodedLen(len(resp)))
210 encoding.Encode(resp64, resp)
211 code, msg64, err := c.cmd(0, strings.TrimSpace(fmt.Sprintf("AUTH %s %s", mech, resp64)))
212 for err == nil {
213 var msg []byte
214 switch code {
215 case 334:
216 msg, err = encoding.DecodeString(msg64)
217 case 235:
218
219 msg = []byte(msg64)
220 default:
221 err = &textproto.Error{Code: code, Msg: msg64}
222 }
223 if err == nil {
224 resp, err = a.Next(msg, code == 334)
225 }
226 if err != nil {
227
228 c.cmd(501, "*")
229 c.Quit()
230 break
231 }
232 if resp == nil {
233 break
234 }
235 resp64 = make([]byte, encoding.EncodedLen(len(resp)))
236 encoding.Encode(resp64, resp)
237 code, msg64, err = c.cmd(0, string(resp64))
238 }
239 return err
240 }
241
242
243
244
245
246
247 func (c *Client) Mail(from string) error {
248 if err := validateLine(from); err != nil {
249 return err
250 }
251 if err := c.hello(); err != nil {
252 return err
253 }
254 cmdStr := "MAIL FROM:<%s>"
255 if c.ext != nil {
256 if _, ok := c.ext["8BITMIME"]; ok {
257 cmdStr += " BODY=8BITMIME"
258 }
259 if _, ok := c.ext["SMTPUTF8"]; ok {
260 cmdStr += " SMTPUTF8"
261 }
262 }
263 _, _, err := c.cmd(250, cmdStr, from)
264 return err
265 }
266
267
268
269
270 func (c *Client) Rcpt(to string) error {
271 if err := validateLine(to); err != nil {
272 return err
273 }
274 _, _, err := c.cmd(25, "RCPT TO:<%s>", to)
275 return err
276 }
277
278 type dataCloser struct {
279 c *Client
280 io.WriteCloser
281 }
282
283 func (d *dataCloser) Close() error {
284 d.WriteCloser.Close()
285 _, _, err := d.c.Text.ReadResponse(250)
286 return err
287 }
288
289
290
291
292
293 func (c *Client) Data() (io.WriteCloser, error) {
294 _, _, err := c.cmd(354, "DATA")
295 if err != nil {
296 return nil, err
297 }
298 return &dataCloser{c, c.Text.DotWriter()}, nil
299 }
300
301 var testHookStartTLS func(*tls.Config)
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323 func SendMail(addr string, a Auth, from string, to []string, msg []byte) error {
324 if err := validateLine(from); err != nil {
325 return err
326 }
327 for _, recp := range to {
328 if err := validateLine(recp); err != nil {
329 return err
330 }
331 }
332 c, err := Dial(addr)
333 if err != nil {
334 return err
335 }
336 defer c.Close()
337 if err = c.hello(); err != nil {
338 return err
339 }
340 if ok, _ := c.Extension("STARTTLS"); ok {
341 config := &tls.Config{ServerName: c.serverName}
342 if testHookStartTLS != nil {
343 testHookStartTLS(config)
344 }
345 if err = c.StartTLS(config); err != nil {
346 return err
347 }
348 }
349 if a != nil && c.ext != nil {
350 if _, ok := c.ext["AUTH"]; !ok {
351 return errors.New("smtp: server doesn't support AUTH")
352 }
353 if err = c.Auth(a); err != nil {
354 return err
355 }
356 }
357 if err = c.Mail(from); err != nil {
358 return err
359 }
360 for _, addr := range to {
361 if err = c.Rcpt(addr); err != nil {
362 return err
363 }
364 }
365 w, err := c.Data()
366 if err != nil {
367 return err
368 }
369 _, err = w.Write(msg)
370 if err != nil {
371 return err
372 }
373 err = w.Close()
374 if err != nil {
375 return err
376 }
377 return c.Quit()
378 }
379
380
381
382
383
384 func (c *Client) Extension(ext string) (bool, string) {
385 if err := c.hello(); err != nil {
386 return false, ""
387 }
388 if c.ext == nil {
389 return false, ""
390 }
391 ext = strings.ToUpper(ext)
392 param, ok := c.ext[ext]
393 return ok, param
394 }
395
396
397
398 func (c *Client) Reset() error {
399 if err := c.hello(); err != nil {
400 return err
401 }
402 _, _, err := c.cmd(250, "RSET")
403 return err
404 }
405
406
407
408 func (c *Client) Noop() error {
409 if err := c.hello(); err != nil {
410 return err
411 }
412 _, _, err := c.cmd(250, "NOOP")
413 return err
414 }
415
416
417 func (c *Client) Quit() error {
418 if err := c.hello(); err != nil {
419 return err
420 }
421 _, _, err := c.cmd(221, "QUIT")
422 if err != nil {
423 return err
424 }
425 return c.Text.Close()
426 }
427
428
429 func validateLine(line string) error {
430 if strings.ContainsAny(line, "\n\r") {
431 return errors.New("smtp: A line must not contain CR or LF")
432 }
433 return nil
434 }
435
View as plain text