[balsa] Crypto-related (mostly) fixes and improvements
- From: Peter Bloomfield <peterb src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [balsa] Crypto-related (mostly) fixes and improvements
- Date: Fri, 11 Aug 2017 01:36:52 +0000 (UTC)
commit 3056bc121abcbb9bf5d0add41346e0bb042225b3
Author: Albrecht Dreß <albrecht dress arcor de>
Date: Thu Aug 10 21:35:39 2017 -0400
Crypto-related (mostly) fixes and improvements
* libbalsa/gmime-multipart-crypt.c: do not qp-encode 7-bit parts for encryption
* libbalsa/identity.[ch]: implement separate forced signing key id's
for gpg and s/mime including selection from the key list; clarify option text
* libbalsa/libbalsa-gpgme-cb.c: simplify key list, show key details on double-click
* libbalsa/libbalsa-gpgme-keys.[ch]: add functions for exporting
and importing ascii-armoured keys; re-factor import result evaluation
* libbalsa/libbalsa-gpgme-widgets.c: extend subkey details
* libbalsa/libbalsa-gpgme.[ch]: fix context creation for s/mime;
add helpers for configuring the gpgme context's home folder,
for exporting a key to ASCII and for identifying the proper key id of a secret key;
fix confusing comment
* libbalsa/message.[ch]: use a reference to the sending identity
instead of copying the key id
* libbalsa/misc.c: re-factor deleting a folder and creating a temp folder
(re-sent from last week's patch)
* libbalsa/rfc3156.c: fix mem leak when encrypting a message
(re-sent from last week's patch)
* libbalsa/send.c: add helper for creating a gpg public key attachment
and attach the key on request; fix mem leak in encryption
* libbalsa/smtp-server.c: remove misleading/confusing comment
(re-sent from last week's patch)
* src/balsa-mime-widget-crypto.[ch]: implement display of application/pgp-keys parts
and the import of the keys within them
* src/balsa-mime-widget.c: call handler for application/pgp-keys parts
* src/information-dialog.c: add missing dialogue flags
* src/sendmsg-window.[ch], ui/sendmsg-window.ui: add user interface
for attaching the GnuPG public key
Signed-off-by: Peter Bloomfield <PeterBloomfield bellsouth net>
ChangeLog | 32 ++++++
libbalsa/gmime-multipart-crypt.c | 6 +-
libbalsa/identity.c | 102 +++++++++++++++++---
libbalsa/identity.h | 3 +-
libbalsa/libbalsa-gpgme-cb.c | 147 +++++++++++++---------------
libbalsa/libbalsa-gpgme-keys.c | 196 +++++++++++++++++++++++++++----------
libbalsa/libbalsa-gpgme-keys.h | 33 ++++++
libbalsa/libbalsa-gpgme-widgets.c | 55 +++++++++--
libbalsa/libbalsa-gpgme.c | 167 +++++++++++++++++++++++++++++++-
libbalsa/libbalsa-gpgme.h | 15 +++
libbalsa/message.c | 6 +-
libbalsa/message.h | 7 +-
libbalsa/misc.c | 90 +++++++-----------
libbalsa/rfc3156.c | 4 +-
libbalsa/send.c | 121 +++++++++++++++++++----
libbalsa/smtp-server.c | 1 -
libnetclient/net-client.c | 3 +-
src/balsa-mime-widget-crypto.c | 143 +++++++++++++++++++++++++++
src/balsa-mime-widget-crypto.h | 4 +
src/balsa-mime-widget.c | 1 +
src/information-dialog.c | 4 +-
src/sendmsg-window.c | 29 +++++-
src/sendmsg-window.h | 1 +
ui/sendmsg-window.ui | 7 ++
24 files changed, 933 insertions(+), 244 deletions(-)
---
diff --git a/ChangeLog b/ChangeLog
index 8f3939b..05b4e1f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,35 @@
+2017-08-10 Albrecht Dreß
+
+ Crypto-related (mostly) fixes and improvements
+
+ * libbalsa/gmime-multipart-crypt.c: do not qp-encode 7-bit parts for encryption
+ * libbalsa/identity.[ch]: implement separate forced signing key id's
+ for gpg and s/mime including selection from the key list; clarify option text
+ * libbalsa/libbalsa-gpgme-cb.c: simplify key list, show key details on double-click
+ * libbalsa/libbalsa-gpgme-keys.[ch]: add functions for exporting
+ and importing ascii-armoured keys; re-factor import result evaluation
+ * libbalsa/libbalsa-gpgme-widgets.c: extend subkey details
+ * libbalsa/libbalsa-gpgme.[ch]: fix context creation for s/mime;
+ add helpers for configuring the gpgme context's home folder,
+ for exporting a key to ASCII and for identifying the proper key id of a secret key;
+ fix confusing comment
+ * libbalsa/message.[ch]: use a reference to the sending identity
+ instead of copying the key id
+ * libbalsa/misc.c: re-factor deleting a folder and creating a temp folder
+ (re-sent from last week's patch)
+ * libbalsa/rfc3156.c: fix mem leak when encrypting a message
+ (re-sent from last week's patch)
+ * libbalsa/send.c: add helper for creating a gpg public key attachment
+ and attach the key on request; fix mem leak in encryption
+ * libbalsa/smtp-server.c: remove misleading/confusing comment
+ (re-sent from last week's patch)
+ * src/balsa-mime-widget-crypto.[ch]: implement display of application/pgp-keys parts
+ and the import of the keys within them
+ * src/balsa-mime-widget.c: call handler for application/pgp-keys parts
+ * src/information-dialog.c: add missing dialogue flags
+ * src/sendmsg-window.[ch], ui/sendmsg-window.ui: add user interface
+ for attaching the GnuPG public key
+
2017-08-05 Peter Bloomfield <pbloomfield bellsouth net>
* libbalsa/imap/imap-tls.c (imap_create_ssl): unlock
diff --git a/libbalsa/gmime-multipart-crypt.c b/libbalsa/gmime-multipart-crypt.c
index 9c4500e..ad158fe 100644
--- a/libbalsa/gmime-multipart-crypt.c
+++ b/libbalsa/gmime-multipart-crypt.c
@@ -42,7 +42,9 @@
*
* Prepare a part (and all subparts) to be signed. To do this we need
* to set the encoding of all parts (that are not already encoded to
- * either QP or Base64) to QP.
+ * either QP or Base64 or 7-bit) to QP.
+ *
+ * Ref: RFC 3156, sect. 3.
**/
static void
sign_prepare(GMimeObject * mime_part)
@@ -72,7 +74,7 @@ sign_prepare(GMimeObject * mime_part)
sign_prepare(subpart);
} else {
encoding = g_mime_part_get_content_encoding(GMIME_PART(mime_part));
- if (encoding != GMIME_CONTENT_ENCODING_BASE64)
+ if ((encoding != GMIME_CONTENT_ENCODING_BASE64) && (encoding != GMIME_CONTENT_ENCODING_7BIT))
g_mime_part_set_content_encoding(GMIME_PART(mime_part),
GMIME_CONTENT_ENCODING_QUOTEDPRINTABLE);
}
diff --git a/libbalsa/identity.c b/libbalsa/identity.c
index 3a1a3b4..5a9ea2f 100644
--- a/libbalsa/identity.c
+++ b/libbalsa/identity.c
@@ -33,6 +33,10 @@
# include "macosx-helpers.h"
#endif
+#ifdef HAVE_GPGME
+# include "libbalsa-gpgme.h"
+#endif
+
#include <string.h>
#include "smtp-server.h"
@@ -111,7 +115,8 @@ libbalsa_identity_init(LibBalsaIdentity* ident)
ident->always_trust = FALSE;
ident->warn_send_plain = TRUE;
ident->crypt_protocol = LIBBALSA_PROTECT_OPENPGP;
- ident->force_key_id = NULL;
+ ident->force_gpg_key_id = NULL;
+ ident->force_smime_key_id = NULL;
ident->request_mdn = FALSE;
ident->request_dsn = FALSE;
/*
@@ -141,7 +146,8 @@ libbalsa_identity_finalize(GObject * object)
g_object_unref(ident->smtp_server);
g_free(ident->face);
g_free(ident->x_face);
- g_free(ident->force_key_id);
+ g_free(ident->force_gpg_key_id);
+ g_free(ident->force_smime_key_id);
G_OBJECT_CLASS(parent_class)->finalize(object);
}
@@ -584,6 +590,11 @@ static void ident_dialog_add_check_and_entry(GtkWidget *, gint, GtkDialog *,
const gchar *, const gchar *);
static void ident_dialog_add_entry(GtkWidget *, gint, GtkDialog *,
const gchar *, const gchar *);
+static void ident_dialog_add_keysel_entry(GtkWidget *grid,
+ gint row,
+ GtkDialog *dialog,
+ const gchar *label_name,
+ const gchar *entry_key);
typedef enum LibBalsaIdentityPathType_ {
LBI_PATH_TYPE_FACE,
LBI_PATH_TYPE_XFACE
@@ -1046,15 +1057,19 @@ setup_ident_frame(GtkDialog * dialog, gboolean createp, gpointer tree,
_("default protocol"),
"identity-crypt-protocol");
ident_dialog_add_checkbutton(grid, row++, dialog,
- _("always trust GnuPG keys when encrypting"),
+ _("always trust GnuPG keys to encrypt messages"),
"identity-trust-always", TRUE);
ident_dialog_add_checkbutton(grid, row++, dialog,
_("remind me if messages can be encrypted"),
"identity-warn-send-plain", TRUE);
- ident_dialog_add_entry(grid, row++, dialog,
- _("use secret key with this id for signing\n"
- "(leave empty for automatic selection)"),
- "identity-keyid");
+ ident_dialog_add_keysel_entry(grid, row++, dialog,
+ _("use secret key with this id for signing GnuPG messages\n"
+ "(leave empty for automatic selection)"),
+ "identity-keyid");
+ ident_dialog_add_keysel_entry(grid, row++, dialog,
+ _("use certificate with this id for signing S/MIME messages\n"
+ "(leave empty for automatic selection)"),
+ "identity-keyid-sm");
#ifndef HAVE_GPGME
gtk_widget_set_sensitive(grid, FALSE);
#endif
@@ -1148,6 +1163,64 @@ ident_dialog_add_entry(GtkWidget * grid, gint row, GtkDialog * dialog,
gtk_widget_grab_focus(entry);
}
+
+#ifdef HAVE_GPGME
+static void
+choose_key(GtkButton *button, gpointer user_data)
+{
+ const gchar *target;
+ gpgme_protocol_t protocol;
+ gchar *email;
+ gchar *keyid;
+ GError *error = NULL;
+
+ target = g_object_get_data(G_OBJECT(button), "target");
+ if (strcmp(target, "identity-keyid") == 0) {
+ protocol = GPGME_PROTOCOL_OpenPGP;
+ } else {
+ protocol = GPGME_PROTOCOL_CMS;
+ }
+
+ email = ident_dialog_get_text(G_OBJECT(user_data), "identity-address");
+ keyid = libbalsa_gpgme_get_seckey(protocol, email, GTK_WINDOW(user_data), &error);
+ if (keyid != NULL) {
+ display_frame_set_field(G_OBJECT(user_data), target, keyid);
+ g_free(keyid);
+ }
+ if (error != NULL) {
+ libbalsa_information(LIBBALSA_INFORMATION_WARNING,
+ _("Error selecting key: %s"), error->message);
+ g_clear_error(&error);
+ }
+}
+#endif
+
+
+/*
+ * Add a GtkEntry to the given dialog with a label next to it
+ * explaining the contents. A reference to the entry is stored as
+ * object data attached to the dialog with the given key. A button
+ * is added behind the entry to choose a key.
+ */
+static void
+ident_dialog_add_keysel_entry(GtkWidget *grid,
+ gint row,
+ GtkDialog *dialog,
+ const gchar *label_name,
+ const gchar *entry_key)
+{
+ GtkWidget *button;
+
+ ident_dialog_add_entry(grid, row, dialog, label_name, entry_key);
+ button = gtk_button_new_with_label(_("Choose…"));
+#ifdef HAVE_GPGME
+ g_object_set_data_full(G_OBJECT(button), "target", g_strdup(entry_key), (GDestroyNotify) g_free);
+ g_signal_connect(button, "clicked", G_CALLBACK(choose_key), dialog);
+#endif
+ gtk_grid_attach(GTK_GRID(grid), button, 2, row, 1, 1);
+}
+
+
/*
* Add a GtkFileChooserButton to the given dialog with a label next to it
* explaining the contents. A reference to the button is stored as
@@ -1472,8 +1545,10 @@ ident_dialog_update(GObject * dlg)
id->warn_send_plain = ident_dialog_get_bool(dlg, "identity-warn-send-plain");
id->crypt_protocol = GPOINTER_TO_INT(ident_dialog_get_value
(dlg, "identity-crypt-protocol"));
- g_free(id->force_key_id);
- id->force_key_id = g_strstrip(ident_dialog_get_text(dlg, "identity-keyid"));
+ g_free(id->force_gpg_key_id);
+ id->force_gpg_key_id = g_strstrip(ident_dialog_get_text(dlg, "identity-keyid"));
+ g_free(id->force_smime_key_id);
+ id->force_smime_key_id = g_strstrip(ident_dialog_get_text(dlg, "identity-keyid-sm"));
return TRUE;
}
@@ -1856,7 +1931,8 @@ display_frame_update(GObject * dialog, LibBalsaIdentity* ident)
ident->warn_send_plain);
display_frame_set_gpg_mode(dialog, "identity-crypt-protocol",
&ident->crypt_protocol);
- display_frame_set_field(dialog, "identity-keyid", ident->force_key_id);
+ display_frame_set_field(dialog, "identity-keyid", ident->force_gpg_key_id);
+ display_frame_set_field(dialog, "identity-keyid-sm", ident->force_smime_key_id);
}
@@ -1957,7 +2033,8 @@ libbalsa_identity_new_config(const gchar* name)
ident->always_trust = libbalsa_conf_get_bool("GpgTrustAlways");
ident->warn_send_plain = libbalsa_conf_get_bool("GpgWarnSendPlain=true");
ident->crypt_protocol = libbalsa_conf_get_int("CryptProtocol=16");
- ident->force_key_id = libbalsa_conf_get_string("ForceKeyID");
+ ident->force_gpg_key_id = libbalsa_conf_get_string("ForceKeyID");
+ ident->force_smime_key_id = libbalsa_conf_get_string("ForceKeyIDSMime");
return ident;
}
@@ -2002,7 +2079,8 @@ libbalsa_identity_save(LibBalsaIdentity* ident, const gchar* group)
libbalsa_conf_set_bool("GpgTrustAlways", ident->always_trust);
libbalsa_conf_set_bool("GpgWarnSendPlain", ident->warn_send_plain);
libbalsa_conf_set_int("CryptProtocol", ident->crypt_protocol);
- libbalsa_conf_set_string("ForceKeyID", ident->force_key_id);
+ libbalsa_conf_set_string("ForceKeyID", ident->force_gpg_key_id);
+ libbalsa_conf_set_string("ForceKeyIDSMime", ident->force_smime_key_id);
libbalsa_conf_pop_group();
}
diff --git a/libbalsa/identity.h b/libbalsa/identity.h
index ad04647..d8768d2 100644
--- a/libbalsa/identity.h
+++ b/libbalsa/identity.h
@@ -83,7 +83,8 @@ G_BEGIN_DECLS
gboolean always_trust;
gboolean warn_send_plain;
gint crypt_protocol;
- gchar *force_key_id;
+ gchar *force_gpg_key_id;
+ gchar *force_smime_key_id;
LibBalsaSmtpServer *smtp_server;
};
diff --git a/libbalsa/libbalsa-gpgme-cb.c b/libbalsa/libbalsa-gpgme-cb.c
index d0db84f..eb95ef8 100644
--- a/libbalsa/libbalsa-gpgme-cb.c
+++ b/libbalsa/libbalsa-gpgme-cb.c
@@ -43,9 +43,6 @@
/* stuff to get a key fingerprint from a selection list */
enum {
GPG_KEY_USER_ID_COLUMN = 0,
- GPG_KEY_ID_COLUMN,
- GPG_KEY_LENGTH_COLUMN,
- GPG_KEY_VALIDITY_COLUMN,
GPG_KEY_PTR_COLUMN,
GPG_KEY_NUM_COLUMNS
};
@@ -128,12 +125,48 @@ lb_gpgme_passphrase(void *hook, const gchar * uid_hint,
}
+static gboolean
+key_button_event_press_cb(GtkWidget *widget,
+ GdkEventButton *event,
+ gpointer data)
+{
+ GtkTreeView *tree_view = GTK_TREE_VIEW(widget);
+ GtkTreeSelection *selection = gtk_tree_view_get_selection(tree_view);
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+
+ g_return_val_if_fail(event != NULL, FALSE);
+ if ((event->type != GDK_2BUTTON_PRESS) || event->window != gtk_tree_view_get_bin_window(tree_view)) {
+ return FALSE;
+ }
+
+ if (gtk_tree_view_get_path_at_pos(tree_view, event->x, event->y, &path, NULL, NULL, NULL)) {
+ if (!gtk_tree_selection_path_is_selected(selection, path)) {
+ gtk_tree_view_set_cursor(tree_view, path, NULL, FALSE);
+ gtk_tree_view_scroll_to_cell(tree_view, path, NULL, FALSE, 0, 0);
+ }
+ gtk_tree_path_free(path);
+ }
+
+ if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
+ gpgme_key_t key;
+ GtkWidget *dialog;
+
+ gtk_tree_model_get(model, &iter, GPG_KEY_PTR_COLUMN, &key, -1);
+ dialog = libbalsa_key_dialog(GTK_WINDOW(data), GTK_BUTTONS_CLOSE, key, GPG_SUBKEY_CAP_ALL,
NULL, NULL);
+ (void) gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+ }
+
+ return TRUE;
+}
+
+
gpgme_key_t
lb_gpgme_select_key(const gchar * user_name, lb_key_sel_md_t mode, GList * keys,
gpgme_protocol_t protocol, GtkWindow * parent)
{
- static const gchar *col_titles[] =
- { N_("User ID"), N_("Key ID"), N_("Length"), N_("Validity") };
GtkWidget *dialog;
GtkWidget *vbox;
GtkWidget *label;
@@ -143,11 +176,11 @@ lb_gpgme_select_key(const gchar * user_name, lb_key_sel_md_t mode, GList * keys,
GtkTreeSortable *sortable;
GtkTreeSelection *selection;
GtkTreeIter iter;
- gint i, last_col;
gchar *prompt;
- gchar *upcase_name;
gpgme_key_t use_key = NULL;
gint width, height;
+ GtkCellRenderer *renderer;
+ GtkTreeViewColumn *column;
/* FIXME: create dialog according to the Gnome HIG */
dialog = gtk_dialog_new_with_buttons(_("Select key"),
@@ -171,18 +204,18 @@ lb_gpgme_select_key(const gchar * user_name, lb_key_sel_md_t mode, GList * keys,
switch (mode) {
case LB_SELECT_PRIVATE_KEY:
prompt =
- g_strdup_printf(_("Select the private key for the signer %s"),
+ g_strdup_printf(_("Select the private key for the signer “%s”"),
user_name);
break;
case LB_SELECT_PUBLIC_KEY_USER:
prompt =
- g_strdup_printf(_("Select the public key for the recipient %s"),
+ g_strdup_printf(_("Select the public key for the recipient “%s”"),
user_name);
break;
case LB_SELECT_PUBLIC_KEY_ANY:
prompt =
g_strdup_printf(_("There seems to be no public key for recipient "
- "%s in your key ring.\nIf you are sure that the "
+ "“%s” in your key ring.\nIf you are sure that the "
"recipient owns a different key, select it from "
"the list."), user_name);
break;
@@ -194,6 +227,10 @@ lb_gpgme_select_key(const gchar * user_name, lb_key_sel_md_t mode, GList * keys,
g_free(prompt);
gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, TRUE, 0);
+ label = gtk_label_new(_("Double-click key to show details"));
+ gtk_widget_set_halign(label, GTK_ALIGN_START);
+ gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, TRUE, 0);
+
scrolled_window = gtk_scrolled_window_new(NULL, NULL);
gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW
(scrolled_window),
@@ -203,9 +240,6 @@ lb_gpgme_select_key(const gchar * user_name, lb_key_sel_md_t mode, GList * keys,
gtk_box_pack_start(GTK_BOX(vbox), scrolled_window, TRUE, TRUE, 0);
model = gtk_list_store_new(GPG_KEY_NUM_COLUMNS, G_TYPE_STRING, /* user ID */
- G_TYPE_STRING, /* key ID */
- G_TYPE_INT, /* length */
- G_TYPE_STRING, /* validity (gpg encrypt only) */
G_TYPE_POINTER); /* key */
sortable = GTK_TREE_SORTABLE(model);
gtk_tree_sortable_set_sort_func(sortable, 0, sort_iter_cmp_fn, NULL, NULL);
@@ -220,87 +254,44 @@ lb_gpgme_select_key(const gchar * user_name, lb_key_sel_md_t mode, GList * keys,
G_CALLBACK(key_selection_changed_cb), &use_key);
/* add the keys */
- upcase_name = g_ascii_strup(user_name, -1);
- while (keys) {
- gpgme_key_t key = (gpgme_key_t) keys->data;
- gpgme_subkey_t subkey = key->subkeys;
- gpgme_user_id_t uid = key->uids;
- gchar *uid_info = NULL;
- gboolean uid_found;
-
- /* find the relevant subkey */
- while (subkey &&
- (((mode == LB_SELECT_PRIVATE_KEY) && !subkey->can_sign) ||
- ((mode != LB_SELECT_PRIVATE_KEY) && !subkey->can_encrypt))) {
- subkey = subkey->next;
- }
-
- /* find the relevant uid */
- uid_found = FALSE;
- while (uid && !uid_found) {
- g_free(uid_info);
- uid_info = libbalsa_cert_subject_readable(uid->uid);
-
- /* check the email field which may or may not be present */
- if (uid->email &&
- ((mode == LB_SELECT_PUBLIC_KEY_ANY) ||
- !g_ascii_strcasecmp(uid->email, user_name)))
- uid_found = TRUE;
- else {
- /* no email or no match, check the uid */
- gchar *upcase_uid = g_ascii_strup(uid_info, -1);
-
- if (strstr(upcase_uid, upcase_name))
- uid_found = TRUE;
- else
- uid = uid->next;
- g_free(upcase_uid);
- }
- }
-
- /* append the element */
- if (subkey && uid && uid_info) {
- gtk_list_store_append(model, &iter);
- gtk_list_store_set(model, &iter,
- GPG_KEY_USER_ID_COLUMN, uid_info,
- GPG_KEY_ID_COLUMN, subkey->keyid,
- GPG_KEY_LENGTH_COLUMN, subkey->length,
- GPG_KEY_VALIDITY_COLUMN,
- libbalsa_gpgme_validity_to_gchar_short(uid->
- validity),
- GPG_KEY_PTR_COLUMN, key, -1);
- }
- g_free(uid_info);
- keys = g_list_next(keys);
+ while (keys != NULL) {
+ gpgme_key_t key = (gpgme_key_t) keys->data;
+
+ /* simply add the primary uid -- the user can show the full key details */
+ if ((key->uids != NULL) && (key->uids->uid != NULL)) {
+ gchar *uid_info;
+
+ uid_info = libbalsa_cert_subject_readable(key->uids->uid);
+ gtk_list_store_append(model, &iter);
+ gtk_list_store_set(model, &iter,
+ GPG_KEY_USER_ID_COLUMN, uid_info,
+ GPG_KEY_PTR_COLUMN, key, -1);
+ g_free(uid_info);
+ }
+ keys = g_list_next(keys);
}
- g_free(upcase_name);
g_object_unref(G_OBJECT(model));
- /* show the validity only if we are asking for a gpg public key */
- last_col = (protocol == GPGME_PROTOCOL_CMS || (mode == LB_SELECT_PRIVATE_KEY)) ?
- GPG_KEY_LENGTH_COLUMN : GPG_KEY_VALIDITY_COLUMN;
- for (i = 0; i <= last_col; i++) {
- GtkCellRenderer *renderer;
- GtkTreeViewColumn *column;
renderer = gtk_cell_renderer_text_new();
column =
- gtk_tree_view_column_new_with_attributes(_(col_titles[i]),
- renderer, "text", i,
+ gtk_tree_view_column_new_with_attributes(_("User ID"),
+ renderer, "text", 0,
NULL);
gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), column);
- gtk_tree_view_column_set_resizable(column, (i == 0) ? TRUE : FALSE);
- }
+ gtk_tree_view_column_set_resizable(column, TRUE);
gtk_container_add(GTK_CONTAINER(scrolled_window), tree_view);
+ g_signal_connect(tree_view, "button_press_event", G_CALLBACK(key_button_event_press_cb), dialog);
/* set window size to 2/3 of the parent */
gtk_window_get_size(parent, &width, &height);
gtk_window_set_default_size(GTK_WINDOW(dialog), (2 * width) / 3, (2 * height) / 3);
gtk_widget_show_all(gtk_dialog_get_content_area(GTK_DIALOG(dialog)));
- if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_OK)
- use_key = NULL;
+ if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_OK) {
+ use_key = NULL;
+ }
gtk_widget_destroy(dialog);
return use_key;
diff --git a/libbalsa/libbalsa-gpgme-keys.c b/libbalsa/libbalsa-gpgme-keys.c
index f9f5050..40ba61e 100644
--- a/libbalsa/libbalsa-gpgme-keys.c
+++ b/libbalsa/libbalsa-gpgme-keys.c
@@ -48,6 +48,8 @@ static gboolean gpgme_import_key(gpgme_ctx_t ctx,
gchar **import_info,
gpgme_key_t *imported_key,
GError **error);
+static gchar *gpgme_import_res_to_gchar(gpgme_import_result_t import_result)
+ G_GNUC_WARN_UNUSED_RESULT;
static gboolean show_keyserver_dialog(gpointer user_data);
static void keyserver_op_free(keyserver_op_t *keyserver_op);
@@ -168,6 +170,81 @@ libbalsa_gpgme_keyserver_op(const gchar *fingerprint,
return result;
}
+
+/* documentation: see header file */
+gchar *
+libbalsa_gpgme_export_key(gpgme_ctx_t ctx,
+ gpgme_key_t key,
+ const gchar *name,
+ GError **error)
+{
+ gpgme_error_t gpgme_err;
+ gpgme_data_t buffer;
+ gchar *result = NULL;
+
+ g_return_val_if_fail((ctx != NULL) && (key != NULL), FALSE);
+
+ gpgme_set_armor(ctx, 1);
+ gpgme_err = gpgme_data_new(&buffer);
+ if (gpgme_err != GPG_ERR_NO_ERROR) {
+ libbalsa_gpgme_set_error(error, gpgme_err, _("cannot create data buffer"));
+ } else {
+ gpgme_key_t keys[2];
+
+ keys[0] = key;
+ keys[1] = NULL;
+ gpgme_err = gpgme_op_export_keys(ctx, keys, 0, buffer);
+ if (gpgme_err != GPG_ERR_NO_ERROR) {
+ libbalsa_gpgme_set_error(error, gpgme_err, _("exporting key for “%s” failed"), name);
+ } else {
+ off_t key_size;
+
+ /* as we are working on a memory buffer, we can omit error checking... */
+ key_size = gpgme_data_seek(buffer, 0, SEEK_END);
+ result = g_malloc0(key_size + 1);
+ (void) gpgme_data_seek(buffer, 0, SEEK_SET);
+ (void) gpgme_data_read(buffer, result, key_size);
+ }
+ gpgme_data_release(buffer);
+ }
+
+ return result;
+}
+
+
+/* documentation: see header file */
+gboolean
+libbalsa_gpgme_import_ascii_key(gpgme_ctx_t ctx,
+ const gchar *key_buf,
+ gchar **import_info,
+ GError **error)
+{
+ gpgme_data_t buffer;
+ gpgme_error_t gpgme_err;
+ gboolean result = FALSE;
+
+ g_return_val_if_fail((ctx != NULL) && (key_buf != NULL), FALSE);
+
+ gpgme_err = gpgme_data_new_from_mem(&buffer, key_buf, strlen(key_buf), 1);
+ if (gpgme_err != GPG_ERR_NO_ERROR) {
+ libbalsa_gpgme_set_error(error, gpgme_err, _("cannot create data buffer"));
+ } else {
+ gpgme_err = gpgme_op_import(ctx, buffer);
+ if (gpgme_err != GPG_ERR_NO_ERROR) {
+ libbalsa_gpgme_set_error(error, gpgme_err, _("importing ASCII-armored key data
failed"));
+ } else {
+ result = TRUE;
+ if (import_info != NULL) {
+ *import_info = gpgme_import_res_to_gchar(gpgme_op_import_result(ctx));
+ }
+ }
+ gpgme_data_release(buffer);
+ }
+
+ return result;
+}
+
+
/* ---- local functions ------------------------------------------------------ */
/** \brief Check if a key is usable
@@ -235,7 +312,7 @@ gpgme_keyserver_run(gpointer user_data)
} else if (keys->next != NULL) {
dialog = gtk_message_dialog_new(keyserver_op->parent,
GTK_DIALOG_DESTROY_WITH_PARENT | libbalsa_dialog_flags(),
GTK_MESSAGE_WARNING, GTK_BUTTONS_CLOSE,
- _("Found %u keys with fingerprint %s on the key server. Please check and
import the proper key manually"),
+ _("Found %u keys with fingerprint %s on the key server. Please check and
import the proper key manually."),
g_list_length(keys), keyserver_op->fingerprint);
} else {
dialog = gpgme_keyserver_do_import(keyserver_op, (gpgme_key_t) keys->data);
@@ -282,7 +359,7 @@ gpgme_keyserver_do_import(keyserver_op_t *keyserver_op,
GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, "%s", import_msg);
} else {
dialog = libbalsa_key_dialog(keyserver_op->parent, GTK_BUTTONS_CLOSE, imported_key,
GPG_SUBKEY_CAP_ALL,
- _("Key imported"), import_msg);
+ NULL, import_msg);
gpgme_key_unref(imported_key);
}
g_free(import_msg);
@@ -329,61 +406,80 @@ gpgme_import_key(gpgme_ctx_t ctx,
gpgme_import_result_t import_result;
import_result = gpgme_op_import_result(ctx);
- if (import_result->considered == 0) {
- *import_info = g_strdup(_("No key was imported or updated."));
- } else {
- if (import_result->imported != 0) {
- *import_info = g_strdup(_("The key was imported into the local key ring."));
- } else if (import_result->unchanged == 0) {
- GString *info;
-
- info = g_string_new(_("The key was updated in the local key ring:"));
- if (import_result->new_user_ids > 0) {
- g_string_append_printf(info,
- ngettext("\n\342\200\242 %d new user ID", "\n\342\200\242 %d
new user IDs", import_result->new_user_ids),
- import_result->new_user_ids);
- }
- if (import_result->new_sub_keys > 0) {
- g_string_append_printf(info,
- ngettext("\n\342\200\242 %d new subkey", "\n\342\200\242 %d
new subkeys", import_result->new_sub_keys),
- import_result->new_sub_keys);
- }
- if (import_result->new_signatures > 0) {
- g_string_append_printf(info,
- ngettext("\n\342\200\242 %d new signature", "\n\342\200\242
%d new signatures",
- import_result->new_signatures),
- import_result->new_signatures);
- }
- if (import_result->new_revocations > 0) {
- g_string_append_printf(info,
- ngettext("\n\342\200\242 %d new revocation", "\n\342\200\242
%d new revocations",
- import_result->new_revocations),
- import_result->new_revocations);
- }
- *import_info = g_string_free(info, FALSE);
- } else {
- *import_info = g_strdup(_("No changes for the key were found on the key
server."));
+ *import_info = gpgme_import_res_to_gchar(import_result);
+
+ /* the key has been considered: load the possibly changed key from the local ring, ignoring
any errors */
+ if ((import_result->considered != 0) && (key->subkeys != NULL)) {
+ gpgme_keylist_mode_t kl_mode;
+
+ /* ensure local key list mode */
+ kl_mode = gpgme_get_keylist_mode(ctx);
+ kl_mode &= ~GPGME_KEYLIST_MODE_EXTERN;
+ kl_mode |= GPGME_KEYLIST_MODE_LOCAL;
+ gpgme_err = gpgme_set_keylist_mode(ctx, kl_mode);
+ if (gpgme_err == GPG_ERR_NO_ERROR) {
+ (void) gpgme_get_key(ctx, key->subkeys->fpr, imported_key, 0);
}
+ }
- /* load the possibly changed key from the local ring, ignoring any errors */
- if (key->subkeys != NULL) {
- gpgme_keylist_mode_t kl_mode;
+ result = TRUE;
+ }
- /* ensure local key list mode */
- kl_mode = gpgme_get_keylist_mode(ctx);
- kl_mode &= ~GPGME_KEYLIST_MODE_EXTERN;
- kl_mode |= GPGME_KEYLIST_MODE_LOCAL;
- gpgme_err = gpgme_set_keylist_mode(ctx, kl_mode);
- if (gpgme_err == GPG_ERR_NO_ERROR) {
- /* gpgme_err = gpgme_get_key(ctx, key->subkeys->fpr, imported_key,
0); */
- (void) gpgme_get_key(ctx, key->subkeys->fpr, imported_key, 0);
- }
+ return result;
+}
+
+
+/** \brief Create a human-readable import result message
+ *
+ * \param import_result GpgME import result data
+ * \return a newly allocated human-readable string containing the key import results
+ *
+ * This helper function collects the information about the last import operation using the passed context
into a human-readable
+ * string.
+ */
+static gchar *
+gpgme_import_res_to_gchar(gpgme_import_result_t import_result)
+{
+ gchar *import_info;
+
+ if (import_result->considered == 0) {
+ import_info = g_strdup(_("No key was imported or updated."));
+ } else {
+ if (import_result->imported != 0) {
+ import_info = g_strdup(_("The key was imported into the local key ring."));
+ } else if (import_result->unchanged == 0) {
+ GString *info;
+
+ info = g_string_new(_("The key was updated in the local key ring:"));
+ if (import_result->new_user_ids > 0) {
+ g_string_append_printf(info,
+ ngettext("\n\342\200\242 %d new user ID", "\n\342\200\242 %d new user
IDs", import_result->new_user_ids),
+ import_result->new_user_ids);
+ }
+ if (import_result->new_sub_keys > 0) {
+ g_string_append_printf(info,
+ ngettext("\n\342\200\242 %d new subkey", "\n\342\200\242 %d new
subkeys", import_result->new_sub_keys),
+ import_result->new_sub_keys);
+ }
+ if (import_result->new_signatures > 0) {
+ g_string_append_printf(info,
+ ngettext("\n\342\200\242 %d new signature", "\n\342\200\242 %d new
signatures",
+ import_result->new_signatures),
+ import_result->new_signatures);
}
+ if (import_result->new_revocations > 0) {
+ g_string_append_printf(info,
+ ngettext("\n\342\200\242 %d new revocation", "\n\342\200\242 %d new
revocations",
+ import_result->new_revocations),
+ import_result->new_revocations);
+ }
+ import_info = g_string_free(info, FALSE);
+ } else {
+ import_info = g_strdup(_("The existing key in the key ring was not changed."));
}
- result = TRUE;
}
- return result;
+ return import_info;
}
diff --git a/libbalsa/libbalsa-gpgme-keys.h b/libbalsa/libbalsa-gpgme-keys.h
index 110b500..aee10b8 100644
--- a/libbalsa/libbalsa-gpgme-keys.h
+++ b/libbalsa/libbalsa-gpgme-keys.h
@@ -80,6 +80,39 @@ gboolean libbalsa_gpgme_keyserver_op(const gchar *fingerprint,
GtkWindow *parent,
GError **error);
+/** \brief Export a public key
+ *
+ * \param ctx GpgME context
+ * \param key the key which shall be exported
+ * \param name key description, used only for creating an error string on error
+ * \param error filled with error information on error, may be NULL
+ * \return a newly allocated string containing the key on success, NULL on error
+ *
+ * Export the passed key as ASCII armoured string.
+ *
+ * \note The returned string shall be freed by the caller.
+ */
+gchar *libbalsa_gpgme_export_key(gpgme_ctx_t ctx,
+ gpgme_key_t key,
+ const gchar *name,
+ GError **error)
+ G_GNUC_WARN_UNUSED_RESULT;
+
+/** \brief Import an ASCII-armoured key
+ *
+ * \param ctx GpgME context
+ * \param key_buf ASCII-armoured GnuPG key buffer
+ * \param import_info filled with human-readable information about the import, may be NULL
+ * \param error filled with error information on error, may be NULL
+ * \return TRUE on success, or FALSE on error
+ *
+ * Import an ASCII-armoured GnuPG key into the key ring.
+ */
+gboolean libbalsa_gpgme_import_ascii_key(gpgme_ctx_t ctx,
+ const gchar *key_buf,
+ gchar **import_info,
+ GError **error);
+
G_END_DECLS
diff --git a/libbalsa/libbalsa-gpgme-widgets.c b/libbalsa/libbalsa-gpgme-widgets.c
index 38d779e..23ecdca 100644
--- a/libbalsa/libbalsa-gpgme-widgets.c
+++ b/libbalsa/libbalsa-gpgme-widgets.c
@@ -48,6 +48,8 @@ static gchar *create_purpose_str(gboolean can_sign,
gboolean can_certify,
gboolean can_auth)
G_GNUC_WARN_UNUSED_RESULT;
+static gchar *create_subkey_type_str(gpgme_subkey_t subkey)
+ G_GNUC_WARN_UNUSED_RESULT;
/* documentation: see header file */
@@ -431,27 +433,31 @@ create_subkey_widget(gpgme_subkey_t subkey)
{
GtkWidget *subkey_grid;
gint subkey_row = 0;
- gchar *status_str;
+ gchar *details_str;
gchar *timebuf;
subkey_grid = gtk_grid_new();
gtk_grid_set_column_spacing(GTK_GRID(subkey_grid), 6);
/* print a warning for a bad subkey status */
- status_str = create_status_str(subkey->expired != 0U, subkey->revoked != 0U, subkey->disabled != 0U,
subkey->invalid != 0U);
- if (strlen(status_str) > 0U) {
- subkey_row = create_key_grid_row(GTK_GRID(subkey_grid), subkey_row, _("Status:"), status_str,
TRUE);
+ details_str = create_status_str(subkey->expired != 0U, subkey->revoked != 0U, subkey->disabled != 0U,
subkey->invalid != 0U);
+ if (strlen(details_str) > 0U) {
+ subkey_row = create_key_grid_row(GTK_GRID(subkey_grid), subkey_row, _("Status:"),
details_str, TRUE);
}
- g_free(status_str);
+ g_free(details_str);
subkey_row = create_key_grid_row(GTK_GRID(subkey_grid), subkey_row, _("Fingerprint:"), subkey->fpr,
FALSE);
- status_str = create_purpose_str(subkey->can_sign != 0U, subkey->can_encrypt != 0, subkey->can_certify
!= 0U,
+ details_str = create_subkey_type_str(subkey);
+ subkey_row = create_key_grid_row(GTK_GRID(subkey_grid), subkey_row, _("Type:"), details_str, FALSE);
+ g_free(details_str);
+
+ details_str = create_purpose_str(subkey->can_sign != 0U, subkey->can_encrypt != 0,
subkey->can_certify != 0U,
subkey->can_authenticate != 0U);
- if (strlen(status_str) > 0U) {
- subkey_row = create_key_grid_row(GTK_GRID(subkey_grid), subkey_row, _("Capabilities:"),
status_str, FALSE);
+ if (strlen(details_str) > 0U) {
+ subkey_row = create_key_grid_row(GTK_GRID(subkey_grid), subkey_row, _("Capabilities:"),
details_str, FALSE);
}
- g_free(status_str);
+ g_free(details_str);
if (subkey->timestamp == -1) {
timebuf = g_strdup(_("invalid timestamp"));
@@ -468,7 +474,6 @@ create_subkey_widget(gpgme_subkey_t subkey)
} else {
timebuf = libbalsa_date_to_utf8(subkey->expires, "%x %X");
}
- /* subkey_row = create_key_grid_row(GTK_GRID(subkey_grid), subkey_row, _("Expires:"), timebuf,
FALSE); */
(void) create_key_grid_row(GTK_GRID(subkey_grid), subkey_row, _("Expires:"), timebuf, FALSE);
g_free(timebuf);
@@ -512,3 +517,33 @@ create_purpose_str(gboolean can_sign,
}
return g_string_free(purpose, FALSE);
}
+
+
+/** \brief Create a subkey type string
+ *
+ * \param subkey the subkey
+ * \return a newly allocated string containing a human-readable description of the subkey type
+ *
+ * Create a string containing the length of the subkey in bits, the public key algorithm supported by it and
for ECC algorithms the
+ * name of the curve. Note that the latter is available for Gpgme >= 1.5.0 only.
+ */
+static gchar *
+create_subkey_type_str(gpgme_subkey_t subkey)
+{
+ GString *type_str;
+ const gchar *algo;
+
+ type_str = g_string_new(NULL);
+ g_string_append_printf(type_str, _("%u bits"), subkey->length);
+ algo = gpgme_pubkey_algo_name(subkey->pubkey_algo);
+ if (algo != NULL) {
+ g_string_append_printf(type_str, " %s", algo);
+ }
+#if GPGME_VERSION_NUMBER >= 0x010500
+ if (subkey->curve != NULL) {
+ g_string_append_printf(type_str, _(" curve “%s”"), subkey->curve);
+ }
+#endif
+
+ return g_string_free(type_str, FALSE);
+}
diff --git a/libbalsa/libbalsa-gpgme.c b/libbalsa/libbalsa-gpgme.c
index 3f71895..4acf9da 100644
--- a/libbalsa/libbalsa-gpgme.c
+++ b/libbalsa/libbalsa-gpgme.c
@@ -39,6 +39,7 @@
#include "gmime-gpgme-signature.h"
#include "libbalsa-gpgme-keys.h"
#include "libbalsa-gpgme.h"
+#include "libbalsa.h"
static gboolean gpgme_add_signer(gpgme_ctx_t ctx, const gchar * signer,
@@ -48,6 +49,13 @@ static gpgme_key_t *gpgme_build_recipients(gpgme_ctx_t ctx,
gboolean accept_low_trust,
GtkWindow * parent,
GError ** error);
+static gpgme_error_t get_key_from_name(gpgme_ctx_t ctx,
+ gpgme_key_t *key,
+ const gchar *name,
+ gboolean secret,
+ gboolean accept_all,
+ GtkWindow *parent,
+ GError **error);
static void release_keylist(gpgme_key_t * keylist);
/* callbacks for gpgme file handling */
@@ -201,7 +209,14 @@ libbalsa_gpgme_new_with_proto(gpgme_protocol_t protocol,
gpgme_release(ctx);
ctx = NULL;
} else {
- gpgme_set_passphrase_cb(ctx, callback, parent);
+ if (protocol == GPGME_PROTOCOL_CMS) {
+ /* s/mime signing fails with error "not implemented" if a passphrase callback
has been set... */
+ gpgme_set_passphrase_cb(ctx, NULL, NULL);
+ /* ...but make sure the user certificate is always included when signing */
+ gpgme_set_include_certs(ctx, 1);
+ } else {
+ gpgme_set_passphrase_cb(ctx, callback, parent);
+ }
}
}
@@ -209,6 +224,50 @@ libbalsa_gpgme_new_with_proto(gpgme_protocol_t protocol,
}
+/** \brief Set the configuration folder for a GpgME context
+ *
+ * \param ctx GpgME context
+ * \param home_dir configuration directory for the crypto engine, or NULL for the default one
+ * \param error Filled with error information on error.
+ * \return TRUE on success, or FALSE on error
+ *
+ * Set the configuration and key ring folder for a GpgME context.
+ */
+gboolean
+libbalsa_gpgme_ctx_set_home(gpgme_ctx_t ctx,
+ const gchar *home_dir,
+ GError **error)
+{
+ gpgme_protocol_t protocol;
+ gpgme_engine_info_t engine_info;
+ gpgme_engine_info_t this_engine;
+ gboolean result = FALSE;
+
+ g_return_val_if_fail(ctx != NULL, FALSE);
+
+ protocol = gpgme_get_protocol(ctx);
+ engine_info = gpgme_ctx_get_engine_info(ctx);
+ for (this_engine = engine_info;
+ (this_engine != NULL) && (this_engine->protocol != protocol);
+ this_engine = this_engine->next) {
+ /* nothing to do */
+ }
+ if (this_engine != NULL) {
+ gpgme_error_t err;
+
+ err = gpgme_ctx_set_engine_info(ctx, protocol, this_engine->file_name, home_dir);
+ if (err == GPG_ERR_NO_ERROR) {
+ result = TRUE;
+ } else {
+ libbalsa_gpgme_set_error(error, err, _("could not set folder “%s” for engine “%s”"), home_dir,
+ gpgme_get_protocol_name(protocol));
+ }
+ }
+
+ return result;
+}
+
+
/** \brief Verify a signature
*
* \param content GMime stream of the signed matter.
@@ -620,6 +679,108 @@ libbalsa_gpgme_decrypt(GMimeStream * crypted, GMimeStream * plain,
}
+/** \brief Export a public key
+ *
+ * \param protocol GpgME crypto protocol to use
+ * \param name pattern (mail address or fingerprint) of the requested key
+ * \param parent parent window to be passed to the callback functions
+ * \param error Filled with error information on error
+ * \return a newly allocated string containing the ASCII-armored public key on success
+ *
+ * Return the ASCII-armored key matching the passed pattern. If necessary, the user is asked to select a
key from a list of
+ * multiple matching keys.
+ */
+gchar *
+libbalsa_gpgme_get_pubkey(gpgme_protocol_t protocol,
+ const gchar *name,
+ GtkWindow *parent,
+ GError **error)
+{
+ gpgme_ctx_t ctx;
+ gchar *armored_key = NULL;
+
+ g_return_val_if_fail(name != NULL, NULL);
+
+ ctx = libbalsa_gpgme_new_with_proto(protocol, NULL, NULL, error);
+ if (ctx != NULL) {
+ gpgme_error_t gpgme_err;
+ gpgme_key_t key = NULL;
+
+ gpgme_err = get_key_from_name(ctx, &key, name, FALSE, FALSE, parent, error);
+ if (gpgme_err == GPG_ERR_NO_ERROR) {
+ armored_key = libbalsa_gpgme_export_key(ctx, key, name, error);
+ gpgme_key_unref(key);
+ }
+ gpgme_release(ctx);
+ }
+
+ return armored_key;
+}
+
+
+/** \brief Get the key id of a secret key
+ *
+ * \param protocol GpgME protocol (OpenPGP or CMS)
+ * \param name email address for which the key shall be selected
+ * \param parent parent window to be passed to the callback functions
+ * \param error Filled with error information on error
+ * \return a newly allocated string containing the key id key on success, shall be freed by the caller
+ *
+ * Call libbalsa_gpgme_list_keys() to list all secret keys for the passed protocol, and \em always call \ref
select_key_cb to let
+ * the user choose the secret key, even if only one is available.
+ */
+gchar *
+libbalsa_gpgme_get_seckey(gpgme_protocol_t protocol,
+ const gchar *name,
+ GtkWindow *parent,
+ GError **error)
+{
+ gpgme_ctx_t ctx;
+ gchar *keyid = NULL;
+
+ ctx = libbalsa_gpgme_new_with_proto(protocol, NULL, NULL, error);
+ if (ctx != NULL) {
+ GList *keys = NULL;
+
+ /* let gpgme list all available keys */
+ if (libbalsa_gpgme_list_keys(ctx, &keys, NULL, name, TRUE, FALSE, error)) {
+ if (keys != NULL) {
+ gpgme_key_t key;
+
+ /* let the user select a key from the list, even if there is only one */
+ if (select_key_cb != NULL) {
+ key = select_key_cb(name, LB_SELECT_PRIVATE_KEY, keys,
gpgme_get_protocol(ctx), parent);
+ if (key != NULL) {
+ gpgme_subkey_t subkey;
+
+ for (subkey = key->subkeys; (subkey != NULL) && (keyid ==
NULL); subkey = subkey->next) {
+ if ((subkey->can_sign != 0) && (subkey->expired ==
0U) && (subkey->revoked == 0U) &&
+ (subkey->disabled == 0U) && (subkey->invalid
== 0U)) {
+ keyid = g_strdup(subkey->keyid);
+ }
+ }
+ }
+ }
+ g_list_free_full(keys, (GDestroyNotify) gpgme_key_unref);
+ } else {
+ GtkWidget *dialog;
+
+ dialog = gtk_message_dialog_new(parent,
+ GTK_DIALOG_DESTROY_WITH_PARENT | libbalsa_dialog_flags(),
+ GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE,
+ _("No private key for protocol %s is available for the signer “%s”"),
+ gpgme_get_protocol_name(protocol), name);
+ (void) gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+ }
+ }
+ gpgme_release(ctx);
+ }
+
+ return keyid;
+}
+
+
/*
* set a GError form GpgME information
*/
@@ -789,8 +950,8 @@ get_key_from_name(gpgme_ctx_t ctx,
}
g_list_free_full(keys, (GDestroyNotify) gpgme_key_unref);
- /* OpenPGP: ask the user if a low-validity key should be trusted for encryption */
- // FIXME - shouldn't we do the same for S/MIME?
+ /* OpenPGP: ask the user if a low-validity key should be trusted for encryption (Note: owner_trust is
not applicable to
+ * S/MIME certificates) */
if ((selected != NULL) &&
(result == GPG_ERR_NO_ERROR) && !secret && !accept_all && (gpgme_get_protocol(ctx) ==
GPGME_PROTOCOL_OpenPGP) &&
(selected->owner_trust < GPGME_VALIDITY_FULL)) {
diff --git a/libbalsa/libbalsa-gpgme.h b/libbalsa/libbalsa-gpgme.h
index e3f0ff3..8a52164 100644
--- a/libbalsa/libbalsa-gpgme.h
+++ b/libbalsa/libbalsa-gpgme.h
@@ -80,6 +80,9 @@ gpgme_ctx_t libbalsa_gpgme_new_with_proto(gpgme_protocol_t protocol,
GtkWindow
*parent,
GError
**error)
G_GNUC_WARN_UNUSED_RESULT;
+gboolean libbalsa_gpgme_ctx_set_home(gpgme_ctx_t ctx,
+ const gchar *home_dir,
+ GError **error);
GMimeGpgmeSigstat *libbalsa_gpgme_verify(GMimeStream * content,
GMimeStream * sig_plain,
@@ -111,6 +114,18 @@ GMimeGpgmeSigstat *libbalsa_gpgme_decrypt(GMimeStream * crypted,
GError ** error)
G_GNUC_WARN_UNUSED_RESULT;
+gchar *libbalsa_gpgme_get_pubkey(gpgme_protocol_t protocol,
+ const gchar *name,
+ GtkWindow *parent,
+ GError **error)
+ G_GNUC_WARN_UNUSED_RESULT;
+
+gchar *libbalsa_gpgme_get_seckey(gpgme_protocol_t protocol,
+ const gchar *name,
+ GtkWindow *parent,
+ GError **error)
+ G_GNUC_WARN_UNUSED_RESULT;
+
void libbalsa_gpgme_set_error(GError **error,
gpgme_error_t gpgme_err,
const gchar *format,
diff --git a/libbalsa/message.c b/libbalsa/message.c
index 3e228de..d9ad40d 100644
--- a/libbalsa/message.c
+++ b/libbalsa/message.c
@@ -101,7 +101,7 @@ libbalsa_message_init(LibBalsaMessage * message)
message->has_all_headers = 0;
#ifdef HAVE_GPGME
message->prot_state = LIBBALSA_MSG_PROTECT_NONE;
- message->force_key_id = NULL;
+ message->ident = NULL;
#endif
}
@@ -180,7 +180,9 @@ libbalsa_message_finalize(GObject * object)
}
#ifdef HAVE_GPGME
- g_free(message->force_key_id);
+ if (message->ident != NULL) {
+ g_object_unref(message->ident);
+ }
#endif
if (message->tempdir) {
diff --git a/libbalsa/message.h b/libbalsa/message.h
index 751f08d..49c7dd9 100644
--- a/libbalsa/message.h
+++ b/libbalsa/message.h
@@ -203,11 +203,14 @@ struct _LibBalsaMessage {
/* GPG sign and/or encrypt message (sending) */
guint gpg_mode;
+ /* attach the GnuPG public key to the message (sending) */
+ gboolean att_pubkey;
+
/* protection (i.e. sign/encrypt) status (received message) */
LibBalsaMsgProtectState prot_state;
- /* forced id of the senders secret key, empty to choose it from the mail address */
- gchar * force_key_id;
+ /* sender identity, required for choosing a forced GnuPG or S/MIME key */
+ LibBalsaIdentity *ident;
#endif
/* request a DSN (sending) */
diff --git a/libbalsa/misc.c b/libbalsa/misc.c
index c05da73..40b2ff8 100644
--- a/libbalsa/misc.c
+++ b/libbalsa/misc.c
@@ -29,7 +29,6 @@
#define _SVID_SOURCE 1
#include <ctype.h>
-#include <dirent.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
@@ -43,6 +42,7 @@
#include "libbalsa_private.h"
#include "html.h"
#include <glib/gi18n.h>
+#include <glib/gstdio.h>
static const gchar *libbalsa_get_codeset_name(const gchar *txt,
LibBalsaCodeset Codeset);
@@ -191,41 +191,39 @@ libbalsa_wrap_string(gchar * str, int width)
gboolean
libbalsa_delete_directory_contents(const gchar *path)
{
- struct stat sb;
- DIR *d;
- struct dirent *de;
- gchar *new_path;
-
- d = opendir(path);
- g_return_val_if_fail(d, FALSE);
-
- for (de = readdir(d); de; de = readdir(d)) {
- if (strcmp(de->d_name, ".") == 0 ||
- strcmp(de->d_name, "..") == 0)
- continue;
- new_path = g_strdup_printf("%s/%s", path, de->d_name);
-
- stat(new_path, &sb);
- if (S_ISDIR(sb.st_mode)) {
- if (!libbalsa_delete_directory_contents(new_path) ||
- rmdir(new_path) == -1) {
- g_free(new_path);
- closedir(d);
- return FALSE;
- }
- } else {
- if (unlink( new_path ) == -1) {
- g_free(new_path);
- closedir(d);
- return FALSE;
- }
- }
- g_free(new_path);
- new_path = 0;
+ GDir *dir;
+ gboolean result;
+
+ g_return_val_if_fail(path != NULL, FALSE);
+ dir = g_dir_open(path, 0, NULL);
+ if (dir == NULL) {
+ result = FALSE;
+ } else {
+ const gchar *item;
+
+ result = TRUE;
+ item = g_dir_read_name(dir);
+ while (result && (item != NULL)) {
+ gchar *full_path;
+
+ full_path = g_build_filename(path, item, NULL);
+ if (g_file_test(full_path, G_FILE_TEST_IS_DIR)) {
+ result = libbalsa_delete_directory_contents(full_path);
+ if (g_rmdir(full_path) != 0) {
+ result = FALSE;
+ }
+ } else {
+ if (g_unlink(full_path) != 0) {
+ result = FALSE;
+ }
+ }
+ g_free(full_path);
+ item = g_dir_read_name(dir);
+ }
+ g_dir_close(dir);
}
- closedir(d);
- return TRUE;
+ return result;
}
/* libbalsa_expand_path:
@@ -252,29 +250,9 @@ libbalsa_expand_path(const gchar * path)
gboolean
libbalsa_mktempdir (char **s)
{
- gchar *name;
- int fd;
-
g_return_val_if_fail(s != NULL, FALSE);
-
- do {
- GError *error = NULL;
- fd = g_file_open_tmp("balsa-tmpdir-XXXXXX", &name, &error);
- close(fd);
- unlink(name);
- /* Here is a short time that the name could be reused */
- fd = mkdir(name, 0700);
- if (fd == -1) {
- g_free(name);
- if (!g_error_matches(error, G_FILE_ERROR, G_FILE_ERROR_EXIST))
- return FALSE;
- }
- if (error)
- g_error_free(error);
- } while (fd == -1);
- *s = name;
- /* FIXME: rmdir(name) at sometime */
- return TRUE;
+ *s = g_build_filename(g_get_tmp_dir(), "balsa-tmpdir-XXXXXX", NULL);
+ return g_mkdtemp_full(*s, 0700) != NULL;
}
/* libbalsa_set_fallback_codeset: sets the codeset for incorrectly
diff --git a/libbalsa/rfc3156.c b/libbalsa/rfc3156.c
index 87084ea..f3b5fe8 100644
--- a/libbalsa/rfc3156.c
+++ b/libbalsa/rfc3156.c
@@ -262,7 +262,7 @@ libbalsa_encrypt_mime_object(GMimeObject ** content, GList * rfc822_for,
result = g_mime_application_pkcs7_encrypt(pkcs7, *content, recipients, always_trust, parent, error);
}
- g_ptr_array_free(recipients, FALSE);
+ g_ptr_array_unref(recipients);
/* error checking */
if (!result) {
@@ -497,7 +497,7 @@ libbalsa_rfc2440_sign_encrypt(GMimePart *part, const gchar *sign_for,
always_trust, parent, error);
/* clean up */
if (recipients)
- g_ptr_array_free(recipients, FALSE);
+ g_ptr_array_unref(recipients);
return result;
}
diff --git a/libbalsa/send.c b/libbalsa/send.c
index 3d48c8b..ca45177 100644
--- a/libbalsa/send.c
+++ b/libbalsa/send.c
@@ -41,9 +41,14 @@
#include "net-client-smtp.h"
#include "gmime-filter-header.h"
#include "smtp-server.h"
+#include "identity.h"
#include "libbalsa-progress.h"
+#ifdef HAVE_GPGME
+#include "libbalsa-gpgme.h"
+#endif
+
#include <glib/gi18n.h>
typedef struct _MessageQueueItem MessageQueueItem;
@@ -238,6 +243,9 @@ static LibBalsaMsgCreateResult do_multipart_crypto(LibBalsaMessage *message,
GtkWindow *parent,
GError **error);
+static GMimePart *lb_create_pubkey_part(LibBalsaMessage *message,
+ GtkWindow *parent,
+ GError **error);
#endif
static gpointer balsa_send_message_real(SendMessageInfo *info);
@@ -1205,12 +1213,20 @@ libbalsa_message_create_mime_message(LibBalsaMessage *message,
InternetAddressList *ia_list;
gchar *tmp;
GList *list;
+ gboolean attach_pubkey = FALSE;
#ifdef HAVE_GPGME
GtkWindow *parent = g_object_get_data(G_OBJECT(message), "parent-window");
#endif
+#ifdef HAVE_GPGME
+ /* attach the public key only if we send the message, not if we just postpone it */
+ if (!postponing && message->att_pubkey && ((message->gpg_mode & LIBBALSA_PROTECT_PROTOCOL) != 0)) {
+ attach_pubkey = TRUE;
+ }
+#endif
+
body = message->body_list;
- if ((body != NULL) && (body->next != NULL)) {
+ if ((body != NULL) && ((body->next != NULL) || attach_pubkey)) {
mime_root = GMIME_OBJECT(g_mime_multipart_new_with_subtype(message->subtype));
}
@@ -1384,6 +1400,26 @@ libbalsa_message_create_mime_message(LibBalsaMessage *message,
}
#ifdef HAVE_GPGME
+ if (attach_pubkey) {
+ GMimePart *pubkey_part;
+
+ pubkey_part = lb_create_pubkey_part(message, parent, error);
+ if (pubkey_part == NULL) {
+ if (mime_root != NULL) {
+ g_object_unref(G_OBJECT(mime_root));
+ }
+ return LIBBALSA_MESSAGE_CREATE_ERROR;
+ }
+ if (mime_root != NULL) {
+ g_mime_multipart_add(GMIME_MULTIPART(mime_root), GMIME_OBJECT(pubkey_part));
+ g_object_unref(G_OBJECT(pubkey_part));
+ } else {
+ mime_root = GMIME_OBJECT(pubkey_part);
+ }
+ }
+#endif
+
+#ifdef HAVE_GPGME
if ((message->body_list != NULL) && !postponing) {
LibBalsaMsgCreateResult crypt_res =
do_multipart_crypto(message, &mime_root, parent, error);
@@ -1672,22 +1708,71 @@ libbalsa_fill_msg_queue_item_from_queu(LibBalsaMessage *message,
#ifdef HAVE_GPGME
+/*
+ * If the identity contains a forced key ID for the passed protocol, return the key ID. Otherwise, return
the email address of the
+ * "From:" address list to let GpeME automagically select the proper key.
+ */
static const gchar *
-lb_send_from(LibBalsaMessage *message)
+lb_send_from(LibBalsaMessage *message,
+ gpgme_protocol_t protocol)
{
- InternetAddress *ia =
- internet_address_list_get_address(message->headers->from, 0);
+ const gchar *from_id;
+
+ if ((protocol == GPGME_PROTOCOL_OpenPGP) &&
+ (message->ident->force_gpg_key_id != NULL) &&
+ (message->ident->force_gpg_key_id[0] != '\0')) {
+ from_id = message->ident->force_gpg_key_id;
+ } else if ((protocol == GPGME_PROTOCOL_CMS) &&
+ (message->ident->force_smime_key_id != NULL) &&
+ (message->ident->force_smime_key_id[0] != '\0')) {
+ from_id = message->ident->force_smime_key_id;
+ } else {
+ InternetAddress *ia = internet_address_list_get_address(message->headers->from, 0);
- if (message->force_key_id != NULL) {
- return message->force_key_id;
- }
+ while (INTERNET_ADDRESS_IS_GROUP(ia)) {
+ ia = internet_address_list_get_address(((InternetAddressGroup *) ia)->members, 0);
+ }
+ from_id = ((InternetAddressMailbox *) ia)->addr;
+ }
- while (INTERNET_ADDRESS_IS_GROUP(ia)) {
- ia = internet_address_list_get_address(((InternetAddressGroup *)
- ia)->members, 0);
- }
+ return from_id;
+}
- return ((InternetAddressMailbox *) ia)->addr;
+
+static GMimePart *
+lb_create_pubkey_part(LibBalsaMessage *message,
+ GtkWindow *parent,
+ GError **error)
+{
+ const gchar *key_id;
+ gchar *keybuf;
+ GMimePart *mime_part = NULL;
+
+ key_id = lb_send_from(message, GPGME_PROTOCOL_OpenPGP);
+ keybuf = libbalsa_gpgme_get_pubkey(GPGME_PROTOCOL_OpenPGP, key_id, parent, error);
+ if (keybuf != NULL) {
+ GMimeStream *stream;
+ GMimeDataWrapper *wrapper;
+ gchar *filename;
+
+ mime_part = g_mime_part_new_with_type("application", "pgp-keys");
+ filename = g_strconcat(key_id, ".asc", NULL);
+ g_mime_object_set_content_type_parameter(GMIME_OBJECT(mime_part), "name", filename);
+ g_mime_object_set_disposition(GMIME_OBJECT(mime_part), GMIME_DISPOSITION_ATTACHMENT);
+ g_mime_object_set_content_disposition_parameter(GMIME_OBJECT(mime_part), "filename", filename);
+ g_free(filename);
+ g_mime_part_set_content_encoding(mime_part, GMIME_CONTENT_ENCODING_7BIT);
+ stream = g_mime_stream_mem_new();
+ g_mime_stream_write(stream, keybuf, strlen(keybuf));
+ g_free(keybuf);
+ wrapper = g_mime_data_wrapper_new();
+ g_mime_data_wrapper_set_stream(wrapper, stream);
+ g_object_unref(stream);
+ g_mime_part_set_content_object(mime_part, wrapper);
+ g_object_unref(wrapper);
+ }
+
+ return mime_part;
}
@@ -1703,7 +1788,7 @@ libbalsa_create_rfc2440_buffer(LibBalsaMessage *message,
switch (mode & LIBBALSA_PROTECT_MODE) {
case LIBBALSA_PROTECT_SIGN: /* sign only */
if (!libbalsa_rfc2440_sign_encrypt(mime_part,
- lb_send_from(message),
+ lb_send_from(message, GPGME_PROTOCOL_OpenPGP),
NULL, FALSE,
parent, error)) {
return LIBBALSA_MESSAGE_SIGN_ERROR;
@@ -1739,7 +1824,7 @@ libbalsa_create_rfc2440_buffer(LibBalsaMessage *message,
if (mode & LIBBALSA_PROTECT_SIGN) {
result =
libbalsa_rfc2440_sign_encrypt(mime_part,
- lb_send_from(message),
+ lb_send_from(message, GPGME_PROTOCOL_OpenPGP),
encrypt_for,
always_trust,
parent, error);
@@ -1797,7 +1882,7 @@ do_multipart_crypto(LibBalsaMessage *message,
switch (message->gpg_mode & LIBBALSA_PROTECT_MODE) {
case LIBBALSA_PROTECT_SIGN: /* sign message */
if (!libbalsa_sign_mime_object(mime_root,
- lb_send_from(message),
+ lb_send_from(message, protocol),
protocol, parent, error)) {
return LIBBALSA_MESSAGE_SIGN_ERROR;
}
@@ -1817,7 +1902,7 @@ do_multipart_crypto(LibBalsaMessage *message,
encrypt_for = get_mailbox_names(encrypt_for,
message->headers->cc_list);
encrypt_for = g_list_append(encrypt_for,
- g_strdup(lb_send_from(message)));
+ g_strdup(lb_send_from(message, protocol)));
if (message->headers->bcc_list
&& (internet_address_list_length(message->headers->
bcc_list) > 0)) {
@@ -1829,7 +1914,7 @@ do_multipart_crypto(LibBalsaMessage *message,
if (message->gpg_mode & LIBBALSA_PROTECT_SIGN) {
success =
libbalsa_sign_encrypt_mime_object(mime_root,
- lb_send_from(message),
+ lb_send_from(message, protocol),
encrypt_for, protocol,
always_trust, parent,
error);
@@ -1839,7 +1924,7 @@ do_multipart_crypto(LibBalsaMessage *message,
protocol, always_trust,
parent, error);
}
- g_list_free(encrypt_for);
+ g_list_free_full(encrypt_for, (GDestroyNotify) g_free);
if (!success) {
return LIBBALSA_MESSAGE_ENCRYPT_ERROR;
diff --git a/libbalsa/smtp-server.c b/libbalsa/smtp-server.c
index d1a24e2..3157ca2 100644
--- a/libbalsa/smtp-server.c
+++ b/libbalsa/smtp-server.c
@@ -47,7 +47,6 @@ struct _LibBalsaSmtpServer {
gchar *name;
guint big_message; /* size of partial messages; in kB; 0 disables splitting */
gint lock_state; /* 0 means unlocked; access via atomic operations */
- // FIXME - add an atomic flag if an operation is running on this server
};
typedef struct _LibBalsaSmtpServerClass {
diff --git a/libnetclient/net-client.c b/libnetclient/net-client.c
index 8fabc37..4dab4ec 100644
--- a/libnetclient/net-client.c
+++ b/libnetclient/net-client.c
@@ -163,7 +163,8 @@ net_client_read_line(NetClient *client, gchar **recv_line, GError **error)
/* check that the protocol-specific maximum line length is not exceeded */
if ((client->priv->max_line_len > 0U) && (length > client->priv->max_line_len)) {
g_set_error(error, NET_CLIENT_ERROR_QUARK, (gint)
NET_CLIENT_ERROR_LINE_TOO_LONG,
- _("reply length %lu exceeds the maximum allowed length %lu"),
(unsigned long) length, (unsigned long) client->priv->max_line_len);
+ _("reply length %lu exceeds the maximum allowed length %lu"),
+ (unsigned long) length, (unsigned long) client->priv->max_line_len);
g_free(line_buf);
} else {
g_debug("R '%s'", line_buf);
diff --git a/src/balsa-mime-widget-crypto.c b/src/balsa-mime-widget-crypto.c
index 360c293..0ce7189 100644
--- a/src/balsa-mime-widget-crypto.c
+++ b/src/balsa-mime-widget-crypto.c
@@ -26,12 +26,16 @@
#include "balsa-app.h"
#include "balsa-icons.h"
#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include "libbalsa-gpgme.h"
#include "libbalsa-gpgme-widgets.h"
#include "libbalsa-gpgme-keys.h"
#include "balsa-mime-widget.h"
static void on_gpg_key_button(GtkWidget *button, const gchar *fingerprint);
+static void on_key_import_button(GtkButton *button, gpointer user_data);
+static gboolean create_import_keys_widget(GtkBox *box, const gchar *key_buf, GError **error);
BalsaMimeWidget *
@@ -53,6 +57,41 @@ balsa_mime_widget_new_signature(BalsaMessage * bm,
return mw;
}
+BalsaMimeWidget *
+balsa_mime_widget_new_pgpkey(BalsaMessage *bm,
+ LibBalsaMessageBody *mime_body,
+ const gchar *content_type,
+ gpointer data)
+{
+ gssize body_size;
+ gchar *body_buf = NULL;
+ GError *err = NULL;
+ BalsaMimeWidget *mw = NULL;
+
+ g_return_val_if_fail(mime_body != NULL, NULL);
+ g_return_val_if_fail(content_type != NULL, NULL);
+
+ body_size = libbalsa_message_body_get_content(mime_body, &body_buf, &err);
+ if (body_size < 0) {
+ balsa_information(LIBBALSA_INFORMATION_ERROR, _("Could not save a text part: %s"),
+ err ? err->message : "Unknown error");
+ g_clear_error(&err);
+ } else {
+ mw = g_object_new(BALSA_TYPE_MIME_WIDGET, NULL);
+ mw->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, BMW_VBOX_SPACE);
+ if (!create_import_keys_widget(GTK_BOX(mw->widget), body_buf, &err)) {
+ balsa_information(LIBBALSA_INFORMATION_ERROR, _("Could not process GnuPG keys: %s"),
+ err ? err->message : "Unknown error");
+ g_clear_error(&err);
+ g_object_unref(G_OBJECT(mw));
+ mw = NULL;
+ }
+ g_free(body_buf);
+ }
+
+ return mw;
+}
+
GtkWidget *
balsa_mime_widget_signature_widget(LibBalsaMessageBody * mime_body,
const gchar * content_type)
@@ -193,7 +232,111 @@ on_gpg_key_button(GtkWidget *button,
if (!libbalsa_gpgme_keyserver_op(fingerprint, balsa_get_parent_window(button), &error)) {
libbalsa_information(LIBBALSA_INFORMATION_ERROR, "%s", error->message);
g_error_free(error);
+ } else {
+ gtk_widget_set_sensitive(GTK_WIDGET(button), FALSE);
}
}
+
+/* Callback: import an attached key */
+static void
+on_key_import_button(GtkButton *button,
+ gpointer user_data)
+{
+ gpgme_ctx_t ctx;
+ gboolean success;
+ GError *error = NULL;
+ gchar *import_info = NULL;
+ GtkWidget *dialog;
+
+ ctx = libbalsa_gpgme_new_with_proto(GPGME_PROTOCOL_OpenPGP, NULL, NULL, &error);
+ if (ctx != NULL) {
+ success = libbalsa_gpgme_import_ascii_key(ctx, g_object_get_data(G_OBJECT(button),
"keydata"), &import_info, &error);
+ gpgme_release(ctx);
+ } else {
+ success = FALSE;
+ }
+
+ if (success) {
+ dialog = gtk_message_dialog_new(GTK_WINDOW(balsa_app.main_window),
+ GTK_DIALOG_DESTROY_WITH_PARENT | libbalsa_dialog_flags(),
+ GTK_MESSAGE_INFO,
+ GTK_BUTTONS_CLOSE,
+ _("Import GnuPG key:\n%s"), import_info);
+ gtk_widget_set_sensitive(GTK_WIDGET(button), FALSE);
+ } else {
+ dialog = gtk_message_dialog_new(GTK_WINDOW(balsa_app.main_window),
+ GTK_DIALOG_DESTROY_WITH_PARENT | libbalsa_dialog_flags(),
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ _("Error importing key data: %s"), error->message);
+ g_clear_error(&error);
+ }
+ g_free(import_info);
+ (void) gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+}
+
+
+static gboolean
+create_import_keys_widget(GtkBox *box, const gchar *key_buf, GError **error)
+{
+ gboolean success = FALSE;
+ gpgme_ctx_t ctx;
+
+ ctx = libbalsa_gpgme_new_with_proto(GPGME_PROTOCOL_OpenPGP, NULL, NULL, error);
+ if (ctx != NULL) {
+ gchar *temp_dir = NULL;
+
+ if (!libbalsa_mktempdir(&temp_dir)) {
+ g_warning("Failed to create a temporary folder");
+ } else {
+ GList *keys = NULL;
+
+ success = libbalsa_gpgme_ctx_set_home(ctx, temp_dir, error) &&
+ libbalsa_gpgme_import_ascii_key(ctx, key_buf, NULL, error) &&
+ libbalsa_gpgme_list_keys(ctx, &keys, NULL, NULL, FALSE, FALSE, error);
+
+ if (success && (keys != NULL)) {
+ GList *item;
+
+ for (item = keys; success && (item != NULL); item = item->next) {
+ gpgme_key_t this_key = (gpgme_key_t) item->data;
+ gchar *key_ascii;
+ GtkWidget *key_widget;
+ GtkWidget *import_btn;
+
+ key_ascii = libbalsa_gpgme_export_key(ctx, this_key, _("(imported)"),
error);
+
+ if (key_ascii == NULL) {
+ success = FALSE;
+ } else {
+ key_widget = libbalsa_gpgme_key(this_key, NULL,
GPG_SUBKEY_CAP_ALL, FALSE);
+ gtk_box_pack_start(box, key_widget, FALSE, FALSE, 0);
+
+ import_btn = gtk_button_new_with_label(_("Import key into the
local key ring"));
+ g_object_set_data_full(G_OBJECT(import_btn), "keydata",
key_ascii, (GDestroyNotify) g_free);
+ g_signal_connect(G_OBJECT(import_btn), "clicked", (GCallback)
on_key_import_button, NULL);
+ gtk_box_pack_start(box, import_btn, FALSE, FALSE, 0);
+
+ if (item->next != NULL) {
+ gtk_box_pack_start(box,
gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), FALSE, FALSE,
+ BMW_VBOX_SPACE);
+ }
+ }
+ }
+
+ g_list_free_full(keys, (GDestroyNotify) gpgme_key_release);
+ }
+
+ libbalsa_delete_directory_contents(temp_dir);
+ g_rmdir(temp_dir);
+ }
+
+ gpgme_release(ctx);
+ }
+
+ return success;
+}
+
#endif /* HAVE_GPGME */
diff --git a/src/balsa-mime-widget-crypto.h b/src/balsa-mime-widget-crypto.h
index 4ec6a09..3e72fe9 100644
--- a/src/balsa-mime-widget-crypto.h
+++ b/src/balsa-mime-widget-crypto.h
@@ -36,6 +36,10 @@ G_BEGIN_DECLS
BalsaMimeWidget *balsa_mime_widget_new_signature(BalsaMessage * bm,
LibBalsaMessageBody * mime_body,
const gchar * content_type, gpointer data);
+BalsaMimeWidget *balsa_mime_widget_new_pgpkey(BalsaMessage *bm,
+ LibBalsaMessageBody
*mime_body,
+ const gchar
*content_type,
+ gpointer
data);
GtkWidget * balsa_mime_widget_signature_widget(LibBalsaMessageBody * mime_body,
const gchar * content_type);
GtkWidget * balsa_mime_widget_crypto_frame(LibBalsaMessageBody * mime_body, GtkWidget * child,
diff --git a/src/balsa-mime-widget.c b/src/balsa-mime-widget.c
index fdd11a9..9f632dc 100644
--- a/src/balsa-mime-widget.c
+++ b/src/balsa-mime-widget.c
@@ -123,6 +123,7 @@ static mime_delegate_t mime_delegate[] =
{FALSE, "application/pgp-signature", balsa_mime_widget_new_signature},
{FALSE, "application/pkcs7-signature", balsa_mime_widget_new_signature},
{FALSE, "application/x-pkcs7-signature", balsa_mime_widget_new_signature},
+ {FALSE, "application/pgp-keys", balsa_mime_widget_new_pgpkey},
#endif
{FALSE, NULL, NULL}
};
diff --git a/src/information-dialog.c b/src/information-dialog.c
index adaba10..f6194b1 100644
--- a/src/information-dialog.c
+++ b/src/information-dialog.c
@@ -180,8 +180,8 @@ balsa_information_dialog(GtkWindow *parent, LibBalsaInformationType type,
* the message string. */
messagebox =
gtk_message_dialog_new(GTK_WINDOW(parent),
- GTK_DIALOG_DESTROY_WITH_PARENT,
- message_type, GTK_BUTTONS_CLOSE,
+ GTK_DIALOG_DESTROY_WITH_PARENT | libbalsa_dialog_flags(),
+ message_type, GTK_BUTTONS_CLOSE,
"%s", msg);
#if HAVE_MACOSX_DESKTOP
libbalsa_macosx_menu_for_parent(messagebox, GTK_WINDOW(parent));
diff --git a/src/sendmsg-window.c b/src/sendmsg-window.c
index 9384bc8..8407592 100644
--- a/src/sendmsg-window.c
+++ b/src/sendmsg-window.c
@@ -4938,13 +4938,15 @@ bsmsg2message(BalsaSendmsg * bsmsg)
message->headers->date = time(NULL);
#ifdef HAVE_GPGME
- if (balsa_app.has_openpgp || balsa_app.has_smime)
+ if (balsa_app.has_openpgp || balsa_app.has_smime) {
message->gpg_mode =
(bsmsg->gpg_mode & LIBBALSA_PROTECT_MODE) != 0 ? bsmsg->gpg_mode : 0;
- else
+ message->att_pubkey = bsmsg->attach_pubkey;
+ message->ident = g_object_ref(ident);
+ } else {
message->gpg_mode = 0;
- if (ident->force_key_id && *ident->force_key_id)
- message->force_key_id = g_strdup(ident->force_key_id);
+ message->att_pubkey = FALSE;
+ }
#endif
/* remember the parent window */
@@ -5350,6 +5352,8 @@ message_postpone(BalsaSendmsg * bsmsg)
#ifdef HAVE_GPGME
g_ptr_array_add(headers, g_strdup("X-Balsa-Crypto"));
g_ptr_array_add(headers, g_strdup_printf("%d", bsmsg->gpg_mode));
+ g_ptr_array_add(headers, g_strdup("X-Balsa-Att-Pubkey"));
+ g_ptr_array_add(headers, g_strdup_printf("%d", bsmsg->attach_pubkey));
#endif
#if HAVE_GSPELL || HAVE_GTKSPELL
@@ -6001,6 +6005,15 @@ sw_encrypt_change_state(GSimpleAction * action, GVariant * state, gpointer data)
}
static void
+sw_att_pubkey_change_state(GSimpleAction * action, GVariant * state, gpointer data)
+{
+ BalsaSendmsg *bsmsg = (BalsaSendmsg *) data;
+
+ bsmsg->attach_pubkey = g_variant_get_boolean(state);
+ g_simple_action_set_state(action, state);
+}
+
+static void
sw_gpg_mode_change_state(GSimpleAction * action,
GVariant * state,
gpointer data)
@@ -6447,6 +6460,7 @@ bsmsg_setup_gpg_ui(BalsaSendmsg *bsmsg)
/* make everything insensitive if we don't have crypto support */
sw_action_set_enabled(bsmsg, "gpg-mode", balsa_app.has_openpgp ||
balsa_app.has_smime);
+ sw_action_set_enabled(bsmsg, "attpubkey", balsa_app.has_openpgp);
}
static void
@@ -6531,6 +6545,8 @@ static GActionEntry win_entries[] = {
sw_gpg_mode_change_state },
{"gpg-mode", libbalsa_radio_activated, "s", "'smime'",
sw_gpg_mode_change_state },
+ {"attpubkey", libbalsa_toggle_activated, NULL, "false",
+ sw_att_pubkey_change_state },
#endif /* HAVE_GPGME */
/* Only a toolbar button: */
{"toolbar-send", sw_toolbar_send_activated }
@@ -6603,6 +6619,7 @@ sendmsg_window_new()
#endif /* HAVE_GTKSPELL */
#ifdef HAVE_GPGME
bsmsg->gpg_mode = LIBBALSA_PROTECT_RFC3156;
+ bsmsg->attach_pubkey = FALSE;
#endif
bsmsg->autosave_timeout_id = /* autosave every 5 minutes */
g_timeout_add_seconds(60*5, (GSourceFunc)sw_autosave_timeout_cb, bsmsg);
@@ -6911,6 +6928,10 @@ sendmsg_window_continue(LibBalsaMailbox * mailbox, guint msgno)
if ((postpone_hdr =
libbalsa_message_get_user_header(message, "X-Balsa-Crypto")))
bsmsg_setup_gpg_ui_by_mode(bsmsg, atoi(postpone_hdr));
+ postpone_hdr = libbalsa_message_get_user_header(message, "X-Balsa-Att-Pubkey");
+ if (postpone_hdr != NULL) {
+ sw_action_set_active(bsmsg, "attpubkey", atoi(postpone_hdr) != 0);
+ }
#endif
if ((postpone_hdr =
libbalsa_message_get_user_header(message, "X-Balsa-MDN")))
diff --git a/src/sendmsg-window.h b/src/sendmsg-window.h
index 5fccf6c..e77aa4a 100644
--- a/src/sendmsg-window.h
+++ b/src/sendmsg-window.h
@@ -95,6 +95,7 @@ G_BEGIN_DECLS
/* is closed. */
#ifdef HAVE_GPGME
guint gpg_mode;
+ gboolean attach_pubkey;
#endif
#if !HAVE_GTKSOURCEVIEW
diff --git a/ui/sendmsg-window.ui b/ui/sendmsg-window.ui
index fc4461d..a763842 100644
--- a/ui/sendmsg-window.ui
+++ b/ui/sendmsg-window.ui
@@ -250,6 +250,13 @@
<attribute name='target'>smime</attribute>
</item>
</section>
+ <section>
+ <item>
+ <attribute name='label'
+ translatable='yes'>Attach GnuPG _Public Key</attribute>
+ <attribute name='action'>win.attpubkey</attribute>
+ </item>
+ </section>
</submenu>
</menu>
</interface>
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]