about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorSteffen Jaeckel <jaeckel-floss@eyet-services.de>2022-01-28 16:40:18 +0100
committerSteffen Jaeckel <jaeckel-floss@eyet-services.de>2022-02-01 15:52:08 +0100
commit0e58509c161ae8409c9accabb9606e0c7006b880 (patch)
tree213f5ee5273b5aae2c0a7c0da68a83d77f01b400
parent9cf78e59d533c7045c2e32e0ce864929b4e15ad7 (diff)
downloadprofani-tty-0e58509c161ae8409c9accabb9606e0c7006b880.tar.gz
handle `see-other-host` XMPP stream error
Fixes #1628

Signed-off-by: Steffen Jaeckel <jaeckel-floss@eyet-services.de>
-rw-r--r--src/xmpp/connection.c72
-rw-r--r--src/xmpp/session.c33
-rw-r--r--src/xmpp/session.h2
-rw-r--r--src/xmpp/stanza.h2
-rw-r--r--src/xmpp/xmpp.h3
5 files changed, 110 insertions, 2 deletions
diff --git a/src/xmpp/connection.c b/src/xmpp/connection.c
index 7d807365..304c65c8 100644
--- a/src/xmpp/connection.c
+++ b/src/xmpp/connection.c
@@ -872,6 +872,70 @@ connection_set_priority(const int priority)
     conn.priority = priority;
 }
 
+#if defined(LIBXMPP_VERSION_MAJOR) && defined(LIBXMPP_VERSION_MINOR) \
+    && ((LIBXMPP_VERSION_MAJOR > 0) || (LIBXMPP_VERSION_MINOR >= 12))
+static xmpp_stanza_t*
+_get_soh_error(xmpp_stanza_t* error_stanza)
+{
+    return xmpp_stanza_get_child_by_path(error_stanza,
+                                         XMPP_STANZA_NAME_IN_NS("error", STANZA_NS_STREAMS),
+                                         XMPP_STANZA_NAME_IN_NS("see-other-host", STANZA_NS_XMPP_STREAMS),
+                                         NULL);
+}
+#else
+static xmpp_stanza_t*
+_get_soh_error(xmpp_stanza_t* error_stanza)
+{
+    const char* name = xmpp_stanza_get_name(error_stanza);
+    const char* ns = xmpp_stanza_get_ns(error_stanza);
+    if (!name || !ns || strcmp(name, "error") || strcmp(ns, STANZA_NS_STREAMS)) {
+        log_debug("_get_soh_error: could not find error stanza");
+        return NULL;
+    }
+    return xmpp_stanza_get_child_by_name_and_ns(error_stanza, "see-other-host", STANZA_NS_XMPP_STREAMS);
+}
+#endif
+
+static bool
+_get_other_host(xmpp_stanza_t* error_stanza, gchar** host, int* port)
+{
+    xmpp_stanza_t* soh_error = _get_soh_error(error_stanza);
+    if (!soh_error || !xmpp_stanza_get_children(soh_error)) {
+        log_debug("_get_other_host: stream-error contains no see-other-host");
+        return false;
+    }
+    const char* alturi = xmpp_stanza_get_text_ptr(xmpp_stanza_get_children(soh_error));
+    if (!alturi) {
+        log_debug("_get_other_host: see-other-host contains no text");
+        return false;
+    }
+    /* Construct a valid URI with `schema://` as `g_uri_split_network()`
+     * requires this to be there.
+     */
+    const char* xmpp = "xmpp://";
+    char* xmpp_uri = malloc(strlen(xmpp) + strlen(alturi) + 1);
+    if (!xmpp_uri) {
+        log_debug("_get_other_host: malloc failed \"%s\"", alturi);
+        return false;
+    }
+    memcpy(xmpp_uri, xmpp, strlen(xmpp));
+    memcpy(xmpp_uri + strlen(xmpp), alturi, strlen(alturi) + 1);
+
+    if (!g_uri_split_network(xmpp_uri, 0, NULL, host, port, NULL)) {
+        log_debug("_get_other_host: Could not split \"%s\"", xmpp_uri);
+        free(xmpp_uri);
+        return false;
+    }
+    free(xmpp_uri);
+    /* fix-up `port` as g_uri_split_network() sets port to `-1` if it's missing
+     * in the passed-in URI, but libstrophe expects a "missing port"
+     * to be passed as `0` (which then results in connecting to the standard port).
+     */
+    if (*port == -1)
+        *port = 0;
+    return true;
+}
+
 static void
 _connection_handler(xmpp_conn_t* const xmpp_conn, const xmpp_conn_event_t status, const int error,
                     xmpp_stream_error_t* const stream_error, void* const userdata)
