aboutsummaryrefslogtreecommitdiffstats
path: root/lib/send/smtp.go
diff options
context:
space:
mode:
authorKarel Balej <balejk@matfyz.cz>2024-01-30 20:11:27 +0100
committerRobin Jarry <robin@jarry.cc>2024-02-12 22:58:40 +0100
commit3553e4f27165b18be84123d0ca015a019d35e41c (patch)
tree7c007d4bc65e242d65f0bb2a8ba1a99f7a7bb4ad /lib/send/smtp.go
parent324e620c5a62fee07970c436f792c7383a3fb1e5 (diff)
downloadaerc-3553e4f27165b18be84123d0ca015a019d35e41c.tar.gz
send: move code to lib for reuse
Move the code which handles the preparation of a sender into which the message can be written into lib to allow for reuse. Also hide the sending backend a bit more from the `:send` command code by introducing a NewSender function which determines which backend should be used and invokes the appropriate sender factory function. Rename send() to sendHelper() to avoid collision. Signed-off-by: Karel Balej <balejk@matfyz.cz> Acked-by: Robin Jarry <robin@jarry.cc>
Diffstat (limited to 'lib/send/smtp.go')
-rw-r--r--lib/send/smtp.go136
1 files changed, 136 insertions, 0 deletions
diff --git a/lib/send/smtp.go b/lib/send/smtp.go
new file mode 100644
index 00000000..2cc53d25
--- /dev/null
+++ b/lib/send/smtp.go
@@ -0,0 +1,136 @@
+package send
+
+import (
+ "crypto/tls"
+ "fmt"
+ "io"
+ "net/url"
+ "strings"
+
+ "github.com/emersion/go-message/mail"
+ "github.com/emersion/go-smtp"
+ "github.com/pkg/errors"
+)
+
+func connectSmtp(starttls bool, host string, domain string) (*smtp.Client, error) {
+ serverName := host
+ if !strings.ContainsRune(host, ':') {
+ host += ":587" // Default to submission port
+ } else {
+ serverName = host[:strings.IndexRune(host, ':')]
+ }
+ conn, err := smtp.Dial(host)
+ if err != nil {
+ return nil, errors.Wrap(err, "smtp.Dial")
+ }
+ if domain != "" {
+ err := conn.Hello(domain)
+ if err != nil {
+ return nil, errors.Wrap(err, "Hello")
+ }
+ }
+ if starttls {
+ if sup, _ := conn.Extension("STARTTLS"); !sup {
+ err := errors.New("STARTTLS requested, but not supported " +
+ "by this SMTP server. Is someone tampering with your " +
+ "connection?")
+ conn.Close()
+ return nil, err
+ }
+ if err = conn.StartTLS(&tls.Config{
+ ServerName: serverName,
+ }); err != nil {
+ conn.Close()
+ return nil, errors.Wrap(err, "StartTLS")
+ }
+ }
+
+ return conn, nil
+}
+
+func connectSmtps(host string) (*smtp.Client, error) {
+ serverName := host
+ if !strings.ContainsRune(host, ':') {
+ host += ":465" // Default to smtps port
+ } else {
+ serverName = host[:strings.IndexRune(host, ':')]
+ }
+ conn, err := smtp.DialTLS(host, &tls.Config{
+ ServerName: serverName,
+ })
+ if err != nil {
+ return nil, errors.Wrap(err, "smtp.DialTLS")
+ }
+ return conn, nil
+}
+
+type smtpSender struct {
+ conn *smtp.Client
+ w io.WriteCloser
+}
+
+func (s *smtpSender) Write(p []byte) (int, error) {
+ return s.w.Write(p)
+}
+
+func (s *smtpSender) Close() error {
+ we := s.w.Close()
+ ce := s.conn.Close()
+ if we != nil {
+ return we
+ }
+ return ce
+}
+
+func newSmtpSender(
+ protocol string, auth string, uri *url.URL, domain string,
+ from *mail.Address, rcpts []*mail.Address,
+) (io.WriteCloser, error) {
+ var err error
+ var conn *smtp.Client
+ switch protocol {
+ case "smtp":
+ conn, err = connectSmtp(true, uri.Host, domain)
+ case "smtp+insecure":
+ conn, err = connectSmtp(false, uri.Host, domain)
+ case "smtps":
+ conn, err = connectSmtps(uri.Host)
+ default:
+ return nil, fmt.Errorf("not a smtp protocol %s", protocol)
+ }
+
+ if err != nil {
+ return nil, errors.Wrap(err, "Connection failed")
+ }
+
+ saslclient, err := newSaslClient(auth, uri)
+ if err != nil {
+ conn.Close()
+ return nil, err
+ }
+ if saslclient != nil {
+ if err := conn.Auth(saslclient); err != nil {
+ conn.Close()
+ return nil, errors.Wrap(err, "conn.Auth")
+ }
+ }
+ s := &smtpSender{
+ conn: conn,
+ }
+ if err := s.conn.Mail(from.Address, nil); err != nil {
+ conn.Close()
+ return nil, errors.Wrap(err, "conn.Mail")
+ }
+ for _, rcpt := range rcpts {
+ if err := s.conn.Rcpt(rcpt.Address); err != nil {
+ conn.Close()
+ return nil, errors.Wrap(err, "conn.Rcpt")
+ }
+ }
+ s.w, err = s.conn.Data()
+ if err != nil {
+ conn.Close()
+ return nil, errors.Wrap(err, "conn.Data")
+ }
+ return s.w, nil
+}