summaryrefslogtreecommitdiffstats
path: root/comm/chat/protocols/xmpp/xmpp-session.sys.mjs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /comm/chat/protocols/xmpp/xmpp-session.sys.mjs
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'comm/chat/protocols/xmpp/xmpp-session.sys.mjs')
-rw-r--r--comm/chat/protocols/xmpp/xmpp-session.sys.mjs764
1 files changed, 764 insertions, 0 deletions
diff --git a/comm/chat/protocols/xmpp/xmpp-session.sys.mjs b/comm/chat/protocols/xmpp/xmpp-session.sys.mjs
new file mode 100644
index 0000000000..ca2fd4eebb
--- /dev/null
+++ b/comm/chat/protocols/xmpp/xmpp-session.sys.mjs
@@ -0,0 +1,764 @@
+/* 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 { DNS } = ChromeUtils.import("resource:///modules/DNS.jsm");
+import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
+import { l10nHelper } from "resource:///modules/imXPCOMUtils.sys.mjs";
+import { Socket } from "resource:///modules/socket.sys.mjs";
+import { Stanza, XMPPParser } from "resource:///modules/xmpp-xml.sys.mjs";
+import { XMPPAuthMechanisms } from "resource:///modules/xmpp-authmechs.sys.mjs";
+
+const lazy = {};
+
+XPCOMUtils.defineLazyGetter(lazy, "_", () =>
+ l10nHelper("chrome://chat/locale/xmpp.properties")
+);
+
+export function XMPPSession(
+ aHost,
+ aPort,
+ aSecurity,
+ aJID,
+ aPassword,
+ aAccount
+) {
+ this._host = aHost;
+ this._port = aPort;
+
+ this._connectionSecurity = aSecurity;
+ if (this._connectionSecurity == "old_ssl") {
+ this._security = ["ssl"];
+ } else if (this._connectionSecurity != "none") {
+ this._security = [aPort == 5223 || aPort == 443 ? "ssl" : "starttls"];
+ }
+
+ if (!aJID.node) {
+ aAccount.reportDisconnecting(
+ Ci.prplIAccount.ERROR_INVALID_USERNAME,
+ lazy._("connection.error.invalidUsername")
+ );
+ aAccount.reportDisconnected();
+ return;
+ }
+ this._jid = aJID;
+ this._domain = aJID.domain;
+ this._password = aPassword;
+ this._account = aAccount;
+ this._resource = aJID.resource;
+ this._handlers = new Map();
+ this._account.reportConnecting();
+
+ // The User has specified a certain server or port, so we should not do
+ // DNS SRV lookup or the preference of disabling DNS SRV part and use
+ // normal connect is set.
+ // RFC 6120 (Section 3.2.3): When Not to Use SRV.
+ if (
+ Services.prefs.getBoolPref("chat.dns.srv.disable") ||
+ this._account.prefs.prefHasUserValue("server") ||
+ this._account.prefs.prefHasUserValue("port")
+ ) {
+ this.connect(this._host, this._port, this._security);
+ return;
+ }
+
+ // RFC 6120 (Section 3.2.1): SRV lookup.
+ this._account.reportConnecting(lazy._("connection.srvLookup"));
+ DNS.srv("_xmpp-client._tcp." + this._host)
+ .then(aResult => this._handleSrvQuery(aResult))
+ .catch(aError => {
+ if (aError === this.SRV_ERROR_XMPP_NOT_SUPPORTED) {
+ this.LOG("SRV: XMPP is not supported on this domain.");
+
+ // RFC 6120 (Section 3.2.1) and RFC 2782 (Usage rules): Abort as the
+ // service is decidedly not available at this domain.
+ this._account.reportDisconnecting(
+ Ci.prplIAccount.ERROR_OTHER_ERROR,
+ lazy._("connection.error.XMPPNotSupported")
+ );
+ this._account.reportDisconnected();
+ return;
+ }
+
+ this.ERROR("Error during SRV lookup:", aError);
+
+ // Since we don't receive a response to SRV query, we SHOULD attempt the
+ // fallback process (use normal connect without SRV lookup).
+ this.connect(this._host, this._port, this._security);
+ });
+}
+
+XMPPSession.prototype = {
+ /* for the socket.jsm helper */
+ __proto__: Socket,
+ connectTimeout: 60,
+ readWriteTimeout: 300,
+
+ // Contains the remaining SRV records if we failed to connect the current one.
+ _srvRecords: [],
+
+ sendPing() {
+ this.sendStanza(
+ Stanza.iq("get", null, null, Stanza.node("ping", Stanza.NS.ping)),
+ this.cancelDisconnectTimer,
+ this
+ );
+ },
+ _lastReceiveTime: 0,
+ _lastSendTime: 0,
+ checkPingTimer(aJustSentSomething = false) {
+ // Don't start a ping timer if we're not fully connected yet.
+ if (this.onXmppStanza != this.stanzaListeners.accountListening) {
+ return;
+ }
+ let now = Date.now();
+ if (aJustSentSomething) {
+ this._lastSendTime = now;
+ } else {
+ this._lastReceiveTime = now;
+ }
+ // We only cancel the ping timer if we've both received and sent
+ // something in the last two minutes. This is because Openfire
+ // servers will disconnect us if we don't send anything for a
+ // couple of minutes.
+ if (
+ Math.min(this._lastSendTime, this._lastReceiveTime) >
+ now - this.kTimeBeforePing
+ ) {
+ this.resetPingTimer();
+ }
+ },
+
+ get DEBUG() {
+ return this._account.DEBUG;
+ },
+ get LOG() {
+ return this._account.LOG;
+ },
+ get WARN() {
+ return this._account.WARN;
+ },
+ get ERROR() {
+ return this._account.ERROR;
+ },
+
+ _security: null,
+ _encrypted: false,
+
+ // DNS SRV errors in XMPP.
+ SRV_ERROR_XMPP_NOT_SUPPORTED: -2,
+
+ // Handles result of DNS SRV query and saves sorted results if it's OK in _srvRecords,
+ // otherwise throws error.
+ _handleSrvQuery(aResult) {
+ this.LOG("SRV lookup: " + JSON.stringify(aResult));
+ if (aResult.length == 0) {
+ // RFC 6120 (Section 3.2.1) and RFC 2782 (Usage rules): No SRV records,
+ // try to login with the given domain name.
+ this.connect(this._host, this._port, this._security);
+ return;
+ } else if (aResult.length == 1 && aResult[0].host == ".") {
+ throw this.SRV_ERROR_XMPP_NOT_SUPPORTED;
+ }
+
+ // Sort results: Lower priority is more preferred and higher weight is
+ // more preferred in equal priorities.
+ aResult.sort(function (a, b) {
+ return a.prio - b.prio || b.weight - a.weight;
+ });
+
+ this._srvRecords = aResult;
+ this._connectNextRecord();
+ },
+
+ _connectNextRecord() {
+ if (!this._srvRecords.length) {
+ this.ERROR(
+ "_connectNextRecord is called and there are no more records " +
+ "to connect."
+ );
+ return;
+ }
+
+ let record = this._srvRecords.shift();
+
+ // RFC 3920 (Section 5.1): Certificates MUST be checked against the
+ // hostname as provided by the initiating entity (e.g. user).
+ this.connect(
+ this._domain,
+ this._port,
+ this._security,
+ null,
+ record.host,
+ record.port
+ );
+ },
+
+ /* Disconnect from the server */
+ disconnect() {
+ if (this.onXmppStanza == this.stanzaListeners.accountListening) {
+ this.send("</stream:stream>");
+ }
+ delete this.onXmppStanza;
+ Socket.disconnect.call(this);
+ if (this._parser) {
+ this._parser.destroy();
+ delete this._parser;
+ }
+ this.cancelDisconnectTimer();
+ },
+
+ /* Report errors to the account */
+ onError(aError, aException) {
+ // If we're trying to connect to SRV entries, then keep trying until a
+ // successful connection occurs or we run out of SRV entries to try.
+ if (this._srvRecords.length) {
+ this._connectNextRecord();
+ return;
+ }
+
+ this._account.onError(aError, aException);
+ },
+
+ /* Send a text message to the server */
+ send(aMsg, aLogString) {
+ this.sendString(aMsg, "UTF-8", aLogString);
+ },
+
+ /* Send a stanza to the server.
+ * Can set a callback if required, which will be called when the server
+ * responds to the stanza with a stanza of the same id. The callback should
+ * return true if the stanza was handled, false if not. Note that an
+ * undefined return value is treated as true.
+ */
+ sendStanza(aStanza, aCallback, aThis, aLogString) {
+ if (!aStanza.attributes.hasOwnProperty("id")) {
+ aStanza.attributes.id = this._account.generateId();
+ }
+ if (aCallback) {
+ this._handlers.set(aStanza.attributes.id, aCallback.bind(aThis));
+ }
+ this.send(aStanza.getXML(), aLogString);
+ this.checkPingTimer(true);
+ return aStanza.attributes.id;
+ },
+
+ /* This method handles callbacks for specific ids. */
+ execHandler(aId, aStanza) {
+ let handler = this._handlers.get(aId);
+ if (!handler) {
+ return false;
+ }
+ let isHandled = handler(aStanza);
+ // Treat undefined return values as handled.
+ if (isHandled === undefined) {
+ isHandled = true;
+ }
+ this._handlers.delete(aId);
+ return isHandled;
+ },
+
+ /* Start the XMPP stream */
+ startStream() {
+ if (this._parser) {
+ this._parser.destroy();
+ }
+ this._parser = new XMPPParser(this);
+ this.send(
+ '<?xml version="1.0"?><stream:stream to="' +
+ this._domain +
+ '" xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams" version="1.0">'
+ );
+ },
+
+ startSession() {
+ this.sendStanza(
+ Stanza.iq("set", null, null, Stanza.node("session", Stanza.NS.session)),
+ aStanza => aStanza.attributes.type == "result"
+ );
+ this.onXmppStanza = this.stanzaListeners.sessionStarted;
+ },
+
+ /* XEP-0078: Non-SASL Authentication */
+ startLegacyAuth() {
+ if (!this._encrypted && this._connectionSecurity == "require_tls") {
+ this.onError(
+ Ci.prplIAccount.ERROR_ENCRYPTION_ERROR,
+ lazy._("connection.error.startTLSNotSupported")
+ );
+ return;
+ }
+
+ this.onXmppStanza = this.stanzaListeners.legacyAuth;
+ let s = Stanza.iq(
+ "get",
+ null,
+ this._domain,
+ Stanza.node(
+ "query",
+ Stanza.NS.auth,
+ null,
+ Stanza.node("username", null, null, this._jid.node)
+ )
+ );
+ this.sendStanza(s);
+ },
+
+ // If aResource is null, it will request to bind a server-generated
+ // resourcepart, otherwise request to bind a client-submitted resourcepart.
+ _requestBind(aResource) {
+ let resourceNode = aResource
+ ? Stanza.node("resource", null, null, aResource)
+ : null;
+ this.sendStanza(
+ Stanza.iq(
+ "set",
+ null,
+ null,
+ Stanza.node("bind", Stanza.NS.bind, null, resourceNode)
+ )
+ );
+ },
+
+ /* Socket events */
+ /* The connection is established */
+ onConnection() {
+ if (this._security.includes("ssl")) {
+ this.onXmppStanza = this.stanzaListeners.startAuth;
+ this._encrypted = true;
+ } else {
+ this.onXmppStanza = this.stanzaListeners.initStream;
+ }
+
+ // Clear SRV results since we have connected.
+ this._srvRecords = [];
+
+ this._account.reportConnecting(lazy._("connection.initializingStream"));
+ this.startStream();
+ },
+
+ /* When incoming data is available to be parsed */
+ onDataReceived(aData) {
+ this.checkPingTimer();
+ this._lastReceivedData = aData;
+ try {
+ this._parser.onDataAvailable(aData);
+ } catch (e) {
+ console.error(e);
+ this.onXMLError("parser-exception", e);
+ }
+ delete this._lastReceivedData;
+ },
+
+ /* The connection got disconnected without us closing it. */
+ onConnectionClosed() {
+ this._networkError(lazy._("connection.error.serverClosedConnection"));
+ },
+ onConnectionSecurityError(aTLSError, aNSSErrorMessage) {
+ let error = this._account.handleConnectionSecurityError(this);
+ this.onError(error, aNSSErrorMessage);
+ },
+ onConnectionReset() {
+ this._networkError(lazy._("connection.error.resetByPeer"));
+ },
+ onConnectionTimedOut() {
+ this._networkError(lazy._("connection.error.timedOut"));
+ },
+ _networkError(aMessage) {
+ this.onError(Ci.prplIAccount.ERROR_NETWORK_ERROR, aMessage);
+ },
+
+ /* Methods called by the XMPPParser instance */
+ onXMLError(aError, aException) {
+ if (aError == "parsing-characters") {
+ this.WARN(aError + ": " + aException + "\n" + this._lastReceivedData);
+ } else {
+ this.ERROR(aError + ": " + aException + "\n" + this._lastReceivedData);
+ }
+ if (aError != "parse-warning" && aError != "parsing-characters") {
+ this._networkError(lazy._("connection.error.receivedUnexpectedData"));
+ }
+ },
+
+ // All the functions in stanzaListeners are used as onXmppStanza
+ // implementations at various steps of establishing the session.
+ stanzaListeners: {
+ initStream(aStanza) {
+ if (aStanza.localName != "features") {
+ this.ERROR(
+ "Unexpected stanza " + aStanza.localName + ", expected 'features'"
+ );
+ this._networkError(lazy._("connection.error.incorrectResponse"));
+ return;
+ }
+
+ let starttls = aStanza.getElement(["starttls"]);
+ if (starttls && this._security.includes("starttls")) {
+ this._account.reportConnecting(
+ lazy._("connection.initializingEncryption")
+ );
+ this.sendStanza(Stanza.node("starttls", Stanza.NS.tls));
+ this.onXmppStanza = this.stanzaListeners.startTLS;
+ return;
+ }
+ if (starttls && starttls.children.some(c => c.localName == "required")) {
+ this.onError(
+ Ci.prplIAccount.ERROR_ENCRYPTION_ERROR,
+ lazy._("connection.error.startTLSRequired")
+ );
+ return;
+ }
+ if (!starttls && this._connectionSecurity == "require_tls") {
+ this.onError(
+ Ci.prplIAccount.ERROR_ENCRYPTION_ERROR,
+ lazy._("connection.error.startTLSNotSupported")
+ );
+ return;
+ }
+
+ // If we aren't starting TLS, jump to the auth step.
+ this.onXmppStanza = this.stanzaListeners.startAuth;
+ this.onXmppStanza(aStanza);
+ },
+ startTLS(aStanza) {
+ if (aStanza.localName != "proceed") {
+ this._networkError(lazy._("connection.error.failedToStartTLS"));
+ return;
+ }
+
+ this.startTLS();
+ this._encrypted = true;
+ this.startStream();
+ this.onXmppStanza = this.stanzaListeners.startAuth;
+ },
+ startAuth(aStanza) {
+ if (aStanza.localName != "features") {
+ this.ERROR(
+ "Unexpected stanza " + aStanza.localName + ", expected 'features'"
+ );
+ this._networkError(lazy._("connection.error.incorrectResponse"));
+ return;
+ }
+
+ let mechs = aStanza.getElement(["mechanisms"]);
+ if (!mechs) {
+ let auth = aStanza.getElement(["auth"]);
+ if (auth && auth.uri == Stanza.NS.auth_feature) {
+ this.startLegacyAuth();
+ } else {
+ this._networkError(lazy._("connection.error.noAuthMec"));
+ }
+ return;
+ }
+
+ // Select the auth mechanism we will use. PLAIN will be treated
+ // a bit differently as we want to avoid it over an unencrypted
+ // connection, except if the user has explicitly allowed that
+ // behavior.
+ let authMechanisms = this._account.authMechanisms || XMPPAuthMechanisms;
+ let selectedMech = "";
+ let canUsePlain = false;
+ mechs = mechs.getChildren("mechanism");
+ for (let m of mechs) {
+ let mech = m.innerText;
+ if (mech == "PLAIN" && !this._encrypted) {
+ // If PLAIN is proposed over an unencrypted connection,
+ // remember that it's a possibility but don't bother
+ // checking if the user allowed it until we have verified
+ // that nothing more secure is available.
+ canUsePlain = true;
+ } else if (authMechanisms.hasOwnProperty(mech)) {
+ selectedMech = mech;
+ break;
+ }
+ }
+ if (!selectedMech && canUsePlain) {
+ if (this._connectionSecurity == "allow_unencrypted_plain_auth") {
+ selectedMech = "PLAIN";
+ } else {
+ this.onError(
+ Ci.prplIAccount.ERROR_AUTHENTICATION_IMPOSSIBLE,
+ lazy._("connection.error.notSendingPasswordInClear")
+ );
+ return;
+ }
+ }
+ if (!selectedMech) {
+ this.onError(
+ Ci.prplIAccount.ERROR_AUTHENTICATION_IMPOSSIBLE,
+ lazy._("connection.error.noCompatibleAuthMec")
+ );
+ return;
+ }
+ let authMec = authMechanisms[selectedMech](
+ this._jid.node,
+ this._password,
+ this._domain
+ );
+ this._password = null;
+
+ this._account.reportConnecting(lazy._("connection.authenticating"));
+ this.onXmppStanza = this.stanzaListeners.authDialog.bind(this, authMec);
+ this.onXmppStanza(null); // the first auth step doesn't read anything
+ },
+ authDialog(aAuthMec, aStanza) {
+ if (aStanza && aStanza.localName == "failure") {
+ let errorMsg = "authenticationFailure";
+ if (
+ aStanza.getElement(["not-authorized"]) ||
+ aStanza.getElement(["bad-auth"])
+ ) {
+ errorMsg = "notAuthorized";
+ }
+ this.onError(
+ Ci.prplIAccount.ERROR_AUTHENTICATION_FAILED,
+ lazy._("connection.error." + errorMsg)
+ );
+ return;
+ }
+
+ let result;
+ try {
+ result = aAuthMec.next(aStanza);
+ } catch (e) {
+ this.ERROR("Error in auth mechanism: " + e);
+ this.onError(
+ Ci.prplIAccount.ERROR_AUTHENTICATION_FAILED,
+ lazy._("connection.error.authenticationFailure")
+ );
+ return;
+ }
+
+ // The authentication mechanism can yield a promise which must resolve
+ // before sending data. If it rejects, abort.
+ if (result.value) {
+ Promise.resolve(result.value).then(
+ value => {
+ // Send the XML stanza that is returned.
+ if (value.send) {
+ this.send(value.send.getXML(), value.log);
+ }
+ },
+ e => {
+ this.ERROR("Error resolving auth mechanism result: " + e);
+ this.onError(
+ Ci.prplIAccount.ERROR_AUTHENTICATION_FAILED,
+ lazy._("connection.error.authenticationFailure")
+ );
+ }
+ );
+ }
+ if (result.done) {
+ this.startStream();
+ this.onXmppStanza = this.stanzaListeners.startBind;
+ }
+ },
+ startBind(aStanza) {
+ if (!aStanza.getElement(["bind"])) {
+ this.ERROR("Unexpected lack of the bind feature");
+ this._networkError(lazy._("connection.error.incorrectResponse"));
+ return;
+ }
+
+ this._account.reportConnecting(lazy._("connection.gettingResource"));
+ this._requestBind(this._resource);
+ this.onXmppStanza = this.stanzaListeners.bindResult;
+ },
+ bindResult(aStanza) {
+ if (aStanza.attributes.type == "error") {
+ let error = this._account.parseError(aStanza);
+ let message;
+ switch (error.condition) {
+ case "resource-constraint":
+ // RFC 6120 (7.6.2.1): Resource Constraint.
+ // The account has reached a limit on the number of simultaneous
+ // connected resources allowed.
+ message = "connection.error.failedMaxResourceLimit";
+ break;
+ case "bad-request":
+ // RFC 6120 (7.7.2.1): Bad Request.
+ // The provided resourcepart cannot be processed by the server.
+ message = "connection.error.failedResourceNotValid";
+ break;
+ case "conflict":
+ // RFC 6120 (7.7.2.2): Conflict.
+ // The provided resourcepart is already in use and the server
+ // disallowed the resource binding attempt.
+ this._requestBind();
+ return;
+ default:
+ this.WARN(`Unhandled bind result error ${error.condition}.`);
+ message = "connection.error.failedToGetAResource";
+ }
+ this._networkError(lazy._(message));
+ return;
+ }
+
+ let jid = aStanza.getElement(["bind", "jid"]);
+ if (!jid) {
+ this._networkError(lazy._("connection.error.failedToGetAResource"));
+ return;
+ }
+ jid = jid.innerText;
+ this.DEBUG("jid = " + jid);
+ this._jid = this._account._parseJID(jid);
+ this._resource = this._jid.resource;
+ this.startSession();
+ },
+ legacyAuth(aStanza) {
+ if (aStanza.attributes.type == "error") {
+ let error = aStanza.getElement(["error"]);
+ if (!error) {
+ this._networkError(lazy._("connection.error.incorrectResponse"));
+ return;
+ }
+
+ let code = parseInt(error.attributes.code, 10);
+ if (code == 401) {
+ // Failed Authentication (Incorrect Credentials)
+ this.onError(
+ Ci.prplIAccount.ERROR_AUTHENTICATION_FAILED,
+ lazy._("connection.error.notAuthorized")
+ );
+ return;
+ } else if (code == 406) {
+ // Failed Authentication (Required Information Not Provided)
+ this.onError(
+ Ci.prplIAccount.ERROR_AUTHENTICATION_FAILED,
+ lazy._("connection.error.authenticationFailure")
+ );
+ return;
+ }
+ // else if (code == 409) {
+ // Failed Authentication (Resource Conflict)
+ // XXX Flo The spec in XEP-0078 defines this error code, but
+ // I've yet to find a server sending it. The server I tested
+ // with just closed the first connection when a second
+ // connection was attempted with the same resource.
+ // libpurple's jabber prpl doesn't support this code either.
+ // }
+ }
+
+ if (aStanza.attributes.type != "result") {
+ this._networkError(lazy._("connection.error.incorrectResponse"));
+ return;
+ }
+
+ if (aStanza.children.length == 0) {
+ // Success!
+ this._password = null;
+ this.startSession();
+ return;
+ }
+
+ let query = aStanza.getElement(["query"]);
+ let values = {};
+ for (let c of query.children) {
+ values[c.qName] = c.innerText;
+ }
+
+ if (!("username" in values) || !("resource" in values)) {
+ this._networkError(lazy._("connection.error.incorrectResponse"));
+ return;
+ }
+
+ // If the resource is empty, we will fallback to brandShortName as
+ // resource is REQUIRED.
+ if (!this._resource) {
+ this._resource = Services.strings
+ .createBundle("chrome://branding/locale/brand.properties")
+ .GetStringFromName("brandShortName");
+ this._jid = this._setJID(
+ this._jid.domain,
+ this._jid.node,
+ this._resource
+ );
+ }
+
+ let children = [
+ Stanza.node("username", null, null, this._jid.node),
+ Stanza.node("resource", null, null, this._resource),
+ ];
+
+ let logString;
+ if ("digest" in values && this._streamId) {
+ let hashBase = this._streamId + this._password;
+
+ let ch = Cc["@mozilla.org/security/hash;1"].createInstance(
+ Ci.nsICryptoHash
+ );
+ ch.init(ch.SHA1);
+ // Non-US-ASCII characters MUST be encoded as UTF-8 since the
+ // SHA-1 hashing algorithm operates on byte arrays.
+ let data = [...new TextEncoder().encode(hashBase)];
+ ch.update(data, data.length);
+ let hash = ch.finish(false);
+ let toHexString = charCode => ("0" + charCode.toString(16)).slice(-2);
+ let digest = Object.keys(hash)
+ .map(i => toHexString(hash.charCodeAt(i)))
+ .join("");
+
+ children.push(Stanza.node("digest", null, null, digest));
+ logString =
+ "legacyAuth stanza containing SHA-1 hash of the password not logged";
+ } else if ("password" in values) {
+ if (
+ !this._encrypted &&
+ this._connectionSecurity != "allow_unencrypted_plain_auth"
+ ) {
+ this.onError(
+ Ci.prplIAccount.ERROR_AUTHENTICATION_IMPOSSIBLE,
+ lazy._("connection.error.notSendingPasswordInClear")
+ );
+ return;
+ }
+ children.push(Stanza.node("password", null, null, this._password));
+ logString = "legacyAuth stanza containing password not logged";
+ } else {
+ this.onError(
+ Ci.prplIAccount.ERROR_AUTHENTICATION_IMPOSSIBLE,
+ lazy._("connection.error.noCompatibleAuthMec")
+ );
+ return;
+ }
+
+ let s = Stanza.iq(
+ "set",
+ null,
+ this._domain,
+ Stanza.node("query", Stanza.NS.auth, null, children)
+ );
+ this.sendStanza(
+ s,
+ undefined,
+ undefined,
+ `<iq type="set".../> (${logString})`
+ );
+ },
+ sessionStarted(aStanza) {
+ this.resetPingTimer();
+ this._account.onConnection();
+ this.LOG("Account successfully connected.");
+ this.onXmppStanza = this.stanzaListeners.accountListening;
+ },
+ accountListening(aStanza) {
+ let id = aStanza.attributes.id;
+ if (id && this.execHandler(id, aStanza)) {
+ return;
+ }
+
+ this._account.onXmppStanza(aStanza);
+ let name = aStanza.qName;
+ if (name == "presence") {
+ this._account.onPresenceStanza(aStanza);
+ } else if (name == "message") {
+ this._account.onMessageStanza(aStanza);
+ } else if (name == "iq") {
+ this._account.onIQStanza(aStanza);
+ }
+ },
+ },
+ onXmppStanza(aStanza) {
+ this.ERROR("should not be reached\n");
+ },
+};