diff --git a/.gitignore b/.gitignore index 0559b357..c38eab72 100644 --- a/.gitignore +++ b/.gitignore @@ -114,3 +114,4 @@ coverage/ *.gcda *.gcov coverage.info +REVIEW.txt diff --git a/Makefile.am b/Makefile.am index a7648fcb..47598773 100644 --- a/Makefile.am +++ b/Makefile.am @@ -352,15 +352,16 @@ EXTRA_DIST += \ apidocs/python/src/prof.py if INCLUDE_GIT_VERSION -EXTRA_DIST += .git/HEAD .git/index +GIT_FILES = $(top_srcdir)/.git/HEAD $(top_srcdir)/.git/index +EXTRA_DIST += $(GIT_FILES) -$(git_include).in: .git/HEAD .git/index +$(git_include).in: $(GIT_FILES) rm -f $@ echo "#ifndef PROF_GIT_BRANCH" >> $@ - echo "#define PROF_GIT_BRANCH \"$(shell git rev-parse --symbolic-full-name --abbrev-ref HEAD)\"" >> $@ + echo "#define PROF_GIT_BRANCH \"$(shell git -C $(top_srcdir) rev-parse --symbolic-full-name --abbrev-ref HEAD)\"" >> $@ echo "#endif" >> $@ echo "#ifndef PROF_GIT_REVISION" >> $@ - echo "#define PROF_GIT_REVISION \"$(shell git log --pretty=format:'%h' -n 1)\"" >> $@ + echo "#define PROF_GIT_REVISION \"$(shell git -C $(top_srcdir) log --pretty=format:'%h' -n 1)\"" >> $@ echo "#endif" >> $@ # diff --git a/configure.ac b/configure.ac index 07abd4d4..bd000fdc 100644 --- a/configure.ac +++ b/configure.ac @@ -175,6 +175,19 @@ AC_LINK_IFELSE([AC_LANG_SOURCE([[ [AC_MSG_RESULT([yes])], [AC_MSG_ERROR([libstrophe is broken, check config.log for details])]) +AC_MSG_CHECKING([whether libstrophe has XMPP_CERT_PUBKEY_FINGERPRINT_SHA256 support]) +AC_LINK_IFELSE([AC_LANG_SOURCE([[ + #include + + int main() { + xmpp_tlscert_get_string(NULL, XMPP_CERT_PUBKEY_FINGERPRINT_SHA256); + return 1; + } + ]])], + [AC_MSG_RESULT([yes]) + AC_DEFINE([HAVE_XMPP_CERT_PUBKEY_FINGERPRINT_SHA256], [1], [Have XMPP_CERT_PUBKEY_FINGERPRINT_SHA256])], + [AC_MSG_RESULT(no)]) + ## Check for curses library PKG_CHECK_MODULES([ncursesw], [ncursesw], [NCURSES_CFLAGS="$ncursesw_CFLAGS"; NCURSES_LIBS="$ncursesw_LIBS"; CURSES="ncursesw"], diff --git a/src/command/cmd_ac.c b/src/command/cmd_ac.c index facbbd30..b2c8cd8b 100644 --- a/src/command/cmd_ac.c +++ b/src/command/cmd_ac.c @@ -134,6 +134,7 @@ static char* _lastactivity_autocomplete(ProfWin* window, const char* const input static char* _intype_autocomplete(ProfWin* window, const char* const input, gboolean previous); static char* _mood_autocomplete(ProfWin* window, const char* const input, gboolean previous); static char* _strophe_autocomplete(ProfWin* window, const char* const input, gboolean previous); +static char* _stamp_autocomplete(ProfWin* window, const char* const input, gboolean previous); static char* _adhoc_cmd_autocomplete(ProfWin* window, const char* const input, gboolean previous); static char* _vcard_autocomplete(ProfWin* window, const char* const input, gboolean previous); static char* _force_encryption_autocomplete(ProfWin* window, const char* const input, gboolean previous); @@ -287,6 +288,8 @@ static Autocomplete mood_type_ac; static Autocomplete strophe_ac; static Autocomplete strophe_sm_ac; static Autocomplete strophe_verbosity_ac; +static Autocomplete stamp_ac; +static Autocomplete stamp_unset_ac; static Autocomplete adhoc_cmd_ac; static Autocomplete lastactivity_ac; static Autocomplete vcard_ac; @@ -441,6 +444,8 @@ static Autocomplete* all_acs[] = { &strophe_ac, &strophe_sm_ac, &strophe_verbosity_ac, + &stamp_ac, + &stamp_unset_ac, &adhoc_cmd_ac, &lastactivity_ac, &vcard_ac, @@ -1185,6 +1190,13 @@ cmd_ac_init(void) autocomplete_add(strophe_verbosity_ac, "2"); autocomplete_add(strophe_verbosity_ac, "3"); + autocomplete_add(stamp_ac, "outgoing"); + autocomplete_add(stamp_ac, "incoming"); + autocomplete_add(stamp_ac, "unset"); + + autocomplete_add(stamp_unset_ac, "outgoing"); + autocomplete_add(stamp_unset_ac, "incoming"); + autocomplete_add(mood_ac, "set"); autocomplete_add(mood_ac, "clear"); autocomplete_add(mood_ac, "on"); @@ -1414,6 +1426,7 @@ cmd_ac_init(void) g_hash_table_insert(ac_funcs, "/status", _status_autocomplete); g_hash_table_insert(ac_funcs, "/statusbar", _statusbar_autocomplete); g_hash_table_insert(ac_funcs, "/strophe", _strophe_autocomplete); + g_hash_table_insert(ac_funcs, "/stamp", _stamp_autocomplete); g_hash_table_insert(ac_funcs, "/sub", _sub_autocomplete); g_hash_table_insert(ac_funcs, "/subject", _subject_autocomplete); g_hash_table_insert(ac_funcs, "/theme", _theme_autocomplete); @@ -1684,6 +1697,9 @@ cmd_ac_complete_filepath(const char* const input, char* const startstr, gboolean } inpcp = strdup(inpcp); + if (!inpcp) { + return NULL; + } // strip quotes if (*inpcp == '"') { @@ -4262,6 +4278,20 @@ _strophe_autocomplete(ProfWin* window, const char* const input, gboolean previou return autocomplete_param_with_ac(input, "/strophe", strophe_ac, FALSE, previous); } +static char* +_stamp_autocomplete(ProfWin* window, const char* const input, gboolean previous) +{ + char* result = NULL; + + result = autocomplete_param_with_ac(input, "/stamp unset", stamp_unset_ac, TRUE, previous); + if (result) { + return result; + } + + result = autocomplete_param_with_ac(input, "/stamp", stamp_ac, TRUE, previous); + return result; +} + static char* _adhoc_cmd_autocomplete(ProfWin* window, const char* const input, gboolean previous) { diff --git a/src/command/cmd_defs.c b/src/command/cmd_defs.c index 5003be66..cce29b54 100644 --- a/src/command/cmd_defs.c +++ b/src/command/cmd_defs.c @@ -404,15 +404,17 @@ static const struct cmd_t command_defs[] = { CMD_DESC( "Manage blocked users (XEP-0191), calling with no arguments shows the current list of blocked users. " "To blog a certain user in a MUC use the following as jid: room@conference.example.org/spammy-user" - "It is also possible to block and report (XEP-0377) a user with the report-abuse and report-spam commands.") + "It is also possible to block and report (XEP-0377) a user with the report-abuse and report-spam commands. " + "For spam reporting, please include the content of the spam message as evidence for the service operator.") CMD_ARGS( { "add []", "Block the specified Jabber ID. If in a chat window and no jid is specified, the current recipient will be blocked." }, { "remove ", "Remove the specified Jabber ID from the blocked list." }, - { "report-abuse []", "Report the jid as abuse with an optional message to the service operator." }, - { "report-spam []", "Report the jid as spam with an optional message to the service operator." }) + { "report-abuse []", "Report a user for abuse with an optional description of the behavior." }, + { "report-spam []", "Report a user for spam, including the actual spam message as evidence." }) CMD_EXAMPLES( "/blocked add hel@helheim.edda", - "/blocked report-spam hel@helheim.edda Very annoying guy", + "/blocked report-abuse hel@helheim.edda Harassing me in MUC", + "/blocked report-spam spambot@example.com \"You won a free prize!\"", "/blocked add profanity@rooms.dismail.de/spammy-user") }, @@ -2353,6 +2355,15 @@ static const struct cmd_t command_defs[] = { "/omemo char *") }, + { CMD_PREAMBLE("/changes", + parse_args, 0, 0, NULL) + CMD_MAINFUNC(cmd_changes) + CMD_SYN( + "/changes") + CMD_DESC( + "Show changes from saved configuration file.") + }, + { CMD_PREAMBLE("/save", parse_args, 0, 0, NULL) CMD_MAINFUNC(cmd_save) @@ -2411,7 +2422,7 @@ static const struct cmd_t command_defs[] = { "/stamp unset outgoing|incoming") CMD_DESC("Set chat window stamp. " "The format of line in the chat window is: \" \" " - "where is \"me:\" for incoming messages or \"username@server/resource\" for outgoing messages. " + "where is \"me:\" for outgoing messages or \"username@server/resource\" for incoming messages. " "This command allows to change value.") CMD_ARGS({ "outgoing", "Set outgoing stamp" }, { "incoming", "Set incoming stamp"}, diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c index 67815bc6..9ae7a209 100644 --- a/src/command/cmd_funcs.c +++ b/src/command/cmd_funcs.c @@ -131,12 +131,12 @@ static gboolean _cmd_execute(ProfWin* window, const char* const command, const c 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)); +_string_matches_one_of(const char* what, const char* is, gboolean is_can_be_null, const char* first, ...) __attribute__((sentinel)); static gboolean _download_install_plugin(ProfWin* window, gchar* url, gchar* path); static gboolean -_string_matches_one_of(const char* what, const char* is, bool is_can_be_null, const char* first, ...) +_string_matches_one_of(const char* what, const char* is, gboolean is_can_be_null, const char* first, ...) { gboolean ret = FALSE; va_list ap; @@ -210,6 +210,9 @@ cmd_process_input(ProfWin* window, char* inp) if (inp[0] == '/') { auto_char char* inp_cpy = strdup(inp); char* command = strtok(inp_cpy, " "); + if (!command) { + return TRUE; + } char* question_mark = strchr(command, '?'); if (question_mark) { *question_mark = '\0'; @@ -291,7 +294,7 @@ cmd_tls_trust(ProfWin* window, const char* const command, gchar** args) return TRUE; } cafile_add(cert); - if (tlscerts_exists(cert->fingerprint)) { + if (tlscerts_exists(cert)) { cons_show("Certificate %s already trusted.", cert->fingerprint); tlscerts_free(cert); return TRUE; @@ -475,7 +478,7 @@ cmd_connect(ProfWin* window, const char* const command, gchar** args) // no account password setting, prompt } else { - account->password = ui_ask_password(false); + account->password = ui_ask_password(FALSE); conn_status = cl_ev_connect_account(account); free(account->password); account->password = NULL; @@ -487,7 +490,7 @@ cmd_connect(ProfWin* window, const char* const command, gchar** args) // connect with JID } else { jid = g_utf8_strdown(user, -1); - auto_char char* passwd = ui_ask_password(false); + auto_char char* passwd = ui_ask_password(FALSE); conn_status = cl_ev_connect_jid(jid, passwd, altdomain, port, tls_policy, auth_policy); } @@ -853,7 +856,7 @@ _account_set_theme(char* account_name, char* theme) ProfAccount* account = accounts_get_account(session_get_account_name()); if (account) { if (g_strcmp0(account->name, account_name) == 0) { - theme_load(theme, false); + theme_load(theme, FALSE); ui_load_colours(); if (prefs_get_boolean(PREF_ROSTER)) { ui_show_roster(); @@ -1262,8 +1265,7 @@ cmd_sub(ProfWin* window, const char* const command, gchar** args) } auto_jid Jid* jidp = jid_create(jid); - - if (jidp == NULL) { + if (!jidp) { cons_show("Malformed JID: %s", jid); return TRUE; } @@ -1277,9 +1279,13 @@ cmd_sub(ProfWin* window, const char* const command, gchar** args) cons_show("Deleted/denied subscription for %s", jidp->barejid); log_info("Deleted/denied subscription for %s", jidp->barejid); } else if (strcmp(subcmd, "request") == 0) { - presence_subscription(jidp->barejid, PRESENCE_SUBSCRIBE); - cons_show("Sent subscription request to %s.", jidp->barejid); - log_info("Sent subscription request to %s.", jidp->barejid); + if (equals_our_barejid(jidp->barejid)) { + cons_show("Subscription to yourself is implicit."); + } else { + presence_subscription(jidp->barejid, PRESENCE_SUBSCRIBE); + cons_show("Sent subscription request to %s.", jidp->barejid); + log_info("Sent subscription request to %s.", jidp->barejid); + } } else if (strcmp(subcmd, "show") == 0) { PContact contact = roster_get_contact(jidp->barejid); if ((contact == NULL) || (p_contact_subscription(contact) == NULL)) { @@ -3649,9 +3655,9 @@ cmd_join(ProfWin* window, const char* const command, gchar** args) if (!muc_active(room)) { presence_join_room(room, nick, passwd); muc_join(room, nick, passwd, FALSE); - iq_room_affiliation_list(room, "member", false); - iq_room_affiliation_list(room, "admin", false); - iq_room_affiliation_list(room, "owner", false); + iq_room_affiliation_list(room, "member", FALSE); + iq_room_affiliation_list(room, "admin", FALSE); + iq_room_affiliation_list(room, "owner", FALSE); } else if (muc_roster_complete(room)) { ui_switch_to_room(room); } @@ -4199,14 +4205,14 @@ cmd_affiliation(ProfWin* window, const char* const command, gchar** args) if (g_strcmp0(cmd, "list") == 0) { if (!affiliation) { - iq_room_affiliation_list(mucwin->roomjid, "owner", true); - iq_room_affiliation_list(mucwin->roomjid, "admin", true); - iq_room_affiliation_list(mucwin->roomjid, "member", true); - iq_room_affiliation_list(mucwin->roomjid, "outcast", true); + iq_room_affiliation_list(mucwin->roomjid, "owner", TRUE); + iq_room_affiliation_list(mucwin->roomjid, "admin", TRUE); + iq_room_affiliation_list(mucwin->roomjid, "member", TRUE); + iq_room_affiliation_list(mucwin->roomjid, "outcast", TRUE); } else if (g_strcmp0(affiliation, "none") == 0) { win_println(window, THEME_DEFAULT, "!", "Cannot list users with no affiliation."); } else { - iq_room_affiliation_list(mucwin->roomjid, affiliation, true); + iq_room_affiliation_list(mucwin->roomjid, affiliation, TRUE); } return TRUE; } @@ -4963,6 +4969,11 @@ cmd_sendfile(ProfWin* window, const char* const command, gchar** args) } FILE* fh = fdopen(fd, "rb"); + if (!fh) { + cons_show_error("Unable to open file descriptor for '%s'.", filename); + close(fd); + goto out; + } if (omemo_enabled) { #ifdef HAVE_OMEMO @@ -4972,12 +4983,23 @@ cmd_sendfile(ProfWin* window, const char* const command, gchar** args) if (err != NULL) { cons_show_error("%s", err); win_println(window, THEME_ERROR, "-", "%s", err); + if (fh) { + fclose(fh); + } goto out; } #endif } HTTPUpload* upload = malloc(sizeof(HTTPUpload)); + if (!upload) { + cons_show_error("Memory allocation failed."); + if (fh) { + fclose(fh); + } + goto out; + } + upload->window = window; upload->filename = strdup(filename); @@ -4985,6 +5007,17 @@ cmd_sendfile(ProfWin* window, const char* const command, gchar** args) upload->filesize = file_size(fd); upload->mime_type = file_mime_type(filename); + if (!upload->filename || !upload->mime_type) { + cons_show_error("Memory allocation failed."); + if (fh) { + fclose(fh); + } + free(upload->filename); + free(upload->mime_type); + free(upload); + goto out; + } + if (alt_scheme != NULL) { upload->alt_scheme = strdup(alt_scheme); } else { @@ -5362,6 +5395,9 @@ cmd_time(ProfWin* window, const char* const command, gchar** args) if (!set_all && n == ARRAY_SIZE(time_prefs)) { cons_bad_cmd_usage(command); } + if (!set_all && n == ARRAY_SIZE(time_prefs)) { + cons_bad_cmd_usage(command); + } if (redraw) ui_redraw(); return TRUE; @@ -6295,6 +6331,10 @@ cmd_reconnect(ProfWin* window, const char* const command, gchar** args) int intval = 0; auto_char char* err_msg = NULL; if (g_strcmp0(value, "now") == 0) { + if (connection_get_status() != JABBER_CONNECTED) { + cons_show("You can't reconnect now, a connection has not been established yet."); + return TRUE; + } cl_ev_reconnect(); } else if (strtoi_range(value, &intval, 0, INT_MAX, &err_msg)) { prefs_set_reconnect(intval); @@ -8784,8 +8824,8 @@ cmd_omemo_trust(ProfWin* window, const char* const command, gchar** args) return TRUE; } - char* fingerprint; - char* barejid; + gchar* fingerprint; + gchar* barejid; /* Contact not provided */ if (!args[2]) { @@ -8801,7 +8841,7 @@ cmd_omemo_trust(ProfWin* window, const char* const command, gchar** args) barejid = chatwin->barejid; } else { fingerprint = args[2]; - char* contact = args[1]; + gchar* contact = args[1]; barejid = roster_barejid_from_name(contact); if (barejid == NULL) { barejid = contact; @@ -8810,7 +8850,7 @@ cmd_omemo_trust(ProfWin* window, const char* const command, gchar** args) omemo_trust(barejid, fingerprint); - auto_char char* unformatted_fingerprint = malloc(strlen(fingerprint)); + auto_gchar gchar* unformatted_fingerprint = g_malloc(strlen(fingerprint) + 1); int i; int j; for (i = 0, j = 0; fingerprint[i] != '\0'; i++) { @@ -8851,8 +8891,8 @@ cmd_omemo_untrust(ProfWin* window, const char* const command, gchar** args) return TRUE; } - char* fingerprint; - char* barejid; + gchar* fingerprint; + gchar* barejid; /* Contact not provided */ if (!args[2]) { @@ -8877,7 +8917,7 @@ cmd_omemo_untrust(ProfWin* window, const char* const command, gchar** args) omemo_untrust(barejid, fingerprint); - auto_char char* unformatted_fingerprint = malloc(strlen(fingerprint)); + auto_gchar gchar* unformatted_fingerprint = g_malloc(strlen(fingerprint) + 1); int i, j; for (i = 0, j = 0; fingerprint[i] != '\0'; i++) { if (!g_ascii_isxdigit(fingerprint[i])) { @@ -8964,6 +9004,14 @@ cmd_omemo_qrcode(ProfWin* window, const char* const command, gchar** args) #endif } +gboolean +cmd_changes(ProfWin* window, const char* const command, gchar** args) +{ + cons_show("Show changes from saved configuration file."); + prefs_changes(); + return TRUE; +} + gboolean cmd_save(ProfWin* window, const char* const command, gchar** args) { @@ -9029,15 +9077,15 @@ gboolean cmd_stamp(ProfWin* window, const char* const command, gchar** args) { if (g_strv_length(args) == 0) { - auto_gchar gchar* def_incoming = prefs_get_string(PREF_OUTGOING_STAMP); - if (def_incoming) { - cons_show("The outgoing stamp is: %s", def_incoming); + auto_gchar gchar* def_outgoing = prefs_get_string(PREF_OUTGOING_STAMP); + if (def_outgoing) { + cons_show("The outgoing stamp is: %s", def_outgoing); } else { cons_show("The default outgoing stamp is used."); } - auto_gchar gchar* def_outgoing = prefs_get_string(PREF_INCOMING_STAMP); - if (def_outgoing) { - cons_show("The incoming stamp is: %s", def_outgoing); + auto_gchar gchar* def_incoming = prefs_get_string(PREF_INCOMING_STAMP); + if (def_incoming) { + cons_show("The incoming stamp is: %s", def_incoming); } else { cons_show("The default incoming stamp is used."); } @@ -9098,12 +9146,12 @@ cmd_color(ProfWin* window, const char* const command, gchar** args) auto_gchar gchar* theme = prefs_get_string(PREF_THEME); if (theme) { - gboolean res = theme_load(theme, false); + gboolean res = theme_load(theme, FALSE); if (res) { cons_show("Theme reloaded: %s", theme); } else { - theme_load("default", false); + theme_load("default", FALSE); } } @@ -9133,9 +9181,9 @@ cmd_avatar(ProfWin* window, const char* const command, gchar** args) cons_show("Profanity has not been built with GDK Pixbuf support enabled which is needed to scale the avatar when uploading."); #endif } else if (g_strcmp0(args[0], "get") == 0) { - avatar_get_by_nick(args[1], false); + avatar_get_by_nick(args[1], FALSE); } else if (g_strcmp0(args[0], "open") == 0) { - avatar_get_by_nick(args[1], true); + avatar_get_by_nick(args[1], TRUE); } else { cons_bad_cmd_usage(command); } @@ -9270,18 +9318,30 @@ _prepare_filename(ProfWin* window, gchar* url, gchar* path) break; } - // Ensure that the downloads directory exists for saving cleartexts. - auto_gchar gchar* downloads_dir = path ? get_expanded_path(path) : files_get_download_path(jid); - if (g_mkdir_with_parents(downloads_dir, S_IRWXU) != 0) { - cons_show_error("Failed to create download directory " - "at '%s' with error '%s'", - downloads_dir, strerror(errno)); - return NULL; + auto_gchar gchar* expanded_path = path ? get_expanded_path(path) : files_get_download_path(jid); + + // If path is provided and doesn't look like a directory, create its parent. + // Otherwise, create the directory itself. + if (path && !g_str_has_suffix(expanded_path, G_DIR_SEPARATOR_S) && !g_file_test(expanded_path, G_FILE_TEST_IS_DIR)) { + auto_gchar gchar* dirname = g_path_get_dirname(expanded_path); + if (g_mkdir_with_parents(dirname, S_IRWXU) != 0) { + cons_show_error("Failed to create download directory " + "at '%s' with error '%s'", + dirname, strerror(errno)); + return NULL; + } + } else { + if (g_mkdir_with_parents(expanded_path, S_IRWXU) != 0) { + cons_show_error("Failed to create download directory " + "at '%s' with error '%s'", + expanded_path, strerror(errno)); + return NULL; + } } // Generate an unique filename from the URL that should be stored in the // downloads directory. - return unique_filename_from_url(url, downloads_dir); + return unique_filename_from_url(url, expanded_path); } #ifdef HAVE_OMEMO @@ -9518,8 +9578,8 @@ cmd_change_password(ProfWin* window, const char* const command, gchar** args) return TRUE; } - auto_char char* passwd = ui_ask_password(false); - auto_char char* confirm_passwd = ui_ask_password(true); + auto_char char* passwd = ui_ask_password(FALSE); + auto_char char* confirm_passwd = ui_ask_password(TRUE); if (g_strcmp0(passwd, confirm_passwd) == 0) { iq_register_change_password(connection_get_user(), passwd); @@ -9587,6 +9647,12 @@ cmd_register(ProfWin* window, const char* const command, gchar** args) gchar* opt_keys[] = { "port", "tls", "auth", NULL }; gboolean parsed; + if (connection_get_status() != JABBER_DISCONNECTED) { + cons_show_error("Can't register unless disconnected, please wait."); + log_info("Can't register unless disconnected, please wait."); + return TRUE; + } + GHashTable* options = parse_options(&args[2], opt_keys, &parsed); if (!parsed) { cons_bad_cmd_usage(command); @@ -9620,8 +9686,8 @@ cmd_register(ProfWin* window, const char* const command, gchar** args) char* username = args[0]; char* server = args[1]; - auto_char char* passwd = ui_ask_password(false); - auto_char char* confirm_passwd = ui_ask_password(true); + auto_char char* passwd = ui_ask_password(FALSE); + auto_char char* confirm_passwd = ui_ask_password(TRUE); if (g_strcmp0(passwd, confirm_passwd) == 0) { log_info("Attempting to register account %s on server %s.", username, server); diff --git a/src/command/cmd_funcs.h b/src/command/cmd_funcs.h index b8b74e3c..ccd727eb 100644 --- a/src/command/cmd_funcs.h +++ b/src/command/cmd_funcs.h @@ -234,6 +234,7 @@ gboolean cmd_omemo_policy(ProfWin* window, const char* const command, gchar** ar gboolean cmd_omemo_clear_device_list(ProfWin* window, const char* const command, gchar** args); gboolean cmd_omemo_qrcode(ProfWin* window, const char* const command, gchar** args); +gboolean cmd_changes(ProfWin* window, const char* const command, gchar** args); gboolean cmd_save(ProfWin* window, const char* const command, gchar** args); gboolean cmd_reload(ProfWin* window, const char* const command, gchar** args); diff --git a/src/common.c b/src/common.c index a1418b01..ef82e737 100644 --- a/src/common.c +++ b/src/common.c @@ -105,6 +105,15 @@ auto_free_guchar(guchar** ptr) g_free(*ptr); } +void +auto_free_gerror(GError** err) +{ + if (err == NULL || *err == NULL) { + return; + } + g_error_free(*err); +} + /** * Frees the memory allocated for a gchar** string array. * @@ -380,6 +389,33 @@ utf8_display_len(const char* const str) return len; } +/** + * Removes illegal XML 1.0 characters from a string. + * + * This function creates a new string that excludes characters in the range + * U+0000 to U+001F, except for U+0009 (TAB), U+000A (LF), and U+000D (CR). + */ +gchar* +str_xml_sanitize(const char* const str) +{ + if (str == NULL) { + return NULL; + } + + GString* sanitized = g_string_new_len(NULL, strlen(str)); + const char* curr = str; + + while (*curr != '\0') { + gunichar c = g_utf8_get_char(curr); + if ((c >= 0x20) || (c == 0x09) || (c == 0x0A) || (c == 0x0D)) { + g_string_append_unichar(sanitized, c); + } + curr = g_utf8_next_char(curr); + } + + return g_string_free(sanitized, FALSE); +} + char* release_get_latest(void) { @@ -711,7 +747,7 @@ _unique_filename(const char* filename) return unique; } -static bool +static gboolean _has_directory_suffix(const char* path) { return (g_str_has_suffix(path, ".") diff --git a/src/common.h b/src/common.h index 0532e0d7..a3f0af53 100644 --- a/src/common.h +++ b/src/common.h @@ -104,6 +104,10 @@ void auto_free_guchar(guchar** str); */ #define auto_guchar __attribute__((__cleanup__(auto_free_guchar))) +void auto_free_gerror(GError** err); +#define auto_gerror __attribute__((__cleanup__(auto_free_gerror))) +#define PROF_GERROR_MESSAGE(err) ((err) ? (err)->message : "unknown") + #define auto_gfd __attribute__((__cleanup__(auto_close_gfd))) void auto_close_gfd(gint* fd); @@ -180,6 +184,7 @@ gboolean copy_file(const char* const src, const char* const target, const gboole char* str_replace(const char* string, const char* substr, const char* replacement); gboolean strtoi_range(const char* str, int* saveptr, int min, int max, char** err_msg); int utf8_display_len(const char* const str); +gchar* str_xml_sanitize(const char* const str); char* release_get_latest(void); gboolean release_is_new(char* found_version); diff --git a/src/config/accounts.c b/src/config/accounts.c index 08794e7e..f5549f46 100644 --- a/src/config/accounts.c +++ b/src/config/accounts.c @@ -59,7 +59,48 @@ static GKeyFile* accounts; static Autocomplete all_ac; static Autocomplete enabled_ac; -static void _save_accounts(void); +static gboolean +_accounts_has_group(const char* account_name) +{ + if (!account_name || !accounts) + return FALSE; + return g_key_file_has_group(accounts, account_name); +} + +static void +_accounts_save(const char* account_name) +{ + prof_keyfile_t current; + + if (!load_data_keyfile(¤t, FILE_ACCOUNTS)) { + log_error("Could not load accounts"); + return; + } + + gsize written = 0; + if (_accounts_has_group(account_name)) { + gsize naccounts; + auto_gcharv gchar** account_names = g_key_file_get_groups(current.keyfile, &naccounts); + for (gsize i = 0; i < naccounts; i++) { + /* EITHER current already contains the account, OR we're at the end of + * the list of accounts and we should add this new one. + */ + if (g_strcmp0(account_name, account_names[i]) && (i != naccounts - 1)) + continue; + gsize nkeys; + auto_gcharv gchar** keys = g_key_file_get_keys(accounts_prof_keyfile.keyfile, account_name, &nkeys, NULL); + for (gsize j = 0; j < nkeys; ++j) { + auto_gchar gchar* new_value = g_key_file_get_value(accounts_prof_keyfile.keyfile, account_name, keys[j], NULL); + g_key_file_set_value(current.keyfile, account_name, keys[j], new_value); + } + written++; + } + } else { + g_key_file_remove_group(current.keyfile, account_name, NULL); + } + save_keyfile(¤t); + free_keyfile(¤t); +} static void _accounts_close(void) @@ -79,7 +120,9 @@ accounts_load(void) all_ac = autocomplete_new(); enabled_ac = autocomplete_new(); - load_data_keyfile(&accounts_prof_keyfile, FILE_ACCOUNTS); + if (!load_data_keyfile(&accounts_prof_keyfile, FILE_ACCOUNTS)) { + log_error("Could not load accounts"); + } accounts = accounts_prof_keyfile.keyfile; // create the logins searchable list for autocompletion @@ -132,7 +175,8 @@ accounts_add(const char* account_name, const char* altdomain, const int port, co } } - if (g_key_file_has_group(accounts, account_name)) { + if (_accounts_has_group(account_name)) { + log_error("Can't add account \"%s\", it already exists.", account_name); return; } @@ -168,7 +212,7 @@ accounts_add(const char* account_name, const char* altdomain, const int port, co g_key_file_set_integer(accounts, account_name, "priority.xa", 0); g_key_file_set_integer(accounts, account_name, "priority.dnd", 0); - _save_accounts(); + _accounts_save(account_name); autocomplete_add(all_ac, account_name); autocomplete_add(enabled_ac, account_name); } @@ -177,7 +221,7 @@ int accounts_remove(const char* account_name) { int r = g_key_file_remove_group(accounts, account_name, NULL); - _save_accounts(); + _accounts_save(account_name); autocomplete_remove(all_ac, account_name); autocomplete_remove(enabled_ac, account_name); return r; @@ -189,10 +233,32 @@ accounts_get_list(void) return g_key_file_get_groups(accounts, NULL); } +static GList* +_g_strv_to_glist(gchar** in, gsize length) +{ + if (in == NULL) + return NULL; + GList* out = NULL; + for (gsize i = 0; i < length; i++) { + out = g_list_append(out, in[i]); + } + g_free(in); + return out; +} + +static GList* +_accounts_get_glist(const gchar* group_name, + const gchar* key) +{ + gsize length = 0; + gchar** list = g_key_file_get_string_list(accounts, group_name, key, &length, NULL); + return _g_strv_to_glist(list, length); +} + ProfAccount* accounts_get_account(const char* const name) { - if (!g_key_file_has_group(accounts, name)) { + if (!_accounts_has_group(name)) { return NULL; } else { gchar* jid = g_key_file_get_string(accounts, name, "jid", NULL); @@ -200,7 +266,7 @@ accounts_get_account(const char* const name) // fix accounts that have no jid property by setting to name if (jid == NULL) { g_key_file_set_string(accounts, name, "jid", name); - _save_accounts(); + _accounts_save(name); } gchar* password = g_key_file_get_string(accounts, name, "password", NULL); @@ -234,92 +300,26 @@ accounts_get_account(const char* const name) } gchar* muc_nick = g_key_file_get_string(accounts, name, "muc.nick", NULL); - gchar* otr_policy = NULL; - if (g_key_file_has_key(accounts, name, "otr.policy", NULL)) { - otr_policy = g_key_file_get_string(accounts, name, "otr.policy", NULL); - } + gchar* otr_policy = g_key_file_get_string(accounts, name, "otr.policy", NULL); + GList* otr_manual = _accounts_get_glist(name, "otr.manual"); + GList* otr_opportunistic = _accounts_get_glist(name, "otr.opportunistic"); + GList* otr_always = _accounts_get_glist(name, "otr.always"); - gsize length; - GList* otr_manual = NULL; - auto_gcharv gchar** manual = g_key_file_get_string_list(accounts, name, "otr.manual", &length, NULL); - if (manual) { - for (int i = 0; i < length; i++) { - otr_manual = g_list_append(otr_manual, strdup(manual[i])); - } - } + gchar* omemo_policy = g_key_file_get_string(accounts, name, "omemo.policy", NULL); + GList* omemo_enabled = _accounts_get_glist(name, "omemo.enabled"); + GList* omemo_disabled = _accounts_get_glist(name, "omemo.disabled"); - GList* otr_opportunistic = NULL; - auto_gcharv gchar** opportunistic = g_key_file_get_string_list(accounts, name, "otr.opportunistic", &length, NULL); - if (opportunistic) { - for (int i = 0; i < length; i++) { - otr_opportunistic = g_list_append(otr_opportunistic, strdup(opportunistic[i])); - } - } + GList* ox_enabled = _accounts_get_glist(name, "ox.enabled"); - GList* otr_always = NULL; - auto_gcharv gchar** always = g_key_file_get_string_list(accounts, name, "otr.always", &length, NULL); - if (always) { - for (int i = 0; i < length; i++) { - otr_always = g_list_append(otr_always, strdup(always[i])); - } - } + GList* pgp_enabled = _accounts_get_glist(name, "pgp.enabled"); - gchar* omemo_policy = NULL; - if (g_key_file_has_key(accounts, name, "omemo.policy", NULL)) { - omemo_policy = g_key_file_get_string(accounts, name, "omemo.policy", NULL); - } + gchar* pgp_keyid = g_key_file_get_string(accounts, name, "pgp.keyid", NULL); - GList* omemo_enabled = NULL; - auto_gcharv gchar** omemo_enabled_list = g_key_file_get_string_list(accounts, name, "omemo.enabled", &length, NULL); - if (omemo_enabled_list) { - for (int i = 0; i < length; i++) { - omemo_enabled = g_list_append(omemo_enabled, strdup(omemo_enabled_list[i])); - } - } + gchar* startscript = g_key_file_get_string(accounts, name, "script.start", NULL); - GList* omemo_disabled = NULL; - auto_gcharv gchar** omemo_disabled_list = g_key_file_get_string_list(accounts, name, "omemo.disabled", &length, NULL); - if (omemo_disabled_list) { - for (int i = 0; i < length; i++) { - omemo_disabled = g_list_append(omemo_disabled, strdup(omemo_disabled_list[i])); - } - } + gchar* client = g_key_file_get_string(accounts, name, "client.account_name", NULL); - GList* ox_enabled = NULL; - auto_gcharv gchar** ox_enabled_list = g_key_file_get_string_list(accounts, name, "ox.enabled", &length, NULL); - if (ox_enabled_list) { - for (int i = 0; i < length; i++) { - ox_enabled = g_list_append(ox_enabled, strdup(ox_enabled_list[i])); - } - } - - GList* pgp_enabled = NULL; - auto_gcharv gchar** pgp_enabled_list = g_key_file_get_string_list(accounts, name, "pgp.enabled", &length, NULL); - if (pgp_enabled_list) { - for (int i = 0; i < length; i++) { - pgp_enabled = g_list_append(pgp_enabled, strdup(pgp_enabled_list[i])); - } - } - - gchar* pgp_keyid = NULL; - if (g_key_file_has_key(accounts, name, "pgp.keyid", NULL)) { - pgp_keyid = g_key_file_get_string(accounts, name, "pgp.keyid", NULL); - } - - gchar* startscript = NULL; - if (g_key_file_has_key(accounts, name, "script.start", NULL)) { - startscript = g_key_file_get_string(accounts, name, "script.start", NULL); - } - - gchar* client = NULL; - if (g_key_file_has_key(accounts, name, "client.name", NULL)) { - client = g_key_file_get_string(accounts, name, "client.name", NULL); - } - - gchar* theme = NULL; - if (g_key_file_has_key(accounts, name, "theme", NULL)) { - theme = g_key_file_get_string(accounts, name, "theme", NULL); - } + gchar* theme = g_key_file_get_string(accounts, name, "theme", NULL); gchar* tls_policy = g_key_file_get_string(accounts, name, "tls.policy", NULL); 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))) { @@ -331,24 +331,22 @@ accounts_get_account(const char* const name) int max_sessions = g_key_file_get_integer(accounts, name, "max.sessions", 0); - ProfAccount* new_account = account_new(g_strdup(name), jid, password, eval_password, enabled, - server, port, resource, last_presence, login_presence, - priority_online, priority_chat, priority_away, priority_xa, - priority_dnd, muc_service, muc_nick, otr_policy, otr_manual, - otr_opportunistic, otr_always, omemo_policy, omemo_enabled, - omemo_disabled, ox_enabled, pgp_enabled, pgp_keyid, - startscript, theme, tls_policy, auth_policy, client, max_sessions); - - return new_account; + return account_new(g_strdup(name), jid, password, eval_password, enabled, + server, port, resource, last_presence, login_presence, + priority_online, priority_chat, priority_away, priority_xa, + priority_dnd, muc_service, muc_nick, otr_policy, otr_manual, + otr_opportunistic, otr_always, omemo_policy, omemo_enabled, + omemo_disabled, ox_enabled, pgp_enabled, pgp_keyid, + startscript, theme, tls_policy, auth_policy, client, max_sessions); } } gboolean accounts_enable(const char* const name) { - if (g_key_file_has_group(accounts, name)) { + if (_accounts_has_group(name)) { g_key_file_set_boolean(accounts, name, "enabled", TRUE); - _save_accounts(); + _accounts_save(name); autocomplete_add(enabled_ac, name); return TRUE; } else { @@ -359,9 +357,9 @@ accounts_enable(const char* const name) gboolean accounts_disable(const char* const name) { - if (g_key_file_has_group(accounts, name)) { + if (_accounts_has_group(name)) { g_key_file_set_boolean(accounts, name, "enabled", FALSE); - _save_accounts(); + _accounts_save(name); autocomplete_remove(enabled_ac, name); return TRUE; } else { @@ -372,62 +370,29 @@ accounts_disable(const char* const name) gboolean accounts_rename(const char* const account_name, const char* const new_name) { - if (g_key_file_has_group(accounts, new_name)) { + if (_accounts_has_group(new_name)) { return FALSE; } - if (!g_key_file_has_group(accounts, account_name)) { + if (!_accounts_has_group(account_name)) { return FALSE; } - // treat all properties as strings for copy - gchar* string_keys[] = { - "enabled", - "jid", - "server", - "port", - "resource", - "password", - "eval_password", - "presence.last", - "presence.laststatus", - "presence.login", - "priority.online", - "priority.chat", - "priority.away", - "priority.xa", - "priority.dnd", - "muc.service", - "muc.nick", - "otr.policy", - "otr.manual", - "otr.opportunistic", - "otr.always", - "omemo.policy", - "omemo.enabled", - "omemo.disabled", - "ox.enabled", - "pgp.enabled", - "pgp.keyid", - "last.activity", - "script.start", - "tls.policy" - }; - - for (int i = 0; i < ARRAY_SIZE(string_keys); i++) { - auto_gchar gchar* value = g_key_file_get_string(accounts, account_name, string_keys[i], NULL); - if (value) { - g_key_file_set_string(accounts, new_name, string_keys[i], value); - } + gsize nkeys; + auto_gcharv gchar** keys = g_key_file_get_keys(accounts, account_name, &nkeys, NULL); + for (gsize i = 0; i < nkeys; i++) { + auto_gchar gchar* new_value = g_key_file_get_value(accounts, account_name, keys[i], NULL); + g_key_file_set_value(accounts, new_name, keys[i], new_value); } g_key_file_remove_group(accounts, account_name, NULL); - _save_accounts(); + _accounts_save(account_name); + _accounts_save(new_name); autocomplete_remove(all_ac, account_name); + autocomplete_remove(enabled_ac, account_name); autocomplete_add(all_ac, new_name); if (g_key_file_get_boolean(accounts, new_name, "enabled", NULL)) { - autocomplete_remove(enabled_ac, account_name); autocomplete_add(enabled_ac, new_name); } @@ -437,7 +402,7 @@ accounts_rename(const char* const account_name, const char* const new_name) gboolean accounts_account_exists(const char* const account_name) { - return g_key_file_has_group(accounts, account_name); + return _accounts_has_group(account_name); } void @@ -445,7 +410,7 @@ accounts_set_jid(const char* const account_name, const char* const value) { auto_jid Jid* jid = jid_create(value); if (jid) { - if (accounts_account_exists(account_name)) { + if (_accounts_has_group(account_name)) { g_key_file_set_string(accounts, account_name, "jid", jid->barejid); if (jid->resourcepart) { g_key_file_set_string(accounts, account_name, "resource", jid->resourcepart); @@ -457,7 +422,7 @@ accounts_set_jid(const char* const account_name, const char* const value) g_key_file_set_string(accounts, account_name, "muc.nick", jid->localpart); } - _save_accounts(); + _accounts_save(account_name); } } } @@ -465,9 +430,9 @@ accounts_set_jid(const char* const account_name, const char* const value) void accounts_set_server(const char* const account_name, const char* const value) { - if (accounts_account_exists(account_name)) { + if (_accounts_has_group(account_name)) { g_key_file_set_string(accounts, account_name, "server", value); - _save_accounts(); + _accounts_save(account_name); } } @@ -476,34 +441,34 @@ accounts_set_port(const char* const account_name, const int value) { if (value != 0) { g_key_file_set_integer(accounts, account_name, "port", value); - _save_accounts(); + _accounts_save(account_name); } } static void _accounts_set_string_option(const char* account_name, const char* const option, const char* const value) { - if (accounts_account_exists(account_name)) { + if (_accounts_has_group(account_name)) { g_key_file_set_string(accounts, account_name, option, value ?: ""); - _save_accounts(); + _accounts_save(account_name); } } static void _accounts_set_int_option(const char* account_name, const char* const option, int value) { - if (accounts_account_exists(account_name)) { + if (_accounts_has_group(account_name)) { g_key_file_set_integer(accounts, account_name, option, value); - _save_accounts(); + _accounts_save(account_name); } } static void _accounts_clear_string_option(const char* account_name, const char* const option) { - if (accounts_account_exists(account_name)) { + if (_accounts_has_group(account_name)) { g_key_file_remove_key(accounts, account_name, option, NULL); - _save_accounts(); + _accounts_save(account_name); } } @@ -630,7 +595,7 @@ accounts_clear_max_sessions(const char* const account_name) void accounts_add_otr_policy(const char* const account_name, const char* const contact_jid, const char* const policy) { - if (accounts_account_exists(account_name)) { + if (_accounts_has_group(account_name)) { GString* key = g_string_new("otr."); g_string_append(key, policy); conf_string_list_add(accounts, account_name, key->str, contact_jid); @@ -650,14 +615,14 @@ accounts_add_otr_policy(const char* const account_name, const char* const contac conf_string_list_remove(accounts, account_name, "otr.manual", contact_jid); } - _save_accounts(); + _accounts_save(account_name); } } void accounts_add_omemo_state(const char* const account_name, const char* const contact_jid, gboolean enabled) { - if (accounts_account_exists(account_name)) { + if (_accounts_has_group(account_name)) { if (enabled) { conf_string_list_add(accounts, account_name, "omemo.enabled", contact_jid); conf_string_list_remove(accounts, account_name, "omemo.disabled", contact_jid); @@ -666,28 +631,28 @@ accounts_add_omemo_state(const char* const account_name, const char* const conta conf_string_list_remove(accounts, account_name, "omemo.enabled", contact_jid); } - _save_accounts(); + _accounts_save(account_name); } } void accounts_add_ox_state(const char* const account_name, const char* const contact_jid, gboolean enabled) { - if (accounts_account_exists(account_name)) { + if (_accounts_has_group(account_name)) { if (enabled) { conf_string_list_add(accounts, account_name, "ox.enabled", contact_jid); } else { conf_string_list_remove(accounts, account_name, "ox.enabled", contact_jid); } - _save_accounts(); + _accounts_save(account_name); } } void accounts_add_pgp_state(const char* const account_name, const char* const contact_jid, gboolean enabled) { - if (accounts_account_exists(account_name)) { + if (_accounts_has_group(account_name)) { if (enabled) { conf_string_list_add(accounts, account_name, "pgp.enabled", contact_jid); conf_string_list_remove(accounts, account_name, "pgp.disabled", contact_jid); @@ -696,17 +661,17 @@ accounts_add_pgp_state(const char* const account_name, const char* const contact conf_string_list_remove(accounts, account_name, "pgp.enabled", contact_jid); } - _save_accounts(); + _accounts_save(account_name); } } void accounts_clear_omemo_state(const char* const account_name, const char* const contact_jid) { - if (accounts_account_exists(account_name)) { + if (_accounts_has_group(account_name)) { conf_string_list_remove(accounts, account_name, "omemo.enabled", contact_jid); conf_string_list_remove(accounts, account_name, "omemo.disabled", contact_jid); - _save_accounts(); + _accounts_save(account_name); } } @@ -779,7 +744,7 @@ accounts_set_priority_dnd(const char* const account_name, const gint value) void accounts_set_priority_all(const char* const account_name, const gint value) { - if (accounts_account_exists(account_name)) { + if (_accounts_has_group(account_name)) { accounts_set_priority_online(account_name, value); accounts_set_priority_chat(account_name, value); accounts_set_priority_away(account_name, value); @@ -833,7 +798,7 @@ accounts_set_last_status(const char* const account_name, const char* const value void accounts_set_last_activity(const char* const account_name) { - if (accounts_account_exists(account_name)) { + if (_accounts_has_group(account_name)) { GDateTime* nowdt = g_date_time_new_now_utc(); GTimeVal nowtv; gboolean res = g_date_time_to_timeval(nowdt, &nowtv); @@ -842,7 +807,7 @@ accounts_set_last_activity(const char* const account_name) if (res) { auto_char char* timestr = g_time_val_to_iso8601(&nowtv); g_key_file_set_string(accounts, account_name, "last.activity", timestr); - _save_accounts(); + _accounts_save(account_name); } } } @@ -850,7 +815,7 @@ accounts_set_last_activity(const char* const account_name) gchar* accounts_get_last_activity(const char* const account_name) { - if (accounts_account_exists(account_name)) { + if (_accounts_has_group(account_name)) { return g_key_file_get_string(accounts, account_name, "last.activity", NULL); } else { return NULL; @@ -860,7 +825,7 @@ accounts_get_last_activity(const char* const account_name) gchar* accounts_get_resource(const char* const account_name) { - if (!accounts_account_exists(account_name)) { + if (!_accounts_has_group(account_name)) { return NULL; } return g_key_file_get_string(accounts, account_name, "resource", NULL); @@ -869,7 +834,7 @@ accounts_get_resource(const char* const account_name) int accounts_get_max_sessions(const char* const account_name) { - if (!accounts_account_exists(account_name)) { + if (!_accounts_has_group(account_name)) { return 0; } return g_key_file_get_integer(accounts, account_name, "max.sessions", 0); @@ -878,9 +843,9 @@ accounts_get_max_sessions(const char* const account_name) void accounts_set_login_presence(const char* const account_name, const char* const value) { - if (accounts_account_exists(account_name)) { + if (_accounts_has_group(account_name)) { g_key_file_set_string(accounts, account_name, "presence.login", value); - _save_accounts(); + _accounts_save(account_name); } } @@ -953,9 +918,3 @@ accounts_get_login_status(const char* const account_name) return status; } - -static void -_save_accounts(void) -{ - save_keyfile(&accounts_prof_keyfile); -} diff --git a/src/config/cafile.c b/src/config/cafile.c index 59aff2fb..1f3a9584 100644 --- a/src/config/cafile.c +++ b/src/config/cafile.c @@ -80,9 +80,13 @@ cafile_add(const TLSCertificate* cert) log_debug("[CAfile] fingerprint %s already stored", cert->fingerprint); return; } + if (strstr(contents, cert->fingerprint_sha1)) { + log_debug("[CAfile] fingerprint %s already stored", cert->fingerprint_sha1); + return; + } } const char* header = "# Profanity CAfile\n# DO NOT EDIT - this file is automatically generated"; - new_contents = g_strdup_printf("%s\n\n# %s\n%s", contents ? contents : header, cert->fingerprint, cert->pem); + new_contents = g_strdup_printf("%s\n# %s\n%s", contents ? contents : header, cert->fingerprint, cert->pem); if (!g_file_set_contents(cafile, new_contents, -1, &glib_error)) log_error("[CAfile] could not write to %s: %s", cafile, glib_error ? glib_error->message : "No GLib error given"); } diff --git a/src/config/conflists.c b/src/config/conflists.c index 05d5d6ed..555919ad 100644 --- a/src/config/conflists.c +++ b/src/config/conflists.c @@ -66,9 +66,7 @@ conf_string_list_add(GKeyFile* keyfile, const char* const group, const char* con // Add item to the existing list const gchar** new_list = g_new(const gchar*, length + 2); - for (gsize i = 0; i < length; ++i) { - new_list[i] = list[i]; - } + memcpy(new_list, list, sizeof(list[0]) * length); new_list[length] = item; new_list[length + 1] = NULL; @@ -89,33 +87,28 @@ conf_string_list_remove(GKeyFile* keyfile, const char* const group, const char* return FALSE; } - GList* glist = NULL; + gsize new_length = 0; + const gchar** new_list = g_new(const gchar*, length + 1); gboolean deleted = FALSE; - for (int i = 0; i < length; i++) { + for (gsize i = 0; i < length; i++) { if (strcmp(list[i], item) == 0) { deleted = TRUE; continue; } - glist = g_list_append(glist, strdup(list[i])); + new_list[new_length++] = list[i]; } + new_list[new_length] = NULL; if (deleted) { - if (g_list_length(glist) == 0) { + if (new_length == 0) { g_key_file_remove_key(keyfile, group, key, NULL); } else { - const gchar* new_list[g_list_length(glist) + 1]; - int i = 0; - - for (GList* curr = glist; curr; curr = g_list_next(curr)) { - new_list[i++] = curr->data; - } - - new_list[i] = NULL; - g_key_file_set_string_list(keyfile, group, key, new_list, g_list_length(glist)); + g_key_file_set_string_list(keyfile, group, key, new_list, new_length); } } - g_list_free_full(glist, g_free); + g_free(new_list); + return deleted; } diff --git a/src/config/preferences.c b/src/config/preferences.c index bad809c8..4646b63d 100644 --- a/src/config/preferences.c +++ b/src/config/preferences.c @@ -49,6 +49,7 @@ #include "tools/autocomplete.h" #include "config/files.h" #include "config/conflists.h" +#include "ui/ui.h" // preference groups refer to the sections in .profrc or theme files // for example [ui] but not [colours] which is handled in theme.c @@ -286,6 +287,77 @@ prefs_load(const char* config_file) _prefs_load(); } +static void +prefs_changes_print(const char* key, const char* newval, const char* oldval, gboolean* banner_shown) +{ +#define PREFS_CHANGES_FORMAT_STRING "%-32s | %-20s | %-20s" + if (!*banner_shown) { + cons_show(PREFS_CHANGES_FORMAT_STRING, "Key", "New value", "Old value"); + *banner_shown = TRUE; + } + cons_show(PREFS_CHANGES_FORMAT_STRING, key, newval, oldval); +#undef PREFS_CHANGES_FORMAT_STRING +} + +void +prefs_changes(void) +{ + const char* undef = ""; + prof_keyfile_t saved; + if (!load_custom_keyfile(&saved, g_strdup(prefs_prof_keyfile.filename))) + return; + gsize ngroups, nsavedgroups, g; + auto_gcharv gchar** groups = g_key_file_get_groups(prefs_prof_keyfile.keyfile, &ngroups); + gboolean banner_shown = FALSE; + for (g = 0; g < ngroups; ++g) { + gsize nkeys, k; + gboolean saved_has_group = g_key_file_has_group(saved.keyfile, groups[g]); + auto_gcharv gchar** keys = g_key_file_get_keys(prefs_prof_keyfile.keyfile, groups[g], &nkeys, NULL); + for (k = 0; k < nkeys; ++k) { + auto_gerror GError* err = NULL; + auto_gchar gchar* full_key = g_strdup_printf("%s.%s", groups[g], keys[k]); + auto_gchar gchar* new_value = g_key_file_get_value(prefs_prof_keyfile.keyfile, groups[g], keys[k], &err); + if (err || !new_value) { + cons_show_error("%s: New value (%s) error: %s", full_key, STR_MAYBE_NULL(new_value), PROF_GERROR_MESSAGE(err)); + continue; + } + if (!saved_has_group + || !g_key_file_has_key(saved.keyfile, groups[g], keys[k], NULL)) { + prefs_changes_print(full_key, new_value, undef, &banner_shown); + continue; + } + auto_gchar gchar* old_value = g_key_file_get_value(saved.keyfile, groups[g], keys[k], &err); + if (err || !old_value) { + cons_show_error("%s: Old value (%s) error: %s", full_key, STR_MAYBE_NULL(old_value), PROF_GERROR_MESSAGE(err)); + continue; + } + if (!g_str_equal(old_value, new_value)) { + prefs_changes_print(full_key, new_value, old_value, &banner_shown); + } + } + } + auto_gcharv gchar** savedgroups = g_key_file_get_groups(saved.keyfile, &nsavedgroups); + for (g = 0; g < nsavedgroups; ++g) { + gsize nkeys, k; + auto_gcharv gchar** keys = g_key_file_get_keys(saved.keyfile, savedgroups[g], &nkeys, NULL); + for (k = 0; k < nkeys; ++k) { + if (g_key_file_has_key(prefs_prof_keyfile.keyfile, savedgroups[g], keys[k], NULL)) + continue; + auto_gerror GError* err = NULL; + auto_gchar gchar* full_key = g_strdup_printf("%s.%s", groups[g], keys[k]); + auto_gchar gchar* old_value = g_key_file_get_value(saved.keyfile, groups[g], keys[k], &err); + if (err || !old_value) { + cons_show_error("%s: Old value (%s) error: %s", full_key, STR_MAYBE_NULL(old_value), PROF_GERROR_MESSAGE(err)); + continue; + } + prefs_changes_print(full_key, undef, old_value, &banner_shown); + } + } + free_keyfile(&saved); + if (!banner_shown) + cons_show("No changes to saved preferences."); +} + void prefs_save(void) { diff --git a/src/config/preferences.h b/src/config/preferences.h index 750a87ee..7effe91c 100644 --- a/src/config/preferences.h +++ b/src/config/preferences.h @@ -206,6 +206,7 @@ typedef struct prof_winplacement_t } ProfWinPlacement; void prefs_load(const char* config_file); +void prefs_changes(void); void prefs_save(void); void prefs_close(void); void prefs_reload(void); diff --git a/src/config/theme.c b/src/config/theme.c index 70b02b2f..74537671 100644 --- a/src/config/theme.c +++ b/src/config/theme.c @@ -533,7 +533,7 @@ _theme_find(const char* const theme_name) g_string_append(path, "/"); g_string_append(path, theme_name); if (!g_file_test(path->str, G_FILE_TEST_EXISTS)) { - g_string_free(path, true); + g_string_free(path, TRUE); path = NULL; } } @@ -667,25 +667,17 @@ _theme_prep_fgnd(char* setting, GString* lookup_str, gboolean* bold) } } -char* +gchar* theme_get_string(char* str) { gchar* res = g_key_file_get_string(theme, "colours", str, NULL); if (!res) { - return strdup(g_hash_table_lookup(defaults, str)); + return g_strdup(g_hash_table_lookup(defaults, str)); } else { return res; } } -void -theme_free_string(char* str) -{ - if (str) { - g_free(str); - } -} - int theme_hash_attrs(const char* str) { diff --git a/src/config/theme.h b/src/config/theme.h index ef076925..3aa38991 100644 --- a/src/config/theme.h +++ b/src/config/theme.h @@ -152,7 +152,6 @@ void theme_close(void); int theme_hash_attrs(const char* str); int theme_attrs(theme_item_t attrs); char* theme_get_string(char* str); -void theme_free_string(char* str); theme_item_t theme_main_presence_attrs(const char* const presence); theme_item_t theme_roster_unread_presence_attrs(const char* const presence); theme_item_t theme_roster_active_presence_attrs(const char* const presence); diff --git a/src/config/tlscerts.c b/src/config/tlscerts.c index 4f45da36..d2d59a5e 100644 --- a/src/config/tlscerts.c +++ b/src/config/tlscerts.c @@ -90,18 +90,18 @@ tlscerts_init(void) } void -tlscerts_set_current(const char* const fp) +tlscerts_set_current(const TLSCertificate* cert) { if (current_fp) { free(current_fp); } - current_fp = strdup(fp); + current_fp = strdup(cert->fingerprint); } -char* -tlscerts_get_current(void) +gboolean +tlscerts_current_fingerprint_equals(const TLSCertificate* cert) { - return current_fp; + return g_strcmp0(current_fp, cert->fingerprint) == 0; } void @@ -114,9 +114,9 @@ tlscerts_clear_current(void) } gboolean -tlscerts_exists(const char* const fingerprint) +tlscerts_exists(const TLSCertificate* cert) { - return g_key_file_has_group(tlscerts, fingerprint); + return g_key_file_has_group(tlscerts, cert->fingerprint); } GList* @@ -127,7 +127,7 @@ tlscerts_list(void) auto_gcharv gchar** groups = g_key_file_get_groups(tlscerts, &len); for (int i = 0; i < g_strv_length(groups); i++) { - char* fingerprint = strdup(groups[i]); + char* fingerprint = groups[i]; int version = g_key_file_get_integer(tlscerts, fingerprint, "version", NULL); auto_gchar gchar* serialnumber = g_key_file_get_string(tlscerts, fingerprint, "serialnumber", NULL); auto_gchar gchar* subjectname = g_key_file_get_string(tlscerts, fingerprint, "subjectname", NULL); @@ -138,7 +138,7 @@ tlscerts_list(void) auto_gchar gchar* signaturealg = g_key_file_get_string(tlscerts, fingerprint, "signaturealg", NULL); TLSCertificate* cert = tlscerts_new(fingerprint, version, serialnumber, subjectname, issuername, notbefore, - notafter, keyalg, signaturealg, NULL); + notafter, keyalg, signaturealg, NULL, NULL, NULL); res = g_list_append(res, cert); } @@ -147,14 +147,22 @@ tlscerts_list(void) } TLSCertificate* -tlscerts_new(const char* const fingerprint, int version, const char* const serialnumber, const char* const subjectname, - const char* const issuername, const char* const notbefore, const char* const notafter, - const char* const key_alg, const char* const signature_alg, const char* const pem) +tlscerts_new(const char* fingerprint_sha1, int version, const char* serialnumber, const char* subjectname, + const char* issuername, const char* notbefore, const char* notafter, + const char* key_alg, const char* signature_alg, const char* pem, + const char* fingerprint_sha256, const char* pubkey_fingerprint) { TLSCertificate* cert = calloc(1, sizeof(TLSCertificate)); - if (fingerprint) { - cert->fingerprint = strdup(fingerprint); + if (fingerprint_sha256) { + cert->fingerprint_sha256 = strdup(fingerprint_sha256); + cert->fingerprint = cert->fingerprint_sha256; + } + if (fingerprint_sha1) { + cert->fingerprint_sha1 = strdup(fingerprint_sha1); + if (!cert->fingerprint) { + cert->fingerprint = cert->fingerprint_sha1; + } } cert->version = version; if (serialnumber) { @@ -181,6 +189,9 @@ tlscerts_new(const char* const fingerprint, int version, const char* const seria if (pem) { cert->pem = strdup(pem); } + if (pubkey_fingerprint) { + cert->pubkey_fingerprint = strdup(pubkey_fingerprint); + } auto_gcharv gchar** fields = g_strsplit(subjectname, "/", 0); for (int i = 0; i < g_strv_length(fields); i++) { @@ -287,7 +298,7 @@ tlscerts_add(const TLSCertificate* cert) } gboolean -tlscerts_revoke(const char* const fingerprint) +tlscerts_revoke(const char* fingerprint) { gboolean result = g_key_file_remove_group(tlscerts, fingerprint, NULL); if (result) { @@ -300,7 +311,7 @@ tlscerts_revoke(const char* const fingerprint) } TLSCertificate* -tlscerts_get_trusted(const char* const fingerprint) +tlscerts_get_trusted(const char* fingerprint) { if (!g_key_file_has_group(tlscerts, fingerprint)) { return NULL; @@ -315,13 +326,12 @@ tlscerts_get_trusted(const char* const fingerprint) auto_gchar gchar* keyalg = g_key_file_get_string(tlscerts, fingerprint, "keyalg", NULL); auto_gchar gchar* signaturealg = g_key_file_get_string(tlscerts, fingerprint, "signaturealg", NULL); - TLSCertificate* cert = tlscerts_new(fingerprint, version, serialnumber, subjectname, issuername, notbefore, - notafter, keyalg, signaturealg, NULL); - return cert; + return tlscerts_new(fingerprint, version, serialnumber, subjectname, issuername, notbefore, + notafter, keyalg, signaturealg, NULL, NULL, NULL); } char* -tlscerts_complete(const char* const prefix, gboolean previous, void* context) +tlscerts_complete(const char* prefix, gboolean previous, void* context) { return autocomplete_complete(certs_ac, prefix, TRUE, previous); } @@ -360,7 +370,9 @@ tlscerts_free(TLSCertificate* cert) free(cert->notbefore); free(cert->notafter); - free(cert->fingerprint); + free(cert->fingerprint_sha1); + free(cert->fingerprint_sha256); + free(cert->pubkey_fingerprint); free(cert->key_alg); free(cert->signature_alg); diff --git a/src/config/tlscerts.h b/src/config/tlscerts.h index b8c9b5c4..e7ed7290 100644 --- a/src/config/tlscerts.h +++ b/src/config/tlscerts.h @@ -41,6 +41,7 @@ typedef struct tls_cert_t { int version; + const char* fingerprint; char* serialnumber; char* subjectname; char* subject_country; @@ -62,37 +63,40 @@ typedef struct tls_cert_t char* issuer_email; char* notbefore; char* notafter; - char* fingerprint; + char* fingerprint_sha1; + char* fingerprint_sha256; char* key_alg; char* signature_alg; char* pem; + char* pubkey_fingerprint; } TLSCertificate; void tlscerts_init(void); -TLSCertificate* tlscerts_new(const char* const fingerprint, int version, const char* const serialnumber, const char* const subjectname, - const char* const issuername, const char* const notbefore, const char* const notafter, - const char* const key_alg, const char* const signature_alg, const char* const pem); +TLSCertificate* tlscerts_new(const char* fingerprint_sha1, int version, const char* serialnumber, const char* subjectname, + const char* issuername, const char* notbefore, const char* notafter, + const char* key_alg, const char* signature_alg, const char* pem, + const char* fingerprint_sha256, const char* pubkey_fingerprint); -void tlscerts_set_current(const char* const fp); +void tlscerts_set_current(const TLSCertificate* cert); -char* tlscerts_get_current(void); +gboolean tlscerts_current_fingerprint_equals(const TLSCertificate* cert); void tlscerts_clear_current(void); -gboolean tlscerts_exists(const char* const fingerprint); +gboolean tlscerts_exists(const TLSCertificate* cert); void tlscerts_add(const TLSCertificate* cert); -gboolean tlscerts_revoke(const char* const fingerprint); +gboolean tlscerts_revoke(const char* fingerprint); -TLSCertificate* tlscerts_get_trusted(const char* const fingerprint); +TLSCertificate* tlscerts_get_trusted(const char* fingerprint); void tlscerts_free(TLSCertificate* cert); GList* tlscerts_list(void); -char* tlscerts_complete(const char* const prefix, gboolean previous, void* context); +char* tlscerts_complete(const char* prefix, gboolean previous, void* context); void tlscerts_reset_ac(void); diff --git a/src/event/client_events.c b/src/event/client_events.c index 7b68bdc1..d91e8d22 100644 --- a/src/event/client_events.c +++ b/src/event/client_events.c @@ -137,9 +137,14 @@ cl_ev_send_msg_correct(ProfChatWin* chatwin, const char* const msg, const char* chat_state_active(chatwin->state); gboolean request_receipt = prefs_get_boolean(PREF_RECEIPTS_REQUEST); + if (request_receipt) { + auto_char char* jid = chat_session_get_jid(chatwin->barejid); + request_receipt = caps_jid_has_feature(jid, XMPP_FEATURE_RECEIPTS); + } auto_char char* plugin_msg = plugins_pre_chat_message_send(chatwin->barejid, msg); - const char* const message = plugin_msg ?: msg; + auto_gchar gchar* sanitized_msg = str_xml_sanitize(plugin_msg ?: msg); + const char* message = sanitized_msg; char* replace_id = NULL; if (correct_last_msg) { @@ -201,7 +206,8 @@ void cl_ev_send_muc_msg_corrected(ProfMucWin* mucwin, const char* const msg, const char* const oob_url, gboolean correct_last_msg) { auto_char char* plugin_msg = plugins_pre_room_message_send(mucwin->roomjid, msg); - const char* const message = plugin_msg ?: msg; + auto_gchar gchar* sanitized_msg = str_xml_sanitize(plugin_msg ?: msg); + const char* message = sanitized_msg; char* replace_id = NULL; if (correct_last_msg) { @@ -242,7 +248,8 @@ cl_ev_send_priv_msg(ProfPrivateWin* privwin, const char* const msg, const char* privwin_message_left_room(privwin); } else { auto_char char* plugin_msg = plugins_pre_priv_message_send(privwin->fulljid, msg); - const char* const message = plugin_msg ?: msg; + auto_gchar gchar* sanitized_msg = str_xml_sanitize(plugin_msg ?: msg); + const char* message = sanitized_msg; auto_jid Jid* jidp = jid_create(privwin->fulljid); auto_char char* id = message_send_private(privwin->fulljid, message, oob_url); diff --git a/src/event/server_events.c b/src/event/server_events.c index cf25773d..e85876e8 100644 --- a/src/event/server_events.c +++ b/src/event/server_events.c @@ -587,6 +587,8 @@ _sv_ev_incoming_omemo(ProfChatWin* chatwin, gboolean new_win, ProfMessage* messa chat_log_omemo_msg_in(message); } chatwin->pgp_recv = FALSE; + + wins_omemo_trust_changed(chatwin->barejid); #endif } @@ -1150,14 +1152,13 @@ int sv_ev_certfail(const char* const errormsg, const TLSCertificate* cert) { // check profanity trusted certs - if (tlscerts_exists(cert->fingerprint)) { + if (tlscerts_exists(cert)) { cafile_add(cert); return 1; } // check current cert - char* current_fp = tlscerts_get_current(); - if (current_fp && g_strcmp0(current_fp, cert->fingerprint) == 0) { + if (tlscerts_current_fingerprint_equals(cert)) { return 1; } @@ -1188,11 +1189,11 @@ sv_ev_certfail(const char* const errormsg, const TLSCertificate* cert) if (g_strcmp0(cmd, "/tls allow") == 0) { cons_show("Continuing with connection."); - tlscerts_set_current(cert->fingerprint); + tlscerts_set_current(cert); return 1; } else if (g_strcmp0(cmd, "/tls always") == 0) { cons_show("Adding %s to trusted certificates.", cert->fingerprint); - if (!tlscerts_exists(cert->fingerprint)) { + if (!tlscerts_exists(cert)) { tlscerts_add(cert); cafile_add(cert); } diff --git a/src/log.c b/src/log.c index e6d10a71..1a05f9bf 100644 --- a/src/log.c +++ b/src/log.c @@ -60,6 +60,7 @@ static FILE* logp; static gchar* mainlogfile = NULL; static gboolean user_provided_log = FALSE; static log_level_t level_filter; +static pid_t prof_pid; static int stderr_inited; static log_level_t stderr_level; @@ -198,6 +199,8 @@ log_init(log_level_t filter, const char* const log_file) logp = fopen(mainlogfile, "a"); g_chmod(mainlogfile, S_IRUSR | S_IWUSR); + + prof_pid = getpid(); } const gchar* @@ -231,7 +234,7 @@ _log_msg(log_level_t level, const char* const area, const char* const msg) auto_gchar gchar* date_fmt = g_date_time_format_iso8601(dt); - fprintf(logp, "%s: %s: %s: %s\n", date_fmt, area, level_str, msg); + fprintf(logp, "%s: %08d: %s: %s: %s\n", date_fmt, prof_pid, area, level_str, msg); g_date_time_unref(dt); fflush(logp); diff --git a/src/omemo/crypto.c b/src/omemo/crypto.c index 8653b1d1..f63f340a 100644 --- a/src/omemo/crypto.c +++ b/src/omemo/crypto.c @@ -389,7 +389,7 @@ out: gcry_error_t aes256gcm_crypt_file(FILE* in, FILE* out, off_t file_size, - unsigned char key[], unsigned char nonce[], bool encrypt) + unsigned char key[], unsigned char nonce[], gboolean encrypt) { if (!gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P)) { diff --git a/src/omemo/crypto.h b/src/omemo/crypto.h index d3f775dd..4d510998 100644 --- a/src/omemo/crypto.h +++ b/src/omemo/crypto.h @@ -185,7 +185,7 @@ int aes128gcm_decrypt(unsigned char* plaintext, const unsigned char* const key, const unsigned char* const tag); gcry_error_t aes256gcm_crypt_file(FILE* in, FILE* out, off_t file_size, - unsigned char key[], unsigned char nonce[], bool encrypt); + unsigned char key[], unsigned char nonce[], gboolean encrypt); char* aes256gcm_create_secure_fragment(unsigned char* key, unsigned char* nonce); diff --git a/src/omemo/omemo.c b/src/omemo/omemo.c index e68271a9..f1ba52cb 100644 --- a/src/omemo/omemo.c +++ b/src/omemo/omemo.c @@ -143,6 +143,34 @@ omemo_init(void) pthread_mutex_init(&omemo_static_data.lock, &omemo_static_data.attr); omemo_static_data.fingerprint_ac = g_hash_table_new_full(g_str_hash, g_str_equal, free, (GDestroyNotify)autocomplete_free); + + wins_omemo_trust_changed(NULL); +} + +static gboolean +_omemo_finalize_identity_load(ProfAccount* account) +{ + auto_gchar gchar* omemo_dir = files_file_in_account_data_path(DIR_OMEMO, account->jid, NULL); + if (!omemo_dir) { + log_error("[OMEMO] failed creating directory"); + return FALSE; + } + + if (load_custom_keyfile(&omemo_ctx.trust, g_strdup_printf("%s/%s", omemo_dir, "trust.txt"))) { + _load_trust(); + } + + if (load_custom_keyfile(&omemo_ctx.sessions, g_strdup_printf("%s/%s", omemo_dir, "sessions.txt"))) { + _load_sessions(); + } + + if (load_custom_keyfile(&omemo_ctx.knowndevices, g_strdup_printf("%s/%s", omemo_dir, "known_devices.txt"))) { + _load_known_devices(); + } + + omemo_devicelist_subscribe(); + + return TRUE; } void @@ -243,46 +271,35 @@ omemo_on_connect(ProfAccount* account) return; } - if (load_custom_keyfile(&omemo_ctx.trust, g_strdup_printf("%s/%s", omemo_dir, "trust.txt"))) { - _load_trust(); - } + _omemo_finalize_identity_load(account); - if (load_custom_keyfile(&omemo_ctx.sessions, g_strdup_printf("%s/%s", omemo_dir, "sessions.txt"))) { - _load_sessions(); - } - - if (load_custom_keyfile(&omemo_ctx.knowndevices, g_strdup_printf("%s/%s", omemo_dir, "known_devices.txt"))) { - _load_known_devices(); - } - - omemo_devicelist_subscribe(); + wins_omemo_trust_changed(NULL); } void omemo_on_disconnect(void) { - if (!omemo_ctx.loaded) { - return; + if (omemo_ctx.loaded) { + free_keyfile(&omemo_ctx.knowndevices); + free_keyfile(&omemo_ctx.sessions); + free_keyfile(&omemo_ctx.trust); + + ec_public_key* pub = ratchet_identity_key_pair_get_public(omemo_ctx.identity_key_pair); + ec_private_key* priv = ratchet_identity_key_pair_get_private(omemo_ctx.identity_key_pair); + ratchet_identity_key_pair_destroy((signal_type_base*)omemo_ctx.identity_key_pair); + ec_private_key_destroy((signal_type_base*)priv); + ec_public_key_destroy((signal_type_base*)pub); } + free_keyfile(&omemo_ctx.identity); g_hash_table_destroy(omemo_ctx.known_devices); g_hash_table_destroy(omemo_ctx.device_list_handler); g_hash_table_destroy(omemo_ctx.device_list); - - free_keyfile(&omemo_ctx.knowndevices); - free_keyfile(&omemo_ctx.sessions); - free_keyfile(&omemo_ctx.trust); - free_keyfile(&omemo_ctx.identity); - signal_protocol_store_context_destroy(omemo_ctx.store); - ec_public_key* pub = ratchet_identity_key_pair_get_public(omemo_ctx.identity_key_pair); - ec_private_key* priv = ratchet_identity_key_pair_get_private(omemo_ctx.identity_key_pair); - ratchet_identity_key_pair_destroy((signal_type_base*)omemo_ctx.identity_key_pair); - ec_private_key_destroy((signal_type_base*)priv); - ec_public_key_destroy((signal_type_base*)pub); - signal_context_destroy(omemo_ctx.signal); memset(&omemo_ctx, 0, sizeof(omemo_ctx)); + + wins_omemo_trust_changed(NULL); } void @@ -302,6 +319,8 @@ omemo_generate_crypto_materials(ProfAccount* account) /* Identity key */ signal_protocol_key_helper_generate_identity_key_pair(&omemo_ctx.identity_key_pair, omemo_ctx.signal); + SIGNAL_REF(ratchet_identity_key_pair_get_public(omemo_ctx.identity_key_pair)); + SIGNAL_REF(ratchet_identity_key_pair_get_private(omemo_ctx.identity_key_pair)); ec_public_key_serialize(&omemo_ctx.identity_key_store.public, ratchet_identity_key_pair_get_public(omemo_ctx.identity_key_pair)); auto_gchar gchar* identity_key_public = g_base64_encode(signal_buffer_data(omemo_ctx.identity_key_store.public), signal_buffer_len(omemo_ctx.identity_key_store.public)); @@ -323,7 +342,8 @@ omemo_generate_crypto_materials(ProfAccount* account) omemo_identity_keyfile_save(); - omemo_ctx.loaded = TRUE; + if ((omemo_ctx.loaded = _omemo_finalize_identity_load(account)) == FALSE) + return; omemo_publish_crypto_materials(); omemo_start_sessions(); @@ -332,15 +352,13 @@ omemo_generate_crypto_materials(ProfAccount* account) void omemo_publish_crypto_materials(void) { - log_debug("[OMEMO] publish crypto materials"); - if (omemo_ctx.loaded != TRUE) { cons_show("OMEMO: cannot publish crypto materials before they are generated"); log_error("[OMEMO] cannot publish crypto materials before they are generated"); return; } - omemo_bundle_publish(true); + omemo_bundle_publish(TRUE); } static void @@ -546,6 +564,8 @@ omemo_set_device_list(const char* const from, GList* device_list) } } } + + wins_omemo_trust_changed(jid->barejid); } GKeyFile* @@ -578,9 +598,13 @@ omemo_sessions_keyfile(void) return omemo_ctx.sessions.keyfile; } +static gboolean omemo_sessions_keyfile_save_disable = FALSE; + void omemo_sessions_keyfile_save(void) { + if (omemo_sessions_keyfile_save_disable) + return; save_keyfile(&omemo_ctx.sessions); } @@ -645,8 +669,13 @@ omemo_start_device_session(const char* const jid, uint32_t device_id, } int prekey_index; + size_t prekeys_len = g_list_length(prekeys); + if (prekeys_len == 0) { + log_error("[OMEMO] No prekeys found for %s device %d", jid, device_id); + goto out; + } gcry_randomize(&prekey_index, sizeof(int), GCRY_STRONG_RANDOM); - prekey_index %= g_list_length(prekeys); + prekey_index %= prekeys_len; omemo_key_t* prekey = g_list_nth_data(prekeys, prekey_index); curve_decode_point(&prekey_public, prekey->data, prekey->length, omemo_ctx.signal); @@ -698,14 +727,16 @@ omemo_on_message_send(ProfWin* win, const char* const message, gboolean request_ unsigned char* key_tag; size_t ciphertext_len, tag_len; + omemo_sessions_keyfile_save_disable = TRUE; + ciphertext_len = strlen(message); ciphertext = malloc(ciphertext_len); tag_len = AES128_GCM_TAG_LENGTH; tag = gcry_malloc_secure(tag_len); key_tag = gcry_malloc_secure(AES128_GCM_KEY_LENGTH + AES128_GCM_TAG_LENGTH); - key = gcry_random_bytes_secure(AES128_GCM_KEY_LENGTH, GCRY_VERY_STRONG_RANDOM); - iv = gcry_random_bytes_secure(AES128_GCM_IV_LENGTH, GCRY_VERY_STRONG_RANDOM); + key = gcry_random_bytes_secure(AES128_GCM_KEY_LENGTH + AES128_GCM_IV_LENGTH, GCRY_VERY_STRONG_RANDOM); + iv = key + AES128_GCM_KEY_LENGTH; res = aes128gcm_encrypt(ciphertext, &ciphertext_len, tag, &tag_len, (const unsigned char* const)message, strlen(message), iv, key); if (res != 0) { @@ -736,7 +767,7 @@ omemo_on_message_send(ProfWin* win, const char* const message, gboolean request_ GList* device_ids_iter; - omemo_ctx.identity_key_store.recv = false; + omemo_ctx.identity_key_store.recv = FALSE; // Encrypt keys for the recipients GList* recipients_iter; @@ -862,10 +893,11 @@ omemo_on_message_send(ProfWin* win, const char* const message, gboolean request_ } out: + omemo_sessions_keyfile_save_disable = FALSE; + omemo_sessions_keyfile_save(); g_list_free_full(keys, (GDestroyNotify)omemo_key_free); free(ciphertext); gcry_free(key); - gcry_free(iv); gcry_free(tag); gcry_free(key_tag); @@ -939,14 +971,14 @@ omemo_on_message_recv(const char* const from_jid, uint32_t sid, ec_public_key* their_identity_key; signal_buffer* identity_buffer = NULL; - omemo_ctx.identity_key_store.recv = true; + omemo_ctx.identity_key_store.recv = TRUE; pre_key_signal_message_deserialize(&message, key->data, key->length, omemo_ctx.signal); their_identity_key = pre_key_signal_message_get_identity_key(message); res = session_cipher_decrypt_pre_key_signal_message(cipher, message, NULL, &plaintext_key); - omemo_ctx.identity_key_store.recv = false; + omemo_ctx.identity_key_store.recv = FALSE; /* Perform a real check of the identity */ ec_public_key_serialize(&identity_buffer, their_identity_key); @@ -964,7 +996,7 @@ omemo_on_message_recv(const char* const from_jid, uint32_t sid, SIGNAL_UNREF(new_pre_key); SIGNAL_UNREF(message); SIGNAL_UNREF(ec_pair); - omemo_bundle_publish(true); + omemo_bundle_publish(TRUE); if (res == 0) { /* Start a new session */ @@ -981,7 +1013,7 @@ omemo_on_message_recv(const char* const from_jid, uint32_t sid, log_error("[OMEMO][RECV] cannot deserialize message"); } else { res = session_cipher_decrypt_signal_message(cipher, message, NULL, &plaintext_key); - *trusted = true; + *trusted = TRUE; SIGNAL_UNREF(message); } } @@ -1104,6 +1136,53 @@ omemo_is_trusted_identity(const char* const jid, const char* const fingerprint) return trusted; } +gboolean +omemo_is_jid_trusted(const char* const jid) +{ + if (!omemo_loaded()) { + return FALSE; + } + + GList* device_list = g_hash_table_lookup(omemo_ctx.device_list, jid); + if (!device_list) { + return FALSE; + } + + GList* device_id_iter; + for (device_id_iter = device_list; device_id_iter != NULL; device_id_iter = device_id_iter->next) { + uint32_t device_id = GPOINTER_TO_UINT(device_id_iter->data); + if (device_id == omemo_ctx.device_id && equals_our_barejid(jid)) { + continue; + } + + GHashTable* known_identities = g_hash_table_lookup(omemo_ctx.known_devices, jid); + if (!known_identities) { + return FALSE; + } + + gboolean found = FALSE; + GList* fp_list = g_hash_table_get_keys(known_identities); + GList* fp_iter; + for (fp_iter = fp_list; fp_iter != NULL; fp_iter = fp_iter->next) { + if (device_id == GPOINTER_TO_UINT(g_hash_table_lookup(known_identities, fp_iter->data))) { + if (!omemo_is_trusted_identity(jid, fp_iter->data)) { + g_list_free(fp_list); + return FALSE; + } + found = TRUE; + break; + } + } + g_list_free(fp_list); + + if (!found) { + return FALSE; + } + } + + return TRUE; +} + static char* _omemo_fingerprint(ec_public_key* identity, gboolean formatted) { @@ -1210,6 +1289,8 @@ omemo_trust(const char* const jid, const char* const fingerprint_formatted) signal_buffer_free(buffer); omemo_bundle_request(jid, device_id, omemo_start_device_session_handle_bundle, free, strdup(jid)); + + wins_omemo_trust_changed(jid); } void @@ -1265,6 +1346,8 @@ omemo_untrust(const char* const jid, const char* const fingerprint_formatted) auto_gchar gchar* device_id_str = g_strdup_printf("%d", device_id); g_key_file_remove_key(omemo_ctx.trust.keyfile, jid, device_id_str, NULL); omemo_trust_keyfile_save(); + + wins_omemo_trust_changed(jid); } static void @@ -1638,6 +1721,8 @@ _cache_device_identity(const char* const jid, uint32_t device_id, ec_public_key* autocomplete_add(ac, formatted_fingerprint); free(formatted_fingerprint); free(fingerprint); + + wins_omemo_trust_changed(jid); } static void @@ -1688,7 +1773,7 @@ omemo_encrypt_file(FILE* in, FILE* out, off_t file_size, int* gcry_res) gcry_create_nonce(nonce, OMEMO_AESGCM_NONCE_LENGTH); char* fragment = aes256gcm_create_secure_fragment(key, nonce); - *gcry_res = aes256gcm_crypt_file(in, out, file_size, key, nonce, true); + *gcry_res = aes256gcm_crypt_file(in, out, file_size, key, nonce, TRUE); if (*gcry_res != GPG_ERR_NO_ERROR) { gcry_free(fragment); @@ -1747,7 +1832,7 @@ omemo_decrypt_file(FILE* in, FILE* out, off_t file_size, const char* fragment) key, OMEMO_AESGCM_KEY_LENGTH); gcry_error_t crypt_res; - crypt_res = aes256gcm_crypt_file(in, out, file_size, key, nonce, false); + crypt_res = aes256gcm_crypt_file(in, out, file_size, key, nonce, FALSE); gcry_free(key); diff --git a/src/omemo/omemo.h b/src/omemo/omemo.h index 39479da5..06708617 100644 --- a/src/omemo/omemo.h +++ b/src/omemo/omemo.h @@ -85,6 +85,7 @@ void omemo_trust(const char* const jid, const char* const fingerprint); void omemo_untrust(const char* const jid, const char* const fingerprint); GList* omemo_known_device_identities(const char* const jid); gboolean omemo_is_trusted_identity(const char* const jid, const char* const fingerprint); +gboolean omemo_is_jid_trusted(const char* const jid); char* omemo_fingerprint_autocomplete(const char* const search_str, gboolean previous, void* context); void omemo_fingerprint_autocomplete_reset(void); gboolean omemo_automatic_start(const char* const recipient); diff --git a/src/omemo/store.h b/src/omemo/store.h index b6a9a74d..742ac2aa 100644 --- a/src/omemo/store.h +++ b/src/omemo/store.h @@ -50,7 +50,7 @@ typedef struct signal_buffer* private; uint32_t registration_id; GHashTable* trusted; - bool recv; + gboolean recv; } identity_key_store_t; GHashTable* session_store_new(void); diff --git a/src/otr/otr.c b/src/otr/otr.c index 0a67bb03..383183fb 100644 --- a/src/otr/otr.c +++ b/src/otr/otr.c @@ -296,7 +296,8 @@ otr_on_message_recv(const char* const barejid, const char* const resource, const // 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)) { + char* tag_position = whitespace_base + strlen(OTRL_MESSAGE_TAG_BASE); + if (strncmp(tag_position, OTRL_MESSAGE_TAG_V2, strlen(OTRL_MESSAGE_TAG_V2)) == 0 || strncmp(tag_position, OTRL_MESSAGE_TAG_V1, strlen(OTRL_MESSAGE_TAG_V1)) == 0) { // Remove whitespace pattern for proper display in UI // Handle both BASE+TAGV1/2(16+8) and BASE+TAGV1+TAGV2(16+8+8) int tag_length = 24; diff --git a/src/pgp/gpg.c b/src/pgp/gpg.c index 72511d77..9cd6b79d 100644 --- a/src/pgp/gpg.c +++ b/src/pgp/gpg.c @@ -86,7 +86,7 @@ _p_gpg_free_pubkeyid(ProfPGPPubKeyId* pubkeyid) free(pubkeyid); } -static gpgme_error_t* +static gpgme_error_t _p_gpg_passphrase_cb(void* hook, const char* uid_hint, const char* passphrase_info, int prev_was_bad, int fd) { if (passphrase) { @@ -108,7 +108,7 @@ _p_gpg_passphrase_cb(void* hook, const char* uid_hint, const char* passphrase_in gpgme_io_write(fd, passphrase_attempt, strlen(passphrase_attempt)); } - return 0; + return GPG_ERR_NO_ERROR; } static void diff --git a/src/pgp/ox.c b/src/pgp/ox.c index f92df503..b06d7ccf 100644 --- a/src/pgp/ox.c +++ b/src/pgp/ox.c @@ -85,6 +85,7 @@ ox_gpg_public_keys(void) if (error != GPG_ERR_EOF && error != GPG_ERR_NO_ERROR) { log_error("OX: gpgme_op_keylist_next %s %s", gpgme_strsource(error), gpgme_strerror(error)); g_hash_table_destroy(result); + gpgme_release(ctx); return NULL; } while (!error) { @@ -145,10 +146,16 @@ ox_gpg_public_keys(void) char* p_ox_gpg_signcrypt(const char* const sender_barejid, const char* const recipient_barejid, const char* const message) { + char* result = NULL; + gpgme_ctx_t ctx = NULL; + gpgme_key_t recp[3] = { NULL, NULL, NULL }; + gpgme_data_t plain = NULL; + gpgme_data_t cipher = NULL; + char* cipher_str = NULL; + setlocale(LC_ALL, ""); gpgme_check_version(NULL); gpgme_set_locale(NULL, LC_CTYPE, setlocale(LC_CTYPE, NULL)); - gpgme_ctx_t ctx; gpgme_error_t error = gpgme_new(&ctx); if (GPG_ERR_NO_ERROR != error) { @@ -165,13 +172,6 @@ p_ox_gpg_signcrypt(const char* const sender_barejid, const char* const recipient gpgme_set_textmode(ctx, 0); gpgme_set_offline(ctx, 1); gpgme_set_keylist_mode(ctx, GPGME_KEYLIST_MODE_LOCAL); - if (error != 0) { - log_error("OX: Signcrypt error: %s", gpgme_strerror(error)); - } - - gpgme_key_t recp[3]; - recp[0] = NULL, - recp[1] = NULL; char* xmpp_jid_me = alloca((strlen(sender_barejid) + 6) * sizeof(char)); char* xmpp_jid_recipient = alloca((strlen(recipient_barejid) + 6) * sizeof(char)); @@ -185,64 +185,73 @@ p_ox_gpg_signcrypt(const char* const sender_barejid, const char* const recipient // lookup own key recp[0] = _ox_key_lookup(sender_barejid, TRUE); - if (error != 0) { + if (recp[0] == NULL) { cons_show_error("Can't find OX key for %s", xmpp_jid_me); - log_error("OX: Key not found for %s. Error: %s", xmpp_jid_me, gpgme_strerror(error)); - return NULL; + log_error("OX: Key not found for %s.", xmpp_jid_me); + goto cleanup; } error = gpgme_signers_add(ctx, recp[0]); if (error != 0) { log_error("OX: gpgme_signers_add %s. Error: %s", xmpp_jid_me, gpgme_strerror(error)); - return NULL; + goto cleanup; } // lookup key of recipient recp[1] = _ox_key_lookup(recipient_barejid, FALSE); - if (error != 0) { + if (recp[1] == NULL) { cons_show_error("Can't find OX key for %s", xmpp_jid_recipient); - log_error("OX: Key not found for %s. Error: %s", xmpp_jid_recipient, gpgme_strerror(error)); - return NULL; + log_error("OX: Key not found for %s.", xmpp_jid_recipient); + goto cleanup; } recp[2] = NULL; - log_debug("OX: %s <%s>", recp[0]->uids->name, recp[0]->uids->email); - log_debug("OX: %s <%s>", recp[1]->uids->name, recp[1]->uids->email); + if (recp[0]->uids) { + log_debug("OX: %s <%s>", recp[0]->uids->name, recp[0]->uids->email); + } + if (recp[1]->uids) { + log_debug("OX: %s <%s>", recp[1]->uids->name, recp[1]->uids->email); + } gpgme_encrypt_flags_t flags = 0; - gpgme_data_t plain; - gpgme_data_t cipher; - - error = gpgme_data_new(&plain); - if (error != 0) { - log_error("OX: %s", gpgme_strerror(error)); - return NULL; - } - error = gpgme_data_new_from_mem(&plain, message, strlen(message), 0); if (error != 0) { log_error("OX: %s", gpgme_strerror(error)); - return NULL; + goto cleanup; } + error = gpgme_data_new(&cipher); if (error != 0) { log_error("OX: %s", gpgme_strerror(error)); - return NULL; + goto cleanup; } error = gpgme_op_encrypt_sign(ctx, recp, flags, plain, cipher); if (error != 0) { log_error("OX: %s", gpgme_strerror(error)); - return NULL; + goto cleanup; } size_t len; - char* cipher_str = gpgme_data_release_and_get_mem(cipher, &len); - char* result = g_base64_encode((unsigned char*)cipher_str, len); - gpgme_key_release(recp[0]); - gpgme_key_release(recp[1]); - gpgme_release(ctx); + cipher_str = gpgme_data_release_and_get_mem(cipher, &len); + cipher = NULL; // Already released by gpgme_data_release_and_get_mem + result = g_base64_encode((unsigned char*)cipher_str, len); + +cleanup: + if (cipher_str) + gpgme_free(cipher_str); + if (plain) + gpgme_data_release(plain); + if (cipher) + gpgme_data_release(cipher); + if (recp[0]) + gpgme_key_release(recp[0]); + if (recp[1]) + gpgme_key_release(recp[1]); + if (ctx) + gpgme_release(ctx); + return result; } @@ -313,6 +322,7 @@ _ox_key_lookup(const char* const barejid, gboolean secret_only) if (uid->name && strlen(uid->name) >= 10) { if (g_strcmp0(uid->name, xmppuri->str) == 0) { gpgme_release(ctx); + g_string_free(xmppuri, TRUE); return key; } } @@ -321,6 +331,7 @@ _ox_key_lookup(const char* const barejid, gboolean secret_only) gpgme_key_unref(key); error = gpgme_op_keylist_next(ctx, &key); } + g_string_free(xmppuri, TRUE); } gpgme_release(ctx); @@ -362,6 +373,13 @@ _ox_key_is_usable(gpgme_key_t key, const char* const barejid, gboolean secret) char* p_ox_gpg_decrypt(char* base64) { + char* result = NULL; + gpgme_ctx_t ctx = NULL; + gpgme_data_t plain = NULL; + gpgme_data_t cipher = NULL; + guchar* encrypted = NULL; + char* plain_str = NULL; + // if there is no private key avaibale, // we don't try do decrypt if (!ox_is_private_key_available(connection_get_barejid())) { @@ -370,7 +388,6 @@ p_ox_gpg_decrypt(char* base64) setlocale(LC_ALL, ""); gpgme_check_version(NULL); gpgme_set_locale(NULL, LC_CTYPE, setlocale(LC_CTYPE, NULL)); - gpgme_ctx_t ctx; gpgme_error_t error = gpgme_new(&ctx); if (GPG_ERR_NO_ERROR != error) { @@ -387,25 +404,19 @@ p_ox_gpg_decrypt(char* base64) gpgme_set_textmode(ctx, 0); gpgme_set_offline(ctx, 1); gpgme_set_keylist_mode(ctx, GPGME_KEYLIST_MODE_LOCAL); - if (error != 0) { - log_error("OX: %s", gpgme_strerror(error)); - } - - gpgme_data_t plain = NULL; - gpgme_data_t cipher = NULL; gsize s; - guchar* encrypted = g_base64_decode(base64, &s); + encrypted = g_base64_decode(base64, &s); error = gpgme_data_new_from_mem(&cipher, (char*)encrypted, s, 0); if (error != 0) { log_error("OX: gpgme_data_new_from_mem: %s", gpgme_strerror(error)); - return NULL; + goto cleanup; } error = gpgme_data_new(&plain); if (error != 0) { log_error("OX: %s", gpgme_strerror(error)); - return NULL; + goto cleanup; } error = gpgme_op_decrypt_verify(ctx, cipher, plain); @@ -413,18 +424,29 @@ p_ox_gpg_decrypt(char* base64) log_error("OX: gpgme_op_decrypt: %s", gpgme_strerror(error)); error = gpgme_op_decrypt(ctx, cipher, plain); if (error != 0) { - return NULL; + goto cleanup; } } size_t len; - char* plain_str = gpgme_data_release_and_get_mem(plain, &len); - char* result = NULL; + plain_str = gpgme_data_release_and_get_mem(plain, &len); + plain = NULL; // Already released by gpgme_data_release_and_get_mem if (plain_str) { result = strndup(plain_str, len); - gpgme_free(plain_str); } +cleanup: + if (encrypted) + g_free(encrypted); + if (plain_str) + gpgme_free(plain_str); + if (plain) + gpgme_data_release(plain); + if (cipher) + gpgme_data_release(cipher); + if (ctx) + gpgme_release(ctx); + return result; } @@ -455,30 +477,33 @@ p_ox_gpg_readkey(const char* const filename, char** key, char** fp) { log_info("PX: Read OpenPGP Key from file %s", filename); - GError* error = NULL; + GError* gerr = NULL; gchar* data = NULL; gsize size = -1; + gpgme_ctx_t ctx = NULL; + gpgme_data_t gpgme_data = NULL; + gpgme_key_t gkey = NULL; + gpgme_key_t end = NULL; gboolean success = g_file_get_contents(filename, &data, &size, - &error); + &gerr); if (success) { setlocale(LC_ALL, ""); gpgme_check_version(NULL); gpgme_set_locale(NULL, LC_CTYPE, setlocale(LC_CTYPE, NULL)); - gpgme_ctx_t ctx; gpgme_error_t error = gpgme_new(&ctx); if (GPG_ERR_NO_ERROR != error) { log_error("OX: Read OpenPGP key from file: gpgme_new failed: %s", gpgme_strerror(error)); - return; + goto cleanup; } error = gpgme_set_protocol(ctx, GPGME_PROTOCOL_OPENPGP); if (error != GPG_ERR_NO_ERROR) { log_error("OX: Read OpenPGP key from file: set GPGME_PROTOCOL_OPENPGP: %s", gpgme_strerror(error)); - return; + goto cleanup; } gpgme_set_armor(ctx, 0); @@ -486,49 +511,54 @@ p_ox_gpg_readkey(const char* const filename, char** key, char** fp) gpgme_set_offline(ctx, 1); gpgme_set_keylist_mode(ctx, GPGME_KEYLIST_MODE_LOCAL); - gpgme_data_t gpgme_data = NULL; - error = gpgme_data_new(&gpgme_data); - if (error != GPG_ERR_NO_ERROR) { - log_error("OX: Read OpenPGP key from file: gpgme_data_new %s", gpgme_strerror(error)); - return; - } - error = gpgme_data_new_from_mem(&gpgme_data, (char*)data, size, 0); if (error != GPG_ERR_NO_ERROR) { log_error("OX: Read OpenPGP key from file: gpgme_data_new_from_mem %s", gpgme_strerror(error)); - return; + goto cleanup; } error = gpgme_op_keylist_from_data_start(ctx, gpgme_data, 0); if (error != GPG_ERR_NO_ERROR) { log_error("OX: Read OpenPGP key from file: gpgme_op_keylist_from_data_start %s", gpgme_strerror(error)); - return; + goto cleanup; } - gpgme_key_t gkey; error = gpgme_op_keylist_next(ctx, &gkey); if (error != GPG_ERR_NO_ERROR) { log_error("OX: Read OpenPGP key from file: gpgme_op_keylist_next %s", gpgme_strerror(error)); - return; + goto cleanup; } - gpgme_key_t end; error = gpgme_op_keylist_next(ctx, &end); if (error == GPG_ERR_NO_ERROR) { log_error("OX: Read OpenPGP key from file: ambiguous key"); - return; + goto cleanup; } if (gkey->revoked || gkey->expired || gkey->disabled || gkey->invalid || gkey->secret) { log_error("OX: Read OpenPGP key from file: Key is not valid"); - return; + goto cleanup; } gchar* keybase64 = g_base64_encode((const guchar*)data, size); *key = strdup(keybase64); *fp = strdup(gkey->fpr); + g_free(keybase64); } else { - log_error("OX: Read OpenPGP key from file: Unable to read file: %s", error->message); + log_error("OX: Read OpenPGP key from file: Unable to read file: %s", gerr->message); + g_error_free(gerr); } + +cleanup: + if (data) + g_free(data); + if (gpgme_data) + gpgme_data_release(gpgme_data); + if (gkey) + gpgme_key_unref(gkey); + if (end) + gpgme_key_unref(end); + if (ctx) + gpgme_release(ctx); } gboolean @@ -536,22 +566,26 @@ p_ox_gpg_import(char* base64_public_key) { gsize size = -1; guchar* key = g_base64_decode(base64_public_key, &size); + gboolean result = TRUE; setlocale(LC_ALL, ""); gpgme_check_version(NULL); gpgme_set_locale(NULL, LC_CTYPE, setlocale(LC_CTYPE, NULL)); - gpgme_ctx_t ctx; + gpgme_ctx_t ctx = NULL; + gpgme_data_t gpgme_data = NULL; gpgme_error_t error = gpgme_new(&ctx); if (GPG_ERR_NO_ERROR != error) { log_error("OX: Read OpenPGP key from file: gpgme_new failed: %s", gpgme_strerror(error)); - return FALSE; + result = FALSE; + goto cleanup; } error = gpgme_set_protocol(ctx, GPGME_PROTOCOL_OPENPGP); if (error != GPG_ERR_NO_ERROR) { log_error("OX: Read OpenPGP key from file: set GPGME_PROTOCOL_OPENPGP: %s", gpgme_strerror(error)); - return FALSE; + result = FALSE; + goto cleanup; } gpgme_set_armor(ctx, 0); @@ -559,18 +593,26 @@ p_ox_gpg_import(char* base64_public_key) gpgme_set_offline(ctx, 1); gpgme_set_keylist_mode(ctx, GPGME_KEYLIST_MODE_LOCAL); - gpgme_data_t gpgme_data = NULL; - error = gpgme_data_new(&gpgme_data); + error = gpgme_data_new_from_mem(&gpgme_data, (gchar*)key, size, 0); if (error != GPG_ERR_NO_ERROR) { - log_error("OX: Read OpenPGP key from file: gpgme_data_new %s", gpgme_strerror(error)); - return FALSE; + log_error("OX: Read OpenPGP key from file: gpgme_data_new_from_mem %s", gpgme_strerror(error)); + result = FALSE; + goto cleanup; } - gpgme_data_new_from_mem(&gpgme_data, (gchar*)key, size, 0); error = gpgme_op_import(ctx, gpgme_data); if (error != GPG_ERR_NO_ERROR) { log_error("OX: Failed to import key"); + result = FALSE; } - return TRUE; +cleanup: + if (key) + g_free(key); + if (gpgme_data) + gpgme_data_release(gpgme_data); + if (ctx) + gpgme_release(ctx); + + return result; } diff --git a/src/plugins/api.c b/src/plugins/api.c index f96eb40a..379a0166 100644 --- a/src/plugins/api.c +++ b/src/plugins/api.c @@ -261,6 +261,11 @@ api_get_current_occupants(void) assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK); GList* occupants_list = muc_roster(mucwin->roomjid); char** result = malloc((g_list_length(occupants_list) + 1) * sizeof(char*)); + if (result == NULL) { + g_list_free(occupants_list); + return NULL; + } + GList* curr = occupants_list; int i = 0; while (curr) { @@ -269,6 +274,9 @@ api_get_current_occupants(void) curr = g_list_next(curr); } result[i] = NULL; + + g_list_free(occupants_list); + return result; } else { return NULL; diff --git a/src/profanity.c b/src/profanity.c index b9b0cc87..8769357b 100644 --- a/src/profanity.c +++ b/src/profanity.c @@ -206,6 +206,13 @@ _connect_default(const char* const account) } } +static void +sigterm_handler(int sig) +{ + log_info("Received signal %d, exiting", sig); + force_quit = TRUE; +} + static void _init(char* log_level, char* config_file, char* log_file, char* theme_name) { @@ -215,6 +222,8 @@ _init(char* log_level, char* config_file, char* log_file, char* theme_name) signal(SIGINT, SIG_IGN); signal(SIGTSTP, SIG_IGN); signal(SIGWINCH, ui_sigwinch_handler); + signal(SIGTERM, sigterm_handler); + signal(SIGHUP, sigterm_handler); if (pthread_mutex_init(&lock, NULL) != 0) { log_error("Mutex init failed"); exit(1); diff --git a/src/tools/aesgcm_download.c b/src/tools/aesgcm_download.c index 6daece5b..fe70228a 100644 --- a/src/tools/aesgcm_download.c +++ b/src/tools/aesgcm_download.c @@ -106,9 +106,11 @@ aesgcm_file_get(void* userdata) http_dl->worker = aesgcm_dl->worker; http_dl->id = strdup(aesgcm_dl->id); http_dl->url = strdup(https_url); + http_dl->display_url = strdup(aesgcm_dl->url); http_dl->filename = strdup(tmpname); http_dl->cmd_template = NULL; http_dl->silent = FALSE; + http_dl->silent_done = TRUE; http_dl->return_bytes_received = TRUE; aesgcm_dl->http_dl = http_dl; @@ -139,10 +141,12 @@ aesgcm_file_get(void* userdata) http_print_transfer_update(aesgcm_dl->window, aesgcm_dl->id, "Downloading '%s' failed: Failed to decrypt " "file (%s).", - https_url, gcry_strerror(crypt_res)); + aesgcm_dl->url, gcry_strerror(crypt_res)); } else { http_print_transfer_update(aesgcm_dl->window, aesgcm_dl->id, - "Decrypted file saved to '%s'", aesgcm_dl->filename); + "Downloading '%s': done\nSaved to '%s'", + aesgcm_dl->url, aesgcm_dl->filename); + win_mark_received(aesgcm_dl->window, aesgcm_dl->id); } if (aesgcm_dl->cmd_template != NULL) { diff --git a/src/tools/bookmark_ignore.h b/src/tools/bookmark_ignore.h index 5724ea2b..adb3f002 100644 --- a/src/tools/bookmark_ignore.h +++ b/src/tools/bookmark_ignore.h @@ -36,7 +36,7 @@ #ifndef BOOKMARK_IGNORE_H #define BOOKMARK_IGNORE_H -void bookmark_ignore_on_connect(); +void bookmark_ignore_on_connect(const char* const barejid); void bookmark_ignore_on_disconnect(); gboolean bookmark_ignored(Bookmark* bookmark); gchar** bookmark_ignore_list(gsize* len); diff --git a/src/tools/http_download.c b/src/tools/http_download.c index ac8d6609..a0d45675 100644 --- a/src/tools/http_download.c +++ b/src/tools/http_download.c @@ -58,7 +58,6 @@ #include "common.h" GSList* download_processes = NULL; -gboolean silent = FALSE; static int _xferinfo(void* userdata, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) @@ -84,9 +83,16 @@ _xferinfo(void* userdata, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultot dlperc = (100 * dlnow) / dltotal; } - if (!silent) - http_print_transfer_update(download->window, download->url, - "Downloading '%s': %d%%", download->url, dlperc); + if (!download->silent) { + const char* url = download->display_url ? download->display_url : download->url; + if (dlnow == dltotal && dltotal > 0) { + http_print_transfer_update(download->window, download->id, + "Downloading '%s': done", url); + } else { + http_print_transfer_update(download->window, download->id, + "Downloading '%s': %d%%", url, dlperc); + } + } pthread_mutex_unlock(&lock); @@ -111,15 +117,15 @@ http_file_get(void* userdata) CURL* curl; CURLcode res; - silent = download->silent; download->cancel = 0; download->bytes_received = 0; pthread_mutex_lock(&lock); - if (!silent) { + const char* display_url = download->display_url ? download->display_url : download->url; + if (!download->silent) { http_print_transfer(download->window, download->id, - "Downloading '%s': 0%%", download->url); + "Downloading '%s': 0%%", display_url); } FILE* outfh = fopen(download->filename, "wb"); @@ -177,7 +183,7 @@ http_file_get(void* userdata) err = strdup(curl_easy_strerror(res)); } - if (ftell(outfh) == 0) { + if (!err && ftell(outfh) == 0) { err = strdup("Output file is empty."); } @@ -185,7 +191,9 @@ http_file_get(void* userdata) curl_global_cleanup(); if (fclose(outfh) == EOF) { - err = strdup(g_strerror(errno)); + if (!err) { + err = strdup(g_strerror(errno)); + } } pthread_mutex_lock(&lock); @@ -196,22 +204,24 @@ http_file_get(void* userdata) http_print_transfer_update(download->window, download->id, "Downloading '%s' failed: " "Download was canceled", - download->url); + display_url); } else { http_print_transfer_update(download->window, download->id, "Downloading '%s' failed: %s", - download->url, err); + display_url, err); } free(err); } else { - if (!download->cancel && !silent) { + if (!download->cancel && !download->silent && !download->silent_done) { http_print_transfer_update(download->window, download->id, "Downloading '%s': done\nSaved to '%s'", - download->url, download->filename); + display_url, download->filename); win_mark_received(download->window, download->id); if (download->return_bytes_received) { ret = malloc(sizeof(*ret)); - *ret = download->bytes_received; + if (ret) { + *ret = download->bytes_received; + } } } } @@ -226,7 +236,7 @@ http_file_get(void* userdata) http_print_transfer_update(download->window, download->id, "Downloading '%s' failed: Unable to call " "command '%s' with file at '%s' (%s).", - download->url, + display_url, download->cmd_template, download->filename, "TODO: Log the error"); @@ -243,6 +253,7 @@ out: free(download->filename); free(download->url); + free(download->display_url); free(download->id); free(download); diff --git a/src/tools/http_download.h b/src/tools/http_download.h index 99a9fc06..9fdc73f4 100644 --- a/src/tools/http_download.h +++ b/src/tools/http_download.h @@ -51,6 +51,7 @@ typedef struct http_download_t { char* id; char* url; + char* display_url; char* filename; char* cmd_template; curl_off_t bytes_received; @@ -58,6 +59,7 @@ typedef struct http_download_t pthread_t worker; int cancel; gboolean silent; + gboolean silent_done; gboolean return_bytes_received; } HTTPDownload; diff --git a/src/tools/http_upload.c b/src/tools/http_upload.c index 166797f1..b9dbec6a 100644 --- a/src/tools/http_upload.c +++ b/src/tools/http_upload.c @@ -91,7 +91,13 @@ _xferinfo(void* userdata, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultot ulperc = (100 * ulnow) / ultotal; } - gchar* msg = g_strdup_printf("Uploading '%s': %d%%", upload->filename, ulperc); + gchar* msg = NULL; + if (ulnow == ultotal && ultotal > 0) { + msg = g_strdup_printf("Uploading '%s': done", upload->filename); + } else { + msg = g_strdup_printf("Uploading '%s': %d%%", upload->filename, ulperc); + } + if (!msg) { msg = g_strdup(FALLBACK_MSG); } @@ -314,7 +320,7 @@ http_file_put(void* userdata) cons_show_error("%s", err_msg); } else { if (!upload->cancel) { - auto_gchar gchar* status_msg = g_strdup_printf("Uploading '%s': 100%%", upload->filename); + auto_gchar gchar* status_msg = g_strdup_printf("Uploading '%s': done", upload->filename); if (!status_msg) { status_msg = g_strdup(FALLBACK_MSG); } diff --git a/src/ui/chatwin.c b/src/ui/chatwin.c index 4a0c1498..83b4195f 100644 --- a/src/ui/chatwin.c +++ b/src/ui/chatwin.c @@ -419,7 +419,8 @@ chatwin_outgoing_msg(ProfChatWin* chatwin, const char* const message, const char const char* display_message = plugin_message ?: message; if (request_receipt && id) { - win_print_outgoing_with_receipt((ProfWin*)chatwin, enc_char, "me", display_message, id, replace_id); + auto_gchar gchar* outgoing_str = prefs_get_string(PREF_OUTGOING_STAMP); + win_print_outgoing_with_receipt((ProfWin*)chatwin, enc_char, outgoing_str, display_message, id, replace_id); } else { win_print_outgoing((ProfWin*)chatwin, enc_char, id, replace_id, display_message); } diff --git a/src/ui/console.c b/src/ui/console.c index 6b6ca79d..38f9eb4e 100644 --- a/src/ui/console.c +++ b/src/ui/console.c @@ -90,6 +90,13 @@ cons_show(const char* const msg, ...) va_end(arg); } +#define cons_show_if_set(fmt, elmnt) \ + do { \ + if (elmnt) { \ + cons_show(fmt, elmnt); \ + } \ + } while (0) + void cons_show_padded(int pad, const char* const msg, ...) { @@ -260,7 +267,9 @@ cons_show_tlscert(const TLSCertificate* cert) cons_show(" Start : %s", cert->notbefore); cons_show(" End : %s", cert->notafter); - cons_show(" Fingerprint : %s", cert->fingerprint); + cons_show_if_set(" Fingerprint SHA1 : %s", cert->fingerprint_sha1); + cons_show_if_set(" Fingerprint SHA256 : %s", cert->fingerprint_sha256); + cons_show_if_set(" Pubkey Fingerprint : %s", cert->pubkey_fingerprint); } void @@ -2503,137 +2512,122 @@ cons_get_string(ProfConsoleWin* conswin) return g_strdup("Console"); } -void -_cons_theme_bar_prop(theme_item_t theme, char* prop) +static void +_cons_theme_bar_prop(ProfWin* console, theme_item_t theme, char* prop) { - ProfWin* console = wins_get_console(); + auto_gchar gchar* propstr = g_strdup_printf("%-24s", prop); + win_print(console, THEME_TEXT, "-", "%s", propstr); - GString* propstr = g_string_new(" "); - g_string_append_printf(propstr, "%-24s", prop); - win_print(console, THEME_TEXT, "-", "%s", propstr->str); - g_string_free(propstr, TRUE); - - GString* valstr = g_string_new(" "); - char* setting = theme_get_string(prop); - g_string_append_printf(valstr, "%s ", setting); - theme_free_string(setting); - win_append(console, theme, "%s", valstr->str); + auto_gchar gchar* setting = theme_get_string(prop); + win_append(console, theme, "%s", setting); win_appendln(console, THEME_TEXT, ""); - g_string_free(valstr, TRUE); } -void -_cons_theme_prop(theme_item_t theme, char* prop) +static void +_cons_theme_prop(ProfWin* console, theme_item_t theme, char* prop) { - ProfWin* console = wins_get_console(); + auto_gchar gchar* propstr = g_strdup_printf("%-24s", prop); + win_print(console, THEME_TEXT, "-", "%s", propstr); - GString* propstr = g_string_new(" "); - g_string_append_printf(propstr, "%-24s", prop); - win_print(console, THEME_TEXT, "-", "%s", propstr->str); - g_string_free(propstr, TRUE); - - GString* valstr = g_string_new(""); - char* setting = theme_get_string(prop); - g_string_append_printf(valstr, "%s", setting); - theme_free_string(setting); - win_appendln(console, theme, "%s", valstr->str); - g_string_free(valstr, TRUE); + auto_gchar gchar* setting = theme_get_string(prop); + win_appendln(console, theme, "%s", setting); } void cons_theme_properties(void) { + ProfWin* console = wins_get_console(); cons_show("Current colours:"); - _cons_theme_bar_prop(THEME_TITLE_TEXT, "titlebar.text"); - _cons_theme_bar_prop(THEME_TITLE_BRACKET, "titlebar.brackets"); + _cons_theme_bar_prop(console, THEME_TITLE_TEXT, "titlebar.text"); + _cons_theme_bar_prop(console, THEME_TITLE_BRACKET, "titlebar.brackets"); - _cons_theme_bar_prop(THEME_TITLE_SCROLLED, "titlebar.scrolled"); + _cons_theme_bar_prop(console, THEME_TITLE_SCROLLED, "titlebar.scrolled"); - _cons_theme_bar_prop(THEME_TITLE_UNENCRYPTED, "titlebar.unencrypted"); - _cons_theme_bar_prop(THEME_TITLE_ENCRYPTED, "titlebar.encrypted"); - _cons_theme_bar_prop(THEME_TITLE_UNTRUSTED, "titlebar.untrusted"); - _cons_theme_bar_prop(THEME_TITLE_TRUSTED, "titlebar.trusted"); + _cons_theme_bar_prop(console, THEME_TITLE_UNENCRYPTED, "titlebar.unencrypted"); + _cons_theme_bar_prop(console, THEME_TITLE_ENCRYPTED, "titlebar.encrypted"); + _cons_theme_bar_prop(console, THEME_TITLE_UNTRUSTED, "titlebar.untrusted"); + _cons_theme_bar_prop(console, THEME_TITLE_TRUSTED, "titlebar.trusted"); - _cons_theme_bar_prop(THEME_TITLE_CHAT, "titlebar.chat"); - _cons_theme_bar_prop(THEME_TITLE_ONLINE, "titlebar.online"); - _cons_theme_bar_prop(THEME_TITLE_AWAY, "titlebar.away"); - _cons_theme_bar_prop(THEME_TITLE_XA, "titlebar.xa"); - _cons_theme_bar_prop(THEME_TITLE_DND, "titlebar.dnd"); - _cons_theme_bar_prop(THEME_TITLE_OFFLINE, "titlebar.offline"); + _cons_theme_bar_prop(console, THEME_TITLE_CHAT, "titlebar.chat"); + _cons_theme_bar_prop(console, THEME_TITLE_ONLINE, "titlebar.online"); + _cons_theme_bar_prop(console, THEME_TITLE_AWAY, "titlebar.away"); + _cons_theme_bar_prop(console, THEME_TITLE_XA, "titlebar.xa"); + _cons_theme_bar_prop(console, THEME_TITLE_DND, "titlebar.dnd"); + _cons_theme_bar_prop(console, THEME_TITLE_OFFLINE, "titlebar.offline"); - _cons_theme_bar_prop(THEME_STATUS_TEXT, "statusbar.text"); - _cons_theme_bar_prop(THEME_STATUS_BRACKET, "statusbar.brackets"); - _cons_theme_bar_prop(THEME_STATUS_ACTIVE, "statusbar.active"); - _cons_theme_bar_prop(THEME_STATUS_CURRENT, "statusbar.current"); - _cons_theme_bar_prop(THEME_STATUS_NEW, "statusbar.new"); - _cons_theme_bar_prop(THEME_STATUS_TIME, "statusbar.time"); + _cons_theme_bar_prop(console, THEME_STATUS_TEXT, "statusbar.text"); + _cons_theme_bar_prop(console, THEME_STATUS_BRACKET, "statusbar.brackets"); + _cons_theme_bar_prop(console, THEME_STATUS_ACTIVE, "statusbar.active"); + _cons_theme_bar_prop(console, THEME_STATUS_CURRENT, "statusbar.current"); + _cons_theme_bar_prop(console, THEME_STATUS_NEW, "statusbar.new"); + _cons_theme_bar_prop(console, THEME_STATUS_TIME, "statusbar.time"); - _cons_theme_prop(THEME_TIME, "main.time"); - _cons_theme_prop(THEME_TEXT, "main.text"); - _cons_theme_prop(THEME_SPLASH, "main.splash"); - _cons_theme_prop(THEME_ERROR, "error"); - _cons_theme_prop(THEME_OTR_STARTED_TRUSTED, "otr.started.trusted"); - _cons_theme_prop(THEME_OTR_STARTED_UNTRUSTED, "otr.started.untrusted"); - _cons_theme_prop(THEME_OTR_ENDED, "otr.ended"); - _cons_theme_prop(THEME_OTR_TRUSTED, "otr.trusted"); - _cons_theme_prop(THEME_OTR_UNTRUSTED, "otr.untrusted"); + _cons_theme_prop(console, THEME_TIME, "main.time"); + _cons_theme_prop(console, THEME_TEXT, "main.text"); + _cons_theme_prop(console, THEME_SPLASH, "main.splash"); + _cons_theme_prop(console, THEME_ERROR, "error"); + _cons_theme_prop(console, THEME_OTR_STARTED_TRUSTED, "otr.started.trusted"); + _cons_theme_prop(console, THEME_OTR_STARTED_UNTRUSTED, "otr.started.untrusted"); + _cons_theme_prop(console, THEME_OTR_ENDED, "otr.ended"); + _cons_theme_prop(console, THEME_OTR_TRUSTED, "otr.trusted"); + _cons_theme_prop(console, THEME_OTR_UNTRUSTED, "otr.untrusted"); - _cons_theme_prop(THEME_ME, "me"); - _cons_theme_prop(THEME_TEXT_ME, "main.text.me"); - _cons_theme_prop(THEME_THEM, "them"); - _cons_theme_prop(THEME_TEXT_THEM, "main.text.them"); - _cons_theme_prop(THEME_TEXT_HISTORY, "main.text.history"); + _cons_theme_prop(console, THEME_ME, "me"); + _cons_theme_prop(console, THEME_TEXT_ME, "main.text.me"); + _cons_theme_prop(console, THEME_THEM, "them"); + _cons_theme_prop(console, THEME_TEXT_THEM, "main.text.them"); + _cons_theme_prop(console, THEME_TEXT_HISTORY, "main.text.history"); - _cons_theme_prop(THEME_CHAT, "chat"); - _cons_theme_prop(THEME_ONLINE, "online"); - _cons_theme_prop(THEME_AWAY, "away"); - _cons_theme_prop(THEME_XA, "xa"); - _cons_theme_prop(THEME_DND, "dnd"); - _cons_theme_prop(THEME_OFFLINE, "offline"); - _cons_theme_prop(THEME_SUBSCRIBED, "subscribed"); - _cons_theme_prop(THEME_UNSUBSCRIBED, "unsubscribed"); + _cons_theme_prop(console, THEME_CHAT, "chat"); + _cons_theme_prop(console, THEME_ONLINE, "online"); + _cons_theme_prop(console, THEME_AWAY, "away"); + _cons_theme_prop(console, THEME_XA, "xa"); + _cons_theme_prop(console, THEME_DND, "dnd"); + _cons_theme_prop(console, THEME_OFFLINE, "offline"); + _cons_theme_prop(console, THEME_SUBSCRIBED, "subscribed"); + _cons_theme_prop(console, THEME_UNSUBSCRIBED, "unsubscribed"); - _cons_theme_prop(THEME_INCOMING, "incoming"); - _cons_theme_prop(THEME_MENTION, "mention"); - _cons_theme_prop(THEME_TRIGGER, "trigger"); - _cons_theme_prop(THEME_TYPING, "typing"); - _cons_theme_prop(THEME_GONE, "gone"); + _cons_theme_prop(console, THEME_INCOMING, "incoming"); + _cons_theme_prop(console, THEME_MENTION, "mention"); + _cons_theme_prop(console, THEME_TRIGGER, "trigger"); + _cons_theme_prop(console, THEME_TYPING, "typing"); + _cons_theme_prop(console, THEME_GONE, "gone"); - _cons_theme_prop(THEME_ROOMINFO, "roominfo"); - _cons_theme_prop(THEME_ROOMMENTION, "roommention"); - _cons_theme_prop(THEME_ROOMMENTION_TERM, "roommention.term"); - _cons_theme_prop(THEME_ROOMTRIGGER, "roomtrigger"); - _cons_theme_prop(THEME_ROOMTRIGGER_TERM, "roomtrigger.term"); + _cons_theme_prop(console, THEME_ROOMINFO, "roominfo"); + _cons_theme_prop(console, THEME_ROOMMENTION, "roommention"); + _cons_theme_prop(console, THEME_ROOMMENTION_TERM, "roommention.term"); + _cons_theme_prop(console, THEME_ROOMTRIGGER, "roomtrigger"); + _cons_theme_prop(console, THEME_ROOMTRIGGER_TERM, "roomtrigger.term"); - _cons_theme_prop(THEME_ROSTER_HEADER, "roster.header"); - _cons_theme_prop(THEME_ROSTER_CHAT, "roster.chat"); - _cons_theme_prop(THEME_ROSTER_ONLINE, "roster.online"); - _cons_theme_prop(THEME_ROSTER_AWAY, "roster.away"); - _cons_theme_prop(THEME_ROSTER_XA, "roster.xa"); - _cons_theme_prop(THEME_ROSTER_DND, "roster.dnd"); - _cons_theme_prop(THEME_ROSTER_OFFLINE, "roster.offline"); - _cons_theme_prop(THEME_ROSTER_CHAT_ACTIVE, "roster.chat.active"); - _cons_theme_prop(THEME_ROSTER_ONLINE_ACTIVE, "roster.online.active"); - _cons_theme_prop(THEME_ROSTER_AWAY_ACTIVE, "roster.away.active"); - _cons_theme_prop(THEME_ROSTER_XA_ACTIVE, "roster.xa.active"); - _cons_theme_prop(THEME_ROSTER_DND_ACTIVE, "roster.dnd.active"); - _cons_theme_prop(THEME_ROSTER_OFFLINE_ACTIVE, "roster.offline.active"); - _cons_theme_prop(THEME_ROSTER_CHAT_UNREAD, "roster.chat.unread"); - _cons_theme_prop(THEME_ROSTER_ONLINE_UNREAD, "roster.online.unread"); - _cons_theme_prop(THEME_ROSTER_AWAY_UNREAD, "roster.away.unread"); - _cons_theme_prop(THEME_ROSTER_XA_UNREAD, "roster.xa.unread"); - _cons_theme_prop(THEME_ROSTER_DND_UNREAD, "roster.dnd.unread"); - _cons_theme_prop(THEME_ROSTER_OFFLINE_UNREAD, "roster.offline.unread"); - _cons_theme_prop(THEME_ROSTER_ROOM, "roster.room"); - _cons_theme_prop(THEME_ROSTER_ROOM_UNREAD, "roster.room.unread"); - _cons_theme_prop(THEME_ROSTER_ROOM_TRIGGER, "roster.room.trigger"); - _cons_theme_prop(THEME_ROSTER_ROOM_MENTION, "roster.room.mention"); + _cons_theme_prop(console, THEME_ROSTER_HEADER, "roster.header"); + _cons_theme_prop(console, THEME_ROSTER_CHAT, "roster.chat"); + _cons_theme_prop(console, THEME_ROSTER_ONLINE, "roster.online"); + _cons_theme_prop(console, THEME_ROSTER_AWAY, "roster.away"); + _cons_theme_prop(console, THEME_ROSTER_XA, "roster.xa"); + _cons_theme_prop(console, THEME_ROSTER_DND, "roster.dnd"); + _cons_theme_prop(console, THEME_ROSTER_OFFLINE, "roster.offline"); + _cons_theme_prop(console, THEME_ROSTER_CHAT_ACTIVE, "roster.chat.active"); + _cons_theme_prop(console, THEME_ROSTER_ONLINE_ACTIVE, "roster.online.active"); + _cons_theme_prop(console, THEME_ROSTER_AWAY_ACTIVE, "roster.away.active"); + _cons_theme_prop(console, THEME_ROSTER_XA_ACTIVE, "roster.xa.active"); + _cons_theme_prop(console, THEME_ROSTER_DND_ACTIVE, "roster.dnd.active"); + _cons_theme_prop(console, THEME_ROSTER_OFFLINE_ACTIVE, "roster.offline.active"); + _cons_theme_prop(console, THEME_ROSTER_CHAT_UNREAD, "roster.chat.unread"); + _cons_theme_prop(console, THEME_ROSTER_ONLINE_UNREAD, "roster.online.unread"); + _cons_theme_prop(console, THEME_ROSTER_AWAY_UNREAD, "roster.away.unread"); + _cons_theme_prop(console, THEME_ROSTER_XA_UNREAD, "roster.xa.unread"); + _cons_theme_prop(console, THEME_ROSTER_DND_UNREAD, "roster.dnd.unread"); + _cons_theme_prop(console, THEME_ROSTER_OFFLINE_UNREAD, "roster.offline.unread"); + _cons_theme_prop(console, THEME_ROSTER_ROOM, "roster.room"); + _cons_theme_prop(console, THEME_ROSTER_ROOM_UNREAD, "roster.room.unread"); + _cons_theme_prop(console, THEME_ROSTER_ROOM_TRIGGER, "roster.room.trigger"); + _cons_theme_prop(console, THEME_ROSTER_ROOM_MENTION, "roster.room.mention"); - _cons_theme_prop(THEME_OCCUPANTS_HEADER, "occupants.header"); + _cons_theme_prop(console, THEME_OCCUPANTS_HEADER, "occupants.header"); - _cons_theme_prop(THEME_RECEIPT_SENT, "receipt.sent"); + _cons_theme_prop(console, THEME_RECEIPT_SENT, "receipt.sent"); - _cons_theme_prop(THEME_INPUT_TEXT, "input.text"); + _cons_theme_prop(console, THEME_INPUT_TEXT, "input.text"); cons_show(""); } @@ -2848,13 +2842,15 @@ cons_strophe_setting(void) } } cons_show("XEP-0198 Stream-Management : %s", sm_setting); - cons_show("libstrophe Verbosity : %s", prefs_get_string(PREF_STROPHE_VERBOSITY)); + auto_gchar gchar* verbosity = prefs_get_string(PREF_STROPHE_VERBOSITY); + cons_show("libstrophe Verbosity : %s", verbosity); } void cons_privacy_setting(void) { - cons_show("Database logging : %s", prefs_get_string(PREF_DBLOG)); + auto_gchar gchar* dblog = prefs_get_string(PREF_DBLOG); + cons_show("Database logging : %s", dblog); if (prefs_get_boolean(PREF_CHLOG)) { cons_show("Chat logging (/logging chat) : ON"); diff --git a/src/ui/core.c b/src/ui/core.c index be77756c..f46addc0 100644 --- a/src/ui/core.c +++ b/src/ui/core.c @@ -361,7 +361,7 @@ void ui_handle_login_account_success(ProfAccount* account, gboolean secured) { if (account->theme) { - if (theme_load(account->theme, false)) { + if (theme_load(account->theme, FALSE)) { ui_load_colours(); if (prefs_get_boolean(PREF_ROSTER)) { ui_show_roster(); diff --git a/src/ui/titlebar.c b/src/ui/titlebar.c index bed87b4a..9c073db9 100644 --- a/src/ui/titlebar.c +++ b/src/ui/titlebar.c @@ -451,6 +451,34 @@ _show_muc_privacy(ProfMucWin* mucwin) wprintw(win, "]"); wattroff(win, bracket_attrs); +#ifdef HAVE_OMEMO + int trusted_attrs = theme_attrs(THEME_TITLE_TRUSTED); + int untrusted_attrs = theme_attrs(THEME_TITLE_UNTRUSTED); + if (mucwin->omemo_trusted) { + wprintw(win, " "); + wattron(win, bracket_attrs); + wprintw(win, "["); + wattroff(win, bracket_attrs); + wattron(win, trusted_attrs); + wprintw(win, "trusted"); + wattroff(win, trusted_attrs); + wattron(win, bracket_attrs); + wprintw(win, "]"); + wattroff(win, bracket_attrs); + } else { + wprintw(win, " "); + wattron(win, bracket_attrs); + wprintw(win, "["); + wattroff(win, bracket_attrs); + wattron(win, untrusted_attrs); + wprintw(win, "untrusted"); + wattroff(win, untrusted_attrs); + wattron(win, bracket_attrs); + wprintw(win, "]"); + wattroff(win, bracket_attrs); + } +#endif + return; } @@ -580,6 +608,32 @@ _show_privacy(ProfChatWin* chatwin) wprintw(win, "]"); wattroff(win, bracket_attrs); +#ifdef HAVE_OMEMO + if (chatwin->omemo_trusted) { + wprintw(win, " "); + wattron(win, bracket_attrs); + wprintw(win, "["); + wattroff(win, bracket_attrs); + wattron(win, trusted_attrs); + wprintw(win, "trusted"); + wattroff(win, trusted_attrs); + wattron(win, bracket_attrs); + wprintw(win, "]"); + wattroff(win, bracket_attrs); + } else { + wprintw(win, " "); + wattron(win, bracket_attrs); + wprintw(win, "["); + wattroff(win, bracket_attrs); + wattron(win, untrusted_attrs); + wprintw(win, "untrusted"); + wattroff(win, untrusted_attrs); + wattron(win, bracket_attrs); + wprintw(win, "]"); + wattroff(win, bracket_attrs); + } +#endif + return; } diff --git a/src/ui/win_types.h b/src/ui/win_types.h index 360732eb..38387f80 100644 --- a/src/ui/win_types.h +++ b/src/ui/win_types.h @@ -178,6 +178,7 @@ typedef struct prof_chat_win_t gboolean pgp_send; gboolean pgp_recv; gboolean is_omemo; + gboolean omemo_trusted; gboolean is_ox; // XEP-0373: OpenPGP for XMPP char* resource_override; gboolean history_shown; @@ -203,6 +204,7 @@ typedef struct prof_muc_win_t gboolean showjid; gboolean showoffline; gboolean is_omemo; + gboolean omemo_trusted; unsigned long memcheck; char* enctext; char* message_char; diff --git a/src/ui/window.c b/src/ui/window.c index 33fcac33..56f1ec00 100644 --- a/src/ui/window.c +++ b/src/ui/window.c @@ -37,6 +37,9 @@ #include "config.h" #include "database.h" #include "ui/window_list.h" +#ifdef HAVE_OMEMO +#include "omemo/omemo.h" +#endif #include #include @@ -63,6 +66,31 @@ #include "xmpp/xmpp.h" #include "xmpp/roster_list.h" +static const int PAD_MIN_HEIGHT = 100; +static const int PAD_THRESHOLD = 3000; +static gboolean _in_redraw = FALSE; + +static void +_win_ensure_pad_capacity(ProfWin* window, WINDOW* win, int lines_needed) +{ + if (!win) { + return; + } + + int cur_height = getmaxy(win); + int cur_width = getmaxx(win); + if (lines_needed >= cur_height - 1) { + // If we are getting too large, trigger a redraw to clean up old lines + // but only if we are not already in a redraw process. + if (window && cur_height >= PAD_THRESHOLD && !_in_redraw) { + win_redraw(window); + } else { + // resize to required lines + some buffer for next messages + int new_height = lines_needed + 100; + wresize(win, new_height, cur_width); + } + } +} static const char* LOADING_MESSAGE = "Loading older messages…"; static const char* CONS_WIN_TITLE = "CProof. Type /help for help information."; static const char* XML_WIN_TITLE = "XML Console"; @@ -111,7 +139,7 @@ _win_create_simple_layout(void) ProfLayoutSimple* layout = malloc(sizeof(ProfLayoutSimple)); layout->base.type = LAYOUT_SIMPLE; - layout->base.win = newpad(PAD_SIZE, cols); + layout->base.win = newpad(PAD_MIN_HEIGHT, cols); wbkgd(layout->base.win, theme_attrs(THEME_TEXT)); layout->base.buffer = buffer_create(); layout->base.y_pos = 0; @@ -129,7 +157,7 @@ _win_create_split_layout(void) ProfLayoutSplit* layout = malloc(sizeof(ProfLayoutSplit)); layout->base.type = LAYOUT_SPLIT; - layout->base.win = newpad(PAD_SIZE, cols); + layout->base.win = newpad(PAD_MIN_HEIGHT, cols); wbkgd(layout->base.win, theme_attrs(THEME_TEXT)); layout->base.buffer = buffer_create(); layout->base.y_pos = 0; @@ -170,6 +198,9 @@ win_create_chat(const char* const barejid) new_win->pgp_recv = FALSE; new_win->pgp_send = FALSE; new_win->is_omemo = FALSE; +#ifdef HAVE_OMEMO + new_win->omemo_trusted = omemo_is_jid_trusted(barejid); +#endif new_win->is_ox = FALSE; new_win->history_shown = FALSE; new_win->unread = 0; @@ -200,12 +231,12 @@ win_create_muc(const char* const roomjid) if (prefs_get_boolean(PREF_OCCUPANTS)) { int subwin_cols = win_occpuants_cols(); - layout->base.win = newpad(PAD_SIZE, cols - subwin_cols); + layout->base.win = newpad(PAD_MIN_HEIGHT, cols - subwin_cols); wbkgd(layout->base.win, theme_attrs(THEME_TEXT)); - layout->subwin = newpad(PAD_SIZE, subwin_cols); + layout->subwin = newpad(PAD_MIN_HEIGHT, subwin_cols); wbkgd(layout->subwin, theme_attrs(THEME_TEXT)); } else { - layout->base.win = newpad(PAD_SIZE, (cols)); + layout->base.win = newpad(PAD_MIN_HEIGHT, (cols)); wbkgd(layout->base.win, theme_attrs(THEME_TEXT)); layout->subwin = NULL; } @@ -236,6 +267,9 @@ win_create_muc(const char* const roomjid) new_win->enctext = NULL; new_win->message_char = NULL; new_win->is_omemo = FALSE; +#ifdef HAVE_OMEMO + new_win->omemo_trusted = omemo_is_jid_trusted(roomjid); +#endif new_win->last_message = NULL; new_win->last_msg_id = NULL; new_win->has_attention = FALSE; @@ -551,11 +585,11 @@ win_hide_subwin(ProfWin* window) layout->subwin = NULL; layout->sub_y_pos = 0; int cols = getmaxx(stdscr); - wresize(layout->base.win, PAD_SIZE, cols); + wresize(layout->base.win, PAD_MIN_HEIGHT, cols); win_redraw(window); } else { int cols = getmaxx(stdscr); - wresize(window->layout->win, PAD_SIZE, cols); + wresize(window->layout->win, PAD_MIN_HEIGHT, cols); win_redraw(window); } } @@ -588,16 +622,16 @@ win_show_subwin(ProfWin* window) subwin_cols = 1; } - layout->subwin = newpad(PAD_SIZE, subwin_cols); + layout->subwin = newpad(PAD_MIN_HEIGHT, subwin_cols); if (layout->subwin == NULL) { // Failed to allocate subwindow; keep base window resized to full width log_error("Failed to create subwindow pad (cols=%d)", subwin_cols); - wresize(layout->base.win, PAD_SIZE, cols); + wresize(layout->base.win, PAD_MIN_HEIGHT, cols); win_redraw(window); return; } wbkgd(layout->subwin, theme_attrs(THEME_TEXT)); - wresize(layout->base.win, PAD_SIZE, cols - subwin_cols); + wresize(layout->base.win, PAD_MIN_HEIGHT, cols - subwin_cols); win_redraw(window); } @@ -880,9 +914,9 @@ win_resize(ProfWin* window) subwin_cols = win_occpuants_cols(); } wbkgd(layout->base.win, theme_attrs(THEME_TEXT)); - wresize(layout->base.win, PAD_SIZE, cols - subwin_cols); + wresize(layout->base.win, PAD_MIN_HEIGHT, cols - subwin_cols); wbkgd(layout->subwin, theme_attrs(THEME_TEXT)); - wresize(layout->subwin, PAD_SIZE, subwin_cols); + wresize(layout->subwin, PAD_MIN_HEIGHT, subwin_cols); if (window->type == WIN_CONSOLE) { rosterwin_roster(); } else if (window->type == WIN_MUC) { @@ -892,11 +926,11 @@ win_resize(ProfWin* window) } } else { wbkgd(layout->base.win, theme_attrs(THEME_TEXT)); - wresize(layout->base.win, PAD_SIZE, cols); + wresize(layout->base.win, PAD_MIN_HEIGHT, cols); } } else { wbkgd(window->layout->win, theme_attrs(THEME_TEXT)); - wresize(window->layout->win, PAD_SIZE, cols); + wresize(window->layout->win, PAD_MIN_HEIGHT, cols); } win_redraw(window); @@ -1889,6 +1923,8 @@ _win_print_internal(ProfWin* window, const char* show_char, int pad_indent, GDat } } + _win_ensure_pad_capacity(window, window->layout->win, getcury(window->layout->win)); + if (prefs_get_boolean(PREF_WRAP)) { _win_print_wrapped(window->layout->win, message + offset, indent, pad_indent); } else { @@ -2038,6 +2074,7 @@ _win_print_wrapped(WINDOW* win, const char* const message, size_t indent, int pa void win_print_trackbar(ProfWin* window) { + _win_ensure_pad_capacity(window, window->layout->win, getcury(window->layout->win)); int cols = getmaxx(window->layout->win); wbkgdset(window->layout->win, theme_attrs(THEME_TRACKBAR)); @@ -2053,12 +2090,20 @@ win_print_trackbar(ProfWin* window) void win_redraw(ProfWin* window) { - int size = buffer_size(window->layout->buffer); + unsigned int size = buffer_size(window->layout->buffer); + _in_redraw = TRUE; + + // shrink pad back to minimum size and erase it + int cols = getmaxx(window->layout->win); + wresize(window->layout->win, PAD_MIN_HEIGHT, cols); werase(window->layout->win); for (int i = 0; i < size; i++) { ProfBuffEntry* e = buffer_get_entry(window->layout->buffer, i); + // check if we need more space before printing + _win_ensure_pad_capacity(window, window->layout->win, getcury(window->layout->win)); + e->y_start_pos = getcury(window->layout->win); if (e->display_from == NULL && e->message && e->message[0] == '-') { // just an indicator to print the trackbar/separator not the actual message @@ -2073,6 +2118,8 @@ win_redraw(ProfWin* window) e->_lines = e->y_end_pos - e->y_start_pos; window->layout->buffer->lines += e->_lines; } + + _in_redraw = FALSE; } void @@ -2207,6 +2254,7 @@ win_toggle_attention(ProfWin* window) void win_sub_print(WINDOW* win, char* msg, gboolean newline, gboolean wrap, int indent) { + _win_ensure_pad_capacity(NULL, win, getcury(win)); int maxx = getmaxx(win); int curx = getcurx(win); int cury = getcury(win); diff --git a/src/ui/window_list.c b/src/ui/window_list.c index 889344b6..3f0f7e54 100644 --- a/src/ui/window_list.c +++ b/src/ui/window_list.c @@ -1017,6 +1017,31 @@ _wins_get_next_available_num(GList* used) } } +void +wins_omemo_trust_changed(const char* const jid) +{ + GList* curr = values; + while (curr) { + ProfWin* window = curr->data; + if (window->type == WIN_CHAT) { + ProfChatWin* chatwin = (ProfChatWin*)window; + if (jid == NULL || strcmp(chatwin->barejid, jid) == 0) { +#ifdef HAVE_OMEMO + chatwin->omemo_trusted = omemo_is_jid_trusted(chatwin->barejid); +#endif + } + } else if (window->type == WIN_MUC) { + ProfMucWin* mucwin = (ProfMucWin*)window; + if (jid == NULL || strcmp(mucwin->roomjid, jid) == 0) { +#ifdef HAVE_OMEMO + mucwin->omemo_trusted = omemo_is_jid_trusted(mucwin->roomjid); +#endif + } + } + curr = g_list_next(curr); + } +} + gboolean wins_tidy(void) { diff --git a/src/ui/window_list.h b/src/ui/window_list.h index d9d11c58..ea5fce04 100644 --- a/src/ui/window_list.h +++ b/src/ui/window_list.h @@ -88,6 +88,7 @@ GSList* wins_get_chat_recipients(void); GSList* wins_get_prune_wins(void); void wins_lost_connection(void); void wins_reestablished_connection(void); +void wins_omemo_trust_changed(const char* const jid); gboolean wins_tidy(void); GSList* wins_create_summary(gboolean unread); GSList* wins_create_summary_attention(); diff --git a/src/xmpp/blocking.c b/src/xmpp/blocking.c index f2a49c7d..ebc47ef7 100644 --- a/src/xmpp/blocking.c +++ b/src/xmpp/blocking.c @@ -144,7 +144,12 @@ blocked_add(char* jid, blocked_report reportkind, const char* const message) if (message) { xmpp_stanza_t* text = xmpp_stanza_new(ctx); - xmpp_stanza_set_name(text, STANZA_NAME_TEXT); + if (reportkind == BLOCKED_REPORT_SPAM) { + xmpp_stanza_set_name(text, STANZA_NAME_BODY); + xmpp_stanza_set_ns(text, "jabber:client"); + } else { + xmpp_stanza_set_name(text, STANZA_NAME_TEXT); + } xmpp_stanza_t* txt = xmpp_stanza_new(ctx); xmpp_stanza_set_text(txt, message); @@ -340,3 +345,65 @@ _blocklist_result_handler(xmpp_stanza_t* const stanza, void* const userdata) return 0; } + +int +reporting_set_handler(xmpp_stanza_t* stanza) +{ + const char* from = xmpp_stanza_get_from(stanza); + xmpp_stanza_t* report = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_REPORTING); + if (!report) { + return 1; + } + + const char* reason = xmpp_stanza_get_attribute(report, STANZA_ATTR_REASON); + const char* display_reason = "unknown"; + if (g_strcmp0(reason, STANZA_REPORTING_SPAM) == 0) { + display_reason = "spam"; + } else if (g_strcmp0(reason, STANZA_REPORTING_ABUSE) == 0) { + display_reason = "abuse"; + } + + char* message = NULL; + xmpp_stanza_t* body = xmpp_stanza_get_child_by_name(report, STANZA_NAME_BODY); + if (body) { + message = xmpp_stanza_get_text(body); + } else { + xmpp_stanza_t* text = xmpp_stanza_get_child_by_name(report, STANZA_NAME_TEXT); + if (text) { + message = xmpp_stanza_get_text(text); + } + } + + // Attempt to find the reported JID if it's wrapped in an item (sync push style) + const char* reported_jid = NULL; + xmpp_stanza_t* block = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_BLOCKING); + if (block) { + xmpp_stanza_t* item = xmpp_stanza_get_child_by_name(block, STANZA_NAME_ITEM); + if (item) { + reported_jid = xmpp_stanza_get_attribute(item, STANZA_ATTR_JID); + } + } + + if (reported_jid) { + if (message) { + cons_show("Incoming %s report from %s: User %s reported for %s. Content: \"%s\"", + display_reason, from, reported_jid, display_reason, message); + } else { + cons_show("Incoming %s report from %s: User %s reported for %s.", + display_reason, from, reported_jid, display_reason); + } + } else { + if (message) { + cons_show("Incoming %s report from %s. Content: \"%s\"", + display_reason, from, message); + } else { + cons_show("Incoming %s report from %s.", display_reason, from); + } + } + + if (message) { + xmpp_free(xmpp_stanza_get_context(stanza), message); + } + + return 1; +} diff --git a/src/xmpp/blocking.h b/src/xmpp/blocking.h index ff09f21b..ad738aa6 100644 --- a/src/xmpp/blocking.h +++ b/src/xmpp/blocking.h @@ -38,5 +38,6 @@ void blocking_request(void); int blocked_set_handler(xmpp_stanza_t* stanza); +int reporting_set_handler(xmpp_stanza_t* stanza); #endif diff --git a/src/xmpp/connection.c b/src/xmpp/connection.c index aa51b5e9..842780f2 100644 --- a/src/xmpp/connection.c +++ b/src/xmpp/connection.c @@ -1080,6 +1080,10 @@ _connection_certfail_cb(const xmpp_tlscert_t* xmpptlscert, const char* errormsg) TLSCertificate* _xmppcert_to_profcert(const xmpp_tlscert_t* xmpptlscert) { + const char* pubkey_fp = NULL; +#ifdef HAVE_XMPP_CERT_PUBKEY_FINGERPRINT_SHA256 + pubkey_fp = xmpp_tlscert_get_string(xmpptlscert, XMPP_CERT_PUBKEY_FINGERPRINT_SHA256); +#endif int version = (int)strtol( xmpp_tlscert_get_string(xmpptlscert, XMPP_CERT_VERSION), NULL, 10); return tlscerts_new( @@ -1092,7 +1096,9 @@ _xmppcert_to_profcert(const xmpp_tlscert_t* xmpptlscert) xmpp_tlscert_get_string(xmpptlscert, XMPP_CERT_NOTAFTER), xmpp_tlscert_get_string(xmpptlscert, XMPP_CERT_KEYALG), xmpp_tlscert_get_string(xmpptlscert, XMPP_CERT_SIGALG), - xmpp_tlscert_get_pem(xmpptlscert)); + xmpp_tlscert_get_pem(xmpptlscert), + xmpp_tlscert_get_string(xmpptlscert, XMPP_CERT_FINGERPRINT_SHA256), + pubkey_fp); } static void diff --git a/src/xmpp/iq.c b/src/xmpp/iq.c index 24b5f1be..dc4eeedd 100644 --- a/src/xmpp/iq.c +++ b/src/xmpp/iq.c @@ -234,6 +234,11 @@ _iq_handler(xmpp_conn_t* const conn, xmpp_stanza_t* const stanza, void* const us blocked_set_handler(stanza); } + xmpp_stanza_t* reporting = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_REPORTING); + if (reporting && (g_strcmp0(type, STANZA_TYPE_SET) == 0)) { + reporting_set_handler(stanza); + } + const char* id = xmpp_stanza_get_id(stanza); if (id) { ProfIqHandler* handler = g_hash_table_lookup(id_handlers, id); @@ -248,6 +253,12 @@ _iq_handler(xmpp_conn_t* const conn, xmpp_stanza_t* const stanza, void* const us return 1; } +static void +_xmpp_stanza_release_destroy_notify(gpointer data) +{ + xmpp_stanza_release((xmpp_stanza_t*)data); +} + void iq_handlers_init(void) { @@ -264,7 +275,7 @@ iq_handlers_init(void) iq_handlers_clear(); id_handlers = g_hash_table_new_full(g_str_hash, g_str_equal, free, (GDestroyNotify)_iq_id_handler_free); - rooms_cache = g_hash_table_new_full(g_str_hash, g_str_equal, free, (GDestroyNotify)xmpp_stanza_release); + rooms_cache = g_hash_table_new_full(g_str_hash, g_str_equal, free, _xmpp_stanza_release_destroy_notify); } struct iq_win_finder @@ -358,6 +369,13 @@ _iq_id_handler_free(ProfIqHandler* handler) void iq_id_handler_add(const char* const id, ProfIqCallback func, ProfIqFreeCallback free_func, void* userdata) { + if (id == NULL) { + if (free_func) { + free_func(userdata); + } + return; + } + ProfIqHandler* handler = malloc(sizeof(ProfIqHandler)); if (handler) { handler->func = func; @@ -2686,8 +2704,10 @@ _iq_mam_request(ProfChatWin* win, GDateTime* startdate, GDateTime* enddate) if (connection_supports(XMPP_FEATURE_MAM2) == FALSE) { log_warning("Server doesn't advertise %s feature.", XMPP_FEATURE_MAM2); cons_show_error("Server doesn't support MAM (%s).", XMPP_FEATURE_MAM2); - g_date_time_unref(startdate); - g_date_time_unref(enddate); + if (startdate) + g_date_time_unref(startdate); + if (enddate) + g_date_time_unref(enddate); return; } @@ -2736,8 +2756,12 @@ void iq_mam_request(ProfChatWin* win, GDateTime* enddate) { ProfMessage* last_msg = log_database_get_limits_info(win->barejid, TRUE); - GDateTime* startdate = g_date_time_ref(last_msg->timestamp); - message_free(last_msg); + GDateTime* startdate = NULL; + if (last_msg) { + if (last_msg->timestamp) + startdate = g_date_time_ref(last_msg->timestamp); + message_free(last_msg); + } // Save request for later if disco items haven't been received yet if (!received_disco_items) { diff --git a/src/xmpp/message.c b/src/xmpp/message.c index 9af7b8c4..95381900 100644 --- a/src/xmpp/message.c +++ b/src/xmpp/message.c @@ -102,7 +102,7 @@ gchar* get_display_name(const ProfMessage* const message, int* flags) { if (equals_our_barejid(message->from_jid->barejid)) { - return g_strdup("me"); + return prefs_get_string(PREF_OUTGOING_STAMP); } else { if (flags) *flags = NO_ME; diff --git a/src/xmpp/omemo.c b/src/xmpp/omemo.c index b9b0faf3..0b4f636e 100644 --- a/src/xmpp/omemo.c +++ b/src/xmpp/omemo.c @@ -116,6 +116,11 @@ omemo_devicelist_request(const char* const jid) void omemo_bundle_publish(gboolean first) { + if (!connection_supports(XMPP_FEATURE_PUBSUB_PUBLISH_OPTIONS)) { + cons_show("OMEMO: Cannot publish bundle: no PUBSUB feature announced"); + log_debug("[OMEMO] Cannot publish bundle: no PUBSUB feature announced"); + return; + } log_debug("[OMEMO] publish own OMEMO bundle"); xmpp_ctx_t* const ctx = connection_get_ctx(); unsigned char* identity_key = NULL; @@ -141,14 +146,10 @@ omemo_bundle_publish(gboolean first) g_list_free(lengths); g_list_free(ids); - if (connection_supports(XMPP_FEATURE_PUBSUB_PUBLISH_OPTIONS)) { - stanza_attach_publish_options_va(ctx, iq, - 4, // 2 * number of key-value pairs - "pubsub#persist_items", "true", - "pubsub#access_model", "open"); - } else { - log_debug("[OMEMO] Cannot publish bundle: no PUBSUB feature announced"); - } + stanza_attach_publish_options_va(ctx, iq, + 4, // 2 * number of key-value pairs + "pubsub#persist_items", "true", + "pubsub#access_model", "open"); iq_id_handler_add(id, _omemo_bundle_publish_result, NULL, GINT_TO_POINTER(first)); @@ -351,6 +352,10 @@ out: char* omemo_receive_message(xmpp_stanza_t* const stanza, gboolean* trusted) { + if (!stanza || !trusted) { + return NULL; + } + char* plaintext = NULL; const char* type = xmpp_stanza_get_type(stanza); const char* from = xmpp_stanza_get_from(stanza); diff --git a/src/xmpp/presence.c b/src/xmpp/presence.c index 5249a1c9..fd3fb5d4 100644 --- a/src/xmpp/presence.c +++ b/src/xmpp/presence.c @@ -533,8 +533,8 @@ _unavailable_handler(xmpp_stanza_t* const stanza) return; } + auto_char char* status_str = stanza_get_status(stanza, NULL); if (strcmp(my_jid->barejid, from_jid->barejid) != 0) { - auto_char char* status_str = stanza_get_status(stanza, NULL); if (from_jid->resourcepart) { sv_ev_contact_offline(from_jid->barejid, from_jid->resourcepart, status_str); @@ -544,6 +544,7 @@ _unavailable_handler(xmpp_stanza_t* const stanza) } } else { if (from_jid->resourcepart) { + sv_ev_contact_offline(from_jid->barejid, from_jid->resourcepart, status_str); connection_remove_available_resource(from_jid->resourcepart); } } @@ -639,8 +640,16 @@ _available_handler(xmpp_stanza_t* const stanza) Resource* resource = stanza_resource_from_presence(xmpp_presence); + char* pgpsig = NULL; + xmpp_stanza_t* x = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_SIGNED); + if (x) { + pgpsig = xmpp_stanza_get_text(x); + } + if (g_strcmp0(xmpp_presence->jid->barejid, my_jid->barejid) == 0) { connection_add_available_resource(resource); + Resource* resource_for_roster = resource_copy(resource); + sv_ev_contact_online(xmpp_presence->jid->barejid, resource_for_roster, xmpp_presence->last_activity, pgpsig); const char* account_name = session_get_account_name(); int max_sessions = accounts_get_max_sessions(account_name); if (max_sessions > 0) { @@ -704,16 +713,11 @@ _available_handler(xmpp_stanza_t* const stanza) } } } else { - char* pgpsig = NULL; - xmpp_stanza_t* x = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_SIGNED); - if (x) { - pgpsig = xmpp_stanza_get_text(x); - } sv_ev_contact_online(xmpp_presence->jid->barejid, resource, xmpp_presence->last_activity, pgpsig); - xmpp_ctx_t* ctx = connection_get_ctx(); - xmpp_free(ctx, pgpsig); } + xmpp_ctx_t* ctx = connection_get_ctx(); + xmpp_free(ctx, pgpsig); stanza_free_presence(xmpp_presence); } @@ -813,7 +817,12 @@ _muc_user_self_handler(xmpp_stanza_t* stanza) affiliation = xmpp_stanza_get_attribute(item, "affiliation"); } } - sv_ev_muc_self_online(room, nick, config_required, role, affiliation, actor, reason, jid, show_str, status_str); + + if (muc_active(room)) { + sv_ev_muc_self_online(room, nick, config_required, role, affiliation, actor, reason, jid, show_str, status_str); + } else { + log_debug("presence: self-presence received for untracked room: %s", room); + } } } diff --git a/src/xmpp/resource.c b/src/xmpp/resource.c index 007a97c0..4d8ef03b 100644 --- a/src/xmpp/resource.c +++ b/src/xmpp/resource.c @@ -99,6 +99,16 @@ resource_destroy(Resource* resource) } } +Resource* +resource_copy(Resource* resource) +{ + if (resource == NULL) { + return NULL; + } + + return resource_new(resource->name, resource->presence, resource->status, resource->priority); +} + gboolean valid_resource_presence_string(const char* const str) { diff --git a/src/xmpp/resource.h b/src/xmpp/resource.h index 41d20511..9a37890f 100644 --- a/src/xmpp/resource.h +++ b/src/xmpp/resource.h @@ -48,6 +48,7 @@ typedef struct resource_t Resource* resource_new(const char* const name, resource_presence_t presence, const char* const status, const int priority); +Resource* resource_copy(Resource* resource); void resource_destroy(Resource* resource); int resource_compare_availability(Resource* first, Resource* second); diff --git a/src/xmpp/roster_list.c b/src/xmpp/roster_list.c index 771c7af1..880848ce 100644 --- a/src/xmpp/roster_list.c +++ b/src/xmpp/roster_list.c @@ -47,6 +47,7 @@ #include "xmpp/resource.h" #include "xmpp/contact.h" #include "xmpp/jid.h" +#include "xmpp/xmpp.h" typedef struct prof_roster_t { @@ -112,6 +113,7 @@ _pendingPresence_free(ProfPendingPresence* presence) if (presence->last_activity) g_date_time_unref(presence->last_activity); free(presence->barejid); + resource_destroy(presence->resource); free(presence); } @@ -391,6 +393,19 @@ roster_add(const char* const barejid, const char* const name, GSList* groups, co } g_hash_table_insert(roster->contacts, strdup(barejid), contact); + + if (equals_our_barejid(barejid)) { + GList* resources = connection_get_available_resources(); + GList* curr = resources; + while (curr) { + Resource* res = curr->data; + Resource* res_copy = resource_copy(res); + p_contact_set_presence(contact, res_copy); + curr = g_list_next(curr); + } + g_list_free(resources); + } + autocomplete_add(roster->barejid_ac, barejid); _add_name_and_barejid(name, barejid); @@ -711,6 +726,7 @@ roster_process_pending_presence(void) for (iter = roster_pending_presence; iter != NULL; iter = iter->next) { ProfPendingPresence* presence = iter->data; roster_update_presence(presence->barejid, presence->resource, presence->last_activity); + presence->resource = NULL; } g_slist_free_full(roster_pending_presence, (GDestroyNotify)_pendingPresence_free); diff --git a/src/xmpp/session.c b/src/xmpp/session.c index 4d0a64b9..79ae6acb 100644 --- a/src/xmpp/session.c +++ b/src/xmpp/session.c @@ -73,7 +73,7 @@ static struct char* passwd; } saved_account; -static struct +static struct session_details { char* name; char* jid; @@ -159,6 +159,18 @@ session_connect_with_account(const ProfAccount* const account) return result; } +static jabber_conn_status_t +_session_connect(struct session_details* details) +{ + return connection_connect( + details->jid, + details->passwd, + details->altdomain, + details->port, + details->tls_policy, + details->auth_policy); +} + jabber_conn_status_t session_connect_with_details(const char* const jid, const char* const passwd, const char* const altdomain, const int port, const char* const tls_policy, const char* const auth_policy) @@ -206,13 +218,7 @@ session_connect_with_details(const char* const jid, const char* const passwd, co // connect with fulljid log_info("Connecting without account, JID: %s", saved_details.jid); - return connection_connect( - saved_details.jid, - passwd, - saved_details.altdomain, - saved_details.port, - saved_details.tls_policy, - saved_details.auth_policy); + return _session_connect(&saved_details); } void @@ -560,31 +566,35 @@ void session_reconnect_now(void) { // reconnect with account. - ProfAccount* account = accounts_get_account(saved_account.name); - if (account == NULL) { - log_error("Unable to reconnect, account no longer exists: %s", saved_account.name); - return; - } + if (saved_account.name) { + ProfAccount* account = accounts_get_account(saved_account.name); + if (account == NULL) { + log_error("Unable to reconnect, account no longer exists: %s", saved_account.name); + return; + } - auto_char char* jid = NULL; - if (account->resource) { - jid = create_fulljid(account->jid, account->resource); - } 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; - } + auto_char char* jid = NULL; + if (account->resource) { + jid = create_fulljid(account->jid, account->resource); + } 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, server, port, account->tls_policy, account->auth_policy); - account_free(account); + log_debug("Attempting reconnect with account %s", account->name); + connection_connect(jid, saved_account.passwd, server, port, account->tls_policy, account->auth_policy); + account_free(account); + } else { + _session_connect(&saved_details); + } if (reconnect_timer) g_timer_start(reconnect_timer); } diff --git a/tests/unittests/omemo/stub_omemo.c b/tests/unittests/omemo/stub_omemo.c index 095fd81c..95f88be4 100644 --- a/tests/unittests/omemo/stub_omemo.c +++ b/tests/unittests/omemo/stub_omemo.c @@ -42,6 +42,12 @@ omemo_is_trusted_identity(const char* const jid, const char* const fingerprint) return TRUE; } +gboolean +omemo_is_jid_trusted(const char* const jid) +{ + return TRUE; +} + GList* omemo_known_device_identities(const char* const jid) { diff --git a/tests/unittests/test_common.c b/tests/unittests/test_common.c index 11a9fe74..eb61b21a 100644 --- a/tests/unittests/test_common.c +++ b/tests/unittests/test_common.c @@ -846,3 +846,35 @@ prof_whole_occurrences_tests(void** state) g_slist_free(expected); expected = NULL; } + +void +str_xml_sanitize__strips_illegal_characters(void** state) +{ + // Test NULL input + assert_null(str_xml_sanitize(NULL)); + + // Test empty string + gchar* res1 = str_xml_sanitize(""); + assert_string_equal("", res1); + g_free(res1); + + // Test string with no illegal characters + gchar* res2 = str_xml_sanitize("Hello World! \t\n\r"); + assert_string_equal("Hello World! \t\n\r", res2); + g_free(res2); + + // Test string with illegal characters (0x16 is ^V, 0x01 is ^A) + gchar* res3 = str_xml_sanitize("Hello\x16World\x01!"); + assert_string_equal("HelloWorld!", res3); + g_free(res3); + + // Test string with mixed legal and illegal control characters + gchar* res4 = str_xml_sanitize("\x09Legal\x0BIllegal\x0ALegal\x1FIllegal\x0DLegal"); + assert_string_equal("\tLegalIllegal\nLegalIllegal\rLegal", res4); + g_free(res4); + + // Test UTF-8 characters + gchar* res5 = str_xml_sanitize("UTF-8: üñîçøðé \x16 and more"); + assert_string_equal("UTF-8: üñîçøðé and more", res5); + g_free(res5); +} diff --git a/tests/unittests/test_common.h b/tests/unittests/test_common.h index ffcc6c9d..d5007d89 100644 --- a/tests/unittests/test_common.h +++ b/tests/unittests/test_common.h @@ -34,3 +34,4 @@ void prof_whole_occurrences_tests(void** state); void prof_occurrences_of_large_message_tests(void** state); void unique_filename_from_url_td(void** state); void format_call_external_argv_td(void** state); +void str_xml_sanitize__strips_illegal_characters(void** state); diff --git a/tests/unittests/unittests.c b/tests/unittests/unittests.c index 69fbd4bf..bd5eee83 100644 --- a/tests/unittests/unittests.c +++ b/tests/unittests/unittests.c @@ -628,6 +628,7 @@ main(int argc, char* argv[]) cmocka_unit_test(prof_partial_occurrences_tests), cmocka_unit_test(prof_whole_occurrences_tests), cmocka_unit_test(prof_occurrences_of_large_message_tests), + cmocka_unit_test(str_xml_sanitize__strips_illegal_characters), cmocka_unit_test_setup_teardown(returns_no_commands, load_preferences, diff --git a/tests/unittests/xmpp/stub_xmpp.c b/tests/unittests/xmpp/stub_xmpp.c index e2af28b8..20c053be 100644 --- a/tests/unittests/xmpp/stub_xmpp.c +++ b/tests/unittests/xmpp/stub_xmpp.c @@ -607,3 +607,9 @@ void blocked_ac_reset(void) { } + +GList* +connection_get_available_resources(void) +{ + return NULL; +}