diff --git a/src/confdialog.c b/src/confdialog.c index 35ac05e..7f83165 100644 --- a/src/confdialog.c +++ b/src/confdialog.c @@ -1,1626 +1,1626 @@ /* confdialog.c - GPGME based configuration dialog for GPA. Copyright (C) 2007, 2008 g10 Code GmbH This file is part of GPA. GPA 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. GPA 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 #include #include #include #include #include #include #include #include "i18n.h" #include "gpgmetools.h" #include "gtktools.h" #include "options.h" #include "gpa.h" /* Violation of GNOME standards: Cancel does not revert previous apply. We do not auto-apply or syntax check after focus change. */ /* Internal public interface. */ static void hide_backend_config (void); /* Some global variables. */ /* Custom response IDs. */ #define CUSTOM_RESPONSE_RESET 1 /* The configuration dialog. */ static GtkWidget *dialog; /* The notebook with one page for each component. */ static gpgme_ctx_t dialog_ctx; /* The notebook with one page for each component. */ static GtkWidget *dialog_notebook; /* The current configuration. */ static gpgme_conf_comp_t dialog_conf; /* If we modified something in the current tab. */ static int dialog_tab_modified; /* The expert level. */ static gpgme_conf_level_t dialog_level; /* We define the following behaviour for options: An option of alt-type NONE gets a check button, and if it has the LIST flag set, also a spin button for a repeat count. The spin button is only active if the button is checked. An option of a different alt-type does not get a check box button but a combo box and a text entry field. If the option does have a default or default description, the combo box contains an entry for "Use default value", which sets the text entry field to that default (and makes it insensitive?). Otherwise the combo box contains an entry for "Option not active" which makes the entry field insensitive. If the option has the OPTIONAl flag set, the combo box contains an entry for "Use default argument" which sets the text entry field to the no arg value or description, if any (and makes it insensitive?). In any case, the combo box contains an entry for "Use custom value" which makes the text entry field sensitive and editable. Options with the LIST flag set could get a more sophisticated dialog where you have "Add" and "Remove" buttons, or a multi-line entry field. Currently, having the LIST flag and OPTIONAL flag at the same time creates an ambiguity, and entering commas in values is not supported. */ typedef enum { OPTION_SIMPLE, OPTION_SPIN, OPTION_ENTRY, OPTION_OPT_ENTRY } option_type_t; typedef enum { COMBO_UNDEF = -1, COMBO_DEFAULT = 0, COMBO_CUSTOM = 1, COMBO_NO_ARG = 2 } option_combo_t; /* This structure combines an option with its GUI elements. */ typedef struct option_widget_s { /* The option. */ gpgme_conf_opt_t option; /* We remember the type of the option to make life easier on us. */ option_type_t type; /* The check button (or, in the case of COMBO being not NULL, the label) widget. */ GtkWidget *check; /* The entry or spin button widget. */ GtkWidget *widget; /* The current user setting in string representation. This defaults to the last input by the user, followed by the current setting, followed by the default. */ char *saved_value; /* This helps to know about state changes. */ int old_combo_box_state; /* The combo box, if any. Only used for OPTION_ENTRY and OPTION_OPT_ENTRY. The combo has COMBO_DEFAULT = 0: Use default value or Do not use option. COMBO_CUSTOM = 1: Use custom value COMBO_NO_ARG = 2: Use default argument (for OPTION_OPT_ENTRY). */ GtkWidget *combo; } *option_widget_t; #if 0 /* Not needed anymore, as all stock actions work on all tabs. However, might come in handy at some point. */ static gpgme_conf_comp_t dialog_current_comp (void) { gpgme_conf_comp_t comp = dialog_conf; int page = gtk_notebook_get_current_page (GTK_NOTEBOOK (dialog_notebook)); const char *label; /* Iterate over all options in the current tab, and reset them. */ label = gtk_notebook_get_tab_label_text (GTK_NOTEBOOK (dialog_notebook), gtk_notebook_get_nth_page (GTK_NOTEBOOK (dialog_notebook), page)); while (comp) { if (! strcmp (label, comp->description)) break; comp = comp->next; } assert (comp); return comp; } #endif /* Convert an argument value to a string representation. Lists are converted to comma-separated values, empty strings in lists are surrounded by double-quotes. */ static char * arg_to_str (gpgme_conf_arg_t arg, gpgme_conf_type_t type) { static char *result; char *new_result = NULL; if (result) { g_free (result); result = NULL; } while (arg) { if (result) { new_result = g_strdup_printf ("%s,", result); g_free (result); result = new_result; } else result = g_strdup (""); if (!arg->no_arg) { switch (type) { case GPGME_CONF_NONE: case GPGME_CONF_UINT32: new_result = g_strdup_printf ("%s%u", result, arg->value.uint32); g_free (result); result = new_result; break; case GPGME_CONF_INT32: new_result = g_strdup_printf ("%s%i", result, arg->value.int32); g_free (result); result = new_result; break; case GPGME_CONF_STRING: case GPGME_CONF_PATHNAME: case GPGME_CONF_LDAP_SERVER: new_result = g_strdup_printf ("%s%s", result, arg->value.string); g_free (result); result = new_result; break; default: assert (!"Not supported."); break; } } arg = arg->next; } return result; } /* Extract the argument from a widget. */ static gpgme_conf_arg_t option_widget_to_arg (option_widget_t opt) { gpgme_error_t err; gpgme_conf_opt_t option = opt->option; gpgme_conf_arg_t arg = NULL; if (opt->type == OPTION_SIMPLE || opt->type == OPTION_SPIN) { int active; unsigned int count = 1; active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (opt->check)); if (!active) return NULL; if (opt->type == OPTION_SPIN) count = gtk_spin_button_get_value (GTK_SPIN_BUTTON (opt->widget)); err = gpgme_conf_arg_new (&arg, option->alt_type, &count); if (err) gpa_gpgme_error (err); return arg; } else { int combo; gpgme_conf_arg_t *argp; char *val; char *valp; combo = gtk_combo_box_get_active (GTK_COMBO_BOX (opt->combo)); if (combo == COMBO_DEFAULT) return NULL; if (combo == COMBO_NO_ARG) { /* A single no-arg option. */ err = gpgme_conf_arg_new (&arg, option->alt_type, NULL); if (err) gpa_gpgme_error (err); return arg; } argp = &arg; val = g_strdup (gtk_entry_get_text (GTK_ENTRY (opt->widget))); valp = val; do { while (*valp == ' ' || *valp == '\t') valp++; if (*valp == ',' || *valp == '\0') { if (option->flags & GPGME_CONF_OPTIONAL) err = gpgme_conf_arg_new (argp, option->alt_type, NULL); else /* This seems a useful default to make things simpler for the user, as the OPTIONAL flag is not much used. */ err = gpgme_conf_arg_new (argp, option->alt_type, ""); if (err) gpa_gpgme_error (err); argp = &(*argp)->next; /* Skip comma. */ if (*valp) valp++; } else { char *str = valp; char *strend = valp; int done = 0; int in_quote = 0; /* Remove quoting. */ do { if (in_quote) { if (*valp == '\0') done = 1; else if (*valp == '"') { in_quote = 0; valp++; } else *(strend++) = *(valp++); } else { if (*valp == ',' || *valp == '\0') done = 1; else if (*valp == '"') { in_quote = 1; valp++; } else *(strend++) = *(valp++); } } while (! done); if (*valp == ',') valp++; /* Find end of the string. */ while (strend > str && (*strend == ' ' || *strend == '\t')) strend--; *strend = '\0'; switch (option->alt_type) { case GPGME_CONF_NONE: case GPGME_CONF_UINT32: { unsigned int nr; nr = strtoul (str, NULL, 0); err = gpgme_conf_arg_new (argp, option->alt_type, &nr); if (err) gpa_gpgme_error (err); argp = &(*argp)->next; } break; case GPGME_CONF_INT32: { int nr; nr = strtoul (str, NULL, 0); err = gpgme_conf_arg_new (argp, option->alt_type, &nr); if (err) gpa_gpgme_error (err); argp = &(*argp)->next; } break; case GPGME_CONF_STRING: case GPGME_CONF_LDAP_SERVER: case GPGME_CONF_PATHNAME: { err = gpgme_conf_arg_new (argp, option->alt_type, str); if (err) gpa_gpgme_error (err); argp = &(*argp)->next; } break; default: assert (!"Not supported."); break; } } } while (*valp); g_free (val); return arg; } } /* Compare two arguments and returns true if they are equal. */ static int args_are_equal (gpgme_conf_arg_t arg1, gpgme_conf_arg_t arg2, gpgme_conf_type_t type) { while (arg1 && arg2) { if ((!arg1->no_arg ^ !arg2->no_arg)) return 0; if (!arg1->no_arg) { switch (type) { case GPGME_CONF_NONE: case GPGME_CONF_UINT32: if (arg1->value.uint32 != arg2->value.uint32) return 0; break; case GPGME_CONF_INT32: if (arg1->value.int32 != arg2->value.int32) return 0; break; case GPGME_CONF_STRING: case GPGME_CONF_LDAP_SERVER: case GPGME_CONF_PATHNAME: if (strcmp (arg1->value.string, arg2->value.string)) return 0; break; default: assert (!"Not supported."); break; } } arg1 = arg1->next; arg2 = arg2->next; } if (arg1 || arg2) return 0; return 1; } /* Commit all the changes in component COMP. */ static void save_options (gpgme_conf_comp_t comp) { gpgme_conf_opt_t option = comp->options; gpgme_error_t err; int changed; changed = 0; while (option) { option_widget_t opt = option->user_data; gpgme_conf_arg_t arg; /* Exclude group headers etc. */ if (!opt) { option = option->next; continue; } arg = option_widget_to_arg (opt); if (! args_are_equal (arg, option->value, option->alt_type)) { err = gpgme_conf_opt_change (option, 0, arg); if (err) gpa_gpgme_error (err); changed++; /* FIXME: Disable this. */ g_debug ("Changing component %s, option %s", comp->name, option->name); } option = option->next; } if (changed) { err = gpgme_op_conf_save (dialog_ctx, comp); if (err) gpa_gpgme_warning (err); } } static void save_all_options (void) { gpgme_conf_comp_t comp; /* Save all tabs. */ comp = dialog_conf; while (comp) { save_options (comp); comp = comp->next; } } /* Update user-changes to the option widget OPT. */ static void update_option (option_widget_t opt) { gpgme_conf_opt_t option; option = opt->option; if (opt->type == OPTION_SPIN) { int active; active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (opt->check)); gtk_widget_set_sensitive (opt->widget, active); } else if (opt->type == OPTION_ENTRY || opt->type == OPTION_OPT_ENTRY) { int combo; GtkWidget *entry = opt->widget; combo = gtk_combo_box_get_active (GTK_COMBO_BOX (opt->combo)); if (opt->old_combo_box_state == COMBO_UNDEF) { /* Initialization. */ if (option->value) { /* A single no-arg argument can be represented directly in the combo box. Otherwise, we use a custom argument and comma-separated lists. */ if (option->value->no_arg && !option->value->next) combo = COMBO_NO_ARG; else combo = COMBO_CUSTOM; } else combo = COMBO_DEFAULT; gtk_combo_box_set_active (GTK_COMBO_BOX (opt->combo), combo); } else if (combo == opt->old_combo_box_state) return; else if (opt->old_combo_box_state == COMBO_CUSTOM) { if (opt->saved_value) g_free (opt->saved_value); opt->saved_value = g_strdup (gtk_entry_get_text (GTK_ENTRY (opt->widget))); } if (combo == COMBO_DEFAULT) { if (option->default_value) gtk_entry_set_text (GTK_ENTRY (entry), arg_to_str (option->default_value, option->alt_type)); else if (option->default_description) gtk_entry_set_text (GTK_ENTRY (entry), option->default_description); else gtk_entry_set_text (GTK_ENTRY (entry), ""); gtk_editable_set_editable (GTK_EDITABLE (entry), FALSE); gtk_widget_set_sensitive (entry, FALSE); } else if (combo == COMBO_NO_ARG) { if (option->no_arg_value) gtk_entry_set_text (GTK_ENTRY (entry), arg_to_str (option->no_arg_value, option->alt_type)); else if (option->no_arg_description) gtk_entry_set_text (GTK_ENTRY (entry), option->no_arg_description); else gtk_entry_set_text (GTK_ENTRY (entry), ""); gtk_editable_set_editable (GTK_EDITABLE (entry), FALSE); gtk_widget_set_sensitive (entry, FALSE); /* FIXME: Change focus. */ } else if (combo == COMBO_CUSTOM) { if (opt->saved_value) gtk_entry_set_text (GTK_ENTRY (entry), opt->saved_value); else if (option->value) gtk_entry_set_text (GTK_ENTRY (entry), arg_to_str (option->value, option->alt_type)); else if (option->default_value) { gtk_entry_set_text (GTK_ENTRY (entry), arg_to_str (option->default_value, option->alt_type)); /* A default (rather than current) value is selected initially. */ gtk_editable_select_region (GTK_EDITABLE (entry), 0, -1); } else gtk_entry_set_text (GTK_ENTRY (entry), ""); gtk_editable_set_editable (GTK_EDITABLE (entry), TRUE); gtk_widget_set_sensitive (entry, TRUE); /* FIXME: Change focus. */ } opt->old_combo_box_state = combo; } } /* Update the tab when it is modified (if modified is true) or reset it (else). */ static void update_modified (int modified) { if (modified) { /* Run with every button click, so keep it short. */ if (!dialog_tab_modified) { dialog_tab_modified = 1; gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_APPLY, TRUE); gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), CUSTOM_RESPONSE_RESET, TRUE); } } else { /* This is also called at initialization time. */ dialog_tab_modified = 0; gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_APPLY, FALSE); gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), CUSTOM_RESPONSE_RESET, FALSE); } } static void update_modified_cb (void *dummy) { update_modified (1); } /* Update a toggled checkbox button. */ static void option_checkbutton_toggled (GtkToggleButton *button, gpointer data) { option_widget_t opt = data; update_option (opt); update_modified (1); } /* Update a toggled combo box. */ static void option_combobox_changed (GtkComboBox *combo, gpointer data) { option_widget_t opt = data; update_option (opt); update_modified (1); } static void create_dialog_tabs (void); /* Handle stock response "apply". */ static void dialog_response_apply (GtkDialog *dummy) { save_all_options (); /* Reload configuration. */ create_dialog_tabs (); } /* Soft reset which reuses the configuration at last load. */ static void reset_options (gpgme_conf_comp_t comp) { gpgme_conf_opt_t option; option = comp->options; while (option) { option_widget_t opt = option->user_data; if (!opt) { /* Exclude group headers etc. */ option = option->next; continue; } if (opt->type == OPTION_SIMPLE || opt->type == OPTION_SPIN) { gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (opt->check), !!option->value); } else if (opt->type == OPTION_ENTRY || opt->type == OPTION_OPT_ENTRY) { /* Force re-initialization. */ opt->old_combo_box_state = COMBO_UNDEF; update_option (opt); } option = option->next; } } static void reset_all_options (void) { gpgme_conf_comp_t comp; /* Save all tabs. */ comp = dialog_conf; while (comp) { reset_options (comp); comp = comp->next; } } /* Handle stock response "reset". */ static void dialog_response_reset (GtkDialog *dummy) { #if 0 /* Allow to use the reset button also as a refresh button. Note: This is kind of awkward, because the reset button is only sensitive if local modifications exist. Currently, we require a dialog cancel/reopen cycle or an expert level change for a "hard" reset. */ create_dialog_tabs (); #else /* Soft reset, using last loaded configuration. Much faster, and consistent with sensitivity of Reset button. */ reset_all_options (); update_modified (0); #endif } /* Handle stock responses like OK, apply and cancel. */ static int dialog_response (GtkDialog *dlg, gint response, gpointer data) { switch (response) { case GTK_RESPONSE_ACCEPT: case GTK_RESPONSE_YES: save_all_options (); hide_backend_config (); break; case GTK_RESPONSE_CANCEL: case GTK_RESPONSE_CLOSE: case GTK_RESPONSE_REJECT: case GTK_RESPONSE_NO: case GTK_RESPONSE_DELETE_EVENT: hide_backend_config (); break; case GTK_RESPONSE_APPLY: dialog_response_apply (dlg); break; case CUSTOM_RESPONSE_RESET: dialog_response_reset (dlg); break; default: g_warning ("unhandled response: %i", response); /* Do whatever is the default. */ return FALSE; } /* Don't do anything. We handled it all. */ return TRUE; } /* Determine the width of a checkbox. We use this to align labels with and without checkboxes vertically. This approach is not proper, as Gtk+ can not automatically adjust the padding when the theme switches, but it is simple. */ static gint get_checkbox_width (void) { GtkWidget *checkbox; GtkRequisition checkbox_size; /* We do not save the result, as it may change with a theme change. In this case we will at least refresh eventually. */ checkbox = gtk_check_button_new (); gtk_widget_size_request (checkbox, &checkbox_size); gtk_widget_destroy (checkbox); return checkbox_size.width; } /* A callback to be used with gtk_container_foreach, which removes each widget unconditionally. */ static void remove_from_container (GtkWidget *widget, gpointer data) { GtkWidget *container = data; gtk_container_remove (GTK_CONTAINER (container), widget); } /* Return true iff the component COMP has any displayed options. */ static gboolean comp_has_options (gpgme_conf_comp_t comp) { gpgme_conf_opt_t option; gboolean has_options; /* Skip over all components that do not have any options. This can happen for example with old installed versions of components, or if there are only options with a higher expert level. */ has_options = FALSE; option = comp->options; while (option) { if (option->level <= dialog_level) { has_options = TRUE; break; } option = option->next; } return has_options; } /* Return true iff the group GROUP has any displayed options. If the result is FALSE, also set NEXT_GROUP to the start of the next group, or NULL if there is no more group. */ static gboolean group_has_options (gpgme_conf_opt_t option, gpgme_conf_opt_t *next_group) { gboolean has_options; /* Skip the group header. */ option = option->next; has_options = FALSE; while (option && ! (option->flags & GPGME_CONF_GROUP)) { if (option->level <= dialog_level) { has_options = TRUE; break; } option = option->next; } if (! has_options && next_group) *next_group = option; return has_options; } static void create_dialog_tabs_2 (gpgme_conf_comp_t old_conf, gpgme_conf_comp_t new_conf) { gpgme_conf_comp_t comp; gpgme_conf_comp_t comp_alt; int reset; int page_nr; gint checkbox_width = get_checkbox_width (); /* We keep size groups for the field elements across all tabs for visual consistency. */ GtkSizeGroup *size_group[2]; size_group[0] = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); size_group[1] = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); /* In most cases, the new components are the same as the old components. We catch this special case here, and if it is true, we reuse the existing tabs in the notebook below. Otherwise, we reset everything. */ reset = 0; comp = old_conf; comp_alt = new_conf; while (comp && comp_alt) { /* A mismatch is found if either the description changes, or if a component changes from no options to some options or vice verse (as we suppress generating tabs for components without options below). */ if (strcmp (comp->description, comp_alt->description) || comp_has_options (comp) != comp_has_options (comp_alt)) break; comp = comp->next; comp_alt = comp_alt->next; } if (comp || comp_alt) reset = 1; if (reset) { gtk_widget_hide (dialog_notebook); /* Remove the current tabs. */ gtk_container_foreach (GTK_CONTAINER (dialog_notebook), &remove_from_container, dialog_notebook); } comp = new_conf; page_nr = 0; while (comp) { GtkWidget *label; GtkWidget *page; gpgme_conf_opt_t option; /* For each group in the component, we keep track of a frame, and the table inside the frame. */ GtkWidget *frame = NULL; GtkWidget *frame_vbox = NULL; /* Skip over all components that do not have any options. This can happen for example with old installed versions of components, or if there are only options with a higher expert level. */ if (! comp_has_options (comp)) { comp = comp->next; continue; } if (reset) { char *description; /* FIXME: Might need to put pages into scrolled panes if there are too many. */ page = gtk_vbox_new (FALSE, 0); gtk_container_add (GTK_CONTAINER (dialog_notebook), page); description = xstrdup (comp->description); percent_unescape (description, 0); label = gtk_label_new (description); xfree (description); gtk_notebook_set_tab_label (GTK_NOTEBOOK (dialog_notebook), gtk_notebook_get_nth_page (GTK_NOTEBOOK (dialog_notebook), page_nr), label); } else { page = gtk_notebook_get_nth_page (GTK_NOTEBOOK (dialog_notebook), page_nr); gtk_container_foreach (GTK_CONTAINER (page), &remove_from_container, page); } option = comp->options; /* All ungrouped options come first. We put them in a frame titled "Main", just so we have a consistent layout. */ if (option && ! (option->flags & GPGME_CONF_GROUP)) { frame = gtk_frame_new (NULL); gtk_box_pack_start (GTK_BOX (page), frame, TRUE, TRUE, 0); gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_NONE); label = gtk_label_new (_("Main")); gtk_label_set_use_markup (GTK_LABEL (label), TRUE); gtk_frame_set_label_widget (GTK_FRAME (frame), label); frame_vbox = gtk_vbox_new (FALSE, 0); gtk_container_add (GTK_CONTAINER (frame), frame_vbox); } while (option) { if (option->flags & GPGME_CONF_GROUP) { char *name; const char *title; if (! group_has_options (option, &option)) continue; frame = gtk_frame_new (NULL); gtk_box_pack_start (GTK_BOX (page), frame, FALSE, FALSE, 0); gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_NONE); /* For i18n reasons we use the description. It might be better to add a new field to privide a localized version of the Group name. Maybe the argname can be used for it. AFAICS, we would only need to prefix the description with the group name and gpgconf would instantly privide that. */ title = (option->argname && *option->argname)? option->argname : option->description; name = g_strdup_printf ("%s", title); percent_unescape (name, 0); label = gtk_label_new (name); xfree (name); gtk_label_set_use_markup (GTK_LABEL (label), TRUE); gtk_frame_set_label_widget (GTK_FRAME (frame), label); frame_vbox = gtk_vbox_new (FALSE, 0); gtk_container_add (GTK_CONTAINER (frame), frame_vbox); } else if (option->level <= dialog_level) { GtkWidget *vbox; GtkWidget *hbox; option_widget_t opt; GtkWidget *widget; GtkWidget *entry; opt = g_new0 (struct option_widget_s, 1); opt->option = option; option->user_data = opt; /* Add the entry fields for the option, depending on its type and flags. */ if (option->alt_type == GPGME_CONF_NONE && !(option->flags & GPGME_CONF_LIST)) opt->type = OPTION_SIMPLE; else if (option->alt_type == GPGME_CONF_NONE && (option->flags & GPGME_CONF_LIST)) opt->type = OPTION_SPIN; else if (option->flags & GPGME_CONF_OPTIONAL && !(option->flags & GPGME_CONF_LIST)) opt->type = OPTION_OPT_ENTRY; else opt->type = OPTION_ENTRY; /* We create a vbox for the option, in case we want to support multi-hbox setups for options. Currently we only add one hbox to that vbox though. */ vbox = gtk_vbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (frame_vbox), vbox, TRUE, TRUE, 0); hbox = gtk_hbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0); /* Add the entry fields for the option, depending on its type and flags. */ if (opt->type == OPTION_SIMPLE) { widget = gtk_check_button_new_with_label (option->name); opt->check = widget; gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0); /* Add the checkbox label to the size group. */ gtk_size_group_add_widget (GTK_SIZE_GROUP (size_group[0]), widget); if (option->value) gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), TRUE); } else if (opt->type == OPTION_SPIN) { widget = gtk_check_button_new_with_label (option->name); opt->check = widget; gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0); /* Add the checkbox label to the size group. */ gtk_size_group_add_widget (GTK_SIZE_GROUP (size_group[0]), widget); if (option->value) gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), TRUE); /* For list options without arguments, we use a simple spin button to capture the repeat count. */ widget = gtk_spin_button_new_with_range (1, 100, 1); opt->widget = widget; if (option->value) gtk_spin_button_set_value (GTK_SPIN_BUTTON (widget), option->value->value.count); g_signal_connect ((gpointer) widget, "value-changed", G_CALLBACK (update_modified_cb), NULL); gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0); } else { GtkWidget *align; guint pt, pb, pl, pr; align = gtk_alignment_new (0, 0.5, 0, 0); gtk_alignment_get_padding (GTK_ALIGNMENT (align), &pt, &pb, &pl, &pr); gtk_alignment_set_padding (GTK_ALIGNMENT (align), pt, pb, pl + checkbox_width, pr); gtk_box_pack_start (GTK_BOX (hbox), align, FALSE, FALSE, 0); widget = gtk_label_new (option->name); opt->check = widget; gtk_container_add (GTK_CONTAINER (align), widget); /* Add the checkbox label to the size group. */ gtk_size_group_add_widget (GTK_SIZE_GROUP (size_group[0]), align); widget = gtk_combo_box_new_text (); gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0); gtk_size_group_add_widget (GTK_SIZE_GROUP (size_group[1]), widget); opt->combo = widget; entry = gtk_entry_new (); gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0); opt->widget = entry; g_signal_connect ((gpointer) entry, "changed", G_CALLBACK (update_modified_cb), NULL); if (option->flags & GPGME_CONF_DEFAULT || option->flags & GPGME_CONF_DEFAULT_DESC) gtk_combo_box_append_text (GTK_COMBO_BOX (widget), (option->flags & GPGME_CONF_LIST) ? _("Use default values") : _("Use default value")); else gtk_combo_box_append_text (GTK_COMBO_BOX (widget), _("Do not use option")); gtk_combo_box_append_text (GTK_COMBO_BOX (widget), (option->flags & GPGME_CONF_LIST) ? _("Use custom values") : _("Use custom value")); if (opt->type == OPTION_OPT_ENTRY) gtk_combo_box_append_text (GTK_COMBO_BOX (widget), _("Use default argument")); } /* Force update. */ opt->old_combo_box_state = COMBO_UNDEF; update_option (opt); if (opt->combo) g_signal_connect ((gpointer) opt->combo, "changed", G_CALLBACK (option_combobox_changed), opt); else g_signal_connect ((gpointer) opt->check, "toggled", G_CALLBACK (option_checkbutton_toggled), opt); /* Grey out options which can not be changed at all. */ if (option->flags & GPGME_CONF_NO_CHANGE) gtk_widget_set_sensitive (vbox, FALSE); #if GTK_CHECK_VERSION (2, 12, 0) /* Add a tooltip description. */ if (option->description) { char *description = xstrdup (option->description); percent_unescape (description, 0); gtk_widget_set_tooltip_text (vbox, description); xfree (description); } #endif } option = option->next; } page_nr++; comp = comp->next; } /* Release our references to the size groups. */ g_object_unref (size_group[0]); g_object_unref (size_group[1]); gtk_widget_show_all (dialog_notebook); update_modified (0); } static void create_dialog_tabs (void) { gpgme_error_t err; gpgme_conf_comp_t new_conf; int page; int nr_pages; char *current_tab = NULL; if (dialog_notebook) { /* Remember the current tab by its label. */ page = gtk_notebook_get_current_page (GTK_NOTEBOOK (dialog_notebook)); if (page >= 0) current_tab = strdup (gtk_notebook_get_tab_label_text (GTK_NOTEBOOK (dialog_notebook), gtk_notebook_get_nth_page (GTK_NOTEBOOK (dialog_notebook), page))); } err = gpgme_op_conf_load (dialog_ctx, &new_conf); if (err) { gpa_gpgme_warning (err); return; } create_dialog_tabs_2 (dialog_conf, new_conf); gpgme_conf_release (dialog_conf); dialog_conf = new_conf; if (current_tab) { /* Rediscover the current tab. */ page = 0; nr_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (dialog_notebook)); while (page < nr_pages) { const char *label = gtk_notebook_get_tab_label_text (GTK_NOTEBOOK (dialog_notebook), gtk_notebook_get_nth_page (GTK_NOTEBOOK (dialog_notebook), page)); if (! strcmp (label, current_tab)) break; page++; } if (page < nr_pages) gtk_notebook_set_current_page (GTK_NOTEBOOK (dialog_notebook), page); free (current_tab); } } static void dialog_level_chooser_cb (GtkComboBox *level_chooser, gpointer *data) { gpgme_conf_level_t level; level = gtk_combo_box_get_active (GTK_COMBO_BOX (level_chooser)); if (level == dialog_level) return; if (dialog_tab_modified) { GtkWidget *window; GtkWidget *hbox; GtkWidget *labelMessage; GtkWidget *pixmap; gint result; window = gtk_dialog_new_with_buttons (_("GPA Message"), (GtkWindow *) dialog, GTK_DIALOG_MODAL, GTK_STOCK_APPLY, GTK_RESPONSE_APPLY, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL); gtk_container_set_border_width (GTK_CONTAINER (window), 5); gtk_dialog_set_default_response (GTK_DIALOG (window), GTK_RESPONSE_CANCEL); hbox = gtk_hbox_new (FALSE, 0); gtk_container_set_border_width (GTK_CONTAINER (hbox), 5); gtk_box_pack_start_defaults (GTK_BOX (GTK_DIALOG (window)->vbox), hbox); pixmap = gtk_image_new_from_stock (GTK_STOCK_DIALOG_INFO, GTK_ICON_SIZE_DIALOG); gtk_box_pack_start (GTK_BOX (hbox), pixmap, TRUE, FALSE, 10); labelMessage = gtk_label_new (_("There are unapplied changes by you. " "Changing the expert setting will apply " "those changes. Do you want to " "continue?")); gtk_label_set_line_wrap (GTK_LABEL (labelMessage), TRUE); gtk_box_pack_start (GTK_BOX (hbox), labelMessage, TRUE, FALSE, 10); gtk_widget_show_all (window); result = gtk_dialog_run (GTK_DIALOG (window)); gtk_widget_destroy (window); if (result != GTK_RESPONSE_APPLY) { gtk_combo_box_set_active (GTK_COMBO_BOX (level_chooser), dialog_level); return; } } save_all_options (); /* Note: We know intimately that this matches GPGME_CONF_BASIC, GPGME_CONF_ADVANCED and GPGME_CONF_BEGINNER. */ dialog_level = level; create_dialog_tabs (); } /* Return a new dialog widget. */ static GtkDialog * create_dialog (void) { gpgme_error_t err; GtkWidget *dialog_vbox; GtkWidget *hbox; GtkWidget *level_chooser; GtkWidget *label; gint xpad; gint ypad; /* Check error. */ err = gpgme_new (&dialog_ctx); if (err) gpa_gpgme_error (err); dialog = gtk_dialog_new_with_buttons (_("Crypto Backend Configuration"), NULL /* transient parent */, 0, NULL); gtk_dialog_add_buttons (GTK_DIALOG (dialog), GTK_STOCK_APPLY, GTK_RESPONSE_APPLY, _("Reset"), CUSTOM_RESPONSE_RESET, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL ); gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT, GTK_RESPONSE_CANCEL, CUSTOM_RESPONSE_RESET, GTK_RESPONSE_APPLY, -1); g_signal_connect ((gpointer) dialog, "response", G_CALLBACK (dialog_response), NULL); dialog_vbox = GTK_DIALOG (dialog)->vbox; /* gtk_box_set_spacing (GTK_CONTAINER (dialog_vbox), 5); */ hbox = gtk_hbox_new (FALSE, 0); label = gtk_label_new (_("Configure the tools of the GnuPG system.")); gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0); gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5); label = gtk_label_new (_("Level:")); gtk_misc_get_padding (GTK_MISC (label), &xpad, &ypad); xpad += 5; gtk_misc_set_padding (GTK_MISC (label), xpad, ypad); gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); level_chooser = gtk_combo_box_new_text (); /* Note: We know intimately that this matches GPGME_CONF_BASIC, GPGME_CONF_ADVANCED and GPGME_CONF_BEGINNER. */ gtk_combo_box_append_text (GTK_COMBO_BOX (level_chooser), _("Basic")); gtk_combo_box_append_text (GTK_COMBO_BOX (level_chooser), _("Advanced")); gtk_combo_box_append_text (GTK_COMBO_BOX (level_chooser), _("Expert")); g_signal_connect ((gpointer) level_chooser, "changed", G_CALLBACK (dialog_level_chooser_cb), NULL); gtk_box_pack_start (GTK_BOX (hbox), level_chooser, FALSE, FALSE, 0); gtk_box_pack_start (GTK_BOX (dialog_vbox), hbox, FALSE, FALSE, 0); gtk_container_set_border_width (GTK_CONTAINER (hbox), 5); if (gpa_options_get_simplified_ui (gpa_options_get_instance ())) dialog_level = GPGME_CONF_BASIC; else dialog_level = GPGME_CONF_ADVANCED; gtk_combo_box_set_active (GTK_COMBO_BOX (level_chooser), dialog_level); dialog_notebook = gtk_notebook_new (); gtk_container_set_border_width (GTK_CONTAINER (dialog_notebook), 5); gtk_box_pack_start (GTK_BOX (dialog_vbox), dialog_notebook, TRUE, TRUE, 0); /* This should also be run on show after hide. */ dialog_tab_modified = 0; create_dialog_tabs (); return GTK_DIALOG (dialog); } GtkWidget * gpa_backend_config_dialog_new (void) { assert (! dialog); create_dialog (); return dialog; } static void hide_backend_config (void) { gtk_widget_destroy (dialog); dialog = NULL; dialog_notebook = NULL; gpgme_conf_release (dialog_conf); dialog_conf = NULL; gpgme_release (dialog_ctx); dialog_ctx = NULL; } /* Load the value of option NAME of component CNAME from the backend. If none is configured, return NULL. Caller must g_free the returned value. */ char * gpa_load_gpgconf_string (const char *cname, const char *name) { gpg_error_t err; gpgme_ctx_t ctx; gpgme_conf_comp_t conf_list, conf; gpgme_conf_opt_t opt; char *retval = NULL; err = gpgme_new (&ctx); if (err) { gpa_gpgme_error (err); return NULL; } err = gpgme_op_conf_load (ctx, &conf_list); if (err) { gpa_gpgme_warning (err); gpgme_release (ctx); return NULL; } for (conf = conf_list; conf; conf = conf->next) { if ( !strcmp (conf->name, cname) ) { for (opt = conf->options; opt; opt = opt->next) if ( !(opt->flags & GPGME_CONF_GROUP) && !strcmp (opt->name, name)) { if (opt->value && opt->alt_type == GPGME_CONF_STRING) retval = g_strdup (opt->value->value.string); break; } break; } } gpgme_conf_release (conf_list); gpgme_release (ctx); return retval; } /* Set the option NAME in component "CNAME" to VALUE. The option needs to be of type string. */ void gpa_store_gpgconf_string (const char *cname, const char *name, const char *value) { gpg_error_t err; gpgme_ctx_t ctx; gpgme_conf_comp_t conf_list, conf; gpgme_conf_opt_t opt; gpgme_conf_arg_t arg; err = gpgme_conf_arg_new (&arg, GPGME_CONF_STRING, (char*)value); if (err) { gpa_gpgme_warning (err); return; } err = gpgme_new (&ctx); if (err) { gpa_gpgme_error (err); return; } err = gpgme_op_conf_load (ctx, &conf_list); if (err) { gpa_gpgme_error (err); gpgme_release (ctx); return; } for (conf = conf_list; conf; conf = conf->next) { if ( !strcmp (conf->name, cname) ) { for (opt = conf->options; opt; opt = opt->next) if ( !(opt->flags & GPGME_CONF_GROUP) && !strcmp (opt->name, name)) { if (opt->alt_type == GPGME_CONF_STRING && !args_are_equal (arg, opt->value, opt->alt_type)) { err = gpgme_conf_opt_change (opt, 0, arg); if (err) gpa_gpgme_error (err); else { err = gpgme_op_conf_save (ctx, conf); if (err) gpa_gpgme_error (err); } } break; } break; } } gpgme_conf_release (conf_list); gpgme_release (ctx); } /* Read the configured keyserver from the backend. If none is configured, return NULL. Caller must g_free the returned value. */ char * gpa_load_configured_keyserver (void) { #ifdef ENABLE_KEYSERVER_SUPPORT return gpa_load_gpgconf_string ("gpg", "keyserver"); #else return NULL; #endif } /* Save the configured keyserver from the backend. If none is configured, return NULL. Caller must g_free the returned value. */ void gpa_store_configured_keyserver (const char *value) { #ifdef ENABLE_KEYSERVER_SUPPORT gpa_store_gpgconf_string ("gpg", "keyserver", value); #endif } /* Ask the user whether to configure GnuPG to use a keyserver. Return NULL if it could or shall not be configured or the name of the keyserver which needs to be g_freed. */ char * gpa_configure_keyserver (GtkWidget *parent) { #ifdef ENABLE_KEYSERVER_SUPPORT GtkWidget *msgbox; char *keyserver; msgbox = gtk_message_dialog_new (GTK_WINDOW(parent), GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_NONE, "%s\n\n%s", _("A keyserver has not been configured."), _("Configure backend to use a keyserver?")); gtk_dialog_add_buttons (GTK_DIALOG (msgbox), _("_Yes"), GTK_RESPONSE_YES, _("_No"), GTK_RESPONSE_NO, NULL); if (gtk_dialog_run (GTK_DIALOG (msgbox)) != GTK_RESPONSE_YES) { gtk_widget_destroy (msgbox); return NULL; } gtk_widget_destroy (msgbox); gpa_store_configured_keyserver ("hkp://keys.gnupg.net"); keyserver = gpa_load_configured_keyserver (); if (!keyserver) { - gpa_show_warning - (parent, _("Configuring the backend to use a keyserver failed")); + gpa_show_warn + (parent, NULL, _("Configuring the backend to use a keyserver failed")); return NULL; } return keyserver; #else (void)parent; return NULL #endif } diff --git a/src/gpacontext.c b/src/gpacontext.c index b049f84..72d532f 100644 --- a/src/gpacontext.c +++ b/src/gpacontext.c @@ -1,498 +1,540 @@ /* gpacontext.c - The GpaContext object. * Copyright (C) 2003 Miguel Coca. * * This file is part of GPA * * GPA 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 2 of the License, or * (at your option) any later version. * * GPA 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ #include #include #include #include "gpa.h" #include "gpgmetools.h" #include "gpacontext.h" /* GObject type functions */ static void gpa_context_init (GpaContext *context); static void gpa_context_class_init (GpaContextClass *klass); static void gpa_context_finalize (GObject *object); /* Default signal handlers */ static void gpa_context_start (GpaContext *context); static void gpa_context_done (GpaContext *context, gpg_error_t err); static void gpa_context_next_key (GpaContext *context, gpgme_key_t key); static void gpa_context_next_trust_item (GpaContext *context, gpgme_trust_item_t item); static void gpa_context_progress (GpaContext *context, int current, int total); /* The GPGME I/O callbacks */ static gpg_error_t gpa_context_register_cb (void *data, int fd, int dir, - gpgme_io_cb_t fnc, void *fnc_data, + gpgme_io_cb_t fnc, void *fnc_data, void **tag); static void gpa_context_remove_cb (void *tag); static void gpa_context_event_cb (void *data, gpgme_event_io_t type, void *type_data); static gpg_error_t gpa_context_passphrase_cb (void *hook, const char *uid_hint, - const char *passphrase_info, int prev_was_bad, + const char *passphrase_info, int prev_was_bad, int fd); static void gpa_context_progress_cb (void *opaque, const char *what, int type, int current, int total); /* Signals */ enum { START, DONE, NEXT_KEY, NEXT_TRUST_ITEM, PROGRESS, LAST_SIGNAL }; static GObjectClass *parent_class = NULL; static guint signals [LAST_SIGNAL] = { 0 }; GType gpa_context_get_type (void) { static GType context_type = 0; - + if (!context_type) { static const GTypeInfo context_info = { sizeof (GpaContextClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) gpa_context_class_init, NULL, /* class_finalize */ NULL, /* class_data */ sizeof (GpaContext), 0, /* n_preallocs */ (GInstanceInitFunc) gpa_context_init, }; - + context_type = g_type_register_static (G_TYPE_OBJECT, "GpaContext", &context_info, 0); } - + return context_type; } static void gpa_context_class_init (GpaContextClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); - + parent_class = g_type_class_peek_parent (klass); object_class->finalize = gpa_context_finalize; klass->start = gpa_context_start; klass->done = gpa_context_done; klass->next_key = gpa_context_next_key; klass->next_trust_item = gpa_context_next_trust_item; klass->progress = gpa_context_progress; /* Signals */ signals[START] = g_signal_new ("start", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GpaContextClass, start), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); signals[DONE] = g_signal_new ("done", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GpaContextClass, done), NULL, NULL, g_cclosure_marshal_VOID__INT, G_TYPE_NONE, 1, G_TYPE_INT); signals[NEXT_KEY] = g_signal_new ("next_key", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GpaContextClass, next_key), NULL, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); signals[NEXT_TRUST_ITEM] = g_signal_new ("next_trust_item", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GpaContextClass, next_trust_item), NULL, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); signals[PROGRESS] = g_signal_new ("progress", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GpaContextClass, progress), NULL, NULL, gtk_marshal_VOID__INT_INT, G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_INT); } static void gpa_context_init (GpaContext *context) { gpg_error_t err; context->busy = FALSE; + context->inhibit_gpgme_events = 0; /* The callback queue */ context->cbs = NULL; /* The context itself */ err = gpgme_new (&context->ctx); if (err) { gpa_gpgme_warning (err); return; } /* Set the appropriate callbacks. Note that we can't set the passphrase callback in CMS mode because it is not implemented by the CMS backend. To make things easier we never set in CMS mode because we can then assume that a proper GnuPG-2 system (with pinentry) is in use and then we don't need that callback for OpenPGP either. */ if (!cms_hack) gpgme_set_passphrase_cb (context->ctx, gpa_context_passphrase_cb, context); gpgme_set_progress_cb (context->ctx, gpa_context_progress_cb, context); /* Fill the CB structure */ context->io_cbs = g_malloc (sizeof (struct gpgme_io_cbs)); context->io_cbs->add = gpa_context_register_cb; context->io_cbs->add_priv = context; context->io_cbs->remove = gpa_context_remove_cb; context->io_cbs->event = gpa_context_event_cb; context->io_cbs->event_priv = context; /* Set the callbacks */ gpgme_set_io_cbs (context->ctx, context->io_cbs); } static void gpa_context_finalize (GObject *object) { GpaContext *context = GPA_CONTEXT (object); - + gpgme_release (context->ctx); g_list_free (context->cbs); g_free (context->io_cbs); - + G_OBJECT_CLASS (parent_class)->finalize (object); } /* API */ /* Create a new GpaContext object. */ GpaContext * gpa_context_new (void) { GpaContext *context; - + context = g_object_new (GPA_CONTEXT_TYPE, NULL); return context; } /* TRUE if an operation is in progress. */ gboolean gpa_context_busy (GpaContext *context) { g_return_val_if_fail (context != NULL, FALSE); g_return_val_if_fail (GPA_IS_CONTEXT (context), FALSE); return context->busy; } -/* - * The GPGME I/O callbacks + +/* Return a malloced string with the last diagnostic data of the + * context. Returns NULL if no diagnostics are available. */ +char * +gpa_context_get_diag (GpaContext *context) +{ +#if GPGME_VERSION_NUMBER >= 0x010c00 /* >= 1.12 */ + gpgme_data_t diag; + char *buffer, *result; + gpg_error_t err; + + if (!context) + return NULL; + if (gpgme_data_new (&diag)) + return NULL; /* Ooops. */ + + context->inhibit_gpgme_events++; + err = gpgme_op_getauditlog (context->ctx, diag, GPGME_AUDITLOG_DIAG); + context->inhibit_gpgme_events--; + if (err) + { + gpgme_data_release (diag); + return NULL; /* No data. */ + } + + /* Append a trailing zero and return the string. */ + gpgme_data_seek (diag, 0, SEEK_END); + gpgme_data_write (diag, "", 1); + buffer = gpgme_data_release_and_get_mem (diag, NULL); + result = g_strdup (buffer); + gpgme_free (buffer); + return result; +#else + return NULL; +#endif +} + + + +/* + * The GPGME I/O callbacks */ /* Registering callbacks with GLib */ /* Macros "borrowed" from GDK */ #define READ_CONDITION (G_IO_IN | G_IO_HUP | G_IO_ERR) #define WRITE_CONDITION (G_IO_OUT | G_IO_ERR) struct gpa_io_cb_data { int fd; int dir; gpgme_io_cb_t fnc; void *fnc_data; gint watch; GpaContext *context; gboolean registered; }; /* This function is called by GLib. It's a wrapper for the callback * gpgme provided, whose prototype does not match the one needed * by g_io_add_watch_full */ static gboolean gpa_io_cb (GIOChannel *source, GIOCondition condition, gpointer data) { struct gpa_io_cb_data *cb = data; /* We have to use the GPGME provided "file descriptor" here. It may not be a system file descriptor after all. */ cb->fnc (cb->fnc_data, cb->fd); return TRUE; } /* Register a GPGME callback with GLib. */ static void register_callback (struct gpa_io_cb_data *cb) { GIOChannel *channel; - + #ifdef G_OS_WIN32 /* We have to ask GPGME for the GIOChannel to use. The "file descriptor" may not be a system file descriptor. */ channel = gpgme_get_giochannel (cb->fd); g_assert (channel); #else channel = g_io_channel_unix_new (cb->fd); #endif - cb->watch = g_io_add_watch_full (channel, G_PRIORITY_DEFAULT, + cb->watch = g_io_add_watch_full (channel, G_PRIORITY_DEFAULT, cb->dir ? READ_CONDITION : WRITE_CONDITION, gpa_io_cb, cb, NULL); cb->registered = TRUE; #ifdef G_OS_WIN32 /* Nothing do to here. */ #else g_io_channel_unref (channel); #endif } /* Queuing callbacks until the START event arrives */ /* Add a callback to the list, until the START event arrives */ static void add_callback (GpaContext *context, struct gpa_io_cb_data *cb) -{ +{ context->cbs = g_list_append (context->cbs, cb); } /* Register with GLib all previously unregistered callbacks. */ static void register_all_callbacks (GpaContext *context) { struct gpa_io_cb_data *cb; GList *list; - + for (list = context->cbs; list; list = g_list_next (list)) { cb = list->data; if (!cb->registered) { register_callback (cb); } } } static void unregister_all_callbacks (GpaContext *context) { struct gpa_io_cb_data *cb; GList *list; - + for (list = context->cbs; list; list = g_list_next (list)) { cb = list->data; if (cb->registered) { g_source_remove (cb->watch); cb->registered = FALSE; } } } /* The real GPGME callbacks */ /* Register a callback. This is called by GPGME when a crypto - operation is initiated in this context. */ + operation is initiated in this context. */ static gpg_error_t gpa_context_register_cb (void *data, int fd, int dir, gpgme_io_cb_t fnc, void *fnc_data, void **tag) { GpaContext *context = data; struct gpa_io_cb_data *cb = g_malloc (sizeof (struct gpa_io_cb_data)); cb->registered = FALSE; cb->fd = fd; - cb->dir = dir; + cb->dir = dir; cb->fnc = fnc; cb->fnc_data = fnc_data; cb->context = context; /* If the context is busy, we already have a START event, and can * register GLib callbacks immediately. */ if (context->busy) register_callback (cb); /* In any case, we add it to the list. */ add_callback (context, cb); *tag = cb; return 0; } /* Remove a callback. This is called by GPGME if a context is to be destroyed. */ static void gpa_context_remove_cb (void *tag) { struct gpa_io_cb_data *cb = tag; if (cb->registered) { g_source_remove (cb->watch); } cb->context->cbs = g_list_remove (cb->context->cbs, cb); g_free (cb); } /* The event callback. It is called by GPGME to signal an event for an operation running in this context. This fucntion merely emits signals for GpaContext; the Glib signal handlers do the real job. */ static void gpa_context_event_cb (void *data, gpgme_event_io_t type, void *type_data) { GpaContext *context = data; gpg_error_t err, op_err; + if (context->inhibit_gpgme_events) + return; + switch (type) { case GPGME_EVENT_START: g_signal_emit (context, signals[START], 0); break; case GPGME_EVENT_DONE: err = ((gpgme_io_event_done_data_t)type_data)->err; op_err = ((gpgme_io_event_done_data_t)type_data)->op_err; g_debug ("EVENT_DONE: err=%s op_err=%s", gpg_strerror (err), gpg_strerror (op_err)); if (!err) err = op_err; g_signal_emit (context, signals[DONE], 0, err); break; case GPGME_EVENT_NEXT_KEY: g_signal_emit (context, signals[NEXT_KEY], 0, type_data); break; case GPGME_EVENT_NEXT_TRUSTITEM: g_signal_emit (context, signals[NEXT_TRUST_ITEM], 0, type_data); break; default: /* Ignore unsupported event types */ break; } } /* Default signal handlers */ static void gpa_context_start (GpaContext *context) { /* g_debug ("gpgme event START enter"); */ context->busy = TRUE; /* We have START, register all queued callbacks */ register_all_callbacks (context); /* g_debug ("gpgme event START leave"); */ } static void gpa_context_done (GpaContext *context, gpg_error_t err) { context->busy = FALSE; /* g_debug ("gpgme event DONE ready"); */ } static void gpa_context_next_key (GpaContext *context, gpgme_key_t key) { /* Do nothing yet */ } static void gpa_context_next_trust_item (GpaContext *context, gpgme_trust_item_t item) { /* Do nothing yet */ } static void gpa_context_progress (GpaContext *context, int current, int total) { /* Do nothing yet */ } /* The passphrase callback */ static gpg_error_t gpa_context_passphrase_cb (void *hook, const char *uid_hint, - const char *passphrase_info, int prev_was_bad, + const char *passphrase_info, int prev_was_bad, int fd) { GpaContext *context = hook; gpg_error_t err; unregister_all_callbacks (context); err = gpa_passphrase_cb (NULL, uid_hint, passphrase_info, prev_was_bad, fd); register_all_callbacks (context); return err; } /* The progress callback */ static void gpa_context_progress_cb (void *opaque, const char *what, int type, int current, int total) { GpaContext *context = opaque; - g_signal_emit (context, signals[PROGRESS], 0, current, total); + g_signal_emit (context, signals[PROGRESS], 0, current, total); } - diff --git a/src/gpacontext.h b/src/gpacontext.h index 42bcfc3..0a091bb 100644 --- a/src/gpacontext.h +++ b/src/gpacontext.h @@ -1,81 +1,85 @@ /* gpacontext.h - The GpaContext object. * Copyright (C) 2003, Miguel Coca. * * This file is part of GPA * * GPA 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 2 of the License, or * (at your option) any later version. * * GPA 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ #ifndef GPA_CONTEXT_H #define GPA_CONTEXT_H #include #include #include /* GObject stuff */ #define GPA_CONTEXT_TYPE (gpa_context_get_type ()) #define GPA_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GPA_CONTEXT_TYPE, GpaContext)) #define GPA_CONTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GPA_CONTEXT_TYPE, GpaContextClass)) #define GPA_IS_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GPA_CONTEXT_TYPE)) #define GPA_IS_CONTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GPA_CONTEXT_TYPE)) #define GPA_CONTEXT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GPA_CONTEXT_TYPE, GpaContextClass)) typedef struct _GpaContext GpaContext; typedef struct _GpaContextClass GpaContextClass; struct _GpaContext { GObject parent; /* public: */ /* The real gpgme_ctx_t */ gpgme_ctx_t ctx; /* Whether there is an operation currently in course */ gboolean busy; /* private: */ /* Queued I/O callbacks */ GList *cbs; /* The IO callback structure */ struct gpgme_io_cbs *io_cbs; + /* Hack to block certain events. */ + int inhibit_gpgme_events; }; struct _GpaContextClass { GObjectClass parent_class; /* Signal handlers */ void (*start) (GpaContext *context); void (*done) (GpaContext *context, gpg_error_t err); void (*next_key) (GpaContext *context, gpgme_key_t key); void (*next_trust_item) (GpaContext *context, gpgme_trust_item_t item); void (*progress) (GpaContext *context, int current, int total); }; GType gpa_context_get_type (void) G_GNUC_CONST; /* API */ /* Create a new GpaContext object. */ GpaContext *gpa_context_new (void); /* TRUE if an operation is in progress. */ gboolean gpa_context_busy (GpaContext *context); -#endif +/* Return a string with the diagnostics from gpgme. */ +char *gpa_context_get_diag (GpaContext *context); +#endif /*GPA_CONTEXT_H*/ diff --git a/src/gpaexportserverop.c b/src/gpaexportserverop.c index de3f781..ceae0b7 100644 --- a/src/gpaexportserverop.c +++ b/src/gpaexportserverop.c @@ -1,285 +1,285 @@ /* gpaexportserverop.c - The GpaExportServerOperation object. * Copyright (C) 2003, Miguel Coca. * * This file is part of GPA * * GPA 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 2 of the License, or * (at your option) any later version. * * GPA 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ #include #include #include #include "gpa.h" #include "i18n.h" #include "gtktools.h" #include "gpgmetools.h" #include "server-access.h" #include "confdialog.h" #include "gpaexportserverop.h" static GObjectClass *parent_class = NULL; static gboolean gpa_export_server_operation_get_destination (GpaExportOperation *operation, gpgme_data_t *dest, gboolean *armor); static void gpa_export_server_operation_complete_export (GpaExportOperation *operation); /* GObject boilerplate */ static void gpa_export_server_operation_finalize (GObject *object) { GpaExportServerOperation *op = GPA_EXPORT_SERVER_OPERATION (object); if (op->server) { g_free (op->server); } G_OBJECT_CLASS (parent_class)->finalize (object); } static void gpa_export_server_operation_init (GpaExportServerOperation *op) { op->server = NULL; } static GObject* gpa_export_server_operation_constructor (GType type, guint n_construct_properties, GObjectConstructParam *construct_properties) { GObject *object; /* GpaExportServerOperation *op; */ /* Invoke parent's constructor */ object = parent_class->constructor (type, n_construct_properties, construct_properties); /* op = GPA_EXPORT_SERVER_OPERATION (object); */ return object; } static void gpa_export_server_operation_class_init (GpaExportServerOperationClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GpaExportOperationClass *export_class = GPA_EXPORT_OPERATION_CLASS (klass); parent_class = g_type_class_peek_parent (klass); object_class->constructor = gpa_export_server_operation_constructor; object_class->finalize = gpa_export_server_operation_finalize; export_class->get_destination = gpa_export_server_operation_get_destination; export_class->complete_export = gpa_export_server_operation_complete_export; } GType gpa_export_server_operation_get_type (void) { static GType file_operation_type = 0; if (!file_operation_type) { static const GTypeInfo file_operation_info = { sizeof (GpaExportServerOperationClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) gpa_export_server_operation_class_init, NULL, /* class_finalize */ NULL, /* class_data */ sizeof (GpaExportServerOperation), 0, /* n_preallocs */ (GInstanceInitFunc) gpa_export_server_operation_init, }; file_operation_type = g_type_register_static (GPA_EXPORT_OPERATION_TYPE, "GpaExportServerOperation", &file_operation_info, 0); } return file_operation_type; } /* Internal */ static gboolean confirm_send (GtkWidget *parent, const gchar *server) { GtkWidget *msgbox; char *info; char *keyserver = NULL; if (is_gpg_version_at_least ("2.1.0")) { keyserver = gpa_load_configured_keyserver (); server = keyserver; } if (!server) { keyserver = gpa_configure_keyserver (parent); if (!keyserver) return FALSE; server = keyserver; } info = g_strdup_printf (_("The selected key(s) will be sent to a public key\n" "server (\"%s\")."), server); g_free (keyserver); msgbox = gtk_message_dialog_new (GTK_WINDOW(parent), GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_NONE, "%s\n\n%s", info, _("Are you sure you want to distribute this key?")); g_free (info); gtk_dialog_add_buttons (GTK_DIALOG (msgbox), _("_Yes"), GTK_RESPONSE_YES, _("_No"), GTK_RESPONSE_NO, NULL); if (gtk_dialog_run (GTK_DIALOG (msgbox)) != GTK_RESPONSE_YES) { gtk_widget_destroy (msgbox); return FALSE; } gtk_widget_destroy (msgbox); return TRUE; } /* Virtual methods */ static gboolean gpa_export_server_operation_get_destination (GpaExportOperation *operation, gpgme_data_t *dest, gboolean *armor) { if (confirm_send (GPA_OPERATION (operation)->window, gpa_options_get_default_keyserver (gpa_options_get_instance ()))) { gpg_error_t err; *armor = TRUE; err = gpgme_data_new (dest); if (err) { gpa_gpgme_warning (err); return FALSE; } else { return TRUE; } } else { return FALSE; } } /* GnuPG 2.1 method to send keys to the keyserver. KEYLIST has a list of keys to be sent. Returns true on success. */ static gboolean send_keys (GpaExportServerOperation *op, GList *keylist) { gpg_error_t err; GList *item; gpgme_key_t *keyarray; gpgme_key_t key; int i; keyarray = g_malloc0_n (g_list_length (keylist)+1, sizeof *keyarray); i = 0; for (item = keylist; item; i++, item = g_list_next (item)) { key = (gpgme_key_t) item->data; if (!key || key->protocol != GPGME_PROTOCOL_OpenPGP) continue; gpgme_key_ref (key); keyarray[i++] = key; } gpgme_set_protocol (GPA_OPERATION (op)->context->ctx, GPGME_PROTOCOL_OpenPGP); err = gpgme_op_export_keys (GPA_OPERATION (op)->context->ctx, keyarray, GPGME_KEYLIST_MODE_EXTERN, NULL); for (i=0; keyarray[i]; i++) gpgme_key_unref (keyarray[i]); g_free (keyarray); if (err) { - gpa_show_warning (GPA_OPERATION (op)->window, + gpa_show_warn (GPA_OPERATION (op)->window, NULL, "%s\n\n(%s <%s>)", _("Error sending key(s) to the server."), gpg_strerror (err), gpg_strsource (err)); return FALSE; } return TRUE; } static void gpa_export_server_operation_complete_export (GpaExportOperation *operation) { GpaExportServerOperation *op = GPA_EXPORT_SERVER_OPERATION (operation); int okay = 0; if (is_gpg_version_at_least ("2.1.0")) { /* GnuPG 2.1.0 does not anymore use the keyserver helpers and thus we need to use the real API for sending keys. */ if (send_keys (op, operation->keys)) okay = 1; } else { gpgme_key_t key = (gpgme_key_t) operation->keys->data; op->server = g_strdup (gpa_options_get_default_keyserver (gpa_options_get_instance ())); if (server_send_keys (op->server, key->subkeys->keyid, operation->dest, GPA_OPERATION (op)->window)) okay = 1; } if (okay) gpa_window_message (_("The keys have been sent to the server."), GPA_OPERATION (op)->window); } /* API */ GpaExportServerOperation* gpa_export_server_operation_new (GtkWidget *window, GList *keys) { GpaExportServerOperation *op; op = g_object_new (GPA_EXPORT_SERVER_OPERATION_TYPE, "window", window, "keys", keys, NULL); return op; } diff --git a/src/gpafiledecryptop.c b/src/gpafiledecryptop.c index c36da44..da8765d 100644 --- a/src/gpafiledecryptop.c +++ b/src/gpafiledecryptop.c @@ -1,543 +1,541 @@ /* gpafiledecryptop.c - The GpaOperation object. * Copyright (C) 2003 Miguel Coca. * Copyright (C) 2008, 2015 g10 Code GmbH. * * This file is part of GPA * * GPA 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 2 of the License, or * (at your option) any later version. * * GPA 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ #include #include #include #ifdef G_OS_UNIX #include #include #include #else #include #endif #include "gpa.h" #include "gtktools.h" #include "gpgmetools.h" #include "filetype.h" #include "gpafiledecryptop.h" #include "verifydlg.h" /* Internal functions */ static gboolean gpa_file_decrypt_operation_idle_cb (gpointer data); static void gpa_file_decrypt_operation_done_cb (GpaContext *context, gpg_error_t err, GpaFileDecryptOperation *op); static void gpa_file_decrypt_operation_done_error_cb (GpaContext *context, gpg_error_t err, GpaFileDecryptOperation *op); /* GObject */ static GObjectClass *parent_class = NULL; /* Properties */ enum { PROP_0, PROP_VERIFY }; static void gpa_file_decrypt_operation_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GpaFileDecryptOperation *op = GPA_FILE_DECRYPT_OPERATION (object); switch (prop_id) { case PROP_VERIFY: g_value_set_boolean (value, op->verify); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gpa_file_decrypt_operation_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GpaFileDecryptOperation *op = GPA_FILE_DECRYPT_OPERATION (object); switch (prop_id) { case PROP_VERIFY: op->verify = g_value_get_boolean (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gpa_file_decrypt_operation_finalize (GObject *object) { GpaFileDecryptOperation *op = GPA_FILE_DECRYPT_OPERATION (object); if (op->dialog) gtk_widget_destroy (op->dialog); G_OBJECT_CLASS (parent_class)->finalize (object); } static void gpa_file_decrypt_operation_init (GpaFileDecryptOperation *op) { op->cipher_fd = -1; op->plain_fd = -1; op->cipher = NULL; op->plain = NULL; } static void gpa_file_decrypt_operation_response_cb (GtkDialog *dialog, gint response, gpointer user_data) { GpaFileDecryptOperation *op = GPA_FILE_DECRYPT_OPERATION (user_data); g_signal_emit_by_name (GPA_OPERATION (op), "completed", op->err); } static GObject* gpa_file_decrypt_operation_constructor (GType type, guint n_construct_properties, GObjectConstructParam *construct_properties) { GObject *object; GpaFileDecryptOperation *op; /* Invoke parent's constructor */ object = parent_class->constructor (type, n_construct_properties, construct_properties); op = GPA_FILE_DECRYPT_OPERATION (object); /* Initialize */ /* Start with the first file after going back into the main loop */ g_idle_add (gpa_file_decrypt_operation_idle_cb, op); /* Connect to the "done" signal */ g_signal_connect (G_OBJECT (GPA_OPERATION (op)->context), "done", G_CALLBACK (gpa_file_decrypt_operation_done_error_cb), op); g_signal_connect (G_OBJECT (GPA_OPERATION (op)->context), "done", G_CALLBACK (gpa_file_decrypt_operation_done_cb), op); /* Give a title to the progress dialog */ gtk_window_set_title (GTK_WINDOW (GPA_FILE_OPERATION (op)->progress_dialog), _("Decrypting...")); if (op->verify) { /* Create the verification dialog */ op->dialog = gpa_file_verify_dialog_new (GPA_OPERATION (op)->window); g_signal_connect (G_OBJECT (op->dialog), "response", G_CALLBACK (gpa_file_decrypt_operation_response_cb), op); } return object; } static void gpa_file_decrypt_operation_class_init (GpaFileDecryptOperationClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); parent_class = g_type_class_peek_parent (klass); object_class->constructor = gpa_file_decrypt_operation_constructor; object_class->finalize = gpa_file_decrypt_operation_finalize; object_class->set_property = gpa_file_decrypt_operation_set_property; object_class->get_property = gpa_file_decrypt_operation_get_property; /* Properties */ g_object_class_install_property (object_class, PROP_VERIFY, g_param_spec_boolean ("verify", "Verify", "Verify", FALSE, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)); } GType gpa_file_decrypt_operation_get_type (void) { static GType file_decrypt_operation_type = 0; if (!file_decrypt_operation_type) { static const GTypeInfo file_decrypt_operation_info = { sizeof (GpaFileDecryptOperationClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) gpa_file_decrypt_operation_class_init, NULL, /* class_finalize */ NULL, /* class_data */ sizeof (GpaFileDecryptOperation), 0, /* n_preallocs */ (GInstanceInitFunc) gpa_file_decrypt_operation_init, }; file_decrypt_operation_type = g_type_register_static (GPA_FILE_OPERATION_TYPE, "GpaFileDecryptOperation", &file_decrypt_operation_info, 0); } return file_decrypt_operation_type; } /* API */ GpaFileDecryptOperation* gpa_file_decrypt_operation_new (GtkWidget *window, GList *files) { GpaFileDecryptOperation *op; op = g_object_new (GPA_FILE_DECRYPT_OPERATION_TYPE, "window", window, "input_files", files, NULL); return op; } GpaFileDecryptOperation* gpa_file_decrypt_verify_operation_new (GtkWidget *window, GList *files) { GpaFileDecryptOperation *op; op = g_object_new (GPA_FILE_DECRYPT_OPERATION_TYPE, "window", window, "input_files", files, "verify", TRUE, NULL); return op; } /* Internal */ static gchar * destination_filename (const gchar *filename) { gchar *extension, *plain_filename; /* Find out the destination file */ extension = g_strrstr (filename, "."); if (extension && (g_str_equal (extension, ".asc") || g_str_equal (extension, ".gpg") || g_str_equal (extension, ".pgp"))) { /* Remove the extension */ plain_filename = g_strdup (filename); *(plain_filename + (extension-filename)) = '\0'; } else { plain_filename = g_strconcat (filename, ".txt", NULL); } return plain_filename; } static gpg_error_t gpa_file_decrypt_operation_start (GpaFileDecryptOperation *op, gpa_file_item_t file_item) { gpg_error_t err; if (file_item->direct_in) { /* No copy is made. */ err = gpgme_data_new_from_mem (&op->cipher, file_item->direct_in, file_item->direct_in_len, 0); if (err) { gpa_gpgme_warning (err); return err; } err = gpgme_data_new (&op->plain); if (err) { gpa_gpgme_warning (err); gpgme_data_release (op->cipher); op->plain = NULL; return err; } gpgme_set_protocol (GPA_OPERATION (op)->context->ctx, is_cms_data (file_item->direct_in, file_item->direct_in_len) ? GPGME_PROTOCOL_CMS : GPGME_PROTOCOL_OpenPGP); } else { gchar *cipher_filename = file_item->filename_in; char *filename_used; file_item->filename_out = destination_filename (cipher_filename); /* Open the files */ op->cipher_fd = gpa_open_input (cipher_filename, &op->cipher, GPA_OPERATION (op)->window); if (op->cipher_fd == -1) /* FIXME: Error value. */ return gpg_error (GPG_ERR_GENERAL); op->plain_fd = gpa_open_output (file_item->filename_out, &op->plain, GPA_OPERATION (op)->window, &filename_used); if (op->plain_fd == -1) { gpgme_data_release (op->cipher); close (op->cipher_fd); xfree (filename_used); /* FIXME: Error value. */ return gpg_error (GPG_ERR_GENERAL); } xfree (file_item->filename_out); file_item->filename_out = filename_used; gpgme_set_protocol (GPA_OPERATION (op)->context->ctx, is_cms_file (cipher_filename) ? GPGME_PROTOCOL_CMS : GPGME_PROTOCOL_OpenPGP); } /* Start the operation. */ err = gpgme_op_decrypt_verify_start (GPA_OPERATION (op)->context->ctx, op->cipher, op->plain); if (err) { gpa_gpgme_warning (err); gpgme_data_release (op->plain); op->plain = NULL; close (op->plain_fd); op->plain_fd = -1; gpgme_data_release (op->cipher); op->cipher = NULL; close (op->cipher_fd); op->cipher_fd = -1; return err; } /* Show and update the progress dialog. */ gtk_widget_show_all (GPA_FILE_OPERATION (op)->progress_dialog); gpa_progress_dialog_set_label (GPA_PROGRESS_DIALOG (GPA_FILE_OPERATION (op)->progress_dialog), file_item->direct_name ? file_item->direct_name : file_item->filename_in); return 0; } static void gpa_file_decrypt_operation_next (GpaFileDecryptOperation *op) { gpg_error_t err; if (! GPA_FILE_OPERATION (op)->current) { if (op->verify && op->signed_files) { op->err = 0; gtk_widget_show_all (op->dialog); } else g_signal_emit_by_name (GPA_OPERATION (op), "completed", 0); return; } err = gpa_file_decrypt_operation_start (op, GPA_FILE_OPERATION (op)->current->data); if (err) { if (op->verify && op->signed_files) { /* All files have been verified: show the results dialog */ op->err = err; gtk_widget_show_all (op->dialog); } else g_signal_emit_by_name (GPA_OPERATION (op), "completed", err); } } static void gpa_file_decrypt_operation_done_cb (GpaContext *context, gpg_error_t err, GpaFileDecryptOperation *op) { gpa_file_item_t file_item = GPA_FILE_OPERATION (op)->current->data; if (file_item->direct_in) { size_t len; char *plain_gpgme = gpgme_data_release_and_get_mem (op->plain, &len); op->plain = NULL; /* Do the memory allocation dance. */ if (plain_gpgme) { /* Conveniently make ASCII stuff into a string. */ file_item->direct_out = g_malloc (len + 1); memcpy (file_item->direct_out, plain_gpgme, len); gpgme_free (plain_gpgme); file_item->direct_out[len] = '\0'; /* Yep, excluding the trailing zero. */ file_item->direct_out_len = len; } else { file_item->direct_out = NULL; file_item->direct_out_len = 0; } } /* Do clean up on the operation */ gpgme_data_release (op->plain); op->plain = NULL; close (op->plain_fd); op->plain_fd = -1; gpgme_data_release (op->cipher); op->cipher = NULL; close (op->cipher_fd); op->cipher_fd = -1; gtk_widget_hide (GPA_FILE_OPERATION (op)->progress_dialog); if (err) { if (! file_item->direct_in) { /* If an error happened, (or the user canceled) delete the created file and abort further decryptions. */ g_unlink (file_item->filename_out); g_free (file_item->filename_out); file_item->filename_out = NULL; } /* FIXME:CLIPBOARD: Server finish? */ g_signal_emit_by_name (GPA_OPERATION (op), "completed", err); } else { /* We've just created a file */ g_signal_emit_by_name (GPA_OPERATION (op), "created_file", file_item); if (op->verify) { gpgme_verify_result_t result; result = gpgme_op_verify_result (GPA_OPERATION (op)->context->ctx); if (result->signatures) { /* Add the file to the result dialog. FIXME: Maybe we should use the filename without the directory. */ gpa_file_verify_dialog_add_file (GPA_FILE_VERIFY_DIALOG (op->dialog), file_item->direct_name ? file_item->direct_name : file_item->filename_in, NULL, NULL, result->signatures); op->signed_files++; } } /* Go to the next file in the list and decrypt it */ GPA_FILE_OPERATION (op)->current = g_list_next (GPA_FILE_OPERATION (op)->current); gpa_file_decrypt_operation_next (op); } } static gboolean gpa_file_decrypt_operation_idle_cb (gpointer data) { GpaFileDecryptOperation *op = data; gpa_file_decrypt_operation_next (op); return FALSE; } static void gpa_file_decrypt_operation_done_error_cb (GpaContext *context, gpg_error_t err, GpaFileDecryptOperation *op) { gpa_file_item_t file_item = GPA_FILE_OPERATION (op)->current->data; - gchar *message; switch (gpg_err_code (err)) { case GPG_ERR_NO_ERROR: case GPG_ERR_CANCELED: /* Ignore these */ break; case GPG_ERR_NO_DATA: - message = g_strdup_printf (file_item->direct_name - ? _("\"%s\" contained no OpenPGP data.") - : _("The file \"%s\" contained no OpenPGP" - "data."), - file_item->direct_name - ? file_item->direct_name - : file_item->filename_in); - gpa_window_error (message, GPA_OPERATION (op)->window); - g_free (message); + gpa_show_warn (GPA_OPERATION (op)->window, GPA_OPERATION (op)->context, + file_item->direct_name + ? _("\"%s\" contained no OpenPGP data.") + : _("The file \"%s\" contained no OpenPGP" + "data."), + file_item->direct_name + ? file_item->direct_name + : file_item->filename_in); break; case GPG_ERR_DECRYPT_FAILED: - message = g_strdup_printf (file_item->direct_name - ? _("\"%s\" contained no valid " - "encrypted data.") - : _("The file \"%s\" contained no valid " - "encrypted data."), - file_item->direct_name - ? file_item->direct_name - : file_item->filename_in); - gpa_window_error (message, GPA_OPERATION (op)->window); - g_free (message); + gpa_show_warn (GPA_OPERATION (op)->window, GPA_OPERATION (op)->context, + file_item->direct_name + ? _("\"%s\" contained no valid " + "encrypted data.") + : _("The file \"%s\" contained no valid " + "encrypted data."), + file_item->direct_name + ? file_item->direct_name + : file_item->filename_in); break; case GPG_ERR_BAD_PASSPHRASE: - gpa_window_error (_("Wrong passphrase!"), GPA_OPERATION (op)->window); + gpa_show_warn (GPA_OPERATION (op)->window, GPA_OPERATION (op)->context, + _("Wrong passphrase!")); break; default: - gpa_gpgme_warning (err); + gpa_gpgme_warn (err, NULL, GPA_OPERATION (op)->context); break; } } diff --git a/src/gpafileimportop.c b/src/gpafileimportop.c index 4cc0dc1..bb78c3f 100644 --- a/src/gpafileimportop.c +++ b/src/gpafileimportop.c @@ -1,311 +1,311 @@ /* gpafileimportop.c - Import keys from a file. Copyright (C) 2003 Miguel Coca. Copyright (C) 2008, 2014 g10 Code GmbH. This file is part of GPA. GPA 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. GPA 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 #ifdef G_OS_UNIX #include #include #include #else #include #endif #include "gpa.h" #include "gtktools.h" #include "gpgmetools.h" #include "filetype.h" #include "gpafileimportop.h" /* Internal functions */ static gboolean gpa_file_import_operation_idle_cb (gpointer data); static void gpa_file_import_operation_done_error_cb (GpaContext *context, gpg_error_t err, GpaFileImportOperation *op); static void gpa_file_import_operation_done_cb (GpaContext *context, gpg_error_t err, GpaFileImportOperation *op); /* GObject */ static GObjectClass *parent_class = NULL; static void gpa_file_import_operation_finalize (GObject *object) { /* GpaFileImportOperation *op = GPA_FILE_IMPORT_OPERATION (object); */ G_OBJECT_CLASS (parent_class)->finalize (object); } static void gpa_file_import_operation_init (GpaFileImportOperation *op) { memset (&op->counters, 0, sizeof op->counters); } static GObject* gpa_file_import_operation_constructor (GType type, guint n_construct_properties, GObjectConstructParam *construct_properties) { GObject *object; GpaFileImportOperation *op; /* Invoke parent's constructor */ object = parent_class->constructor (type, n_construct_properties, construct_properties); op = GPA_FILE_IMPORT_OPERATION (object); /* Initialize */ /* Start with the first file after going back into the main loop */ g_idle_add (gpa_file_import_operation_idle_cb, op); /* Connect to the "done" signal */ g_signal_connect (G_OBJECT (GPA_OPERATION (op)->context), "done", G_CALLBACK (gpa_file_import_operation_done_error_cb), op); g_signal_connect (G_OBJECT (GPA_OPERATION (op)->context), "done", G_CALLBACK (gpa_file_import_operation_done_cb), op); /* Give a title to the progress dialog */ gtk_window_set_title (GTK_WINDOW (GPA_FILE_OPERATION (op)->progress_dialog), _("Importing...")); return object; } static void gpa_file_import_operation_class_init (GpaFileImportOperationClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); parent_class = g_type_class_peek_parent (klass); object_class->constructor = gpa_file_import_operation_constructor; object_class->finalize = gpa_file_import_operation_finalize; } GType gpa_file_import_operation_get_type (void) { static GType file_import_operation_type = 0; if (!file_import_operation_type) { static const GTypeInfo file_import_operation_info = { sizeof (GpaFileImportOperationClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) gpa_file_import_operation_class_init, NULL, /* class_finalize */ NULL, /* class_data */ sizeof (GpaFileImportOperation), 0, /* n_preallocs */ (GInstanceInitFunc) gpa_file_import_operation_init }; file_import_operation_type = g_type_register_static (GPA_FILE_OPERATION_TYPE, "GpaFileImportOperation", &file_import_operation_info, 0); } return file_import_operation_type; } /* API */ GpaFileImportOperation* gpa_file_import_operation_new (GtkWidget *window, GList *files) { GpaFileImportOperation *op; op = g_object_new (GPA_FILE_IMPORT_OPERATION_TYPE, "window", window, "input_files", files, NULL); return op; } /* Internal */ static gboolean proc_one_file (GpaFileImportOperation *op, gpa_file_item_t file_item) { gpg_error_t err; int fd; gpgme_data_t data; if (file_item->direct_in) { /* No copy is made. */ err = gpgme_data_new_from_mem (&data, file_item->direct_in, file_item->direct_in_len, 0); if (err) { gpa_gpgme_warning (err); return FALSE; } gpgme_set_protocol (GPA_OPERATION (op)->context->ctx, is_cms_data (file_item->direct_in, file_item->direct_in_len) ? GPGME_PROTOCOL_CMS : GPGME_PROTOCOL_OpenPGP); } else { const char *filename = file_item->filename_in; fd = gpa_open_input (filename, &data, GPA_OPERATION (op)->window); if (fd == -1) return FALSE; gpgme_set_protocol (GPA_OPERATION (op)->context->ctx, is_cms_file (filename) ? GPGME_PROTOCOL_CMS : GPGME_PROTOCOL_OpenPGP); } /* Start importing one file. */ err = gpgme_op_import_start (GPA_OPERATION (op)->context->ctx, data); if (err) { gpa_gpgme_warning (err); return FALSE; } /* Show and update the progress dialog */ gtk_widget_show_all (GPA_FILE_OPERATION (op)->progress_dialog); gpa_progress_dialog_set_label (GPA_PROGRESS_DIALOG (GPA_FILE_OPERATION (op)->progress_dialog), file_item->direct_name ? file_item->direct_name : file_item->filename_in); return TRUE; } static void gpa_file_import_operation_next (GpaFileImportOperation *op) { if (!GPA_FILE_OPERATION (op)->current || !proc_one_file (op, GPA_FILE_OPERATION (op)->current->data)) { /* Finished all files. */ gtk_widget_hide (GPA_FILE_OPERATION (op)->progress_dialog); if (op->counters.imported > 0) { if (op->counters.secret_imported) g_signal_emit_by_name (GPA_OPERATION (op), "imported_secret_keys"); else g_signal_emit_by_name (GPA_OPERATION (op), "imported_keys"); } gpa_gpgme_show_import_results (GPA_OPERATION (op)->window, &op->counters); } } static gboolean gpa_file_import_operation_idle_cb (gpointer data) { GpaFileImportOperation *op = data; gpa_file_import_operation_next (op); return FALSE; } static void gpa_file_import_operation_done_cb (GpaContext *context, gpg_error_t err, GpaFileImportOperation *op) { if (err) { gpa_gpgme_update_import_results (&op->counters, 1, 1, NULL); } else { gpgme_import_result_t res; res = gpgme_op_import_result (GPA_OPERATION (op)->context->ctx); gpa_gpgme_update_import_results (&op->counters, 1, 0, res); } if (gpg_err_code (err) != GPG_ERR_CANCELED) { /* Go to the next file in the list and import it. */ GPA_FILE_OPERATION (op)->current = (g_list_next (GPA_FILE_OPERATION (op)->current)); gpa_file_import_operation_next (op); } } static void gpa_file_import_operation_done_error_cb (GpaContext *context, gpg_error_t err, GpaFileImportOperation *op) { gpa_file_item_t file_item = GPA_FILE_OPERATION (op)->current->data; /* FIXME: Add the errors to a list and show a dialog with all import errors, similar to the verify status. */ switch (gpg_err_code (err)) { case GPG_ERR_NO_ERROR: case GPG_ERR_CANCELED: /* Ignore these */ break; case GPG_ERR_NO_DATA: - gpa_show_warning (GPA_OPERATION (op)->window, + gpa_show_warn (GPA_OPERATION (op)->window, NULL, file_item->direct_name ? _("\"%s\" contained no OpenPGP data.") : _("The file \"%s\" contained no OpenPGP" "data."), file_item->direct_name ? file_item->direct_name : file_item->filename_in); break; default: - gpa_show_warning (GPA_OPERATION (op)->window, + gpa_show_warn (GPA_OPERATION (op)->window, NULL, _("Error importing \"%s\": %s <%s>"), file_item->direct_name ? file_item->direct_name : file_item->filename_in, gpg_strerror (err), gpg_strsource (err)); break; } } diff --git a/src/gpaimportserverop.c b/src/gpaimportserverop.c index fb3e0b9..7e347a2 100644 --- a/src/gpaimportserverop.c +++ b/src/gpaimportserverop.c @@ -1,289 +1,289 @@ /* gpaimportserverop.c - The GpaImportServerOperation object. * Copyright (C) 2003, Miguel Coca. * * This file is part of GPA * * GPA 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 2 of the License, or * (at your option) any later version. * * GPA 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ #include #include #include #include "gpa.h" #include "i18n.h" #include "gtktools.h" #include "gparecvkeydlg.h" #include "gpaimportserverop.h" #include "server-access.h" /* The number of keys we allow to import at once. If we have more than this we terminate the dialog and ask the user to give a better specification of the key. A better way to do this would be to pop up a dialog to allow the user to select matching keys. */ #define MAX_KEYSEARCH_RESULTS 5 static GObjectClass *parent_class = NULL; static gboolean gpa_import_server_operation_get_source (GpaImportOperation *operation); static void gpa_import_server_operation_complete_import (GpaImportOperation *operation); /* GObject boilerplate */ static void gpa_import_server_operation_finalize (GObject *object) { G_OBJECT_CLASS (parent_class)->finalize (object); } static void gpa_import_server_operation_init (GpaImportServerOperation *op) { } static GObject* gpa_import_server_operation_constructor (GType type, guint n_construct_properties, GObjectConstructParam *construct_properties) { GObject *object; /* GpaImportServerOperation *op; */ /* Invoke parent's constructor */ object = parent_class->constructor (type, n_construct_properties, construct_properties); /* op = GPA_IMPORT_SERVER_OPERATION (object); */ return object; } static void gpa_import_server_operation_class_init (GpaImportServerOperationClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GpaImportOperationClass *import_class = GPA_IMPORT_OPERATION_CLASS (klass); parent_class = g_type_class_peek_parent (klass); object_class->constructor = gpa_import_server_operation_constructor; object_class->finalize = gpa_import_server_operation_finalize; import_class->get_source = gpa_import_server_operation_get_source; import_class->complete_import = gpa_import_server_operation_complete_import; } GType gpa_import_server_operation_get_type (void) { static GType server_operation_type = 0; if (!server_operation_type) { static const GTypeInfo server_operation_info = { sizeof (GpaImportServerOperationClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) gpa_import_server_operation_class_init, NULL, /* class_finalize */ NULL, /* class_data */ sizeof (GpaImportServerOperation), 0, /* n_preallocs */ (GInstanceInitFunc) gpa_import_server_operation_init, }; server_operation_type = g_type_register_static (GPA_IMPORT_OPERATION_TYPE, "GpaImportServerOperation", &server_operation_info, 0); } return server_operation_type; } /* Internal */ /* Search for keys with KEYID. Return true on success and set the SOURCE2 instance variable. */ static gboolean search_keys (GpaImportOperation *operation, const char *keyid) { gpg_error_t err; gboolean result = FALSE; gpgme_ctx_t ctx; gpgme_key_t key; gpgme_key_t *keyarray; int i, nkeys; gpgme_keylist_mode_t listmode; char *mbox = NULL; if (!keyid || !*keyid) return FALSE; keyarray = g_malloc0_n (MAX_KEYSEARCH_RESULTS + 1, sizeof *keyarray); /* We need to use a separate context because the operaion's context has already been setup and the done signal would relate to the actual import operation done later. */ ctx = gpa_gpgme_new (); gpgme_set_protocol (ctx, GPGME_PROTOCOL_OpenPGP); /* Switch to extern-only or locate list mode. We use --locate-key * iff KEYID is a single mail address. */ listmode = GPGME_KEYLIST_MODE_EXTERN; #if GPGME_VERSION_NUMBER >= 0x010701 mbox = gpgme_addrspec_from_uid (keyid); if (mbox) { listmode = GPGME_KEYLIST_MODE_LOCATE; /* We already extracted the mbox - use it directly than letting * gnupg extract it. */ keyid = mbox; } #endif /* GPGME >= 1.7.1 */ err = gpgme_set_keylist_mode (ctx, listmode); if (err) gpa_gpgme_error (err); /* List keys matching the given keyid. Actually all kind of search specifications can be given. */ nkeys = 0; err = gpgme_op_keylist_start (ctx, keyid, 0); while (!err && !(err = gpgme_op_keylist_next (ctx, &key))) { if (nkeys >= MAX_KEYSEARCH_RESULTS) { - gpa_show_warning (GPA_OPERATION (operation)->window, + gpa_show_warn (GPA_OPERATION (operation)->window, NULL, _("More than %d keys match your search pattern.\n" "Use the long keyid or a fingerprint " "for a better match"), nkeys); gpgme_key_unref (key); err = gpg_error (GPG_ERR_TRUNCATED); break; } keyarray[nkeys++] = key; } gpgme_op_keylist_end (ctx); if (gpg_err_code (err) == GPG_ERR_EOF) err = 0; if (!err && !nkeys) { - gpa_show_warning (GPA_OPERATION (operation)->window, + gpa_show_warn (GPA_OPERATION (operation)->window, NULL, _("No keys were found.")); } else if (!err) { operation->source2 = keyarray; keyarray = NULL; result = TRUE; } else if (gpg_err_code (err) != GPG_ERR_TRUNCATED) gpa_gpgme_warning (err); gpgme_release (ctx); if (keyarray) { for (i=0; keyarray[i]; i++) gpgme_key_unref (keyarray[i]); g_free (keyarray); } gpgme_free (mbox); return result; } /* Virtual methods */ static gboolean gpa_import_server_operation_get_source (GpaImportOperation *operation) { GpaImportServerOperation *op = GPA_IMPORT_SERVER_OPERATION (operation); GtkWidget *dialog; GtkResponseType response; gchar *keyid; int i; dialog = gpa_receive_key_dialog_new (GPA_OPERATION (op)->window); gtk_widget_show_all (dialog); response = gtk_dialog_run (GTK_DIALOG (dialog)); keyid = g_strdup (gpa_receive_key_dialog_get_id (GPA_RECEIVE_KEY_DIALOG (dialog))); gtk_widget_destroy (dialog); /* Better reset the source variables. */ gpgme_data_release (operation->source); operation->source = NULL; if (operation->source2) { for (i=0; operation->source2[i]; i++) gpgme_key_unref (operation->source2[i]); g_free (operation->source2); operation->source2 = NULL; } if (response == GTK_RESPONSE_OK && is_gpg_version_at_least ("2.1.0")) { /* GnuPG 2.1.0 does not anymore use the keyserver helpers and thus we need to use the real API for receiving keys. Given that there is currently no way to create a list of keys from the keyids to be passed to the import function we run a --search-keys first to get the list of matching keys and pass them to the actual import function (which does a --recv-keys). */ /* Fixme: As with server_get_key (below), this is a blocking operation. */ if (search_keys (operation, keyid)) { /* Okay, found key(s). */ g_free (keyid); return TRUE; } } else if (response == GTK_RESPONSE_OK) { if (server_get_key (gpa_options_get_default_keyserver (gpa_options_get_instance ()), keyid, &operation->source, GPA_OPERATION (op)->window)) { g_free (keyid); return TRUE; } } g_free (keyid); return FALSE; } static void gpa_import_server_operation_complete_import (GpaImportOperation *operation) { /* Nothing special to do */ } /* API */ GpaImportServerOperation* gpa_import_server_operation_new (GtkWidget *window) { GpaImportServerOperation *op; op = g_object_new (GPA_IMPORT_SERVER_OPERATION_TYPE, "window", window, NULL); return op; } diff --git a/src/gpgmetools.c b/src/gpgmetools.c index 2dfd741..e2631ff 100644 --- a/src/gpgmetools.c +++ b/src/gpgmetools.c @@ -1,1845 +1,1842 @@ /* gpgmetools.h - Additional gpgme support functions for GPA. Copyright (C) 2002 Miguel Coca. Copyright (C) 2005, 2008, 2009, 2012, 2014, 2015 g10 Code GmbH. This file is part of GPA GPA 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. GPA 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 . */ /* A set of auxiliary functions for common tasks related to GPGME */ #include #include #include #include #include "gpa.h" #include "gtktools.h" #include "gpgmetools.h" #include #ifdef G_OS_UNIX #include #include #include #include #else #include #endif #ifdef G_OS_WIN32 #include #endif #include #ifndef O_BINARY #ifdef _O_BINARY #define O_BINARY _O_BINARY #else #define O_BINARY 0 #endif #endif /* Helper to strip the path from a source file name. This helps to avoid showing long filenames in case of VPATH builds. */ static const char * strip_path (const char *file) { const char *s = strrchr (file, '/'); return s? s+1:file; } /* Report an unexpected error in GPGME and quit the application. */ void _gpa_gpgme_error (gpg_error_t err, const char *file, int line) { gchar *message = g_strdup_printf (_("Fatal Error in GPGME library\n" "(invoked from file %s, line %i):\n\n" "\t%s\n\n" "The application will be terminated"), strip_path (file), line, gpgme_strerror (err)); gpa_window_error (message, NULL); g_free (message); exit (EXIT_FAILURE); } -/* (Please use the gpa_gpgme_warning macros). */ +/* (Please use the gpa_gpgme_warn macros). */ void -_gpa_gpgme_warning (gpg_error_t err, const char *desc, - const char *file, int line) +_gpa_gpgme_warn (gpg_error_t err, const char *desc, GpaContext *ctx, + const char *file, int line) { char *argbuf = NULL; const char *arg; - char *message; if (desc && (!err || gpg_err_code (err) == GPG_ERR_GENERAL)) arg = desc; else if (desc) { argbuf = g_strdup_printf ("%s (%s)", gpgme_strerror (err), desc); arg = argbuf; } else arg = gpgme_strerror (err); - message = g_strdup_printf - (_("The GPGME library returned an unexpected\n" - "error at %s:%d. The error was:\n\n" - "\t%s\n\n" - "This is either an installation problem or a bug in %s.\n" - "%s will now try to recover from this error."), - strip_path (file), line, arg, GPA_NAME, GPA_NAME); + gpa_show_warn (NULL, ctx, + _("The GPGME library returned an unexpected\n" + "error at %s:%d. The error was:\n\n" + "\t%s\n\n" + "This is either an installation problem or a bug in %s.\n" + "%s will now try to recover from this error."), + strip_path (file), line, arg, GPA_NAME, GPA_NAME); g_free (argbuf); - gpa_window_error (message, NULL); - g_free (message); } /* Initialize a gpgme_ctx_t for use with GPA. */ gpgme_ctx_t gpa_gpgme_new (void) { gpgme_ctx_t ctx; gpg_error_t err; /* g_assert (!"using gpa_gpgme_new"); */ err = gpgme_new (&ctx); if (gpg_err_code (err) != GPG_ERR_NO_ERROR) gpa_gpgme_error (err); if (! cms_hack) gpgme_set_passphrase_cb (ctx, gpa_passphrase_cb, NULL); return ctx; } /* Write the contents of the gpgme_data_t object to the file. Receives a filehandle instead of the filename, so that the caller can make sure the file is accesible before putting anything into data. This is only used for a TMP file, thus it is okay to terminate the application on error. */ void dump_data_to_file (gpgme_data_t data, FILE *file) { char buffer[128]; int nread; nread = gpgme_data_seek (data, 0, SEEK_SET); if (nread == -1) { gpa_window_error (strerror (errno), NULL); exit (EXIT_FAILURE); } while ((nread = gpgme_data_read (data, buffer, sizeof (buffer))) > 0) fwrite (buffer, nread, 1, file); if (nread == -1) { gpa_window_error (strerror (errno), NULL); exit (EXIT_FAILURE); } return; } static char * check_overwriting (const char *filename, GtkWidget *parent) { GtkWidget *dialog; int response; GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_SAVE; char *filename_used = xstrdup (filename); while (1) { /* If the file exists, ask before overwriting. */ if (! g_file_test (filename_used, G_FILE_TEST_EXISTS)) return filename_used; dialog = gtk_message_dialog_new (GTK_WINDOW (parent), GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_NONE, _("The file %s already exists.\n" "Do you want to overwrite it?"), filename_used); gtk_dialog_add_buttons (GTK_DIALOG (dialog), _("_Yes"), GTK_RESPONSE_YES, _("_No"), GTK_RESPONSE_NO, _("_Use a different filename"), 1, NULL); response = gtk_dialog_run (GTK_DIALOG (dialog)); gtk_widget_destroy (dialog); if (response == GTK_RESPONSE_YES) return filename_used; if (response == GTK_RESPONSE_NO) { xfree (filename_used); return NULL; } /* Use a different filename. */ dialog = gtk_file_chooser_dialog_new ("Open File", GTK_WINDOW (parent), action, _("_Cancel"), GTK_RESPONSE_CANCEL, _("_Open"), GTK_RESPONSE_ACCEPT, NULL); response = gtk_dialog_run (GTK_DIALOG (dialog)); if (response == GTK_RESPONSE_ACCEPT) { GtkFileChooser *chooser = GTK_FILE_CHOOSER (dialog); filename_used = gtk_file_chooser_get_filename (chooser); } gtk_widget_destroy (dialog); } } /* Not really a gpgme function, but needed in most places dump_data_to_file is used. Opens a file for writing, asking the user to overwrite if it exists and reporting any errors. Returns NULL on failure, but you can assume the user has been informed of the error (or maybe he just didn't want to overwrite!). */ FILE * gpa_fopen (const char *filename, GtkWidget *parent, char **filename_used) { FILE *target; *filename_used = check_overwriting (filename, parent); if (! *filename_used) return NULL; target = g_fopen (*filename_used, "w"); if (!target) { gchar *message; message = g_strdup_printf ("%s: %s", *filename_used, strerror(errno)); gpa_window_error (message, parent); g_free (message); } return target; } int gpa_open_output_direct (const char *filename, gpgme_data_t *data, GtkWidget *parent) { int target = -1; gpg_error_t err; target = g_open (filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666); if (target == -1) { gchar *message; message = g_strdup_printf ("%s: %s", filename, strerror(errno)); gpa_window_error (message, parent); g_free (message); } err = gpgme_data_new_from_fd (data, target); if (gpg_err_code (err) != GPG_ERR_NO_ERROR) { close (target); target = -1; } return target; } int gpa_open_output (const char *filename, gpgme_data_t *data, GtkWidget *parent, char **filename_used) { int res; *filename_used = check_overwriting (filename, parent); if (! *filename_used) return -1; res = gpa_open_output_direct (*filename_used, data, parent); return res; } int gpa_open_input (const char *filename, gpgme_data_t *data, GtkWidget *parent) { gpg_error_t err; int target = -1; target = g_open (filename, O_RDONLY | O_BINARY, 0); if (target == -1) { gchar *message; message = g_strdup_printf ("%s: %s", filename, strerror(errno)); gpa_window_error (message, parent); g_free (message); } err = gpgme_data_new_from_fd (data, target); if (gpg_err_code (err) != GPG_ERR_NO_ERROR) { close (target); target = -1; } return target; } /* Do a gpgme_data_new_from_file and report any GPGME_File_Error to the user. */ gpg_error_t gpa_gpgme_data_new_from_file (gpgme_data_t *data, const char *filename, GtkWidget *parent) { gpg_error_t err; err = gpgme_data_new_from_file (data, filename, 1); if (gpg_err_code_to_errno (err) != 0) { gchar *message; message = g_strdup_printf ("%s: %s", filename, strerror(errno)); gpa_window_error (message, NULL); g_free (message); } return err; } /* Write the contents of the gpgme_data_t into the clipboard. Assumes that the data is ASCII. Return 0 on success. */ int dump_data_to_clipboard (gpgme_data_t data, GtkClipboard *clipboard) { char buffer[512]; int nread; gchar *text = NULL; size_t len = 0; nread = gpgme_data_seek (data, 0, SEEK_SET); if (nread == -1) { gpa_window_error (strerror (errno), NULL); return -1; } while ((nread = gpgme_data_read (data, buffer, sizeof (buffer))) > 0) { text = g_realloc (text, len + nread + 1); strncpy (text + len, buffer, nread); len += nread; } if (nread == -1) { gpa_window_error (strerror (errno), NULL); return -1; } gtk_clipboard_set_text (clipboard, text, (int)len); g_free (text); return 0; } /* Assemble the parameter string for gpgme_op_genkey for GnuPG. We don't need worry about the user ID being UTF-8 as long as we are using GTK+2, because all user input is UTF-8 in it. */ static gchar * build_genkey_parms (gpa_keygen_para_t *params) { gchar *string; const char *key_algo; gchar *subkeys = NULL; gchar *name = NULL; gchar *email = NULL; gchar *comment = NULL; gchar *expire = NULL; /* Choose which keys and subkeys to generate. */ switch (params->algo) { case GPA_KEYGEN_ALGO_RSA_RSA: key_algo = "RSA"; subkeys = g_strdup_printf ("Subkey-Type: RSA\n" "Subkey-Length: %d\n" "Subkey-Usage: encrypt\n", params->keysize); break; case GPA_KEYGEN_ALGO_RSA_ELGAMAL: key_algo = "RSA"; subkeys = g_strdup_printf ("Subkey-Type: ELG-E\n" "Subkey-Length: %d\n" "Subkey-Usage: encrypt\n", params->keysize); break; case GPA_KEYGEN_ALGO_RSA: key_algo = "RSA"; break; case GPA_KEYGEN_ALGO_DSA_ELGAMAL: key_algo = "DSA"; subkeys = g_strdup_printf ("Subkey-Type: ELG-E\n" "Subkey-Length: %i\n" "Subkey-Usage: encrypt\n", params->keysize); break; case GPA_KEYGEN_ALGO_DSA: key_algo = "DSA"; break; default: /* Can't happen */ return NULL; } /* Construct the user ID. */ if (params->name && params->name[0]) name = g_strdup_printf ("Name-Real: %s\n", params->name); if (params->email && params->email[0]) email = g_strdup_printf ("Name-Email: %s\n", params->email); if (params->comment && params->comment[0]) comment = g_strdup_printf ("Name-Comment: %s\n", params->comment); /* Build the expiration date string if needed */ if (g_date_valid (¶ms->expire)) expire = g_strdup_printf ("Expire-Date: %04d-%02d-%02d\n", g_date_get_year (¶ms->expire), g_date_get_month (¶ms->expire), g_date_get_day (¶ms->expire)); /* Assemble the final parameter string */ string = g_strdup_printf ("\n" "Key-Type: %s\n" "Key-Length: %i\n" "Key-Usage: sign\n" "%s" /* Subkeys */ "%s" /* Name */ "%s" /* Email */ "%s" /* Comment */ "%s" /* Expiration date */ "%%ask-passphrase\n" "\n", key_algo, params->keysize, subkeys? subkeys : "", name? name:"", email? email : "", comment? comment : "", expire? expire : ""); /* Free auxiliary strings if they are not empty */ g_free (subkeys); g_free (name); g_free (email); g_free (comment); g_free (expire); return string; } /* Begin generation of a key with the given parameters. It prepares the parameters required by GPGME and returns whatever gpgme_op_genkey_start returns. */ gpg_error_t gpa_generate_key_start (gpgme_ctx_t ctx, gpa_keygen_para_t *params) { gchar *parm_string; gpg_error_t err; parm_string = build_genkey_parms (params); err = gpgme_op_genkey_start (ctx, parm_string, NULL, NULL); g_free (parm_string); return err; } /* Retrieve the path to the GPG executable. */ static const gchar * get_gpg_path (void) { gpgme_engine_info_t engine; gpgme_get_engine_info (&engine); while (engine) { if (engine->protocol == GPGME_PROTOCOL_OpenPGP) return engine->file_name; engine = engine->next; } return NULL; } /* Retrieve the path to the GPGSM executable. */ static const gchar * get_gpgsm_path (void) { gpgme_engine_info_t engine; gpgme_get_engine_info (&engine); while (engine) { if (engine->protocol == GPGME_PROTOCOL_CMS) return engine->file_name; engine = engine->next; } return NULL; } /* Retrieve the path to the GPGCONF executable. */ static const gchar * get_gpgconf_path (void) { gpgme_engine_info_t engine; gpgme_get_engine_info (&engine); while (engine) { if (engine->protocol == GPGME_PROTOCOL_GPGCONF) return engine->file_name; engine = engine->next; } return NULL; } /* Retrieve the path to the GPG-CONNECT-AGENT executable. Note that the caller must free the returned string. */ static char * get_gpg_connect_agent_path (void) { const char *gpgconf; char *fname, *p; gpgconf = get_gpgconf_path (); if (!gpgconf) return NULL; #ifdef G_OS_WIN32 # define NEWNAME "gpg-connect-agent.exe" #else # define NEWNAME "gpg-connect-agent" #endif fname = g_malloc (strlen (gpgconf) + strlen (NEWNAME) + 1); strcpy (fname, gpgconf); #ifdef G_OS_WIN32 for (p=fname; *p; p++) if (*p == '\\') *p = '/'; #endif /*G_OS_WIN32*/ p = strrchr (fname, '/'); if (p) p++; else p = fname; strcpy (p, NEWNAME); #undef NEWNAME return fname; } /* Backup a key. It exports both the public and secret keys to a file. IS_X509 tells the function that the fingerprint is from an X.509 key. Returns TRUE on success and FALSE on error. It displays errors to the user. */ gboolean gpa_backup_key (const gchar *fpr, const char *filename, int is_x509) { const char *header_argv[] = { "", "--batch", "--no-tty", "--fingerprint", (char*) fpr, NULL }; const char *pub_argv[] = { "", "--batch", "--no-tty", "--armor", "--export", (char*) fpr, NULL }; const char *sec_argv[] = { "", "--batch", "--no-tty", "--armor", "--export-secret-key", (char*) fpr, NULL }; const char *seccms_argv[] = { "", "--batch", "--no-tty", "--armor", "--export-secret-key-p12", (char*) fpr, NULL }; gpg_error_t err; FILE *fp; gpgme_data_t dfp = NULL; const char *pgm; gpgme_ctx_t ctx = NULL; int result = FALSE; /* Get the gpg path. */ if (is_x509) pgm = get_gpgsm_path (); else pgm = get_gpg_path (); g_return_val_if_fail (pgm && *pgm, FALSE); /* Open the file */ { mode_t mask = umask (0077); fp = g_fopen (filename, "w"); umask (mask); } if (!fp) { gchar message[256]; g_snprintf (message, sizeof(message), "%s: %s", filename, strerror(errno)); gpa_window_error (message, NULL); return FALSE; } fputs (_( "************************************************************************\n" "* WARNING: This file is a backup of your secret key. Please keep it in *\n" "* a safe place. *\n" "************************************************************************\n" "\n"), fp); fputs (_("The key backed up in this file is:\n\n"), fp); fflush (fp); err = gpgme_data_new_from_stream (&dfp, fp); if (err) { g_message ("error creating data object '%s': %s", filename, gpg_strerror (err)); goto leave; } ctx = gpa_gpgme_new (); gpgme_set_protocol (ctx, GPGME_PROTOCOL_SPAWN); err = gpgme_op_spawn (ctx, pgm, header_argv, NULL, dfp, NULL, GPGME_SPAWN_DETACHED|GPGME_SPAWN_ALLOW_SET_FG); if (err) { g_message ("error running '%s' (1): %s", pgm, gpg_strerror (err)); goto leave; } gpgme_data_write (dfp, "\n", 1); err = gpgme_op_spawn (ctx, pgm, pub_argv, NULL, dfp, NULL, GPGME_SPAWN_DETACHED|GPGME_SPAWN_ALLOW_SET_FG); if (err) { g_message ("error running '%s' (2): %s", pgm, gpg_strerror (err)); goto leave; } gpgme_data_write (dfp, "\n", 1); err = gpgme_op_spawn (ctx, pgm, is_x509? seccms_argv : sec_argv, NULL, dfp, NULL, GPGME_SPAWN_DETACHED|GPGME_SPAWN_ALLOW_SET_FG); if (err) { g_message ("error running '%s' (3): %s", pgm, gpg_strerror (err)); goto leave; } result = TRUE; leave: gpgme_release (ctx); gpgme_data_release (dfp); fclose (fp); return result; } void gpa_keygen_para_free (gpa_keygen_para_t *params) { if (params) { g_free (params->name); g_free (params->email); g_free (params->comment); g_free (params->r_error_desc); g_free (params); } } gpa_keygen_para_t * gpa_keygen_para_new (void) { gpa_keygen_para_t *params = xcalloc (1, sizeof *params); g_date_clear (¶ms->expire, 1); return params; } /* Ownertrust strings. */ const gchar * gpa_key_ownertrust_string (gpgme_key_t key) { if (key->protocol == GPGME_PROTOCOL_CMS) return ""; switch (key->owner_trust) { case GPGME_VALIDITY_UNKNOWN: case GPGME_VALIDITY_UNDEFINED: default: return _("Unknown"); break; case GPGME_VALIDITY_NEVER: return _("Never"); break; case GPGME_VALIDITY_MARGINAL: return _("Marginal"); break; case GPGME_VALIDITY_FULL: return _("Full"); break; case GPGME_VALIDITY_ULTIMATE: return _("Ultimate"); break; } } /* Key validity strings. */ const gchar * gpa_key_validity_string (gpgme_key_t key) { if (!key->uids) return _("Unknown"); switch (key->uids->validity) { case GPGME_VALIDITY_UNKNOWN: case GPGME_VALIDITY_UNDEFINED: case GPGME_VALIDITY_NEVER: case GPGME_VALIDITY_MARGINAL: default: if (key->subkeys->revoked) return _("Revoked"); else if (key->subkeys->expired) return _("Expired"); else if (key->subkeys->disabled) return _("Disabled"); else if (key->subkeys->invalid) return _("Incomplete"); else return _("Unknown"); break; case GPGME_VALIDITY_FULL: case GPGME_VALIDITY_ULTIMATE: return _("Fully Valid"); } } /* UID validity strings. */ const gchar * gpa_uid_validity_string (gpgme_user_id_t uid) { if (uid->revoked) return _("Revoked"); else if (uid->invalid) return _("Invalid"); switch (uid->validity) { case GPGME_VALIDITY_UNKNOWN: case GPGME_VALIDITY_UNDEFINED:return _("Unknown"); case GPGME_VALIDITY_NEVER: return _("Faked"); case GPGME_VALIDITY_MARGINAL: return _("Marginal"); case GPGME_VALIDITY_FULL: return _("Fully"); case GPGME_VALIDITY_ULTIMATE: return _("Ultimate"); default: return "[?]"; } } static GtkWidget * passphrase_question_label (const char *uid_hint, const char *passphrase_info, int prev_was_bad) { GtkWidget *label; gchar *input; gchar *text; gchar *keyid, *userid; gint i; /* Just in case this is called without a user id hint we return a simple text to avoid a crash. */ if (!uid_hint) return gtk_label_new ("Passphrase?"); input = g_strdup (uid_hint); /* The first word in the hint is the key ID */ keyid = input; for (i = 0; input[i] != ' '; i++) { } input[i++] = '\0'; /* The rest of the hint is the user ID */ userid = input+i; /* Build the label widget */ if (!prev_was_bad) { text = g_strdup_printf ("%s\n\n%s %s\n%s %s", _("Please enter the passphrase for" " the following key:"), _("User Name:"), userid, _("Key ID:"), keyid); } else { text = g_strdup_printf ("%s\n\n%s %s\n%s %s", _("Wrong passphrase, please try again:"), _("User Name:"), userid, _("Key ID:"), keyid); } label = gtk_label_new (text); g_free (input); g_free (text); return label; } /* This is the function called by GPGME when it wants a passphrase. */ gpg_error_t gpa_passphrase_cb (void *hook, const char *uid_hint, const char *passphrase_info, int prev_was_bad, int fd) { GtkWidget * dialog; GtkWidget * hbox; GtkWidget * vbox; GtkWidget * entry; GtkWidget * pixmap; GtkResponseType response; gchar *passphrase; dialog = gtk_dialog_new_with_buttons (_("Enter Passphrase"), NULL, GTK_DIALOG_MODAL, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL); gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), GTK_RESPONSE_OK, GTK_RESPONSE_CANCEL, -1); hbox = gtk_hbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), hbox, TRUE, FALSE, 10); pixmap = gtk_image_new_from_stock (GTK_STOCK_DIALOG_QUESTION, GTK_ICON_SIZE_DIALOG); gtk_box_pack_start (GTK_BOX (hbox), pixmap, TRUE, FALSE, 10); vbox = gtk_vbox_new (TRUE, 0); gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, FALSE, 10); /* The default button is OK */ gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); /* Set the contents of the dialog */ gtk_box_pack_start_defaults (GTK_BOX (vbox), passphrase_question_label (uid_hint, passphrase_info, prev_was_bad)); entry = gtk_entry_new (); gtk_entry_set_visibility (GTK_ENTRY (entry), FALSE); gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE); gtk_box_pack_start (GTK_BOX (vbox), entry, TRUE, FALSE, 10); gtk_widget_grab_focus (entry); /* Run the dialog */ gtk_widget_show_all (dialog); response = gtk_dialog_run (GTK_DIALOG (dialog)); passphrase = g_strdup_printf ("%s\n", gtk_entry_get_text (GTK_ENTRY (entry))); gtk_widget_destroy (dialog); if (response == GTK_RESPONSE_OK) { int passphrase_len = strlen (passphrase); #ifdef G_OS_WIN32 DWORD res; if (WriteFile ((HANDLE) _get_osfhandle (fd), passphrase, passphrase_len, &res, NULL) == 0 || res < passphrase_len) { g_free (passphrase); return gpg_error (gpg_err_code_from_errno (EIO)); } else return gpg_error (GPG_ERR_NO_ERROR); #else int res; res = write (fd, passphrase, passphrase_len); g_free (passphrase); if (res == -1) return gpg_error (gpg_err_code_from_errno (errno)); else if (res < passphrase_len) return gpg_error (gpg_err_code_from_errno (EIO)); else return gpg_error (GPG_ERR_NO_ERROR); #endif } else { g_free (passphrase); return gpg_error (GPG_ERR_CANCELED); } } /* Convenience functions to access key attributes, which need to be filtered before being displayed to the user. */ /* Return the user ID, making sure it is properly UTF-8 encoded. Allocates a new string, which must be freed with g_free (). */ static gchar * string_to_utf8 (const gchar *string) { const char *s; if (!string) return NULL; /* Due to a bug in old and not so old PGP versions user IDs have been copied verbatim into the key. Thus many users with Umlauts et al. in their name will see their names garbled. Although this is not an issue for me (;-)), I have a couple of friends with Umlauts in their name, so let's try to make their life easier by detecting invalid encodings and convert that to Latin-1. We use this even for X.509 because it may make things even better given all the invalid encodings often found in X.509 certificates. */ for (s = string; *s && !(*s & 0x80); s++) ; if (*s && ((s[1] & 0xc0) == 0x80) && ( ((*s & 0xe0) == 0xc0) || ((*s & 0xf0) == 0xe0) || ((*s & 0xf8) == 0xf0) || ((*s & 0xfc) == 0xf8) || ((*s & 0xfe) == 0xfc)) ) { /* Possible utf-8 character followed by continuation byte. Although this might still be Latin-1 we better assume that it is valid utf-8. */ return g_strdup (string); } else if (*s && !strchr (string, 0xc3)) { /* No 0xC3 character in the string; assume that it is Latin-1. */ return g_convert (string, -1, "UTF-8", "ISO-8859-1", NULL, NULL, NULL); } else { /* Everything else is assumed to be UTF-8. We do this even that we know the encoding is not valid. However as we only test the first non-ascii character, valid encodings might follow. */ return g_strdup (string); } } gchar * gpa_gpgme_key_get_userid (gpgme_user_id_t uid) { gchar *uid_utf8; if (!uid) return g_strdup (_("[None]")); uid_utf8 = string_to_utf8 (uid->uid); /* Tag revoked UID's*/ if (uid->revoked) { /* FIXME: I think, this code should be simply disabled. Even if the uid U is revoked, it is "U", not "[Revoked] U". Side note: adding this prefix obviously breaks sorting by uid. -moritz */ gchar *tmp = g_strdup_printf ("[%s] %s", _("Revoked"), uid_utf8); g_free (uid_utf8); uid_utf8 = tmp; } return uid_utf8; } /* Return the key fingerprint, properly formatted according to the key version. Allocates a new string, which must be freed with g_free(). This is based on code from GPAPA's extract_fingerprint. */ gchar * gpa_gpgme_key_format_fingerprint (const char *fpraw) { if (strlen (fpraw) == 32 ) /* v3 */ { char *fp = g_malloc (strlen (fpraw) + 16 + 1); const char *r = fpraw; char *q = fp; gint c = 0; while (*r) { *q++ = *r++; c++; if (c < 32) { if (c % 2 == 0) *q++ = ' '; if (c % 16 == 0) *q++ = ' '; } } *q = 0; return fp; } else { char *fp = g_malloc (strlen (fpraw) + 10 + 1); const char *r = fpraw; char *q = fp; gint c = 0; while (*r) { *q++ = *r++; c++; if (c < 40) { if (c % 4 == 0) *q++ = ' '; if (c % 20 == 0) *q++ = ' '; } } *q = 0; return fp; } } /* Return the short key ID of the indicated key. The returned string is valid as long as the key is valid. */ const gchar * gpa_gpgme_key_get_short_keyid (gpgme_key_t key) { const char *keyid = key->subkeys->keyid; if (!keyid) return NULL; else return keyid + 8; } /* Convenience function to access key signature attibutes, much like the previous ones. */ /* Return the user ID, making sure it is properly UTF-8 encoded. Allocates a new string, which must be freed with g_free(). */ gchar * gpa_gpgme_key_sig_get_userid (gpgme_key_sig_t sig) { if (!sig->uid || !*sig->uid) /* Duplicate it to make sure it can be g_free'd. */ return g_strdup (_("[Unknown user ID]")); else return string_to_utf8 (sig->uid); } /* Return the short key ID of the indicated key. The returned string is valid as long as the key is valid. */ const gchar * gpa_gpgme_key_sig_get_short_keyid (gpgme_key_sig_t sig) { return sig->keyid + 8; } /* Return a string with the status of the key signature. */ const gchar * gpa_gpgme_key_sig_get_sig_status (gpgme_key_sig_t sig, GHashTable *revoked) { const gchar *status; switch (sig->status) { case GPGME_SIG_STAT_GOOD: status = _("Valid"); break; case GPGME_SIG_STAT_BAD: status = _("Bad"); default: status = _("Unknown"); } if (sig->expired) { status = _("Expired"); } else if (g_hash_table_lookup (revoked, sig->keyid)) { status = _("Revoked"); } return status; } /* Return a string with the level of the key signature. */ const gchar * gpa_gpgme_key_sig_get_level (gpgme_key_sig_t sig) { switch (sig->sig_class) { case 0x10: return _("Generic"); break; case 0x11: return _("Persona"); break; case 0x12: return _("Casual"); break; case 0x13: return _("Positive"); break; default: return _("Unknown"); break; } } /* Return a human readable string with the status of the signature SIG. If R_KEYDESC is not NULL, the description of the key (e.g.. the user ID) will be stored as a malloced string at that address; if no key is known, NULL will be stored. If R_KEY is not NULL, a key object will be stored at that address; NULL if no key is known. CTX is used as helper to figure out the key description. */ char * gpa_gpgme_get_signature_desc (gpgme_ctx_t ctx, gpgme_signature_t sig, char **r_keydesc, gpgme_key_t *r_key) { gpgme_key_t key = NULL; char *keydesc = NULL; char *sigdesc; const char *sigstatus; sigstatus = sig->status? gpg_strerror (sig->status) : ""; if (sig->fpr && ctx) { gpgme_get_key (ctx, sig->fpr, &key, 0); if (key) keydesc = gpa_gpgme_key_get_userid (key->uids); } if (sig->summary & GPGME_SIGSUM_RED) { if (keydesc && *sigstatus) sigdesc = g_strdup_printf (_("Bad signature by %s: %s"), keydesc, sigstatus); else if (keydesc) sigdesc = g_strdup_printf (_("Bad signature by %s"), keydesc); else if (sig->fpr && *sigstatus) sigdesc = g_strdup_printf (_("Bad signature by unknown key " "%s: %s"), sig->fpr, sigstatus); else if (sig->fpr) sigdesc = g_strdup_printf (_("Bad signature by unknown key " "%s"), sig->fpr); else if (*sigstatus) sigdesc = g_strdup_printf (_("Bad signature by unknown key: " "%s"), sigstatus); else sigdesc = g_strdup_printf (_("Bad signature by unknown key")); } else if (sig->summary & GPGME_SIGSUM_VALID) { if (keydesc && *sigstatus) sigdesc = g_strdup_printf (_("Good signature by %s: %s"), keydesc, sigstatus); else if (keydesc) sigdesc = g_strdup_printf (_("Good signature by %s"), keydesc); else if (sig->fpr && *sigstatus) sigdesc = g_strdup_printf (_("Good signature by unknown key " "%s: %s"), sig->fpr, sigstatus); else if (sig->fpr) sigdesc = g_strdup_printf (_("Good signature by unknown key " "%s"), sig->fpr); else if (*sigstatus) sigdesc = g_strdup_printf (_("Good signature by unknown key: " "%s"), sigstatus); else sigdesc = g_strdup_printf (_("Good signature by unknown key")); } else { if (keydesc && *sigstatus) sigdesc = g_strdup_printf (_("Uncertain signature by %s: %s"), keydesc, sigstatus); else if (keydesc) sigdesc = g_strdup_printf (_("Uncertain signature by %s"), keydesc); else if (sig->fpr && *sigstatus) sigdesc = g_strdup_printf (_("Uncertain signature by unknown key " "%s: %s"), sig->fpr, sigstatus); else if (sig->fpr) sigdesc = g_strdup_printf (_("Uncertain signature by unknown key " "%s"), sig->fpr); else if (*sigstatus) sigdesc = g_strdup_printf (_("Uncertain signature by unknown " "key: %s"), sigstatus); else sigdesc = g_strdup_printf (_("Uncertain signature by unknown " "key")); } if (r_keydesc) *r_keydesc = keydesc; else g_free (keydesc); if (r_key) *r_key = key; else gpgme_key_unref (key); return sigdesc; } /* Return a string listing the capabilities of a key. */ const gchar * gpa_get_key_capabilities_text (gpgme_key_t key) { if (key->can_certify) { if (key->can_sign) { if (key->can_encrypt) return _("The key can be used for certification, signing " "and encryption."); else return _("The key can be used for certification and " "signing, but not for encryption."); } else { if (key->can_encrypt) return _("The key can be used for certification and " "encryption."); else return _("The key can be used only for certification."); } } else { if (key->can_sign) { if (key->can_encrypt) return _("The key can be used only for signing and " "encryption, but not for certification."); else return _("The key can be used only for signing."); } else { if (key->can_encrypt) return _("The key can be used only for encryption."); else /* Can't happen, even for subkeys. */ return _("This key is useless."); } } } /* Update the result structure RESULT using the gpgme result INFO and the FILES and BAD_FILES counter. */ void gpa_gpgme_update_import_results (gpa_import_result_t result, unsigned int files, unsigned int bad_files, gpgme_import_result_t info) { result->files += files; result->bad_files += bad_files; if (info) { result->considered += info->considered; result->imported += info->imported; result->unchanged += info->unchanged; result->secret_read += info->secret_read; result->secret_imported += info->secret_imported; result->secret_unchanged += info->secret_unchanged; } } void gpa_gpgme_show_import_results (GtkWidget *parent, gpa_import_result_t result) { char *buf1, *buf2; if (result->files) buf2 = g_strdup_printf (_("%u file(s) read\n" "%u file(s) with errors"), result->files, result->bad_files); else buf2 = NULL; if (!result->considered) - gpa_show_warning (parent, "%s%s%s", + gpa_show_warn (parent, NULL, "%s%s%s", _("No keys were found."), buf2? "\n":"", buf2? buf2:""); else { buf1 = g_strdup_printf (_("%i public keys read\n" "%i public keys imported\n" "%i public keys unchanged\n" "%i secret keys read\n" "%i secret keys imported\n" "%i secret keys unchanged"), result->considered, result->imported, result->unchanged, result->secret_read, result->secret_imported, result->secret_unchanged); gpa_show_info (parent, "%s%s%s", buf1, buf2? "\n":"", buf2? buf2:""); g_free (buf1); } g_free (buf2); } /* Return a copy of the key array. */ gpgme_key_t * gpa_gpgme_copy_keyarray (gpgme_key_t *keys) { gpgme_key_t *newarray; int idx; if (!keys) return NULL; for (idx=0; keys[idx]; idx++) ; idx++; newarray = g_new (gpgme_key_t, idx); for (idx=0; keys[idx]; idx++) { gpgme_key_ref (keys[idx]); newarray[idx] = keys[idx]; } newarray[idx] = NULL; return newarray; } /* Release all keys in the array KEYS as well as ARRAY itself. */ void gpa_gpgme_release_keyarray (gpgme_key_t *keys) { if (keys) { int idx; for (idx=0; keys[idx]; idx++) gpgme_key_unref (keys[idx]); g_free (keys); } } /* Read the next number in the version string STR and return it in *NUMBER. Return a pointer to the tail of STR after parsing, or *NULL if the version string was invalid. */ static const char * parse_version_number (const char *str, int *number) { #define MAXVAL ((INT_MAX - 10) / 10) int val = 0; /* Leading zeros are not allowed. */ if (*str == '0' && isascii (str[1]) && isdigit (str[1])) return NULL; while (isascii (*str) && isdigit (*str) && val <= MAXVAL) { val *= 10; val += *(str++) - '0'; } *number = val; return val > MAXVAL ? NULL : str; #undef MAXVAL } /* Parse the version string STR in the format MAJOR.MINOR.MICRO (for example, 9.3.2) and return the components in MAJOR, MINOR and MICRO as integers. The function returns the tail of the string that follows the version number. This might be te empty string if there is nothing following the version number, or a patchlevel. The function returns NULL if the version string is not valid. */ static const char * parse_version_string (const char *str, int *major, int *minor, int *micro) { str = parse_version_number (str, major); if (!str || *str != '.') return NULL; str++; str = parse_version_number (str, minor); if (!str || *str != '.') return NULL; str++; str = parse_version_number (str, micro); if (!str) return NULL; /* A patchlevel might follow. */ return str; } /* Return true if MY_VERSION is at least REQ_VERSION, and false otherwise. */ static int compare_version_strings (const char *my_version, const char *rq_version) { int my_major, my_minor, my_micro; int rq_major, rq_minor, rq_micro; const char *my_plvl, *rq_plvl; if (!rq_version) return 1; if (!my_version) return 0; my_plvl = parse_version_string (my_version, &my_major, &my_minor, &my_micro); if (!my_plvl) return 0; rq_plvl = parse_version_string (rq_version, &rq_major, &rq_minor, &rq_micro); if (!rq_plvl) return 0; if (my_major > rq_major || (my_major == rq_major && my_minor > rq_minor) || (my_major == rq_major && my_minor == rq_minor && my_micro > rq_micro) || (my_major == rq_major && my_minor == rq_minor && my_micro == rq_micro && strcmp (my_plvl, rq_plvl) >= 0)) return 1; return 0; } /* Try switching to the gpg2 backend or the one given by filename. */ /* Return 1 if the gpg engine has at least version NEED_VERSION, otherwise 0. */ int is_gpg_version_at_least (const char *need_version) { gpgme_engine_info_t engine; gpgme_get_engine_info (&engine); while (engine) { if (engine->protocol == GPGME_PROTOCOL_OpenPGP) return !!compare_version_strings (engine->version, need_version); engine = engine->next; } return 0; /* No gpg-engine available. */ } /* Structure used to communicate with gpg_simple_stdio_cb. */ struct gpg_simple_stdio_parm_s { gboolean (*cb)(void *opaque, char *line); void *cb_arg; GString *string; int only_status_lines; }; /* Helper for gpa_start_simple_gpg_command. */ static gboolean gpg_simple_stdio_cb (GIOChannel *channel, GIOCondition condition, void *user_data) { struct gpg_simple_stdio_parm_s *parm = user_data; GIOStatus status; char *line, *p; if ((condition & G_IO_IN)) { /* We don't use a while but an if because that allows to update progress bars nicely. A bit slower, but no real problem. */ if ((status = g_io_channel_read_line_string (channel, parm->string, NULL, NULL)) == G_IO_STATUS_NORMAL) { line = parm->string->str; /* Strip line terminator. */ p = strchr (line, '\n'); if (p) { if (p > line && p[-1] == '\r') p[-1] = 0; else *p = 0; } /* We care only about status lines. */ if (parm->only_status_lines && !strncmp (line, "[GNUPG:] ", 9)) { line += 9; /* Call user callback. */ if (parm->cb && !parm->cb (parm->cb_arg, line)) { /* User requested EOF. */ goto cleanup; } /* Return direct so that we do not run into the G_IO_HUP check. This is required to read all buffered input. */ return TRUE; /* Keep on watching this channel. */ } else if (!parm->only_status_lines) { /* Call user callback. */ if (parm->cb && !parm->cb (parm->cb_arg, line)) { /* User requested EOF. */ goto cleanup; } return TRUE; /* Keep on watching this channel. */ } } if (status != G_IO_STATUS_NORMAL && status != G_IO_STATUS_AGAIN ) { /* Error or EOF. */ goto cleanup; } } if ((condition & G_IO_HUP)) { goto cleanup; } return TRUE; /* Keep on watching this channel. */ cleanup: if (parm->cb) parm->cb (parm->cb_arg, NULL); /* Tell user about EOF. */ g_string_free (parm->string, TRUE); xfree (parm); g_io_channel_unref (channel); /* Close channel. */ return FALSE; /* Remove us from the watch list. */ } /* Run gpg asynchronously with the given arguments and return a gpg error code on error. The list of arguments are all of type (const char*) and end with a NULL argument (FIRST_ARG may already be NULL, but that does not make any sense). STDIN and STDOUT are connected to /dev/null. No more than 20 arguments may be given. Because the protocol GPGME_PROTOCOL_ASSUAN makes no sense here, it is used to call gpg-connect-agent. If the function returns success the provided callback CB is called for each line received on stdout (respective stderr if USE_STADERR is true). EOF is send to this callback by passing a LINE as NULL. The callback may use this for cleanup. If the callback returns FALSE, an EOF is forced so that the callback is called once more with LINE set to NULL. This function is used to run gpgsm --learn-card gpg-connect-agent NOP /bye The problem is that under Windows g_spawn does not allow to specify flags for the underlying CreateProcess. Thus it is not possible to create a detached process (i.e. without a console); the result is that a console window pops up. I can see two solutions: (1) Use a wrapper process to start them detached, or (2) move the required function into GPGME and use that new API. */ gpg_error_t gpa_start_simple_gpg_command (gboolean (*cb)(void *opaque, char *line), void *cb_arg, gpgme_protocol_t protocol, int use_stderr, const char *first_arg, ...) { char *argv[24]; int argc; int fd_stdio; GIOChannel *channel; struct gpg_simple_stdio_parm_s *parm = NULL; char *freeme = NULL; if (protocol == GPGME_PROTOCOL_OpenPGP) argv[0] = (char*)get_gpg_path (); else if (protocol == GPGME_PROTOCOL_CMS) argv[0] = (char*)get_gpgsm_path (); else if (protocol == GPGME_PROTOCOL_GPGCONF) argv[0] = (char*)get_gpgconf_path (); else if (protocol == GPGME_PROTOCOL_ASSUAN) argv[0] = freeme = get_gpg_connect_agent_path (); else argv[0] = NULL; if (!argv[0]) { gpa_window_error (_("A required engine component is not installed."), NULL); return gpg_error (GPG_ERR_INV_ARG); } argc = 1; if (protocol != GPGME_PROTOCOL_GPGCONF && protocol != GPGME_PROTOCOL_ASSUAN) { argv[argc++] = (char*)"--status-fd"; argv[argc++] = (char*)"2"; } argv[argc++] = (char*)first_arg; if (first_arg) { va_list arg_ptr; const char *s; va_start (arg_ptr, first_arg); while (argc < DIM (argv)-1 && (s=va_arg (arg_ptr, const char *))) argv[argc++] = (char*)s; va_end (arg_ptr); argv[argc] = NULL; g_return_val_if_fail (argc < DIM (argv), gpg_error (GPG_ERR_INV_ARG)); } parm = g_try_malloc (sizeof *parm); if (!parm) return gpg_error_from_syserror (); parm->cb = cb; parm->cb_arg = cb_arg; parm->string = g_string_sized_new (200); parm->only_status_lines = use_stderr; if (!g_spawn_async_with_pipes (NULL, argv, NULL, (use_stderr ? G_SPAWN_STDOUT_TO_DEV_NULL : G_SPAWN_STDERR_TO_DEV_NULL), NULL, NULL, NULL, NULL, use_stderr? NULL : &fd_stdio, use_stderr? &fd_stdio : NULL, NULL)) { gpa_window_error (_("Calling the crypto engine program failed."), NULL); xfree (parm); g_free (freeme); return gpg_error (GPG_ERR_GENERAL); } g_free (freeme); #ifdef G_OS_WIN32 channel = g_io_channel_win32_new_fd (fd_stdio); #else channel = g_io_channel_unix_new (fd_stdio); #endif g_io_channel_set_encoding (channel, NULL, NULL); /* Note that we need a buffered channel, so that we can use the read line function. */ g_io_channel_set_close_on_unref (channel, TRUE); /* Create a watch for the channel. */ if (!g_io_add_watch (channel, (G_IO_IN|G_IO_HUP), gpg_simple_stdio_cb, parm)) { g_debug ("error creating watch for gpg command"); g_io_channel_unref (channel); xfree (parm); return gpg_error (GPG_ERR_GENERAL); } return 0; } /* Try to start the gpg-agent if it has not yet been started. Starting the agent works in the background. Thus if the function returns, it is not sure that the agent is now running. */ void gpa_start_agent (void) { gpg_error_t err; gpgme_ctx_t ctx; char *pgm; const char *argv[3]; pgm = get_gpg_connect_agent_path (); if (!pgm) { g_message ("tool to start the agent is not available"); return; } ctx = gpa_gpgme_new (); gpgme_set_protocol (ctx, GPGME_PROTOCOL_SPAWN); argv[0] = ""; /* Auto-insert the basename. */ argv[1] = "NOP"; argv[2] = NULL; err = gpgme_op_spawn (ctx, pgm, argv, NULL, NULL, NULL, GPGME_SPAWN_DETACHED); if (err) g_message ("error running '%s': %s", pgm, gpg_strerror (err)); g_free (pgm); gpgme_release (ctx); } /* Fucntions matching the user id verification isn gpg's key generation. */ const char * gpa_validate_gpg_name (const char *name) { const char *result = NULL; if (!name || !*name) result = _("You must enter a name."); else if (strpbrk (name, "<>")) result = _("Invalid character in name."); else if (g_ascii_isdigit (*name)) result = _("Name may not start with a digit."); else if (g_utf8_strlen (name, -1) < 5) result = _("Name is too short."); return result; } /* Check whether the string has characters not valid in an RFC-822 address. To cope with OpenPGP we allow non-ascii characters so that for example umlauts are legal in an email address. An OpenPGP user ID must be utf-8 encoded but there is no strict requirement for RFC-822. Thus to avoid IDNA encoding we put the address verbatim as utf-8 into the user ID under the assumption that mail programs handle IDNA at a lower level and take OpenPGP user IDs as utf-8. */ static int has_invalid_email_chars (const char *s) { int at_seen = 0; const char *valid_chars= "01234567890_-.abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; for ( ; *s; s++ ) { if ((*s & 0x80)) continue; /* We only care about ASCII. */ if (*s == '@') at_seen=1; else if (!at_seen && !( !!strchr( valid_chars, *s ) || *s == '+' )) return 1; else if (at_seen && !strchr (valid_chars, *s)) return 1; } return 0; } static int string_count_chr (const char *string, int c) { int count; for (count=0; *string; string++ ) if ( *string == c ) count++; return count; } /* Check whether NAME represents a valid mailbox according to RFC822 except for non-ascii utf-8 characters. Returns true if so. */ static int is_valid_mailbox (const char *name) { return !( !name || !*name || has_invalid_email_chars (name) || string_count_chr (name,'@') != 1 || *name == '@' || name[strlen(name)-1] == '@' || name[strlen(name)-1] == '.' || strstr (name, "..") ); } const char * gpa_validate_gpg_email (const char *email) { const char *result = NULL; if (!email || !*email) ; else if (!is_valid_mailbox (email)) result = _("Email address is not valid."); return result; } const char * gpa_validate_gpg_comment (const char *comment) { const char *result = NULL; if (!comment || !*comment) ; else if (strpbrk (comment, "()")) result = _("Invalid character in comments."); return result; } diff --git a/src/gpgmetools.h b/src/gpgmetools.h index dc39839..95ab4d5 100644 --- a/src/gpgmetools.h +++ b/src/gpgmetools.h @@ -1,267 +1,271 @@ /* gpgmetools.h - additional gpgme support functions for GPA. Copyright (C) 2002, Miguel Coca. Copyright (C) 2005, 2008 g10 Code GmbH. Copyright (C) 2015 g10 Code GmbH. This file is part of GPA GPA 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. GPA 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 . */ /* A set of auxiliary functions for common tasks related to GPGME. */ #ifndef GPGMETOOLS_H #define GPGMETOOLS_H #include #include +#include "gpacontext.h" + /* Internal algorithm identifiers, describing which keys to create. */ typedef enum { GPA_KEYGEN_ALGO_RSA_RSA, GPA_KEYGEN_ALGO_RSA_ELGAMAL, GPA_KEYGEN_ALGO_RSA, GPA_KEYGEN_ALGO_DSA_ELGAMAL, GPA_KEYGEN_ALGO_DSA, GPA_KEYGEN_ALGO_VIA_CARD } gpa_keygen_algo_t; typedef struct { /* User ID. */ gchar *name; gchar *email; gchar *comment; /* Algorithm. */ gpa_keygen_algo_t algo; /* Key size. */ gint keysize; /* The password to use. */ gchar *password; /* Epiration date. It is only used if it is valid. */ GDate expire; /* True if the encryption key is created with an backup file. */ gboolean backup; /* Used to return an error description. */ char *r_error_desc; } gpa_keygen_para_t; /* An object to collect information about key imports. */ struct gpa_import_result_s { unsigned int files; /* # of files imported. */ unsigned int bad_files; /* # of files with errors. */ /* To avoid breaking translated strings the variables below are int and not unsigned int as they should be for counters. */ int considered; int imported; int unchanged; int secret_read; int secret_imported; int secret_unchanged; }; typedef struct gpa_import_result_s *gpa_import_result_t; /* Report an unexpected error in GPGME and quit the application. Better to use the macro instead of the function. */ #define gpa_gpgme_error(err) \ do { _gpa_gpgme_error (err, __FILE__, __LINE__); } while (0) void _gpa_gpgme_error (gpg_error_t err, const char *file, int line) G_GNUC_NORETURN; /* The same as gpa_gpgme_error, without quitting. */ +#define gpa_gpgme_warn(err,desc,ctx) \ + do { _gpa_gpgme_warn (err, desc, ctx, __FILE__, __LINE__); } while (0) #define gpa_gpgme_warning_ext(err,desc) \ - do { _gpa_gpgme_warning (err, desc, __FILE__, __LINE__); } while (0) + do { _gpa_gpgme_warn (err, desc, NULL, __FILE__, __LINE__); } while (0) #define gpa_gpgme_warning(err) \ - do { _gpa_gpgme_warning (err, NULL, __FILE__, __LINE__); } while (0) -void _gpa_gpgme_warning (gpg_error_t err, const char *desc, - const char *file, int line); + do { _gpa_gpgme_warn (err, NULL, NULL, __FILE__, __LINE__); } while (0) +void _gpa_gpgme_warn (gpg_error_t err, const char *desc, GpaContext *ctx, + const char *file, int line); /* Initialize a gpgme_ctx_t for use with GPA. */ gpgme_ctx_t gpa_gpgme_new (void); /* Write the contents of the gpgme_data_t object to the file. Receives a filehandle instead of the filename, so that the caller can make sure the file is accesible before putting anything into data. */ void dump_data_to_file (gpgme_data_t data, FILE *file); /* Not really a gpgme function, but needed in most places dump_data_to_file is used. Opens a file for writing, asking the user to overwrite if it exists and reporting any errors. Returns NULL on failure, but you can assume the user has been informed of the error (or maybe he just didn't want to overwrite!). The filename of the file that is actually open (if FILENAME already exists, then the user can choose a different file) is saved in *FILENAME_USED. It must be xfreed. This is set even if this function returns NULL! */ FILE *gpa_fopen (const char *filename, GtkWidget *parent, char **filename_used); /* Do a gpgme_data_new_from_file and report any GPG_ERR_File_Error to the user. */ gpg_error_t gpa_gpgme_data_new_from_file (gpgme_data_t *data, const char *filename, GtkWidget *parent); /* Create a new gpgme_data_t from a file for writing, and return the file descriptor for the file. Always reports all errors to the user. The _direct variant does not check for overwriting. The filename of the file that is actually open (if FILENAME already exists, then the user can choose a different file) is saved in *FILENAME_USED. It must be xfreed. This is set even if this function returns NULL! */ int gpa_open_output_direct (const char *filename, gpgme_data_t *data, GtkWidget *parent); int gpa_open_output (const char *filename, gpgme_data_t *data, GtkWidget *parent, char **filename_used); /* Create a new gpgme_data_t from a file for reading, and return the file descriptor for the file. Always reports all errors to the user. */ int gpa_open_input (const char *filename, gpgme_data_t *data, GtkWidget *parent); /* Write the contents of the gpgme_data_t into the clipboard. */ int dump_data_to_clipboard (gpgme_data_t data, GtkClipboard *clipboard); /* Begin generation of a key with the given parameters. It prepares the parameters required by Gpgme and returns whatever gpgme_op_genkey_start returns. */ gpg_error_t gpa_generate_key_start (gpgme_ctx_t ctx, gpa_keygen_para_t *params); /* Backup a key. It exports both the public and secret keys to a file. IS_X509 tells the function that the fingerprint is from an X.509 key. Returns TRUE on success and FALSE on error. It displays errors to the user. */ gboolean gpa_backup_key (const gchar *fpr, const char *filename, int is_x509); gpa_keygen_para_t *gpa_keygen_para_new (void); void gpa_keygen_para_free (gpa_keygen_para_t *params); /* Ownertrust strings. */ const gchar *gpa_key_ownertrust_string (gpgme_key_t key); /* Key validity strings. */ const gchar *gpa_key_validity_string (gpgme_key_t key); /* UID validity strings. */ const gchar *gpa_uid_validity_string (gpgme_user_id_t uid); /* Function to manage import results. */ void gpa_gpgme_update_import_results (gpa_import_result_t result, unsigned int files, unsigned int bad_files, gpgme_import_result_t info); void gpa_gpgme_show_import_results (GtkWidget *parent, gpa_import_result_t result); /* This is the function called by GPGME when it wants a passphrase. */ gpg_error_t gpa_passphrase_cb (void *hook, const char *uid_hint, const char *passphrase_info, int prev_was_bad, int fd); /* Convenience functions to access key attributes, which need to be filtered before being displayed to the user. */ /* Return the user ID string, making sure it is properly UTF-8 encoded. Allocates a new string, which must be freed with g_free(). */ gchar *gpa_gpgme_key_get_userid (gpgme_user_id_t key); /* Return the key fingerprint, properly formatted according to the key version. Allocates a new string, which must be freed with g_free(). This is based on code from GPAPA's extract_fingerprint. */ gchar *gpa_gpgme_key_format_fingerprint (const char *fpraw); /* Return the short key ID of the indicated key. The returned string is valid as long as the key is valid. */ const gchar *gpa_gpgme_key_get_short_keyid (gpgme_key_t key); /* Convenience function to access key signature attibutes, much like the previous ones. */ /* Return the user ID, making sure it is properly UTF-8 encoded. Allocates a new string, which must be freed with g_free(). */ gchar *gpa_gpgme_key_sig_get_userid (gpgme_key_sig_t sig); /* Return the short key ID of the indicated key. The returned string is valid as long as the key is valid. */ const gchar *gpa_gpgme_key_sig_get_short_keyid (gpgme_key_sig_t sig); /* Return a string with the status of the key signature. */ const gchar *gpa_gpgme_key_sig_get_sig_status (gpgme_key_sig_t sig, GHashTable *revoked); /* Return a string with the level of the key signature. */ const gchar *gpa_gpgme_key_sig_get_level (gpgme_key_sig_t sig); /* Return a human readable string with the status of the signature SIG. */ char *gpa_gpgme_get_signature_desc (gpgme_ctx_t ctx, gpgme_signature_t sig, char **r_keydesc, gpgme_key_t *r_key); /* Return a string listing the capabilities of a key. */ const gchar *gpa_get_key_capabilities_text (gpgme_key_t key); /* Return a copy of the key array. */ gpgme_key_t *gpa_gpgme_copy_keyarray (gpgme_key_t *keys); /* Release all keys in the array KEYS as weel as ARRY itself. */ void gpa_gpgme_release_keyarray (gpgme_key_t *keys); /* Try switching to the gpg2 backend. */ void gpa_switch_to_gpg2 (const char *gpg_binary, const char *gpgsm_binary); /* Return true if the gpg engine has at least version NEED_VERSION. */ int is_gpg_version_at_least (const char *need_version); /* Run a simple gpg command. */ gpg_error_t gpa_start_simple_gpg_command (gboolean (*cb) (void *opaque, char *line), void *cb_arg, gpgme_protocol_t protocol, int use_stderr, const char *first_arg, ... ) G_GNUC_NULL_TERMINATED; void gpa_start_agent (void); /* Funtions to check user inputs in the same way gpg does. */ const char *gpa_validate_gpg_name (const char *name); const char *gpa_validate_gpg_email (const char *email); const char *gpa_validate_gpg_comment (const char *comment); #endif /*GPGMETOOLS_H*/ diff --git a/src/gtktools.c b/src/gtktools.c index 8bf2867..b0f9ba8 100644 --- a/src/gtktools.c +++ b/src/gtktools.c @@ -1,162 +1,180 @@ /* gtktools.c - The GNU Privacy Assistant Copyright (C) 2000, 2001 G-N-U GmbH. Copyright (C) 2008, 2014 g10 Code GmbH. This file is part of GPA. GPA 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. GPA 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 /* NOTE: Here are a lot of old GTK+ functions and wrappers. They should be replaced by modern GTK+ code and some of the wrappers are not needed anymore. */ #include #include #include #include "gpa.h" #include "gtktools.h" #include "gpawindowkeeper.h" #include "icons.h" - /* Deprecated - use gpa_show_warning instead. */ void gpa_window_error (const gchar *message, GtkWidget *messenger) { - gpa_show_warning (messenger, "%s", message); + gpa_show_warn (messenger, NULL, "%s", message); } /* Deprecated - use gpa_show_info instead. */ void gpa_window_message (const gchar *message, GtkWidget * messenger) { gpa_show_info (messenger, "%s", message); } static void -show_gtk_message (GtkWidget *parent, GtkMessageType mtype, +show_gtk_message (GtkWidget *parent, GtkMessageType mtype, GpaContext *ctx, const char *format, va_list arg_ptr) { GtkWidget *dialog; char *buffer; buffer = g_strdup_vprintf (format, arg_ptr); dialog = gtk_message_dialog_new (parent? GTK_WINDOW (parent):NULL, GTK_DIALOG_MODAL, mtype, GTK_BUTTONS_CLOSE, "%s", buffer); g_free (buffer); + buffer = NULL; + if (ctx) + gtk_dialog_add_buttons (GTK_DIALOG (dialog), + _("_Details"), GTK_RESPONSE_HELP, + NULL); gtk_widget_show_all (dialog); - gtk_dialog_run (GTK_DIALOG (dialog)); + if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_HELP && ctx) + { + /* If requested and possible get diagnostics from GPGME. */ + buffer = gpa_context_get_diag (ctx); + if (!buffer) + gpa_show_info (parent, "No diagnostic data available"); + else + { + gpa_show_info (parent, "Diagnostics:\n%s", buffer); + g_free (buffer); + } + } + gtk_widget_destroy (dialog); } /* Show a modal info message. */ void gpa_show_info (GtkWidget *parent, const char *format, ...) { va_list arg_ptr; va_start (arg_ptr, format); - show_gtk_message (parent, GTK_MESSAGE_INFO, format, arg_ptr); + show_gtk_message (parent, GTK_MESSAGE_INFO, NULL, format, arg_ptr); va_end (arg_ptr); } -/* Show a modal warning message. */ +/* Show a modal warning message. PARENT is the parent windows, CTX is + * eitehr NULL or a related GPGME context to be used to allow shoing + * additional information. */ void -gpa_show_warning (GtkWidget *parent, const char *format, ...) +gpa_show_warn (GtkWidget *parent, GpaContext *ctx, const char *format, ...) { va_list arg_ptr; va_start (arg_ptr, format); - show_gtk_message (parent, GTK_MESSAGE_WARNING, format, arg_ptr); + show_gtk_message (parent, GTK_MESSAGE_WARNING, ctx, format, arg_ptr); va_end (arg_ptr); } /* Set a tooltip TEXT to WIDGET. TEXT and WIDGET may both be NULL. This function is useful so that GPA can be build with older GTK+ versions. */ void gpa_add_tooltip (GtkWidget *widget, const char *text) { #if GTK_CHECK_VERSION (2, 12, 0) if (widget && text && *text) gtk_widget_set_tooltip_text (widget, text); #endif } /* Set the title of COLUMN to TITLE and also set TOOLTIP. */ void gpa_set_column_title (GtkTreeViewColumn *column, const char *title, const char *tooltip) { GtkWidget *label; label = gtk_label_new (title); /* We need to show the label before setting the widget. */ gtk_widget_show (label); gtk_tree_view_column_set_widget (column, label); if (tooltip) gpa_add_tooltip (gtk_tree_view_column_get_widget (column), tooltip); } static void set_homogeneous (GtkWidget *widget, gpointer data) { gboolean *is_hom_p = data; gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (widget), *is_hom_p); } /* Set the homogeneous property for all children of TOOLBAR to IS_HOM. */ void gpa_toolbar_set_homogeneous (GtkToolbar *toolbar, gboolean is_hom) { gtk_container_foreach (GTK_CONTAINER (toolbar), (GtkCallback) set_homogeneous, &is_hom); } /* Customized set title function. */ void gpa_window_set_title (GtkWindow *window, const char *string) { const char *prefix = GPA_LONG_NAME; char *buffer; if (!string || !*string) { gtk_window_set_title (window, prefix); } else { buffer = g_strdup_printf ("%s - %s", prefix, string); gtk_window_set_title (window, buffer); g_free (buffer); } } diff --git a/src/gtktools.h b/src/gtktools.h index 41fafc5..ce9a969 100644 --- a/src/gtktools.h +++ b/src/gtktools.h @@ -1,57 +1,60 @@ /* gtktools.h - The GNU Privacy Assistant Copyright (C) 2000, 2001 G-N-U GmbH. Copyright (C) 2008 g10 Code GmbH. This file is part of GPA. GPA 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. GPA 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 . */ #ifndef GTK_TOOLS_H_ #define GTK_TOOLS_H_ +#include #include +#include "gpacontext.h" + /* Show a modal info message. */ void gpa_show_info (GtkWidget *parent, const char *format, ...) G_GNUC_PRINTF(2,3); /* Show a modal warning message. */ -void gpa_show_warning (GtkWidget *parent, - const char *format, ...) G_GNUC_PRINTF(2,3); +void gpa_show_warn (GtkWidget *parent, GpaContext *ctx, + const char *format, ...) G_GNUC_PRINTF(3,4); /* Set a tooltip TEXT to WIDGET. TEXT and WIDGET may both be NULL. This function is useful so that GPA can be build with older GTK+ versions. */ void gpa_add_tooltip (GtkWidget *widget, const char *text); /* Set the title of COLUMN to TITLE and also set TOOLTIP. */ void gpa_set_column_title (GtkTreeViewColumn *column, const char *title, const char *tooltip); /* Set the homogeneous property for all children of TOOLBAR to IS_HOM. */ void gpa_toolbar_set_homogeneous (GtkToolbar *toolbar, gboolean is_hom); /* Customized set title function. */ void gpa_window_set_title (GtkWindow *window, const char *string); /* Deprecated functions. */ void gpa_window_error (const gchar * message, GtkWidget * messenger); void gpa_window_message (const gchar * message, GtkWidget * messenger); #endif /* GTK_TOOLS_H_ */