/* 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 * * * @param clientConfigXML {JXON} - The 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 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 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 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 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 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 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 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 */