forked from dcposch/scramble
/
smtp_client.go
110 lines (99 loc) · 2.58 KB
/
smtp_client.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
package main
import (
"errors"
"fmt"
"log"
"net"
"net/smtp"
"time"
)
// Cache MX lookups, eg "gmail.com" -> "gmail-smtp-in.l.google.com"
var mxCache map[string]string
func init() {
mxCache = make(map[string]string)
go smtpSendLoop()
}
// Looks up the SMTP server for an email host
// For example, smtpLookUp("gmail.com") returns "gmail-smtp-in.l.google.com"
func smtpLookUp(host string) (string, error) {
cachedServer := mxCache[host]
if cachedServer != "" {
return cachedServer, nil
}
log.Printf("looking up smtp server (mx record) for %s\n", host)
mxs, err := net.LookupMX(host)
if err != nil {
log.Printf("lookup failed for %s\n", host)
return "", err
}
bestServer, bestPref := "", 1<<16
for _, mx := range mxs {
if int(mx.Pref) < bestPref {
bestPref = int(mx.Pref)
bestServer = mx.Host
}
}
mxCache[host] = bestServer
return bestServer, nil
}
// Polls the outbox every second.
// Sends all messages over SMTP.
func smtpSendLoop() {
for {
msgs := CheckoutOutbox(1)
for _, msg := range msgs {
go func(msg BoxedEmail) {
// msg.Address is the destination host for outbox messages
addrs := ParseEmailAddresses(msg.To).FilterByHost(msg.Address)
err := smtpSendTo(&msg.Email, msg.Address, addrs)
if err != nil {
// TODO: better error handling.
// mail servers & internet connectivity go down regularly,
// so maybe create an "outbox-error" box
// MarkOutboxAs([]BoxedEmail{msg}, "outbox-error")
panic(err)
}
MarkOutboxAs([]BoxedEmail{msg}, "outbox-sent")
}(msg)
}
time.Sleep(time.Second)
}
}
func smtpSend(msg *Email) error {
hostAddrs := GroupAddrsByHost(msg.To)
addrsFailed := EmailAddresses{}
for host, addrs := range hostAddrs {
smtpHost, err := smtpLookUp(host)
if err != nil {
addrsFailed = append(addrsFailed, addrs...)
}
err = smtpSendTo(msg, smtpHost, addrs)
if err != nil {
addrsFailed = append(addrsFailed, addrs...)
}
}
if len(addrsFailed) == 0 {
log.Printf("Email sent!\n")
return nil
} else {
err := errors.New(fmt.Sprintf("SMTP sending failed to %v\n", addrsFailed))
return err
}
}
const smtpTemplate = `Message-ID: <%s@%s>
From: <%s>
To: %s
Subject: Encrypted message
%s
%s`
func smtpSendTo(email *Email, smtpHost string, addrs EmailAddresses) error {
msg := fmt.Sprintf(smtpTemplate,
email.MessageID, GetConfig().SmtpMxHost,
email.From,
ParseEmailAddresses(email.To).AngledString(),
email.CipherSubject,
email.CipherBody)
log.Printf("SMTP: sending to %s\n", smtpHost)
err := smtp.SendMail(smtpHost+":25", nil, email.From, addrs.Strings(), []byte(msg))
return err
}