A task-oriented GnuPG frontend
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

407 lines
12 KiB

/*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "encrypt.h"
#include <glib.h>
#include <gtk/gtk.h>
#include <gpgme.h>
#include "error.h"
#include "gpgutil.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"));
}
static int
confirm_overwrite(GtkWindow *parent, yki_encrypt_data_t *yki)
{
GtkWidget *dlg;
int ret;
dlg = gtk_message_dialog_new(parent, GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
"A file name \"%s\" already exists. "
"Do you want to replace it?", yki->output_basename);
gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dlg),
"Replacing it will overwrite its contents.");
gtk_dialog_add_buttons(GTK_DIALOG(dlg),
"Cancel", GTK_RESPONSE_CANCEL,
"Replace", GTK_RESPONSE_OK,
NULL);
ret = gtk_dialog_run(GTK_DIALOG(dlg)) == GTK_RESPONSE_OK;
gtk_widget_destroy(dlg);
return ret;
}
/* 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 = confirm_overwrite(GTK_WINDOW(dlg), &yki);
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;
}