make it easier to run with existing webserver

- make it easier to run with an existing webserver. the quickstart now has a new option for that, it generates a different mox.conf, and further instructions such as configuring the tls keys/certs and reverse proxy urls. and changes to make autoconfig work in that case too.
- when starting up, request a tls cert for the hostname and for the autoconfig endpoint. the first will be requested soon anyway, and the autoconfig cert is needed early so the first autoconfig request doesn't time out (without helpful message to the user by at least thunderbird). and don't request the certificate before the servers are online. the root process was now requesting the certs, before the child process was serving on the tls port.
- add examples of configs generated by the quickstart.
- enable debug logging in config from quickstart, to give user more info.

for issue #5
This commit is contained in:
Mechiel Lukkien
2023-03-04 00:49:02 +01:00
parent 73bfc58453
commit 15e262b043
11 changed files with 417 additions and 277 deletions

View File

@ -51,100 +51,103 @@ var (
// Autoconfiguration for Mozilla Thunderbird.
// User should create a DNS record: autoconfig.<domain> (CNAME or A).
// See https://wiki.mozilla.org/Thunderbird:Autoconfiguration:ConfigFileFormat
func autoconfHandle(l config.Listener) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log := xlog.WithContext(r.Context())
func autoconfHandle(w http.ResponseWriter, r *http.Request) {
log := xlog.WithContext(r.Context())
var addrDom string
defer func() {
metricAutoconf.WithLabelValues(addrDom).Inc()
}()
var addrDom string
defer func() {
metricAutoconf.WithLabelValues(addrDom).Inc()
}()
email := r.FormValue("emailaddress")
log.Debug("autoconfig request", mlog.Field("email", email))
addr, err := smtp.ParseAddress(email)
if err != nil {
http.Error(w, "400 - bad request - invalid parameter emailaddress", http.StatusBadRequest)
return
}
email := r.FormValue("emailaddress")
log.Debug("autoconfig request", mlog.Field("email", email))
addr, err := smtp.ParseAddress(email)
if err != nil {
http.Error(w, "400 - bad request - invalid parameter emailaddress", http.StatusBadRequest)
return
}
if _, ok := mox.Conf.Domain(addr.Domain); !ok {
http.Error(w, "400 - bad request - unknown domain", http.StatusBadRequest)
return
}
addrDom = addr.Domain.Name()
if _, ok := mox.Conf.Domain(addr.Domain); !ok {
http.Error(w, "400 - bad request - unknown domain", http.StatusBadRequest)
return
}
addrDom = addr.Domain.Name()
hostname := l.HostnameDomain
if hostname.IsZero() {
hostname = mox.Conf.Static.HostnameDomain
}
hostname := mox.Conf.Static.HostnameDomain
// Thunderbird doesn't seem to allow U-labels, always return ASCII names.
var resp autoconfigResponse
resp.Version = "1.1"
resp.EmailProvider.ID = addr.Domain.ASCII
resp.EmailProvider.Domain = addr.Domain.ASCII
resp.EmailProvider.DisplayName = email
resp.EmailProvider.DisplayShortName = addr.Domain.ASCII
// Thunderbird doesn't seem to allow U-labels, always return ASCII names.
var resp autoconfigResponse
resp.Version = "1.1"
resp.EmailProvider.ID = addr.Domain.ASCII
resp.EmailProvider.Domain = addr.Domain.ASCII
resp.EmailProvider.DisplayName = email
resp.EmailProvider.DisplayShortName = addr.Domain.ASCII
var imapPort int
var imapSocket string
var imapPort int
var imapSocket string
for _, l := range mox.Conf.Static.Listeners {
if l.IMAPS.Enabled {
imapPort = config.Port(l.IMAPS.Port, 993)
imapSocket = "SSL"
imapPort = config.Port(l.IMAPS.Port, 993)
} else if l.IMAP.Enabled {
imapPort = config.Port(l.IMAP.Port, 143)
if l.TLS != nil {
if l.TLS != nil && imapSocket != "SSL" {
imapSocket = "STARTTLS"
} else {
imapPort = config.Port(l.IMAP.Port, 143)
} else if imapSocket == "" {
imapSocket = "plain"
imapPort = config.Port(l.IMAP.Port, 143)
}
} else {
log.Error("autoconfig: no imap configured?")
}
}
if imapPort == 0 {
log.Error("autoconfig: no imap configured?")
}
// todo: specify SCRAM-SHA-256 once thunderbird and autoconfig supports it. or perhaps that will fall under "password-encrypted" by then.
// todo: specify SCRAM-SHA-256 once thunderbird and autoconfig supports it. or perhaps that will fall under "password-encrypted" by then.
resp.EmailProvider.IncomingServer.Type = "imap"
resp.EmailProvider.IncomingServer.Hostname = hostname.ASCII
resp.EmailProvider.IncomingServer.Port = imapPort
resp.EmailProvider.IncomingServer.SocketType = imapSocket
resp.EmailProvider.IncomingServer.Username = email
resp.EmailProvider.IncomingServer.Authentication = "password-encrypted"
resp.EmailProvider.IncomingServer.Type = "imap"
resp.EmailProvider.IncomingServer.Hostname = hostname.ASCII
resp.EmailProvider.IncomingServer.Port = imapPort
resp.EmailProvider.IncomingServer.SocketType = imapSocket
resp.EmailProvider.IncomingServer.Username = email
resp.EmailProvider.IncomingServer.Authentication = "password-encrypted"
var smtpPort int
var smtpSocket string
var smtpPort int
var smtpSocket string
for _, l := range mox.Conf.Static.Listeners {
if l.Submissions.Enabled {
smtpPort = config.Port(l.Submissions.Port, 465)
smtpSocket = "SSL"
smtpPort = config.Port(l.Submissions.Port, 465)
} else if l.Submission.Enabled {
smtpPort = config.Port(l.Submission.Port, 587)
if l.TLS != nil {
if l.TLS != nil && smtpSocket != "SSL" {
smtpSocket = "STARTTLS"
} else {
smtpPort = config.Port(l.Submission.Port, 587)
} else if smtpSocket == "" {
smtpSocket = "plain"
smtpPort = config.Port(l.Submission.Port, 587)
}
} else {
log.Error("autoconfig: no smtp submission configured?")
}
}
if smtpPort == 0 {
log.Error("autoconfig: no smtp submission configured?")
}
resp.EmailProvider.OutgoingServer.Type = "smtp"
resp.EmailProvider.OutgoingServer.Hostname = hostname.ASCII
resp.EmailProvider.OutgoingServer.Port = smtpPort
resp.EmailProvider.OutgoingServer.SocketType = smtpSocket
resp.EmailProvider.OutgoingServer.Username = email
resp.EmailProvider.OutgoingServer.Authentication = "password-encrypted"
resp.EmailProvider.OutgoingServer.Type = "smtp"
resp.EmailProvider.OutgoingServer.Hostname = hostname.ASCII
resp.EmailProvider.OutgoingServer.Port = smtpPort
resp.EmailProvider.OutgoingServer.SocketType = smtpSocket
resp.EmailProvider.OutgoingServer.Username = email
resp.EmailProvider.OutgoingServer.Authentication = "password-encrypted"
// todo: should we put the email address in the URL?
resp.ClientConfigUpdate.URL = fmt.Sprintf("https://%s/mail/config-v1.1.xml", hostname.ASCII)
// todo: should we put the email address in the URL?
resp.ClientConfigUpdate.URL = fmt.Sprintf("https://%s/mail/config-v1.1.xml", hostname.ASCII)
w.Header().Set("Content-Type", "application/xml; charset=utf-8")
enc := xml.NewEncoder(w)
enc.Indent("", "\t")
fmt.Fprint(w, xml.Header)
if err := enc.Encode(resp); err != nil {
log.Errorx("marshal autoconfig response", err)
}
w.Header().Set("Content-Type", "application/xml; charset=utf-8")
enc := xml.NewEncoder(w)
enc.Indent("", "\t")
fmt.Fprint(w, xml.Header)
if err := enc.Encode(resp); err != nil {
log.Errorx("marshal autoconfig response", err)
}
}
@ -158,125 +161,129 @@ func autoconfHandle(l config.Listener) http.HandlerFunc {
// errors.
//
// Thunderbird does understand autodiscover.
func autodiscoverHandle(l config.Listener) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log := xlog.WithContext(r.Context())
func autodiscoverHandle(w http.ResponseWriter, r *http.Request) {
log := xlog.WithContext(r.Context())
var addrDom string
defer func() {
metricAutodiscover.WithLabelValues(addrDom).Inc()
}()
var addrDom string
defer func() {
metricAutodiscover.WithLabelValues(addrDom).Inc()
}()
if r.Method != "POST" {
http.Error(w, "405 - method not allowed - post required", http.StatusMethodNotAllowed)
return
}
if r.Method != "POST" {
http.Error(w, "405 - method not allowed - post required", http.StatusMethodNotAllowed)
return
}
var req autodiscoverRequest
if err := xml.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "400 - bad request - parsing autodiscover request: "+err.Error(), http.StatusMethodNotAllowed)
return
}
var req autodiscoverRequest
if err := xml.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "400 - bad request - parsing autodiscover request: "+err.Error(), http.StatusMethodNotAllowed)
return
}
log.Debug("autodiscover request", mlog.Field("email", req.Request.EmailAddress))
log.Debug("autodiscover request", mlog.Field("email", req.Request.EmailAddress))
addr, err := smtp.ParseAddress(req.Request.EmailAddress)
if err != nil {
http.Error(w, "400 - bad request - invalid parameter emailaddress", http.StatusBadRequest)
return
}
addr, err := smtp.ParseAddress(req.Request.EmailAddress)
if err != nil {
http.Error(w, "400 - bad request - invalid parameter emailaddress", http.StatusBadRequest)
return
}
if _, ok := mox.Conf.Domain(addr.Domain); !ok {
http.Error(w, "400 - bad request - unknown domain", http.StatusBadRequest)
return
}
addrDom = addr.Domain.Name()
if _, ok := mox.Conf.Domain(addr.Domain); !ok {
http.Error(w, "400 - bad request - unknown domain", http.StatusBadRequest)
return
}
addrDom = addr.Domain.Name()
hostname := l.HostnameDomain
if hostname.IsZero() {
hostname = mox.Conf.Static.HostnameDomain
}
hostname := mox.Conf.Static.HostnameDomain
// The docs are generated and fragmented in many tiny pages, hard to follow.
// High-level starting point, https://learn.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxdscli/78530279-d042-4eb0-a1f4-03b18143cd19
// Request: https://learn.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxdscli/2096fab2-9c3c-40b9-b123-edf6e8d55a9b
// Response, protocol: https://learn.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxdscli/f4238db6-a983-435c-807a-b4b4a624c65b
// It appears autodiscover does not allow specifying SCRAM-SHA-256 as
// authentication method, or any authentication method that real clients actually
// use. See
// https://learn.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxdscli/21fd2dd5-c4ee-485b-94fb-e7db5da93726
// The docs are generated and fragmented in many tiny pages, hard to follow.
// High-level starting point, https://learn.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxdscli/78530279-d042-4eb0-a1f4-03b18143cd19
// Request: https://learn.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxdscli/2096fab2-9c3c-40b9-b123-edf6e8d55a9b
// Response, protocol: https://learn.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxdscli/f4238db6-a983-435c-807a-b4b4a624c65b
// It appears autodiscover does not allow specifying SCRAM-SHA-256 as
// authentication method, or any authentication method that real clients actually
// use. See
// https://learn.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxdscli/21fd2dd5-c4ee-485b-94fb-e7db5da93726
var imapPort int
imapSSL := "off"
var imapEncryption string
var imapPort int
imapSSL := "off"
var imapEncryption string
var smtpPort int
smtpSSL := "off"
var smtpEncryption string
for _, l := range mox.Conf.Static.Listeners {
if l.IMAPS.Enabled {
imapPort = config.Port(l.IMAPS.Port, 993)
imapSSL = "on"
imapEncryption = "TLS" // Assuming this means direct TLS.
} else if l.IMAP.Enabled {
imapPort = config.Port(l.IMAP.Port, 143)
if l.TLS != nil {
if l.TLS != nil && imapEncryption != "TLS" {
imapSSL = "on"
imapPort = config.Port(l.IMAP.Port, 143)
} else if imapSSL == "" {
imapPort = config.Port(l.IMAP.Port, 143)
}
} else {
log.Error("autoconfig: no imap configured?")
}
var smtpPort int
smtpSSL := "off"
var smtpEncryption string
if l.Submissions.Enabled {
smtpPort = config.Port(l.Submissions.Port, 465)
smtpSSL = "on"
smtpEncryption = "TLS" // Assuming this means direct TLS.
} else if l.Submission.Enabled {
smtpPort = config.Port(l.Submission.Port, 587)
if l.TLS != nil {
if l.TLS != nil && smtpEncryption != "TLS" {
smtpSSL = "on"
smtpPort = config.Port(l.Submission.Port, 587)
} else if smtpSSL == "" {
smtpPort = config.Port(l.Submission.Port, 587)
}
} else {
log.Error("autoconfig: no smtp submission configured?")
}
}
if imapPort == 0 {
log.Error("autoconfig: no smtp submission configured?")
}
if smtpPort == 0 {
log.Error("autoconfig: no imap configured?")
}
w.Header().Set("Content-Type", "application/xml; charset=utf-8")
w.Header().Set("Content-Type", "application/xml; charset=utf-8")
resp := autodiscoverResponse{}
resp.XMLName.Local = "Autodiscover"
resp.XMLName.Space = "http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006"
resp.Response.XMLName.Local = "Response"
resp.Response.XMLName.Space = "http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a"
resp.Response.Account = autodiscoverAccount{
AccountType: "email",
Action: "settings",
Protocol: []autodiscoverProtocol{
{
Type: "IMAP",
Server: hostname.ASCII,
Port: imapPort,
LoginName: req.Request.EmailAddress,
SSL: imapSSL,
Encryption: imapEncryption,
SPA: "off", // Override default "on", this is Microsofts proprietary authentication protocol.
AuthRequired: "on",
},
{
Type: "SMTP",
Server: hostname.ASCII,
Port: smtpPort,
LoginName: req.Request.EmailAddress,
SSL: smtpSSL,
Encryption: smtpEncryption,
SPA: "off", // Override default "on", this is Microsofts proprietary authentication protocol.
AuthRequired: "on",
},
resp := autodiscoverResponse{}
resp.XMLName.Local = "Autodiscover"
resp.XMLName.Space = "http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006"
resp.Response.XMLName.Local = "Response"
resp.Response.XMLName.Space = "http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a"
resp.Response.Account = autodiscoverAccount{
AccountType: "email",
Action: "settings",
Protocol: []autodiscoverProtocol{
{
Type: "IMAP",
Server: hostname.ASCII,
Port: imapPort,
LoginName: req.Request.EmailAddress,
SSL: imapSSL,
Encryption: imapEncryption,
SPA: "off", // Override default "on", this is Microsofts proprietary authentication protocol.
AuthRequired: "on",
},
}
enc := xml.NewEncoder(w)
enc.Indent("", "\t")
fmt.Fprint(w, xml.Header)
if err := enc.Encode(resp); err != nil {
log.Errorx("marshal autodiscover response", err)
}
{
Type: "SMTP",
Server: hostname.ASCII,
Port: smtpPort,
LoginName: req.Request.EmailAddress,
SSL: smtpSSL,
Encryption: smtpEncryption,
SPA: "off", // Override default "on", this is Microsofts proprietary authentication protocol.
AuthRequired: "on",
},
},
}
enc := xml.NewEncoder(w)
enc.Indent("", "\t")
fmt.Fprint(w, xml.Header)
if err := enc.Encode(resp); err != nil {
log.Errorx("marshal autodiscover response", err)
}
}