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);