summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/addrbook/modules/LDAPOperation.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mailnews/addrbook/modules/LDAPOperation.jsm')
-rw-r--r--comm/mailnews/addrbook/modules/LDAPOperation.jsm198
1 files changed, 198 insertions, 0 deletions
diff --git a/comm/mailnews/addrbook/modules/LDAPOperation.jsm b/comm/mailnews/addrbook/modules/LDAPOperation.jsm
new file mode 100644
index 0000000000..d0e2d64a54
--- /dev/null
+++ b/comm/mailnews/addrbook/modules/LDAPOperation.jsm
@@ -0,0 +1,198 @@
+/* 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 = ["LDAPOperation"];
+
+const lazy = {};
+
+ChromeUtils.defineModuleGetter(
+ lazy,
+ "LDAPClient",
+ "resource:///modules/LDAPClient.jsm"
+);
+
+/**
+ * A module to manage LDAP operation.
+ *
+ * @implements {nsILDAPOperation}
+ */
+class LDAPOperation {
+ QueryInterface = ChromeUtils.generateQI(["nsILDAPOperation"]);
+
+ init(connection, listener, closure) {
+ this._listener = listener;
+ this._connection = connection;
+ this._client = connection.wrappedJSObject.client;
+
+ this._referenceUrls = [];
+
+ // Cache request arguments to use when searching references.
+ this._simpleBindPassword = null;
+ this._saslBindArgs = null;
+ this._searchArgs = null;
+ }
+
+ simpleBind(password) {
+ this._password = password;
+ try {
+ this._messageId = this._client.bind(
+ this._connection.bindName,
+ password,
+ res => this._onBindSuccess(res.result.resultCode)
+ );
+ } catch (e) {
+ this._listener.onLDAPError(e.result, null, "");
+ }
+ }
+
+ saslBind(service, mechanism, authModuleType, serverCredentials) {
+ this._saslBindArgs = [service, mechanism, authModuleType];
+ try {
+ this._client.saslBind(
+ service,
+ mechanism,
+ authModuleType,
+ serverCredentials,
+ res => {
+ if (res.result.resultCode == Ci.nsILDAPErrors.SASL_BIND_IN_PROGRESS) {
+ this.saslBind(
+ service,
+ mechanism,
+ authModuleType,
+ res.result.serverSaslCreds
+ );
+ } else if (res.result.resultCode == Ci.nsILDAPErrors.SUCCESS) {
+ this._onBindSuccess(res.result.resultCode);
+ }
+ }
+ );
+ } catch (e) {
+ this._listener.onLDAPError(e.result, null, "");
+ }
+ }
+
+ searchExt(baseDN, scope, filter, attributes, timeout, limit) {
+ this._searchArgs = [baseDN, scope, filter, attributes, timeout, limit];
+ try {
+ this._messageId = this._client.search(
+ baseDN,
+ scope,
+ filter,
+ attributes,
+ timeout,
+ limit,
+ res => {
+ if (res.constructor.name == "SearchResultEntry") {
+ this._listener.onLDAPMessage({
+ QueryInterface: ChromeUtils.generateQI(["nsILDAPMessage"]),
+ errorCode: 0,
+ type: Ci.nsILDAPMessage.RES_SEARCH_ENTRY,
+ getAttributes() {
+ return Object.keys(res.result.attributes);
+ },
+ // Find the matching attribute name while ignoring the case.
+ _getAttribute(attr) {
+ attr = attr.toLowerCase();
+ return this.getAttributes().find(x => x.toLowerCase() == attr);
+ },
+ getValues(attr) {
+ attr = this._getAttribute(attr);
+ return res.result.attributes[attr]?.map(v =>
+ new TextDecoder().decode(v)
+ );
+ },
+ getBinaryValues(attr) {
+ attr = this._getAttribute(attr);
+ return res.result.attributes[attr]?.map(v => ({
+ // @see nsILDAPBERValue
+ get: () => new Uint8Array(v),
+ }));
+ },
+ });
+ } else if (res.constructor.name == "SearchResultReference") {
+ this._referenceUrls.push(...res.result);
+ } else if (res.constructor.name == "SearchResultDone") {
+ // NOTE: we create a new connection for every search, can be changed
+ // to reuse connections.
+ this._client.onError = () => {};
+ this._client.unbind();
+ this._messageId = null;
+ if (this._referenceUrls.length) {
+ this._searchReference(this._referenceUrls.shift());
+ } else {
+ this._listener.onLDAPMessage({
+ errorCode: res.result.resultCode,
+ type: Ci.nsILDAPMessage.RES_SEARCH_RESULT,
+ });
+ }
+ }
+ }
+ );
+ } catch (e) {
+ this._listener.onLDAPError(e.result, null, "");
+ }
+ }
+
+ abandonExt() {
+ if (this._messageId) {
+ this._client.abandon(this._messageId);
+ }
+ }
+
+ /**
+ * Decide what to do on bind success. When searching a reference url, trigger
+ * a new search. Otherwise, emit a message to this._listener.
+ *
+ * @param {number} errorCode - The result code of BindResponse.
+ */
+ _onBindSuccess(errorCode) {
+ if (this._searchingReference) {
+ this.searchExt(...this._searchArgs);
+ } else {
+ this._listener.onLDAPMessage({
+ errorCode,
+ type: Ci.nsILDAPMessage.RES_BIND,
+ });
+ }
+ }
+
+ /**
+ * Connect to a reference url and continue the search.
+ *
+ * @param {string} urlStr - A url string we get from SearchResultReference.
+ */
+ _searchReference(urlStr) {
+ this._searchingReference = true;
+ let urlParser = Cc["@mozilla.org/network/ldap-url-parser;1"].createInstance(
+ Ci.nsILDAPURLParser
+ );
+ let url;
+ try {
+ url = urlParser.parse(urlStr);
+ } catch (e) {
+ console.error(e);
+ return;
+ }
+ this._client = new lazy.LDAPClient(
+ url.host,
+ url.port,
+ url.options & Ci.nsILDAPURL.OPT_SECURE
+ );
+ this._client.onOpen = () => {
+ if (this._password) {
+ this.simpleBind(this._password);
+ } else {
+ this.saslBind(...this._saslBindData);
+ }
+ };
+ this._client.onError = (status, secInfo) => {
+ this._listener.onLDAPError(status, secInfo, `${url.host}:${url.port}`);
+ };
+ this._client.connect();
+ }
+}
+
+LDAPOperation.prototype.classID = Components.ID(
+ "{a6f94ca4-cd2d-4983-bcf2-fe936190955c}"
+);