diff --git a/src/encryptdlg.c b/src/encryptdlg.c index 2a11000..da74598 100644 --- a/src/encryptdlg.c +++ b/src/encryptdlg.c @@ -1,401 +1,397 @@ /* encryptdlg.c - The GNU Privacy Assistant * Copyright (C) 2000, 2001 G-N-U GmbH. * Copyright (C) 2002, 2003 Miguel Coca. * 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 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 #include #include #include #include "gpa.h" #include "gtktools.h" #include "gpawidgets.h" #include "gpakeyselector.h" #include "encryptdlg.h" /* Internal functions */ static void changed_select_row_cb (GtkTreeSelection *treeselection, gpointer user_data); static void toggle_sign_cb (GtkToggleButton *togglebutton, gpointer user_data); /* Properties */ enum { PROP_0, PROP_WINDOW, PROP_FORCE_ARMOR, PROP_ARMOR, PROP_SIGN }; static GObjectClass *parent_class = NULL; static void gpa_file_encrypt_dialog_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GpaFileEncryptDialog *dialog = GPA_FILE_ENCRYPT_DIALOG (object); switch (prop_id) { case PROP_WINDOW: g_value_set_object (value, gtk_window_get_transient_for (GTK_WINDOW (dialog))); break; case PROP_FORCE_ARMOR: g_value_set_boolean (value, dialog->force_armor); break; case PROP_ARMOR: g_value_set_boolean (value, gpa_file_encrypt_dialog_get_armor (dialog)); break; case PROP_SIGN: g_value_set_boolean (value, gpa_file_encrypt_dialog_get_sign (dialog)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gpa_file_encrypt_dialog_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GpaFileEncryptDialog *dialog = GPA_FILE_ENCRYPT_DIALOG (object); switch (prop_id) { case PROP_WINDOW: gtk_window_set_transient_for (GTK_WINDOW (dialog), g_value_get_object (value)); break; case PROP_FORCE_ARMOR: dialog->force_armor = g_value_get_boolean (value); break; case PROP_ARMOR: gpa_file_encrypt_dialog_set_armor (dialog, g_value_get_boolean (value)); break; case PROP_SIGN: gpa_file_encrypt_dialog_set_sign (dialog, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gpa_file_encrypt_dialog_finalize (GObject *object) { G_OBJECT_CLASS (parent_class)->finalize (object); } static void gpa_file_encrypt_dialog_init (GpaFileEncryptDialog *dialog) { } static GObject* gpa_file_encrypt_dialog_constructor (GType type, guint n_construct_properties, GObjectConstructParam *construct_properties) { GObject *object; GpaFileEncryptDialog *dialog; GtkWidget *vboxEncrypt; GtkWidget *labelKeys; GtkWidget *scrollerKeys; GtkWidget *clistKeys; GtkWidget *checkerSign; GtkWidget *checkerArmor; GtkWidget *labelWho; GtkWidget *scrollerWho; GtkWidget *clistWho; /* Invoke parent's constructor */ object = parent_class->constructor (type, n_construct_properties, construct_properties); dialog = GPA_FILE_ENCRYPT_DIALOG (object); /* Initialize */ /* Set up the dialog */ gpa_window_set_title (GTK_WINDOW (dialog), _("Encrypt documents")); gtk_dialog_add_buttons (GTK_DIALOG (dialog), 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); gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_OK, FALSE); gtk_container_set_border_width (GTK_CONTAINER (dialog), 5); //vboxEncrypt = GTK_DIALOG (dialog)->vbox; vboxEncrypt = gtk_dialog_get_content_area(dialog); gtk_container_set_border_width (GTK_CONTAINER (vboxEncrypt), 5); labelKeys = gtk_label_new_with_mnemonic (_("_Public Keys")); gtk_misc_set_alignment (GTK_MISC (labelKeys), 0.0, 0.5); gtk_box_pack_start (GTK_BOX (vboxEncrypt), labelKeys, FALSE, FALSE, 0); scrollerKeys = gtk_scrolled_window_new (NULL, NULL); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrollerKeys), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_box_pack_start (GTK_BOX (vboxEncrypt), scrollerKeys, TRUE, TRUE, 0); gtk_widget_set_size_request (scrollerKeys, 400, 200); gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrollerKeys), GTK_SHADOW_IN); clistKeys = gpa_key_selector_new (FALSE, TRUE); g_signal_connect (G_OBJECT (gtk_tree_view_get_selection (GTK_TREE_VIEW (clistKeys))), "changed", G_CALLBACK (changed_select_row_cb), dialog); dialog->clist_keys = clistKeys; gtk_container_add (GTK_CONTAINER (scrollerKeys), clistKeys); gtk_label_set_mnemonic_widget (GTK_LABEL (labelKeys), clistKeys); checkerSign = gtk_check_button_new_with_mnemonic (_("_Sign")); gtk_box_pack_start (GTK_BOX (vboxEncrypt), checkerSign, FALSE, FALSE, 0); dialog->check_sign = checkerSign; g_signal_connect (G_OBJECT (checkerSign), "toggled", G_CALLBACK (toggle_sign_cb), dialog); labelWho = gtk_label_new_with_mnemonic (_("Sign _as ")); gtk_box_pack_start (GTK_BOX (vboxEncrypt), labelWho, FALSE, TRUE, 0); scrollerWho = gtk_scrolled_window_new (NULL, NULL); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrollerWho), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_widget_set_size_request (scrollerWho, 400, 200); gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrollerWho), GTK_SHADOW_IN); gtk_box_pack_start (GTK_BOX (vboxEncrypt), scrollerWho, TRUE, TRUE, 0); clistWho = gpa_key_selector_new (TRUE, TRUE); dialog->clist_who = clistWho; gtk_container_add (GTK_CONTAINER (scrollerWho), clistWho); gtk_label_set_mnemonic_widget (GTK_LABEL (labelWho), clistWho); /* FIXME: We can't make the key selector insensitive, as it will make itself sensitive again automatically after the keyloading. So we make the whole scroller insensitive. This is a bit overzealous, but should not affect usability too much. Eventually we could add a property to the key selector to force insensitivity even across key reloads. */ #if 0 gtk_widget_set_sensitive (clistWho, FALSE); #else dialog->scroller_who = scrollerWho; gtk_widget_set_sensitive (scrollerWho, FALSE); #endif checkerArmor = gtk_check_button_new_with_mnemonic (_("A_rmor")); gtk_box_pack_start (GTK_BOX (vboxEncrypt), checkerArmor, FALSE, FALSE, 0); dialog->check_armor = checkerArmor; if (dialog->force_armor) { gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->check_armor), TRUE); gtk_widget_set_sensitive (dialog->check_armor, FALSE); } return object; } static void gpa_file_encrypt_dialog_class_init (GpaFileEncryptDialogClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); parent_class = g_type_class_peek_parent (klass); object_class->constructor = gpa_file_encrypt_dialog_constructor; object_class->finalize = gpa_file_encrypt_dialog_finalize; object_class->set_property = gpa_file_encrypt_dialog_set_property; object_class->get_property = gpa_file_encrypt_dialog_get_property; /* Properties */ g_object_class_install_property (object_class, PROP_WINDOW, g_param_spec_object ("window", "Parent window", "Parent window", GTK_TYPE_WIDGET, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, PROP_FORCE_ARMOR, g_param_spec_boolean ("force-armor", "Force armor", "Force armor mode", FALSE, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, PROP_ARMOR, g_param_spec_boolean ("armor", "Armor", "Armor mode", FALSE, G_PARAM_WRITABLE)); g_object_class_install_property (object_class, PROP_SIGN, g_param_spec_boolean ("sign", "Sign", "Sign", FALSE, G_PARAM_WRITABLE)); } GType gpa_file_encrypt_dialog_get_type (void) { static GType encrypt_dialog_type = 0; if (!encrypt_dialog_type) { static const GTypeInfo encrypt_dialog_info = { sizeof (GpaFileEncryptDialogClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) gpa_file_encrypt_dialog_class_init, NULL, /* class_finalize */ NULL, /* class_data */ sizeof (GpaFileEncryptDialog), 0, /* n_preallocs */ (GInstanceInitFunc) gpa_file_encrypt_dialog_init, }; encrypt_dialog_type = g_type_register_static (GTK_TYPE_DIALOG, "GpaFileEncryptDialog", &encrypt_dialog_info, 0); } return encrypt_dialog_type; } /* API */ GtkWidget * gpa_file_encrypt_dialog_new (GtkWidget *parent, gboolean force_armor) { GpaFileEncryptDialog *dialog; dialog = g_object_new (GPA_FILE_ENCRYPT_DIALOG_TYPE, "window", parent, "force-armor", force_armor, NULL); return GTK_WIDGET(dialog); } GList * gpa_file_encrypt_dialog_recipients (GpaFileEncryptDialog *dialog) { return gpa_key_selector_get_selected_keys (GPA_KEY_SELECTOR (dialog->clist_keys)); } GList * gpa_file_encrypt_dialog_signers (GpaFileEncryptDialog *dialog) { return gpa_key_selector_get_selected_keys (GPA_KEY_SELECTOR (dialog->clist_who)); } gboolean gpa_file_encrypt_dialog_get_armor (GpaFileEncryptDialog *dialog) { return gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->check_armor)); } void gpa_file_encrypt_dialog_set_armor (GpaFileEncryptDialog *dialog, gboolean armor) { gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->check_armor), armor); } gboolean gpa_file_encrypt_dialog_get_sign (GpaFileEncryptDialog *dialog) { return gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->check_sign)); } void gpa_file_encrypt_dialog_set_sign (GpaFileEncryptDialog *dialog, gboolean sign) { gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->check_sign), sign); } static void changed_select_row_cb (GtkTreeSelection *treeselection, gpointer user_data) { GpaFileEncryptDialog *dialog = user_data; if (gpa_key_selector_has_selection (GPA_KEY_SELECTOR (dialog->clist_keys))) { gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_OK, TRUE); } else { gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_OK, FALSE); } } static void toggle_sign_cb (GtkToggleButton *togglebutton, gpointer user_data) { GpaFileEncryptDialog *dialog = user_data; /* FIXME: See above. */ #if 0 gtk_widget_set_sensitive (dialog->clist_who, gtk_toggle_button_get_active (togglebutton)); #else gtk_widget_set_sensitive (dialog->scroller_who, gtk_toggle_button_get_active (togglebutton)); #endif } diff --git a/src/gpgmetools.c b/src/gpgmetools.c index 24e83e5..b1fe530 100644 --- a/src/gpgmetools.c +++ b/src/gpgmetools.c @@ -1,1844 +1,1839 @@ /* 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_warn macros). */ void _gpa_gpgme_warn (gpg_error_t err, const char *desc, GpaContext *ctx, const char *file, int line) { char *argbuf = NULL; const char *arg; 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); 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); } /* 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, _("_Cancel"), GTK_RESPONSE_CANCEL, _("_OK"), GTK_RESPONSE_OK, NULL); - /*gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), - GTK_RESPONSE_OK, - GTK_RESPONSE_CANCEL, - -1); - */ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); GtkWidget *box = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); gtk_box_pack_start (GTK_BOX (box), hbox, TRUE, FALSE, 10); pixmap = gtk_image_new_from_icon_name("dialog-warning", GTK_ICON_SIZE_DIALOG); gtk_box_pack_start (GTK_BOX (hbox), pixmap, TRUE, FALSE, 10); vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 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 (GTK_BOX (vbox), passphrase_question_label (uid_hint, passphrase_info, prev_was_bad), TRUE, TRUE, 0); 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_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/ownertrustdlg.c b/src/ownertrustdlg.c index eb21b81..3f2f6e4 100644 --- a/src/ownertrustdlg.c +++ b/src/ownertrustdlg.c @@ -1,263 +1,259 @@ /* keyring.c - The GNU Privacy Assistant * Copyright (C) 2000, 2001 G-N-U 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 #include #include "gpa.h" #include "gpawidgets.h" #include "gtktools.h" #include "gpgmeedit.h" /* * Owner Trust dialog */ static void init_radio_buttons (gpgme_validity_t trust, GtkWidget *unknown_radio, GtkWidget *never_radio, GtkWidget *marginal_radio, GtkWidget *full_radio, GtkWidget *ultimate_radio) { switch (trust) { case GPGME_VALIDITY_UNKNOWN: case GPGME_VALIDITY_UNDEFINED: gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (unknown_radio), TRUE); break; case GPGME_VALIDITY_NEVER: gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (never_radio), TRUE); break; case GPGME_VALIDITY_MARGINAL: gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (marginal_radio), TRUE); break; case GPGME_VALIDITY_FULL: gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (full_radio), TRUE); break; case GPGME_VALIDITY_ULTIMATE: gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ultimate_radio), TRUE); break; } } static gpgme_validity_t get_selected_validity (GtkWidget *unknown_radio, GtkWidget *never_radio, GtkWidget *marginal_radio, GtkWidget *full_radio, GtkWidget *ultimate_radio) { if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (unknown_radio))) { return GPGME_VALIDITY_UNKNOWN; } else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (never_radio))) { return GPGME_VALIDITY_NEVER; } else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (marginal_radio))) { return GPGME_VALIDITY_MARGINAL; } else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (full_radio))) { return GPGME_VALIDITY_FULL; } else { return GPGME_VALIDITY_ULTIMATE; } } /* Run the owner trust dialog modally. */ gboolean gpa_ownertrust_run_dialog (gpgme_key_t key, GtkWidget *parent, gpgme_validity_t *return_trust) { GtkWidget *dialog; GtkWidget *key_info; GtkWidget *grid; GtkWidget *frame; GtkWidget *unknown_radio, *never_radio, *marginal_radio, *full_radio, *ultimate_radio; GtkWidget *label; GtkResponseType response; gpgme_validity_t trust = key->owner_trust; gboolean result; /* Create the dialog */ dialog = gtk_dialog_new_with_buttons (_("Change key ownertrust"), GTK_WINDOW(parent), 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); gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); gtk_container_set_border_width (GTK_CONTAINER (dialog), 5); key_info = gpa_key_info_new (key); GtkWidget *box = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); gtk_box_pack_start(GTK_BOX (box), key_info, FALSE, FALSE, 0); /* Create the "Owner Trust" frame */ frame = gtk_frame_new (_("Owner Trust")); gtk_box_pack_start (GTK_BOX (box), frame, FALSE, FALSE, 0); grid = gtk_grid_new(); unknown_radio = gtk_radio_button_new (NULL); gtk_grid_attach(GTK_GRID(grid), unknown_radio, 0, 0, 1, 1); label = gtk_label_new_with_mnemonic (_("_Unknown")); gtk_label_set_mnemonic_widget (GTK_LABEL (label), unknown_radio); gtk_widget_set_halign(label, GTK_ALIGN_START); gtk_widget_set_valign(label, GTK_ALIGN_START); gtk_grid_attach(GTK_GRID (grid), label, 1, 0, 1, 1); label = gtk_label_new (_("You don't know how much to trust this user to " "verify other people's keys.\n")); gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); gtk_widget_set_halign(label, GTK_ALIGN_START); gtk_widget_set_valign(label, GTK_ALIGN_START); gtk_grid_attach(GTK_GRID (grid), label, 1, 1, 1, 1); never_radio = gtk_radio_button_new_from_widget (GTK_RADIO_BUTTON (unknown_radio)); gtk_grid_attach(GTK_GRID (grid), never_radio, 0, 2, 1, 1); label = gtk_label_new_with_mnemonic (_("_Never")); gtk_label_set_mnemonic_widget (GTK_LABEL (label), never_radio); gtk_widget_set_halign(label, GTK_ALIGN_START); gtk_widget_set_valign(label, GTK_ALIGN_START); gtk_grid_attach(GTK_GRID (grid), label, 1, 2, 1, 1); label = gtk_label_new (_("You don't trust this user at all to verify the " "validity of other people's keys at all.\n")); gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); gtk_widget_set_halign(label, GTK_ALIGN_START); gtk_widget_set_valign(label, GTK_ALIGN_START); gtk_grid_attach(GTK_GRID (grid), label, 1, 3, 1, 1); marginal_radio = gtk_radio_button_new_from_widget (GTK_RADIO_BUTTON (unknown_radio)); gtk_grid_attach(GTK_GRID (grid), marginal_radio, 0, 4, 1, 1); label = gtk_label_new_with_mnemonic (_("_Marginal")); gtk_label_set_mnemonic_widget (GTK_LABEL (label), marginal_radio); gtk_widget_set_halign(label, GTK_ALIGN_START); gtk_widget_set_valign(label, GTK_ALIGN_START); gtk_grid_attach(GTK_GRID (grid), label, 1, 4, 1, 1); label = gtk_label_new (_("You don't trust this user's ability to " "verify the validity of other people's keys " "enough to consider keys valid based on his/her " "sole word.\n" "However, provided this user's key is " "valid, you will consider a key signed by this " "user valid if it is also signed by at least " "other two marginally trusted users with " "valid keys\n")); gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); gtk_widget_set_halign(label, GTK_ALIGN_START); gtk_widget_set_valign(label, GTK_ALIGN_START); gtk_grid_attach(GTK_GRID (grid), label, 1, 5, 1, 1); full_radio = gtk_radio_button_new_from_widget (GTK_RADIO_BUTTON (unknown_radio)); gtk_grid_attach(GTK_GRID (grid), full_radio, 0, 6, 1, 1); label = gtk_label_new_with_mnemonic (_("_Full")); gtk_label_set_mnemonic_widget (GTK_LABEL (label), full_radio); gtk_widget_set_halign(label, GTK_ALIGN_START); gtk_widget_set_valign(label, GTK_ALIGN_START); gtk_grid_attach(GTK_GRID (grid), label, 1, 6, 1, 1); label = gtk_label_new (_("You trust this user's ability to " "verify the validity of other people's keys " "so much, that you'll consider valid any key " "signed by him/her, provided this user's key " "is valid.\n")); gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); gtk_widget_set_halign(label, GTK_ALIGN_START); gtk_widget_set_valign(label, GTK_ALIGN_START); gtk_grid_attach(GTK_GRID (grid), label, 1, 7, 1, 1); ultimate_radio = gtk_radio_button_new_from_widget (GTK_RADIO_BUTTON (unknown_radio)); gtk_grid_attach(GTK_GRID (grid), ultimate_radio, 0, 8, 1, 1); label = gtk_label_new_with_mnemonic (_("U_ltimate")); gtk_label_set_mnemonic_widget (GTK_LABEL (label), ultimate_radio); gtk_widget_set_halign(label, GTK_ALIGN_START); gtk_widget_set_valign(label, GTK_ALIGN_START); gtk_grid_attach(GTK_GRID (grid), label, 1, 8, 1, 1); label = gtk_label_new (_("You consider this key valid, and trust the user " "so much that you will consider any key signed " "by him/her fully valid.\n\n" "(Warning: This is intended to be used for keys " "you own. Don't use it with other people's keys " "unless you really know what you are doing)\n")); gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); gtk_widget_set_halign(label, GTK_ALIGN_START); gtk_widget_set_valign(label, GTK_ALIGN_START); gtk_grid_attach(GTK_GRID (grid), label, 1, 9, 1, 1); /* Initialize */ init_radio_buttons (trust, unknown_radio, never_radio, marginal_radio, full_radio, ultimate_radio); gtk_container_add (GTK_CONTAINER (frame), grid); gtk_window_resize(GTK_WINDOW(dialog), 400, 300); /* Run */ gtk_widget_show_all (dialog); response = gtk_dialog_run (GTK_DIALOG (dialog)); /* Return the ownertrust */ if (response == GTK_RESPONSE_OK) { gpgme_validity_t new_trust = get_selected_validity (unknown_radio, never_radio, marginal_radio, full_radio, ultimate_radio); /* If the user didn't change the trust, don't edit the key */ if (trust == new_trust || (trust == GPGME_VALIDITY_UNDEFINED && new_trust == GPGME_VALIDITY_UNKNOWN)) { result = FALSE; } else { *return_trust = new_trust; result = TRUE; } } else { result = FALSE; } gtk_widget_destroy (dialog); return result; } diff --git a/src/recipientdlg.c b/src/recipientdlg.c index f317acb..0782d9c 100644 --- a/src/recipientdlg.c +++ b/src/recipientdlg.c @@ -1,1244 +1,1240 @@ /* recipientdlg.c - A dialog to select a mail recipient. 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 . */ #ifdef HAVE_CONFIG_H # include #endif #include #include "gpa.h" #include "i18n.h" #include "gtktools.h" #include "selectkeydlg.h" #include "recipientdlg.h" struct _RecipientDlg { GtkDialog parent; GtkWidget *clist_keys; GtkWidget *statushint; GtkWidget *radio_pgp; GtkWidget *radio_x509; GtkWidget *radio_auto; GtkWidget *popup_menu; /* Flag to disable updates of the status hint. This is actual a counter with updates only allowed if it is zero. */ int freeze_update_statushint; /* Flag to disable any key selection. This is used while a key selection is active. Implemented as a counter. */ int freeze_key_selection; /* Set if this dialog has usable key to be passed back to the caller. You need to call update_statushint to set it. */ int usable; /* The selected protocol. This is also set by update_statushint. */ gpgme_protocol_t selected_protocol; }; struct _RecipientDlgClass { GtkDialogClass parent_class; }; /* The parent class. */ static GObjectClass *parent_class; /* Indentifiers for our properties. */ enum { PROP_0, PROP_WINDOW, PROP_FORCE_ARMOR }; /* For performance reasons we truncate the listing of ambiguous keys at a reasonable value. */ #define TRUNCATE_KEYSEARCH_AT 40 /* An object to keep information about keys. */ struct keyinfo_s { /* An array with associated key(s) or NULL if none found/selected. */ gpgme_key_t *keys; /* The allocated size of the KEYS array. This includes the terminating NULL entry. */ unsigned int dimof_keys; /* If set, indicates that the KEYS array has been truncated. */ int truncated:1; }; /* Management information for each recipient. This data is used per recipient. */ struct userdata_s { /* The recipient's address. */ char *mailbox; /* Information about PGP keys. */ struct keyinfo_s pgp; /* Information about X.509 keys. */ struct keyinfo_s x509; /* If the user has set this field, no encryption key will be required for the recipient. */ int ignore_recipient; }; /* Identifiers for the columns of the RECPLIST. */ enum { RECPLIST_MAILBOX, /* The rfc822 mailbox to whom a key needs to be associated. */ RECPLIST_HAS_PGP, /* A PGP certificate is available. */ RECPLIST_HAS_X509, /* An X.509 certificate is available. */ RECPLIST_KEYID, /* The key ID of the associated key. */ RECPLIST_USERDATA, /* Pointer to management information (struct userdata_s *). */ RECPLIST_N_COLUMNS }; /* Create the main list of this dialog. */ static GtkWidget * recplist_window_new (void) { GtkListStore *store; GtkWidget *list; GtkCellRenderer *renderer; GtkTreeViewColumn *column; /* Create a model and associate a view. */ store = gtk_list_store_new (RECPLIST_N_COLUMNS, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_POINTER); list = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store)); /* Define the columns. */ renderer = gtk_cell_renderer_text_new (); column = gtk_tree_view_column_new_with_attributes (NULL, renderer, "markup", RECPLIST_MAILBOX, NULL); gpa_set_column_title (column, _("Recipient"), _("Shows the recipients of the message." " A key needs to be assigned to each recipient.")); gtk_tree_view_append_column (GTK_TREE_VIEW (list), column); renderer = gtk_cell_renderer_toggle_new (); column = gtk_tree_view_column_new_with_attributes (NULL, renderer, "active", RECPLIST_HAS_PGP, NULL); gpa_set_column_title (column, "PGP", _("Checked if at least one matching" " OpenPGP certificate has been found.")); gtk_tree_view_append_column (GTK_TREE_VIEW (list), column); renderer = gtk_cell_renderer_toggle_new (); column = gtk_tree_view_column_new_with_attributes (NULL, renderer, "active", RECPLIST_HAS_X509, NULL); gpa_set_column_title (column, "X.509", _("Checked if at least one matching" " X.509 certificate for use with S/MIME" " has been found.")); gtk_tree_view_append_column (GTK_TREE_VIEW (list), column); renderer = gtk_cell_renderer_text_new (); column = gtk_tree_view_column_new_with_attributes (NULL, renderer, "text", RECPLIST_KEYID, NULL); gpa_set_column_title (column, _("Key ID"), _("Shows the key ID of the selected key or" " an indication that a key needs to be selected.")); gtk_tree_view_append_column (GTK_TREE_VIEW (list), column); return list; } /* Get an interator for the selected row. Store it in ITER and returns the mdeol. if nothing is selected NULL is return and ITER is not valid. */ static GtkTreeModel * get_selected_row (RecipientDlg *dialog, GtkTreeIter *iter) { GtkTreeSelection *selection; GtkTreeModel *model; g_return_val_if_fail (dialog, NULL); selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->clist_keys)); if (gtk_tree_selection_count_selected_rows (selection) == 1 && gtk_tree_selection_get_selected (selection, &model, iter)) return model; return NULL; } /* Compute and display a new help text for the statushint. */ static void update_statushint (RecipientDlg *dialog) { gpgme_protocol_t req_protocol, sel_protocol; GtkTreeModel *model; GtkTreeIter iter; int missing_keys = 0; int ambiguous_pgp_keys = 0; int ambiguous_x509_keys = 0; int n_pgp_keys = 0; int n_x509_keys = 0; int n_keys = 0; const char *hint; int okay = 0; if (dialog->freeze_update_statushint) return; model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->clist_keys)); if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->radio_pgp))) req_protocol = GPGME_PROTOCOL_OpenPGP; else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->radio_x509))) req_protocol = GPGME_PROTOCOL_CMS; else req_protocol = GPGME_PROTOCOL_UNKNOWN; sel_protocol = GPGME_PROTOCOL_UNKNOWN; if (gtk_tree_model_get_iter_first (model, &iter)) { do { gboolean has_pgp, has_x509; struct userdata_s *info; gtk_tree_model_get (model, &iter, RECPLIST_HAS_PGP, &has_pgp, RECPLIST_HAS_X509, &has_x509, RECPLIST_USERDATA, &info, -1); if (!info) missing_keys++; /* Oops */ else if (info->ignore_recipient) ; else if (!info->pgp.keys && !info->x509.keys) missing_keys++; else if ((req_protocol == GPGME_PROTOCOL_OpenPGP && !has_pgp) ||(req_protocol == GPGME_PROTOCOL_CMS && !has_x509)) ; /* Not of the requested protocol. */ else { n_keys++; if (info->pgp.keys && info->pgp.keys[0]) { n_pgp_keys++; if (info->pgp.keys[1]) ambiguous_pgp_keys++; } if (info->x509.keys && info->x509.keys[0]) { n_x509_keys++; if (info->x509.keys[1]) ambiguous_x509_keys++; } } } while (gtk_tree_model_iter_next (model, &iter)); } if (req_protocol == GPGME_PROTOCOL_UNKNOWN) { /* We select the protocol with the most available keys, preferring PGP. */ if (n_pgp_keys >= n_x509_keys) sel_protocol = GPGME_PROTOCOL_OpenPGP; else if (n_x509_keys) sel_protocol = GPGME_PROTOCOL_CMS; } else sel_protocol = req_protocol; if (missing_keys) hint = _("You need to select a key for each recipient.\n" "To select a key right-click on the respective line."); else if ((sel_protocol == GPGME_PROTOCOL_OpenPGP && ambiguous_pgp_keys) || (sel_protocol == GPGME_PROTOCOL_CMS && ambiguous_x509_keys) || (sel_protocol == GPGME_PROTOCOL_UNKNOWN && (ambiguous_pgp_keys || ambiguous_x509_keys ))) hint = _("You need to select exactly one key for each recipient.\n" "To select a key right-click on the respective line."); else if ((sel_protocol == GPGME_PROTOCOL_OpenPGP && n_keys != n_pgp_keys) || (sel_protocol == GPGME_PROTOCOL_CMS && n_keys != n_x509_keys) || (sel_protocol == GPGME_PROTOCOL_UNKNOWN)) hint = _("Although you selected keys for all recipients " "a common encryption protocol can't be used. " "Please decide on one protocol by clicking one " "of the above radio buttons."); else if (n_pgp_keys && sel_protocol == GPGME_PROTOCOL_OpenPGP) { hint = _("Using OpenPGP for encryption."); okay = 1; } else if (n_x509_keys && sel_protocol == GPGME_PROTOCOL_CMS) { hint = _("Using S/MIME for encryption."); okay = 1; } else hint = _("No recipients - encryption is not possible"); gtk_label_set_text (GTK_LABEL (dialog->statushint), hint); gtk_label_set_line_wrap (GTK_LABEL (dialog->statushint), TRUE); dialog->usable = okay; dialog->selected_protocol = sel_protocol; gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_OK, okay); } /* Add KEY to the keyarray of KEYINFO. Ownership of KEY is moved to KEYARRAY. Returns the number of keys in KEYINFO. */ static unsigned int append_key_to_keyinfo (struct keyinfo_s *keyinfo, gpgme_key_t key) { unsigned int nkeys; if (!keyinfo->keys) { keyinfo->dimof_keys = 5; /* Space for 4 keys. */ keyinfo->keys = g_new (gpgme_key_t, keyinfo->dimof_keys); keyinfo->keys[0] = NULL; } for (nkeys=0; keyinfo->keys[nkeys]; nkeys++) ; /* Note that we silently skip a KEY of NULL because we can't store a NULL in the array. */ if (key) { if (nkeys+1 >= keyinfo->dimof_keys) { keyinfo->dimof_keys += 10; keyinfo->keys = g_renew (gpgme_key_t, keyinfo->keys, keyinfo->dimof_keys); } keyinfo->keys[nkeys++] = key; keyinfo->keys[nkeys] = NULL; } return nkeys; } /* Clear the content of a keyinfo object. */ static void clear_keyinfo (struct keyinfo_s *keyinfo) { unsigned int nkeys; if (keyinfo) { if (keyinfo->keys) { for (nkeys=0; keyinfo->keys[nkeys]; nkeys++) gpgme_key_unref (keyinfo->keys[nkeys]); g_free (keyinfo->keys); keyinfo->keys = NULL; } keyinfo->dimof_keys = 0; keyinfo->truncated = 0; } } /* Update the row in the list described by by STORE and ITER. The new data shall be taken from INFO. */ static void update_recplist_row (GtkListStore *store, GtkTreeIter *iter, struct userdata_s *info) { char *infostr = NULL; char *oldinfostr = NULL; int any_pgp = 0, any_x509 = 0; gpgme_key_t key; char *mailbox; char *oldmailbox; if (info->pgp.keys && info->pgp.keys[0]) any_pgp = 1; if (info->x509.keys && info->x509.keys[0]) any_x509 = 1; if (info->ignore_recipient) infostr = NULL; else if (any_pgp && any_x509 && info->pgp.keys[1] && info->x509.keys[1]) infostr = g_strdup (_("[Ambiguous keys. Right-click to select]")); else if (any_pgp && info->pgp.keys[1]) infostr = g_strdup (_("[Ambiguous PGP key. Right-click to select]")); else if (any_x509 && info->x509.keys[1]) infostr = g_strdup (_("[Ambiguous X.509 key. Right-click to select]")); else if (any_pgp && !info->pgp.keys[1]) { /* Exactly one key found. */ key = info->pgp.keys[0]; infostr = gpa_gpgme_key_get_userid (key->uids); } else if (any_x509 && !info->x509.keys[1]) { key = info->x509.keys[0]; infostr = gpa_gpgme_key_get_userid (key->uids); } else infostr = g_strdup (_("[Right-click to select]")); mailbox = g_markup_printf_escaped ("%s", info->ignore_recipient? "true":"false", info->mailbox); g_print (" mbox=`%s' fmt=`%s'\n", info->mailbox, mailbox); gtk_tree_model_get (GTK_TREE_MODEL (store), iter, RECPLIST_MAILBOX, &oldmailbox, RECPLIST_KEYID, &oldinfostr, -1); gtk_list_store_set (store, iter, RECPLIST_MAILBOX, mailbox, RECPLIST_HAS_PGP, any_pgp, RECPLIST_HAS_X509, any_x509, RECPLIST_KEYID, infostr, -1); g_free (oldmailbox); g_free (mailbox); g_free (infostr); g_free (oldinfostr); } /* Parse one recipient, this is the working horse of parse_recipeints. */ static void parse_one_recipient (gpgme_ctx_t ctx, GtkListStore *store, GtkTreeIter *iter, struct userdata_s *info) { static int have_locate = -1; gpgme_key_t key = NULL; gpgme_keylist_mode_t mode; if (have_locate == -1) have_locate = is_gpg_version_at_least ("2.0.10"); g_return_if_fail (info); clear_keyinfo (&info->pgp); gpgme_set_protocol (ctx, GPGME_PROTOCOL_OpenPGP); mode = gpgme_get_keylist_mode (ctx); if (have_locate) gpgme_set_keylist_mode (ctx, (mode | (GPGME_KEYLIST_MODE_LOCAL | GPGME_KEYLIST_MODE_EXTERN))); if (!gpgme_op_keylist_start (ctx, info->mailbox, 0)) { while (!gpgme_op_keylist_next (ctx, &key)) { if (key->revoked || key->disabled || key->expired || !key->can_encrypt) gpgme_key_unref (key); else if (append_key_to_keyinfo (&info->pgp, key) >= TRUNCATE_KEYSEARCH_AT) { /* Note that the truncation flag is not 100% correct. In case the next iteration would not yield a new key we have not actually truncated the search. */ info->pgp.truncated = 1; break; } } } gpgme_op_keylist_end (ctx); gpgme_set_keylist_mode (ctx, mode); clear_keyinfo (&info->x509); gpgme_set_protocol (ctx, GPGME_PROTOCOL_CMS); if (!gpgme_op_keylist_start (ctx, info->mailbox, 0)) { while (!gpgme_op_keylist_next (ctx, &key)) { if (key->revoked || key->disabled || key->expired || !key->can_encrypt) gpgme_key_unref (key); else if (append_key_to_keyinfo (&info->x509,key) >= TRUNCATE_KEYSEARCH_AT) { info->x509.truncated = 1; break; } } } gpgme_op_keylist_end (ctx); update_recplist_row (store, iter, info); } /* Parse the list of recipients, find possible keys and update the store. */ static void parse_recipients (GtkListStore *store) { GtkTreeModel *model; GtkTreeIter iter; gpg_error_t err; gpgme_ctx_t ctx; err = gpgme_new (&ctx); if (err) gpa_gpgme_error (err); model = GTK_TREE_MODEL (store); /* Walk through the list, reading each row. */ if (gtk_tree_model_get_iter_first (model, &iter)) do { struct userdata_s *info; gtk_tree_model_get (model, &iter, RECPLIST_USERDATA, &info, -1); /* Do something with the data */ /*g_print ("parsing mailbox `%s'\n", info? info->mailbox:"(null)");*/ parse_one_recipient (ctx, store, &iter,info); } while (gtk_tree_model_iter_next (model, &iter)); gpgme_release (ctx); } /* Callback for the row-activated signal. */ static void recplist_row_activated_cb (GtkTreeView *tree_view, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data) { /*RecipientDlg *dialog = user_data;*/ GtkTreeIter iter; GtkTreeModel *model; char *mailbox; model = gtk_tree_view_get_model (tree_view); if (!gtk_tree_model_get_iter (model, &iter, path)) return; gtk_tree_model_get (model, &iter, RECPLIST_MAILBOX, &mailbox, -1); g_free (mailbox); } static void recplist_row_changed_cb (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *changediter, gpointer user_data) { RecipientDlg *dialog = user_data; g_return_if_fail (dialog); update_statushint (dialog); } static void rbutton_toggled_cb (GtkToggleButton *button, gpointer user_data) { RecipientDlg *dialog = user_data; GtkTreeViewColumn *column; int pgp = FALSE; int x509 = FALSE; if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->radio_pgp))) { pgp = TRUE; } else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->radio_x509))) { x509 = TRUE; } else { pgp = TRUE; x509 = TRUE; } column = gtk_tree_view_get_column (GTK_TREE_VIEW (dialog->clist_keys), RECPLIST_HAS_PGP); gtk_tree_view_column_set_visible (column, pgp); column = gtk_tree_view_get_column (GTK_TREE_VIEW (dialog->clist_keys), RECPLIST_HAS_X509); gtk_tree_view_column_set_visible (column, x509); update_statushint (dialog); } /* The select key selection has returned. */ static void select_key_response_cb (SelectKeyDlg *seldlg, int response, void *user_data) { RecipientDlg *dialog = user_data; gpgme_key_t key; if (response != GTK_RESPONSE_OK) { /* The dialog was canceled */ gtk_widget_destroy (GTK_WIDGET (seldlg)); dialog->freeze_key_selection--; return; } key = select_key_dlg_get_key (seldlg); if (key) { GtkTreeModel *model; GtkTreeIter iter; struct userdata_s *info = NULL; char *uidstr = gpa_gpgme_key_get_userid (key->uids); g_free (uidstr); if ((model = get_selected_row (dialog, &iter))) { gtk_tree_model_get (model, &iter, RECPLIST_USERDATA, &info, -1); if (info) { if (key->protocol == GPGME_PROTOCOL_OpenPGP) { clear_keyinfo (&info->pgp); gpgme_key_ref (key); append_key_to_keyinfo (&info->pgp, key); } else if (key->protocol == GPGME_PROTOCOL_CMS) { clear_keyinfo (&info->x509); gpgme_key_ref (key); append_key_to_keyinfo (&info->x509, key); } update_recplist_row (GTK_LIST_STORE (model), &iter, info); update_statushint (dialog); } } gpgme_key_unref (key); } gtk_widget_destroy (GTK_WIDGET (seldlg)); dialog->freeze_key_selection--; } static void do_select_key (RecipientDlg *dialog, gpgme_protocol_t protocol) { GtkTreeModel *model; GtkTreeIter iter; struct userdata_s *info = NULL; SelectKeyDlg *seldlg; if ((model = get_selected_row (dialog, &iter))) { gtk_tree_model_get (model, &iter, RECPLIST_USERDATA, &info, -1); if (info) { gpgme_key_t *keys; if (protocol == GPGME_PROTOCOL_OpenPGP) keys = info->pgp.keys; else if (protocol == GPGME_PROTOCOL_CMS) keys = info->x509.keys; else keys = NULL; seldlg = select_key_dlg_new_with_keys (GTK_WIDGET (dialog), protocol, keys, info->mailbox); g_signal_connect (G_OBJECT (seldlg), "response", G_CALLBACK (select_key_response_cb), dialog); gtk_widget_show_all (GTK_WIDGET (seldlg)); } } else dialog->freeze_key_selection--; } static void recplist_popup_pgp (GSimpleAction *simple, GVariant *parameter, gpointer user_data) { RecipientDlg *dialog = user_data; do_select_key (dialog, GPGME_PROTOCOL_OpenPGP); } static void recplist_popup_x509 (GSimpleAction *simple, GVariant *parameter, gpointer user_data) { RecipientDlg *dialog = user_data; do_select_key (dialog, GPGME_PROTOCOL_CMS); } static void recplist_popup_ignore (GSimpleAction *simple, GVariant *parameter, gpointer user_data) { GtkTreeModel *model; GtkTreeIter iter; struct userdata_s *info; RecipientDlg *dialog = user_data; if ((model = get_selected_row (dialog, &iter))) { gtk_tree_model_get (model, &iter, RECPLIST_USERDATA, &info, -1); info->ignore_recipient = !info->ignore_recipient; update_recplist_row (GTK_LIST_STORE (model), &iter, info); update_statushint (dialog); } dialog->freeze_key_selection--; } /* Left Click on the list auto selects an action. */ static void recplist_default_action (RecipientDlg *dialog) { dialog->freeze_key_selection--; } static gint recplist_display_popup_menu (RecipientDlg *dialog, GdkEvent *event, GtkListStore *list) { GtkMenu *menu; GdkEventButton *event_button; g_return_val_if_fail (dialog, FALSE); g_return_val_if_fail (event, FALSE); menu = GTK_MENU (dialog->popup_menu); if (event->type == GDK_BUTTON_PRESS) { event_button = (GdkEventButton *) event; if (event_button->button == 1 || event_button->button == 3) { GtkTreeSelection *selection; GtkTreePath *path; GtkTreeIter iter; selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list)); /* Make sure the clicked key is selected. */ if (!dialog->freeze_key_selection && gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (list), event_button->x, event_button->y, &path, NULL, NULL, NULL)) { dialog->freeze_key_selection++; gtk_tree_model_get_iter (gtk_tree_view_get_model (GTK_TREE_VIEW (list)), &iter, path); if (!gtk_tree_selection_iter_is_selected (selection, &iter)) { gtk_tree_selection_unselect_all (selection); gtk_tree_selection_select_path (selection, path); } if (event_button->button == 1) recplist_default_action (dialog); else gtk_menu_popup_at_pointer (menu, NULL); } return TRUE; } } return FALSE; } /* Create the popup menu for the recipient list. */ static GtkWidget * recplist_popup_menu_new (GtkWidget *window, RecipientDlg *dialog) { static const GActionEntry entries [] = { { "select_gpg_key", recplist_popup_pgp }, { "select_cms_key", recplist_popup_x509 }, { "toggle_ignore_flag", recplist_popup_ignore }, }; static const char *menu_string = "" "" "
" "" "Select _PGP key" "app.select_pgp_key" "" "" "Select _S\\/MIME key" "app.select_cms_key" "" "" "Toggle _Ignore flag" "app.toggle_ignore_flag" "" "
" "
" "
"; GError *err = NULL; GtkWidget *popup_menu_widget; GtkBuilder *gtk_builder = gtk_builder_new (); if (gtk_builder_add_from_string( gtk_builder, menu_string , -1, &err) == 0) { printf("ERROR menu: %s \n", err->message); } GMenuModel *popup_menu_model = G_MENU_MODEL (gtk_builder_get_object (GTK_BUILDER (gtk_builder), "popup_menu")); popup_menu_widget = gtk_menu_new_from_model (popup_menu_model); GApplication *gpa_app = G_APPLICATION (get_gpa_application ()); g_action_map_add_action_entries (G_ACTION_MAP (gpa_app), entries, G_N_ELEMENTS (entries), window); return popup_menu_widget; } /************************************************************ ****************** Object Management ******************** ************************************************************/ static void recipient_dlg_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { RecipientDlg *dialog = RECIPIENT_DLG (object); switch (prop_id) { case PROP_WINDOW: g_value_set_object (value, gtk_window_get_transient_for (GTK_WINDOW (dialog))); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void recipient_dlg_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { RecipientDlg *dialog = RECIPIENT_DLG (object); switch (prop_id) { case PROP_WINDOW: gtk_window_set_transient_for (GTK_WINDOW (dialog), g_value_get_object (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void recipient_dlg_finalize (GObject *object) { /* Fixme: Release the store. */ G_OBJECT_CLASS (parent_class)->finalize (object); } static void recipient_dlg_init (RecipientDlg *dialog) { } static GObject* recipient_dlg_constructor (GType type, guint n_construct_properties, GObjectConstructParam *construct_properties) { GObject *object; RecipientDlg *dialog; GtkWidget *vbox; GtkWidget *hbox; GtkWidget *widget; GtkWidget *labelKeys; GtkWidget *scrollerKeys; GtkWidget *clistKeys; object = parent_class->constructor (type, n_construct_properties, construct_properties); dialog = RECIPIENT_DLG (object); gpa_window_set_title (GTK_WINDOW (dialog), _("Select keys for recipients")); gtk_dialog_add_buttons (GTK_DIALOG (dialog), 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); gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_OK, FALSE); gtk_container_set_border_width (GTK_CONTAINER (dialog), 5); //vbox = GTK_DIALOG (dialog)->vbox; vbox = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); gtk_container_set_border_width (GTK_CONTAINER (vbox), 5); labelKeys = gtk_label_new_with_mnemonic (_("_Recipient list")); gtk_widget_set_halign (GTK_WIDGET (labelKeys), GTK_ALIGN_START); gtk_widget_set_valign (GTK_WIDGET (labelKeys), GTK_ALIGN_CENTER); gtk_box_pack_start (GTK_BOX (vbox), labelKeys, FALSE, FALSE, 0); scrollerKeys = gtk_scrolled_window_new (NULL, NULL); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrollerKeys), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_box_pack_start (GTK_BOX (vbox), scrollerKeys, TRUE, TRUE, 0); gtk_widget_set_size_request (scrollerKeys, 400, 200); clistKeys = recplist_window_new (); dialog->clist_keys = clistKeys; gtk_container_add (GTK_CONTAINER (scrollerKeys), clistKeys); gtk_label_set_mnemonic_widget (GTK_LABEL (labelKeys), clistKeys); dialog->popup_menu = recplist_popup_menu_new (dialog->clist_keys, dialog); g_signal_connect_swapped (G_OBJECT (dialog->clist_keys), "button_press_event", G_CALLBACK (recplist_display_popup_menu), dialog); dialog->radio_pgp = gtk_radio_button_new_with_mnemonic (NULL, _("Use _PGP")); dialog->radio_x509 = gtk_radio_button_new_with_mnemonic (gtk_radio_button_get_group (GTK_RADIO_BUTTON (dialog->radio_pgp)), _("Use _X.509")); dialog->radio_auto = gtk_radio_button_new_with_mnemonic (gtk_radio_button_get_group (GTK_RADIO_BUTTON (dialog->radio_pgp)), _("_Auto selection")); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->radio_auto), TRUE); hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); gtk_container_set_border_width (GTK_CONTAINER (hbox), 5); gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0); gtk_box_pack_start (GTK_BOX (hbox), dialog->radio_pgp, FALSE, FALSE, 0); gtk_box_pack_start (GTK_BOX (hbox), dialog->radio_x509, FALSE, FALSE, 0); gtk_box_pack_start (GTK_BOX (hbox), dialog->radio_auto, FALSE, FALSE, 0); widget = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL); gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0); dialog->statushint = gtk_label_new (NULL); gtk_box_pack_start (GTK_BOX (vbox), dialog->statushint, FALSE, FALSE, 0); g_signal_connect (G_OBJECT (GTK_TREE_VIEW (dialog->clist_keys)), "row-activated", G_CALLBACK (recplist_row_activated_cb), dialog); g_signal_connect (G_OBJECT (gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->clist_keys))), "row-changed", G_CALLBACK (recplist_row_changed_cb), dialog); g_signal_connect (G_OBJECT (dialog->radio_pgp), "toggled", G_CALLBACK (rbutton_toggled_cb), dialog); g_signal_connect (G_OBJECT (dialog->radio_x509), "toggled", G_CALLBACK (rbutton_toggled_cb), dialog); g_signal_connect (G_OBJECT (dialog->radio_auto), "toggled", G_CALLBACK (rbutton_toggled_cb), dialog); return object; } static void recipient_dlg_class_init (RecipientDlgClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); parent_class = g_type_class_peek_parent (klass); object_class->constructor = recipient_dlg_constructor; object_class->finalize = recipient_dlg_finalize; object_class->set_property = recipient_dlg_set_property; object_class->get_property = recipient_dlg_get_property; g_object_class_install_property (object_class, PROP_WINDOW, g_param_spec_object ("window", "Parent window", "Parent window", GTK_TYPE_WIDGET, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)); } GType recipient_dlg_get_type (void) { static GType this_type; if (!this_type) { static const GTypeInfo this_info = { sizeof (RecipientDlgClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) recipient_dlg_class_init, NULL, /* class_finalize */ NULL, /* class_data */ sizeof (RecipientDlg), 0, /* n_preallocs */ (GInstanceInitFunc) recipient_dlg_init, }; this_type = g_type_register_static (GTK_TYPE_DIALOG, "RecipientDlg", &this_info, 0); } return this_type; } /************************************************************ ********************** Public API ************************ ************************************************************/ RecipientDlg * recipient_dlg_new (GtkWidget *parent) { RecipientDlg *dialog; dialog = g_object_new (RECIPIENT_DLG_TYPE, "window", parent, NULL); return dialog; } /* Put RECIPIENTS into the list. PROTOCOL select the default protocol. */ void recipient_dlg_set_recipients (RecipientDlg *dialog, GSList *recipients, gpgme_protocol_t protocol) { GtkListStore *store; GSList *recp; GtkTreeIter iter; const char *name; GtkWidget *widget; g_return_if_fail (dialog); dialog->freeze_update_statushint++; if (protocol == GPGME_PROTOCOL_OpenPGP) widget = dialog->radio_pgp; else if (protocol == GPGME_PROTOCOL_CMS) widget = dialog->radio_x509; else widget = dialog->radio_auto; gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), TRUE); if (widget != dialog->radio_auto) { gtk_widget_set_sensitive (GTK_WIDGET (dialog->radio_pgp), FALSE); gtk_widget_set_sensitive (GTK_WIDGET (dialog->radio_x509), FALSE); gtk_widget_set_sensitive (GTK_WIDGET (dialog->radio_auto), FALSE); } store = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->clist_keys))); gtk_list_store_clear (store); for (recp = recipients; recp; recp = g_slist_next (recp)) { name = recp->data; if (name && *name) { struct userdata_s *info = g_malloc0 (sizeof *info); info->mailbox = g_strdup (name); gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, RECPLIST_MAILBOX, g_strdup (""), RECPLIST_HAS_PGP, FALSE, RECPLIST_HAS_X509, FALSE, RECPLIST_KEYID, NULL, RECPLIST_USERDATA, info, -1); } } parse_recipients (store); dialog->freeze_update_statushint--; update_statushint (dialog); } /* Return the selected keys as well as the selected protocol. */ gpgme_key_t * recipient_dlg_get_keys (RecipientDlg *dialog, gpgme_protocol_t *r_protocol) { GtkTreeModel *model; GtkTreeIter iter; size_t idx, nkeys; gpgme_key_t key, *keyarray; gpgme_protocol_t protocol; g_return_val_if_fail (dialog, NULL); if (!dialog->usable) return NULL; /* No valid keys available. */ protocol = dialog->selected_protocol; model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->clist_keys)); /* Count the number of possible keys. */ nkeys = 0; if (gtk_tree_model_get_iter_first (model, &iter)) { do nkeys++; while (gtk_tree_model_iter_next (model, &iter)); } keyarray = g_new (gpgme_key_t, nkeys+1); idx = 0; if (gtk_tree_model_get_iter_first (model, &iter)) { do { char *mailbox; struct userdata_s *info; if (idx >= nkeys) { g_debug ("key list grew unexpectedly\n"); break; } gtk_tree_model_get (model, &iter, RECPLIST_MAILBOX, &mailbox, RECPLIST_USERDATA, &info, -1); if (info && !info->ignore_recipient) { if (protocol == GPGME_PROTOCOL_OpenPGP && info->pgp.keys) key = info->pgp.keys[0]; else if (protocol == GPGME_PROTOCOL_CMS && info->x509.keys) key = info->x509.keys[0]; else key = NULL; if (key) { gpgme_key_ref (key); keyarray[idx++] = key; } } g_free (mailbox); } while (gtk_tree_model_iter_next (model, &iter)); } g_assert (idx < nkeys+1); keyarray[idx] = NULL; if (r_protocol) *r_protocol = protocol; return keyarray; }