diff --git a/http/autoconf.go b/http/autoconf.go index c1a27a6..e767555 100644 --- a/http/autoconf.go +++ b/http/autoconf.go @@ -328,15 +328,23 @@ func mobileconfigHandle(w http.ResponseWriter, r *http.Request) { http.Error(w, "405 - method not allowed - get required", http.StatusMethodNotAllowed) return } - address := r.FormValue("address") + addresses := r.FormValue("addresses") fullName := r.FormValue("name") - buf, err := MobileConfig(address, fullName) + var buf []byte + var err error + if addresses == "" { + err = fmt.Errorf("missing/empty field addresses") + } + l := strings.Split(addresses, ",") + if err == nil { + buf, err = MobileConfig(l, fullName) + } if err != nil { http.Error(w, "400 - bad request - "+err.Error(), http.StatusBadRequest) return } h := w.Header() - filename := address + filename := l[0] filename = strings.ReplaceAll(filename, ".", "-") filename = strings.ReplaceAll(filename, "@", "-at-") filename = "email-account-" + filename + ".mobileconfig" diff --git a/http/mobileconfig.go b/http/mobileconfig.go index 94bfc33..4e8164b 100644 --- a/http/mobileconfig.go +++ b/http/mobileconfig.go @@ -109,9 +109,15 @@ func (m dict) MarshalXML(e *xml.Encoder, start xml.StartElement) error { // password because sending opaque files containing passwords around to users seems // like bad security practice. // +// Multiple addresses can be passed, the first is used for IMAP/submission login, +// and likely seen as primary account by Apple software. +// // The config is not signed, so users must ignore warnings about unsigned profiles. -func MobileConfig(address, fullName string) ([]byte, error) { - addr, err := smtp.ParseAddress(address) +func MobileConfig(addresses []string, fullName string) ([]byte, error) { + if len(addresses) == 0 { + return nil, fmt.Errorf("need at least 1 address") + } + addr, err := smtp.ParseAddress(addresses[0]) if err != nil { return nil, fmt.Errorf("parsing address: %v", err) } @@ -131,7 +137,7 @@ func MobileConfig(address, fullName string) ([]byte, error) { const key = "mox0" uuid := func(prefix string) string { mac := hmac.New(sha256.New, []byte(key)) - mac.Write([]byte(prefix + "\n" + "\n" + address)) + mac.Write([]byte(prefix + "\n" + "\n" + strings.Join(addresses, ","))) sum := mac.Sum(nil) uuid := fmt.Sprintf("%x-%x-%x-%x-%x", sum[0:4], sum[4:6], sum[6:8], sum[8:10], sum[10:16]) return uuid @@ -152,26 +158,28 @@ func MobileConfig(address, fullName string) ([]byte, error) { p := deviceManagementProfile{ Version: "1.0", Dict: dict(map[string]any{ - "PayloadDisplayName": fmt.Sprintf("%s email account", address), + "PayloadDisplayName": fmt.Sprintf("%s email account", addresses[0]), "PayloadIdentifier": reverseAddr + ".email", "PayloadType": "Configuration", "PayloadUUID": uuidConfig, "PayloadVersion": 1, "PayloadContent": array{ dict(map[string]any{ - "EmailAccountDescription": address, - "EmailAccountName": fullName, - "EmailAccountType": "EmailTypeIMAP", - "EmailAddress": address, + "EmailAccountDescription": addresses[0], + "EmailAccountName": fullName, + "EmailAccountType": "EmailTypeIMAP", + // Comma-separated multiple addresses are not documented at Apple, but seem to + // work. + "EmailAddress": strings.Join(addresses, ","), "IncomingMailServerAuthentication": "EmailAuthCRAMMD5", // SCRAM not an option at time of writing.. - "IncomingMailServerUsername": address, + "IncomingMailServerUsername": addresses[0], "IncomingMailServerHostName": config.IMAP.Host.ASCII, "IncomingMailServerPortNumber": config.IMAP.Port, "IncomingMailServerUseSSL": config.IMAP.TLSMode == mox.TLSModeImmediate, "OutgoingMailServerAuthentication": "EmailAuthCRAMMD5", // SCRAM not an option at time of writing... "OutgoingMailServerHostName": config.Submission.Host.ASCII, "OutgoingMailServerPortNumber": config.Submission.Port, - "OutgoingMailServerUsername": address, + "OutgoingMailServerUsername": addresses[0], "OutgoingMailServerUseSSL": config.Submission.TLSMode == mox.TLSModeImmediate, "OutgoingPasswordSameAsIncomingPassword": true, "PayloadIdentifier": reverseAddr + ".email.account", diff --git a/webaccount/account.html b/webaccount/account.html index b8e7976..f1fe0c8 100644 --- a/webaccount/account.html +++ b/webaccount/account.html @@ -601,6 +601,8 @@ const destination = async (name) => { let fullName let saveButton + const addresses = [name, ...Object.keys(destinations).filter(a => !a.startsWith('@') && a !== name)] + const page = document.getElementById('page') dom._kids(page, crumbs( @@ -685,9 +687,9 @@ const destination = async (name) => { dom.br(), dom.p("Apple's mail applications don't do account autoconfiguration, and when adding an account it can choose defaults that don't work with modern email servers. Adding an account through a \"mobileconfig\" profile file can be more convenient: It contains the IMAP/SMTP settings such as host name, port, TLS, authentication mechanism and user name. This profile does not contain a login password. Opening the profile adds it under Profiles in System Preferences (macOS) or Settings (iOS), where you can install it. These profiles are not signed, so users will have to ignore the warnings about them being unsigned. ", dom.br(), - dom.a(attr({href: 'https://autoconfig.'+domainName(domain)+'/profile.mobileconfig?address='+encodeURIComponent(name)+'&name='+encodeURIComponent(dest.FullName), download: ''}), 'Download .mobileconfig email account profile'), + dom.a(attr({href: 'https://autoconfig.'+domainName(domain)+'/profile.mobileconfig?addresses='+encodeURIComponent(addresses.join(','))+'&name='+encodeURIComponent(dest.FullName), download: ''}), 'Download .mobileconfig email account profile'), dom.br(), - dom.a(attr({href: 'https://autoconfig.'+domainName(domain)+'/profile.mobileconfig.qrcode.png?address='+encodeURIComponent(name)+'&name='+encodeURIComponent(dest.FullName), download: ''}), 'Open QR-code with link to .mobileconfig profile'), + dom.a(attr({href: 'https://autoconfig.'+domainName(domain)+'/profile.mobileconfig.qrcode.png?addresses='+encodeURIComponent(addresses.join(','))+'&name='+encodeURIComponent(dest.FullName), download: ''}), 'Open QR-code with link to .mobileconfig profile'), ), ) }