Bug 806228 - Fix login to XMPP servers that implement XMPP v1.0, don't support SASL, but advertise iq-auth support (eg. fastmail), r=clokep, a=Standard8.
authorFlorian Quèze <florian@queze.net>
Fri, 02 Nov 2012 00:05:04 +0100
changeset 13538 a18af72a983183d5323036fe0f4eb7356bbdc7df
parent 13537 4fbd531743e2de3868878820a67440c59f863cd7
child 13539 7dbb40e0446514292ffa7067785f4057397a4d10
push idunknown
push userunknown
push dateunknown
reviewersclokep, Standard8
bugs806228
Bug 806228 - Fix login to XMPP servers that implement XMPP v1.0, don't support SASL, but advertise iq-auth support (eg. fastmail), r=clokep, a=Standard8.
chat/protocols/xmpp/xmpp-session.jsm
chat/protocols/xmpp/xmpp-xml.jsm
--- a/chat/protocols/xmpp/xmpp-session.jsm
+++ b/chat/protocols/xmpp/xmpp-session.jsm
@@ -127,24 +127,23 @@ XMPPSession.prototype = {
 
   startSession: function() {
     this.sendStanza(Stanza.iq("set", null, null,
                               Stanza.node("session", Stanza.NS.session)));
     this.onXmppStanza = this.stanzaListeners.sessionStarted;
   },
 
   /* XEP-0078: Non-SASL Authentication */
-  startLegacyAuth: function(aStreamId) {
+  startLegacyAuth: function() {
     if (!this._encrypted && this._connectionSecurity == "require_tls") {
       this.onError(Ci.prplIAccount.ERROR_ENCRYPTION_ERROR,
                    _("connection.error.startTLSNotSupported"));
       return;
     }
 
-    this._streamId = aStreamId;
     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);
   },
 
@@ -255,32 +254,41 @@ XMPPSession.prototype = {
       if (aStanza.localName != "features") {
         ERROR("Unexpected stanza " + aStanza.localName + ", expected 'features'");
         this._networkError(_("connection.error.incorrectResponse"));
         return;
       }
 
       let mechs = aStanza.getElement(["mechanisms"]);
       if (!mechs) {
-        this._networkError(_("connection.error.noAuthMec"));
+        let auth = aStanza.getElement(["auth"]);
+        if (auth && auth.uri == Stanza.NS.auth_feature)
+          this.startLegacyAuth();
+        else
+          this._networkError(_("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 explicly allowed that
       // behavior.
       let authMechanisms = this._account.authMechanisms || XMPPAuthMechanisms;
       let selectedMech = "";
       let canUsePlain = false;
       mechs = mechs.getChildren("mechanism");
       for each (let m in mechs) {
         let mech = m.innerText;
-        if (mech == "PLAIN" && !this._encrypted)
+        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";
@@ -443,17 +451,18 @@ XMPPSession.prototype = {
         let hash = ch.finish(false);
         let toHexString =
           function(charCode) ("0" + charCode.toString(16)).slice(-2);
         let digest = [toHexString(hash.charCodeAt(i)) for (i in hash)].join("");
 
         children.push(Stanza.node("digest", null, null, digest));
       }
       else if ("password" in values) {
-        if (this._connectionSecurity != "allow_unencrypted_plain_auth") {
+        if (!this._encrypted &&
+            this._connectionSecurity != "allow_unencrypted_plain_auth") {
           this.onError(Ci.prplIAccount.ERROR_AUTHENTICATION_IMPOSSIBLE,
                        _("connection.error.notSendingPasswordInClear"));
           return;
         }
         children.push(Stanza.node("password", null, null, this._password));
       }
       else {
         this.onError(Ci.prplIAccount.ERROR_AUTHENTICATION_IMPOSSIBLE,
--- a/chat/protocols/xmpp/xmpp-xml.jsm
+++ b/chat/protocols/xmpp/xmpp-xml.jsm
@@ -15,16 +15,17 @@ const NS = {
   client                    : "jabber:client",
   streams                   : "http://etherx.jabber.org/streams",
   stream                    : "urn:ietf:params:xml:ns:xmpp-streams",
   sasl                      : "urn:ietf:params:xml:ns:xmpp-sasl",
   tls                       : "urn:ietf:params:xml:ns:xmpp-tls",
   bind                      : "urn:ietf:params:xml:ns:xmpp-bind",
   session                   : "urn:ietf:params:xml:ns:xmpp-session",
   auth                      : "jabber:iq:auth",
+  auth_feature              : "http://jabber.org/features/iq-auth",
   http_bind                 : "http://jabber.org/protocol/httpbind",
   http_auth                 : "http://jabber.org/protocol/http-auth",
   xbosh                     : "urn:xmpp:xbosh",
 
   "private"                 : "jabber:iq:private",
   xdata                     : "jabber:x:data",
 
   //roster
@@ -343,18 +344,19 @@ XMPPParser.prototype = {
       this._logReceivedData(node.convertToString().slice(0, -3) + ">\n");
 
       if ("_node" in this) {
         this._listener.onXMLError("unexpected-stream-start",
                                   "stream:stream inside an already started stream");
         return;
       }
 
+      this._listener._streamId = node.attributes["id"];
       if (!("version" in node.attributes))
-        this._listener.startLegacyAuth(node.attributes["id"]);
+        this._listener.startLegacyAuth();
 
       this._node = null;
       return;
     }
 
     let node = new XMLNode(this._node, aUri, aLocalName, aQName, aAttributes);
     if (this._node)
       this._node.addChild(node);