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
6 7 import (
8 "crypto/tls"
9 "encoding/base64"
10 "errors"
11 "fmt"
12 "io"
13 "net"
14 "net/textproto"
15 "sort"
16 "strconv"
17 "strings"
18 "time"
19 20 "github.com/emersion/go-sasl"
21 )
22 23 // A Client represents a client connection to an SMTP server.
24 type Client struct {
25 // keep a reference to the connection so it can be used to create a TLS
26 // connection later
27 conn net.Conn
28 text *textproto.Conn
29 serverName string
30 lmtp bool
31 ext map[string]string // supported extensions
32 localName string // the name to use in HELO/EHLO/LHLO
33 didGreet bool // whether we've received greeting from server
34 greetError error // the error from the greeting
35 didHello bool // whether we've said HELO/EHLO/LHLO
36 helloError error // the error from the hello
37 rcpts []string // recipients accumulated for the current session
38 39 // Time to wait for command responses (this includes 3xx reply to DATA).
40 CommandTimeout time.Duration
41 // Time to wait for responses after final dot.
42 SubmissionTimeout time.Duration
43 44 // Logger for all network activity.
45 DebugWriter io.Writer
46 }
47 48 // 30 seconds was chosen as it's the same duration as http.DefaultTransport's
49 // timeout.
50 var defaultDialer = net.Dialer{Timeout: 30 * time.Second}
51 52 // Dial returns a new Client connected to an SMTP server at addr. The addr must
53 // include a port, as in "mail.example.com:smtp".
54 //
55 // This function returns a plaintext connection. To enable TLS, use
56 // DialStartTLS.
57 func Dial(addr string) (*Client, error) {
58 conn, err := defaultDialer.Dial("tcp", addr)
59 if err != nil {
60 return nil, err
61 }
62 client := NewClient(conn)
63 client.serverName, _, _ = net.SplitHostPort(addr)
64 return client, nil
65 }
66 67 // DialTLS returns a new Client connected to an SMTP server via TLS at addr.
68 // The addr must include a port, as in "mail.example.com:smtps".
69 //
70 // A nil tlsConfig is equivalent to a zero tls.Config.
71 func DialTLS(addr string, tlsConfig *tls.Config) (*Client, error) {
72 tlsDialer := tls.Dialer{
73 NetDialer: &defaultDialer,
74 Config: tlsConfig,
75 }
76 conn, err := tlsDialer.Dial("tcp", addr)
77 if err != nil {
78 return nil, err
79 }
80 client := NewClient(conn)
81 client.serverName, _, _ = net.SplitHostPort(addr)
82 return client, nil
83 }
84 85 // DialStartTLS returns a new Client connected to an SMTP server via STARTTLS
86 // at addr. The addr must include a port, as in "mail.example.com:smtp".
87 //
88 // A nil tlsConfig is equivalent to a zero tls.Config.
89 func DialStartTLS(addr string, tlsConfig *tls.Config) (*Client, error) {
90 c, err := Dial(addr)
91 if err != nil {
92 return nil, err
93 }
94 if err := initStartTLS(c, tlsConfig); err != nil {
95 c.Close()
96 return nil, err
97 }
98 return c, nil
99 }
100 101 // NewClient returns a new Client using an existing connection and host as a
102 // server name to be used when authenticating.
103 func NewClient(conn net.Conn) *Client {
104 c := &Client{
105 localName: "localhost",
106 // As recommended by RFC 5321. For DATA command reply (3xx one) RFC
107 // recommends a slightly shorter timeout but we do not bother
108 // differentiating these.
109 CommandTimeout: 5 * time.Minute,
110 // 10 minutes + 2 minute buffer in case the server is doing transparent
111 // forwarding and also follows recommended timeouts.
112 SubmissionTimeout: 12 * time.Minute,
113 }
114 115 c.setConn(conn)
116 117 return c
118 }
119 120 // NewClientStartTLS creates a new Client and performs a STARTTLS command.
121 func NewClientStartTLS(conn net.Conn, tlsConfig *tls.Config) (*Client, error) {
122 c := NewClient(conn)
123 if err := initStartTLS(c, tlsConfig); err != nil {
124 c.Close()
125 return nil, err
126 }
127 return c, nil
128 }
129 130 func initStartTLS(c *Client, tlsConfig *tls.Config) error {
131 if err := c.hello(); err != nil {
132 return err
133 }
134 if ok, _ := c.Extension("STARTTLS"); !ok {
135 return errors.New("smtp: server doesn't support STARTTLS")
136 }
137 if err := c.startTLS(tlsConfig); err != nil {
138 return err
139 }
140 return nil
141 }
142 143 // NewClientLMTP returns a new LMTP Client (as defined in RFC 2033) using an
144 // existing connection and host as a server name to be used when authenticating.
145 func NewClientLMTP(conn net.Conn) *Client {
146 c := NewClient(conn)
147 c.lmtp = true
148 return c
149 }
150 151 // setConn sets the underlying network connection for the client.
152 func (c *Client) setConn(conn net.Conn) {
153 c.conn = conn
154 155 var r io.Reader = conn
156 var w io.Writer = conn
157 158 r = &lineLimitReader{
159 R: conn,
160 // Doubled maximum line length per RFC 5321 (Section 4.5.3.1.6)
161 LineLimit: 2000,
162 }
163 164 r = io.TeeReader(r, clientDebugWriter{c})
165 w = io.MultiWriter(w, clientDebugWriter{c})
166 167 rwc := struct {
168 io.Reader
169 io.Writer
170 io.Closer
171 }{
172 Reader: r,
173 Writer: w,
174 Closer: conn,
175 }
176 c.text = textproto.NewConn(rwc)
177 }
178 179 // Close closes the connection.
180 func (c *Client) Close() error {
181 return c.text.Close()
182 }
183 184 func (c *Client) greet() error {
185 if c.didGreet {
186 return c.greetError
187 }
188 189 // Initial greeting timeout. RFC 5321 recommends 5 minutes.
190 c.conn.SetDeadline(time.Now().Add(c.CommandTimeout))
191 defer c.conn.SetDeadline(time.Time{})
192 193 c.didGreet = true
194 _, _, err := c.readResponse(220)
195 if err != nil {
196 c.greetError = err
197 c.text.Close()
198 }
199 200 return c.greetError
201 }
202 203 // hello runs a hello exchange if needed.
204 func (c *Client) hello() error {
205 if c.didHello {
206 return c.helloError
207 }
208 209 if err := c.greet(); err != nil {
210 return err
211 }
212 213 c.didHello = true
214 if err := c.ehlo(); err != nil {
215 var smtpError *SMTPError
216 if errors.As(err, &smtpError) && (smtpError.Code == 500 || smtpError.Code == 502) {
217 // The server doesn't support EHLO, fallback to HELO
218 c.helloError = c.helo()
219 } else {
220 c.helloError = err
221 }
222 }
223 return c.helloError
224 }
225 226 // Hello sends a HELO or EHLO to the server as the given host name.
227 // Calling this method is only necessary if the client needs control
228 // over the host name used. The client will introduce itself as "localhost"
229 // automatically otherwise. If Hello is called, it must be called before
230 // any of the other methods.
231 //
232 // If server returns an error, it will be of type *SMTPError.
233 func (c *Client) Hello(localName string) error {
234 if err := validateLine(localName); err != nil {
235 return err
236 }
237 if c.didHello {
238 return errors.New("smtp: Hello called after other methods")
239 }
240 c.localName = localName
241 return c.hello()
242 }
243 244 func (c *Client) readResponse(expectCode int) (int, string, error) {
245 code, msg, err := c.text.ReadResponse(expectCode)
246 if protoErr, ok := err.(*textproto.Error); ok {
247 err = toSMTPErr(protoErr)
248 }
249 return code, msg, err
250 }
251 252 // cmd is a convenience function that sends a command and returns the response
253 // textproto.Error returned by c.text.ReadResponse is converted into SMTPError.
254 func (c *Client) cmd(expectCode int, format string, args ...interface{}) (int, string, error) {
255 c.conn.SetDeadline(time.Now().Add(c.CommandTimeout))
256 defer c.conn.SetDeadline(time.Time{})
257 258 id, err := c.text.Cmd(format, args...)
259 if err != nil {
260 return 0, "", err
261 }
262 c.text.StartResponse(id)
263 defer c.text.EndResponse(id)
264 265 return c.readResponse(expectCode)
266 }
267 268 // helo sends the HELO greeting to the server. It should be used only when the
269 // server does not support ehlo.
270 func (c *Client) helo() error {
271 c.ext = nil
272 _, _, err := c.cmd(250, "HELO %s", c.localName)
273 return err
274 }
275 276 // ehlo sends the EHLO (extended hello) greeting to the server. It
277 // should be the preferred greeting for servers that support it.
278 func (c *Client) ehlo() error {
279 cmd := "EHLO"
280 if c.lmtp {
281 cmd = "LHLO"
282 }
283 284 _, msg, err := c.cmd(250, "%s %s", cmd, c.localName)
285 if err != nil {
286 return err
287 }
288 ext := make(map[string]string)
289 extList := strings.Split(msg, "\n")
290 if len(extList) > 1 {
291 extList = extList[1:]
292 for _, line := range extList {
293 args := strings.SplitN(line, " ", 2)
294 if len(args) > 1 {
295 ext[args[0]] = args[1]
296 } else {
297 ext[args[0]] = ""
298 }
299 }
300 }
301 c.ext = ext
302 return err
303 }
304 305 // startTLS sends the STARTTLS command and encrypts all further communication.
306 // Only servers that advertise the STARTTLS extension support this function.
307 //
308 // A nil config is equivalent to a zero tls.Config.
309 //
310 // If server returns an error, it will be of type *SMTPError.
311 func (c *Client) startTLS(config *tls.Config) error {
312 if err := c.hello(); err != nil {
313 return err
314 }
315 _, _, err := c.cmd(220, "STARTTLS")
316 if err != nil {
317 return err
318 }
319 if config == nil {
320 config = &tls.Config{}
321 }
322 if config.ServerName == "" && c.serverName != "" {
323 // Make a copy to avoid polluting argument
324 config = config.Clone()
325 config.ServerName = c.serverName
326 }
327 if testHookStartTLS != nil {
328 testHookStartTLS(config)
329 }
330 c.setConn(tls.Client(c.conn, config))
331 c.didHello = false
332 return nil
333 }
334 335 // TLSConnectionState returns the client's TLS connection state.
336 // The return values are their zero values if STARTTLS did
337 // not succeed.
338 func (c *Client) TLSConnectionState() (state tls.ConnectionState, ok bool) {
339 tc, ok := c.conn.(*tls.Conn)
340 if !ok {
341 return
342 }
343 return tc.ConnectionState(), true
344 }
345 346 // Verify checks the validity of an email address on the server.
347 // If Verify returns nil, the address is valid. A non-nil return
348 // does not necessarily indicate an invalid address. Many servers
349 // will not verify addresses for security reasons.
350 //
351 // If server returns an error, it will be of type *SMTPError.
352 func (c *Client) Verify(addr string) error {
353 if err := validateLine(addr); err != nil {
354 return err
355 }
356 if err := c.hello(); err != nil {
357 return err
358 }
359 _, _, err := c.cmd(250, "VRFY %s", addr)
360 return err
361 }
362 363 // Auth authenticates a client using the provided authentication mechanism.
364 // Only servers that advertise the AUTH extension support this function.
365 //
366 // If server returns an error, it will be of type *SMTPError.
367 func (c *Client) Auth(a sasl.Client) error {
368 if err := c.hello(); err != nil {
369 return err
370 }
371 encoding := base64.StdEncoding
372 mech, resp, err := a.Start()
373 if err != nil {
374 return err
375 }
376 var resp64 []byte
377 if len(resp) > 0 {
378 resp64 = make([]byte, encoding.EncodedLen(len(resp)))
379 encoding.Encode(resp64, resp)
380 } else if resp != nil {
381 resp64 = []byte{'='}
382 }
383 code, msg64, err := c.cmd(0, strings.TrimSpace(fmt.Sprintf("AUTH %s %s", mech, resp64)))
384 for err == nil {
385 var msg []byte
386 switch code {
387 case 334:
388 msg, err = encoding.DecodeString(msg64)
389 case 235:
390 // the last message isn't base64 because it isn't a challenge
391 msg = []byte(msg64)
392 default:
393 err = toSMTPErr(&textproto.Error{Code: code, Msg: msg64})
394 }
395 if err == nil {
396 if code == 334 {
397 resp, err = a.Next(msg)
398 } else {
399 resp = nil
400 }
401 }
402 if err != nil {
403 // abort the AUTH
404 c.cmd(501, "*")
405 break
406 }
407 if resp == nil {
408 break
409 }
410 resp64 = make([]byte, encoding.EncodedLen(len(resp)))
411 encoding.Encode(resp64, resp)
412 code, msg64, err = c.cmd(0, string(resp64))
413 }
414 return err
415 }
416 417 // Mail issues a MAIL command to the server using the provided email address.
418 // If the server supports the 8BITMIME extension, Mail adds the BODY=8BITMIME
419 // parameter.
420 // This initiates a mail transaction and is followed by one or more Rcpt calls.
421 //
422 // If opts is not nil, MAIL arguments provided in the structure will be added
423 // to the command. Handling of unsupported options depends on the extension.
424 //
425 // If server returns an error, it will be of type *SMTPError.
426 func (c *Client) Mail(from string, opts *MailOptions) error {
427 if err := validateLine(from); err != nil {
428 return err
429 }
430 if err := c.hello(); err != nil {
431 return err
432 }
433 434 var sb strings.Builder
435 // A high enough power of 2 than 510+14+26+11+9+9+39+500
436 sb.Grow(2048)
437 fmt.Fprintf(&sb, "MAIL FROM:<%s>", from)
438 if _, ok := c.ext["8BITMIME"]; ok {
439 sb.WriteString(" BODY=8BITMIME")
440 }
441 if _, ok := c.ext["SIZE"]; ok && opts != nil && opts.Size != 0 {
442 fmt.Fprintf(&sb, " SIZE=%v", opts.Size)
443 }
444 if opts != nil && opts.RequireTLS {
445 if _, ok := c.ext["REQUIRETLS"]; ok {
446 sb.WriteString(" REQUIRETLS")
447 } else {
448 return errors.New("smtp: server does not support REQUIRETLS")
449 }
450 }
451 if opts != nil && opts.UTF8 {
452 if _, ok := c.ext["SMTPUTF8"]; ok {
453 sb.WriteString(" SMTPUTF8")
454 } else {
455 return errors.New("smtp: server does not support SMTPUTF8")
456 }
457 }
458 if _, ok := c.ext["DSN"]; ok && opts != nil {
459 switch opts.Return {
460 case DSNReturnFull, DSNReturnHeaders:
461 fmt.Fprintf(&sb, " RET=%s", string(opts.Return))
462 case "":
463 // This space is intentionally left blank
464 default:
465 return errors.New("smtp: Unknown RET parameter value")
466 }
467 if opts.EnvelopeID != "" {
468 if !isPrintableASCII(opts.EnvelopeID) {
469 return errors.New("smtp: Malformed ENVID parameter value")
470 }
471 fmt.Fprintf(&sb, " ENVID=%s", encodeXtext(opts.EnvelopeID))
472 }
473 }
474 if opts != nil && opts.Auth != nil {
475 if _, ok := c.ext["AUTH"]; ok {
476 fmt.Fprintf(&sb, " AUTH=%s", encodeXtext(*opts.Auth))
477 }
478 // We can safely discard parameter if server does not support AUTH.
479 }
480 _, _, err := c.cmd(250, "%s", sb.String())
481 return err
482 }
483 484 // Rcpt issues a RCPT command to the server using the provided email address.
485 // A call to Rcpt must be preceded by a call to Mail and may be followed by
486 // a Data call or another Rcpt call.
487 //
488 // If opts is not nil, RCPT arguments provided in the structure will be added
489 // to the command. Handling of unsupported options depends on the extension.
490 //
491 // If server returns an error, it will be of type *SMTPError.
492 func (c *Client) Rcpt(to string, opts *RcptOptions) error {
493 if err := validateLine(to); err != nil {
494 return err
495 }
496 497 var sb strings.Builder
498 // A high enough power of 2 than 510+29+501
499 sb.Grow(2048)
500 fmt.Fprintf(&sb, "RCPT TO:<%s>", to)
501 if _, ok := c.ext["DSN"]; ok && opts != nil {
502 if len(opts.Notify) != 0 {
503 sb.WriteString(" NOTIFY=")
504 if err := checkNotifySet(opts.Notify); err != nil {
505 return errors.New("smtp: Malformed NOTIFY parameter value")
506 }
507 for i, v := range opts.Notify {
508 if i != 0 {
509 sb.WriteString(",")
510 }
511 sb.WriteString(string(v))
512 }
513 }
514 if opts.OriginalRecipient != "" {
515 var enc string
516 switch opts.OriginalRecipientType {
517 case DSNAddressTypeRFC822:
518 if !isPrintableASCII(opts.OriginalRecipient) {
519 return errors.New("smtp: Illegal address")
520 }
521 enc = encodeXtext(opts.OriginalRecipient)
522 case DSNAddressTypeUTF8:
523 if _, ok := c.ext["SMTPUTF8"]; ok {
524 enc = encodeUTF8AddrUnitext(opts.OriginalRecipient)
525 } else {
526 enc = encodeUTF8AddrXtext(opts.OriginalRecipient)
527 }
528 default:
529 return errors.New("smtp: Unknown address type")
530 }
531 fmt.Fprintf(&sb, " ORCPT=%s;%s", string(opts.OriginalRecipientType), enc)
532 }
533 }
534 if _, ok := c.ext["RRVS"]; ok && opts != nil && !opts.RequireRecipientValidSince.IsZero() {
535 sb.WriteString(fmt.Sprintf(" RRVS=%s", opts.RequireRecipientValidSince.Format(time.RFC3339)))
536 }
537 if _, ok := c.ext["DELIVERBY"]; ok && opts != nil && opts.DeliverBy != nil {
538 if opts.DeliverBy.Mode == DeliverByReturn && opts.DeliverBy.Time < 1 {
539 return errors.New("smtp: DELIVERBY mode must be greater than zero with return mode")
540 }
541 arg := fmt.Sprintf(" BY=%d;%s", int(opts.DeliverBy.Time.Seconds()), opts.DeliverBy.Mode)
542 if opts.DeliverBy.Trace {
543 arg += "T"
544 }
545 sb.WriteString(arg)
546 }
547 if _, ok := c.ext["MT-PRIORITY"]; ok && opts != nil && opts.MTPriority != nil {
548 if *opts.MTPriority < -9 || *opts.MTPriority > 9 {
549 return errors.New("smtp: MT-PRIORITY must be between -9 and 9")
550 }
551 sb.WriteString(fmt.Sprintf(" MT-PRIORITY=%d", *opts.MTPriority))
552 }
553 if _, _, err := c.cmd(25, "%s", sb.String()); err != nil {
554 return err
555 }
556 c.rcpts = append(c.rcpts, to)
557 return nil
558 }
559 560 // DataCommand is a pending DATA command. DataCommand is an io.WriteCloser.
561 // See Client.Data.
562 type DataCommand struct {
563 client *Client
564 wc io.WriteCloser
565 566 closeErr error
567 }
568 569 var _ io.WriteCloser = (*DataCommand)(nil)
570 571 // Write implements io.Writer.
572 func (cmd *DataCommand) Write(b []byte) (int, error) {
573 return cmd.wc.Write(b)
574 }
575 576 // Close implements io.Closer.
577 func (cmd *DataCommand) Close() error {
578 var err error
579 if cmd.client.lmtp {
580 _, err = cmd.CloseWithLMTPResponse()
581 } else {
582 _, err = cmd.CloseWithResponse()
583 }
584 return err
585 }
586 587 // CloseWithResponse is equivalent to Close, but also returns the server
588 // response. It cannot be called when the LMTP protocol is used.
589 //
590 // If server returns an error, it will be of type *SMTPError.
591 func (cmd *DataCommand) CloseWithResponse() (*DataResponse, error) {
592 if cmd.client.lmtp {
593 return nil, errors.New("smtp: CloseWithResponse used with an LMTP client")
594 }
595 596 if err := cmd.close(); err != nil {
597 return nil, err
598 }
599 600 cmd.client.conn.SetDeadline(time.Now().Add(cmd.client.SubmissionTimeout))
601 defer cmd.client.conn.SetDeadline(time.Time{})
602 603 _, msg, err := cmd.client.readResponse(250)
604 if err != nil {
605 cmd.closeErr = err
606 return nil, err
607 }
608 609 return &DataResponse{StatusText: msg}, nil
610 }
611 612 // CloseWithLMTPResponse is equivalent to Close, but also returns per-recipient
613 // server responses. It can only be called when the LMTP protocol is used.
614 //
615 // If server returns an error, it will be of type LMTPDataError.
616 func (cmd *DataCommand) CloseWithLMTPResponse() (map[string]*DataResponse, error) {
617 if !cmd.client.lmtp {
618 return nil, errors.New("smtp: CloseWithLMTPResponse used without an LMTP client")
619 }
620 621 if err := cmd.close(); err != nil {
622 return nil, err
623 }
624 625 cmd.client.conn.SetDeadline(time.Now().Add(cmd.client.SubmissionTimeout))
626 defer cmd.client.conn.SetDeadline(time.Time{})
627 628 resp := make(map[string]*DataResponse, len(cmd.client.rcpts))
629 lmtpErr := make(LMTPDataError, len(cmd.client.rcpts))
630 for i := 0; i < len(cmd.client.rcpts); i++ {
631 rcpt := cmd.client.rcpts[i]
632 _, msg, err := cmd.client.readResponse(250)
633 if err != nil {
634 if smtpErr, ok := err.(*SMTPError); ok {
635 lmtpErr[rcpt] = smtpErr
636 } else {
637 if len(lmtpErr) > 0 {
638 return resp, errors.Join(err, lmtpErr)
639 }
640 return resp, err
641 }
642 } else {
643 resp[rcpt] = &DataResponse{StatusText: msg}
644 }
645 }
646 647 if len(lmtpErr) > 0 {
648 return resp, lmtpErr
649 }
650 return resp, nil
651 }
652 653 func (cmd *DataCommand) close() error {
654 if cmd.closeErr != nil {
655 return cmd.closeErr
656 }
657 658 if err := cmd.wc.Close(); err != nil {
659 cmd.closeErr = err
660 return err
661 }
662 663 cmd.closeErr = errors.New("smtp: data writer closed twice")
664 return nil
665 }
666 667 // DataResponse is the response returned by a DATA command. See
668 // DataCommand.CloseWithResponse.
669 type DataResponse struct {
670 // StatusText is the status text returned by the server. It may contain
671 // tracking information.
672 StatusText string
673 }
674 675 // LMTPDataError is a collection of errors returned by an LMTP server for a
676 // DATA command. It holds per-recipient errors.
677 type LMTPDataError map[string]*SMTPError
678 679 // Error implements error.
680 func (lmtpErr LMTPDataError) Error() string {
681 return errors.Join(lmtpErr.Unwrap()...).Error()
682 }
683 684 // Unwrap returns all per-recipient errors returned by the server.
685 func (lmtpErr LMTPDataError) Unwrap() []error {
686 l := make([]error, 0, len(lmtpErr))
687 for rcpt, smtpErr := range lmtpErr {
688 l = append(l, fmt.Errorf("<%v>: %w", rcpt, smtpErr))
689 }
690 sort.Slice(l, func(i, j int) bool {
691 return l[i].Error() < l[j].Error()
692 })
693 return l
694 }
695 696 // Data issues a DATA command to the server and returns a writer that
697 // can be used to write the mail headers and body. The caller should
698 // close the writer before calling any more methods on c. A call to
699 // Data must be preceded by one or more calls to Rcpt.
700 func (c *Client) Data() (*DataCommand, error) {
701 _, _, err := c.cmd(354, "DATA")
702 if err != nil {
703 return nil, err
704 }
705 return &DataCommand{client: c, wc: c.text.DotWriter()}, nil
706 }
707 708 // SendMail will use an existing connection to send an email from
709 // address from, to addresses to, with message r.
710 //
711 // This function does not start TLS, nor does it perform authentication. Use
712 // DialStartTLS and Auth before-hand if desirable.
713 //
714 // The addresses in the to parameter are the SMTP RCPT addresses.
715 //
716 // The r parameter should be an RFC 822-style email with headers
717 // first, a blank line, and then the message body. The lines of r
718 // should be CRLF terminated. The r headers should usually include
719 // fields such as "From", "To", "Subject", and "Cc". Sending "Bcc"
720 // messages is accomplished by including an email address in the to
721 // parameter but not including it in the r headers.
722 func (c *Client) SendMail(from string, to []string, r io.Reader) error {
723 var err error
724 725 if err = c.Mail(from, nil); err != nil {
726 return err
727 }
728 for _, addr := range to {
729 if err = c.Rcpt(addr, nil); err != nil {
730 return err
731 }
732 }
733 w, err := c.Data()
734 if err != nil {
735 return err
736 }
737 _, err = io.Copy(w, r)
738 if err != nil {
739 return err
740 }
741 return w.Close()
742 }
743 744 var testHookStartTLS func(*tls.Config) // nil, except for tests
745 746 func sendMail(addr string, implicitTLS bool, a sasl.Client, from string, to []string, r io.Reader) error {
747 var (
748 c *Client
749 err error
750 )
751 if implicitTLS {
752 c, err = DialTLS(addr, nil)
753 } else {
754 c, err = DialStartTLS(addr, nil)
755 }
756 if err != nil {
757 return err
758 }
759 defer c.Close()
760 761 if a != nil {
762 if ok, _ := c.Extension("AUTH"); !ok {
763 return errors.New("smtp: server doesn't support AUTH")
764 }
765 if err = c.Auth(a); err != nil {
766 return err
767 }
768 }
769 770 if err := c.SendMail(from, to, r); err != nil {
771 return err
772 }
773 774 return c.Quit()
775 }
776 777 // SendMail connects to the server at addr, switches to TLS, authenticates with
778 // the optional SASL client, and then sends an email from address from, to
779 // addresses to, with message r. The addr must include a port, as in
780 // "mail.example.com:smtp".
781 //
782 // The addresses in the to parameter are the SMTP RCPT addresses.
783 //
784 // The r parameter should be an RFC 822-style email with headers
785 // first, a blank line, and then the message body. The lines of r
786 // should be CRLF terminated. The r headers should usually include
787 // fields such as "From", "To", "Subject", and "Cc". Sending "Bcc"
788 // messages is accomplished by including an email address in the to
789 // parameter but not including it in the r headers.
790 //
791 // SendMail is intended to be used for very simple use-cases. If you want to
792 // customize SendMail's behavior, use a Client instead.
793 //
794 // The SendMail function and the go-smtp package are low-level
795 // mechanisms and provide no support for DKIM signing (see go-msgauth), MIME
796 // attachments (see the mime/multipart package or the go-message package), or
797 // other mail functionality.
798 func SendMail(addr string, a sasl.Client, from string, to []string, r io.Reader) error {
799 return sendMail(addr, false, a, from, to, r)
800 }
801 802 // SendMailTLS works like SendMail, but with implicit TLS.
803 func SendMailTLS(addr string, a sasl.Client, from string, to []string, r io.Reader) error {
804 return sendMail(addr, true, a, from, to, r)
805 }
806 807 // Extension reports whether an extension is support by the server.
808 // The extension name is case-insensitive. If the extension is supported,
809 // Extension also returns a string that contains any parameters the
810 // server specifies for the extension.
811 func (c *Client) Extension(ext string) (bool, string) {
812 if err := c.hello(); err != nil {
813 return false, ""
814 }
815 ext = strings.ToUpper(ext)
816 param, ok := c.ext[ext]
817 return ok, param
818 }
819 820 // SupportsAuth checks whether an authentication mechanism is supported.
821 func (c *Client) SupportsAuth(mech string) bool {
822 if err := c.hello(); err != nil {
823 return false
824 }
825 mechs, ok := c.ext["AUTH"]
826 if !ok {
827 return false
828 }
829 for _, m := range strings.Split(mechs, " ") {
830 if strings.EqualFold(m, mech) {
831 return true
832 }
833 }
834 return false
835 }
836 837 // MaxMessageSize returns the maximum message size accepted by the server.
838 // 0 means unlimited.
839 //
840 // If the server doesn't convey this information, ok = false is returned.
841 func (c *Client) MaxMessageSize() (size int, ok bool) {
842 if err := c.hello(); err != nil {
843 return 0, false
844 }
845 v := c.ext["SIZE"]
846 if v == "" {
847 return 0, false
848 }
849 size, err := strconv.Atoi(v)
850 if err != nil || size < 0 {
851 return 0, false
852 }
853 return size, true
854 }
855 856 // Reset sends the RSET command to the server, aborting the current mail
857 // transaction.
858 func (c *Client) Reset() error {
859 if err := c.hello(); err != nil {
860 return err
861 }
862 if _, _, err := c.cmd(250, "RSET"); err != nil {
863 return err
864 }
865 866 // allow custom HELLO again
867 c.didHello = false
868 c.helloError = nil
869 870 c.rcpts = nil
871 return nil
872 }
873 874 // Noop sends the NOOP command to the server. It does nothing but check
875 // that the connection to the server is okay.
876 func (c *Client) Noop() error {
877 if err := c.hello(); err != nil {
878 return err
879 }
880 _, _, err := c.cmd(250, "NOOP")
881 return err
882 }
883 884 // Quit sends the QUIT command and closes the connection to the server.
885 //
886 // If Quit fails the connection is not closed, Close should be used
887 // in this case.
888 func (c *Client) Quit() error {
889 if err := c.hello(); err != nil {
890 return err
891 }
892 _, _, err := c.cmd(221, "QUIT")
893 if err != nil {
894 return err
895 }
896 return c.Close()
897 }
898 899 func parseEnhancedCode(s string) (EnhancedCode, error) {
900 parts := strings.Split(s, ".")
901 if len(parts) != 3 {
902 return EnhancedCode{}, fmt.Errorf("wrong amount of enhanced code parts")
903 }
904 905 code := EnhancedCode{}
906 for i, part := range parts {
907 num, err := strconv.Atoi(part)
908 if err != nil {
909 return code, err
910 }
911 code[i] = num
912 }
913 return code, nil
914 }
915 916 // toSMTPErr converts textproto.Error into SMTPError, parsing
917 // enhanced status code if it is present.
918 func toSMTPErr(protoErr *textproto.Error) *SMTPError {
919 smtpErr := &SMTPError{
920 Code: protoErr.Code,
921 Message: protoErr.Msg,
922 }
923 924 parts := strings.SplitN(protoErr.Msg, " ", 2)
925 if len(parts) != 2 {
926 return smtpErr
927 }
928 929 enchCode, err := parseEnhancedCode(parts[0])
930 if err != nil {
931 return smtpErr
932 }
933 934 msg := parts[1]
935 936 // Per RFC 2034, enhanced code should be prepended to each line.
937 msg = strings.ReplaceAll(msg, "\n"+parts[0]+" ", "\n")
938 939 smtpErr.EnhancedCode = enchCode
940 smtpErr.Message = msg
941 return smtpErr
942 }
943 944 type clientDebugWriter struct {
945 c *Client
946 }
947 948 func (cdw clientDebugWriter) Write(b []byte) (int, error) {
949 if cdw.c.DebugWriter == nil {
950 return len(b), nil
951 }
952 return cdw.c.DebugWriter.Write(b)
953 }
954 955 // validateLine checks to see if a line has CR or LF.
956 func validateLine(line string) error {
957 if strings.ContainsAny(line, "\n\r") {
958 return errors.New("smtp: a line must not contain CR or LF")
959 }
960 return nil
961 }
962