about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/command/cmd_ac.c39
-rw-r--r--src/command/cmd_defs.c5
-rw-r--r--src/command/cmd_funcs.c149
-rw-r--r--src/config/cafile.c2
-rw-r--r--src/config/preferences.c10
-rw-r--r--src/config/preferences.h1
-rw-r--r--src/config/theme.c28
-rw-r--r--src/event/server_events.c28
-rw-r--r--src/event/server_events.h2
-rw-r--r--src/log.c10
-rw-r--r--src/tools/http_download.c2
-rw-r--r--src/tools/http_upload.c2
-rw-r--r--src/ui/console.c5
-rw-r--r--src/ui/core.c4
-rw-r--r--src/ui/inputwin.c109
-rw-r--r--src/ui/window.c8
-rw-r--r--src/xmpp/connection.c215
-rw-r--r--src/xmpp/message.c8
-rw-r--r--src/xmpp/ox.c14
19 files changed, 333 insertions, 308 deletions
diff --git a/src/command/cmd_ac.c b/src/command/cmd_ac.c
index 3024ab17..83721f02 100644
--- a/src/command/cmd_ac.c
+++ b/src/command/cmd_ac.c
@@ -146,6 +146,7 @@ static Autocomplete notify_chat_ac;
 static Autocomplete notify_room_ac;
 static Autocomplete notify_typing_ac;
 static Autocomplete notify_mention_ac;
+static Autocomplete notify_offline_ac;
 static Autocomplete notify_trigger_ac;
 static Autocomplete prefs_ac;
 static Autocomplete sub_ac;
@@ -344,6 +345,7 @@ cmd_ac_init(void)
     autocomplete_add(notify_room_ac, "on");
     autocomplete_add(notify_room_ac, "off");
     autocomplete_add(notify_room_ac, "mention");
+    autocomplete_add(notify_room_ac, "offline");
     autocomplete_add(notify_room_ac, "current");
     autocomplete_add(notify_room_ac, "text");
     autocomplete_add(notify_room_ac, "trigger");
@@ -361,6 +363,10 @@ cmd_ac_init(void)
     autocomplete_add(notify_mention_ac, "word_whole");
     autocomplete_add(notify_mention_ac, "word_part");
 
+    notify_offline_ac = autocomplete_new();
+    autocomplete_add(notify_offline_ac, "on");
+    autocomplete_add(notify_offline_ac, "off");
+
     notify_trigger_ac = autocomplete_new();
     autocomplete_add(notify_trigger_ac, "add");
     autocomplete_add(notify_trigger_ac, "remove");
@@ -876,7 +882,6 @@ cmd_ac_init(void)
     autocomplete_add(pgp_sendfile_ac, "on");
     autocomplete_add(pgp_sendfile_ac, "off");
 
-    // XEP-0373: OX
     ox_ac = autocomplete_new();
     autocomplete_add(ox_ac, "keys");
     autocomplete_add(ox_ac, "contacts");
@@ -1437,6 +1442,7 @@ cmd_ac_reset(ProfWin* window)
     autocomplete_reset(pgp_ac);
     autocomplete_reset(pgp_log_ac);
     autocomplete_reset(pgp_sendfile_ac);
+    autocomplete_reset(ox_ac);
 #endif
     autocomplete_reset(tls_ac);
     autocomplete_reset(titlebar_ac);
@@ -1598,6 +1604,9 @@ cmd_ac_uninit(void)
     autocomplete_free(pgp_ac);
     autocomplete_free(pgp_log_ac);
     autocomplete_free(pgp_sendfile_ac);
+    autocomplete_free(ox_ac);
+    autocomplete_free(ox_log_ac);
+    autocomplete_free(ox_sendfile_ac);
 #endif
     autocomplete_free(tls_ac);
     autocomplete_free(titlebar_ac);
@@ -2338,6 +2347,11 @@ _notify_autocomplete(ProfWin* window, const char* const input, gboolean previous
         return result;
     }
 
+    result = autocomplete_param_with_ac(input, "/notify room offline", notify_offline_ac, TRUE, previous);
+    if (result) {
+        return result;
+    }
+
     result = autocomplete_param_with_ac(input, "/notify room trigger", notify_trigger_ac, TRUE, previous);
     if (result) {
         return result;
@@ -2569,6 +2583,11 @@ _ox_autocomplete(ProfWin* window, const char* const input, gboolean previous)
 {
     char* found = NULL;
 
+    found = autocomplete_param_with_ac(input, "/ox", ox_ac, TRUE, previous);
+    if (found) {
+        return found;
+    }
+
     jabber_conn_status_t conn_status = connection_get_status();
 
     if (conn_status == JABBER_CONNECTED) {
@@ -2576,13 +2595,16 @@ _ox_autocomplete(ProfWin* window, const char* const input, gboolean previous)
         if (found) {
             return found;
         }
-    }
 
-    if (conn_status == JABBER_CONNECTED) {
         found = autocomplete_param_with_func(input, "/ox discover", roster_contact_autocomplete, previous, NULL);
         if (found) {
             return found;
         }
+
+        found = autocomplete_param_with_func(input, "/ox setkey", roster_barejid_autocomplete, previous, NULL);
+        if (found) {
+            return found;
+        }
     }
 
     found = autocomplete_param_with_ac(input, "/ox log", ox_log_ac, TRUE, previous);
@@ -2599,16 +2621,7 @@ _ox_autocomplete(ProfWin* window, const char* const input, gboolean previous)
         return cmd_ac_complete_filepath(input, "/ox announce", previous);
     }
 
-    if (conn_status == JABBER_CONNECTED) {
-        found = autocomplete_param_with_func(input, "/ox setkey", roster_barejid_autocomplete, previous, NULL);
-        if (found) {
-            return found;
-        }
-    }
-
-    found = autocomplete_param_with_ac(input, "/ox", ox_ac, TRUE, previous);
-
-    return found;
+    return NULL;
 }
 #endif
 
