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 ...any) (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 k, v, _ := strings.Cut(line, " ")
140 ext[k] = v
141 }
142 }
143 if mechs, ok := ext["AUTH"]; ok {
144 c.auth = strings.Split(mechs, " ")
145 }
146 c.ext = ext
147 return err
148 }
149
150
151
152 func (c *Client) StartTLS(config *tls.Config) error {
153 if err := c.hello(); err != nil {
154 return err
155 }
156 _, _, err := c.cmd(220, "STARTTLS")
157 if err != nil {
158 return err
159 }
160 c.conn = tls.Client(c.conn, config)
161 c.Text = textproto.NewConn(c.conn)
162 c.tls = true
163 return c.ehlo()
164 }
165
166
167
168
169 func (c *Client) TLSConnectionState() (state tls.ConnectionState, ok bool) {
170 tc, ok := c.conn.(*tls.Conn)
171 if !ok {
172 return
173 }
174 return tc.ConnectionState(), true
175 }
176
177
178
179
180
181 func (c *Client) Verify(addr string) error {
182 if err := validateLine(addr); err != nil {
183 return err
184 }
185 if err := c.hello(); err != nil {
186 return err
187 }
188 _, _, err := c.cmd(250, "VRFY %s", addr)
189 return err
190 }
191
192
193
194
195 func (c *Client) Auth(a Auth) error {
196 if err := c.hello(); err != nil {
197 return err
198 }
199 encoding := base64.StdEncoding
200 mech, resp, err := a.Start(&ServerInfo{c.serverName, c.tls, c.auth})
201 if err != nil {
202 c.Quit()
203 return err
204 }
205 resp64 := make([]byte, encoding.EncodedLen(len(resp)))
206 encoding.Encode(resp64, resp)
207 code, msg64, err := c.cmd(0, strings.TrimSpace(fmt.Sprintf("AUTH %s %s", mech, resp64)))
208 for err == nil {
209 var msg []byte
210 switch code {
211 case 334:
212 msg, err = encoding.DecodeString(msg64)
213 case 235:
214
215 msg = []byte(msg64)
216 default:
217 err = &textproto.Error{Code: code, Msg: msg64}
218 }
219 if err == nil {
220 resp, err = a.Next(msg, code == 334)
221 }
222 if err != nil {
223
224 c.cmd(501, "*")
225 c.Quit()
226 break
227 }
228 if resp == nil {
229 break
230 }
231 resp64 = make([]byte, encoding.EncodedLen(len(resp)))
232 encoding.Encode(resp64, resp)
233 code, msg64, err = c.cmd(0, string(resp64))
234 }
235 return err
236 }
237
238
239
240
241
242
243 func (c *Client) Mail(from string) error {
244 if err := validateLine(from); err != nil {
245 return err
246 }
247 if err := c.hello(); err != nil {
248 return err
249 }
250 cmdStr := "MAIL FROM:<%s>"
251 if c.ext != nil {
252 if _, ok := c.ext["8BITMIME"]; ok {
253 cmdStr += " BODY=8BITMIME"
254 }
255 if _, ok := c.ext["SMTPUTF8"]; ok {
256 cmdStr += " SMTPUTF8"
257 }
258 }
259 _, _, err := c.cmd(250, cmdStr, from)
260 return err
261 }
262
263
264
265
266 func (c *Client) Rcpt(to string) error {
267 if err := validateLine(to); err != nil {
268 return err
269 }
270 _, _, err := c.cmd(25, "RCPT TO:<%s>", to)
271 return err
272 }
273
274 type dataCloser struct {
275 c *Client
276 io.WriteCloser
277 }
278
279 func (d *dataCloser) Close() error {
280 d.WriteCloser.Close()
281 _, _, err := d.c.Text.ReadResponse(250)
282 return err
283 }
284
285
286
287
288
289 func (c *Client) Data() (io.WriteCloser, error) {
290 _, _, err := c.cmd(354, "DATA")
291 if err != nil {
292 return nil, err
293 }
294 return &dataCloser{c, c.Text.DotWriter()}, nil
295 }
296
297 var testHookStartTLS func(*tls.Config)
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319 func SendMail(addr string, a Auth, from string, to []string, msg []byte) error {
320 if err := validateLine(from); err != nil {
321 return err
322 }
323 for _, recp := range to {
324 if err := validateLine(recp); err != nil {
325 return err
326 }
327 }
328 c, err := Dial(addr)
329 if err != nil {
330 return err
331 }
332 defer c.Close()
333 if err = c.hello(); err != nil {
334 return err
335 }
336 if ok, _ := c.Extension("STARTTLS"); ok {
337 config := &tls.Config{ServerName: c.serverName}
338 if testHookStartTLS != nil {
339 testHookStartTLS(config)
340 }
341 if err = c.StartTLS(config); err != nil {
342 return err
343 }
344 }
345 if a != nil && c.ext != nil {
346 if _, ok := c.ext["AUTH"]; !ok {
347 return errors.New("smtp: server doesn't support AUTH")
348 }
349 if err = c.Auth(a); err != nil {
350 return err
351 }
352 }
353 if err = c.Mail(from); err != nil {
354 return err
355 }
356 for _, addr := range to {
357 if err = c.Rcpt(addr); err != nil {
358 return err
359 }
360 }
361 w, err := c.Data()
362 if err != nil {
363 return err
364 }
365 _, err = w.Write(msg)
366 if err != nil {
367 return err
368 }
369 err = w.Close()
370 if err != nil {
371 return err
372 }
373 return c.Quit()
374 }
375
376
377
378
379
380 func (c *Client) Extension(ext string) (bool, string) {
381 if err := c.hello(); err != nil {
382 return false, ""
383 }
384 if c.ext == nil {
385 return false, ""
386 }
387 ext = strings.ToUpper(ext)
388 param, ok := c.ext[ext]
389 return ok, param
390 }
391
392
393
394 func (c *Client) Reset() error {
395 if err := c.hello(); err != nil {
396 return err
397 }
398 _, _, err := c.cmd(250, "RSET")
399 return err
400 }
401
402
403
404 func (c *Client) Noop() error {
405 if err := c.hello(); err != nil {
406 return err
407 }
408 _, _, err := c.cmd(250, "NOOP")
409 return err
410 }
411
412
413 func (c *Client) Quit() error {
414 if err := c.hello(); err != nil {
415 return err
416 }
417 _, _, err := c.cmd(221, "QUIT")
418 if err != nil {
419 return err
420 }
421 return c.Text.Close()
422 }
423
424
425 func validateLine(line string) error {
426 if strings.ContainsAny(line, "\n\r") {
427 return errors.New("smtp: A line must not contain CR or LF")
428 }
429 return nil
430 }
431
View as plain text