diff options
Diffstat (limited to 'comm/mail/components/accountcreation/readFromXML.jsm')
-rw-r--r-- | comm/mail/components/accountcreation/readFromXML.jsm | 352 |
1 files changed, 352 insertions, 0 deletions
diff --git a/comm/mail/components/accountcreation/readFromXML.jsm b/comm/mail/components/accountcreation/readFromXML.jsm new file mode 100644 index 0000000000..b853a81117 --- /dev/null +++ b/comm/mail/components/accountcreation/readFromXML.jsm @@ -0,0 +1,352 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const EXPORTED_SYMBOLS = ["readFromXML"]; + +const lazy = {}; + +ChromeUtils.defineModuleGetter( + lazy, + "AccountConfig", + "resource:///modules/accountcreation/AccountConfig.jsm" +); +ChromeUtils.defineModuleGetter( + lazy, + "AccountCreationUtils", + "resource:///modules/accountcreation/AccountCreationUtils.jsm" +); +ChromeUtils.defineModuleGetter( + lazy, + "Sanitizer", + "resource:///modules/accountcreation/Sanitizer.jsm" +); + +/* eslint-disable complexity */ +/** + * Takes an XML snipplet (as JXON) and reads the values into + * a new AccountConfig object. + * It does so securely (or tries to), by trying to avoid remote execution + * and similar holes which can appear when reading too naively. + * Of course it cannot tell whether the actual values are correct, + * e.g. it can't tell whether the host name is a good server. + * + * The XML format is documented at + * <https://wiki.mozilla.org/Thunderbird:Autoconfiguration:ConfigFileFormat> + * + * @param clientConfigXML {JXON} - The <clientConfig> node. + * @param source {String} - Used for the subSource field of AccountConfig. + * @returns AccountConfig object filled with the data from XML + */ +function readFromXML(clientConfigXML, subSource) { + function array_or_undef(value) { + return value === undefined ? [] : value; + } + var exception; + if ( + typeof clientConfigXML != "object" || + !("clientConfig" in clientConfigXML) || + !("emailProvider" in clientConfigXML.clientConfig) + ) { + dump( + `client config xml = ${JSON.stringify(clientConfigXML).substr(0, 50)} \n` + ); + let stringBundle = lazy.AccountCreationUtils.getStringBundle( + "chrome://messenger/locale/accountCreationModel.properties" + ); + throw stringBundle.GetStringFromName("no_emailProvider.error"); + } + var xml = clientConfigXML.clientConfig.emailProvider; + + var d = new lazy.AccountConfig(); + d.source = lazy.AccountConfig.kSourceXML; + d.subSource = `xml-from-${subSource}`; + + d.id = lazy.Sanitizer.hostname(xml["@id"]); + d.displayName = d.id; + try { + d.displayName = lazy.Sanitizer.label(xml.displayName); + } catch (e) { + console.error(e); + } + for (var domain of xml.$domain) { + try { + d.domains.push(lazy.Sanitizer.hostname(domain)); + } catch (e) { + console.error(e); + exception = e; + } + } + if (d.domains.length == 0) { + throw exception ? exception : "need proper <domain> in XML"; + } + exception = null; + + // incoming server + for (let iX of array_or_undef(xml.$incomingServer)) { + // input (XML) + let iO = d.createNewIncoming(); // output (object) + try { + // throws if not supported + iO.type = lazy.Sanitizer.enum(iX["@type"], [ + "pop3", + "imap", + "nntp", + "exchange", + ]); + iO.hostname = lazy.Sanitizer.hostname(iX.hostname); + iO.port = lazy.Sanitizer.integerRange(iX.port, 1, 65535); + // We need a username even for Kerberos, need it even internally. + iO.username = lazy.Sanitizer.string(iX.username); // may be a %VARIABLE% + + if ("password" in iX) { + d.rememberPassword = true; + iO.password = lazy.Sanitizer.string(iX.password); + } + + for (let iXsocketType of array_or_undef(iX.$socketType)) { + try { + iO.socketType = lazy.Sanitizer.translate(iXsocketType, { + plain: Ci.nsMsgSocketType.plain, + SSL: Ci.nsMsgSocketType.SSL, + STARTTLS: Ci.nsMsgSocketType.alwaysSTARTTLS, + }); + break; // take first that we support + } catch (e) { + exception = e; + } + } + if (iO.socketType == -1) { + throw exception ? exception : "need proper <socketType> in XML"; + } + exception = null; + + for (let iXauth of array_or_undef(iX.$authentication)) { + try { + iO.auth = lazy.Sanitizer.translate(iXauth, { + "password-cleartext": Ci.nsMsgAuthMethod.passwordCleartext, + // @deprecated TODO remove + plain: Ci.nsMsgAuthMethod.passwordCleartext, + "password-encrypted": Ci.nsMsgAuthMethod.passwordEncrypted, + // @deprecated TODO remove + secure: Ci.nsMsgAuthMethod.passwordEncrypted, + GSSAPI: Ci.nsMsgAuthMethod.GSSAPI, + NTLM: Ci.nsMsgAuthMethod.NTLM, + OAuth2: Ci.nsMsgAuthMethod.OAuth2, + }); + break; // take first that we support + } catch (e) { + exception = e; + } + } + if (!iO.auth) { + throw exception ? exception : "need proper <authentication> in XML"; + } + exception = null; + + if (iO.type == "exchange") { + try { + if ("owaURL" in iX) { + iO.owaURL = lazy.Sanitizer.url(iX.owaURL); + } + } catch (e) { + console.error(e); + } + try { + if ("ewsURL" in iX) { + iO.ewsURL = lazy.Sanitizer.url(iX.ewsURL); + } + } catch (e) { + console.error(e); + } + try { + if ("easURL" in iX) { + iO.easURL = lazy.Sanitizer.url(iX.easURL); + } + } catch (e) { + console.error(e); + } + iO.oauthSettings = { + issuer: iO.hostname, + scope: iO.owaURL || iO.ewsURL || iO.easURL, + }; + } + // defaults are in accountConfig.js + if (iO.type == "pop3" && "pop3" in iX) { + try { + if ("leaveMessagesOnServer" in iX.pop3) { + iO.leaveMessagesOnServer = lazy.Sanitizer.boolean( + iX.pop3.leaveMessagesOnServer + ); + } + if ("daysToLeaveMessagesOnServer" in iX.pop3) { + iO.daysToLeaveMessagesOnServer = lazy.Sanitizer.integer( + iX.pop3.daysToLeaveMessagesOnServer + ); + } + } catch (e) { + console.error(e); + } + try { + if ("downloadOnBiff" in iX.pop3) { + iO.downloadOnBiff = lazy.Sanitizer.boolean(iX.pop3.downloadOnBiff); + } + } catch (e) { + console.error(e); + } + } + + try { + if ("useGlobalPreferredServer" in iX) { + iO.useGlobalPreferredServer = lazy.Sanitizer.boolean( + iX.useGlobalPreferredServer + ); + } + } catch (e) { + console.error(e); + } + + // processed successfully, now add to result object + if (!d.incoming.hostname) { + // first valid + d.incoming = iO; + } else { + d.incomingAlternatives.push(iO); + } + } catch (e) { + exception = e; + } + } + if (!d.incoming.hostname) { + // throw exception for last server + throw exception ? exception : "Need proper <incomingServer> in XML file"; + } + exception = null; + + // outgoing server + for (let oX of array_or_undef(xml.$outgoingServer)) { + // input (XML) + let oO = d.createNewOutgoing(); // output (object) + try { + if (oX["@type"] != "smtp") { + let stringBundle = lazy.AccountCreationUtils.getStringBundle( + "chrome://messenger/locale/accountCreationModel.properties" + ); + throw stringBundle.GetStringFromName("outgoing_not_smtp.error"); + } + oO.hostname = lazy.Sanitizer.hostname(oX.hostname); + oO.port = lazy.Sanitizer.integerRange(oX.port, 1, 65535); + + for (let oXsocketType of array_or_undef(oX.$socketType)) { + try { + oO.socketType = lazy.Sanitizer.translate(oXsocketType, { + plain: Ci.nsMsgSocketType.plain, + SSL: Ci.nsMsgSocketType.SSL, + STARTTLS: Ci.nsMsgSocketType.alwaysSTARTTLS, + }); + break; // take first that we support + } catch (e) { + exception = e; + } + } + if (oO.socketType == -1) { + throw exception ? exception : "need proper <socketType> in XML"; + } + exception = null; + + for (let oXauth of array_or_undef(oX.$authentication)) { + try { + oO.auth = lazy.Sanitizer.translate(oXauth, { + // open relay + none: Ci.nsMsgAuthMethod.none, + // inside ISP or corp network + "client-IP-address": Ci.nsMsgAuthMethod.none, + // hope for the best + "smtp-after-pop": Ci.nsMsgAuthMethod.none, + "password-cleartext": Ci.nsMsgAuthMethod.passwordCleartext, + // @deprecated TODO remove + plain: Ci.nsMsgAuthMethod.passwordCleartext, + "password-encrypted": Ci.nsMsgAuthMethod.passwordEncrypted, + // @deprecated TODO remove + secure: Ci.nsMsgAuthMethod.passwordEncrypted, + GSSAPI: Ci.nsMsgAuthMethod.GSSAPI, + NTLM: Ci.nsMsgAuthMethod.NTLM, + OAuth2: Ci.nsMsgAuthMethod.OAuth2, + }); + + break; // take first that we support + } catch (e) { + exception = e; + } + } + if (!oO.auth) { + throw exception ? exception : "need proper <authentication> in XML"; + } + exception = null; + + if ( + "username" in oX || + // if password-based auth, we need a username, + // so go there anyways and throw. + oO.auth == Ci.nsMsgAuthMethod.passwordCleartext || + oO.auth == Ci.nsMsgAuthMethod.passwordEncrypted + ) { + oO.username = lazy.Sanitizer.string(oX.username); + } + + if ("password" in oX) { + d.rememberPassword = true; + oO.password = lazy.Sanitizer.string(oX.password); + } + + try { + // defaults are in accountConfig.js + if ("addThisServer" in oX) { + oO.addThisServer = lazy.Sanitizer.boolean(oX.addThisServer); + } + if ("useGlobalPreferredServer" in oX) { + oO.useGlobalPreferredServer = lazy.Sanitizer.boolean( + oX.useGlobalPreferredServer + ); + } + } catch (e) { + console.error(e); + } + + // processed successfully, now add to result object + if (!d.outgoing.hostname) { + // first valid + d.outgoing = oO; + } else { + d.outgoingAlternatives.push(oO); + } + } catch (e) { + console.error(e); + exception = e; + } + } + if (!d.outgoing.hostname) { + // throw exception for last server + throw exception ? exception : "Need proper <outgoingServer> in XML file"; + } + exception = null; + + d.inputFields = []; + for (let inputField of array_or_undef(xml.$inputField)) { + try { + let fieldset = { + varname: lazy.Sanitizer.alphanumdash(inputField["@key"]).toUpperCase(), + displayName: lazy.Sanitizer.label(inputField["@label"]), + exampleValue: lazy.Sanitizer.label(inputField.value), + }; + d.inputFields.push(fieldset); + } catch (e) { + console.error(e); + // For now, don't throw, + // because we don't support custom fields yet anyways. + } + } + + return d; +} +/* eslint-enable complexity */ |