diff --git a/src/command/cmd_defs.c b/src/command/cmd_defs.c
index 0d4a3b19..a9e45147 100644
--- a/src/command/cmd_defs.c
+++ b/src/command/cmd_defs.c
@@ -1436,6 +1436,7 @@ static struct cmd_t command_defs[] = {
               "/notify room mention on|off",
               "/notify room mention case_sensitive|case_insensitive",
               "/notify room mention word_whole|word_part",
+              "/notify room offline on|off",
               "/notify room current on|off",
               "/notify room text on|off",
               "/notify room trigger add <text>",
@@ -1464,6 +1465,7 @@ static struct cmd_t command_defs[] = {
               { "room mention case_insensitive", "Set room mention notifications as case insensitive." },
               { "room mention word_whole", "Set room mention notifications only on whole word match, i.e. when nickname is not part of a larger word." },
               { "room mention word_part", "Set room mention notifications on partial word match, i.e. nickname may be part of a larger word." },
+              { "room offline on|off", "Notifications for chat room messages that were sent while you were offline." },
               { "room current on|off", "Whether to show all chat room messages notifications when the window is focused." },
               { "room text on|off", "Show message text in chat room message notifications." },
               { "room trigger add <text>", "Notify when specified text included in all chat room messages." },
@@ -1483,6 +1485,7 @@ static struct cmd_t command_defs[] = {
               "/notify chat on",
               "/notify chat text on",
               "/notify room mention on",
+              "/notify room offline on",
               "/notify room trigger add beer",
               "/notify room trigger on",
               "/notify room current off",
@@ -2111,7 +2114,7 @@ static struct cmd_t command_defs[] = {
               { "set <account> tls disable", "Disable TLS for the connection." },
               { "set <account> auth default", "Use default authentication process." },
               { "set <account> auth legacy", "Allow legacy authentication." },
-              { "set <account> <theme>", "Set the UI theme for the account." },
+              { "set <account> theme <theme>", "Set the UI theme for the account." },
               { "clear <account> server", "Remove the server setting for this account." },
               { "clear <account> port", "Remove the port setting for this account." },
               { "clear <account> password", "Remove the password setting for this account." },
diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c
index f8dc151a..e76831fc 100644
--- a/src/command/cmd_funcs.c
+++ b/src/command/cmd_funcs.c
@@ -124,6 +124,62 @@ static void _who_roster(ProfWin* window, const char* const command, gchar** args
 static gboolean _cmd_execute(ProfWin* window, const char* const command, const char* const inp);
 static gboolean _cmd_execute_default(ProfWin* window, const char* inp);
 static gboolean _cmd_execute_alias(ProfWin* window, const char* const inp, gboolean* ran);
+static gboolean
+_string_matches_one_of(const char* what, const char* is, bool is_can_be_null, const char* first, ...) __attribute__((sentinel));
+
+static gboolean
+_string_matches_one_of(const char* what, const char* is, bool is_can_be_null, const char* first, ...)
+{
+    gboolean ret = FALSE;
+    va_list ap;
+    const char* cur = first;
+    if (!is)
+        return is_can_be_null;
+
+    va_start(ap, first);
+    while (cur != NULL) {
+        if (g_strcmp0(is, cur) == 0) {
+            ret = TRUE;
+            break;
+        }
+        cur = va_arg(ap, const char*);
+    }
+    va_end(ap);
+    if (!ret && what) {
+        cons_show("Invalid %s: '%s'", what, is);
+        char errmsg[256] = { 0 };
+        size_t sz = 0;
+        int s = snprintf(errmsg, sizeof(errmsg) - sz, "%s must be one of:", what);
+        if (s < 0 || s + sz >= sizeof(errmsg))
+            return ret;
+        sz += s;
+
+        cur = first;
+        va_start(ap, first);
+        while (cur != NULL) {
+            const char* next = va_arg(ap, const char*);
+            if (next) {
+                s = snprintf(errmsg + sz, sizeof(errmsg) - sz, " '%s',", cur);
+            } else {
+                /* remove last ',' */
+                sz--;
+                errmsg[sz] = '\0';
+                s = snprintf(errmsg + sz, sizeof(errmsg) - sz, " or '%s'.", cur);
+            }
+            if (s < 0 || s + sz >= sizeof(errmsg)) {
+                log_debug("Error message too long or some other error occurred (%d).", s);
+                s = -1;
+                break;
+            }
+            sz += s;
+            cur = next;
+        }
+        va_end(ap);
+        if (s > 0)
+            cons_show(errmsg);
+    }
+    return ret;
+}
 
 /*
  * Take a line of input and process it, return TRUE if profanity is to
@@ -339,7 +395,7 @@ cmd_connect(ProfWin* window, const char* const command, gchar** args)
     char* altdomain = g_hash_table_lookup(options, "server");
 
     char* tls_policy = g_hash_table_lookup(options, "tls");
-    if (tls_policy && (g_strcmp0(tls_policy, "force") != 0) && (g_strcmp0(tls_policy, "allow") != 0) && (g_strcmp0(tls_policy, "trust") != 0) && (g_strcmp0(tls_policy, "disable") != 0) && (g_strcmp0(tls_policy, "legacy") != 0)) {
+    if (!_string_matches_one_of("TLS policy", tls_policy, TRUE, "force", "allow", "trust", "disable", "legacy", NULL)) {
         cons_bad_cmd_usage(command);
         cons_show("");
         options_destroy(options);
@@ -347,7 +403,7 @@ cmd_connect(ProfWin* window, const char* const command, gchar** args)
     }
 
     char* auth_policy = g_hash_table_lookup(options, "auth");
-    if (auth_policy && (g_strcmp0(auth_policy, "default") != 0) && (g_strcmp0(auth_policy, "legacy") != 0)) {
+    if (!_string_matches_one_of("Auth policy", auth_policy, TRUE, "default", "legacy", NULL)) {
         cons_bad_cmd_usage(command);
         cons_show("");
         options_destroy(options);
@@ -732,11 +788,7 @@ _account_set_nick(char* account_name, char* nick)
 gboolean
 _account_set_otr(char* account_name, char* policy)
 {
-    if ((g_strcmp0(policy, "manual") != 0)
-        && (g_strcmp0(policy, "opportunistic") != 0)
-        && (g_strcmp0(policy, "always") != 0)) {
-        cons_show("OTR policy must be one of: manual, opportunistic or always.");
-    } else {
+    if (_string_matches_one_of("OTR policy", policy, FALSE, "manual", "opportunistic", "always", NULL)) {
         accounts_set_otr_policy(account_name, policy);
         cons_show("Updated OTR policy for account %s: %s", account_name, policy);
         cons_show("");
@@ -821,13 +873,7 @@ _account_set_theme(char* account_name, char* theme)
 gboolean
 _account_set_tls(char* account_name, char* policy)
 {
-    if ((g_strcmp0(policy, "force") != 0)
-        && (g_strcmp0(policy, "allow") != 0)
-        && (g_strcmp0(policy, "trust") != 0)
-        && (g_strcmp0(policy, "disable") != 0)
-        && (g_strcmp0(policy, "legacy") != 0)) {
-        cons_show("TLS policy must be one of: force, allow, legacy or disable.");
-    } else {
+    if (_string_matches_one_of("TLS policy", policy, FALSE, "force", "allow", "trust", "disable", "legacy", NULL)) {
         accounts_set_tls_policy(account_name, policy);
         cons_show("Updated TLS policy for account %s: %s", account_name, policy);
         cons_show("");
@@ -838,10 +884,7 @@ _account_set_tls(char* account_name, char* policy)
 gboolean
 _account_set_auth(char* account_name, char* policy)
 {
-    if ((g_strcmp0(policy, "default") != 0)
-        && (g_strcmp0(policy, "legacy") != 0)) {
-        cons_show("Auth policy must be either default or legacy.");
-    } else {
+    if (_string_matches_one_of("Auth policy", policy, FALSE, "default", "legacy", NULL)) {
         accounts_set_auth_policy(account_name, policy);
         cons_show("Updated auth policy for account %s: %s", account_name, policy);
         cons_show("");
@@ -1763,7 +1806,7 @@ _who_room(ProfWin* window, const char* const command, gchar** args)
     }
 
     // bad arg
-    if (args[0] && (g_strcmp0(args[0], "online") != 0) && (g_strcmp0(args[0], "available") != 0) && (g_strcmp0(args[0], "unavailable") != 0) && (g_strcmp0(args[0], "away") != 0) && (g_strcmp0(args[0], "chat") != 0) && (g_strcmp0(args[0], "xa") != 0) && (g_strcmp0(args[0], "dnd") != 0) && (g_strcmp0(args[0], "any") != 0) && (g_strcmp0(args[0], "moderator") != 0) && (g_strcmp0(args[0], "participant") != 0) && (g_strcmp0(args[0], "visitor") != 0) && (g_strcmp0(args[0], "owner") != 0) && (g_strcmp0(args[0], "admin") != 0) && (g_strcmp0(args[0], "member") != 0) && (g_strcmp0(args[0], "outcast") != 0) && (g_strcmp0(args[0], "none") != 0)) {
+    if (!_string_matches_one_of(NULL, args[0], TRUE, "online", "available", "unavailable", "away", "chat", "xa", "dnd", "any", "moderator", "participant", "visitor", "owner", "admin", "member", "outcast", "none", NULL)) {
         cons_bad_cmd_usage(command);
         return;
     }
@@ -1772,7 +1815,7 @@ _who_room(ProfWin* window, const char* const command, gchar** args)
     assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
 
     // presence filter
-    if (args[0] == NULL || (g_strcmp0(args[0], "online") == 0) || (g_strcmp0(args[0], "available") == 0) || (g_strcmp0(args[0], "unavailable") == 0) || (g_strcmp0(args[0], "away") == 0) || (g_strcmp0(args[0], "chat") == 0) || (g_strcmp0(args[0], "xa") == 0) || (g_strcmp0(args[0], "dnd") == 0) || (g_strcmp0(args[0], "any") == 0)) {
+    if (_string_matches_one_of(NULL, args[0], TRUE, "online", "available", "unavailable", "away", "chat", "xa", "dnd", "any", NULL)) {
 
         char* presence = args[0];
         GList* occupants = muc_roster(mucwin->roomjid);
@@ -1871,16 +1914,7 @@ _who_roster(ProfWin* window, const char* const command, gchar** args)
     char* presence = args[0];
 
     // bad arg
-    if (presence
-        && (strcmp(presence, "online") != 0)
-        && (strcmp(presence, "available") != 0)
-        && (strcmp(presence, "unavailable") != 0)
-        && (strcmp(presence, "offline") != 0)
-        && (strcmp(presence, "away") != 0)
-        && (strcmp(presence, "chat") != 0)
-        && (strcmp(presence, "xa") != 0)
-        && (strcmp(presence, "dnd") != 0)
-        && (strcmp(presence, "any") != 0)) {
+    if (!_string_matches_one_of(NULL, presence, TRUE, "online", "available", "unavailable", "offline", "away", "chat", "xa", "dnd", "any", NULL)) {
         cons_bad_cmd_usage(command);
         return;
     }
@@ -3773,7 +3807,7 @@ cmd_form_field(ProfWin* window, char* tag, gchar** args)
             if (cmd) {
                 value = args[1];
             }
-            if ((g_strcmp0(cmd, "add") != 0) && (g_strcmp0(cmd, "remove"))) {
+            if (!_string_matches_one_of(NULL, cmd, FALSE, "add", "remove", NULL)) {
                 win_println(window, THEME_DEFAULT, "-", "Invalid command, usage:");
                 confwin_field_help(confwin, tag);
                 win_println(window, THEME_DEFAULT, "-", "");
@@ -3827,7 +3861,7 @@ cmd_form_field(ProfWin* window, char* tag, gchar** args)
             if (cmd) {
                 value = args[1];
             }
-            if ((g_strcmp0(cmd, "add") != 0) && (g_strcmp0(cmd, "remove"))) {
+            if (!_string_matches_one_of(NULL, cmd, FALSE, "add", "remove", NULL)) {
                 win_println(window, THEME_DEFAULT, "-", "Invalid command, usage:");
                 confwin_field_help(confwin, tag);
                 win_println(window, THEME_DEFAULT, "-", "");
@@ -3878,7 +3912,7 @@ cmd_form_field(ProfWin* window, char* tag, gchar** args)
             if (cmd) {
                 value = args[1];
             }
-            if ((g_strcmp0(cmd, "add") != 0) && (g_strcmp0(cmd, "remove"))) {
+            if (!_string_matches_one_of(NULL, cmd, FALSE, "add", "remove", NULL)) {
                 win_println(window, THEME_DEFAULT, "-", "Invalid command, usage:");
                 confwin_field_help(confwin, tag);
                 win_println(window, THEME_DEFAULT, "-", "");
@@ -3934,7 +3968,7 @@ cmd_form(ProfWin* window, const char* const command, gchar** args)
         return TRUE;
     }
 
-    if ((g_strcmp0(args[0], "submit") != 0) && (g_strcmp0(args[0], "cancel") != 0) && (g_strcmp0(args[0], "show") != 0) && (g_strcmp0(args[0], "help") != 0)) {
+    if (!_string_matches_one_of(NULL, args[0], FALSE, "submit", "cancel", "show", "help", NULL)) {
         cons_bad_cmd_usage(command);
         return TRUE;
     }
@@ -4183,7 +4217,7 @@ cmd_affiliation(ProfWin* window, const char* const command, gchar** args)
     }
 
     char* affiliation = args[1];
-    if (affiliation && (g_strcmp0(affiliation, "owner") != 0) && (g_strcmp0(affiliation, "admin") != 0) && (g_strcmp0(affiliation, "member") != 0) && (g_strcmp0(affiliation, "none") != 0) && (g_strcmp0(affiliation, "outcast") != 0)) {
+    if (!_string_matches_one_of(NULL, affiliation, TRUE, "owner", "admin", "member", "none", "outcast", NULL)) {
         cons_bad_cmd_usage(command);
         return TRUE;
     }
@@ -4258,7 +4292,7 @@ cmd_role(ProfWin* window, const char* const command, gchar** args)
     }
 
     char* role = args[1];
-    if (role && (g_strcmp0(role, "visitor") != 0) && (g_strcmp0(role, "participant") != 0) && (g_strcmp0(role, "moderator") != 0) && (g_strcmp0(role, "none") != 0)) {
+    if (!_string_matches_one_of(NULL, role, TRUE, "visitor", "participant", "moderator", "none", NULL)) {
         cons_bad_cmd_usage(command);
         return TRUE;
     }
@@ -5242,13 +5276,13 @@ cmd_console(ProfWin* window, const char* const command, gchar** args)
 {
     gboolean isMuc = (g_strcmp0(args[0], "muc") == 0);
 
-    if ((g_strcmp0(args[0], "chat") != 0) && !isMuc && (g_strcmp0(args[0], "private") != 0)) {
+    if (!_string_matches_one_of(NULL, args[0], FALSE, "chat", "private", NULL) && !isMuc) {
         cons_bad_cmd_usage(command);
         return TRUE;
     }
 
     gchar* setting = args[1];
-    if ((g_strcmp0(setting, "all") != 0) && (g_strcmp0(setting, "first") != 0) && (g_strcmp0(setting, "none") != 0)) {
+    if (!_string_matches_one_of(NULL, setting, FALSE, "all", "first", "none", NULL)) {
         if (!(isMuc && (g_strcmp0(setting, "mention") == 0))) {
             cons_bad_cmd_usage(command);
             return TRUE;
@@ -5741,6 +5775,16 @@ cmd_notify(ProfWin* window, const char* const command, gchar** args)
             } else {
                 cons_show("Usage: /notify room mention on|off");
             }
+        } else if (g_strcmp0(args[1], "offline") == 0) {
+            if (g_strcmp0(args[2], "on") == 0) {
+                cons_show("Room notifications for offline messages enabled.");
+                prefs_set_boolean(PREF_NOTIFY_ROOM_OFFLINE, TRUE);
+            } else if (g_strcmp0(args[2], "off") == 0) {
+                cons_show("Room notifications for offline messages disabled.");
+                prefs_set_boolean(PREF_NOTIFY_ROOM_OFFLINE, FALSE);
+            } else {
+                cons_show("Usage: /notify room offline on|off");
+            }
         } else if (g_strcmp0(args[1], "current") == 0) {
             if (g_strcmp0(args[2], "on") == 0) {
                 cons_show("Current window chat room message notifications enabled.");
@@ -6558,15 +6602,12 @@ cmd_ping(ProfWin* window, const char* const command, gchar** args)
 gboolean
 cmd_autoaway(ProfWin* window, const char* const command, gchar** args)
 {
-    if ((g_strcmp0(args[0], "mode") != 0) && (g_strcmp0(args[0], "time") != 0) && (g_strcmp0(args[0], "message") != 0) && (g_strcmp0(args[0], "check") != 0)) {
-        cons_show("Setting must be one of 'mode', 'time', 'message' or 'check'");
+    if (!_string_matches_one_of("Setting", args[0], FALSE, "mode", "time", "message", "check", NULL)) {
         return TRUE;
     }
 
     if (g_strcmp0(args[0], "mode") == 0) {
-        if ((g_strcmp0(args[1], "idle") != 0) && (g_strcmp0(args[1], "away") != 0) && (g_strcmp0(args[1], "off") != 0)) {
-            cons_show("Mode must be one of 'idle', 'away' or 'off'");
-        } else {
+        if (_string_matches_one_of("Mode", args[1], FALSE, "idle", "away", "off", NULL)) {
             prefs_set_string(PREF_AUTOAWAY_MODE, args[1]);
             cons_show("Auto away mode set to: %s.", args[1]);
         }
@@ -7472,11 +7513,6 @@ cmd_pgp(ProfWin* window, const char* const command, gchar** args)
 
 #ifdef HAVE_LIBGPGME
 
-/*!
- * \brief Command for XEP-0373: OpenPGP for XMPP
- *
- */
-
 gboolean
 cmd_ox(ProfWin* window, const char* const command, gchar** args)
 {
@@ -7669,7 +7705,7 @@ cmd_ox(ProfWin* window, const char* const command, gchar** args)
             cons_show("JID and OpenPGP Key ID are required");
         }
     } else {
-        cons_show("OX not implemented");
+        cons_bad_cmd_usage(command);
     }
     return TRUE;
 }
@@ -7752,8 +7788,7 @@ cmd_otr_policy(ProfWin* window, const char* const command, gchar** args)
     }
 
     char* choice = args[1];
-    if ((g_strcmp0(choice, "manual") != 0) && (g_strcmp0(choice, "opportunistic") != 0) && (g_strcmp0(choice, "always") != 0)) {
-        cons_show("OTR policy can be set to: manual, opportunistic or always.");
+    if (!_string_matches_one_of("OTR policy", choice, FALSE, "manual", "opportunistic", "always", NULL)) {
         return TRUE;
     }
 
@@ -8942,8 +8977,7 @@ cmd_omemo_policy(ProfWin* window, const char* const command, gchar** args)
     }
 
     char* choice = args[1];
-    if ((g_strcmp0(choice, "manual") != 0) && (g_strcmp0(choice, "automatic") != 0) && (g_strcmp0(choice, "always") != 0)) {
-        cons_show("OMEMO policy can be set to: manual, automatic or always.");
+    if (!_string_matches_one_of("OMEMO policy", choice, FALSE, "manual", "automatic", "always", NULL)) {
         return TRUE;
     }
 
@@ -9533,13 +9567,6 @@ cmd_change_password(ProfWin* window, const char* const command, gchar** args)
 gboolean
 cmd_editor(ProfWin* window, const char* const command, gchar** args)
 {
-    jabber_conn_status_t conn_status = connection_get_status();
-
-    if (conn_status != JABBER_CONNECTED) {
-        cons_show("You are currently not connected.");
-        return TRUE;
-    }
-
     gchar* message = NULL;
 
     if (get_message_from_editor(NULL, &message)) {
@@ -9607,7 +9634,7 @@ cmd_register(ProfWin* window, const char* const command, gchar** args)
     }
 
     char* tls_policy = g_hash_table_lookup(options, "tls");
-    if (tls_policy && (g_strcmp0(tls_policy, "force") != 0) && (g_strcmp0(tls_policy, "allow") != 0) && (g_strcmp0(tls_policy, "trust") != 0) && (g_strcmp0(tls_policy, "disable") != 0) && (g_strcmp0(tls_policy, "legacy") != 0)) {
+    if (!_string_matches_one_of("TLS policy", tls_policy, TRUE, "force", "allow", "trust", "disable", "legacy", NULL)) {
         cons_bad_cmd_usage(command);
         cons_show("");
         options_destroy(options);
diff --git a/src/config/cafile.c b/src/config/cafile.c
index 4ac832bf..2bb3c4d0 100644
--- a/src/config/cafile.c
+++ b/src/config/cafile.c
@@ -33,6 +33,8 @@
  *
  */
 
+#include "config.h"
+
 #include <fcntl.h>
 #include <glib.h>
 #include <errno.h>
diff --git a/src/config/preferences.c b/src/config/preferences.c
index ec536762..1a01ab66 100644
--- a/src/config/preferences.c
+++ b/src/config/preferences.c
@@ -1830,6 +1830,7 @@ _get_group(preference_t pref)
     case PREF_NOTIFY_CHAT_TEXT:
     case PREF_NOTIFY_ROOM:
     case PREF_NOTIFY_ROOM_MENTION:
+    case PREF_NOTIFY_ROOM_OFFLINE:
     case PREF_NOTIFY_ROOM_TRIGGER:
     case PREF_NOTIFY_ROOM_CURRENT:
     case PREF_NOTIFY_ROOM_TEXT:
@@ -1966,6 +1967,8 @@ _get_key(preference_t pref)
         return "room.trigger";
     case PREF_NOTIFY_ROOM_MENTION:
         return "room.mention";
+    case PREF_NOTIFY_ROOM_OFFLINE:
+        return "room.offline";
     case PREF_NOTIFY_ROOM_CURRENT:
         return "room.current";
     case PREF_NOTIFY_ROOM_TEXT:
@@ -2220,8 +2223,6 @@ _get_default_string(preference_t pref)
     case PREF_OTR_POLICY:
         return "manual";
     case PREF_STATUSES_CONSOLE:
-    case PREF_STATUSES_CHAT:
-    case PREF_STATUSES_MUC:
         return "all";
     case PREF_ROSTER_BY:
         return "presence";
@@ -2234,6 +2235,8 @@ _get_default_string(preference_t pref)
     case PREF_ROSTER_ROOMS_POS:
         return "last";
     case PREF_ROSTER_ROOMS_BY:
+    case PREF_STATUSES_CHAT:
+    case PREF_STATUSES_MUC:
         return "none";
     case PREF_ROSTER_ROOMS_USE_AS_NAME:
         return "name";
@@ -2283,7 +2286,8 @@ _get_default_string(preference_t pref)
         return "xdg-open";
     case PREF_URL_OPEN_CMD:
         return "xdg-open %u";
-    case PREF_COMPOSE_EDITOR: {
+    case PREF_COMPOSE_EDITOR:
+    {
         gchar* editor = getenv("EDITOR");
         return editor ? editor : "vim";
     }
diff --git a/src/config/preferences.h b/src/config/preferences.h
index 6c8ad125..b663e82a 100644
--- a/src/config/preferences.h
+++ b/src/config/preferences.h
@@ -178,6 +178,7 @@ typedef enum {
     PREF_SILENCE_NON_ROSTER,
     PREF_OUTGOING_STAMP,
     PREF_INCOMING_STAMP,
+    PREF_NOTIFY_ROOM_OFFLINE,
 } preference_t;
 
 typedef struct prof_alias_t
diff --git a/src/config/theme.c b/src/config/theme.c
index 9becc833..38eb3f2e 100644
--- a/src/config/theme.c
+++ b/src/config/theme.c
@@ -184,6 +184,9 @@ theme_exists(const char* const theme_name)
 gboolean
 theme_load(const char* const theme_name, gboolean load_theme_prefs)
 {
+    if (!theme_exists(theme_name))
+        return FALSE;
+
     color_pair_cache_reset();
 
     if (_theme_load_file(theme_name)) {
@@ -663,20 +666,21 @@ theme_get_bkgnd(void)
 static void
 _theme_prep_fgnd(char* setting, GString* lookup_str, gboolean* bold)
 {
-    gchar* val = g_key_file_get_string(theme, "colours", setting, NULL);
-    if (!val) {
-        char* def = g_hash_table_lookup(defaults, setting);
-        g_string_append(lookup_str, def);
+    gchar* conf_str = g_key_file_get_string(theme, "colours", setting, NULL);
+    gchar* val = conf_str;
+
+    if (!val)
+        val = g_hash_table_lookup(defaults, setting);
+
+    if (g_str_has_prefix(val, "bold_")) {
+        g_string_append(lookup_str, &val[5]);
+        *bold = TRUE;
     } else {
-        if (g_str_has_prefix(val, "bold_")) {
-            g_string_append(lookup_str, &val[5]);
-            *bold = TRUE;
-        } else {
-            g_string_append(lookup_str, val);
-            *bold = FALSE;
-        }
+        g_string_append(lookup_str, val);
+        *bold = FALSE;
     }
-    g_free(val);
+
+    g_free(conf_str);
 }
 
 char*
diff --git a/src/event/server_events.c b/src/event/server_events.c
index 4a35302f..620a8b2d 100644
--- a/src/event/server_events.c
+++ b/src/event/server_events.c
@@ -50,6 +50,7 @@
 #include "config/cafile.h"
 #include "config/scripts.h"
 #include "event/client_events.h"
+#include "event/server_events.h"
 #include "event/common.h"
 #include "plugins/plugins.h"
 #include "ui/window_list.h"
@@ -272,6 +273,33 @@ sv_ev_room_subject(const char* const room, const char* const nick, const char* c
 void
 sv_ev_room_history(ProfMessage* message)
 {
+    if (prefs_get_boolean(PREF_NOTIFY_ROOM_OFFLINE)) {
+        // check if this message was sent while we were offline.
+        // if so, treat it as a new message rather than a history event.
+        char* account_name = session_get_account_name();
+        char* last_activity = accounts_get_last_activity(account_name);
+        int msg_is_new = 0;
+
+        if (last_activity) {
+            GTimeVal lasttv;
+
+            if (g_time_val_from_iso8601(last_activity, &lasttv)) {
+                GDateTime* lastdt = g_date_time_new_from_timeval_utc(&lasttv);
+                GDateTime* msgdt = message->timestamp;
+                GTimeSpan time_diff = g_date_time_difference(msgdt, lastdt);
+
+                msg_is_new = (time_diff > 0);
+                g_date_time_unref(lastdt);
+            }
+            g_free(last_activity);
+
+            if (msg_is_new) {
+                sv_ev_room_message(message);
+                return;
+            }
+        }
+    }
+
     ProfMucWin* mucwin = wins_get_muc(message->from_jid->barejid);
     if (mucwin) {
         // if this is the first successful connection
diff --git a/src/event/server_events.h b/src/event/server_events.h
index 55818642..53bb27f0 100644
--- a/src/event/server_events.h
+++ b/src/event/server_events.h
@@ -55,7 +55,7 @@ void sv_ev_delayed_private_message(ProfMessage* message);
 void sv_ev_typing(char* barejid, char* resource);
 void sv_ev_paused(char* barejid, char* resource);
 void sv_ev_inactive(char* barejid, char* resource);
-void sv_ev_activity(char* barejid, char* resource, gboolean send_states);
+void sv_ev_activity(const char* barejid, const char* resource, gboolean send_states);
 void sv_ev_gone(const char* const barejid, const char* const resource);
 void sv_ev_subscription(const char* from, jabber_subscr_t type);
 void sv_ev_message_receipt(const char* const barejid, const char* const id);
diff --git a/src/log.c b/src/log.c
index 546f7804..1f6eaa9d 100644
--- a/src/log.c
+++ b/src/log.c
@@ -679,8 +679,16 @@ _get_log_filename(const char* const other, const char* const login, GDateTime* d
 {
     gchar* chatlogs_dir = files_file_in_account_data_path(DIR_CHATLOGS, login, is_room ? "rooms" : NULL);
     gchar* logfile_name = g_date_time_format(dt, "%Y_%m_%d.log");
-    gchar* logfile_path = files_file_in_account_data_path(chatlogs_dir, other, logfile_name);
+    gchar* other_ = str_replace(other, "@", "_at_");
+    gchar* logs_path = g_strdup_printf("%s/%s", chatlogs_dir, other_);
+    gchar* logfile_path = NULL;
 
+    if (create_dir(logs_path)) {
+        logfile_path = g_strdup_printf("%s/%s", logs_path, logfile_name);
+    }
+
+    g_free(logs_path);
+    g_free(other_);
     g_free(logfile_name);
     g_free(chatlogs_dir);
 
diff --git a/src/tools/http_download.c b/src/tools/http_download.c
index 57e36e61..bbbccce1 100644
--- a/src/tools/http_download.c
+++ b/src/tools/http_download.c
@@ -128,7 +128,7 @@ http_file_get(void* userdata)
     char* cert_path = prefs_get_string(PREF_TLS_CERTPATH);
     gchar* cafile = cafile_get_name();
     ProfAccount* account = accounts_get_account(session_get_account_name());
-    gboolean insecure = strcmp(account->tls_policy, "trust") == 0;
+    gboolean insecure = account->tls_policy && strcmp(account->tls_policy, "trust") == 0;
     account_free(account);
     pthread_mutex_unlock(&lock);
 
diff --git a/src/tools/http_upload.c b/src/tools/http_upload.c
index 5b783441..4dae41c5 100644
--- a/src/tools/http_upload.c
+++ b/src/tools/http_upload.c
@@ -187,7 +187,7 @@ http_file_put(void* userdata)
     char* cert_path = prefs_get_string(PREF_TLS_CERTPATH);
     gchar* cafile = cafile_get_name();
     ProfAccount* account = accounts_get_account(session_get_account_name());
-    gboolean insecure = strcmp(account->tls_policy, "trust") == 0;
+    gboolean insecure = account->tls_policy && strcmp(account->tls_policy, "trust") == 0;
     account_free(account);
     pthread_mutex_unlock(&lock);
 
diff --git a/src/ui/console.c b/src/ui/console.c
index 170def7c..08ccba17 100644
--- a/src/ui/console.c
+++ b/src/ui/console.c
@@ -1717,6 +1717,11 @@ cons_notify_setting(void)
     else
         cons_show("Room mention (/notify room)         : OFF");
 
+    if (prefs_get_boolean(PREF_NOTIFY_ROOM_OFFLINE))
+        cons_show("Room offline messages (/notify room): ON");
+    else
+        cons_show("Room offline messages (/notify room): OFF");
+
     if (prefs_get_boolean(PREF_NOTIFY_MENTION_CASE_SENSITIVE))
         cons_show("Room mention case (/notify room)    : Case sensitive");
     else
diff --git a/src/ui/core.c b/src/ui/core.c
index 3b10888b..5ce42e50 100644
--- a/src/ui/core.c
+++ b/src/ui/core.c
@@ -144,10 +144,8 @@ ui_update(void)
     doupdate();
 
     if (perform_resize) {
-        signal(SIGWINCH, SIG_IGN);
-        ui_resize();
         perform_resize = FALSE;
-        signal(SIGWINCH, ui_sigwinch_handler);
+        ui_resize();
     }
 }
 
diff --git a/src/ui/inputwin.c b/src/ui/inputwin.c
index d19d8719..caab9ff1 100644
--- a/src/ui/inputwin.c
+++ b/src/ui/inputwin.c
@@ -95,6 +95,7 @@ static int _inp_edited(const wint_t ch);
 static void _inp_win_handle_scroll(void);
 static int _inp_offset_to_col(char* str, int offset);
 static void _inp_write(char* line, int offset);
+static void _inp_redisplay(void);
 
 static void _inp_rl_addfuncs(void);
 static int _inp_rl_getc(FILE* stream);
@@ -149,6 +150,7 @@ create_input_window(void)
     rl_readline_name = "profanity";
     _inp_rl_addfuncs();
     rl_getc_function = _inp_rl_getc;
+    rl_redisplay_function = _inp_redisplay;
     rl_startup_hook = _inp_rl_startup_hook;
     rl_callback_handler_install(NULL, _inp_rl_linehandler);
 
@@ -190,9 +192,6 @@ inp_readline(void)
         }
 
         ui_reset_idle_time();
-        if (!get_password) {
-            _inp_write(rl_line_buffer, rl_point);
-        }
         inp_nonblocking(TRUE);
     } else {
         inp_nonblocking(FALSE);
@@ -320,9 +319,39 @@ _inp_win_update_virtual(void)
 static void
 _inp_write(char* line, int offset)
 {
+    int x;
+    int y __attribute__((unused));
     int col = _inp_offset_to_col(line, offset);
     werase(inp_win);
-    waddstr(inp_win, line);
+
+    waddstr(inp_win, rl_display_prompt);
+    getyx(inp_win, y, x);
+    col += x;
+
+    for (size_t i = 0; line[i] != '\0'; i++) {
+        char* c = &line[i];
+        char retc[MB_CUR_MAX];
+
+        size_t ch_len = mbrlen(c, MB_CUR_MAX, NULL);
+        if ((ch_len == (size_t)-2) || (ch_len == (size_t)-1)) {
+            waddch(inp_win, ' ');
+            continue;
+        }
+
+        if (line[i] == '\n') {
+            c = retc;
+            ch_len = wctomb(retc, L'\u23ce'); /* return symbol */
+            if (ch_len == -1) {               /* not representable */
+                retc[0] = '\\';
+                ch_len = 1;
+            }
+        } else {
+            i += ch_len - 1;
+        }
+
+        waddnstr(inp_win, c, ch_len);
+    }
+
     wmove(inp_win, 0, col);
     _inp_win_handle_scroll();
 
@@ -373,6 +402,10 @@ _inp_offset_to_col(char* str, int offset)
     while (i < offset && str[i] != '\0') {
         gunichar uni = g_utf8_get_char(&str[i]);
         size_t ch_len = mbrlen(&str[i], MB_CUR_MAX, NULL);
+        if ((ch_len == (size_t)-2) || (ch_len == (size_t)-1)) {
+            i++;
+            continue;
+        }
         i += ch_len;
         col++;
         if (g_unichar_iswide(uni)) {
@@ -430,13 +463,18 @@ _inp_rl_addfuncs(void)
     rl_add_funmap_entry("prof_win_prev", _inp_rl_win_prev_handler);
     rl_add_funmap_entry("prof_win_next", _inp_rl_win_next_handler);
     rl_add_funmap_entry("prof_win_next_unread", _inp_rl_win_next_unread_handler);
+    rl_add_funmap_entry("prof_win_set_attention", _inp_rl_win_attention_handler);
+    rl_add_funmap_entry("prof_win_attention_next", _inp_rl_win_attention_next_handler);
     rl_add_funmap_entry("prof_win_pageup", _inp_rl_win_pageup_handler);
     rl_add_funmap_entry("prof_win_pagedown", _inp_rl_win_pagedown_handler);
     rl_add_funmap_entry("prof_subwin_pageup", _inp_rl_subwin_pageup_handler);
     rl_add_funmap_entry("prof_subwin_pagedown", _inp_rl_subwin_pagedown_handler);
+    rl_add_funmap_entry("prof_complete_next", _inp_rl_tab_handler);
+    rl_add_funmap_entry("prof_complete_prev", _inp_rl_shift_tab_handler);
     rl_add_funmap_entry("prof_win_clear", _inp_rl_win_clear_handler);
     rl_add_funmap_entry("prof_win_close", _inp_rl_win_close_handler);
     rl_add_funmap_entry("prof_send_to_editor", _inp_rl_send_to_editor);
+    rl_add_funmap_entry("prof_cut_to_history", _inp_rl_down_arrow_handler);
 }
 
 // Readline callbacks
@@ -479,10 +517,12 @@ _inp_rl_startup_hook(void)
     rl_bind_keyseq("\\e[1;9D", _inp_rl_win_prev_handler);
     rl_bind_keyseq("\\e[1;3D", _inp_rl_win_prev_handler);
     rl_bind_keyseq("\\e\\e[D", _inp_rl_win_prev_handler);
+    rl_bind_keyseq("\\e\\eOD", _inp_rl_win_prev_handler);
 
     rl_bind_keyseq("\\e[1;9C", _inp_rl_win_next_handler);
     rl_bind_keyseq("\\e[1;3C", _inp_rl_win_next_handler);
     rl_bind_keyseq("\\e\\e[C", _inp_rl_win_next_handler);
+    rl_bind_keyseq("\\e\\eOC", _inp_rl_win_next_handler);
 
     rl_bind_keyseq("\\ea", _inp_rl_win_next_unread_handler);
     rl_bind_keyseq("\\ev", _inp_rl_win_attention_handler);
@@ -506,6 +546,7 @@ _inp_rl_startup_hook(void)
     rl_bind_keyseq("\\e[Z", _inp_rl_shift_tab_handler);
 
     rl_bind_keyseq("\\e[1;5B", _inp_rl_down_arrow_handler); // ctrl+arrow down
+    rl_bind_keyseq("\\eOb", _inp_rl_down_arrow_handler);
 
     // unbind unwanted mappings
     rl_bind_keyseq("\\e=", NULL);
@@ -566,6 +607,14 @@ _inp_rl_getc(FILE* stream)
     return ch;
 }
 
+static void
+_inp_redisplay(void)
+{
+    if (!get_password) {
+        _inp_write(rl_line_buffer, rl_point);
+    }
+}
+
 static int
 _inp_rl_win_clear_handler(int count, int key)
 {
@@ -584,7 +633,7 @@ _inp_rl_win_close_handler(int count, int key)
 }
 
 static int
-_inp_rl_tab_handler(int count, int key)
+_inp_rl_tab_com_handler(int count, int key, gboolean previous)
 {
     if (rl_point != rl_end || !rl_line_buffer) {
         return 0;
@@ -592,7 +641,7 @@ _inp_rl_tab_handler(int count, int key)
 
     if (strncmp(rl_line_buffer, "/", 1) == 0) {
         ProfWin* window = wins_get_current();
-        char* result = cmd_ac_complete(window, rl_line_buffer, FALSE);
+        char* result = cmd_ac_complete(window, rl_line_buffer, previous);
         if (result) {
             rl_replace_line(result, 1);
             rl_point = rl_end;
@@ -603,7 +652,7 @@ _inp_rl_tab_handler(int count, int key)
 
     if (strncmp(rl_line_buffer, ">", 1) == 0) {
         ProfWin* window = wins_get_current();
-        char* result = win_quote_autocomplete(window, rl_line_buffer, FALSE);
+        char* result = win_quote_autocomplete(window, rl_line_buffer, previous);
         if (result) {
             rl_replace_line(result, 1);
             rl_point = rl_end;
@@ -614,7 +663,7 @@ _inp_rl_tab_handler(int count, int key)
 
     ProfWin* current = wins_get_current();
     if (current->type == WIN_MUC) {
-        char* result = muc_autocomplete(current, rl_line_buffer, FALSE);
+        char* result = muc_autocomplete(current, rl_line_buffer, previous);
         if (result) {
             rl_replace_line(result, 1);
             rl_point = rl_end;
@@ -626,45 +675,15 @@ _inp_rl_tab_handler(int count, int key)
 }
 
 static int
-_inp_rl_shift_tab_handler(int count, int key)
+_inp_rl_tab_handler(int count, int key)
 {
-    if (rl_point != rl_end || !rl_line_buffer) {
-        return 0;
-    }
-
-    if (strncmp(rl_line_buffer, "/", 1) == 0) {
-        ProfWin* window = wins_get_current();
-        char* result = cmd_ac_complete(window, rl_line_buffer, TRUE);
-        if (result) {
-            rl_replace_line(result, 1);
-            rl_point = rl_end;
-            free(result);
-            return 0;
-        }
-    }
-
-    if (strncmp(rl_line_buffer, ">", 1) == 0) {
-        ProfWin* window = wins_get_current();
-        char* result = win_quote_autocomplete(window, rl_line_buffer, TRUE);
-        if (result) {
-            rl_replace_line(result, 1);
-            rl_point = rl_end;
-            free(result);
-            return 0;
-        }
-    }
-
-    ProfWin* current = wins_get_current();
-    if (current->type == WIN_MUC) {
-        char* result = muc_autocomplete(current, rl_line_buffer, TRUE);
-        if (result) {
-            rl_replace_line(result, 1);
-            rl_point = rl_end;
-            free(result);
-        }
-    }
+    return _inp_rl_tab_com_handler(count, key, FALSE);
+}
 
-    return 0;
+static int
+_inp_rl_shift_tab_handler(int count, int key)
+{
+    return _inp_rl_tab_com_handler(count, key, TRUE);
 }
 
 static void
diff --git a/src/ui/window.c b/src/ui/window.c
index 4283d61a..7d0e6381 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -415,6 +415,7 @@ win_get_last_sent_message(ProfWin* window)
         ProfMucWin* mucwin = (ProfMucWin*)window;
         assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
         last_message = mucwin->last_message;
+        break;
     }
     default:
         break;
@@ -1270,6 +1271,7 @@ win_print_history(ProfWin* window, const ProfMessage* const message)
         display_name = strdup("me");
     } else {
         display_name = roster_get_msg_display_name(message->from_jid->barejid, message->from_jid->resourcepart);
+        flags = NO_ME;
     }
 
     jid_destroy(jidp);
@@ -1579,7 +1581,7 @@ _win_print_internal(ProfWin* window, const char* show_char, int pad_indent, GDat
 
         char* color_pref = prefs_get_string(PREF_COLOR_NICK);
         if (color_pref != NULL && (strcmp(color_pref, "false") != 0)) {
-            if (flags & NO_ME || (!(flags & NO_ME) && prefs_get_boolean(PREF_COLOR_NICK_OWN))) {
+            if ((flags & NO_ME) || (!(flags & NO_ME) && prefs_get_boolean(PREF_COLOR_NICK_OWN))) {
                 colour = theme_hash_attrs(from);
             }
         }
@@ -2034,8 +2036,8 @@ win_quote_autocomplete(ProfWin* window, const char* const input, gboolean previo
         return NULL;
     }
 
-    gchar **parts = g_strsplit(result, "\n", -1);
-    gchar *quoted_result = g_strjoinv("\n> ", parts);
+    gchar** parts = g_strsplit(result, "\n", -1);
+    gchar* quoted_result = g_strjoinv("\n> ", parts);
 
     GString* replace_with = g_string_new("> ");
     g_string_append(replace_with, quoted_result);
diff --git a/src/xmpp/connection.c b/src/xmpp/connection.c
index 54bb1449..9505622a 100644
--- a/src/xmpp/connection.c
+++ b/src/xmpp/connection.c
@@ -58,7 +58,6 @@
 
 typedef struct prof_conn_t
 {
-    xmpp_log_t* xmpp_log;
     xmpp_ctx_t* xmpp_ctx;
     xmpp_conn_t* xmpp_conn;
     gboolean xmpp_in_event_loop;
@@ -82,25 +81,51 @@ static ProfConnection conn;
 static gchar* profanity_instance_id = NULL;
 static gchar* prof_identifier = NULL;
 
-static xmpp_log_t* _xmpp_get_file_logger(void);
 static void _xmpp_file_logger(void* const userdata, const xmpp_log_level_t level, const char* const area, const char* const msg);
 
 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);
 
-TLSCertificate* _xmppcert_to_profcert(const xmpp_tlscert_t* xmpptlscert);
+static TLSCertificate* _xmppcert_to_profcert(const xmpp_tlscert_t* xmpptlscert);
 static int _connection_certfail_cb(const xmpp_tlscert_t* xmpptlscert, const char* errormsg);
 
 static void _random_bytes_init(void);
 static void _random_bytes_close(void);
 static void _compute_identifier(const char* barejid);
 
+static void*
+_xmalloc(size_t size, void* userdata)
+{
+    void* ret = malloc(size);
+    assert(ret != NULL);
+    return ret;
+}
+
+static void
+_xfree(void* p, void* userdata)
+{
+    free(p);
+}
+
+static void*
+_xrealloc(void* p, size_t size, void* userdata)
+{
+    void* ret = realloc(p, size);
+    assert(ret != NULL);
+    return ret;
+}
+
+static xmpp_mem_t prof_mem = {
+    _xmalloc, _xfree, _xrealloc, NULL
+};
+static xmpp_log_t prof_log = {
+    _xmpp_file_logger, NULL
+};
+
 void
 connection_init(void)
 {
     xmpp_initialize();
-    conn.xmpp_conn = NULL;
-    conn.xmpp_ctx = NULL;
     conn.xmpp_in_event_loop = FALSE;
     conn.conn_status = JABBER_DISCONNECTED;
     conn.conn_last_event = XMPP_CONN_DISCONNECT;
@@ -110,6 +135,9 @@ connection_init(void)
     conn.available_resources = g_hash_table_new_full(g_str_hash, g_str_equal, free, (GDestroyNotify)resource_destroy);
     conn.requested_features = g_hash_table_new_full(g_str_hash, g_str_equal, free, NULL);
 
+    conn.xmpp_ctx = xmpp_ctx_new(&prof_mem, &prof_log);
+    conn.xmpp_conn = xmpp_conn_new(conn.xmpp_ctx);
+
     _random_bytes_init();
 }
 
@@ -125,61 +153,38 @@ void
 connection_shutdown(void)
 {
     connection_clear_data();
+    if (conn.xmpp_conn) {
+        xmpp_conn_release(conn.xmpp_conn);
+        conn.xmpp_conn = NULL;
+    }
+    if (conn.xmpp_ctx) {
+        xmpp_ctx_free(conn.xmpp_ctx);
+        conn.xmpp_ctx = NULL;
+    }
     xmpp_shutdown();
 
-    free(conn.xmpp_log);
-    conn.xmpp_log = NULL;
-
     _random_bytes_close();
 }
 
-jabber_conn_status_t
-connection_connect(const char* const jid, const char* const passwd, const char* const altdomain, int port,
-                   const char* const tls_policy, const char* const auth_policy)
+static gboolean
+_conn_apply_settings(const char* const jid, const char* const passwd, const char* const tls_policy, const char* const auth_policy)
 {
-    long flags;
-
-    assert(jid != NULL);
-    assert(passwd != NULL);
-
     Jid* jidp = jid_create(jid);
     if (jidp == NULL) {
         log_error("Malformed JID not able to connect: %s", jid);
         conn.conn_status = JABBER_DISCONNECTED;
-        return conn.conn_status;
+        return FALSE;
     }
 
     _compute_identifier(jidp->barejid);
     jid_destroy(jidp);
 
-    log_info("Connecting as %s", jid);
-
-    if (conn.xmpp_log) {
-        free(conn.xmpp_log);
-    }
-    conn.xmpp_log = _xmpp_get_file_logger();
-
-    if (conn.xmpp_conn) {
-        xmpp_conn_release(conn.xmpp_conn);
-    }
-    if (conn.xmpp_ctx) {
-        xmpp_ctx_free(conn.xmpp_ctx);
-    }
-    conn.xmpp_ctx = xmpp_ctx_new(NULL, conn.xmpp_log);
-    if (conn.xmpp_ctx == NULL) {
-        log_warning("Failed to get libstrophe ctx during connect");
-        return JABBER_DISCONNECTED;
-    }
     xmpp_ctx_set_verbosity(conn.xmpp_ctx, 0);
-    conn.xmpp_conn = xmpp_conn_new(conn.xmpp_ctx);
-    if (conn.xmpp_conn == NULL) {
-        log_warning("Failed to get libstrophe conn during connect");
-        return JABBER_DISCONNECTED;
-    }
     xmpp_conn_set_jid(conn.xmpp_conn, jid);
-    xmpp_conn_set_pass(conn.xmpp_conn, passwd);
+    if (passwd)
+        xmpp_conn_set_pass(conn.xmpp_conn, passwd);
 
-    flags = xmpp_conn_get_flags(conn.xmpp_conn);
+    long flags = xmpp_conn_get_flags(conn.xmpp_conn);
 
     if (!tls_policy || (g_strcmp0(tls_policy, "force") == 0)) {
         flags |= XMPP_CONN_FLAG_MANDATORY_TLS;
@@ -221,6 +226,19 @@ connection_connect(const char* const jid, const char* const passwd, const char*
 
     xmpp_conn_set_certfail_handler(conn.xmpp_conn, _connection_certfail_cb);
 
+    return TRUE;
+}
+
+jabber_conn_status_t
+connection_connect(const char* const jid, const char* const passwd, const char* const altdomain, int port,
+                   const char* const tls_policy, const char* const auth_policy)
+{
+    assert(jid != NULL);
+    assert(passwd != NULL);
+    log_info("Connecting as %s", jid);
+
+    _conn_apply_settings(jid, passwd, tls_policy, auth_policy);
+
     int connect_status = xmpp_connect_client(
         conn.xmpp_conn,
         altdomain,
@@ -465,69 +483,7 @@ jabber_conn_status_t
 connection_register(const char* const altdomain, int port, const char* const tls_policy,
                     const char* const username, const char* const password)
 {
-    long flags;
-
-    Jid* jidp = jid_create(altdomain);
-    if (jidp == NULL) {
-        log_error("Malformed JID not able to connect: %s", altdomain);
-        conn.conn_status = JABBER_DISCONNECTED;
-        return conn.conn_status;
-    }
-
-    _compute_identifier(jidp->barejid);
-    jid_destroy(jidp);
-
-    if (conn.xmpp_log) {
-        free(conn.xmpp_log);
-    }
-    conn.xmpp_log = _xmpp_get_file_logger();
-
-    if (conn.xmpp_conn) {
-        xmpp_conn_release(conn.xmpp_conn);
-    }
-    if (conn.xmpp_ctx) {
-        xmpp_ctx_free(conn.xmpp_ctx);
-    }
-    conn.xmpp_ctx = xmpp_ctx_new(NULL, conn.xmpp_log);
-    if (conn.xmpp_ctx == NULL) {
-        log_warning("Failed to get libstrophe ctx during connect");
-        return JABBER_DISCONNECTED;
-    }
-    conn.xmpp_conn = xmpp_conn_new(conn.xmpp_ctx);
-    if (conn.xmpp_conn == NULL) {
-        log_warning("Failed to get libstrophe conn during connect");
-        return JABBER_DISCONNECTED;
-    }
-    xmpp_conn_set_jid(conn.xmpp_conn, altdomain);
-
-    flags = xmpp_conn_get_flags(conn.xmpp_conn);
-
-    if (!tls_policy || (g_strcmp0(tls_policy, "force") == 0)) {
-        flags |= XMPP_CONN_FLAG_MANDATORY_TLS;
-    } else if (g_strcmp0(tls_policy, "trust") == 0) {
-        flags |= XMPP_CONN_FLAG_MANDATORY_TLS;
-        flags |= XMPP_CONN_FLAG_TRUST_TLS;
-    } else if (g_strcmp0(tls_policy, "disable") == 0) {
-        flags |= XMPP_CONN_FLAG_DISABLE_TLS;
-    } else if (g_strcmp0(tls_policy, "legacy") == 0) {
-        flags |= XMPP_CONN_FLAG_LEGACY_SSL;
-    }
-
-    xmpp_conn_set_flags(conn.xmpp_conn, flags);
-
-    /* Print debug logs that can help when users share the logs */
-    if (flags != 0) {
-        log_debug("Connecting with flags (0x%lx):", flags);
-#define LOG_FLAG_IF_SET(name)  \
-    if (flags & name) {        \
-        log_debug("  " #name); \
-    }
-        LOG_FLAG_IF_SET(XMPP_CONN_FLAG_MANDATORY_TLS);
-        LOG_FLAG_IF_SET(XMPP_CONN_FLAG_TRUST_TLS);
-        LOG_FLAG_IF_SET(XMPP_CONN_FLAG_DISABLE_TLS);
-        LOG_FLAG_IF_SET(XMPP_CONN_FLAG_LEGACY_SSL);
-#undef LOG_FLAG_IF_SET
-    }
+    _conn_apply_settings(altdomain, NULL, tls_policy, NULL);
 
     prof_reg_t* reg;
 
@@ -540,14 +496,6 @@ connection_register(const char* const altdomain, int port, const char* const tls
     reg->username = strdup(username);
     reg->password = strdup(password);
 
-    char* cert_path = prefs_get_tls_certpath();
-    if (cert_path) {
-        xmpp_conn_set_capath(conn.xmpp_conn, cert_path);
-        free(cert_path);
-    }
-
-    xmpp_conn_set_certfail_handler(conn.xmpp_conn, _connection_certfail_cb);
-
     int connect_status = xmpp_connect_raw(
         conn.xmpp_conn,
         altdomain,
@@ -584,12 +532,7 @@ connection_disconnect(void)
     if (!conn.xmpp_in_event_loop) {
         if (conn.xmpp_conn) {
             xmpp_conn_release(conn.xmpp_conn);
-            conn.xmpp_conn = NULL;
-        }
-
-        if (conn.xmpp_ctx) {
-            xmpp_ctx_free(conn.xmpp_ctx);
-            conn.xmpp_ctx = NULL;
+            conn.xmpp_conn = xmpp_conn_new(conn.xmpp_ctx);
         }
     }
 
@@ -909,11 +852,7 @@ _split_url(const char* alturi, gchar** host, gint* port)
      * 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;
-    }
+    char* xmpp_uri = _xmalloc(strlen(xmpp) + strlen(alturi) + 1, NULL);
     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);
@@ -1104,34 +1043,6 @@ _xmppcert_to_profcert(const xmpp_tlscert_t* xmpptlscert)
         xmpp_tlscert_get_pem(xmpptlscert));
 }
 
-static xmpp_log_t*
-_xmpp_get_file_logger(void)
-{
-    log_level_t prof_level = log_get_filter();
-    xmpp_log_level_t xmpp_level = XMPP_LEVEL_ERROR;
-
-    switch (prof_level) {
-    case PROF_LEVEL_DEBUG:
-        xmpp_level = XMPP_LEVEL_DEBUG;
-        break;
-    case PROF_LEVEL_INFO:
-        xmpp_level = XMPP_LEVEL_INFO;
-        break;
-    case PROF_LEVEL_WARN:
-        xmpp_level = XMPP_LEVEL_WARN;
-        break;
-    default:
-        xmpp_level = XMPP_LEVEL_ERROR;
-        break;
-    }
-
-    xmpp_log_t* file_log = malloc(sizeof(xmpp_log_t));
-    file_log->handler = _xmpp_file_logger;
-    file_log->userdata = &xmpp_level;
-
-    return file_log;
-}
-
 static void
 _xmpp_file_logger(void* const userdata, const xmpp_log_level_t xmpp_level, const char* const area, const char* const msg)
 {
diff --git a/src/xmpp/message.c b/src/xmpp/message.c
index 44e551c2..c18f9376 100644
--- a/src/xmpp/message.c
+++ b/src/xmpp/message.c
@@ -1452,16 +1452,13 @@ _handle_chat(xmpp_stanza_t* const stanza, gboolean is_mam, gboolean is_carbon, c
 
     // 0085 works only with resource
     if (jid->resourcepart) {
-        // XEP-0085: Chat State Notifications
+        // XEP-0085: Chat Stase Notifications
         _handle_chat_states(stanza, jid);
     }
 
     message_free(message);
 }
 
-/*!
- * @brief Handle incoming XMMP-OX chat message.
- */
 static void
 _handle_ox_chat(xmpp_stanza_t* const stanza, ProfMessage* message, gboolean is_mam)
 {
@@ -1482,8 +1479,7 @@ _handle_ox_chat(xmpp_stanza_t* const stanza, ProfMessage* message, gboolean is_m
                 }
                 xmpp_stanza_t* b = xmpp_stanza_get_child_by_name(p, "body");
                 if (!b) {
-                    log_warning("OX Stanza - no body");
-                    message->plain = "OX error: No paylod body found";
+                    log_debug("OX Stanza - no body");
                     return;
                 }
                 message->plain = xmpp_stanza_get_text(b);
diff --git a/src/xmpp/ox.c b/src/xmpp/ox.c
index 012a4017..0fa5cece 100644
--- a/src/xmpp/ox.c
+++ b/src/xmpp/ox.c
@@ -91,8 +91,8 @@ ox_announce_public_key(const char* const filename)
 {
     assert(filename);
 
-    cons_show("Annonuce OpenPGP Key for OX %s ...", filename);
-    log_info("[OX] Annonuce OpenPGP Key of OX: %s", filename);
+    cons_show("Announce OpenPGP Key for OX %s ...", filename);
+    log_info("[OX] Announce OpenPGP Key of OX: %s", filename);
 
     // key the key and the fingerprint via GnuPG from file
     char* key = NULL;
@@ -104,7 +104,7 @@ ox_announce_public_key(const char* const filename)
         return FALSE;
     }
 
-    log_info("[OX] Annonuce OpenPGP Key for Fingerprint: %s", fp);
+    log_info("[OX] Announce OpenPGP Key for Fingerprint: %s", fp);
     xmpp_ctx_t* const ctx = connection_get_ctx();
     char* id = xmpp_uuid_gen(ctx);
     xmpp_stanza_t* iq = xmpp_iq_new(ctx, STANZA_TYPE_SET, id);
@@ -231,7 +231,7 @@ ox_request_public_key(const char* const jid, const char* const fingerprint)
 void
 _ox_metadata_node__public_key(const char* const fingerprint)
 {
-    log_info("Annonuce OpenPGP metadata: %s", fingerprint);
+    log_info("Announce OpenPGP metadata: %s", fingerprint);
     assert(fingerprint);
     assert(strlen(fingerprint) == KEYID_LENGTH);
     // iq
@@ -340,7 +340,11 @@ _ox_request_public_key(const char* const jid, const char* const fingerprint)
 {
     assert(jid);
     assert(fingerprint);
-    assert(strlen(fingerprint) == KEYID_LENGTH);
+
+    if (strlen(fingerprint) != KEYID_LENGTH) {
+        cons_show_error("Invalid fingerprint length for: %s", fingerprint);
+    }
+
     cons_show("Requesting Public Key %s for %s", fingerprint, jid);
     log_info("[OX] Request %s's public key %s.", jid, fingerprint);
     // iq