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