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 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
|
// Package autoconfig provides a somewhat smart way of automatically
// determining the mailservers for a given address.
//
// It works by first checking for a Thunderbird autoconfiguration, and if not
// found checking common names (imap|smtp|mail).example.com and performing a TCP
// Ping on their ports.
//
// The first part is described here: https://wiki.mozilla.org/Thunderbird:Autoconfiguration
package autoconfig
import (
"context"
"encoding/xml"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"git.sr.ht/~rjarry/aerc/lib/log"
)
// getFromAutoconfig retrieves the config from the provider using a Mozilla
// Thunderbird autoconfig service
func getFromAutoconfig(ctx context.Context, localpart, domain string, result chan<- *Config) {
defer log.PanicHandler()
defer close(result)
res := make(chan *Config, 1)
go func(res chan *Config) {
defer log.PanicHandler()
defer close(res)
var addresses []*url.URL
u, err := url.Parse(fmt.Sprintf(
"https://autoconfig.%s/mail/config-v1.1.xml?emailaddress=%s",
domain, url.QueryEscape(localpart+"@"+domain)))
if err == nil {
addresses = append(addresses, u)
}
u, err = url.Parse(fmt.Sprintf(
"https://%s/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress=%s",
domain, url.QueryEscape(localpart+"@"+domain)))
if err == nil {
addresses = append(addresses, u)
}
log.Debugf("checking for autoconfig files at %v", addresses)
cc := new(ClientConfig)
for _, u := range addresses {
response, err := httpGet(u.String())
if err != nil {
continue
}
err = xml.NewDecoder(response.Body).Decode(cc)
if err != nil {
continue
}
// IMAP sanity check
var incoming *IncomingServer
for i := range cc.EmailProvider.IncomingServer {
if strings.ToLower(cc.EmailProvider.IncomingServer[i].Type) != "imap" {
continue
}
incoming = &cc.EmailProvider.IncomingServer[i]
break
}
if incoming == nil {
// no imap server found
continue
}
var incomingPort int
if incomingPort, err = strconv.Atoi(incoming.Port); err != nil {
continue
}
inenc := EncryptionSTARTTLS
switch strings.ToLower(incoming.SocketType) {
case "plain":
inenc = EncryptionInsecure
case "ssl":
inenc = EncryptionTLS
}
if strings.ToLower(incoming.Username) == "%emailaddress%" {
incoming.Username = localpart + "@" + domain
}
var outport int
if outport, err = strconv.Atoi(cc.EmailProvider.OutgoingServer.Port); err != nil {
continue
}
outenc := EncryptionSTARTTLS
switch strings.ToLower(cc.EmailProvider.OutgoingServer.SocketType) {
case "plain":
outenc = EncryptionInsecure
case "ssl":
outenc = EncryptionTLS
}
if strings.ToLower(cc.EmailProvider.OutgoingServer.Username) == "%emailaddress%" {
cc.EmailProvider.OutgoingServer.Username = localpart + "@" + domain
}
log.Debugf("found HTTP-based autoconfig at %s", u)
res <- &Config{
Found: ProtocolIMAP,
IMAP: Credentials{
Encryption: inenc,
Address: incoming.Hostname,
Port: incomingPort,
Username: incoming.Username,
},
SMTP: Credentials{
Encryption: outenc,
Address: cc.EmailProvider.OutgoingServer.Hostname,
Port: outport,
Username: cc.EmailProvider.OutgoingServer.Username,
},
}
return
}
}(res)
select {
case r, isValue := <-res:
if isValue {
result <- r
}
case <-ctx.Done():
}
}
var httpGet = http.Get
///////////////////////////////////////////////////////////////////////////////
// Autogenerated struct… you probably don't want to touch this. //
///////////////////////////////////////////////////////////////////////////////
type ClientConfig struct {
XMLName xml.Name `xml:"clientConfig"`
Text string `xml:",chardata"`
Version string `xml:"version,attr"`
EmailProvider EmailProvider `xml:"emailProvider"`
}
type EmailProvider struct {
Text string `xml:",chardata"`
ID string `xml:"id,attr"`
Domain []string `xml:"domain"`
IncomingServer []IncomingServer `xml:"incomingServer"`
OutgoingServer OutgoingServer `xml:"outgoingServer"`
}
type IncomingServer struct {
Text string `xml:",chardata"`
// can only be "imap" or "pop3", only imap is supported by aerc
Type string `xml:"type,attr"`
Hostname string `xml:"hostname"`
Port string `xml:"port"`
SocketType string `xml:"socketType"`
Username string `xml:"username"`
Authentication string `xml:"authentication"`
Password string `xml:"password"`
}
type OutgoingServer struct {
Text string `xml:",chardata"`
Type string `xml:"type,attr"`
Hostname string `xml:"hostname"`
Port string `xml:"port"`
SocketType string `xml:"socketType"`
Username string `xml:"username"`
Authentication string `xml:"authentication"`
Restriction string `xml:"restriction"`
AddThisServer string `xml:"addThisServer"`
UseGlobalPreferredServer string `xml:"useGlobalPreferredServer"`
Password string `xml:"password"`
}
|