diff --git a/src/filetype.c b/src/filetype.c
index 4e68d27..58b4145 100644
--- a/src/filetype.c
+++ b/src/filetype.c
@@ -1,202 +1,225 @@
/* filetype.c - Identify file types
* Copyright (C) 2012 g10 Code GmbH
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This file 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see .
*/
#ifdef HAVE_CONFIG_H
# include
#endif
#include
#include
#include
#include
#include "parsetlv.h"
#include "filetype.h"
/* The size of the buffer we use to identify CMS objects. */
#define CMS_BUFFER_SIZE 2048
/* Warning: DATA may be binary but there must be a Nul before DATALEN. */
#ifndef HAVE_GPGME_DATA_IDENTIFY
static int
detect_cms (const char *data, size_t datalen)
{
tlvinfo_t ti;
const char *s;
size_t n;
if (datalen < 24) /* Object is probably too short for CMS. */
return 0;
s = data;
n = datalen;
if (parse_tlv (&s, &n, &ti))
goto try_pgp; /* Not properly BER encoded. */
if (!(ti.cls == ASN1_CLASS_UNIVERSAL && ti.tag == ASN1_TAG_SEQUENCE
&& ti.is_cons))
goto try_pgp; /* A CMS object always starts witn a sequence. */
if (parse_tlv (&s, &n, &ti))
goto try_pgp; /* Not properly BER encoded. */
if (!(ti.cls == ASN1_CLASS_UNIVERSAL && ti.tag == ASN1_TAG_OBJECT_ID
&& !ti.is_cons && ti.length) || ti.length > n)
goto try_pgp; /* This is not an OID as expected. */
if (ti.length == 9)
{
if (!memcmp (s, "\x2A\x86\x48\x86\xF7\x0D\x01\x07\x03", 9))
return 1; /* Encrypted (aka Enveloped Data). */
if (!memcmp (s, "\x2A\x86\x48\x86\xF7\x0D\x01\x07\x02", 9))
return 1; /* Signed. */
}
try_pgp:
/* Check whether this might be a non-armored PGP message. We need
to do this before checking for armor lines, so that we don't get
fooled by armored messages inside a signed binary PGP message. */
if ((data[0] & 0x80))
{
/* That might be a binary PGP message. At least it is not plain
ASCII. Of course this might be certain lead-in text of
armored CMS messages. However, I am not sure whether this is
at all defined and in any case it is uncommon. Thus we don't
do any further plausibility checks but stupidly assume no CMS
armored data will follow. */
return 0;
}
/* Now check whether there are armor lines. */
for (s = data; s && *s; s = (*s=='\n')?(s+1):((s=strchr (s,'\n'))?(s+1):s))
{
if (!strncmp (s, "-----BEGIN ", 11))
{
if (!strncmp (s+11, "PGP ", 4))
return 0; /* This is PGP */
return 1; /* Not PGP, thus we assume CMS. */
}
}
return 0;
}
#endif /*!HAVE_GPGME_DATA_IDENTIFY*/
/* Return true if the file FNAME looks like an CMS file. There is no
error return, just a best effort try to identify CMS in a file with
a CMS object. */
int
is_cms_file (const char *fname)
{
#ifdef HAVE_GPGME_DATA_IDENTIFY
FILE *fp;
gpgme_data_t dh;
gpgme_data_type_t dt;
fp = fopen (fname, "rb");
if (!fp)
return 0; /* Not found - can't be a CMS file. */
if (gpgme_data_new_from_stream (&dh, fp))
{
fclose (fp);
return 0;
}
dt = gpgme_data_identify (dh, 0);
gpgme_data_release (dh);
fclose (fp);
switch (dt)
{
case GPGME_DATA_TYPE_CMS_SIGNED:
case GPGME_DATA_TYPE_CMS_ENCRYPTED:
case GPGME_DATA_TYPE_CMS_OTHER:
case GPGME_DATA_TYPE_X509_CERT:
case GPGME_DATA_TYPE_PKCS12:
return 1;
default:
return 0;
}
#else
int result;
FILE *fp;
char *data;
size_t datalen;
fp = fopen (fname, "rb");
if (!fp)
return 0; /* Not found - can't be a CMS file. */
data = malloc (CMS_BUFFER_SIZE);
if (!data)
{
fclose (fp);
return 0; /* Oops */
}
datalen = fread (data, 1, CMS_BUFFER_SIZE - 1, fp);
data[datalen] = 0;
fclose (fp);
result = detect_cms (data, datalen);
free (data);
return result;
#endif
}
/* Return true if the data (DATA,DATALEN) looks like an CMS object.
There is no error return, just a best effort try to identify CMS. */
int
is_cms_data (const char *data, size_t datalen)
{
#ifdef HAVE_GPGME_DATA_IDENTIFY
gpgme_data_t dh;
gpgme_data_type_t dt;
if (gpgme_data_new_from_mem (&dh, data, datalen, 0))
return 0;
dt = gpgme_data_identify (dh, 0);
gpgme_data_release (dh);
switch (dt)
{
case GPGME_DATA_TYPE_CMS_SIGNED:
case GPGME_DATA_TYPE_CMS_ENCRYPTED:
case GPGME_DATA_TYPE_CMS_OTHER:
case GPGME_DATA_TYPE_X509_CERT:
case GPGME_DATA_TYPE_PKCS12:
return 1;
default:
return 0;
}
#else
int result;
char *buffer;
if (datalen < 24)
return 0; /* Too short - don't bother to copy the buffer. */
if (datalen > CMS_BUFFER_SIZE - 1)
datalen = CMS_BUFFER_SIZE - 1;
buffer = malloc (datalen + 1);
if (!buffer)
return 0; /* Oops */
memcpy (buffer, data, datalen);
buffer[datalen] = 0;
result = detect_cms (buffer, datalen);
free (buffer);
return result;
#endif
}
+
+/* New version of is_cms_data which requires a decent version of
+ gpgme. In contrast to is_cms_data this works directly on a gpgme
+ data object. */
+int
+is_cms_data_ext (gpgme_data_t dh)
+{
+#ifdef HAVE_GPGME_DATA_IDENTIFY
+ switch (gpgme_data_identify (dh, 0))
+ {
+ case GPGME_DATA_TYPE_CMS_SIGNED:
+ case GPGME_DATA_TYPE_CMS_ENCRYPTED:
+ case GPGME_DATA_TYPE_CMS_OTHER:
+ case GPGME_DATA_TYPE_X509_CERT:
+ case GPGME_DATA_TYPE_PKCS12:
+ return 1;
+ default:
+ return 0;
+ }
+#else
+ return 0;
+#endif
+}
diff --git a/src/filetype.h b/src/filetype.h
index 6c88c68..8ba4c2e 100644
--- a/src/filetype.h
+++ b/src/filetype.h
@@ -1,25 +1,26 @@
/* filetype.h - Identify file types
* Copyright (C) 2012 g10 Code GmbH
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This file 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see .
*/
#ifndef FILETYPE_H
#define FILETYPE_H
int is_cms_file (const char *fname);
int is_cms_data (const char *data, size_t datalen);
+int is_cms_data_ext (gpgme_data_t dh);
#endif /*FILETYPE_H*/
diff --git a/src/gpaexportop.c b/src/gpaexportop.c
index 51df806..9caf49f 100644
--- a/src/gpaexportop.c
+++ b/src/gpaexportop.c
@@ -1,255 +1,272 @@
/* gpaexportfileop.c - The GpaExportFileOperation object.
* Copyright (C) 2003, Miguel Coca.
*
* This file is part of GPA
*
* GPA is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* GPA is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
*/
#include
#include
#include "gpa.h"
#include "i18n.h"
#include "gtktools.h"
#include "gpaexportop.h"
static GObjectClass *parent_class = NULL;
/* Properties */
enum
{
PROP_0,
PROP_KEYS
};
static gboolean gpa_export_operation_idle_cb (gpointer data);
static void gpa_export_operation_done_cb (GpaContext *context, gpg_error_t err,
GpaExportOperation *op);
-static void gpa_export_operation_done_error_cb (GpaContext *context,
+static void gpa_export_operation_done_error_cb (GpaContext *context,
gpg_error_t err,
GpaExportOperation *op);
/* GObject boilerplate */
static void
gpa_export_operation_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GpaExportOperation *op = GPA_EXPORT_OPERATION (object);
switch (prop_id)
{
case PROP_KEYS:
g_value_set_pointer (value, op->keys);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gpa_export_operation_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GpaExportOperation *op = GPA_EXPORT_OPERATION (object);
switch (prop_id)
{
case PROP_KEYS:
op->keys = (GList*) g_value_get_pointer (value);
/* Make sure we keep a reference for our keys */
g_list_foreach (op->keys, (GFunc) gpgme_key_ref, NULL);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gpa_export_operation_finalize (GObject *object)
{
GpaExportOperation *op = GPA_EXPORT_OPERATION (object);
/* Free each key, and then the list of keys */
g_list_foreach (op->keys, (GFunc) gpgme_key_unref, NULL);
g_list_free (op->keys);
/* Free the data object, if it exists */
if (op->dest)
{
gpgme_data_release (op->dest);
}
-
+
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
gpa_export_operation_init (GpaExportOperation *op)
{
op->keys = NULL;
op->dest = NULL;
}
static GObject*
gpa_export_operation_constructor (GType type,
guint n_construct_properties,
GObjectConstructParam *construct_properties)
{
GObject *object;
GpaExportOperation *op;
/* Invoke parent's constructor */
object = parent_class->constructor (type,
n_construct_properties,
construct_properties);
op = GPA_EXPORT_OPERATION (object);
g_signal_connect (G_OBJECT (GPA_OPERATION (op)->context), "done",
G_CALLBACK (gpa_export_operation_done_error_cb), op);
g_signal_connect (G_OBJECT (GPA_OPERATION (op)->context), "done",
G_CALLBACK (gpa_export_operation_done_cb), op);
/* Begin working when we are back into the main loop */
g_idle_add (gpa_export_operation_idle_cb, op);
return object;
}
static void
gpa_export_operation_class_init (GpaExportOperationClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
+
parent_class = g_type_class_peek_parent (klass);
object_class->constructor = gpa_export_operation_constructor;
object_class->finalize = gpa_export_operation_finalize;
object_class->set_property = gpa_export_operation_set_property;
object_class->get_property = gpa_export_operation_get_property;
-
+
/* Virtual methods */
klass->get_destination = NULL;
klass->complete_export = NULL;
/* Properties */
g_object_class_install_property (object_class,
PROP_KEYS,
- g_param_spec_pointer
+ g_param_spec_pointer
("keys", "Keys",
"Keys",
G_PARAM_WRITABLE|G_PARAM_CONSTRUCT_ONLY));
}
GType
gpa_export_operation_get_type (void)
{
static GType file_operation_type = 0;
-
+
if (!file_operation_type)
{
static const GTypeInfo file_operation_info =
{
sizeof (GpaExportOperationClass),
(GBaseInitFunc) NULL,
(GBaseFinalizeFunc) NULL,
(GClassInitFunc) gpa_export_operation_class_init,
NULL, /* class_finalize */
NULL, /* class_data */
sizeof (GpaExportOperation),
0, /* n_preallocs */
(GInstanceInitFunc) gpa_export_operation_init,
};
-
+
file_operation_type = g_type_register_static (GPA_OPERATION_TYPE,
"GpaExportOperation",
&file_operation_info, 0);
}
-
+
return file_operation_type;
}
/* Private functions */
static gboolean
gpa_export_operation_idle_cb (gpointer data)
{
GpaExportOperation *op = data;
gboolean armor = TRUE;
if (GPA_EXPORT_OPERATION_GET_CLASS (op)->get_destination (op, &op->dest,
&armor))
{
- gpg_error_t err;
+ gpg_error_t err = 0;
const char **patterns;
GList *k;
int i;
+ gpgme_protocol_t prot = GPGME_PROTOCOL_UNKNOWN;
gpgme_set_armor (GPA_OPERATION (op)->context->ctx, armor);
/* Create the set of keys to export */
patterns = g_malloc0 (sizeof(gchar*)*(g_list_length(op->keys)+1));
for (i = 0, k = op->keys; k; i++, k = g_list_next (k))
{
gpgme_key_t key = (gpgme_key_t) k->data;
patterns[i] = key->subkeys->fpr;
+ if (prot == GPGME_PROTOCOL_UNKNOWN)
+ prot = key->protocol;
+ else if (prot != key->protocol)
+ {
+ gpa_window_error
+ (_("Only keys of the same procotol may be exported"
+ " as a collection."), NULL);
+ g_signal_emit_by_name (GPA_OPERATION (op), "completed", err);
+ goto cleanup;
+ }
}
+ if (prot == GPGME_PROTOCOL_UNKNOWN)
+ {
+ g_signal_emit_by_name (GPA_OPERATION (op), "completed", err);
+ goto cleanup; /* No keys. */
+ }
+ gpgme_set_protocol (GPA_OPERATION (op)->context->ctx, prot);
/* Export to the gpgme_data_t */
- err = gpgme_op_export_ext_start (GPA_OPERATION (op)->context->ctx,
+ err = gpgme_op_export_ext_start (GPA_OPERATION (op)->context->ctx,
patterns, 0, op->dest);
if (err)
{
gpa_gpgme_warning (err);
g_signal_emit_by_name (GPA_OPERATION (op), "completed", err);
}
- /* Clean up */
- g_free (patterns);
+ cleanup:
+ g_free (patterns);
}
else
/* Abort the operation. */
g_signal_emit_by_name (GPA_OPERATION (op), "completed",
gpg_error (GPG_ERR_CANCELED));
return FALSE;
}
static void
gpa_export_operation_done_cb (GpaContext *context, gpg_error_t err,
GpaExportOperation *op)
{
if (! err)
GPA_EXPORT_OPERATION_GET_CLASS (op)->complete_export (op);
g_signal_emit_by_name (GPA_OPERATION (op), "completed", err);
}
static void
gpa_export_operation_done_error_cb (GpaContext *context, gpg_error_t err,
GpaExportOperation *op)
{
switch (gpg_err_code (err))
{
case GPG_ERR_NO_ERROR:
/* Ignore these */
break;
default:
gpa_gpgme_warning (err);
break;
}
}
diff --git a/src/gpaimportop.c b/src/gpaimportop.c
index 81ed769..0680a21 100644
--- a/src/gpaimportop.c
+++ b/src/gpaimportop.c
@@ -1,261 +1,265 @@
/* gpaimportop.c - The GpaImportOperation object.
* Copyright (C) 2003, Miguel Coca.
*
* This file is part of GPA
*
* GPA is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* GPA is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
*/
#include
#include
#include "gpa.h"
#include "i18n.h"
#include "gtktools.h"
#include "gpaimportop.h"
+#include "filetype.h"
static GObjectClass *parent_class = NULL;
/* Signals */
enum
{
IMPORTED_KEYS,
IMPORTED_SECRET_KEYS,
LAST_SIGNAL
};
static guint signals [LAST_SIGNAL] = { 0 };
static gboolean gpa_import_operation_idle_cb (gpointer data);
static void gpa_import_operation_done_cb (GpaContext *context, gpg_error_t err,
GpaImportOperation *op);
-static void gpa_import_operation_done_error_cb (GpaContext *context,
+static void gpa_import_operation_done_error_cb (GpaContext *context,
gpg_error_t err,
GpaImportOperation *op);
/* GObject boilerplate */
static void
gpa_import_operation_finalize (GObject *object)
{
GpaImportOperation *op = GPA_IMPORT_OPERATION (object);
/* Free the data object, if it exists */
if (op->source)
{
gpgme_data_release (op->source);
}
-
+
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
gpa_import_operation_init (GpaImportOperation *op)
{
op->source = NULL;
}
static GObject*
gpa_import_operation_constructor (GType type,
guint n_construct_properties,
GObjectConstructParam *construct_properties)
{
GObject *object;
GpaImportOperation *op;
/* Invoke parent's constructor */
object = parent_class->constructor (type,
n_construct_properties,
construct_properties);
op = GPA_IMPORT_OPERATION (object);
g_signal_connect (G_OBJECT (GPA_OPERATION (op)->context), "done",
G_CALLBACK (gpa_import_operation_done_error_cb), op);
g_signal_connect (G_OBJECT (GPA_OPERATION (op)->context), "done",
G_CALLBACK (gpa_import_operation_done_cb), op);
/* Begin working when we are back into the main loop */
g_idle_add (gpa_import_operation_idle_cb, op);
return object;
}
static void
gpa_import_operation_class_init (GpaImportOperationClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
+
parent_class = g_type_class_peek_parent (klass);
object_class->constructor = gpa_import_operation_constructor;
object_class->finalize = gpa_import_operation_finalize;
-
+
/* Virtual methods */
klass->get_source = NULL;
klass->complete_import = NULL;
/* Signals */
klass->imported_keys = NULL;
signals[IMPORTED_KEYS] =
g_signal_new ("imported_keys",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (GpaImportOperationClass, imported_keys),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
signals[IMPORTED_SECRET_KEYS] =
g_signal_new ("imported_secret_keys",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (GpaImportOperationClass, imported_keys),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
}
GType
gpa_import_operation_get_type (void)
{
static GType file_operation_type = 0;
-
+
if (!file_operation_type)
{
static const GTypeInfo file_operation_info =
{
sizeof (GpaImportOperationClass),
(GBaseInitFunc) NULL,
(GBaseFinalizeFunc) NULL,
(GClassInitFunc) gpa_import_operation_class_init,
NULL, /* class_finalize */
NULL, /* class_data */
sizeof (GpaImportOperation),
0, /* n_preallocs */
(GInstanceInitFunc) gpa_import_operation_init,
};
-
+
file_operation_type = g_type_register_static (GPA_OPERATION_TYPE,
"GpaImportOperation",
&file_operation_info, 0);
}
-
+
return file_operation_type;
}
/* Private functions */
static gboolean
gpa_import_operation_idle_cb (gpointer data)
{
GpaImportOperation *op = data;
if (GPA_IMPORT_OPERATION_GET_CLASS (op)->get_source (op, &op->source))
{
gpg_error_t err;
-
+
+ gpgme_set_protocol (GPA_OPERATION (op)->context->ctx,
+ is_cms_data_ext (op->source)?
+ GPGME_PROTOCOL_CMS : GPGME_PROTOCOL_OpenPGP);
err = gpgme_op_import_start (GPA_OPERATION (op)->context->ctx,
op->source);
if (err)
{
gpa_gpgme_warning (err);
g_signal_emit_by_name (GPA_OPERATION (op), "completed", err);
}
}
else
/* Abort the operation. */
g_signal_emit_by_name (GPA_OPERATION (op), "completed",
gpg_error (GPG_ERR_CANCELED));
return FALSE;
}
-static void
-key_import_results_dialog_run (GtkWidget *parent,
+static void
+key_import_results_dialog_run (GtkWidget *parent,
gpgme_import_result_t info)
{
GtkWidget *dialog;
if (info->considered == 0)
{
dialog = gtk_message_dialog_new (GTK_WINDOW (parent),
GTK_DIALOG_MODAL,
GTK_MESSAGE_WARNING,
GTK_BUTTONS_CLOSE,
_("No keys were found."));
}
else
{
dialog = gtk_message_dialog_new (GTK_WINDOW (parent),
GTK_DIALOG_MODAL,
GTK_MESSAGE_INFO,
GTK_BUTTONS_CLOSE,
_("%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"),
info->considered, info->imported,
info->unchanged, info->secret_read,
info->secret_imported,
info->secret_unchanged);
- }
+ }
/* Run the dialog */
gtk_widget_show_all (dialog);
gtk_dialog_run (GTK_DIALOG (dialog));
gtk_widget_destroy (dialog);
}
static void
gpa_import_operation_done_cb (GpaContext *context, gpg_error_t err,
GpaImportOperation *op)
{
if (! err)
{
gpgme_import_result_t res;
GPA_IMPORT_OPERATION_GET_CLASS (op)->complete_import (op);
res = gpgme_op_import_result (GPA_OPERATION (op)->context->ctx);
if (res->imported > 0 && res->secret_imported )
{
g_signal_emit_by_name (GPA_OPERATION (op), "imported_secret_keys");
}
else if (res->imported > 0)
{
g_signal_emit_by_name (GPA_OPERATION (op), "imported_keys");
}
key_import_results_dialog_run (GPA_OPERATION (op)->window, res);
}
g_signal_emit_by_name (GPA_OPERATION (op), "completed", err);
}
static void
gpa_import_operation_done_error_cb (GpaContext *context, gpg_error_t err,
GpaImportOperation *op)
{
switch (gpg_err_code (err))
{
case GPG_ERR_NO_ERROR:
/* Ignore these */
break;
default:
gpa_gpgme_warning (err);
break;
}
}