Bug 1710023 - Init LDAPClient.jsm and LDAPMessage.jsm. r=darktrojan
authorPing Chen <remotenonsense@gmail.com>
Tue, 01 Jun 2021 01:29:59 +0000
changeset 42667 3c6d03ec8bc6db2446bd2392cbbae0a6f0de18ec
parent 42666 cb49d22d17426cca2c51d5e29790eee89f573136
child 42668 47d61cac48976ca89fda320590700fcdb531123b
push id3180
push usertbbld-merge
push dateMon, 12 Jul 2021 18:31:42 +0000
treeherdercomm-beta@1024b46dbd5e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdarktrojan
bugs1710023
Bug 1710023 - Init LDAPClient.jsm and LDAPMessage.jsm. r=darktrojan Supports sending a simple bind request and parsing the bind response. Differential Revision: https://phabricator.services.mozilla.com/D115866
ldap/modules/LDAPClient.jsm
ldap/modules/LDAPMessage.jsm
ldap/modules/moz.build
ldap/moz.build
mailnews/mailnews.js
new file mode 100644
--- /dev/null
+++ b/ldap/modules/LDAPClient.jsm
@@ -0,0 +1,78 @@
+/* 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 = ["LDAPClient"];
+var { BindRequest, LDAPResponse } = ChromeUtils.import(
+  "resource:///modules/LDAPMessage.jsm"
+);
+
+class LDAPClient {
+  constructor(url, port) {
+    this._socket = new TCPSocket(url, port, {
+      binaryType: "arraybuffer",
+    });
+    this._socket.onopen = this._onOpen;
+    this._socket.onerror = this._onError;
+
+    this._messageId = 1;
+
+    this._logger = console.createInstance({
+      prefix: "mailnews.ldap",
+      maxLogLevel: "Warn",
+      maxLogLevelPref: "mailnews.ldap.loglevel",
+    });
+  }
+
+  /**
+   * Send a bind request to the server.
+   * @param {string} dn - The name to bind.
+   * @param {string} password - The password.
+   */
+  bind(dn, password) {
+    this._logger.debug(`Binding ${dn}`);
+    let req = new BindRequest(dn, password);
+    this._send(req);
+  }
+
+  /**
+   * The open event handler.
+   */
+  _onOpen = () => {
+    this._logger.debug("Connected");
+    this._socket.ondata = this._onData;
+    this._socket.onclose = this._onClose;
+  };
+
+  /**
+   * The data event handler.
+   * @param {TCPSocketEvent} event - The data event.
+   */
+  _onData = event => {
+    let res = LDAPResponse.fromBER(event.data);
+    this._logger.debug(res.constructor.name, res);
+  };
+
+  /**
+   * The close event handler.
+   */
+  _onClose = () => {
+    this._logger.debug("Connection closed");
+  };
+
+  /**
+   * The error event handler.
+   * @param {TCPSocketErrorEvent} event - The error event.
+   */
+  _onError = event => {
+    this._logger.error(event);
+  };
+
+  /**
+   * Send a message to the server.
+   * @param {LDAPMessage} msg - The message to send.
+   */
+  _send(msg) {
+    this._socket.send(msg.toBER(this._messageId++));
+  }
+}
new file mode 100644
--- /dev/null
+++ b/ldap/modules/LDAPMessage.jsm
@@ -0,0 +1,164 @@
+/* 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 = ["BindRequest", "LDAPResponse"];
+
+var {
+  asn1js: { asn1js },
+} = ChromeUtils.import("chrome://global/content/certviewer/asn1js_bundle.js");
+
+/**
+ * A base class for all LDAP request and response messages, see
+ * rfc4511#section-4.1.1.
+ *
+ * @property {number} messageId - The message id.
+ * @property {LocalBaseBlock} protocolOp - The message content, in a data
+ *   structure provided by asn1js.
+ */
+class LDAPMessage {
+  /**
+   * Encode the current message by Basic Encoding Rules (BER).
+   * @param {number} messageId - The id of the current message.
+   * @returns {ArrayBuffer} BER encoded message.
+   */
+  toBER(messageId = this.messageId) {
+    let msg = new asn1js.Sequence({
+      value: [new asn1js.Integer({ value: messageId }), this.protocolOp],
+    });
+    return msg.toBER();
+  }
+}
+
+class BindRequest extends LDAPMessage {
+  /**
+   * @param {string} dn - The name to bind.
+   * @param {string} password - The password.
+   */
+  constructor(dn, password) {
+    super();
+    this.protocolOp = new asn1js.Constructed({
+      // [APPLICATION 0]
+      idBlock: {
+        tagClass: 2,
+        tagNumber: 0,
+      },
+      value: [
+        // version
+        new asn1js.Integer({ value: 3 }),
+        // name
+        new asn1js.OctetString({
+          valueHex: new TextEncoder().encode(dn),
+        }),
+        // authentication
+        new asn1js.Primitive({
+          // Context-specific [0]
+          idBlock: {
+            tagClass: 3,
+            tagNumber: 0,
+          },
+          valueHex: new TextEncoder().encode(password),
+        }),
+      ],
+    });
+  }
+}
+
+class LDAPResult {
+  /**
+   * @param {number} resultCode - The result code.
+   * @param {string} matchedDN - For certain result codes, matchedDN is the last entry used.
+   * @param {string} diagnosticMessage - A diagnostic message returned by the server.
+   */
+  constructor(resultCode, matchedDN, diagnosticMessage) {
+    this.resultCode = resultCode;
+    this.matchedDN = matchedDN;
+    this.diagnosticMessage = diagnosticMessage;
+  }
+}
+
+/**
+ * A base class for all LDAP response messages.
+ *
+ * @property {LDAPResult} result - The result of a response.
+ */
+class LDAPResponse extends LDAPMessage {
+  /**
+   * @param {number} messageId - The message id.
+   * @param {LocalBaseBlock} protocolOp - The message content.
+   */
+  constructor(messageId, protocolOp) {
+    super();
+    this.messageId = messageId;
+    this.protocolOp = protocolOp;
+  }
+
+  /**
+   * Find the corresponding response class name from a tag number.
+   * @param {number} tagNumber - The tag number of a block.
+   * @returns {LDAPResponse}
+   */
+  static _getResponseClassFromTagNumber(tagNumber) {
+    return {
+      1: BindResponse,
+      4: SearchResultEntry,
+      5: SearchResultDone,
+      19: SearchResultReference,
+      24: ExtendedResponse,
+    }[tagNumber];
+  }
+
+  /**
+   * Decode a raw server response to LDAPResponse instance.
+   * @param {ArrayBuffer} buffer - The raw message received from the server.
+   * @returns {LDAPResponse} A concrete instance of LDAPResponse subclass.
+   */
+  static fromBER(buffer) {
+    let decoded = asn1js.fromBER(buffer);
+    let value = decoded.result.valueBlock.value;
+    let protocolOp = value[1];
+    if (protocolOp.idBlock.tagClass != 2) {
+      throw Components.Exception(
+        `Unexpected tagClass ${protocolOp.idBlock.tagClass}`,
+        Cr.NS_ERROR_ILLEGAL_VALUE
+      );
+    }
+    let ProtocolOp = this._getResponseClassFromTagNumber(
+      protocolOp.idBlock.tagNumber
+    );
+    if (!ProtocolOp) {
+      throw Components.Exception(
+        `Unexpected tagNumber ${protocolOp.idBlock.tagNumber}`,
+        Cr.NS_ERROR_ILLEGAL_VALUE
+      );
+    }
+    let op = new ProtocolOp(value[0].valueBlock.valueDec, protocolOp);
+    op.parse();
+    return op;
+  }
+
+  /**
+   * Parse the protocolOp part of a LDAPMessage to LDAPResult. For LDAP
+   * responses that are simply LDAPResult, reuse this function. Other responses
+   * need to implement this function.
+   */
+  parse() {
+    let value = this.protocolOp.valueBlock.value;
+    let resultCode = value[0].valueBlock.valueDec;
+    let matchedDN = new TextDecoder().decode(value[1].valueBlock.valueHex);
+    let diagnosticMessage = new TextDecoder().decode(
+      value[2].valueBlock.valueHex
+    );
+    this.result = new LDAPResult(resultCode, matchedDN, diagnosticMessage);
+  }
+}
+
+class BindResponse extends LDAPResponse {}
+
+class SearchResultEntry extends LDAPResponse {}
+
+class SearchResultDone extends LDAPResponse {}
+
+class SearchResultReference extends LDAPResponse {}
+
+class ExtendedResponse extends LDAPResponse {}
new file mode 100644
--- /dev/null
+++ b/ldap/modules/moz.build
@@ -0,0 +1,9 @@
+# vim: set filetype=python:
+# 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/.
+
+EXTRA_JS_MODULES += [
+    "LDAPClient.jsm",
+    "LDAPMessage.jsm",
+]
--- a/ldap/moz.build
+++ b/ldap/moz.build
@@ -1,14 +1,17 @@
 # vim: set filetype=python:
 # 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/.
 
 Library("ldapsdks")
 
-DIRS += ["c-sdk/libraries"]
+DIRS += [
+    "c-sdk/libraries",
+    "modules",
+]
 
 USE_LIBS += [
     "ldap60",
     "ldif60",
     "prldap60",
 ]
--- a/mailnews/mailnews.js
+++ b/mailnews/mailnews.js
@@ -409,16 +409,17 @@ pref("ldap_2.servers.default.attrmap.Pre
 pref("ldap_2.servers.default.attrmap.LastModifiedDate", "modifytimestamp");
 
 pref("ldap_2.user_id", 0);
 // Update kCurrentListVersion in include/dirprefs.h if you change this
 pref("ldap_2.version", 3);
 
 // If true, LDAPDirectory.jsm is used. Otherwise, nsAbLDAPDirectory.cpp is used.
 pref("mailnews.ldap.jsmodule", false);
+pref("mailnews.ldap.loglevel", "Warn");
 
 pref("mailnews.confirm.moveFoldersToTrash", true);
 
 // space-delimited list of extra headers to add to .msf file
 pref("mailnews.customDBHeaders", "");
 
 // close standalone message window when deleting the displayed message
 pref("mail.close_message_window.on_delete", false);