@@ -924,6 +988,14 @@ _connection_handler(xmpp_conn_t* const xmpp_conn, const xmpp_conn_event_t status
 
             // login attempt failed
         } else if (conn.conn_status != JABBER_DISCONNECTING) {
+            gchar* host;
+            int port;
+            if (stream_error && stream_error->stanza && _get_other_host(stream_error->stanza, &host, &port)) {
+                session_reconnect(host, port);
+                log_debug("Connection handler: Forcing a re-connect to \"%s\"", host);
+                conn.conn_status = JABBER_RECONNECT;
+                return;
+            }
             log_debug("Connection handler: Login failed");
             session_login_failed();
         }
diff --git a/src/xmpp/session.c b/src/xmpp/session.c
index fabf9f06..ce3c557a 100644
--- a/src/xmpp/session.c
+++ b/src/xmpp/session.c
@@ -272,6 +272,9 @@ session_process_events(void)
             }
         }
         break;
+    case JABBER_RECONNECT:
+        _session_reconnect();
+        break;
     default:
         break;
     }
@@ -532,6 +535,24 @@ session_check_autoaway(void)
     g_free(mode);
 }
 
+static struct
+{
+    gchar* altdomain;
+    unsigned short altport;
+} reconnect;
+
+/* This takes ownership of `altdomain`, i.e. the caller must not
+ * free the value after calling this function.
+ */
+void
+session_reconnect(gchar* altdomain, unsigned short altport)
+{
+    reconnect.altdomain = altdomain;
+    reconnect.altport = altport;
+    assert(reconnect_timer == NULL);
+    reconnect_timer = g_timer_new();
+}
+
 static void
 _session_reconnect(void)
 {
@@ -548,9 +569,18 @@ _session_reconnect(void)
     } else {
         jid = strdup(account->jid);
     }
+    const char* server;
+    unsigned short port;
+    if (reconnect.altdomain) {
+        server = reconnect.altdomain;
+        port = reconnect.altport;
+    } else {
+        server = account->server;
+        port = account->port;
+    }
 
     log_debug("Attempting reconnect with account %s", account->name);
-    connection_connect(jid, saved_account.passwd, account->server, account->port, account->tls_policy, account->auth_policy);
+    connection_connect(jid, saved_account.passwd, server, port, account->tls_policy, account->auth_policy);
     free(jid);
     account_free(account);
     g_timer_start(reconnect_timer);
@@ -561,6 +591,7 @@ _session_free_internals(void)
 {
     FREE_SET_NULL(saved_account.name);
     FREE_SET_NULL(saved_account.passwd);
+    GFREE_SET_NULL(reconnect.altdomain);
     _session_free_saved_details();
 }
 
diff --git a/src/xmpp/session.h b/src/xmpp/session.h
index 62ee9b10..d8565fa4 100644
--- a/src/xmpp/session.h
+++ b/src/xmpp/session.h
@@ -46,4 +46,6 @@ void session_autoping_fail(void);
 void session_init_activity(void);
 void session_check_autoaway(void);
 
+void session_reconnect(gchar* altdomain, unsigned short altport);
+
 #endif
diff --git a/src/xmpp/stanza.h b/src/xmpp/stanza.h
index 9e957749..07d1f395 100644
--- a/src/xmpp/stanza.h
+++ b/src/xmpp/stanza.h
@@ -245,6 +245,8 @@
 #define STANZA_NS_REPORTING               "urn:xmpp:reporting:1"
 #define STANZA_NS_MOOD                    "http://jabber.org/protocol/mood"
 #define STANZA_NS_MOOD_NOTIFY             "http://jabber.org/protocol/mood+notify"
+#define STANZA_NS_STREAMS                 "http://etherx.jabber.org/streams"
+#define STANZA_NS_XMPP_STREAMS            "urn:ietf:params:xml:ns:xmpp-streams"
 
 #define STANZA_DATAFORM_SOFTWARE "urn:xmpp:dataforms:softwareinfo"
 
diff --git a/src/xmpp/xmpp.h b/src/xmpp/xmpp.h
index 457d740a..6aaa0cb9 100644
--- a/src/xmpp/xmpp.h
+++ b/src/xmpp/xmpp.h
@@ -72,7 +72,8 @@ typedef enum {
     JABBER_DISCONNECTING,
     JABBER_DISCONNECTED,
     JABBER_RAW_CONNECTING,
-    JABBER_RAW_CONNECTED
+    JABBER_RAW_CONNECTED,
+    JABBER_RECONNECT
 } jabber_conn_status_t;
 
 typedef enum {