/* * Yorkie - A task-oriented GnuPG frontend * Copyright (C) 2020 Damien Goutte-Gattat * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifdef HAVE_CONFIG_H #include #endif #include "encrypt.h" #include #include #include #include "error.h" #include "gpgutil.h" #include "util.h" /* Context structure and associated functions. */ typedef struct { gpgme_ctx_t gpgme; GtkListStore *public_keys; unsigned selected_keys; gboolean symmetric; gboolean sign; gboolean detached; gboolean armored; const char *original_filename; char *output_filename; char *output_basename; gboolean output_filename_userset; GtkWidget *ok_button; GtkWidget *detached_button; GtkWidget *output_button; } yki_encrypt_data_t; static void set_default_output_filename(yki_encrypt_data_t *yki, const char *original) { if ( yki->output_filename_userset ) return; if ( original ) yki->original_filename = original; g_free(yki->output_filename); g_free(yki->output_basename); yki->output_filename = g_strdup_printf("%s.%s", yki->original_filename, yki->armored ? "asc" : "gpg"); yki->output_basename = g_path_get_basename(yki->output_filename); gtk_button_set_label(GTK_BUTTON(yki->output_button), yki->output_basename); } /* GUI stuff. */ static void update_buttons(yki_encrypt_data_t *yki) { gtk_widget_set_sensitive(yki->ok_button, yki->symmetric || yki->selected_keys || yki->sign); gtk_widget_set_sensitive(yki->detached_button, yki->sign && ! ( yki->symmetric || yki->selected_keys)); } static void symmetric_encrypt_toggled(GtkWidget *widget, gpointer data) { ((yki_encrypt_data_t *)data)->symmetric = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); update_buttons((yki_encrypt_data_t *)data); } static void sign_toggled(GtkWidget *widget, gpointer data) { ((yki_encrypt_data_t *)data)->sign = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); update_buttons((yki_encrypt_data_t *)data); } static void detach_sign_toggled(GtkWidget *widget, gpointer data) { ((yki_encrypt_data_t *)data)->detached = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); } static void armored_toggled(GtkWidget *widget, gpointer data) { ((yki_encrypt_data_t *)data)->armored = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); set_default_output_filename((yki_encrypt_data_t *)data, NULL); } static void public_key_toggled(GtkCellRendererToggle *renderer, gchar *path, gpointer data) { yki_encrypt_data_t *yki = (yki_encrypt_data_t *)data; GtkTreeIter iter; gboolean selected; gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(yki->public_keys), &iter, path); gtk_tree_model_get(GTK_TREE_MODEL(yki->public_keys), &iter, 0, &selected, -1); selected = !selected; yki->selected_keys += selected ? 1 : -1; gtk_list_store_set(GTK_LIST_STORE(yki->public_keys), &iter, 0, selected, -1); update_buttons(yki); } static void output_clicked(GtkButton *button, gpointer data) { yki_encrypt_data_t *yki = (yki_encrypt_data_t *)data; GtkWidget *dlg; dlg = gtk_file_chooser_dialog_new("Save as", NULL, GTK_FILE_CHOOSER_ACTION_SAVE, "_Cancel", GTK_RESPONSE_CANCEL, "_OK", GTK_RESPONSE_ACCEPT, NULL); gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dlg), yki->output_filename); gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dlg), yki->output_basename); gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dlg), TRUE); if ( gtk_dialog_run(GTK_DIALOG(dlg)) == GTK_RESPONSE_ACCEPT ) { g_free(yki->output_filename); g_free(yki->output_basename); yki->output_filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dlg)); yki->output_filename_userset = TRUE; yki->output_basename = g_path_get_basename(yki->output_filename); gtk_button_set_label(GTK_BUTTON(yki->output_button), yki->output_basename); } gtk_widget_destroy(dlg); } static gboolean public_key_search(GtkTreeModel *model, gint column, const gchar *pattern, GtkTreeIter *iter, gpointer data) { gchar *uid; gtk_tree_model_get(model, iter, 1, &uid, -1); return g_strstr_len(uid, -1, pattern) ? FALSE : TRUE; } static GtkDialog * create_encrypt_dialog(yki_encrypt_data_t *yki) { GtkBuilder *builder; GObject *widget; GtkCellRenderer *renderer; GtkTreeViewColumn *column; builder = gtk_builder_new_from_resource("/org/incenp/Yorkie/yki-encrypt.ui"); g_signal_connect(gtk_builder_get_object(builder, "chkSymEncrypt"), "clicked", G_CALLBACK(symmetric_encrypt_toggled), yki); g_signal_connect(gtk_builder_get_object(builder, "chkSign"), "clicked", G_CALLBACK(sign_toggled), yki); g_signal_connect(gtk_builder_get_object(builder, "chkDetachSign"), "clicked", G_CALLBACK(detach_sign_toggled), yki); g_signal_connect(gtk_builder_get_object(builder, "chkArmor"), "clicked", G_CALLBACK(armored_toggled), yki); g_signal_connect(gtk_builder_get_object(builder, "btnOutput"), "clicked", G_CALLBACK(output_clicked), yki); widget = gtk_builder_get_object(builder, "trvPublicKeys"); yki->public_keys = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(widget))); gtk_tree_view_set_search_column(GTK_TREE_VIEW(widget), 1); gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(widget), public_key_search, NULL, NULL); renderer = gtk_cell_renderer_toggle_new(); column = gtk_tree_view_column_new_with_attributes("Selected", renderer, "active", 0, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(widget), column); g_signal_connect(renderer, "toggled", G_CALLBACK(public_key_toggled), yki); renderer = gtk_cell_renderer_text_new(); column = gtk_tree_view_column_new_with_attributes("User ID", renderer, "text", 1, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(widget), column); yki->ok_button = GTK_WIDGET(gtk_builder_get_object(builder, "btnOK")); yki->detached_button = GTK_WIDGET(gtk_builder_get_object(builder, "chkDetachSign")); yki->output_button = GTK_WIDGET(gtk_builder_get_object(builder, "btnOutput")); gtk_button_set_always_show_image(GTK_BUTTON(yki->output_button), TRUE); update_buttons(yki); return GTK_DIALOG(gtk_builder_get_object(builder, "dlgEncrypt")); } /* Dealing with public keys. */ static int populate_public_key_list(gpgme_ctx_t ctx, GtkListStore *key_list, GError **error) { GtkTreeIter iter; gpgme_key_t key; gpgme_error_t gerr; gerr = gpgme_op_keylist_start(ctx, NULL, 0); while ( ! gerr ) { gerr = gpgme_op_keylist_next(ctx, &key); if ( ! gerr ) { if ( key->revoked || key->expired || key->disabled || key->invalid || ! key->can_encrypt ) continue; gtk_list_store_append(key_list, &iter); gtk_list_store_set(key_list, &iter, 0, FALSE, /* Selected key? */ 1, key->uids[0].uid, /* User ID */ 2, key, /* The key itself */ -1); } } if ( gpgme_err_code(gerr) == GPG_ERR_EOF ) gerr = 0; /* Normal end of list, not an error condition */ if ( gerr ) yki_error_gpgme(error, YKI_ERROR_GPGME_KEYLIST, gerr); return gpgme_err_code(gerr) == 0; } static gboolean free_key_in_list(GtkTreeModel *store, GtkTreePath *path, GtkTreeIter *iter, gpointer data) { gpgme_key_t key; gtk_tree_model_get(store, iter, 2, &key, -1); gpgme_key_release(key); return TRUE; } static gpgme_key_t * get_selected_keys(yki_encrypt_data_t *yki) { gpgme_key_t *keys; GtkTreeIter iter; gboolean loop; unsigned i; keys = g_malloc((yki->selected_keys + 1) * sizeof(gpgme_key_t)); loop = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(yki->public_keys), &iter); i = 0; while ( loop ) { gboolean selected; gpointer key; gtk_tree_model_get(GTK_TREE_MODEL(yki->public_keys), &iter, 0, &selected, 2, &key, -1); if ( selected ) keys[i++] = (gpgme_key_t)key; loop = gtk_tree_model_iter_next(GTK_TREE_MODEL(yki->public_keys), &iter); } keys[i] = NULL; return keys; } /* Actual encryption or sign operation. */ #define yki_can_encrypt(yki) ((yki).selected_keys || (yki).symmetric || (yki).sign) static int do_encrypt(yki_encrypt_data_t *yki, gpgme_data_t in, gpgme_data_t out, GError **error) { gpgme_error_t gerr = 0; if ( yki->symmetric || yki->selected_keys ) { gpgme_key_t *keys = get_selected_keys(yki); gpgme_encrypt_flags_t flags = yki->symmetric ? GPGME_ENCRYPT_SYMMETRIC : 0; if ( yki->sign ) gerr = gpgme_op_encrypt_sign(yki->gpgme, keys, flags, in, out); else gerr = gpgme_op_encrypt(yki->gpgme, keys, flags, in, out); g_free(keys); } else if ( yki->sign ) { gpgme_sig_mode_t mode = yki->detached ? GPGME_SIG_MODE_DETACH : GPGME_SIG_MODE_NORMAL; gerr = gpgme_op_sign(yki->gpgme, in, out, mode); } else g_assert_not_reached(); if ( gerr ) yki_error_gpgme(error, YKI_ERROR_GPGME_CRYPTO, gerr); return gpgme_err_code(gerr) == 0; } /* Public interface. */ int yki_encrypt(gpgme_ctx_t gpgme, const char *file, GError **error) { yki_encrypt_data_t yki = { 0 }; gpgutil_file_t in = { 0 }, out = { 0 }; GtkDialog *dlg; int ret; g_assert(gpgme && file); yki.gpgme = gpgme; dlg = create_encrypt_dialog(&yki); ret = populate_public_key_list(gpgme, yki.public_keys, error); set_default_output_filename(&yki, file); if ( ret && gtk_dialog_run(dlg) == GTK_RESPONSE_OK && yki_can_encrypt(yki) ) { gpgme_set_armor(gpgme, yki.armored); /* Check with user before overwriting a file with the default filename. */ if ( g_file_test(yki.output_filename, G_FILE_TEST_EXISTS) && ! yki.output_filename_userset ) ret = yki_confirm_overwrite(yki.output_filename); if ( ret ) ret = gpgutil_open_file(file, "r", &in, error); if ( ret ) ret = gpgutil_open_file(yki.output_filename, "w", &out, error); if ( ret ) ret = do_encrypt(&yki, in.buffer, out.buffer, error); } gpgutil_close_file(&in, NULL); gpgutil_close_file(&out, NULL); gtk_tree_model_foreach(GTK_TREE_MODEL(yki.public_keys), free_key_in_list, NULL); gtk_widget_destroy(GTK_WIDGET(dlg)); g_free(yki.output_filename); g_free(yki.output_basename); return ret; }