about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--.clang-format1
-rw-r--r--src/command/cmd_funcs.c2
-rw-r--r--src/database.c2
-rw-r--r--src/event/server_events.c2
-rw-r--r--src/omemo/crypto.h178
-rw-r--r--src/omemo/omemo.c4
-rw-r--r--src/otr/otr.c2
-rw-r--r--src/pgp/gpg.c28
-rw-r--r--src/tools/clipboard.c2
-rw-r--r--src/ui/chatwin.c10
-rw-r--r--src/ui/console.c4
-rw-r--r--src/ui/notifier.c4
-rw-r--r--src/ui/occupantswin.c4
-rw-r--r--src/ui/window.c2
-rw-r--r--src/xmpp/avatar.c2
-rw-r--r--src/xmpp/connection.c110
-rw-r--r--src/xmpp/omemo.c2
-rw-r--r--src/xmpp/ox.c2
-rw-r--r--src/xmpp/ox.h10
-rw-r--r--src/xmpp/session.c53
-rw-r--r--src/xmpp/session.h2
-rw-r--r--src/xmpp/stanza.c2
-rw-r--r--src/xmpp/stanza.h2
-rw-r--r--src/xmpp/xmpp.h5
-rw-r--r--tests/unittests/test_callbacks.c4
25 files changed, 291 insertions, 148 deletions
diff --git a/.clang-format b/.clang-format
index a59cb616..d16e045c 100644
--- a/.clang-format
+++ b/.clang-format
@@ -22,6 +22,7 @@ AlignOperands: true
 AlignTrailingComments: true
 AllowAllArgumentsOnNextLine: true
 AllowShortBlocksOnASingleLine: true
+IndentGotoLabels: false
 IndentWidth: 4
 BreakBeforeBraces: Custom
 BraceWrapping:
diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c
index d9a61727..ba06d59e 100644
--- a/src/command/cmd_funcs.c
+++ b/src/command/cmd_funcs.c
@@ -1144,7 +1144,7 @@ cmd_export(ProfWin* window, const char* const command, gchar** args)
         g_slist_free(list);
         close(fd);
         return TRUE;
-    write_error:
+write_error:
         cons_show("error: write failed: %s", strerror(errno));
         cons_show("");
         g_slist_free(list);
diff --git a/src/database.c b/src/database.c
index 6d8e79d4..c04dcff9 100644
--- a/src/database.c
+++ b/src/database.c
@@ -178,7 +178,7 @@ _log_database_add_outgoing(char* type, const char* const id, const char* const b
     msg->from_jid = jid_create(barejid);
     msg->plain = message ? strdup(message) : NULL;
     msg->replace_id = replace_id ? strdup(replace_id) : NULL;
-    msg->timestamp = g_date_time_new_now_local(); //TODO: get from outside. best to have whole ProfMessage from outside
+    msg->timestamp = g_date_time_new_now_local(); // TODO: get from outside. best to have whole ProfMessage from outside
     msg->enc = enc;
 
     Jid* myjid = jid_create(connection_get_fulljid());
diff --git a/src/event/server_events.c b/src/event/server_events.c
index a61aecf6..0b7858a3 100644
--- a/src/event/server_events.c
+++ b/src/event/server_events.c
@@ -522,7 +522,7 @@ _sv_ev_incoming_ox(ProfChatWin* chatwin, gboolean new_win, ProfMessage* message,
         chat_log_pgp_msg_in(message);
     }
     chatwin->pgp_recv = TRUE;
-    //p_gpg_free_decrypted(message->plain);
+    // p_gpg_free_decrypted(message->plain);
     message->plain = NULL;
 #endif
 }
