/* 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/. */ let USER_CERT_ATTRIBUTE = "usercertificate;binary"; let gEmailAddresses; let gDirectoryPref; let gLdapServerURL; let gLdapConnection; let gCertDB; let gLdapOperation; let gLogin; window.addEventListener("DOMContentLoaded", onLoad); document.addEventListener("dialogcancel", stopFetching); /** * Expects the following arguments: * - pref name of LDAP directory to fetch from * - array with email addresses * * Display modal dialog with message and stop button. * In onload, kick off binding to LDAP. * When bound, kick off the searches. * On finding certificates, import into permanent cert database. * When all searches are finished, close the dialog. */ function onLoad() { gDirectoryPref = window.arguments[0]; gEmailAddresses = window.arguments[1]; if (!gEmailAddresses.length) { window.close(); return; } setTimeout(search); } function search() { // Get the login to authenticate as, if there is one. No big deal if we don't // have one. gLogin = Services.prefs.getStringPref(gDirectoryPref + ".auth.dn", undefined); try { let url = Services.prefs.getCharPref(gDirectoryPref + ".uri"); gLdapServerURL = Services.io.newURI(url).QueryInterface(Ci.nsILDAPURL); gLdapConnection = Cc["@mozilla.org/network/ldap-connection;1"] .createInstance() .QueryInterface(Ci.nsILDAPConnection); gLdapConnection.init( gLdapServerURL, gLogin, new BindListener(), null, Ci.nsILDAPConnection.VERSION3 ); } catch (ex) { console.error(ex); window.close(); } } function stopFetching() { if (gLdapOperation) { try { gLdapOperation.abandon(); } catch (e) {} } } function importCert(ber_value) { if (!gCertDB) { gCertDB = Cc["@mozilla.org/security/x509certdb;1"].getService( Ci.nsIX509CertDB ); } // ber_value has type nsILDAPBERValue let cert_bytes = ber_value.get(); if (cert_bytes) { gCertDB.importEmailCertificate(cert_bytes, cert_bytes.length, null); } } function getLDAPOperation() { gLdapOperation = Cc["@mozilla.org/network/ldap-operation;1"].createInstance( Ci.nsILDAPOperation ); gLdapOperation.init(gLdapConnection, new LDAPMessageListener(), null); } async function getPassword() { // we only need a password if we are using credentials if (!gLogin) { return null; } let authPrompter = Services.ww.getNewAuthPrompter(window); let strBundle = document.getElementById("bundle_ldap"); let password = { value: "" }; // nsLDAPAutocompleteSession uses asciiHost instead of host for the prompt // text, I think we should be consistent. if ( await authPrompter.asyncPromptPassword( strBundle.getString("authPromptTitle"), strBundle.getFormattedString("authPromptText", [ gLdapServerURL.asciiHost, ]), gLdapServerURL.spec, authPrompter.SAVE_PASSWORD_PERMANENTLY, password ) ) { return password.value; } return null; } /** * Checks if the LDAP connection can be bound. * @implements {nsILDAPMessageListener} */ class BindListener { QueryInterface = ChromeUtils.generateQI(["nsILDAPMessageListener"]); async onLDAPInit(conn, status) { // Kick off bind. getLDAPOperation(); gLdapOperation.simpleBind(await getPassword()); } onLDAPMessage(message) {} onLDAPError(status, secInfo, location) { if (secInfo) { console.warn(`LDAP bind connection security error for ${location}`); } else { console.warn(`LDAP bind error: ${status}`); } window.close(); } } /** * LDAPMessageListener. * @implements {nsILDAPMessageListener} */ class LDAPMessageListener { QueryInterface = ChromeUtils.generateQI(["nsILDAPMessageListener"]); onLDAPInit(conn, status) {} onLDAPMessage(message) { if (Ci.nsILDAPMessage.RES_SEARCH_RESULT == message.type) { window.close(); return; } if (Ci.nsILDAPMessage.RES_BIND == message.type) { if (Ci.nsILDAPErrors.SUCCESS != message.errorCode) { window.close(); return; } // Kick off search. let prefix1 = ""; let suffix1 = ""; let urlFilter = gLdapServerURL.filter; if ( urlFilter != null && urlFilter.length > 0 && urlFilter != "(objectclass=*)" ) { if (urlFilter.startsWith("(")) { prefix1 = "(&" + urlFilter; } else { prefix1 = "(&(" + urlFilter + ")"; } suffix1 = ")"; } let prefix2 = ""; let suffix2 = ""; if (gEmailAddresses.length > 1) { prefix2 = "(|"; suffix2 = ")"; } let mailFilter = ""; for (let email of gEmailAddresses) { mailFilter += "(mail=" + email + ")"; } let filter = prefix1 + prefix2 + mailFilter + suffix2 + suffix1; // Max search results => // Double number of email addresses, because each person might have // multiple certificates listed. We expect at most two certificates, // one for signing, one for encrypting. // Maybe that number should be larger, to allow for deployments, // where even more certs can be stored per user??? let maxEntriesWanted = gEmailAddresses.length * 2; getLDAPOperation(); gLdapOperation.searchExt( gLdapServerURL.dn, gLdapServerURL.scope, filter, USER_CERT_ATTRIBUTE, 0, maxEntriesWanted ); return; } if (Ci.nsILDAPMessage.RES_SEARCH_ENTRY == message.type) { let outBinValues = null; try { // This call may throw if the result message is empty or doesn't // contain this attribute. // It's an allowed condition that the attribute is missing on // the server, so we silently ignore a failure to obtain it. outBinValues = message.getBinaryValues(USER_CERT_ATTRIBUTE); } catch (ex) {} if (outBinValues) { for (let i = 0; i < outBinValues.length; ++i) { importCert(outBinValues[i]); } } } } /** * @param {nsresult} status * @param {?nsITransportSecurityInfo} secInfo * @param {?string} location */ onLDAPError(status, secInfo, location) { if (secInfo) { console.warn(`LDAP connection security error for ${location}`); } else { console.warn(`LDAP error: ${status}`); } window.close(); } }