[balsa] Autocrypt improvements
- From: Albrecht Dreß <albrecht src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [balsa] Autocrypt improvements
- Date: Sun, 31 Jan 2021 16:48:44 +0000 (UTC)
commit da4d141549b0fbcc8741f82688daa02327cc8645
Author: Albrecht Dreß <albrecht dress arcor de>
Date: Sun Jan 31 17:49:10 2021 +0100
Autocrypt improvements
This change adds the following improvements to Autocrypt support:
1. add a context menu to the Autocrypt database dialogue, with options
to view the key details (replacing the double-click), and to delete a
key. The context menu can be opened as usual (right click, Sh-F10);
2. enable sorting by address or time stamps in the database dialogue;
3. display a warning if a message contains a broken Autocrypt header, or
if the Autocrypt header contains a different key than the one used
for signing the message.
Changes:
- libbalsa/autocrypt.c:(global) add SQLite query for deleting a key; add
list store columns for raw time stamps; autocrypt_db_dialog_run():
drop row-activated signal connection, connect gesture pressed and
popup-menu signals, make columns sortable; remove obsolete function
row_activated_cb(); implement callbacks for pressed, popup-menu, key
details display and key removal; extract_ac_keydata(): remove unused
parameter
- src/balsa-mime-widget-crypto.c: balsa_mime_widget_signature_widget():
implement #3
Signed-off-by: Albrecht Dreß <albrecht dress arcor de>
libbalsa/autocrypt.c | 231 ++++++++++++++++++++++++++++++++++-------
src/balsa-mime-widget-crypto.c | 4 +
2 files changed, 197 insertions(+), 38 deletions(-)
---
diff --git a/libbalsa/autocrypt.c b/libbalsa/autocrypt.c
index 4dacc2cfb..c5d2ee1c4 100644
--- a/libbalsa/autocrypt.c
+++ b/libbalsa/autocrypt.c
@@ -65,7 +65,7 @@
"prefer_encrypt BOOLEAN DEFAULT 0);"
-#define NUM_QUERIES 6U
+#define NUM_QUERIES 7U
struct _AutocryptData {
@@ -83,7 +83,9 @@ typedef struct _AutocryptData AutocryptData;
enum {
AC_ADDRESS_COLUMN = 0,
+ AC_LAST_SEEN_INT_COLUMN,
AC_LAST_SEEN_COLUMN,
+ AC_TIMESTAMP_INT_COLUMN,
AC_TIMESTAMP_COLUMN,
AC_PREFER_ENCRYPT_COLUMN,
AC_KEY_PTR_COLUMN,
@@ -101,8 +103,7 @@ typedef struct {
static void autocrypt_close(void);
static gboolean extract_ac_keydata(GMimeAutocryptHeader *autocrypt_header,
- ac_key_data_t *dest,
- GError **error);
+ ac_key_data_t *dest);
static void add_or_update_user_info(GMimeAutocryptHeader *autocrypt_header,
const ac_key_data_t *ac_key_data,
gboolean update,
@@ -118,14 +119,24 @@ static AutocryptRecommend autocrypt_check_ia_list(gpgme_ctx_t gpgme_ct
time_t
ref_time,
GList
**missing_keys,
GError
**error);
-static void row_activated_cb(GtkTreeView *tree_view,
- GtkTreePath *path,
- GtkTreeViewColumn *column,
- gpointer user_data);
+
+static gboolean popup_menu_cb(GtkWidget *widget,
+ gpointer user_data);
+static void button_press_cb(GtkGestureMultiPress *multi_press_gesture,
+ gint n_press,
+ gdouble x,
+ gdouble y,
+ gpointer user_data);
+static void popup_menu_real(GtkWidget *widget,
+ const GdkEvent *event);
+static void show_key_details_cb(GtkMenuItem *menuitem,
+ gpointer user_data);
+static void remove_key_cb(GtkMenuItem *menuitem,
+ gpointer user_data);
static sqlite3 *autocrypt_db = NULL;
-static sqlite3_stmt *query[NUM_QUERIES] = { NULL, NULL, NULL, NULL, NULL, NULL };
+static sqlite3_stmt *query[NUM_QUERIES] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL };
G_LOCK_DEFINE_STATIC(db_mutex);
@@ -140,7 +151,8 @@ autocrypt_init(GError **error)
" expires = ?5, prefer_encrypt = ?6 WHERE addr = LOWER(?1)",
"UPDATE autocrypt SET last_seen = ?2 WHERE addr = LOWER(?1) AND last_seen < ?2 AND
ac_timestamp < ?2",
"SELECT pubkey FROM autocrypt WHERE fingerprint LIKE ?",
- "SELECT addr, last_seen, ac_timestamp, prefer_encrypt, pubkey FROM autocrypt ORDER BY addr
ASC"
+ "SELECT addr, last_seen, ac_timestamp, prefer_encrypt, pubkey FROM autocrypt ORDER BY addr
ASC",
+ "DELETE FROM autocrypt WHERE addr = LOWER(?1)"
};
gboolean result;
@@ -237,7 +249,7 @@ autocrypt_from_message(LibBalsaMessage *message,
/* update the database */
G_LOCK(db_mutex);
- if (extract_ac_keydata(headers->autocrypt_hdr, &ac_key_data, error)) {
+ if (extract_ac_keydata(headers->autocrypt_hdr, &ac_key_data)) {
AutocryptData *db_info;
db_info = autocrypt_user_info(g_mime_autocrypt_header_get_address_as_string(headers->autocrypt_hdr),
error);
@@ -422,13 +434,13 @@ autocrypt_db_dialog_run(const gchar *date_string, GtkWindow *parent)
{
GtkWidget *dialog;
GtkWidget *vbox;
- GtkWidget *label;
GtkWidget *scrolled_window;
GtkWidget *tree_view;
GtkListStore *model;
GtkTreeSelection *selection;
GtkCellRenderer *renderer;
GtkTreeViewColumn *column;
+ GtkGesture *gesture;
GList *keys = NULL;
int sqlite_res;
@@ -438,25 +450,29 @@ autocrypt_db_dialog_run(const gchar *date_string, GtkWindow *parent)
vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), vbox);
- gtk_widget_set_vexpand (vbox, TRUE);
- label = gtk_label_new(_("Double-click key to show details"));
- gtk_widget_set_halign(label, GTK_ALIGN_START);
- gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, TRUE, 0);
+ gtk_widget_set_vexpand(vbox, TRUE);
scrolled_window = gtk_scrolled_window_new(NULL, NULL);
+ gtk_container_set_border_width(GTK_CONTAINER(scrolled_window), 12U);
gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled_window), GTK_SHADOW_ETCHED_IN);
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), GTK_POLICY_AUTOMATIC,
GTK_POLICY_AUTOMATIC);
gtk_box_pack_start(GTK_BOX(vbox), scrolled_window, TRUE, TRUE, 0);
model = gtk_list_store_new(AC_DB_VIEW_COLUMNS, G_TYPE_STRING, /* address */
+ G_TYPE_INT64,
/* last seen timestamp value (for sorting) */
G_TYPE_STRING,
/* formatted last seen timestamp */
+ G_TYPE_INT64,
/* last Autocrypt message timestamp value (for sorting) */
G_TYPE_STRING,
/* formatted last Autocrypt message timestamp */
G_TYPE_BOOLEAN,
/* user prefers encrypted messages */
G_TYPE_POINTER);
/* key */
tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
- g_signal_connect(tree_view, "row-activated", G_CALLBACK(row_activated_cb), dialog);
+ gesture = gtk_gesture_multi_press_new(tree_view);
+ gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(gesture), 0);
+ g_signal_connect(gesture, "pressed", G_CALLBACK(button_press_cb), NULL);
+ gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER(gesture), GTK_PHASE_CAPTURE);
+ g_signal_connect(tree_view, "popup-menu", G_CALLBACK(popup_menu_cb), NULL);
gtk_container_add(GTK_CONTAINER(scrolled_window), tree_view);
selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
@@ -465,20 +481,26 @@ autocrypt_db_dialog_run(const gchar *date_string, GtkWindow *parent)
/* add the keys */
sqlite_res = sqlite3_step(query[5]);
while (sqlite_res == SQLITE_ROW) {
+ gint64 last_seen_val;
gchar *last_seen_buf;
+ gint64 last_ac_val;
gchar *last_ac_buf;
GBytes *key;
GtkTreeIter iter;
- last_seen_buf = libbalsa_date_to_utf8(sqlite3_column_int64(query[5], 1), date_string);
- last_ac_buf = libbalsa_date_to_utf8(sqlite3_column_int64(query[5], 2), date_string);
+ last_seen_val = sqlite3_column_int64(query[5], 1);
+ last_seen_buf = libbalsa_date_to_utf8(last_seen_val, date_string);
+ last_ac_val = sqlite3_column_int64(query[5], 2);
+ last_ac_buf = libbalsa_date_to_utf8(last_ac_val, date_string);
key = g_bytes_new(sqlite3_column_blob(query[5], 4), sqlite3_column_bytes(query[5], 4));
keys = g_list_prepend(keys, key);
gtk_list_store_append(model, &iter);
gtk_list_store_set(model, &iter,
AC_ADDRESS_COLUMN, sqlite3_column_text(query[5], 0),
+ AC_LAST_SEEN_INT_COLUMN, last_seen_val,
AC_LAST_SEEN_COLUMN, last_seen_buf,
+ AC_TIMESTAMP_INT_COLUMN, last_ac_val,
AC_TIMESTAMP_COLUMN, last_ac_buf,
AC_PREFER_ENCRYPT_COLUMN, sqlite3_column_int(query[5], 3),
AC_KEY_PTR_COLUMN, key,
@@ -491,29 +513,33 @@ autocrypt_db_dialog_run(const gchar *date_string, GtkWindow *parent)
sqlite3_reset(query[5]);
/* set up the tree view */
- g_object_unref(model);
-
renderer = gtk_cell_renderer_text_new();
- column = gtk_tree_view_column_new_with_attributes(_("Mailbox"), renderer, "text", 0, NULL);
+ column = gtk_tree_view_column_new_with_attributes(_("Mailbox"), renderer, "text", AC_ADDRESS_COLUMN,
NULL);
+ gtk_tree_view_column_set_sort_column_id(column, AC_ADDRESS_COLUMN);
gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), column);
gtk_tree_view_column_set_resizable(column, TRUE);
renderer = gtk_cell_renderer_text_new();
- column = gtk_tree_view_column_new_with_attributes(_("Last seen"), renderer, "text", 1, NULL);
+ column = gtk_tree_view_column_new_with_attributes(_("Last seen"), renderer, "text",
AC_LAST_SEEN_COLUMN, NULL);
+ gtk_tree_view_column_set_sort_column_id(column, AC_LAST_SEEN_INT_COLUMN);
gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), column);
gtk_tree_view_column_set_resizable(column, TRUE);
renderer = gtk_cell_renderer_text_new();
- column = gtk_tree_view_column_new_with_attributes(_("Last Autocrypt message"), renderer, "text", 2,
NULL);
+ column = gtk_tree_view_column_new_with_attributes(_("Last Autocrypt message"), renderer, "text",
AC_TIMESTAMP_COLUMN, NULL);
+ gtk_tree_view_column_set_sort_column_id(column, AC_TIMESTAMP_INT_COLUMN);
gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), column);
gtk_tree_view_column_set_resizable(column, TRUE);
renderer = gtk_cell_renderer_toggle_new();
- column = gtk_tree_view_column_new_with_attributes(_("Prefer encryption"), renderer, "active", 3,
NULL);
+ column = gtk_tree_view_column_new_with_attributes(_("Prefer encryption"), renderer, "active",
AC_PREFER_ENCRYPT_COLUMN, NULL);
gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), column);
gtk_tree_view_column_set_resizable(column, TRUE);
gtk_widget_show_all(vbox);
+ gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model), AC_ADDRESS_COLUMN, GTK_SORT_ASCENDING);
+ g_object_unref(model);
+
(void) gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
g_list_free_full(keys, (GDestroyNotify) g_bytes_unref);
@@ -664,7 +690,7 @@ autocrypt_user_info(const gchar *mailbox, GError **error)
static gboolean
-extract_ac_keydata(GMimeAutocryptHeader *autocrypt_header, ac_key_data_t *dest, GError **error)
+extract_ac_keydata(GMimeAutocryptHeader *autocrypt_header, ac_key_data_t *dest)
{
GBytes *keydata;
gboolean success = FALSE;
@@ -773,18 +799,99 @@ update_last_seen(GMimeAutocryptHeader *autocrypt_header, GError **error)
}
+/* callback: popup menu key in autocrypt database dialogue activated */
+static gboolean
+popup_menu_cb(GtkWidget *widget, gpointer G_GNUC_UNUSED user_data)
+{
+ GtkTreeView *tree_view = GTK_TREE_VIEW(widget);
+ GtkTreeModel *model;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+
+ selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
+ if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
+ GtkTreePath *path;
+
+ path = gtk_tree_model_get_path(model, &iter);
+ gtk_tree_view_scroll_to_cell(tree_view, path, NULL, FALSE, 0.0, 0.0);
+ gtk_tree_path_free(path);
+ popup_menu_real(widget, NULL);
+ }
+
+ return TRUE;
+}
+
+
+/* callback: mouse click in autocrypt database dialogue activated */
+static void
+button_press_cb(GtkGestureMultiPress *multi_press_gesture, gint G_GNUC_UNUSED n_press, gdouble x, gdouble y,
+ gpointer G_GNUC_UNUSED user_data)
+{
+ GtkWidget *widget = gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(multi_press_gesture));
+ GtkTreeView *tree_view = GTK_TREE_VIEW(widget);
+ GtkGesture *gesture;
+ GdkEventSequence *sequence;
+ const GdkEvent *event;
+
+ gesture = GTK_GESTURE(multi_press_gesture);
+ sequence = gtk_gesture_single_get_current_sequence(GTK_GESTURE_SINGLE(multi_press_gesture));
+ event = gtk_gesture_get_last_event(gesture, sequence);
+ if (gdk_event_triggers_context_menu(event) && (gdk_event_get_window(event) ==
gtk_tree_view_get_bin_window(tree_view))) {
+ gint bx;
+ gint by;
+ GtkTreePath *path;
+
+ gtk_tree_view_convert_widget_to_bin_window_coords(tree_view, (gint) x, (gint) y, &bx, &by);
+ if (gtk_tree_view_get_path_at_pos(tree_view, bx, by, &path, NULL, NULL, NULL)) {
+ GtkTreeSelection *selection = gtk_tree_view_get_selection(tree_view);
+ GtkTreeModel *model = gtk_tree_view_get_model(tree_view);
+ GtkTreeIter iter;
+
+ gtk_tree_selection_unselect_all(selection);
+ gtk_tree_selection_select_path(selection, path);
+ gtk_tree_view_set_cursor(GTK_TREE_VIEW(tree_view), path, NULL, FALSE);
+ if (gtk_tree_model_get_iter(model, &iter, path)) {
+ popup_menu_real(GTK_WIDGET(tree_view), event);
+ }
+ gtk_tree_path_free(path);
+ }
+ }
+}
+
+
+/* autocrypt database dialogue context menu */
static void
-row_activated_cb(GtkTreeView *tree_view,
- GtkTreePath *path,
- GtkTreeViewColumn *column,
- gpointer user_data)
+popup_menu_real(GtkWidget *widget, const GdkEvent *event)
+{
+ GtkWidget *popup_menu;
+ GtkWidget* menu_item;
+
+ popup_menu = gtk_menu_new();
+ menu_item = gtk_menu_item_new_with_mnemonic(_("_Show details…"));
+ g_signal_connect(menu_item, "activate", G_CALLBACK(show_key_details_cb), widget);
+ gtk_menu_shell_append(GTK_MENU_SHELL(popup_menu), menu_item);
+ menu_item = gtk_menu_item_new_with_mnemonic(_("_Delete"));
+ g_signal_connect(menu_item, "activate", G_CALLBACK(remove_key_cb), widget);
+ gtk_menu_shell_append(GTK_MENU_SHELL(popup_menu), menu_item);
+ gtk_widget_show_all(popup_menu);
+ if (event != NULL) {
+ gtk_menu_popup_at_pointer(GTK_MENU(popup_menu), event);
+ } else {
+ gtk_menu_popup_at_widget(GTK_MENU(popup_menu), widget, GDK_GRAVITY_CENTER, GDK_GRAVITY_CENTER, NULL);
+ }
+}
+
+
+/* key context menu callback: show key details */
+static void
+show_key_details_cb(GtkMenuItem G_GNUC_UNUSED *menuitem, gpointer user_data)
{
GtkTreeModel *model;
+ GtkTreeSelection *selection;
GtkTreeIter iter;
- /* note: silently ignore all errors below... */
- model = gtk_tree_view_get_model(tree_view);
- if (gtk_tree_model_get_iter(model, &iter, path)) {
+ selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(user_data));
+ if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
gpgme_ctx_t ctx;
ctx = libbalsa_gpgme_new_with_proto(GPGME_PROTOCOL_OpenPGP, NULL, NULL, NULL);
@@ -793,30 +900,78 @@ row_activated_cb(GtkTreeView *tree_view,
if (libbalsa_mktempdir(&temp_dir)) {
GBytes *key;
+ gchar *mail_addr;
GList *keys = NULL;
gboolean success;
- gtk_tree_model_get(model, &iter, AC_KEY_PTR_COLUMN, &key, -1);
+ gtk_tree_model_get(model, &iter, AC_KEY_PTR_COLUMN, &key, AC_ADDRESS_COLUMN,
&mail_addr, -1);
success = libbalsa_gpgme_ctx_set_home(ctx, temp_dir, NULL) &&
libbalsa_gpgme_import_bin_key(ctx, key, NULL, NULL) &&
libbalsa_gpgme_list_keys(ctx, &keys, NULL, NULL, FALSE, FALSE, TRUE,
NULL);
- if (success && (keys != NULL)) {
- GtkWindow *window = user_data;
+ if (success) {
+ GtkWidget *toplevel;
+ GtkWindow *window;
GtkWidget *dialog;
- dialog = libbalsa_key_dialog(window, GTK_BUTTONS_CLOSE, (gpgme_key_t)
keys->data, GPG_SUBKEY_CAP_ALL,
- NULL, NULL);
+ toplevel = gtk_widget_get_toplevel(GTK_WIDGET(user_data));
+ window = GTK_IS_WINDOW(toplevel) ? GTK_WINDOW(toplevel) : NULL;
+ if (keys != NULL) {
+ dialog = libbalsa_key_dialog(window, GTK_BUTTONS_CLOSE, (gpgme_key_t)
keys->data, GPG_SUBKEY_CAP_ALL,
+ NULL, NULL);
+ } else {
+ dialog = gtk_message_dialog_new(window,
GTK_DIALOG_DESTROY_WITH_PARENT | libbalsa_dialog_flags(),
+ GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, _("The database entry
for “%s” does not contain a key."),
+ mail_addr);
+ }
(void) gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
g_list_free_full(keys, (GDestroyNotify) gpgme_key_release);
}
libbalsa_delete_directory_contents(temp_dir);
g_rmdir(temp_dir);
+ g_free(mail_addr);
}
gpgme_release(ctx);
}
- }
+ }
}
+
+/* key context menu callback: remove key from database */
+static void
+remove_key_cb(GtkMenuItem G_GNUC_UNUSED *menuitem, gpointer user_data)
+{
+ GtkTreeModel *model;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+
+ selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(user_data));
+ if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
+ GtkWidget *toplevel;
+ GtkWindow *window;
+ GtkWidget *dialog;
+ gchar *mail_addr;
+
+ toplevel = gtk_widget_get_toplevel(GTK_WIDGET(user_data));
+ window = GTK_IS_WINDOW(toplevel) ? GTK_WINDOW(toplevel) : NULL;
+ gtk_tree_model_get(model, &iter, AC_ADDRESS_COLUMN, &mail_addr, -1);
+ dialog = gtk_message_dialog_new(window, GTK_DIALOG_DESTROY_WITH_PARENT |
libbalsa_dialog_flags(),
+ GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, _("Delete the Autocrypt key for “%s” from
the database?"), mail_addr);
+ if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_YES) {
+ if ((sqlite3_bind_text(query[6], 1, mail_addr, -1, SQLITE_STATIC) != SQLITE_OK) ||
+ (sqlite3_step(query[6]) != SQLITE_DONE)) {
+ g_warning("deleting database entry for \"%s\" failed: %s", mail_addr,
sqlite3_errmsg(autocrypt_db));
+ } else {
+ g_debug("deleted database entry for \"%s\"", mail_addr);
+ }
+ sqlite3_reset(query[6]);
+ gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
+ }
+ gtk_widget_destroy(dialog);
+ g_free(mail_addr);
+ }
+}
+
+
#endif /* ENABLE_AUTOCRYPT */
diff --git a/src/balsa-mime-widget-crypto.c b/src/balsa-mime-widget-crypto.c
index ce11f0992..71878363a 100644
--- a/src/balsa-mime-widget-crypto.c
+++ b/src/balsa-mime-widget-crypto.c
@@ -143,6 +143,10 @@ balsa_mime_widget_signature_widget(LibBalsaMessageBody * mime_body,
g_object_set_data_full(G_OBJECT(button), "autocrypt_key", autocrypt_key,
(GDestroyNotify) g_bytes_unref);
g_signal_connect(button, "clicked", G_CALLBACK(on_key_import_button), NULL);
gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
+ } else if (libbalsa_message_get_headers(mime_body->message)->autocrypt_hdr != NULL) {
+ libbalsa_information(LIBBALSA_INFORMATION_WARNING,
+ _("The message contains an Autocrypt header, but it is either broken "
+ "or the signature has been created using a different key."));
}
#endif
button = gtk_button_new_with_mnemonic(_("_Search key server for this key"));
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]