diff --git a/src/omemo/crypto.h b/src/omemo/crypto.h
index 5adbffd8..d3f775dd 100644
--- a/src/omemo/crypto.h
+++ b/src/omemo/crypto.h
@@ -43,110 +43,110 @@
 
 int omemo_crypto_init(void);
 /**
-* Callback for a secure random number generator.
-* This function shall fill the provided buffer with random bytes.
-*
-* @param data pointer to the output buffer
-* @param len size of the output buffer
-* @return 0 on success, negative on failure
-*/
+ * Callback for a secure random number generator.
+ * This function shall fill the provided buffer with random bytes.
+ *
+ * @param data pointer to the output buffer
+ * @param len size of the output buffer
+ * @return 0 on success, negative on failure
+ */
 int omemo_random_func(uint8_t* data, size_t len, void* user_data);
 
 /**
-* Callback for an HMAC-SHA256 implementation.
-* This function shall initialize an HMAC context with the provided key.
-*
-* @param hmac_context private HMAC context pointer
-* @param key pointer to the key
-* @param key_len length of the key
-* @return 0 on success, negative on failure
-*/
+ * Callback for an HMAC-SHA256 implementation.
+ * This function shall initialize an HMAC context with the provided key.
+ *
+ * @param hmac_context private HMAC context pointer
+ * @param key pointer to the key
+ * @param key_len length of the key
+ * @return 0 on success, negative on failure
+ */
 int omemo_hmac_sha256_init_func(void** hmac_context, const uint8_t* key, size_t key_len, void* user_data);
 
 /**
-* Callback for an HMAC-SHA256 implementation.
-* This function shall update the HMAC context with the provided data
-*
-* @param hmac_context private HMAC context pointer
-* @param data pointer to the data
-* @param data_len length of the data
-* @return 0 on success, negative on failure
-*/
+ * Callback for an HMAC-SHA256 implementation.
+ * This function shall update the HMAC context with the provided data
+ *
+ * @param hmac_context private HMAC context pointer
+ * @param data pointer to the data
+ * @param data_len length of the data
+ * @return 0 on success, negative on failure
+ */
 int omemo_hmac_sha256_update_func(void* hmac_context, const uint8_t* data, size_t data_len, void* user_data);
 
 /**
-* Callback for an HMAC-SHA256 implementation.
-* This function shall finalize an HMAC calculation and populate the output
-* buffer with the result.
-*
-* @param hmac_context private HMAC context pointer
-* @param output buffer to be allocated and populated with the result
-* @return 0 on success, negative on failure
-*/
+ * Callback for an HMAC-SHA256 implementation.
+ * This function shall finalize an HMAC calculation and populate the output
+ * buffer with the result.
+ *
+ * @param hmac_context private HMAC context pointer
+ * @param output buffer to be allocated and populated with the result
+ * @return 0 on success, negative on failure
+ */
 int omemo_hmac_sha256_final_func(void* hmac_context, signal_buffer** output, void* user_data);
 
 /**
-* Callback for an HMAC-SHA256 implementation.
-* This function shall free the private context allocated in
-* hmac_sha256_init_func.
-*
-* @param hmac_context private HMAC context pointer
-*/
+ * Callback for an HMAC-SHA256 implementation.
+ * This function shall free the private context allocated in
+ * hmac_sha256_init_func.
+ *
+ * @param hmac_context private HMAC context pointer
+ */
 void omemo_hmac_sha256_cleanup_func(void* hmac_context, void* user_data);
 
 /**
-* Callback for a SHA512 message digest implementation.
-* This function shall initialize a digest context.
-*
-* @param digest_context private digest context pointer
-* @return 0 on success, negative on failure
-*/
+ * Callback for a SHA512 message digest implementation.
+ * This function shall initialize a digest context.
+ *
+ * @param digest_context private digest context pointer
+ * @return 0 on success, negative on failure
+ */
 int omemo_sha512_digest_init_func(void** digest_context, void* user_data);
 
 /**
-* Callback for a SHA512 message digest implementation.
-* This function shall update the digest context with the provided data.
-*
-* @param digest_context private digest context pointer
-* @param data pointer to the data
-* @param data_len length of the data
-* @return 0 on success, negative on failure
-*/
+ * Callback for a SHA512 message digest implementation.
+ * This function shall update the digest context with the provided data.
+ *
+ * @param digest_context private digest context pointer
+ * @param data pointer to the data
+ * @param data_len length of the data
+ * @return 0 on success, negative on failure
+ */
 int omemo_sha512_digest_update_func(void* digest_context, const uint8_t* data, size_t data_len, void* user_data);
 
 /**
-* Callback for a SHA512 message digest implementation.
-* This function shall finalize the digest calculation, populate the
-* output buffer with the result, and prepare the context for reuse.
-*
-* @param digest_context private digest context pointer
-* @param output buffer to be allocated and populated with the result
-* @return 0 on success, negative on failure
-*/
+ * Callback for a SHA512 message digest implementation.
+ * This function shall finalize the digest calculation, populate the
+ * output buffer with the result, and prepare the context for reuse.
+ *
+ * @param digest_context private digest context pointer
+ * @param output buffer to be allocated and populated with the result
+ * @return 0 on success, negative on failure
+ */
 int omemo_sha512_digest_final_func(void* digest_context, signal_buffer** output, void* user_data);
 
 /**
-* Callback for a SHA512 message digest implementation.
-* This function shall free the private context allocated in
-* sha512_digest_init_func.
-*
-* @param digest_context private digest context pointer
-*/
+ * Callback for a SHA512 message digest implementation.
+ * This function shall free the private context allocated in
+ * sha512_digest_init_func.
+ *
+ * @param digest_context private digest context pointer
+ */
 void omemo_sha512_digest_cleanup_func(void* digest_context, void* user_data);
 
 /**
-* Callback for an AES encryption implementation.
-*
-* @param output buffer to be allocated and populated with the ciphertext
-* @param cipher specific cipher variant to use, either SG_CIPHER_AES_CTR_NOPADDING or SG_CIPHER_AES_CBC_PKCS5
-* @param key the encryption key
-* @param key_len length of the encryption key
-* @param iv the initialization vector
-* @param iv_len length of the initialization vector
-* @param plaintext the plaintext to encrypt
-* @param plaintext_len length of the plaintext
-* @return 0 on success, negative on failure
-*/
+ * Callback for an AES encryption implementation.
+ *
+ * @param output buffer to be allocated and populated with the ciphertext
+ * @param cipher specific cipher variant to use, either SG_CIPHER_AES_CTR_NOPADDING or SG_CIPHER_AES_CBC_PKCS5
+ * @param key the encryption key
+ * @param key_len length of the encryption key
+ * @param iv the initialization vector
+ * @param iv_len length of the initialization vector
+ * @param plaintext the plaintext to encrypt
+ * @param plaintext_len length of the plaintext
+ * @return 0 on success, negative on failure
+ */
 int omemo_encrypt_func(signal_buffer** output,
                        int cipher,
                        const uint8_t* key, size_t key_len,
@@ -155,18 +155,18 @@ int omemo_encrypt_func(signal_buffer** output,
                        void* user_data);
 
 /**
-* Callback for an AES decryption implementation.
-*
-* @param output buffer to be allocated and populated with the plaintext
-* @param cipher specific cipher variant to use, either SG_CIPHER_AES_CTR_NOPADDING or SG_CIPHER_AES_CBC_PKCS5
-* @param key the encryption key
-* @param key_len length of the encryption key
-* @param iv the initialization vector
-* @param iv_len length of the initialization vector
-* @param ciphertext the ciphertext to decrypt
-* @param ciphertext_len length of the ciphertext
-* @return 0 on success, negative on failure
-*/
+ * Callback for an AES decryption implementation.
+ *
+ * @param output buffer to be allocated and populated with the plaintext
+ * @param cipher specific cipher variant to use, either SG_CIPHER_AES_CTR_NOPADDING or SG_CIPHER_AES_CBC_PKCS5
+ * @param key the encryption key
+ * @param key_len length of the encryption key
+ * @param iv the initialization vector
+ * @param iv_len length of the initialization vector
+ * @param ciphertext the ciphertext to decrypt
+ * @param ciphertext_len length of the ciphertext
+ * @return 0 on success, negative on failure
+ */
 int omemo_decrypt_func(signal_buffer** output,
                        int cipher,
                        const uint8_t* key, size_t key_len,
diff --git a/src/omemo/omemo.c b/src/omemo/omemo.c
index 4d53ad0c..87599de0 100644
--- a/src/omemo/omemo.c
+++ b/src/omemo/omemo.c
@@ -388,7 +388,9 @@ omemo_publish_crypto_materials(void)
     omemo_bundle_publish(true);
 }
 
-static void _acquire_sender_devices_list(void) {
+static void
+_acquire_sender_devices_list(void)
+{
     char* barejid = connection_get_barejid();
 
     g_hash_table_insert(omemo_ctx.device_list_handler, strdup(barejid), _handle_own_device_list);
diff --git a/src/otr/otr.c b/src/otr/otr.c
index 1cf1072c..5f45e23c 100644
--- a/src/otr/otr.c
+++ b/src/otr/otr.c
@@ -292,7 +292,7 @@ otr_on_message_recv(const char* const barejid, const char* const resource, const
     prof_otrpolicy_t policy = otr_get_policy(barejid);
     char* whitespace_base = strstr(message, OTRL_MESSAGE_TAG_BASE);
 
-    //check for OTR whitespace (opportunistic or always)
+    // check for OTR whitespace (opportunistic or always)
     if (policy == PROF_OTRPOLICY_OPPORTUNISTIC || policy == PROF_OTRPOLICY_ALWAYS) {
         if (whitespace_base) {
             if (strstr(message, OTRL_MESSAGE_TAG_V2) || strstr(message, OTRL_MESSAGE_TAG_V1)) {
diff --git a/src/pgp/gpg.c b/src/pgp/gpg.c
index d4863492..549f9d3b 100644
--- a/src/pgp/gpg.c
+++ b/src/pgp/gpg.c
@@ -871,15 +871,15 @@ ox_gpg_public_keys(void)
     }
     gpgme_release(ctx);
 
-    //autocomplete_clear(key_ac);
-    //    GList *ids = g_hash_table_get_keys(result);
-    //    GList *curr = ids;
-    //    while (curr) {
-    //        ProfPGPKey *key = g_hash_table_lookup(result, curr->data);
-    //        autocomplete_add(key_ac, key->id);
-    //        curr = curr->next;
-    //    }
-    //    g_list_free(ids);
+    // autocomplete_clear(key_ac);
+    //     GList *ids = g_hash_table_get_keys(result);
+    //     GList *curr = ids;
+    //     while (curr) {
+    //         ProfPGPKey *key = g_hash_table_lookup(result, curr->data);
+    //         autocomplete_add(key_ac, key->id);
+    //         curr = curr->next;
+    //     }
+    //     g_list_free(ids);
 
     return result;
 }
@@ -1210,14 +1210,14 @@ p_ox_gpg_decrypt(char* base64)
  *
  * This function is used to read a key and push it on PEP. There are some checks
  * in this function:
- * 
+ *
  * Key is not
- * - gkey->revoked 
- * - gkey->expired 
+ * - gkey->revoked
+ * - gkey->expired
  * - gkey->disabled
- * - gkey->invalid 
+ * - gkey->invalid
  * - gkey->secret
- * 
+ *
  * Only one key in the file.
  *
  * \param filename filename to read the file.
diff --git a/src/tools/clipboard.c b/src/tools/clipboard.c
index b2ea1bf3..a9f67eac 100644
--- a/src/tools/clipboard.c
+++ b/src/tools/clipboard.c
@@ -63,7 +63,7 @@ clipboard_get(void)
         return NULL;
     }
 
-    //while(!gtk_clipboard_wait_is_text_available(cld)) {}
+    // while(!gtk_clipboard_wait_is_text_available(cld)) {}
 
     clip = gtk_clipboard_wait_for_text(cl);
     return clip;
diff --git a/src/ui/chatwin.c b/src/ui/chatwin.c
index 1c33e6c3..0224d720 100644
--- a/src/ui/chatwin.c
+++ b/src/ui/chatwin.c
@@ -298,11 +298,11 @@ chatwin_incoming_msg(ProfChatWin* chatwin, ProfMessage* message, gboolean win_cr
 
         chatwin->unread++;
 
-        //TODO: so far we don't ask for MAM when incoming message occurs.
-        //Need to figure out:
-        //1) only send IQ once
-        //2) sort incoming messages on timestamp
-        //for now if experimental MAM is enabled we dont show no history from sql either
+        // TODO: so far we don't ask for MAM when incoming message occurs.
+        // Need to figure out:
+        // 1) only send IQ once
+        // 2) sort incoming messages on timestamp
+        // for now if experimental MAM is enabled we dont show no history from sql either
 
         // MUCPMs also get printed here. In their case we don't save any logs (because nick owners can change) and thus we shouldn't read logs
         // (and if we do we need to check the resourcepart)
diff --git a/src/ui/console.c b/src/ui/console.c
index 61c1f1c2..8d3e13a3 100644
--- a/src/ui/console.c
+++ b/src/ui/console.c
@@ -2159,8 +2159,8 @@ cons_executable_setting(void)
     cons_show("Default '/avatar open' command (/executable avatar)                      : %s", avatar);
     g_free(avatar);
 
-    //TODO: there needs to be a way to get all the "locales"/schemes so we can
-    //display the default openers for all filetypes
+    // TODO: there needs to be a way to get all the "locales"/schemes so we can
+    // display the default openers for all filetypes
     gchar* urlopen = prefs_get_string(PREF_URL_OPEN_CMD);
     cons_show("Default '/url open' command (/executable urlopen)                        : %s", urlopen);
     g_free(urlopen);
diff --git a/src/ui/notifier.c b/src/ui/notifier.c
index 9a0eb291..92ebf835 100644
--- a/src/ui/notifier.c
+++ b/src/ui/notifier.c
@@ -234,10 +234,10 @@ notify(const char* const message, int timeout, const char* const category)
     NOTIFYICONDATA nid;
     memset(&nid, 0, sizeof(nid));
     nid.cbSize = sizeof(NOTIFYICONDATA);
-    //nid.hWnd = hWnd;
+    // nid.hWnd = hWnd;
     nid.uID = 100;
     nid.uVersion = NOTIFYICON_VERSION;
-    //nid.uCallbackMessage = WM_MYMESSAGE;
+    // nid.uCallbackMessage = WM_MYMESSAGE;
     nid.hIcon = LoadIcon(NULL, IDI_APPLICATION);
     strcpy(nid.szTip, "Tray Icon");
     nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
diff --git a/src/ui/occupantswin.c b/src/ui/occupantswin.c
index 1524de3f..04150e25 100644
--- a/src/ui/occupantswin.c
+++ b/src/ui/occupantswin.c
@@ -46,8 +46,8 @@
 static void
 _occuptantswin_occupant(ProfLayoutSplit* layout, GList* item, gboolean showjid, gboolean isoffline)
 {
-    int colour = 0;                                     //init to workaround compiler warning
-    theme_item_t presence_colour = THEME_ROSTER_ONLINE; //init to workaround compiler warning
+    int colour = 0;                                     // init to workaround compiler warning
+    theme_item_t presence_colour = THEME_ROSTER_ONLINE; // init to workaround compiler warning
     Occupant* occupant = item->data;
 
     if (isoffline) {
diff --git a/src/ui/window.c b/src/ui/window.c
index 7442ce9c..54932f21 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -1439,7 +1439,7 @@ win_print_outgoing_with_receipt(ProfWin* window, const char* show_char, const ch
     const char* myjid = connection_get_fulljid();
     if (replace_id) {
         _win_correct(window, message, id, replace_id, myjid);
-        free(receipt); //TODO: probably we should use this in _win_correct()
+        free(receipt); // TODO: probably we should use this in _win_correct()
     } else {
         buffer_append(window->layout->buffer, show_char, 0, time, 0, THEME_TEXT_ME, from, myjid, message, receipt, id);
         _win_print_internal(window, show_char, 0, time, 0, THEME_TEXT_ME, from, message, receipt);
diff --git a/src/xmpp/avatar.c b/src/xmpp/avatar.c
index 89d34fbc..346e85ba 100644
--- a/src/xmpp/avatar.c
+++ b/src/xmpp/avatar.c
@@ -79,7 +79,7 @@ avatar_pep_subscribe(void)
     message_pubsub_event_handler_add(STANZA_NS_USER_AVATAR_METADATA, _avatar_metadata_handler, NULL, NULL);
     message_pubsub_event_handler_add(STANZA_NS_USER_AVATAR_DATA, _avatar_metadata_handler, NULL, NULL);
 
-    //caps_add_feature(XMPP_FEATURE_USER_AVATAR_METADATA_NOTIFY);
+    // caps_add_feature(XMPP_FEATURE_USER_AVATAR_METADATA_NOTIFY);
 
     if (looking_for) {
         g_hash_table_destroy(looking_for);
diff --git a/src/xmpp/connection.c b/src/xmpp/connection.c
index 516006a9..c04f9df2 100644
--- a/src/xmpp/connection.c
+++ b/src/xmpp/connection.c
@@ -872,6 +872,106 @@ 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
+
+#if GLIB_CHECK_VERSION(2, 66, 0)
+static gboolean
+_split_url(const char* alturi, gchar** host, gint* port)
+{
+    /* 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);
+    gboolean ret = g_uri_split_network(xmpp_uri, 0, NULL, host, port, NULL);
+    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 ret;
+}
+#else
+/* poor-mans URL splitting */
+static gboolean
+_split_url(const char* alturi, gchar** host, gint* port)
+{
+    ptrdiff_t hostlen;
+    /* search ':' from start and end
+     * if `first` matches `last` it's a `hostname:port` combination
+     * if `first` is different than `last` it's `[ip:v6]:port`
+     */
+    char* first = strchr(alturi, ':');
+    char* last = strrchr(alturi, ':');
+    if (first && first == last) {
+        hostlen = last - alturi;
+        if (!strtoi_range(last + 1, port, 1, 65535, NULL))
+            return FALSE;
+    } else {
+        hostlen = strlen(alturi) + 1;
+        *port = 0;
+    }
+    gchar* buf = g_malloc(hostlen);
+    if (!buf)
+        return FALSE;
+    memcpy(buf, alturi, hostlen);
+    buf[hostlen - 1] = '\0';
+    *host = buf;
+    return TRUE;
+}
+#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;
+    }
+
+    if (!_split_url(alturi, host, port)) {
+        log_debug("_get_other_host: Could not split \"%s\"", alturi);
+        return false;
+    }
+    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 +1024,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();
         }
@@ -1074,7 +1182,7 @@ _random_bytes_close(void)
 static void
 _compute_identifier(const char* barejid)
 {
-    //in case of reconnect (lost connection)
+    // in case of reconnect (lost connection)
     free(prof_identifier);
 
     prof_identifier = g_compute_hmac_for_string(G_CHECKSUM_SHA256,
diff --git a/src/xmpp/omemo.c b/src/xmpp/omemo.c
index 26cd6b37..c4d82e42 100644
--- a/src/xmpp/omemo.c
+++ b/src/xmpp/omemo.c
@@ -437,7 +437,7 @@ omemo_receive_message(xmpp_stanza_t* const stanza, gboolean* trusted)
         keys = g_list_append(keys, key);
         continue;
 
-    skip:
+skip:
         free(key);
     }
 
diff --git a/src/xmpp/ox.c b/src/xmpp/ox.c
index b8a0a41e..c4efa5d6 100644
--- a/src/xmpp/ox.c
+++ b/src/xmpp/ox.c
@@ -222,7 +222,7 @@ ox_request_public_key(const char* const jid, const char* const fingerprint)
       </item>
     </publish>
   </pubsub>
-</iq>    
+</iq>
 </pre>
  *
  */
diff --git a/src/xmpp/ox.h b/src/xmpp/ox.h
index 143cce95..06acd119 100644
--- a/src/xmpp/ox.h
+++ b/src/xmpp/ox.h
@@ -2,7 +2,7 @@
  * ox.h
  * vim: expandtab:ts=4:sts=4:sw=4
  *
- * Copyright (C) 2020 Stefan Kropp <stefan@debxwoody.de> 
+ * Copyright (C) 2020 Stefan Kropp <stefan@debxwoody.de>
  *
  * This file is part of Profanity.
  *
@@ -35,10 +35,10 @@
 
 /*!
  * \page OX OX Implementation
- * 
+ *
  * \section OX XEP-0373: OpenPGP for XMPP
  * XEP-0373: OpenPGP for XMPP (OX) is the implementation of OpenPGP for XMPP
- * replace the XEP-0027.  
+ * replace the XEP-0027.
  *
  * https://xmpp.org/extensions/xep-0373.html
  */
@@ -48,10 +48,10 @@
  *
  * Reads the public key from the given file. Checks the key-information and
  * pushes the key on PEP.
- *  
+ *
  * https://xmpp.org/extensions/xep-0373.html#announcing-pubkey
  *
- * \param filename name of the file with the public key 
+ * \param filename name of the file with the public key
  * \return TRUE: success; FALSE: failed
  */
 
diff --git a/src/xmpp/session.c b/src/xmpp/session.c
index 4a19e211..ce3c557a 100644
--- a/src/xmpp/session.c
+++ b/src/xmpp/session.c
@@ -98,7 +98,7 @@ static char* saved_status;
 
 static void _session_reconnect(void);
 
-static void _session_free_saved_account(void);
+static void _session_free_internals(void);
 static void _session_free_saved_details(void);
 
 void
@@ -117,8 +117,7 @@ session_connect_with_account(const ProfAccount* const account)
 
     log_info("Connecting using account: %s", account->name);
 
-    _session_free_saved_account();
-    _session_free_saved_details();
+    _session_free_internals();
 
     // save account name and password for reconnect
     saved_account.name = strdup(account->name);
@@ -152,8 +151,7 @@ session_connect_with_details(const char* const jid, const char* const passwd, co
     assert(jid != NULL);
     assert(passwd != NULL);
 
-    _session_free_saved_account();
-    _session_free_saved_details();
+    _session_free_internals();
 
     // save details for reconnect, remember name for account creating on success
     saved_details.name = strdup(jid);
@@ -240,8 +238,7 @@ session_disconnect(void)
 void
 session_shutdown(void)
 {
-    _session_free_saved_account();
-    _session_free_saved_details();
+    _session_free_internals();
 
     chat_sessions_clear();
     presence_clear_sub_requests();
@@ -275,6 +272,9 @@ session_process_events(void)
             }
         }
         break;
+    case JABBER_RECONNECT:
+        _session_reconnect();
+        break;
     default:
         break;
     }
@@ -371,8 +371,7 @@ session_login_failed(void)
     if (reconnect_timer == NULL) {
         log_debug("Connection handler: No reconnect timer");
         sv_ev_failed_login();
-        _session_free_saved_account();
-        _session_free_saved_details();
+        _session_free_internals();
     } else {
         log_debug("Connection handler: Restarting reconnect timer");
         if (prefs_get_reconnect() != 0) {
@@ -394,8 +393,7 @@ session_lost_connection(void)
         assert(reconnect_timer == NULL);
         reconnect_timer = g_timer_new();
     } else {
-        _session_free_saved_account();
-        _session_free_saved_details();
+        _session_free_internals();
     }
 }
 
@@ -537,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)
 {
@@ -553,19 +569,30 @@ _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);
 }
 
 static void
-_session_free_saved_account(void)
+_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();
 }
 
 static void
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.c b/src/xmpp/stanza.c
index d28ed3d2..bfb782da 100644
--- a/src/xmpp/stanza.c
+++ b/src/xmpp/stanza.c
@@ -2605,7 +2605,7 @@ stanza_create_mam_iq(xmpp_ctx_t* ctx, const char* const jid, const char* const s
     char* id = connection_create_stanza_id();
     xmpp_stanza_t* iq = xmpp_iq_new(ctx, STANZA_TYPE_SET, id);
     free(id);
-    //xmpp_stanza_set_to(iq, jid);
+    // xmpp_stanza_set_to(iq, jid);
 
     xmpp_stanza_t* query = xmpp_stanza_new(ctx);
     xmpp_stanza_set_name(query, STANZA_NAME_QUERY);
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 8a2d2eb2..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 {
@@ -99,7 +100,7 @@ typedef struct bookmark_t
     char* password;
     char* name;
     gboolean autojoin;
-    int ext_gajim_minimize; //0 - non existent, 1 - true, 2 - false
+    int ext_gajim_minimize; // 0 - non existent, 1 - true, 2 - false
 } Bookmark;
 
 typedef struct disco_identity_t
diff --git a/tests/unittests/test_callbacks.c b/tests/unittests/test_callbacks.c
index 3c5267fe..a9b1138b 100644
--- a/tests/unittests/test_callbacks.c
+++ b/tests/unittests/test_callbacks.c
@@ -61,6 +61,6 @@ returns_commands(void** state)
     assert_true(foundCommand1 && foundCommand2 && foundCommand3);
 
     g_list_free(names);
-    //TODO: why does this make the test fail?
-    //callbacks_close();
+    // TODO: why does this make the test fail?
+    // callbacks_close();
 }