From a59623a007e4a8995df7934456e91681c3f9f10e Mon Sep 17 00:00:00 2001 From: John Hernandez <129467592+H3rnand3zzz@users.noreply.github.com> Date: Fri, 30 Jun 2023 14:28:28 +0200 Subject: [PATCH 1/4] Add `/pgp sendpub` command Command allows to share your PGP pub key with ease, it's not described in XEP-0027, but used in some clients, such as PSI, Pidgin. Fix typos Minor improvements --- src/command/cmd_ac.c | 1 + src/command/cmd_defs.c | 6 ++- src/command/cmd_funcs.c | 48 ++++++++++++++---- src/pgp/gpg.c | 91 +++++++++++++++++++++++++++------- src/pgp/gpg.h | 1 + tests/unittests/pgp/stub_gpg.c | 6 +++ tests/unittests/test_cmd_pgp.c | 4 +- 7 files changed, 126 insertions(+), 31 deletions(-) diff --git a/src/command/cmd_ac.c b/src/command/cmd_ac.c index 48d712e4..e2affcf6 100644 --- a/src/command/cmd_ac.c +++ b/src/command/cmd_ac.c @@ -910,6 +910,7 @@ cmd_ac_init(void) autocomplete_add(pgp_ac, "log"); autocomplete_add(pgp_ac, "char"); autocomplete_add(pgp_ac, "sendfile"); + autocomplete_add(pgp_ac, "sendpub"); pgp_log_ac = autocomplete_new(); autocomplete_add(pgp_log_ac, "on"); diff --git a/src/command/cmd_defs.c b/src/command/cmd_defs.c index 9828ab1a..d3f21260 100644 --- a/src/command/cmd_defs.c +++ b/src/command/cmd_defs.c @@ -1705,7 +1705,8 @@ static const struct cmd_t command_defs[] = { "/pgp end", "/pgp log on|off|redact", "/pgp char ", - "/pgp sendfile on|off") + "/pgp sendfile on|off", + "/pgp sendpub") CMD_DESC( "Open PGP commands to manage keys, and perform PGP encryption during chat sessions. " "See the /account command to set your own PGP key.") @@ -1719,7 +1720,8 @@ static const struct cmd_t command_defs[] = { { "log on|off", "Enable or disable plaintext logging of PGP encrypted messages." }, { "log redact", "Log PGP encrypted messages, but replace the contents with [redacted]. This is the default." }, { "char ", "Set the character to be displayed next to PGP encrypted messages." }, - { "sendfile on|off", "Allow /sendfile to send unencrypted files while otherwise using PGP." }) + { "sendfile on|off", "Allow /sendfile to send unencrypted files while otherwise using PGP." }, + { "sendpub", "Used in chat. Sends a message to the current recipient with your PGP public key." }) CMD_EXAMPLES( "/pgp log off", "/pgp setkey odin@valhalla.edda BA19CACE5A9592C5", diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c index bb6cc49d..ad2a06f2 100644 --- a/src/command/cmd_funcs.c +++ b/src/command/cmd_funcs.c @@ -7489,12 +7489,12 @@ cmd_pgp(ProfWin* window, const char* const command, gchar** args) if (g_strcmp0(args[0], "start") == 0) { jabber_conn_status_t conn_status = connection_get_status(); if (conn_status != JABBER_CONNECTED) { - cons_show("You must be connected to start PGP encrpytion."); + cons_show("You must be connected to start PGP encryption."); return TRUE; } if (window->type != WIN_CHAT && args[1] == NULL) { - cons_show("You must be in a regular chat window to start PGP encrpytion."); + cons_show("You must be in a regular chat window to start PGP encryption."); return TRUE; } @@ -7533,14 +7533,17 @@ cmd_pgp(ProfWin* window, const char* const command, gchar** args) } ProfAccount* account = accounts_get_account(session_get_account_name()); - char* err_str = NULL; - if (!p_gpg_valid_key(account->pgp_keyid, &err_str)) { - win_println(window, THEME_DEFAULT, "!", "Invalid PGP key ID %s: %s, cannot start PGP encryption.", account->pgp_keyid, err_str); - free(err_str); + if (account->pgp_keyid == NULL) { + win_println(window, THEME_DEFAULT, "!", "Couldn't start PGP session. Please, set your PGP key using /account set %s pgpkeyid . To list pgp keys, use /pgp keys.", account->name); + account_free(account); + return TRUE; + } + auto_char char* err_str = NULL; + if (!p_gpg_valid_key(account->pgp_keyid, &err_str)) { + win_println(window, THEME_DEFAULT, "!", "Invalid PGP key ID %s: %s, cannot start PGP encryption.", account->pgp_keyid, err_str); account_free(account); return TRUE; } - free(err_str); account_free(account); if (!p_gpg_available(chatwin->barejid)) { @@ -7562,7 +7565,7 @@ cmd_pgp(ProfWin* window, const char* const command, gchar** args) } if (window->type != WIN_CHAT) { - cons_show("You must be in a regular chat window to end PGP encrpytion."); + cons_show("You must be in a regular chat window to end PGP encryption."); return TRUE; } @@ -7583,6 +7586,31 @@ cmd_pgp(ProfWin* window, const char* const command, gchar** args) return TRUE; } + if (g_strcmp0(args[0], "sendpub") == 0) { + if (window->type != WIN_CHAT) { + cons_show_error("Please, use this command only in chat windows."); + return TRUE; + } + ProfChatWin* chatwin = (ProfChatWin*)window; + ProfAccount* account = accounts_get_account(session_get_account_name()); + + if (account->pgp_keyid == NULL) { + cons_show_error("Please, set the PGP key first using /account set %s pgpkeyid . To list pgp keys, use /pgp keys.", account->name); + account_free(account); + return TRUE; + } + auto_char char* pubkey = p_gpg_get_pubkey(account->pgp_keyid); + if (pubkey == NULL) { + cons_show_error("Couldn't get your PGP public key. Please, check error logs."); + account_free(account); + return TRUE; + } + cl_ev_send_msg(chatwin, pubkey, NULL); + cons_show("PGP key has been shared with %s.", chatwin->barejid); + account_free(account); + return TRUE; + } + cons_bad_cmd_usage(command); return TRUE; #else @@ -7691,12 +7719,12 @@ cmd_ox(ProfWin* window, const char* const command, gchar** args) } else if (g_strcmp0(args[0], "start") == 0) { jabber_conn_status_t conn_status = connection_get_status(); if (conn_status != JABBER_CONNECTED) { - cons_show("You must be connected to start OX encrpytion."); + cons_show("You must be connected to start OX encryption."); return TRUE; } if (window->type != WIN_CHAT && args[1] == NULL) { - cons_show("You must be in a regular chat window to start OX encrpytion."); + cons_show("You must be in a regular chat window to start OX encryption."); return TRUE; } diff --git a/src/pgp/gpg.c b/src/pgp/gpg.c index 3ef69c1d..bd25a58f 100644 --- a/src/pgp/gpg.c +++ b/src/pgp/gpg.c @@ -71,6 +71,7 @@ static Autocomplete key_ac; static char* _remove_header_footer(char* str, const char* const footer); static char* _add_header_footer(const char* const str, const char* const header, const char* const footer); +static char* _gpgme_data_to_char(gpgme_data_t data); static void _save_pubkeys(void); void @@ -422,7 +423,9 @@ p_gpg_valid_key(const char* const keyid, char** err_str) gpgme_error_t error = gpgme_new(&ctx); if (error) { log_error("GPG: Failed to create gpgme context. %s %s", gpgme_strsource(error), gpgme_strerror(error)); - *err_str = strdup(gpgme_strerror(error)); + if (err_str) { + *err_str = strdup(gpgme_strerror(error)); + } return FALSE; } @@ -431,13 +434,9 @@ p_gpg_valid_key(const char* const keyid, char** err_str) if (error || key == NULL) { log_error("GPG: Failed to get key. %s %s", gpgme_strsource(error), gpgme_strerror(error)); - *err_str = strdup(gpgme_strerror(error)); - gpgme_release(ctx); - return FALSE; - } - - if (key == NULL) { - *err_str = strdup("Unknown error"); + if (err_str) { + *err_str = strdup(error ? gpgme_strerror(error) : "gpgme didn't return any error, but it didn't return a key"); + } gpgme_release(ctx); return FALSE; } @@ -717,19 +716,11 @@ p_gpg_decrypt(const char* const cipher) } gpgme_release(ctx); - size_t len = 0; - char* plain_str = gpgme_data_release_and_get_mem(plain_data, &len); - char* result = NULL; - if (plain_str) { - result = strndup(plain_str, len); - gpgme_free(plain_str); - } - if (passphrase_attempt) { passphrase = strdup(passphrase_attempt); } - return result; + return _gpgme_data_to_char(plain_data); } void @@ -769,6 +760,72 @@ p_gpg_format_fp_str(char* fp) return g_string_free(format, FALSE); } +/** + * \brief Function to extract specific public key from PGP + * \param keyid Key ID that will be used to search key in the current PGP context, if NULL returns null + * \returns null-terminated char* string with the the public key in armored format, that must be free'd to avoid memory leaks + * or NULL on error + */ +char* +p_gpg_get_pubkey(const char* keyid) +{ + if (!keyid || *keyid == '\0') { + return NULL; + } + + gpgme_ctx_t context = NULL; + gpgme_error_t error = GPG_ERR_NO_ERROR; + gpgme_data_t data = NULL; + + error = gpgme_new(&context); + if (error != GPG_ERR_NO_ERROR) { + log_error("GPG: Failed to create gpgme context. %s", gpgme_strerror(error)); + goto cleanup; + } + + error = gpgme_data_new(&data); + if (error != GPG_ERR_NO_ERROR || data == NULL) { + log_error("GPG: Failed to create new gpgme data. %s", gpgme_strerror(error)); + goto cleanup; + } + + gpgme_set_armor(context, 1); + + error = gpgme_op_export(context, keyid, GPGME_EXPORT_MODE_MINIMAL, data); + if (error != GPG_ERR_NO_ERROR) { + log_error("GPG: Failed to export public key. %s", gpgme_strerror(error)); + goto cleanup; + } + +cleanup: + gpgme_release(context); + return _gpgme_data_to_char(data); +} + +/** + * Convert a gpgme_data_t object to a null-terminated char* string. + * The returned string is allocated using malloc and should be freed by the caller. + * If an error occurs or the data is empty, NULL is returned and errors written to the error log. + */ +static char* +_gpgme_data_to_char(gpgme_data_t data) +{ + size_t buffer_size = 0; + char* gpgme_buffer = gpgme_data_release_and_get_mem(data, &buffer_size); + + if (!gpgme_buffer) { + log_error("GPG: Unable to extract gpgmedata."); + return NULL; + } + + char* buffer = malloc(buffer_size + 1); + memcpy(buffer, gpgme_buffer, buffer_size); + buffer[buffer_size] = '\0'; + gpgme_free(gpgme_buffer); + + return buffer; +} + static char* _remove_header_footer(char* str, const char* const footer) { diff --git a/src/pgp/gpg.h b/src/pgp/gpg.h index a5cc9cee..23d32fa7 100644 --- a/src/pgp/gpg.h +++ b/src/pgp/gpg.h @@ -73,6 +73,7 @@ void p_gpg_free_decrypted(char* decrypted); char* p_gpg_autocomplete_key(const char* const search_str, gboolean previous, void* context); void p_gpg_autocomplete_key_reset(void); char* p_gpg_format_fp_str(char* fp); +char* p_gpg_get_pubkey(const char* const keyid); ProfPGPKey* p_gpg_key_new(void); void p_gpg_free_key(ProfPGPKey* key); diff --git a/tests/unittests/pgp/stub_gpg.c b/tests/unittests/pgp/stub_gpg.c index 35616e61..e1efae72 100644 --- a/tests/unittests/pgp/stub_gpg.c +++ b/tests/unittests/pgp/stub_gpg.c @@ -98,3 +98,9 @@ p_gpg_format_fp_str(char* fp) { return NULL; } + +char* +p_gpg_get_pubkey(const char* const keyid) +{ + return NULL; +} \ No newline at end of file diff --git a/tests/unittests/test_cmd_pgp.c b/tests/unittests/test_cmd_pgp.c index 55130dac..f9d66e2b 100644 --- a/tests/unittests/test_cmd_pgp.c +++ b/tests/unittests/test_cmd_pgp.c @@ -36,7 +36,7 @@ cmd_pgp_start_shows_message_when_connection(jabber_conn_status_t conn_status) will_return(connection_get_status, conn_status); - expect_cons_show("You must be connected to start PGP encrpytion."); + expect_cons_show("You must be connected to start PGP encryption."); gboolean result = cmd_pgp(&window, CMD_PGP, args); assert_true(result); @@ -69,7 +69,7 @@ cmd_pgp_start_shows_message_when_no_arg_in_wintype(win_type_t wintype) will_return(connection_get_status, JABBER_CONNECTED); - expect_cons_show("You must be in a regular chat window to start PGP encrpytion."); + expect_cons_show("You must be in a regular chat window to start PGP encryption."); gboolean result = cmd_pgp(&window, CMD_PGP, args); assert_true(result); From 349e4cb32227256fc0d8d3c04a83456a243a9295 Mon Sep 17 00:00:00 2001 From: John Hernandez <129467592+H3rnand3zzz@users.noreply.github.com> Date: Thu, 15 Jun 2023 10:36:20 +0200 Subject: [PATCH 2/4] Refactor `_cmd_set_boolean_preference` function Improve documentation Update code formatting and indentation for better readability Show state if no argument provided Show if argument wasn't changed Reduce amount of arguments --- src/command/cmd_funcs.c | 131 ++++++++++++++++++++++------------------ 1 file changed, 71 insertions(+), 60 deletions(-) diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c index ad2a06f2..a4662aa8 100644 --- a/src/command/cmd_funcs.c +++ b/src/command/cmd_funcs.c @@ -121,8 +121,8 @@ static void _update_presence(const resource_presence_t presence, const char* const show, gchar** args); -static gboolean _cmd_set_boolean_preference(gchar* arg, const char* const command, - const char* const display, preference_t pref); +static gboolean _cmd_set_boolean_preference(gchar* arg, const char* const display, + preference_t pref); static void _who_room(ProfWin* window, const char* const command, gchar** args); static void _who_roster(ProfWin* window, const char* const command, gchar** args); static gboolean _cmd_execute(ProfWin* window, const char* const command, const char* const inp); @@ -2427,7 +2427,7 @@ cmd_roster(ProfWin* window, const char* const command, gchar** args) cons_bad_cmd_usage(command); return TRUE; } else { - _cmd_set_boolean_preference(args[1], command, "Roster panel line wrap", PREF_ROSTER_WRAP); + _cmd_set_boolean_preference(args[1], "Roster panel line wrap", PREF_ROSTER_WRAP); rosterwin_roster(); return TRUE; } @@ -2517,7 +2517,7 @@ cmd_roster(ProfWin* window, const char* const command, gchar** args) } } } else if (g_strcmp0(args[1], "join") == 0) { - _cmd_set_boolean_preference(args[2], command, "Roster join", PREF_ROSTER_RESOURCE_JOIN); + _cmd_set_boolean_preference(args[2], "Roster join", PREF_ROSTER_RESOURCE_JOIN); rosterwin_roster(); return TRUE; } else { @@ -2662,7 +2662,7 @@ cmd_roster(ProfWin* window, const char* const command, gchar** args) } else if (g_strcmp0(args[0], "count") == 0) { if (g_strcmp0(args[1], "zero") == 0) { - _cmd_set_boolean_preference(args[2], command, "Roster header zero count", PREF_ROSTER_COUNT_ZERO); + _cmd_set_boolean_preference(args[2], "Roster header zero count", PREF_ROSTER_COUNT_ZERO); if (conn_status == JABBER_CONNECTED) { rosterwin_roster(); } @@ -2694,7 +2694,7 @@ cmd_roster(ProfWin* window, const char* const command, gchar** args) } } else if (g_strcmp0(args[0], "color") == 0) { - _cmd_set_boolean_preference(args[1], command, "Roster consistent colors", PREF_ROSTER_COLOR_NICK); + _cmd_set_boolean_preference(args[1], "Roster consistent colors", PREF_ROSTER_COLOR_NICK); ui_show_roster(); return TRUE; @@ -3169,7 +3169,7 @@ cmd_resource(ProfWin* window, const char* const command, gchar** args) cons_bad_cmd_usage(command); return TRUE; } else { - _cmd_set_boolean_preference(setting, command, "Message resource", PREF_RESOURCE_MESSAGE); + _cmd_set_boolean_preference(setting, "Message resource", PREF_RESOURCE_MESSAGE); return TRUE; } } else if (g_strcmp0(cmd, "title") == 0) { @@ -3178,7 +3178,7 @@ cmd_resource(ProfWin* window, const char* const command, gchar** args) cons_bad_cmd_usage(command); return TRUE; } else { - _cmd_set_boolean_preference(setting, command, "Title resource", PREF_RESOURCE_TITLE); + _cmd_set_boolean_preference(setting, "Title resource", PREF_RESOURCE_TITLE); return TRUE; } } @@ -4467,7 +4467,7 @@ cmd_occupants(ProfWin* window, const char* const command, gchar** args) cons_bad_cmd_usage(command); return TRUE; } else { - _cmd_set_boolean_preference(args[1], command, "Occupants panel line wrap", PREF_OCCUPANTS_WRAP); + _cmd_set_boolean_preference(args[1], "Occupants panel line wrap", PREF_OCCUPANTS_WRAP); occupantswin_occupants_all(); return TRUE; } @@ -4491,7 +4491,7 @@ cmd_occupants(ProfWin* window, const char* const command, gchar** args) } if (g_strcmp0(args[0], "color") == 0) { - _cmd_set_boolean_preference(args[1], command, "Occupants consistent colors", PREF_OCCUPANTS_COLOR_NICK); + _cmd_set_boolean_preference(args[1], "Occupants consistent colors", PREF_OCCUPANTS_COLOR_NICK); occupantswin_occupants_all(); return TRUE; } @@ -5104,7 +5104,7 @@ cmd_lastactivity(ProfWin* window, const char* const command, gchar** args) { if ((g_strcmp0(args[0], "set") == 0)) { if ((g_strcmp0(args[1], "on") == 0) || (g_strcmp0(args[1], "off") == 0)) { - _cmd_set_boolean_preference(args[1], command, "Last activity", PREF_LASTACTIVITY); + _cmd_set_boolean_preference(args[1], "Last activity", PREF_LASTACTIVITY); if (g_strcmp0(args[1], "on") == 0) { caps_add_feature(XMPP_FEATURE_LASTACTIVITY); } @@ -5251,7 +5251,7 @@ cmd_clear(ProfWin* window, const char* const command, gchar** args) if (args[1] != NULL) { if ((g_strcmp0(args[1], "on") == 0) || (g_strcmp0(args[1], "off") == 0)) { - _cmd_set_boolean_preference(args[1], command, "Persistent history", PREF_CLEAR_PERSIST_HISTORY); + _cmd_set_boolean_preference(args[1], "Persistent history", PREF_CLEAR_PERSIST_HISTORY); return TRUE; } } else { @@ -5272,7 +5272,7 @@ cmd_clear(ProfWin* window, const char* const command, gchar** args) gboolean cmd_privileges(ProfWin* window, const char* const command, gchar** args) { - _cmd_set_boolean_preference(args[0], command, "MUC privileges", PREF_MUC_PRIVILEGES); + _cmd_set_boolean_preference(args[0], "MUC privileges", PREF_MUC_PRIVILEGES); ui_redraw_all_room_rosters(); @@ -5302,7 +5302,7 @@ cmd_charset(ProfWin* window, const char* const command, gchar** args) gboolean cmd_beep(ProfWin* window, const char* const command, gchar** args) { - _cmd_set_boolean_preference(args[0], command, "Sound", PREF_BEEP); + _cmd_set_boolean_preference(args[0], "Sound", PREF_BEEP); return TRUE; } @@ -5354,7 +5354,7 @@ cmd_presence(ProfWin* window, const char* const command, gchar** args) } if (strcmp(args[0], "titlebar") == 0) { - _cmd_set_boolean_preference(args[1], command, "Contact presence", PREF_PRESENCE); + _cmd_set_boolean_preference(args[1], "Contact presence", PREF_PRESENCE); return TRUE; } @@ -5402,7 +5402,7 @@ cmd_presence(ProfWin* window, const char* const command, gchar** args) gboolean cmd_wrap(ProfWin* window, const char* const command, gchar** args) { - _cmd_set_boolean_preference(args[0], command, "Word wrap", PREF_WRAP); + _cmd_set_boolean_preference(args[0], "Word wrap", PREF_WRAP); wins_resize_all(); @@ -5641,7 +5641,7 @@ cmd_states(ProfWin* window, const char* const command, gchar** args) return FALSE; } - _cmd_set_boolean_preference(args[0], command, "Sending chat states", PREF_STATES); + _cmd_set_boolean_preference(args[0], "Sending chat states", PREF_STATES); // if disabled, disable outtype and gone if (strcmp(args[0], "off") == 0) { @@ -5663,9 +5663,9 @@ cmd_wintitle(ProfWin* window, const char* const command, gchar** args) ui_clear_win_title(); } if (g_strcmp0(args[0], "show") == 0) { - _cmd_set_boolean_preference(args[1], command, "Window title show", PREF_WINTITLE_SHOW); + _cmd_set_boolean_preference(args[1], "Window title show", PREF_WINTITLE_SHOW); } else { - _cmd_set_boolean_preference(args[1], command, "Window title goodbye", PREF_WINTITLE_GOODBYE); + _cmd_set_boolean_preference(args[1], "Window title goodbye", PREF_WINTITLE_GOODBYE); } return TRUE; @@ -5678,7 +5678,7 @@ cmd_outtype(ProfWin* window, const char* const command, gchar** args) return FALSE; } - _cmd_set_boolean_preference(args[0], command, "Sending typing notifications", PREF_OUTTYPE); + _cmd_set_boolean_preference(args[0], "Sending typing notifications", PREF_OUTTYPE); // if enabled, enable states if (strcmp(args[0], "on") == 0) { @@ -6121,7 +6121,7 @@ cmd_inpblock(ProfWin* window, const char* const command, gchar** args) return TRUE; } - _cmd_set_boolean_preference(value, command, "Dynamic input blocking", PREF_INPBLOCK_DYNAMIC); + _cmd_set_boolean_preference(value, "Dynamic input blocking", PREF_INPBLOCK_DYNAMIC); return TRUE; } @@ -6541,12 +6541,12 @@ cmd_log(ProfWin* window, const char* const command, gchar** args) } if (strcmp(subcmd, "rotate") == 0) { - _cmd_set_boolean_preference(value, command, "Log rotate", PREF_LOG_ROTATE); + _cmd_set_boolean_preference(value, "Log rotate", PREF_LOG_ROTATE); return TRUE; } if (strcmp(subcmd, "shared") == 0) { - _cmd_set_boolean_preference(value, command, "Shared log", PREF_LOG_SHARED); + _cmd_set_boolean_preference(value, "Shared log", PREF_LOG_SHARED); cons_show("Setting only takes effect after saving and restarting Profanity."); return TRUE; } @@ -6763,7 +6763,7 @@ cmd_autoaway(ProfWin* window, const char* const command, gchar** args) } if (g_strcmp0(args[0], "check") == 0) { - _cmd_set_boolean_preference(args[1], command, "Online check", PREF_AUTOAWAY_CHECK); + _cmd_set_boolean_preference(args[1], "Online check", PREF_AUTOAWAY_CHECK); return TRUE; } @@ -6808,7 +6808,7 @@ cmd_vercheck(ProfWin* window, const char* const command, gchar** args) cons_check_version(TRUE); return TRUE; } else { - _cmd_set_boolean_preference(args[0], command, "Version checking", PREF_VERCHECK); + _cmd_set_boolean_preference(args[0], "Version checking", PREF_VERCHECK); return TRUE; } } @@ -6830,7 +6830,7 @@ cmd_xmlconsole(ProfWin* window, const char* const command, gchar** args) gboolean cmd_flash(ProfWin* window, const char* const command, gchar** args) { - _cmd_set_boolean_preference(args[0], command, "Screen flash", PREF_FLASH); + _cmd_set_boolean_preference(args[0], "Screen flash", PREF_FLASH); return TRUE; } @@ -6884,7 +6884,7 @@ cmd_tray(ProfWin* window, const char* const command, gchar** args) return TRUE; } else { gboolean old = prefs_get_boolean(PREF_TRAY); - _cmd_set_boolean_preference(args[0], command, "Tray icon", PREF_TRAY); + _cmd_set_boolean_preference(args[0], "Tray icon", PREF_TRAY); gboolean new = prefs_get_boolean(PREF_TRAY); if (old != new) { if (new) { @@ -6906,9 +6906,9 @@ gboolean cmd_intype(ProfWin* window, const char* const command, gchar** args) { if (g_strcmp0(args[0], "console") == 0) { - _cmd_set_boolean_preference(args[1], command, "Show contact typing in console", PREF_INTYPE_CONSOLE); + _cmd_set_boolean_preference(args[1], "Show contact typing in console", PREF_INTYPE_CONSOLE); } else if (g_strcmp0(args[0], "titlebar") == 0) { - _cmd_set_boolean_preference(args[1], command, "Show contact typing in titlebar", PREF_INTYPE); + _cmd_set_boolean_preference(args[1], "Show contact typing in titlebar", PREF_INTYPE); } else { cons_bad_cmd_usage(command); } @@ -6919,7 +6919,7 @@ cmd_intype(ProfWin* window, const char* const command, gchar** args) gboolean cmd_splash(ProfWin* window, const char* const command, gchar** args) { - _cmd_set_boolean_preference(args[0], command, "Splash screen", PREF_SPLASH); + _cmd_set_boolean_preference(args[0], "Splash screen", PREF_SPLASH); return TRUE; } @@ -6955,7 +6955,7 @@ cmd_logging(ProfWin* window, const char* const command, gchar** args) } if (strcmp(args[0], "chat") == 0 && args[1] != NULL) { - _cmd_set_boolean_preference(args[1], command, "Chat logging", PREF_CHLOG); + _cmd_set_boolean_preference(args[1], "Chat logging", PREF_CHLOG); // if set to off, disable history if (strcmp(args[1], "off") == 0) { @@ -6965,7 +6965,7 @@ cmd_logging(ProfWin* window, const char* const command, gchar** args) return TRUE; } else if (g_strcmp0(args[0], "group") == 0 && args[1] != NULL) { if (g_strcmp0(args[1], "on") == 0 || g_strcmp0(args[1], "off") == 0) { - _cmd_set_boolean_preference(args[1], command, "Groupchat logging", PREF_GRLOG); + _cmd_set_boolean_preference(args[1], "Groupchat logging", PREF_GRLOG); return TRUE; } } @@ -6981,7 +6981,7 @@ cmd_history(ProfWin* window, const char* const command, gchar** args) return FALSE; } - _cmd_set_boolean_preference(args[0], command, "Chat history", PREF_HISTORY); + _cmd_set_boolean_preference(args[0], "Chat history", PREF_HISTORY); // if set to on, set chlog (/logging chat on) if (strcmp(args[0], "on") == 0) { @@ -6998,7 +6998,7 @@ cmd_carbons(ProfWin* window, const char* const command, gchar** args) return FALSE; } - _cmd_set_boolean_preference(args[0], command, "Message carbons preference", PREF_CARBONS); + _cmd_set_boolean_preference(args[0], "Message carbons preference", PREF_CARBONS); jabber_conn_status_t conn_status = connection_get_status(); @@ -7018,7 +7018,7 @@ gboolean cmd_receipts(ProfWin* window, const char* const command, gchar** args) { if (g_strcmp0(args[0], "send") == 0) { - _cmd_set_boolean_preference(args[1], command, "Send delivery receipts", PREF_RECEIPTS_SEND); + _cmd_set_boolean_preference(args[1], "Send delivery receipts", PREF_RECEIPTS_SEND); if (g_strcmp0(args[1], "on") == 0) { caps_add_feature(XMPP_FEATURE_RECEIPTS); } @@ -7026,7 +7026,7 @@ cmd_receipts(ProfWin* window, const char* const command, gchar** args) caps_remove_feature(XMPP_FEATURE_RECEIPTS); } } else if (g_strcmp0(args[0], "request") == 0) { - _cmd_set_boolean_preference(args[1], command, "Request delivery receipts", PREF_RECEIPTS_REQUEST); + _cmd_set_boolean_preference(args[1], "Request delivery receipts", PREF_RECEIPTS_REQUEST); } else { cons_bad_cmd_usage(command); } @@ -7582,7 +7582,7 @@ cmd_pgp(ProfWin* window, const char* const command, gchar** args) } if (g_strcmp0(args[0], "sendfile") == 0) { - _cmd_set_boolean_preference(args[1], command, "Sending unencrypted files using /sendfile while otherwise using PGP", PREF_PGP_SENDFILE); + _cmd_set_boolean_preference(args[1], "Sending unencrypted files using /sendfile while otherwise using PGP", PREF_PGP_SENDFILE); return TRUE; } @@ -8331,7 +8331,7 @@ gboolean cmd_otr_sendfile(ProfWin* window, const char* const command, gchar** args) { #ifdef HAVE_LIBOTR - _cmd_set_boolean_preference(args[1], command, "Sending unencrypted files in an OTR session via /sendfile", PREF_OTR_SENDFILE); + _cmd_set_boolean_preference(args[1], "Sending unencrypted files in an OTR session via /sendfile", PREF_OTR_SENDFILE); return TRUE; #else @@ -8634,24 +8634,35 @@ _update_presence(const resource_presence_t resource_presence, } } -// helper function for boolean preference commands +/** + * Sets a boolean preference based on the provided argument. + * + * @param arg The argument value specifying the preference state ("on" or "off"). + * @param display The display (UI) name of the preference being set. + * @param preference The preference to be changed. + * + * @return TRUE if the preference was successfully set, FALSE otherwise. + */ static gboolean -_cmd_set_boolean_preference(gchar* arg, const char* const command, - const char* const display, preference_t pref) +_cmd_set_boolean_preference(gchar* arg, const char* const display, preference_t preference) { + gboolean prev_state = prefs_get_boolean(preference); if (arg == NULL) { - cons_bad_cmd_usage(command); - return FALSE; - } else if (g_strcmp0(arg, "on") == 0) { - cons_show("%s enabled.", display); - prefs_set_boolean(pref, TRUE); - } else if (g_strcmp0(arg, "off") == 0) { - cons_show("%s disabled.", display); - prefs_set_boolean(pref, FALSE); - } else { - cons_bad_cmd_usage(command); + cons_show("%s is %s.", display, prev_state ? "enabled" : "disabled"); return FALSE; } + + if (g_strcmp0(arg, "on") == 0) { + cons_show("%s %senabled.", display, prev_state ? "is already " : ""); + prefs_set_boolean(preference, TRUE); + } else if (g_strcmp0(arg, "off") == 0) { + cons_show("%s %sdisabled.", display, !prev_state ? "is already " : ""); + prefs_set_boolean(preference, FALSE); + } else { + cons_show_error("Invalid argument value. Expected 'on' or 'off'."); + return FALSE; + } + return TRUE; } @@ -9287,7 +9298,7 @@ cmd_color(ProfWin* window, const char* const command, gchar** args) prefs_set_string(PREF_COLOR_NICK, "blue"); } else if (g_strcmp0(args[0], "own") == 0) { if (g_strcmp0(args[1], "on") == 0) { - _cmd_set_boolean_preference(args[1], command, "Color generation for own nick", PREF_COLOR_NICK_OWN); + _cmd_set_boolean_preference(args[1], "Color generation for own nick", PREF_COLOR_NICK_OWN); } } else { cons_bad_cmd_usage(command); @@ -9349,7 +9360,7 @@ cmd_avatar(ProfWin* window, const char* const command, gchar** args) gboolean cmd_os(ProfWin* window, const char* const command, gchar** args) { - _cmd_set_boolean_preference(args[0], command, "Revealing OS name", PREF_REVEAL_OS); + _cmd_set_boolean_preference(args[0], "Revealing OS name", PREF_REVEAL_OS); return TRUE; } @@ -9359,11 +9370,11 @@ cmd_correction(ProfWin* window, const char* const command, gchar** args) { // enable/disable if (g_strcmp0(args[0], "on") == 0) { - _cmd_set_boolean_preference(args[0], command, "Last Message Correction", PREF_CORRECTION_ALLOW); + _cmd_set_boolean_preference(args[0], "Last Message Correction", PREF_CORRECTION_ALLOW); caps_add_feature(XMPP_FEATURE_LAST_MESSAGE_CORRECTION); return TRUE; } else if (g_strcmp0(args[0], "off") == 0) { - _cmd_set_boolean_preference(args[0], command, "Last Message Correction", PREF_CORRECTION_ALLOW); + _cmd_set_boolean_preference(args[0], "Last Message Correction", PREF_CORRECTION_ALLOW); caps_remove_feature(XMPP_FEATURE_LAST_MESSAGE_CORRECTION); return TRUE; } @@ -9452,7 +9463,7 @@ cmd_slashguard(ProfWin* window, const char* const command, gchar** args) return FALSE; } - _cmd_set_boolean_preference(args[0], command, "Slashguard", PREF_SLASH_GUARD); + _cmd_set_boolean_preference(args[0], "Slashguard", PREF_SLASH_GUARD); return TRUE; } @@ -9708,7 +9719,7 @@ cmd_executable_vcard_photo(ProfWin* window, const char* const command, gchar** a gboolean cmd_mam(ProfWin* window, const char* const command, gchar** args) { - _cmd_set_boolean_preference(args[0], command, "Message Archive Management", PREF_MAM); + _cmd_set_boolean_preference(args[0], "Message Archive Management", PREF_MAM); return TRUE; } @@ -9790,7 +9801,7 @@ cmd_correct_editor(ProfWin* window, const char* const command, gchar** args) gboolean cmd_silence(ProfWin* window, const char* const command, gchar** args) { - _cmd_set_boolean_preference(args[0], command, "Block all messages from JIDs that are not in the roster", PREF_SILENCE_NON_ROSTER); + _cmd_set_boolean_preference(args[0], "Block all messages from JIDs that are not in the roster", PREF_SILENCE_NON_ROSTER); return TRUE; } @@ -9864,10 +9875,10 @@ gboolean cmd_mood(ProfWin* window, const char* const command, gchar** args) { if (g_strcmp0(args[0], "on") == 0) { - _cmd_set_boolean_preference(args[0], command, "User mood", PREF_MOOD); + _cmd_set_boolean_preference(args[0], "User mood", PREF_MOOD); caps_add_feature(STANZA_NS_MOOD_NOTIFY); } else if (g_strcmp0(args[0], "off") == 0) { - _cmd_set_boolean_preference(args[0], command, "User mood", PREF_MOOD); + _cmd_set_boolean_preference(args[0], "User mood", PREF_MOOD); caps_remove_feature(STANZA_NS_MOOD_NOTIFY); } else if (g_strcmp0(args[0], "set") == 0) { if (args[1]) { From 36784738fcc19122ed6df04ff7f53e4266c77707 Mon Sep 17 00:00:00 2001 From: John Hernandez <129467592+H3rnand3zzz@users.noreply.github.com> Date: Fri, 30 Jun 2023 14:33:17 +0200 Subject: [PATCH 3/4] Add optional pgp public key autoimport Refactor `p_gpg_list_keys` Add `/pgp autoimport` command, it's not described in XEP-0027, but used in some clients, such as PSI, Pidgin. It will autoimport keys received with `/pgp sendpub`, in plain text as a message, or using features, provided in other clients. It doesn't autoassign them, but shows command to assign, letting user to decide. Improve documentation for some preexisting functions Add contact argument to `/pgp sendpub` --- src/command/cmd_ac.c | 18 +++ src/command/cmd_defs.c | 5 +- src/command/cmd_funcs.c | 44 ++++++- src/config/preferences.c | 4 + src/config/preferences.h | 1 + src/pgp/gpg.c | 204 ++++++++++++++++++++++++++------- src/pgp/gpg.h | 2 + src/ui/chatwin.c | 27 ++++- tests/unittests/pgp/stub_gpg.c | 14 ++- tests/unittests/test_cmd_pgp.c | 2 +- 10 files changed, 269 insertions(+), 52 deletions(-) diff --git a/src/command/cmd_ac.c b/src/command/cmd_ac.c index e2affcf6..c14648d9 100644 --- a/src/command/cmd_ac.c +++ b/src/command/cmd_ac.c @@ -238,6 +238,7 @@ static Autocomplete reconnect_ac; static Autocomplete pgp_ac; static Autocomplete pgp_log_ac; static Autocomplete pgp_sendfile_ac; +static Autocomplete pgp_autoimport_ac; static Autocomplete ox_ac; static Autocomplete ox_log_ac; #endif @@ -911,6 +912,7 @@ cmd_ac_init(void) autocomplete_add(pgp_ac, "char"); autocomplete_add(pgp_ac, "sendfile"); autocomplete_add(pgp_ac, "sendpub"); + autocomplete_add(pgp_ac, "autoimport"); pgp_log_ac = autocomplete_new(); autocomplete_add(pgp_log_ac, "on"); @@ -921,6 +923,10 @@ cmd_ac_init(void) autocomplete_add(pgp_sendfile_ac, "on"); autocomplete_add(pgp_sendfile_ac, "off"); + pgp_autoimport_ac = autocomplete_new(); + autocomplete_add(pgp_autoimport_ac, "on"); + autocomplete_add(pgp_autoimport_ac, "off"); + ox_ac = autocomplete_new(); autocomplete_add(ox_ac, "keys"); autocomplete_add(ox_ac, "contacts"); @@ -1675,6 +1681,7 @@ cmd_ac_reset(ProfWin* window) autocomplete_reset(pgp_ac); autocomplete_reset(pgp_log_ac); autocomplete_reset(pgp_sendfile_ac); + autocomplete_reset(pgp_autoimport_ac); autocomplete_reset(ox_ac); autocomplete_reset(ox_log_ac); #endif @@ -1857,6 +1864,7 @@ cmd_ac_uninit(void) autocomplete_free(pgp_ac); autocomplete_free(pgp_log_ac); autocomplete_free(pgp_sendfile_ac); + autocomplete_free(pgp_autoimport_ac); autocomplete_free(ox_ac); autocomplete_free(ox_log_ac); #endif @@ -2737,6 +2745,11 @@ _pgp_autocomplete(ProfWin* window, const char* const input, gboolean previous) if (found) { return found; } + + found = autocomplete_param_with_func(input, "/pgp sendpub", roster_contact_autocomplete, previous, NULL); + if (found) { + return found; + } } found = autocomplete_param_with_ac(input, "/pgp log", pgp_log_ac, TRUE, previous); @@ -2749,6 +2762,11 @@ _pgp_autocomplete(ProfWin* window, const char* const input, gboolean previous) return found; } + found = autocomplete_param_with_ac(input, "/pgp autoimport", pgp_autoimport_ac, TRUE, previous); + if (found) { + return found; + } + gboolean result; auto_gcharv gchar** args = parse_args(input, 2, 3, &result); if ((strncmp(input, "/pgp", 4) == 0) && (result == TRUE)) { diff --git a/src/command/cmd_defs.c b/src/command/cmd_defs.c index d3f21260..34776875 100644 --- a/src/command/cmd_defs.c +++ b/src/command/cmd_defs.c @@ -1706,7 +1706,7 @@ static const struct cmd_t command_defs[] = { "/pgp log on|off|redact", "/pgp char ", "/pgp sendfile on|off", - "/pgp sendpub") + "/pgp sendpub []") CMD_DESC( "Open PGP commands to manage keys, and perform PGP encryption during chat sessions. " "See the /account command to set your own PGP key.") @@ -1721,7 +1721,8 @@ static const struct cmd_t command_defs[] = { { "log redact", "Log PGP encrypted messages, but replace the contents with [redacted]. This is the default." }, { "char ", "Set the character to be displayed next to PGP encrypted messages." }, { "sendfile on|off", "Allow /sendfile to send unencrypted files while otherwise using PGP." }, - { "sendpub", "Used in chat. Sends a message to the current recipient with your PGP public key." }) + { "autoimport on|off", "Autoimport PGP keys from messages." }, + { "sendpub []", "Sends a message to the current recipient with your PGP public key, current contact will be used if not specified." }) CMD_EXAMPLES( "/pgp log off", "/pgp setkey odin@valhalla.edda BA19CACE5A9592C5", diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c index a4662aa8..da475030 100644 --- a/src/command/cmd_funcs.c +++ b/src/command/cmd_funcs.c @@ -7361,7 +7361,9 @@ cmd_pgp(ProfWin* window, const char* const command, gchar** args) } cons_bad_cmd_usage(command); return TRUE; - } else if (g_strcmp0(args[0], "log") == 0) { + } + + if (g_strcmp0(args[0], "log") == 0) { char* choice = args[1]; if (g_strcmp0(choice, "on") == 0) { prefs_set_string(PREF_PGP_LOG, "on"); @@ -7384,6 +7386,11 @@ cmd_pgp(ProfWin* window, const char* const command, gchar** args) return TRUE; } + if (g_strcmp0(args[0], "autoimport") == 0) { + _cmd_set_boolean_preference(args[1], "PGP keys autoimport from messages", PREF_PGP_PUBKEY_AUTOIMPORT); + return TRUE; + } + if (g_strcmp0(args[0], "keys") == 0) { GHashTable* keys = p_gpg_list_keys(); if (!keys || g_hash_table_size(keys) == 0) { @@ -7494,7 +7501,7 @@ cmd_pgp(ProfWin* window, const char* const command, gchar** args) } if (window->type != WIN_CHAT && args[1] == NULL) { - cons_show("You must be in a regular chat window to start PGP encryption."); + cons_show("You must set recipient in an argument or be in a regular chat window to start PGP encryption."); return TRUE; } @@ -7587,11 +7594,17 @@ cmd_pgp(ProfWin* window, const char* const command, gchar** args) } if (g_strcmp0(args[0], "sendpub") == 0) { - if (window->type != WIN_CHAT) { - cons_show_error("Please, use this command only in chat windows."); + jabber_conn_status_t conn_status = connection_get_status(); + if (conn_status != JABBER_CONNECTED) { + cons_show("You must be connected to share your PGP public key."); return TRUE; } - ProfChatWin* chatwin = (ProfChatWin*)window; + + if (window->type != WIN_CHAT && args[1] == NULL) { + cons_show("You must set recipient in an argument or use this command in a regular chat window to share your PGP key."); + return TRUE; + } + ProfAccount* account = accounts_get_account(session_get_account_name()); if (account->pgp_keyid == NULL) { @@ -7605,7 +7618,28 @@ cmd_pgp(ProfWin* window, const char* const command, gchar** args) account_free(account); return TRUE; } + + ProfChatWin* chatwin = NULL; + + if (args[1]) { + char* contact = args[1]; + char* barejid = roster_barejid_from_name(contact); + if (barejid == NULL) { + barejid = contact; + } + + chatwin = wins_get_chat(barejid); + if (!chatwin) { + chatwin = chatwin_new(barejid); + } + ui_focus_win((ProfWin*)chatwin); + } else { + chatwin = (ProfChatWin*)window; + assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK); + } + cl_ev_send_msg(chatwin, pubkey, NULL); + win_update_entry_message((ProfWin*)chatwin, chatwin->last_msg_id, "[you shared your PGP key]"); cons_show("PGP key has been shared with %s.", chatwin->barejid); account_free(account); return TRUE; diff --git a/src/config/preferences.c b/src/config/preferences.c index c10f4d83..5dffe22a 100644 --- a/src/config/preferences.c +++ b/src/config/preferences.c @@ -1863,6 +1863,7 @@ _get_group(preference_t pref) return PREF_GROUP_OTR; case PREF_PGP_LOG: case PREF_PGP_SENDFILE: + case PREF_PGP_PUBKEY_AUTOIMPORT: return PREF_GROUP_PGP; case PREF_BOOKMARK_INVITE: case PREF_ROOM_LIST_CACHE: @@ -2084,6 +2085,8 @@ _get_key(preference_t pref) return "log"; case PREF_PGP_SENDFILE: return "sendfile"; + case PREF_PGP_PUBKEY_AUTOIMPORT: + return "pgp.pubkey.autoimport"; case PREF_TLS_CERTPATH: return "tls.certpath"; case PREF_TLS_SHOW: @@ -2215,6 +2218,7 @@ _get_default_boolean(preference_t pref) case PREF_STROPHE_SM_ENABLED: case PREF_STROPHE_SM_RESEND: return TRUE; + case PREF_PGP_PUBKEY_AUTOIMPORT: default: return FALSE; } diff --git a/src/config/preferences.h b/src/config/preferences.h index ecb28485..ceac115a 100644 --- a/src/config/preferences.h +++ b/src/config/preferences.h @@ -147,6 +147,7 @@ typedef enum { PREF_TITLEBAR_MUC_TITLE_NAME, PREF_PGP_LOG, PREF_PGP_SENDFILE, + PREF_PGP_PUBKEY_AUTOIMPORT, PREF_TLS_CERTPATH, PREF_TLS_SHOW, PREF_LASTACTIVITY, diff --git a/src/pgp/gpg.c b/src/pgp/gpg.c index bd25a58f..09bcdadc 100644 --- a/src/pgp/gpg.c +++ b/src/pgp/gpg.c @@ -53,10 +53,12 @@ #include "tools/autocomplete.h" #include "ui/ui.h" -#define PGP_SIGNATURE_HEADER "-----BEGIN PGP SIGNATURE-----" -#define PGP_SIGNATURE_FOOTER "-----END PGP SIGNATURE-----" -#define PGP_MESSAGE_HEADER "-----BEGIN PGP MESSAGE-----" -#define PGP_MESSAGE_FOOTER "-----END PGP MESSAGE-----" +#define PGP_SIGNATURE_HEADER "-----BEGIN PGP SIGNATURE-----" +#define PGP_SIGNATURE_FOOTER "-----END PGP SIGNATURE-----" +#define PGP_MESSAGE_HEADER "-----BEGIN PGP MESSAGE-----" +#define PGP_MESSAGE_FOOTER "-----END PGP MESSAGE-----" +#define PGP_PUBLIC_KEY_HEADER "-----BEGIN PGP PUBLIC KEY BLOCK-----" +#define PGP_PUBLIC_KEY_FOOTER "-----END PGP PUBLIC KEY BLOCK-----" static const char* libversion = NULL; static GHashTable* pubkeys; @@ -73,6 +75,7 @@ static char* _remove_header_footer(char* str, const char* const footer); static char* _add_header_footer(const char* const str, const char* const header, const char* const footer); static char* _gpgme_data_to_char(gpgme_data_t data); static void _save_pubkeys(void); +static ProfPGPKey* _gpgme_key_to_ProfPGPKey(gpgme_key_t key); void _p_gpg_free_pubkeyid(ProfPGPPubKeyId* pubkeyid) @@ -304,6 +307,21 @@ p_gpg_free_key(ProfPGPKey* key) } } +/** + * Retrieve a list of GPG keys and create a hash table of ProfPGPKey objects. + * + * This function utilizes the GPGME library to retrieve both public and secret keys. + * It iterates over the keys and their subkeys to populate a hash table with ProfPGPKey objects. + * The key name is used as the key in the hash table, and the ProfPGPKey object is the corresponding value. + * + * @return A newly created GHashTable* containing ProfPGPKey objects, with the key name as the key. + * Returns NULL if an error occurs during key retrieval or if memory allocation fails. + * + * @note The returned hash table should be released using p_gpg_free_keys() + * when they are no longer needed to avoid memory leaks. + * + * @note This function may perform additional operations, such as autocomplete, related to the retrieved keys. + */ GHashTable* p_gpg_list_keys(void) { @@ -312,48 +330,23 @@ p_gpg_list_keys(void) gpgme_ctx_t ctx; error = gpgme_new(&ctx); - if (error) { - log_error("GPG: Could not list keys. %s %s", gpgme_strsource(error), gpgme_strerror(error)); + log_error("GPG: Could not create GPGME context. %s %s", gpgme_strsource(error), gpgme_strerror(error)); + g_hash_table_destroy(result); return NULL; } error = gpgme_op_keylist_start(ctx, NULL, 0); if (error == GPG_ERR_NO_ERROR) { gpgme_key_t key; + error = gpgme_op_keylist_next(ctx, &key); while (!error) { - gpgme_subkey_t sub = key->subkeys; - - ProfPGPKey* p_pgpkey = p_gpg_key_new(); - p_pgpkey->id = strdup(sub->keyid); - p_pgpkey->name = strdup(key->uids->uid); - p_pgpkey->fp = strdup(sub->fpr); - if (sub->can_encrypt) - p_pgpkey->encrypt = TRUE; - if (sub->can_authenticate) - p_pgpkey->authenticate = TRUE; - if (sub->can_certify) - p_pgpkey->certify = TRUE; - if (sub->can_sign) - p_pgpkey->sign = TRUE; - - sub = sub->next; - while (sub) { - if (sub->can_encrypt) - p_pgpkey->encrypt = TRUE; - if (sub->can_authenticate) - p_pgpkey->authenticate = TRUE; - if (sub->can_certify) - p_pgpkey->certify = TRUE; - if (sub->can_sign) - p_pgpkey->sign = TRUE; - - sub = sub->next; + ProfPGPKey* p_pgpkey = _gpgme_key_to_ProfPGPKey(key); + if (p_pgpkey != NULL) { + g_hash_table_insert(result, strdup(p_pgpkey->name), p_pgpkey); } - g_hash_table_insert(result, strdup(p_pgpkey->name), p_pgpkey); - gpgme_key_unref(key); error = gpgme_op_keylist_next(ctx, &key); } @@ -382,6 +375,7 @@ p_gpg_list_keys(void) gpgme_release(ctx); + // TODO: move autocomplete in other place autocomplete_clear(key_ac); GList* ids = g_hash_table_get_keys(result); GList* curr = ids; @@ -761,10 +755,13 @@ p_gpg_format_fp_str(char* fp) } /** - * \brief Function to extract specific public key from PGP - * \param keyid Key ID that will be used to search key in the current PGP context, if NULL returns null - * \returns null-terminated char* string with the the public key in armored format, that must be free'd to avoid memory leaks - * or NULL on error + * Returns the public key data for the given key ID. + * + * @param keyid The key ID for which to retrieve the public key data. + * If the key ID is empty or NULL, returns NULL. + * @return The public key data as a null-terminated char* string allocated using malloc. + * The returned string should be freed by the caller. + * Returns NULL on error, and errors are written to the error log. */ char* p_gpg_get_pubkey(const char* keyid) @@ -802,10 +799,135 @@ cleanup: return _gpgme_data_to_char(data); } +/** + * Validate that the provided buffer has the format of an armored public key. + * + * This function only briefly checks for presence of armored header and footer. + * + * @param buffer The buffer containing the key data. + * @return TRUE if the buffer has the expected header and footer of an armored public key, FALSE otherwise. + */ +gboolean +p_gpg_is_public_key_format(const char* buffer) +{ + if (buffer == NULL || buffer[0] == '\0') { + return false; + } + + const char* headerPos = strstr(buffer, PGP_PUBLIC_KEY_HEADER); + if (headerPos == NULL) { + return false; + } + + const char* footerPos = strstr(buffer, PGP_PUBLIC_KEY_FOOTER); + + return (footerPos != NULL && footerPos > headerPos); +} + +/** + * Imports a PGP public key(s) from a buffer. + * + * @param buffer The buffer containing the PGP key data. + * @return A pointer to the first imported ProfPGPKey structure, or NULL if an error occurs. + * + * @note The caller is responsible for freeing the memory of the returned ProfPGPKey structure + * by calling p_gpg_free_key() to avoid resource leaks. + */ +ProfPGPKey* +p_gpg_import_pubkey(const char* buffer) +{ + gpgme_ctx_t ctx; + ProfPGPKey* result = NULL; + gpgme_error_t error = gpgme_new(&ctx); + if (error != GPG_ERR_NO_ERROR) { + log_error("GPG: Error creating GPGME context"); + goto out; + } + + gpgme_data_t key_data; + error = gpgme_data_new_from_mem(&key_data, buffer, strlen(buffer), 1); + if (error != GPG_ERR_NO_ERROR) { + log_error("GPG: Error creating GPGME data from buffer"); + goto out; + } + + error = gpgme_op_import(ctx, key_data); + + gpgme_data_release(key_data); + + if (error != GPG_ERR_NO_ERROR) { + log_error("GPG: Error importing key data"); + goto out; + } + + gpgme_import_result_t import_result = gpgme_op_import_result(ctx); + gpgme_import_status_t status = import_result->imports; + gboolean is_valid = (status && status->result == GPG_ERR_NO_ERROR); + + if (!is_valid) { + log_error("GPG: Error importing PGP key (%s).", status ? gpgme_strerror(status->result) : "Invalid import status."); + goto out; + } + + gpgme_key_t key = NULL; + error = gpgme_get_key(ctx, status->fpr, &key, 0); + if (error != GPG_ERR_NO_ERROR) { + log_error("GPG: Unable to find imported PGP key (%s).", gpgme_strerror(error)); + goto out; + } + + result = _gpgme_key_to_ProfPGPKey(key); + gpgme_key_release(key); + +out: + gpgme_release(ctx); + return result; +} + +/** + * Converts a GPGME key struct to a ProfPGPKey struct. + * + * @param key The GPGME key struct to convert. + * @return A newly allocated ProfPGPKey struct populated with the converted data, + * or NULL if an error occurs. + * + * @note The caller is responsible for freeing the memory of the returned ProfPGPKey structure + * by calling p_gpg_free_key() to avoid resource leaks. + */ +static ProfPGPKey* +_gpgme_key_to_ProfPGPKey(gpgme_key_t key) +{ + if (key == NULL) { + return NULL; + } + + ProfPGPKey* p_pgpkey = p_gpg_key_new(); + gpgme_subkey_t sub = key->subkeys; + p_pgpkey->id = strdup(sub->keyid); + p_pgpkey->name = strdup(key->uids->uid); + p_pgpkey->fp = strdup(sub->fpr); + + while (sub) { + if (sub->can_encrypt) + p_pgpkey->encrypt = TRUE; + if (sub->can_authenticate) + p_pgpkey->authenticate = TRUE; + if (sub->can_certify) + p_pgpkey->certify = TRUE; + if (sub->can_sign) + p_pgpkey->sign = TRUE; + + sub = sub->next; + } + return p_pgpkey; +} + /** * Convert a gpgme_data_t object to a null-terminated char* string. - * The returned string is allocated using malloc and should be freed by the caller. - * If an error occurs or the data is empty, NULL is returned and errors written to the error log. + * + * @param data The gpgme_data_t object to convert. + * @return The converted string allocated using malloc, which should be freed by the caller. + * If an error occurs or the data is empty, NULL is returned and errors are written to the error log. */ static char* _gpgme_data_to_char(gpgme_data_t data) diff --git a/src/pgp/gpg.h b/src/pgp/gpg.h index 23d32fa7..883aaf73 100644 --- a/src/pgp/gpg.h +++ b/src/pgp/gpg.h @@ -74,6 +74,8 @@ char* p_gpg_autocomplete_key(const char* const search_str, gboolean previous, vo void p_gpg_autocomplete_key_reset(void); char* p_gpg_format_fp_str(char* fp); char* p_gpg_get_pubkey(const char* const keyid); +gboolean p_gpg_is_public_key_format(const char* buffer); +ProfPGPKey* p_gpg_import_pubkey(const char* buffer); ProfPGPKey* p_gpg_key_new(void); void p_gpg_free_key(ProfPGPKey* key); diff --git a/src/ui/chatwin.c b/src/ui/chatwin.c index 0c540998..6ae2c681 100644 --- a/src/ui/chatwin.c +++ b/src/ui/chatwin.c @@ -53,6 +53,9 @@ #ifdef HAVE_LIBOTR #include "otr/otr.h" #endif +#ifdef HAVE_LIBGPGME +#include "pgp/gpg.h" +#endif #ifdef HAVE_OMEMO #include "omemo/omemo.h" #endif @@ -320,6 +323,7 @@ chatwin_incoming_msg(ProfChatWin* chatwin, ProfMessage* message, gboolean win_cr char* old_plain = message->plain; message->plain = plugins_pre_chat_message_display(message->from_jid->barejid, message->from_jid->resourcepart, message->plain); + gboolean show_message = true; ProfWin* window = (ProfWin*)chatwin; int num = wins_get_num(window); @@ -333,12 +337,29 @@ chatwin_incoming_msg(ProfChatWin* chatwin, ProfMessage* message, gboolean win_cr } free(mybarejid); +#ifdef HAVE_LIBGPGME + if (prefs_get_boolean(PREF_PGP_PUBKEY_AUTOIMPORT)) { + if (p_gpg_is_public_key_format(message->plain)) { + ProfPGPKey* key = p_gpg_import_pubkey(message->plain); + if (key != NULL) { + show_message = false; + win_println(window, THEME_DEFAULT, "-", "Received and imported PGP key %s: \"%s\". To assign it to the correspondent using /pgp setkey %s %s", key->fp, key->name, display_name, key->id); + p_gpg_free_key(key); + } else { + win_println(window, THEME_DEFAULT, "-", "Received PGP key, but couldn't import PGP key above."); + } + } + } +#endif + gboolean is_current = wins_is_current(window); gboolean notify = prefs_do_chat_notify(is_current) && !message->is_mam; // currently viewing chat window with sender if (wins_is_current(window)) { - win_print_incoming(window, display_name, message); + if (show_message) { + win_print_incoming(window, display_name, message); + } title_bar_set_typing(FALSE); status_bar_active(num, WIN_CHAT, chatwin->barejid); @@ -377,7 +398,9 @@ chatwin_incoming_msg(ProfChatWin* chatwin, ProfMessage* message, gboolean win_cr } win_insert_last_read_position_marker((ProfWin*)chatwin, chatwin->barejid); - win_print_incoming(window, display_name, message); + if (show_message) { + win_print_incoming(window, display_name, message); + } } if (!message->is_mam) { diff --git a/tests/unittests/pgp/stub_gpg.c b/tests/unittests/pgp/stub_gpg.c index e1efae72..6333e723 100644 --- a/tests/unittests/pgp/stub_gpg.c +++ b/tests/unittests/pgp/stub_gpg.c @@ -103,4 +103,16 @@ char* p_gpg_get_pubkey(const char* const keyid) { return NULL; -} \ No newline at end of file +} + +gboolean +p_gpg_is_public_key_format(const char* buffer) +{ + return TRUE; +} + +ProfPGPKey* +p_gpg_import_pubkey(const char* buffer) +{ + return NULL; +} diff --git a/tests/unittests/test_cmd_pgp.c b/tests/unittests/test_cmd_pgp.c index f9d66e2b..35df812f 100644 --- a/tests/unittests/test_cmd_pgp.c +++ b/tests/unittests/test_cmd_pgp.c @@ -69,7 +69,7 @@ cmd_pgp_start_shows_message_when_no_arg_in_wintype(win_type_t wintype) will_return(connection_get_status, JABBER_CONNECTED); - expect_cons_show("You must be in a regular chat window to start PGP encryption."); + expect_cons_show("You must set recipient in an argument or be in a regular chat window to start PGP encryption."); gboolean result = cmd_pgp(&window, CMD_PGP, args); assert_true(result); From 7d6cbfdcd2122b5ede3a0e1e43b85794d9c47c3e Mon Sep 17 00:00:00 2001 From: John Hernandez <129467592+H3rnand3zzz@users.noreply.github.com> Date: Tue, 16 May 2023 09:42:47 +0200 Subject: [PATCH 4/4] Refactor autocomplete Use `_cmd_ac_complete_params` when fits, thus reducing plurality --- src/command/cmd_ac.c | 48 +++++--------------------------------------- 1 file changed, 5 insertions(+), 43 deletions(-) diff --git a/src/command/cmd_ac.c b/src/command/cmd_ac.c index c14648d9..cb8df805 100644 --- a/src/command/cmd_ac.c +++ b/src/command/cmd_ac.c @@ -150,7 +150,6 @@ static Autocomplete notify_chat_ac; static Autocomplete notify_room_ac; static Autocomplete notify_typing_ac; static Autocomplete notify_mention_ac; -static Autocomplete notify_offline_ac; static Autocomplete notify_trigger_ac; static Autocomplete prefs_ac; static Autocomplete sub_ac; @@ -197,7 +196,6 @@ static Autocomplete bookmark_ignore_ac; static Autocomplete otr_ac; static Autocomplete otr_log_ac; static Autocomplete otr_policy_ac; -static Autocomplete otr_sendfile_ac; #endif #ifdef HAVE_OMEMO static Autocomplete omemo_ac; @@ -237,8 +235,6 @@ static Autocomplete reconnect_ac; #ifdef HAVE_LIBGPGME static Autocomplete pgp_ac; static Autocomplete pgp_log_ac; -static Autocomplete pgp_sendfile_ac; -static Autocomplete pgp_autoimport_ac; static Autocomplete ox_ac; static Autocomplete ox_log_ac; #endif @@ -294,7 +290,6 @@ static Autocomplete vcard_set_ac; static Autocomplete vcard_name_ac; static Autocomplete vcard_set_param_ac; static Autocomplete vcard_togglable_param_ac; -static Autocomplete vcard_toggle_ac; static Autocomplete vcard_address_type_ac; static GHashTable* ac_funcs = NULL; @@ -387,10 +382,6 @@ cmd_ac_init(void) autocomplete_add(notify_mention_ac, "word_whole"); autocomplete_add(notify_mention_ac, "word_part"); - notify_offline_ac = autocomplete_new(); - autocomplete_add(notify_offline_ac, "on"); - autocomplete_add(notify_offline_ac, "off"); - notify_trigger_ac = autocomplete_new(); autocomplete_add(notify_trigger_ac, "add"); autocomplete_add(notify_trigger_ac, "remove"); @@ -716,10 +707,6 @@ cmd_ac_init(void) autocomplete_add(otr_policy_ac, "manual"); autocomplete_add(otr_policy_ac, "opportunistic"); autocomplete_add(otr_policy_ac, "always"); - - otr_sendfile_ac = autocomplete_new(); - autocomplete_add(otr_sendfile_ac, "on"); - autocomplete_add(otr_sendfile_ac, "off"); #endif #ifdef HAVE_OMEMO @@ -919,14 +906,6 @@ cmd_ac_init(void) autocomplete_add(pgp_log_ac, "off"); autocomplete_add(pgp_log_ac, "redact"); - pgp_sendfile_ac = autocomplete_new(); - autocomplete_add(pgp_sendfile_ac, "on"); - autocomplete_add(pgp_sendfile_ac, "off"); - - pgp_autoimport_ac = autocomplete_new(); - autocomplete_add(pgp_autoimport_ac, "on"); - autocomplete_add(pgp_autoimport_ac, "off"); - ox_ac = autocomplete_new(); autocomplete_add(ox_ac, "keys"); autocomplete_add(ox_ac, "contacts"); @@ -1316,10 +1295,6 @@ cmd_ac_init(void) autocomplete_add(vcard_togglable_param_ac, "preferred"); autocomplete_add(vcard_togglable_param_ac, "x400"); - vcard_toggle_ac = autocomplete_new(); - autocomplete_add(vcard_toggle_ac, "on"); - autocomplete_add(vcard_toggle_ac, "off"); - vcard_address_type_ac = autocomplete_new(); autocomplete_add(vcard_address_type_ac, "domestic"); autocomplete_add(vcard_address_type_ac, "international"); @@ -1640,7 +1615,6 @@ cmd_ac_reset(ProfWin* window) autocomplete_reset(otr_ac); autocomplete_reset(otr_log_ac); autocomplete_reset(otr_policy_ac); - autocomplete_reset(otr_sendfile_ac); #endif #ifdef HAVE_OMEMO autocomplete_reset(omemo_ac); @@ -1680,8 +1654,6 @@ cmd_ac_reset(ProfWin* window) #ifdef HAVE_LIBGPGME autocomplete_reset(pgp_ac); autocomplete_reset(pgp_log_ac); - autocomplete_reset(pgp_sendfile_ac); - autocomplete_reset(pgp_autoimport_ac); autocomplete_reset(ox_ac); autocomplete_reset(ox_log_ac); #endif @@ -1731,7 +1703,6 @@ cmd_ac_reset(ProfWin* window) autocomplete_reset(vcard_name_ac); autocomplete_reset(vcard_set_param_ac); autocomplete_reset(vcard_togglable_param_ac); - autocomplete_reset(vcard_toggle_ac); autocomplete_reset(vcard_address_type_ac); autocomplete_reset(script_ac); @@ -1824,7 +1795,6 @@ cmd_ac_uninit(void) autocomplete_free(otr_ac); autocomplete_free(otr_log_ac); autocomplete_free(otr_policy_ac); - autocomplete_free(otr_sendfile_ac); #endif #ifdef HAVE_OMEMO autocomplete_free(omemo_ac); @@ -1863,8 +1833,6 @@ cmd_ac_uninit(void) #ifdef HAVE_LIBGPGME autocomplete_free(pgp_ac); autocomplete_free(pgp_log_ac); - autocomplete_free(pgp_sendfile_ac); - autocomplete_free(pgp_autoimport_ac); autocomplete_free(ox_ac); autocomplete_free(ox_log_ac); #endif @@ -1915,7 +1883,6 @@ cmd_ac_uninit(void) autocomplete_free(vcard_name_ac); autocomplete_free(vcard_set_param_ac); autocomplete_free(vcard_togglable_param_ac); - autocomplete_free(vcard_toggle_ac); autocomplete_free(vcard_address_type_ac); } @@ -2544,7 +2511,7 @@ _notify_autocomplete(ProfWin* window, const char* const input, gboolean previous } gchar* boolean_choices1[] = { "/notify room current", "/notify chat current", "/notify typing current", - "/notify room text", "/notify chat text" }; + "/notify room text", "/notify chat text", "/notify room offline" }; for (int i = 0; i < ARRAY_SIZE(boolean_choices1); i++) { result = autocomplete_param_with_func(input, boolean_choices1[i], prefs_autocomplete_boolean_choice, previous, NULL); if (result) { @@ -2557,11 +2524,6 @@ _notify_autocomplete(ProfWin* window, const char* const input, gboolean previous return result; } - result = autocomplete_param_with_ac(input, "/notify room offline", notify_offline_ac, TRUE, previous); - if (result) { - return result; - } - result = autocomplete_param_with_ac(input, "/notify room trigger", notify_trigger_ac, TRUE, previous); if (result) { return result; @@ -2718,7 +2680,7 @@ _otr_autocomplete(ProfWin* window, const char* const input, gboolean previous) return found; } - found = autocomplete_param_with_ac(input, "/otr sendfile", otr_sendfile_ac, TRUE, previous); + found = autocomplete_param_with_func(input, "/otr sendfile", prefs_autocomplete_boolean_choice, previous, NULL); if (found) { return found; } @@ -2757,12 +2719,12 @@ _pgp_autocomplete(ProfWin* window, const char* const input, gboolean previous) return found; } - found = autocomplete_param_with_ac(input, "/pgp sendfile", pgp_sendfile_ac, TRUE, previous); + found = autocomplete_param_with_func(input, "/pgp sendfile", prefs_autocomplete_boolean_choice, previous, NULL); if (found) { return found; } - found = autocomplete_param_with_ac(input, "/pgp autoimport", pgp_autoimport_ac, TRUE, previous); + found = autocomplete_param_with_func(input, "/pgp autoimport", prefs_autocomplete_boolean_choice, previous, NULL); if (found) { return found; } @@ -4565,7 +4527,7 @@ _vcard_autocomplete(ProfWin* window, const char* const input, gboolean previous) } else if ((num_args == 3 && space_at_end && is_num && autocomplete_contains(vcard_togglable_param_ac, args[2])) || (num_args == 4 && !space_at_end && is_num && autocomplete_contains(vcard_togglable_param_ac, args[2]))) { GString* beginning = g_string_new("/vcard"); g_string_append_printf(beginning, " %s %s %s", args[0], args[1], args[2]); - result = autocomplete_param_with_ac(input, beginning->str, vcard_toggle_ac, TRUE, previous); + result = autocomplete_param_with_func(input, beginning->str, prefs_autocomplete_boolean_choice, previous, NULL); g_string_free(beginning, TRUE); if (result) { return result;