/
sendmail.go
121 lines (109 loc) · 2.8 KB
/
sendmail.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package email
import (
"context"
"crypto/tls"
"errors"
"net"
"net/smtp"
"strings"
)
// validateLine checks to see if a line has CR or LF as per RFC 5321
func validateLine(line string) error {
if strings.ContainsAny(line, "\n\r") {
return errors.New("smtp: A line must not contain CR or LF")
}
return nil
}
func validateAddrs(from string, to []string) error {
if err := validateLine(from); err != nil {
return err
}
for _, recp := range to {
if err := validateLine(recp); err != nil {
return err
}
}
return nil
}
// NegotiateAuth should return the appropriate smtp.Auth for the given server auth string.
type NegotiateAuth func(auths string) smtp.Auth
// SendMailTLS will send a message using the provided server over a TLS connection and optional auth.
func SendMailTLS(ctx context.Context, addr string, a NegotiateAuth, from string, to []string, msg []byte, cfg *tls.Config) error {
err := validateAddrs(from, to)
if err != nil {
return err
}
t, _ := ctx.Deadline()
conn, err := tls.DialWithDialer(&net.Dialer{Deadline: t}, "tcp", addr, cfg)
if err != nil {
return err
}
conn.SetDeadline(t)
defer conn.Close()
host, _, _ := net.SplitHostPort(addr)
return sendMail(ctx, conn, host, a, from, to, msg, nil)
}
// SendMail will send a message using the provided server and optional auth. It will attempt to use STARTTLS if available from the server.
func SendMail(ctx context.Context, addr string, a NegotiateAuth, from string, to []string, msg []byte, cfg *tls.Config) error {
err := validateAddrs(from, to)
if err != nil {
return err
}
t, _ := ctx.Deadline()
var d net.Dialer
conn, err := d.DialContext(ctx, "tcp", addr)
if err != nil {
return err
}
conn.SetDeadline(t)
defer conn.Close()
host, _, _ := net.SplitHostPort(addr)
return sendMail(ctx, conn, host, a, from, to, msg, cfg)
}
func sendMail(ctx context.Context, conn net.Conn, host string, a NegotiateAuth, from string, to []string, msg []byte, cfg *tls.Config) error {
c, err := smtp.NewClient(conn, host)
if err != nil {
return err
}
defer c.Close()
_, isTLS := conn.(*tls.Conn)
if ok, _ := c.Extension("STARTTLS"); !isTLS && ok {
if err = c.StartTLS(cfg); err != nil {
return err
}
}
if a != nil {
ok, auths := c.Extension("AUTH")
if !ok {
return errors.New("notification/email: server doesn't support AUTH")
}
auth := a(auths)
if auth == nil {
return errors.New("notification/email: no supported AUTH mechanism available")
}
if err = c.Auth(auth); err != nil {
return err
}
}
if err = c.Mail(from); err != nil {
return err
}
for _, addr := range to {
if err = c.Rcpt(addr); err != nil {
return err
}
}
w, err := c.Data()
if err != nil {
return err
}
_, err = w.Write(msg)
if err != nil {
return err
}
err = w.Close()
if err != nil {
return err
}
return c.Quit()
}