diff --git a/src/gpastreamencryptop.c b/src/gpastreamencryptop.c index 146707f..70a5e24 100644 --- a/src/gpastreamencryptop.c +++ b/src/gpastreamencryptop.c @@ -1,521 +1,525 @@ /* gpastreamdecryptop.c - The GpaOperation object. * Copyright (C) 2007, 2008 g10 Code GmbH * * This file is part of GPA. * * GPA is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GPA is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public * License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include "gpgmetools.h" #include "recipientdlg.h" #include "gpawidgets.h" #include "gpastreamencryptop.h" #include "selectkeydlg.h" -struct _GpaStreamEncryptOperation +struct _GpaStreamEncryptOperation { GpaStreamOperation parent; - - SelectKeyDlg *key_dialog; + + SelectKeyDlg *key_dialog; RecipientDlg *recp_dialog; GSList *recipients; gpgme_key_t *keys; gpgme_protocol_t selected_protocol; }; -struct _GpaStreamEncryptOperationClass +struct _GpaStreamEncryptOperationClass { GpaStreamOperationClass parent_class; }; /* Indentifiers for our properties. */ -enum +enum { PROP_0, PROP_RECIPIENTS, PROP_RECIPIENT_KEYS, PROP_PROTOCOL }; static void response_cb (GtkDialog *dialog, gint response, gpointer user_data); static gboolean start_encryption_cb (gpointer data); static void done_error_cb (GpaContext *context, gpg_error_t err, GpaStreamEncryptOperation *op); static void done_cb (GpaContext *context, gpg_error_t err, GpaStreamEncryptOperation *op); static GObjectClass *parent_class; /* Helper to be used as a GFunc for free. */ static void free_func (void *p, void *dummy) { (void)dummy; g_free (p); } static void release_recipients (GSList *recipients) { if (recipients) { g_slist_foreach (recipients, free_func, NULL); g_slist_free (recipients); } } /* Return a deep copy of the recipients list. */ static GSList * copy_recipients (GSList *recipients) { GSList *recp, *newlist; - + newlist= NULL; for (recp = recipients; recp; recp = g_slist_next (recp)) newlist = g_slist_append (newlist, g_strdup (recp->data)); return newlist; } static void gpa_stream_encrypt_operation_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GpaStreamEncryptOperation *op = GPA_STREAM_ENCRYPT_OPERATION (object); - + switch (prop_id) { case PROP_RECIPIENTS: g_value_set_pointer (value, op->recipients); break; case PROP_RECIPIENT_KEYS: g_value_set_pointer (value, op->keys); break; case PROP_PROTOCOL: g_value_set_int (value, op->selected_protocol); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gpa_stream_encrypt_operation_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GpaStreamEncryptOperation *op = GPA_STREAM_ENCRYPT_OPERATION (object); switch (prop_id) { case PROP_RECIPIENTS: op->recipients = (GSList*)g_value_get_pointer (value); break; case PROP_RECIPIENT_KEYS: op->keys = (gpgme_key_t*)g_value_get_pointer (value); break; case PROP_PROTOCOL: op->selected_protocol = g_value_get_int (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gpa_stream_encrypt_operation_finalize (GObject *object) -{ +{ GpaStreamEncryptOperation *op = GPA_STREAM_ENCRYPT_OPERATION (object); release_recipients (op->recipients); op->recipients = NULL; gpa_gpgme_release_keyarray (op->keys); op->keys = NULL; G_OBJECT_CLASS (parent_class)->finalize (object); } static void gpa_stream_encrypt_operation_init (GpaStreamEncryptOperation *op) { op->key_dialog = NULL; op->recp_dialog = NULL; op->recipients = NULL; op->keys = NULL; op->selected_protocol = GPGME_PROTOCOL_UNKNOWN; } static GObject* -gpa_stream_encrypt_operation_constructor +gpa_stream_encrypt_operation_constructor (GType type, guint n_construct_properties, GObjectConstructParam *construct_properties) { GObject *object; GpaStreamEncryptOperation *op; object = parent_class->constructor (type, n_construct_properties, construct_properties); op = GPA_STREAM_ENCRYPT_OPERATION (object); /* Create the recipient key selection dialog if we don't know the keys yet. */ if (!op->keys && (!op->recipients || !g_slist_length (op->recipients))) { /* No recipients - use a generic key selection dialog. */ op->key_dialog = select_key_dlg_new (GPA_OPERATION (op)->window); g_signal_connect (G_OBJECT (op->key_dialog), "response", G_CALLBACK (response_cb), op); } else if (!op->keys) { /* Caller gave us some recipients - use the mail address matching key selectiion dialog. */ op->recp_dialog = recipient_dlg_new (GPA_OPERATION (op)->window); recipient_dlg_set_recipients (op->recp_dialog, op->recipients, op->selected_protocol); g_signal_connect (G_OBJECT (op->recp_dialog), "response", G_CALLBACK (response_cb), op); } else g_idle_add (start_encryption_cb, op); /* We connect the done signal to two handles. The error handler is called first. */ g_signal_connect (G_OBJECT (GPA_OPERATION (op)->context), "done", G_CALLBACK (done_error_cb), op); g_signal_connect (G_OBJECT (GPA_OPERATION (op)->context), "done", G_CALLBACK (done_cb), op); - gtk_window_set_title + gtk_window_set_title (GTK_WINDOW (GPA_STREAM_OPERATION (op)->progress_dialog), _("Encrypting message ...")); if (op->key_dialog) gtk_widget_show_all (GTK_WIDGET (op->key_dialog)); if (op->recp_dialog) gtk_widget_show_all (GTK_WIDGET (op->recp_dialog)); return object; } static void gpa_stream_encrypt_operation_class_init (GpaStreamEncryptOperationClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); - + parent_class = g_type_class_peek_parent (klass); object_class->constructor = gpa_stream_encrypt_operation_constructor; object_class->finalize = gpa_stream_encrypt_operation_finalize; object_class->set_property = gpa_stream_encrypt_operation_set_property; object_class->get_property = gpa_stream_encrypt_operation_get_property; - g_object_class_install_property + g_object_class_install_property (object_class, PROP_RECIPIENTS, - g_param_spec_pointer + g_param_spec_pointer ("recipients", "Recipients", "A list of recipients in rfc-822 mailbox format.", G_PARAM_WRITABLE|G_PARAM_CONSTRUCT_ONLY)); - g_object_class_install_property + g_object_class_install_property (object_class, PROP_RECIPIENT_KEYS, - g_param_spec_pointer + g_param_spec_pointer ("recipient-keys", "Recipient-keys", "An array of gpgme_key_t with the selected keys.", G_PARAM_WRITABLE|G_PARAM_CONSTRUCT_ONLY)); - g_object_class_install_property + g_object_class_install_property (object_class, PROP_PROTOCOL, - g_param_spec_int + g_param_spec_int ("protocol", "Protocol", "The gpgme protocol currently selected.", GPGME_PROTOCOL_OpenPGP, GPGME_PROTOCOL_UNKNOWN, GPGME_PROTOCOL_UNKNOWN, G_PARAM_WRITABLE|G_PARAM_CONSTRUCT_ONLY)); } GType gpa_stream_encrypt_operation_get_type (void) { static GType stream_encrypt_operation_type = 0; - + if (!stream_encrypt_operation_type) { static const GTypeInfo stream_encrypt_operation_info = { sizeof (GpaStreamEncryptOperationClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) gpa_stream_encrypt_operation_class_init, NULL, /* class_finalize */ NULL, /* class_data */ sizeof (GpaStreamEncryptOperation), 0, /* n_preallocs */ (GInstanceInitFunc) gpa_stream_encrypt_operation_init, }; - - stream_encrypt_operation_type = g_type_register_static + + stream_encrypt_operation_type = g_type_register_static (GPA_STREAM_OPERATION_TYPE, "GpaStreamEncryptOperation", &stream_encrypt_operation_info, 0); } - + return stream_encrypt_operation_type; } /* Return true if all keys are matching the protocol. */ static int keys_match_protocol_p (gpgme_key_t *keys, gpgme_protocol_t protocol) { int idx; if (!keys) return 1; /* No keys: assume match. */ for (idx = 0; keys[idx]; idx++) if (keys[idx]->protocol != protocol) return 0; return 1; } /* * Fire up the encryption. */ static void start_encryption (GpaStreamEncryptOperation *op) { gpg_error_t err; int prep_only = 0; if (!op->keys || !op->keys[0]) { err = gpg_error (GPG_ERR_NO_PUBKEY); goto leave; } if (op->selected_protocol == GPGME_PROTOCOL_OpenPGP) err = gpa_operation_write_status (GPA_OPERATION (op), "PROTOCOL", "OpenPGP", NULL); else if (op->selected_protocol == GPGME_PROTOCOL_CMS) err = gpa_operation_write_status (GPA_OPERATION (op), "PROTOCOL", "CMS", NULL); else err = gpg_error (GPG_ERR_NO_PUBKEY); if (err) goto leave; /* Set the output encoding. */ - if (GPA_STREAM_OPERATION (op)->input_stream + if (GPA_STREAM_OPERATION (op)->input_stream && GPA_STREAM_OPERATION (op)->output_stream) { - if (op->selected_protocol == GPGME_PROTOCOL_CMS) + if (gpgme_data_get_encoding (GPA_STREAM_OPERATION(op)->output_stream)) + gpgme_data_set_encoding + (GPA_STREAM_OPERATION (op)->output_stream, + gpgme_data_get_encoding (GPA_STREAM_OPERATION(op)->output_stream)); + else if (op->selected_protocol == GPGME_PROTOCOL_CMS) gpgme_data_set_encoding (GPA_STREAM_OPERATION (op)->output_stream, GPGME_DATA_ENCODING_BASE64); else gpgme_set_armor (GPA_OPERATION (op)->context->ctx, 1); if (!keys_match_protocol_p (op->keys, op->selected_protocol)) { g_debug ("the selected keys do not match the protocol"); err = gpg_error (GPG_ERR_CONFLICT); goto leave; } - - gpgme_set_protocol (GPA_OPERATION (op)->context->ctx, + + gpgme_set_protocol (GPA_OPERATION (op)->context->ctx, op->selected_protocol); /* We always trust the keys because the recipient selection dialog has already sorted unusable out. */ err = gpgme_op_encrypt_start (GPA_OPERATION (op)->context->ctx, op->keys, GPGME_ENCRYPT_ALWAYS_TRUST, GPA_STREAM_OPERATION (op)->input_stream, GPA_STREAM_OPERATION (op)->output_stream); if (err) { gpa_gpgme_warning (err); goto leave; } /* Show and update the progress dialog. */ gtk_widget_show_all (GPA_STREAM_OPERATION (op)->progress_dialog); - gpa_progress_dialog_set_label + gpa_progress_dialog_set_label (GPA_PROGRESS_DIALOG (GPA_STREAM_OPERATION (op)->progress_dialog), _("Message encryption")); } else { /* We are just preparing an encryption. */ prep_only = 1; err = 0; } leave: if (err || prep_only) g_signal_emit_by_name (GPA_OPERATION (op), "completed", err); } /* The recipient key selection dialog has returned. */ -static void +static void response_cb (GtkDialog *dialog, int response, void *user_data) { GpaStreamEncryptOperation *op = user_data; gtk_widget_hide (GTK_WIDGET (dialog)); - + if (response != GTK_RESPONSE_OK) { /* The dialog was canceled, so we do nothing and complete the operation. */ g_signal_emit_by_name (GPA_OPERATION (op), "completed", gpg_error (GPG_ERR_CANCELED)); /* FIXME: We might need to destroy the widget in the KEY_DIALOG case. */ return; } /* Get the keys. */ gpa_gpgme_release_keyarray (op->keys); op->keys = NULL; if (op->key_dialog) op->keys = select_key_dlg_get_keys (op->key_dialog); else if (op->recp_dialog) op->keys = recipient_dlg_get_keys (op->recp_dialog, &op->selected_protocol); start_encryption (op); } /* This is the idle function used to start the encryption if no recipient key selection dialog has been requested. */ static gboolean start_encryption_cb (void *user_data) { GpaStreamEncryptOperation *op = user_data; start_encryption (op); return FALSE; /* Remove this callback from the event loop. */ } /*Show an error message. */ static void done_error_cb (GpaContext *context, gpg_error_t err, GpaStreamEncryptOperation *op) { switch (gpg_err_code (err)) { case GPG_ERR_NO_ERROR: case GPG_ERR_CANCELED: /* Ignore these */ break; /* case GPG_ERR_BAD_PASSPHRASE: */ /* gpa_window_error (_("Wrong passphrase!"), GPA_OPERATION (op)->window); */ /* break; */ default: gpa_gpgme_warning (err); break; } } /* Operation is ready. Tell the server. */ static void done_cb (GpaContext *context, gpg_error_t err, GpaStreamEncryptOperation *op) { gtk_widget_hide (GPA_STREAM_OPERATION (op)->progress_dialog); g_signal_emit_by_name (GPA_OPERATION (op), "completed", err); } /* Public API. */ /* Start encrypting INPUT_STREAM to OUTPUT_STREAM using WINDOW. RECIPIENTS gives a list of recipients and the function matches them with existing keys and selects appropriate keys. RECP_KEYS is either NULL or an array with gpgme keys which will then immediatley be used and suppresses the recipient key selection dialog. If it is not possible to unambigiously select keys and SILENT is not given, a key selection dialog offers the user a way to manually select keys. INPUT_STREAM and OUTPUT_STREAM may be given as NULL in which case the function skips the actual encryption step and just verifies the recipients. */ GpaStreamEncryptOperation* gpa_stream_encrypt_operation_new (GtkWidget *window, gpgme_data_t input_stream, gpgme_data_t output_stream, GSList *recipients, gpgme_key_t *recp_keys, gpgme_protocol_t protocol, int silent) { GpaStreamEncryptOperation *op; /* Fixme: SILENT is not yet implemented. */ g_debug ("recipients %p recp_keys %p", recipients, recp_keys); op = g_object_new (GPA_STREAM_ENCRYPT_OPERATION_TYPE, "window", window, "input_stream", input_stream, "output_stream", output_stream, "recipients", copy_recipients (recipients), "recipient-keys", gpa_gpgme_copy_keyarray (recp_keys), "protocol", (int)protocol, NULL); return op; } /* Return an array of keys for the set of recipients of this object. The function also returns the selected protocol. */ gpgme_key_t * gpa_stream_encrypt_operation_get_keys (GpaStreamEncryptOperation *op, gpgme_protocol_t *r_protocol) { g_return_val_if_fail (op, NULL); - + if (r_protocol) *r_protocol = op->selected_protocol; return gpa_gpgme_copy_keyarray (op->keys); } diff --git a/src/gpastreamsignop.c b/src/gpastreamsignop.c index 0b955eb..a19b0a3 100644 --- a/src/gpastreamsignop.c +++ b/src/gpastreamsignop.c @@ -1,507 +1,511 @@ /* gpastreamsignop.c - The GpaStreamSignOperation object. * 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 . */ #include #include #include "gpgmetools.h" #include "gtktools.h" #include "filesigndlg.h" #include "gpastreamsignop.h" -struct _GpaStreamSignOperation +struct _GpaStreamSignOperation { GpaStreamOperation parent; GtkWidget *sign_dialog; - + const char *sender; gpgme_protocol_t requested_protocol; gboolean detached; }; -struct _GpaStreamSignOperationClass +struct _GpaStreamSignOperationClass { GpaStreamOperationClass parent_class; }; /* Indentifiers for our properties. */ -enum +enum { PROP_0, PROP_SENDER, PROP_PROTOCOL, PROP_DETACHED }; static void response_cb (GtkDialog *dialog, gint response, gpointer user_data); static void done_error_cb (GpaContext *context, gpg_error_t err, GpaStreamSignOperation *op); static void done_cb (GpaContext *context, gpg_error_t err, GpaStreamSignOperation *op); static GObjectClass *parent_class; static void gpa_stream_sign_operation_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GpaStreamSignOperation *op = GPA_STREAM_SIGN_OPERATION (object); - + switch (prop_id) { case PROP_SENDER: g_value_set_string (value, op->sender); break; case PROP_PROTOCOL: g_value_set_int (value, op->requested_protocol); break; case PROP_DETACHED: g_value_set_boolean (value, op->detached); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gpa_stream_sign_operation_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GpaStreamSignOperation *op = GPA_STREAM_SIGN_OPERATION (object); switch (prop_id) { case PROP_SENDER: op->sender = g_value_get_string (value); break; case PROP_PROTOCOL: op->requested_protocol = g_value_get_int (value); break; case PROP_DETACHED: op->detached = g_value_get_boolean (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gpa_stream_sign_operation_finalize (GObject *object) -{ +{ /* GpaStreamSignOperation *op = GPA_STREAM_SIGN_OPERATION (object); */ G_OBJECT_CLASS (parent_class)->finalize (object); } static void gpa_stream_sign_operation_init (GpaStreamSignOperation *op) { op->requested_protocol = GPGME_PROTOCOL_UNKNOWN; } static GObject* gpa_stream_sign_operation_ctor (GType type, guint n_construct_properties, GObjectConstructParam *construct_properties) { GObject *object; GpaStreamSignOperation *op; object = parent_class->constructor (type, n_construct_properties, construct_properties); op = GPA_STREAM_SIGN_OPERATION (object); op->sign_dialog = gpa_file_sign_dialog_new (GPA_OPERATION (op)->window); { GpaFileSignDialog *dialog = GPA_FILE_SIGN_DIALOG (op->sign_dialog); /* Note: The information here is wrong. The actual sig_mode and armor settings are determined from the selected key (which determines the protocol). We set the values here to those for OpenPGP, and force (==hide) the selection widgets. */ gpa_file_sign_dialog_set_armor (dialog, TRUE); gpa_file_sign_dialog_set_force_armor (dialog, TRUE); gpa_file_sign_dialog_set_sig_mode (dialog, GPGME_SIG_MODE_NORMAL); gpa_file_sign_dialog_set_force_sig_mode (dialog, TRUE); } g_signal_connect (G_OBJECT (op->sign_dialog), "response", G_CALLBACK (response_cb), op); /* We connect the done signal to two handles. The error handler is called first. */ g_signal_connect (G_OBJECT (GPA_OPERATION (op)->context), "done", G_CALLBACK (done_error_cb), op); g_signal_connect (G_OBJECT (GPA_OPERATION (op)->context), "done", G_CALLBACK (done_cb), op); - gtk_window_set_title + gtk_window_set_title (GTK_WINDOW (GPA_STREAM_OPERATION (op)->progress_dialog), _("Signing message ...")); if (op->sign_dialog) gtk_widget_show_all (GTK_WIDGET (op->sign_dialog)); return object; } static void gpa_stream_sign_operation_class_init (GpaStreamSignOperationClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); - + parent_class = g_type_class_peek_parent (klass); object_class->constructor = gpa_stream_sign_operation_ctor; object_class->finalize = gpa_stream_sign_operation_finalize; object_class->set_property = gpa_stream_sign_operation_set_property; object_class->get_property = gpa_stream_sign_operation_get_property; - g_object_class_install_property + g_object_class_install_property (object_class, PROP_SENDER, - g_param_spec_pointer + g_param_spec_pointer ("sender", "Sender", "The sender of the message in rfc-822 mailbox format or NULL.", G_PARAM_WRITABLE|G_PARAM_CONSTRUCT_ONLY)); - g_object_class_install_property + g_object_class_install_property (object_class, PROP_PROTOCOL, - g_param_spec_int + g_param_spec_int ("protocol", "Protocol", "The requested gpgme protocol.", GPGME_PROTOCOL_OpenPGP, GPGME_PROTOCOL_UNKNOWN, GPGME_PROTOCOL_UNKNOWN, G_PARAM_WRITABLE|G_PARAM_CONSTRUCT_ONLY)); - g_object_class_install_property + g_object_class_install_property (object_class, PROP_DETACHED, - g_param_spec_boolean + g_param_spec_boolean ("detached", "Detached", "Flag requesting a detached signature.", FALSE, G_PARAM_WRITABLE|G_PARAM_CONSTRUCT_ONLY)); } GType gpa_stream_sign_operation_get_type (void) { static GType stream_sign_operation_type = 0; - + if (!stream_sign_operation_type) { static const GTypeInfo stream_sign_operation_info = { sizeof (GpaStreamSignOperationClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) gpa_stream_sign_operation_class_init, NULL, /* class_finalize */ NULL, /* class_data */ sizeof (GpaStreamSignOperation), 0, /* n_preallocs */ (GInstanceInitFunc) gpa_stream_sign_operation_init, }; - - stream_sign_operation_type = g_type_register_static + + stream_sign_operation_type = g_type_register_static (GPA_STREAM_OPERATION_TYPE, "GpaStreamSignOperation", &stream_sign_operation_info, 0); } - + return stream_sign_operation_type; } /* Setting the signers and the protocol for the context. The protocol * to use is derived from the keys. An error will be displayed if the * selected keys are not all of one protocol. Returns true on * success. */ static gboolean set_signers (GpaStreamSignOperation *op, GList *signers) { GList *cur; gpg_error_t err; gpgme_protocol_t protocol = GPGME_PROTOCOL_UNKNOWN; gpgme_signers_clear (GPA_OPERATION (op)->context->ctx); if (!signers) { /* Can't happen */ gpa_window_error (_("You didn't select any key for signing"), GPA_OPERATION (op)->window); return FALSE; } for (cur = signers; cur; cur = g_list_next (cur)) { gpgme_key_t key = cur->data; if (protocol == GPGME_PROTOCOL_UNKNOWN) protocol = key->protocol; else if (key->protocol != protocol) { /* Should not happen because the selection dialog should have not allowed to select different key types. */ - gpa_window_error + gpa_window_error (_("The selected certificates are not all of the same type." " That is, you mixed OpenPGP and X.509 certificates." " Please make sure to select only certificates of the" - " same type."), + " same type."), GPA_OPERATION (op)->window); return FALSE; } } gpgme_set_protocol (GPA_OPERATION (op)->context->ctx, protocol); for (cur = signers; cur; cur = g_list_next (cur)) { gpgme_key_t key = cur->data; err = gpgme_signers_add (GPA_OPERATION (op)->context->ctx, key); if (err) gpa_gpgme_error (err); } return TRUE; } /* * Fire up the signing */ static void start_signing (GpaStreamSignOperation *op) { gpg_error_t err; int prep_only = 0; GList *signers; gpgme_protocol_t protocol; - signers = gpa_file_sign_dialog_signers + signers = gpa_file_sign_dialog_signers (GPA_FILE_SIGN_DIALOG (op->sign_dialog)); if (!set_signers (op, signers)) { err = gpg_error (GPG_ERR_NO_SECKEY); goto leave; } protocol = gpgme_get_protocol (GPA_OPERATION (op)->context->ctx); if (protocol == GPGME_PROTOCOL_OpenPGP) err = gpa_operation_write_status (GPA_OPERATION (op), "PROTOCOL", "OpenPGP", NULL); else if (protocol == GPGME_PROTOCOL_CMS) err = gpa_operation_write_status (GPA_OPERATION (op), "PROTOCOL", "CMS", NULL); else err = gpg_error (GPG_ERR_NO_SECKEY); if (err) goto leave; /* Set the output encoding. */ - if (GPA_STREAM_OPERATION (op)->input_stream + if (GPA_STREAM_OPERATION (op)->input_stream && GPA_STREAM_OPERATION (op)->output_stream) { - if (protocol == GPGME_PROTOCOL_CMS) + if (gpgme_data_get_encoding (GPA_STREAM_OPERATION(op)->output_stream)) + gpgme_data_set_encoding + (GPA_STREAM_OPERATION (op)->output_stream, + gpgme_data_get_encoding (GPA_STREAM_OPERATION(op)->output_stream)); + else if (protocol == GPGME_PROTOCOL_CMS) gpgme_data_set_encoding (GPA_STREAM_OPERATION (op)->output_stream, GPGME_DATA_ENCODING_BASE64); else gpgme_set_armor (GPA_OPERATION (op)->context->ctx, 1); - err = gpgme_op_sign_start (GPA_OPERATION (op)->context->ctx, + err = gpgme_op_sign_start (GPA_OPERATION (op)->context->ctx, GPA_STREAM_OPERATION (op)->input_stream, GPA_STREAM_OPERATION (op)->output_stream, (op->detached? GPGME_SIG_MODE_DETACH - /* */ : GPGME_SIG_MODE_NORMAL)); + /* */ : GPGME_SIG_MODE_NORMAL)); if (err) { gpa_gpgme_warning (err); goto leave; } - + /* Show and update the progress dialog. */ gtk_widget_show_all (GPA_STREAM_OPERATION (op)->progress_dialog); - gpa_progress_dialog_set_label + gpa_progress_dialog_set_label (GPA_PROGRESS_DIALOG (GPA_STREAM_OPERATION (op)->progress_dialog), _("Message signing")); } else { /* We are just preparing an encryption. */ prep_only = 1; err = 0; } leave: if (err || prep_only) g_signal_emit_by_name (GPA_OPERATION (op), "completed", err); } /* The recipient key selection dialog has returned. */ -static void +static void response_cb (GtkDialog *dialog, int response, void *user_data) { GpaStreamSignOperation *op = user_data; gtk_widget_hide (GTK_WIDGET (dialog)); - + if (response != GTK_RESPONSE_OK) { /* The dialog was canceled, so we do nothing and complete the operation. */ g_signal_emit_by_name (GPA_OPERATION (op), "completed", gpg_error (GPG_ERR_CANCELED)); return; } start_signing (op); } /* This is the idle function used to start the encryption if no recipient key selection dialog has been requested. */ #if 0 /* Not yet used. */ static gboolean start_signing_cb (void *user_data) { GpaStreamSignOperation *op = user_data; start_signing (op); return FALSE; /* Remove this callback from the event loop. */ } #endif /* Show an error message. */ static void done_error_cb (GpaContext *context, gpg_error_t err, GpaStreamSignOperation *op) { switch (gpg_err_code (err)) { case GPG_ERR_NO_ERROR: case GPG_ERR_CANCELED: /* Ignore these */ break; case GPG_ERR_BAD_PASSPHRASE: gpa_window_error (_("Wrong passphrase!"), GPA_OPERATION (op)->window); break; default: gpa_gpgme_warning (err); break; } } /* Operation is ready. Tell the server. */ static void done_cb (GpaContext *context, gpg_error_t err, GpaStreamSignOperation *op) { gtk_widget_hide (GPA_STREAM_OPERATION (op)->progress_dialog); if (! err) { gpgme_protocol_t protocol; gpgme_sign_result_t res; gpgme_new_signature_t sig; protocol = gpgme_get_protocol (GPA_OPERATION (op)->context->ctx); res = gpgme_op_sign_result (GPA_OPERATION (op)->context->ctx); if (res) { sig = res->signatures; while (sig) { char *str; char *algo_name; - + str = g_strdup_printf ("%s%s", (protocol == GPGME_PROTOCOL_OpenPGP) ? "pgp-" : "", gpgme_hash_algo_name (sig->hash_algo)); algo_name = g_ascii_strdown (str, -1); g_free (str); /* FIXME: Error handling. */ err = gpa_operation_write_status (GPA_OPERATION (op), "MICALG", algo_name, NULL); g_free (algo_name); sig = sig->next; } } } g_signal_emit_by_name (GPA_OPERATION (op), "completed", err); } /* Public API. */ /* Start signing INPUT_STREAM to OUTPUT_STREAM using WINDOW. SENDER gives the name of the sender's role (usually a mailbox) or is NULL for the default sender. If it is not possible to unambigiously select a signing key a key selection dialog offers the user a way to manually select signing keys. INPUT_STREAM and OUTPUT_STREAM may be given as NULL in which case the function skips the actual signing step and just verifies the signing key. */ GpaStreamSignOperation* gpa_stream_sign_operation_new (GtkWidget *window, gpgme_data_t input_stream, gpgme_data_t output_stream, const gchar *sender, gpgme_protocol_t protocol, gboolean detached) { GpaStreamSignOperation *op; op = g_object_new (GPA_STREAM_SIGN_OPERATION_TYPE, "window", window, "input_stream", input_stream, "output_stream", output_stream, "sender", sender, "protocol", (int)protocol, "detached", detached, NULL); return op; } diff --git a/src/server.c b/src/server.c index ee4ec8f..d220ccb 100644 --- a/src/server.c +++ b/src/server.c @@ -1,2050 +1,2073 @@ /* server.c - The UI server part of GPA. Copyright (C) 2007, 2008 g10 Code GmbH This file is part of GPA GPA is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GPA is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #ifndef HAVE_W32_SYSTEM # include # include #endif /*HAVE_W32_SYSTEM*/ #include #include #include #include "gpa.h" #include "i18n.h" #include "gpastreamencryptop.h" #include "gpastreamsignop.h" #include "gpastreamdecryptop.h" #include "gpastreamverifyop.h" #include "gpafileop.h" #include "gpafileencryptop.h" #include "gpafilesignop.h" #include "gpafiledecryptop.h" #include "gpafileverifyop.h" #define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t)) /* The object used to keep track of the a connection's state. */ struct conn_ctrl_s; typedef struct conn_ctrl_s *conn_ctrl_t; struct conn_ctrl_s { /* True if we are currently processing a command. */ int in_command; /* NULL or continuation function for a command. */ void (*cont_cmd) (assuan_context_t, gpg_error_t); /* Flag indicating that the client died while a continuation was still registyered. */ int client_died; /* This is a helper to detect that the unfinished error code actually comes from our command handler. */ int is_unfinished; /* An GPAOperation object. */ GpaOperation *gpa_op; /* File descriptors used by the gpgme callbacks. */ int input_fd; int output_fd; /* File descriptor set with the MESSAGE command. */ int message_fd; + /* Flag indicating the the output shall be binary. */ + int output_binary; + /* Channels used with the gpgme callbacks. */ GIOChannel *input_channel; GIOChannel *output_channel; GIOChannel *message_channel; /* List of collected recipients. */ GSList *recipients; /* Array of keys already prepared for RECIPIENTS. */ gpgme_key_t *recipient_keys; /* The protocol as selected by the user. */ gpgme_protocol_t selected_protocol; /* The current sender address (malloced) and a flag telleing whether the sender ist just informational. */ gchar *sender; int sender_just_info; gpgme_protocol_t sender_protocol_hint; /* Session information: A session number and a malloced title or NULL. */ unsigned int session_number; char *session_title; /* The list of all files to be processed. */ GList *files; gboolean files_finished; }; /* The number of active connections. */ static int connection_counter; /* A flag requesting a shutdown. */ static gboolean shutdown_pending; /* The nonce used by the server connection. This nonce is required under Windows to emulate Unix Domain Sockets. This is managed by libassuan but we need to store the nonce in the application. Under Unix this is just a stub. */ static assuan_sock_nonce_t socket_nonce; /* Forward declarations. */ static void run_server_continuation (assuan_context_t ctx, gpg_error_t err); static int not_finished (conn_ctrl_t ctrl) { ctrl->is_unfinished = 1; return gpg_error (GPG_ERR_UNFINISHED); } /* Test whether LINE contains thye option NAME. An optional argument of the option is ignored. For example with NAME being "--protocol" this function returns true for "--protocol" as well as for "--protocol=foo". The returned pointer points right behind the option name, which may be an equal sign, Nul or a space. If tehre is no option NAME, false (i.e. NULL) is returned. */ static const char * has_option_name (const char *line, const char *name) { const char *s; int n = strlen (name); s = strstr (line, name); return (s && (s == line || spacep (s-1)) && (!s[n] || spacep (s+n) || s[n] == '=')) ? (s+n) : NULL; } /* Check whether LINE contains the option NAME. */ static int has_option (const char *line, const char *name) { const char *s; int n = strlen (name); s = strstr (line, name); return (s && (s == line || spacep (s-1)) && (!s[n] || spacep (s+n))); } /* Skip over options. */ static char * skip_options (char *line) { while (spacep (line)) line++; while ( *line == '-' && line[1] == '-' ) { while (*line && !spacep (line)) line++; while (spacep (line)) line++; } return line; } /* Helper to be used as a GFunc for free. */ static void free_func (void *p, void *dummy) { (void)dummy; g_free (p); } static ssize_t my_gpgme_read_cb (void *opaque, void *buffer, size_t size) { conn_ctrl_t ctrl = opaque; GIOStatus status; size_t nread; int retval; /* g_debug ("my_gpgme_read_cb: requesting %d bytes\n", (int)size); */ status = g_io_channel_read_chars (ctrl->input_channel, buffer, size, &nread, NULL); if (status == G_IO_STATUS_AGAIN || (status == G_IO_STATUS_NORMAL && !nread)) { errno = EAGAIN; retval = -1; } else if (status == G_IO_STATUS_NORMAL) retval = (int)nread; else if (status == G_IO_STATUS_EOF) retval = 0; else { errno = EIO; retval = -1; } /* g_debug ("my_gpgme_read_cb: got status=%x, %d bytes, retval=%d\n", */ /* status, (int)size, retval); */ return retval; } static ssize_t my_gpgme_write_cb (void *opaque, const void *buffer, size_t size) { conn_ctrl_t ctrl = opaque; GIOStatus status; size_t nwritten; int retval; status = g_io_channel_write_chars (ctrl->output_channel, buffer, size, &nwritten, NULL); if (status == G_IO_STATUS_AGAIN) { errno = EAGAIN; retval = -1; } else if (status == G_IO_STATUS_NORMAL) retval = (int)nwritten; else { errno = EIO; retval = 1; } return retval; } static struct gpgme_data_cbs my_gpgme_data_cbs = { my_gpgme_read_cb, my_gpgme_write_cb, NULL, NULL }; static ssize_t my_gpgme_message_read_cb (void *opaque, void *buffer, size_t size) { conn_ctrl_t ctrl = opaque; GIOStatus status; size_t nread; int retval; status = g_io_channel_read_chars (ctrl->message_channel, buffer, size, &nread, NULL); if (status == G_IO_STATUS_AGAIN || (status == G_IO_STATUS_NORMAL && !nread)) { errno = EAGAIN; retval = -1; } else if (status == G_IO_STATUS_NORMAL) retval = (int)nread; else if (status == G_IO_STATUS_EOF) retval = 0; else { errno = EIO; retval = -1; } return retval; } static struct gpgme_data_cbs my_gpgme_message_cbs = { my_gpgme_message_read_cb, NULL, NULL, NULL }; static ssize_t my_devnull_write_cb (void *opaque, const void *buffer, size_t size) { return size; } static struct gpgme_data_cbs my_devnull_data_cbs = { NULL, my_devnull_write_cb, NULL, NULL }; /* Release the recipients stored in the connection context. */ static void release_recipients (conn_ctrl_t ctrl) { if (ctrl->recipients) { g_slist_foreach (ctrl->recipients, free_func, NULL); g_slist_free (ctrl->recipients); ctrl->recipients = NULL; } } static void free_file_item (gpa_file_item_t item) { if (item->filename_in) g_free (item->filename_in); if (item->filename_out) g_free (item->filename_out); } static void release_files (conn_ctrl_t ctrl) { if (! ctrl->files) return; g_list_foreach (ctrl->files, (GFunc) free_file_item, NULL); g_list_free (ctrl->files); ctrl->files = NULL; ctrl->files_finished = FALSE; } static void release_keys (gpgme_key_t *keys) { if (keys) { int idx; for (idx=0; keys[idx]; idx++) gpgme_key_unref (keys[idx]); g_free (keys); } } /* Reset already prepared keys. */ static void reset_prepared_keys (conn_ctrl_t ctrl) { release_keys (ctrl->recipient_keys); ctrl->recipient_keys = NULL; ctrl->selected_protocol = GPGME_PROTOCOL_UNKNOWN; } /* Helper to parse an protocol option. */ static gpg_error_t parse_protocol_option (assuan_context_t ctx, char *line, int mandatory, gpgme_protocol_t *r_protocol) { *r_protocol = GPGME_PROTOCOL_UNKNOWN; if (has_option (line, "--protocol=OpenPGP")) *r_protocol = GPGME_PROTOCOL_OpenPGP; else if (has_option (line, "--protocol=CMS")) *r_protocol = GPGME_PROTOCOL_CMS; else if (has_option_name (line, "--protocol")) return set_error (GPG_ERR_ASS_PARAMETER, "invalid protocol"); else if (mandatory) return set_error (GPG_ERR_ASS_PARAMETER, "no protocol specified"); return 0; } static void close_message_fd (conn_ctrl_t ctrl) { if (ctrl->message_fd != -1) { close (ctrl->message_fd); ctrl->message_fd = -1; } } static void finish_io_streams (assuan_context_t ctx, gpgme_data_t *r_input_data, gpgme_data_t *r_output_data, gpgme_data_t *r_message_data) { conn_ctrl_t ctrl = assuan_get_pointer (ctx); if (r_input_data) gpgme_data_release (*r_input_data); if (r_output_data) gpgme_data_release (*r_output_data); if (r_message_data) gpgme_data_release (*r_message_data); if (ctrl->input_channel) { g_io_channel_shutdown (ctrl->input_channel, 0, NULL); ctrl->input_channel = NULL; } if (ctrl->output_channel) { g_io_channel_shutdown (ctrl->output_channel, 0, NULL); ctrl->output_channel = NULL; } if (ctrl->message_channel) { g_io_channel_shutdown (ctrl->message_channel, 0, NULL); ctrl->message_channel = NULL; } close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); ctrl->input_fd = -1; ctrl->output_fd = -1; } /* Translate the input and output file descriptors and return an error if they are not set. */ static gpg_error_t translate_io_streams (assuan_context_t ctx) { conn_ctrl_t ctrl = assuan_get_pointer (ctx); ctrl->input_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0); if (ctrl->input_fd == -1) return set_error (GPG_ERR_ASS_NO_INPUT, NULL); ctrl->output_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); if (ctrl->output_fd == -1) return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); return 0; } static gpg_error_t prepare_io_streams (assuan_context_t ctx, gpgme_data_t *r_input_data, gpgme_data_t *r_output_data, gpgme_data_t *r_message_data) { gpg_error_t err; conn_ctrl_t ctrl = assuan_get_pointer (ctx); if (r_input_data) *r_input_data = NULL; if (r_output_data) *r_output_data = NULL; if (r_message_data) *r_message_data = NULL; if (ctrl->input_fd != -1 && r_input_data) { #ifdef HAVE_W32_SYSTEM ctrl->input_channel = g_io_channel_win32_new_fd (ctrl->input_fd); #else ctrl->input_channel = g_io_channel_unix_new (ctrl->input_fd); #endif if (!ctrl->input_channel) { g_debug ("error creating input channel"); err = gpg_error (GPG_ERR_EIO); goto leave; } g_io_channel_set_encoding (ctrl->input_channel, NULL, NULL); g_io_channel_set_buffered (ctrl->input_channel, FALSE); } if (ctrl->output_fd != -1 && r_output_data) { #ifdef HAVE_W32_SYSTEM ctrl->output_channel = g_io_channel_win32_new_fd (ctrl->output_fd); #else ctrl->output_channel = g_io_channel_unix_new (ctrl->output_fd); #endif if (!ctrl->output_channel) { g_debug ("error creating output channel"); err = gpg_error (GPG_ERR_EIO); goto leave; } g_io_channel_set_encoding (ctrl->output_channel, NULL, NULL); g_io_channel_set_buffered (ctrl->output_channel, FALSE); } if (ctrl->message_fd != -1 && r_message_data) { #ifdef HAVE_W32_SYSTEM ctrl->message_channel = g_io_channel_win32_new_fd (ctrl->message_fd); #else ctrl->message_channel = g_io_channel_unix_new (ctrl->message_fd); #endif if (!ctrl->message_channel) { g_debug ("error creating message channel"); err = gpg_error (GPG_ERR_EIO); goto leave; } g_io_channel_set_encoding (ctrl->message_channel, NULL, NULL); g_io_channel_set_buffered (ctrl->message_channel, FALSE); } if (ctrl->input_channel) { err = gpgme_data_new_from_cbs (r_input_data, &my_gpgme_data_cbs, ctrl); if (err) goto leave; } if (ctrl->output_channel) { err = gpgme_data_new_from_cbs (r_output_data, &my_gpgme_data_cbs, ctrl); if (err) goto leave; + if (ctrl->output_binary) + gpgme_data_set_encoding (*r_output_data, GPGME_DATA_ENCODING_BINARY); } if (ctrl->message_channel) { err = gpgme_data_new_from_cbs (r_message_data, &my_gpgme_message_cbs, ctrl); if (err) goto leave; } err = 0; leave: if (err) finish_io_streams (ctx, r_input_data, r_output_data, r_message_data); return err; } static const char hlp_session[] = "SESSION []\n" "\n" "The NUMBER is an arbitrary value, a server may use to associate\n" "simultaneous running sessions. It is a 32 bit unsigned integer\n" "with 0 as a special value indicating that no session association\n" "shall be done.\n" "\n" "If STRING is given, the server may use this as the title of a\n" "window or, in the case of an email operation, to extract the\n" "sender's address. The string may contain spaces; thus no\n" "plus-escaping is used.\n" "\n" "This command may be used at any time and overrides the effect of\n" "the last command. A RESET command undoes the effect of this\n" "command."; static gpg_error_t cmd_session (assuan_context_t ctx, char *line) { conn_ctrl_t ctrl = assuan_get_pointer (ctx); char *endp; line = skip_options (line); ctrl->session_number = strtoul (line, &endp, 10); for (line = endp; spacep (line); line++) ; xfree (ctrl->session_title); ctrl->session_title = *line? xstrdup (line) : NULL; return assuan_process_done (ctx, 0); } static const char hlp_recipient[] = "RECIPIENT \n" "\n" "Set the recipient for the encryption. is an RFC2822\n" "recipient name. This command may or may not check the recipient for\n" "validity right away; if it does not (as here) all recipients are\n" "checked at the time of the ENCRYPT command. All RECIPIENT commands\n" "are cumulative until a RESET or an successful ENCRYPT command."; static gpg_error_t cmd_recipient (assuan_context_t ctx, char *line) { conn_ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; reset_prepared_keys (ctrl); if (*line) ctrl->recipients = g_slist_append (ctrl->recipients, xstrdup (line)); return assuan_process_done (ctx, err); } static const char hlp_message[] = "MESSAGE FD[=]\n" "\n" "Set the file descriptor to read a message of a detached signature to N."; static gpg_error_t cmd_message (assuan_context_t ctx, char *line) { conn_ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; assuan_fd_t sysfd; int fd; err = assuan_command_parse_fd (ctx, line, &sysfd); if (!err) { fd = translate_sys2libc_fd (sysfd, 0); if (fd == -1) err = set_error (GPG_ERR_ASS_NO_INPUT, NULL); else ctrl->message_fd = fd; } return assuan_process_done (ctx, err); } /* Continuation for cmd_encrypt. */ static void cont_encrypt (assuan_context_t ctx, gpg_error_t err) { conn_ctrl_t ctrl = assuan_get_pointer (ctx); g_debug ("cont_encrypt called with ERR=%s <%s>", gpg_strerror (err), gpg_strsource (err)); finish_io_streams (ctx, NULL, NULL, NULL); if (!err) release_recipients (ctrl); assuan_process_done (ctx, err); } static const char hlp_encrypt[] = "ENCRYPT --protocol=OpenPGP|CMS\n" "\n" "Encrypt the data received on INPUT to OUTPUT."; static gpg_error_t cmd_encrypt (assuan_context_t ctx, char *line) { conn_ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; gpgme_protocol_t protocol = 0; GpaStreamEncryptOperation *op; gpgme_data_t input_data = NULL; gpgme_data_t output_data = NULL; err = parse_protocol_option (ctx, line, 1, &protocol); if (err) goto leave; if (protocol != ctrl->selected_protocol) { if (ctrl->selected_protocol != GPGME_PROTOCOL_UNKNOWN) g_debug ("note: protocol does not match the one from PREP_ENCRYPT"); reset_prepared_keys (ctrl); } line = skip_options (line); if (*line) { err = set_error (GPG_ERR_ASS_SYNTAX, NULL); goto leave; } err = translate_io_streams (ctx); if (err) goto leave; err = prepare_io_streams (ctx, &input_data, &output_data, NULL); if (err) goto leave; ctrl->cont_cmd = cont_encrypt; op = gpa_stream_encrypt_operation_new (NULL, input_data, output_data, ctrl->recipients, ctrl->recipient_keys, protocol, 0); input_data = output_data = NULL; g_signal_connect_swapped (G_OBJECT (op), "completed", G_CALLBACK (run_server_continuation), ctx); g_signal_connect (G_OBJECT (op), "completed", G_CALLBACK (g_object_unref), NULL); g_signal_connect_swapped (G_OBJECT (op), "status", G_CALLBACK (assuan_write_status), ctx); return not_finished (ctrl); leave: finish_io_streams (ctx, &input_data, &output_data, NULL); close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); ctrl->input_fd = -1; ctrl->output_fd = -1; return assuan_process_done (ctx, err); } /* Continuation for cmd_prep_encrypt. */ static void cont_prep_encrypt (assuan_context_t ctx, gpg_error_t err) { conn_ctrl_t ctrl = assuan_get_pointer (ctx); g_debug ("cont_prep_encrypt called with ERR=%s <%s>", gpg_strerror (err), gpg_strsource (err)); if (!err) { release_keys (ctrl->recipient_keys); ctrl->recipient_keys = gpa_stream_encrypt_operation_get_keys (GPA_STREAM_ENCRYPT_OPERATION (ctrl->gpa_op), &ctrl->selected_protocol); if (ctrl->recipient_keys) g_print ("received some keys\n"); else g_print ("received no keys\n"); } if (ctrl->gpa_op) { g_object_unref (ctrl->gpa_op); ctrl->gpa_op = NULL; } assuan_process_done (ctx, err); } static const char hlp_prep_encrypt[] = "PREP_ENCRYPT [--protocol=OpenPGP|CMS]\n" "\n" "Dummy encryption command used to check whether the given recipients\n" "are all valid and to tell the client the preferred protocol."; static gpg_error_t cmd_prep_encrypt (assuan_context_t ctx, char *line) { conn_ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; gpgme_protocol_t protocol; GpaStreamEncryptOperation *op; err = parse_protocol_option (ctx, line, 0, &protocol); if (err) goto leave; line = skip_options (line); if (*line) { err = set_error (GPG_ERR_ASS_SYNTAX, NULL); goto leave; } reset_prepared_keys (ctrl); if (ctrl->gpa_op) { g_debug ("Oops: there is still an GPA_OP active\n"); g_object_unref (ctrl->gpa_op); ctrl->gpa_op = NULL; } ctrl->cont_cmd = cont_prep_encrypt; op = gpa_stream_encrypt_operation_new (NULL, NULL, NULL, ctrl->recipients, ctrl->recipient_keys, protocol, 0); /* Store that instance for later use but also install a signal handler to unref it. */ g_object_ref (op); ctrl->gpa_op = GPA_OPERATION (op); g_signal_connect_swapped (G_OBJECT (op), "completed", G_CALLBACK (run_server_continuation), ctx); g_signal_connect (G_OBJECT (op), "completed", G_CALLBACK (g_object_unref), NULL); g_signal_connect_swapped (G_OBJECT (op), "status", G_CALLBACK (assuan_write_status), ctx); return not_finished (ctrl); leave: return assuan_process_done (ctx, err); } static const char hlp_sender[] = "SENDER \n" "\n" "EMAIL is the plain ASCII encoded address (\"addr-spec\" as per\n" "RFC-2822) enclosed in angle brackets. The address set by this\n" "command is valid until a successful \"SIGN\" command or until a\n" "\"RESET\" command. A second command overrides the effect of\n" "the first one; if EMAIL is not given the server shall use the\n" "default signing key."; static gpg_error_t cmd_sender (assuan_context_t ctx, char *line) { conn_ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; gpgme_protocol_t protocol; err = parse_protocol_option (ctx, line, 0, &protocol); if (err) goto leave; ctrl->sender_just_info = has_option (line, "--info"); line = skip_options (line); xfree (ctrl->sender); ctrl->sender = NULL; if (*line) ctrl->sender = xstrdup (line); if (!err) ctrl->sender_protocol_hint = protocol; leave: return assuan_process_done (ctx, err); } /* Continuation for cmd_sign. */ static void cont_sign (assuan_context_t ctx, gpg_error_t err) { conn_ctrl_t ctrl = assuan_get_pointer (ctx); g_debug ("cont_sign called with ERR=%s <%s>", gpg_strerror (err), gpg_strsource (err)); finish_io_streams (ctx, NULL, NULL, NULL); if (!err) { xfree (ctrl->sender); ctrl->sender = NULL; ctrl->sender_protocol_hint = GPGME_PROTOCOL_UNKNOWN; } assuan_process_done (ctx, err); } static const char hlp_sign[] = "SIGN --protocol=OpenPGP|CMS [--detached]\n" "\n" "Sign the data received on INPUT to OUTPUT."; static gpg_error_t cmd_sign (assuan_context_t ctx, char *line) { conn_ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; gpgme_protocol_t protocol; gboolean detached; GpaStreamSignOperation *op; gpgme_data_t input_data = NULL; gpgme_data_t output_data = NULL; err = parse_protocol_option (ctx, line, 1, &protocol); if (err) goto leave; detached = has_option (line, "--detached"); line = skip_options (line); if (*line) { err = set_error (GPG_ERR_ASS_SYNTAX, NULL); goto leave; } err = translate_io_streams (ctx); if (err) goto leave; err = prepare_io_streams (ctx, &input_data, &output_data, NULL); if (err) goto leave; ctrl->cont_cmd = cont_sign; op = gpa_stream_sign_operation_new (NULL, input_data, output_data, ctrl->sender, protocol, detached); input_data = output_data = NULL; g_signal_connect_swapped (G_OBJECT (op), "completed", G_CALLBACK (run_server_continuation), ctx); g_signal_connect (G_OBJECT (op), "completed", G_CALLBACK (g_object_unref), NULL); g_signal_connect_swapped (G_OBJECT (op), "status", G_CALLBACK (assuan_write_status), ctx); return not_finished (ctrl); leave: finish_io_streams (ctx, &input_data, &output_data, NULL); close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); ctrl->input_fd = -1; ctrl->output_fd = -1; return assuan_process_done (ctx, err); } /* Continuation for cmd_decrypt. */ static void cont_decrypt (assuan_context_t ctx, gpg_error_t err) { g_debug ("cont_decrypt called with ERR=%s <%s>", gpg_strerror (err), gpg_strsource (err)); finish_io_streams (ctx, NULL, NULL, NULL); assuan_process_done (ctx, err); } static const char hlp_decrypt[] = "DECRYPT --protocol=OpenPGP|CMS [--no-verify]\n" "\n" "Decrypt a message given by the source set with the INPUT command\n" "and write the plaintext to the sink set with the OUTPUT command.\n" "\n" "If the option --no-verify is given, the server should not try to\n" "verify a signature, in case the input data is an OpenPGP combined\n" "message."; static gpg_error_t cmd_decrypt (assuan_context_t ctx, char *line) { conn_ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; gpgme_protocol_t protocol = 0; int no_verify; GpaStreamDecryptOperation *op; gpgme_data_t input_data = NULL; gpgme_data_t output_data = NULL; err = parse_protocol_option (ctx, line, 1, &protocol); if (err) goto leave; no_verify = has_option (line, "--no-verify"); line = skip_options (line); if (*line) { err = set_error (GPG_ERR_ASS_SYNTAX, NULL); goto leave; } err = translate_io_streams (ctx); if (err) goto leave; err = prepare_io_streams (ctx, &input_data, &output_data, NULL); if (err) goto leave; ctrl->cont_cmd = cont_decrypt; op = gpa_stream_decrypt_operation_new (NULL, input_data, output_data, no_verify, protocol, ctrl->session_title); input_data = output_data = NULL; g_signal_connect_swapped (G_OBJECT (op), "completed", G_CALLBACK (run_server_continuation), ctx); g_signal_connect (G_OBJECT (op), "completed", G_CALLBACK (g_object_unref), NULL); g_signal_connect_swapped (G_OBJECT (op), "status", G_CALLBACK (assuan_write_status), ctx); return not_finished (ctrl); leave: finish_io_streams (ctx, &input_data, &output_data, NULL); close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); ctrl->input_fd = -1; ctrl->output_fd = -1; return assuan_process_done (ctx, err); } /* Continuation for cmd_verify. */ static void cont_verify (assuan_context_t ctx, gpg_error_t err) { g_debug ("cont_verify called with ERR=%s <%s>", gpg_strerror (err), gpg_strsource (err)); finish_io_streams (ctx, NULL, NULL, NULL); assuan_process_done (ctx, err); } static const char hlp_verify[] = "VERIFY --protocol=OpenPGP|CMS [--silent]\n" "\n" "Verify a message. Depending on the combination of the\n" "sources/sinks set by the commands MESSAGE, INPUT and OUTPUT, the\n" "verification mode is selected like this:\n" "\n" "MESSAGE and INPUT\n" " This indicates a detached signature. Output data is not applicable.\n" "\n" "INPUT \n" " This indicates an opaque signature. As no output command has\n" " been given, we are only required to check the signature.\n" "\n" "INPUT and OUTPUT\n" " This indicates an opaque signature. We write the signed data to\n" " the file descriptor set by the OUTPUT command. This data is even\n" " written if the signature can't be verified.\n" "\n" "With the option --silent we won't display any dialog; this is for\n" "example used by the client to get the content of an opaque signed\n" "message. This status message is always send before an OK response:\n" "\n" " SIGSTATUS \n" "\n" "Defined FLAGS are:\n" "\n" " none\n" " The message has a signature but it could not not be verified\n" " due to a missing key.\n" "\n" " green\n" " The signature is fully valid.\n" "\n" " yellow\n" " The signature is valid but additional information was shown\n" " regarding the validity of the key.\n" "\n" " red\n" " The signature is not valid. \n" "\n" "The DISPLAYSTRING is a percent-and-plus-encoded string with a short\n" "human readable description of the status."; static gpg_error_t cmd_verify (assuan_context_t ctx, char *line) { conn_ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; gpgme_protocol_t protocol = 0; int silent; GpaStreamVerifyOperation *op; gpgme_data_t input_data = NULL; gpgme_data_t output_data = NULL; gpgme_data_t message_data = NULL; enum { VERIFY_DETACH, VERIFY_OPAQUE, VERIFY_OPAQUE_WITH_OUTPUT } op_mode; err = parse_protocol_option (ctx, line, 1, &protocol); if (err) goto leave; silent = has_option (line, "--silent"); line = skip_options (line); if (*line) { err = set_error (GPG_ERR_ASS_SYNTAX, NULL); goto leave; } /* Note: We can't use translate_io_streams because that returns an error if one is not opened but that is not an error here. */ ctrl->input_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0); ctrl->output_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); if (ctrl->message_fd != -1 && ctrl->input_fd != -1 && ctrl->output_fd == -1) op_mode = VERIFY_DETACH; else if (ctrl->message_fd == -1 && ctrl->input_fd != -1 && ctrl->output_fd == -1) op_mode = VERIFY_OPAQUE; else if (ctrl->message_fd == -1 && ctrl->input_fd != -1 && ctrl->output_fd != -1) op_mode = VERIFY_OPAQUE_WITH_OUTPUT; else { err = set_error (GPG_ERR_CONFLICT, "invalid verify mode"); goto leave; } err = prepare_io_streams (ctx, &input_data, &output_data, &message_data); if (! err && op_mode == VERIFY_OPAQUE) err = gpgme_data_new_from_cbs (&output_data, &my_devnull_data_cbs, ctrl); if (err) goto leave; ctrl->cont_cmd = cont_verify; op = gpa_stream_verify_operation_new (NULL, input_data, message_data, output_data, silent, protocol, ctrl->session_title); input_data = output_data = message_data = NULL; g_signal_connect_swapped (G_OBJECT (op), "completed", G_CALLBACK (run_server_continuation), ctx); g_signal_connect (G_OBJECT (op), "completed", G_CALLBACK (g_object_unref), NULL); g_signal_connect_swapped (G_OBJECT (op), "status", G_CALLBACK (assuan_write_status), ctx); return not_finished (ctrl); leave: finish_io_streams (ctx, &input_data, &output_data, &message_data); close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); ctrl->input_fd = -1; ctrl->output_fd = -1; return assuan_process_done (ctx, err); } static const char hlp_start_keymanager[] = "START_KEYMANAGER\n" "\n" "Pop up the key manager window. The client expects that the key\n" "manager is brought into the foregound and that this command\n" "immediatley returns."; static gpg_error_t cmd_start_keymanager (assuan_context_t ctx, char *line) { gpa_open_key_manager (NULL, NULL); return assuan_process_done (ctx, 0); } static const char hlp_start_cardmanager[] = "START_CARDMANAGER\n" "\n" "Pop up the card manager window. The client expects that the key\n" "manager is brought into the foregound and that this command\n" "immediatley returns."; static gpg_error_t cmd_start_cardmanager (assuan_context_t ctx, char *line) { gpa_open_cardmanager (NULL, NULL); return assuan_process_done (ctx, 0); } static const char hlp_start_confdialog[] = "START_CONFDIALOG\n" "\n" "Pop up the configure dialog. The client expects that the key\n" "manager is brought into the foregound and that this command\n" "immediatley returns."; static gpg_error_t cmd_start_confdialog (assuan_context_t ctx, char *line) { gpa_open_settings_dialog (NULL, NULL); return assuan_process_done (ctx, 0); } static const char hlp_getinfo[] = "GETINFO \n" "\n" "Multipurpose function to return a variety of information.\n" "Supported values for WHAT are:\n" "\n" " version - Return the version of the program.\n" " pid - Return the process id of the server."; static gpg_error_t cmd_getinfo (assuan_context_t ctx, char *line) { gpg_error_t err; if (!strcmp (line, "version")) { const char *s = PACKAGE_NAME " " PACKAGE_VERSION; err = assuan_send_data (ctx, s, strlen (s)); } else if (!strcmp (line, "pid")) { char numbuf[50]; snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ()); err = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT"); return assuan_process_done (ctx, err); } /* Convert two hexadecimal digits from STR to the value they represent. Returns -1 if one of the characters is not a hexadecimal digit. */ static int hextobyte (const char *str) { int val = 0; int i; #define NROFHEXDIGITS 2 for (i = 0; i < NROFHEXDIGITS; i++) { if (*str >= '0' && *str <= '9') val += *str - '0'; else if (*str >= 'A' && *str <= 'F') val += 10 + *str - 'A'; else if (*str >= 'a' && *str <= 'f') val += 10 + *str - 'a'; else return -1; if (i < NROFHEXDIGITS - 1) val *= 16; str++; } return val; } /* Decode the percent escaped string STR in place. */ static void decode_percent_string (char *str) { char *src = str; char *dest = str; /* Convert the string. */ while (*src) { if (*src != '%') { *(dest++) = *(src++); continue; } else { int val = hextobyte (&src[1]); if (val == -1) { /* Should not happen. */ *(dest++) = *(src++); if (*src) *(dest++) = *(src++); if (*src) *(dest++) = *(src++); } else { if (!val) { /* A binary zero is not representable in a C string. */ *(dest++) = '\\'; *(dest++) = '0'; } else *((unsigned char *) dest++) = val; src += 3; } } } *(dest++) = 0; } /* FILE [--continued] Set the files on which to operate. */ static gpg_error_t cmd_file (assuan_context_t ctx, char *line) { gpg_error_t err = 0; conn_ctrl_t ctrl = assuan_get_pointer (ctx); gboolean continued; gpa_file_item_t file_item; char *tail; continued = has_option (line, "--continued"); if (ctrl->files_finished) release_files (ctrl); tail = line; while (*tail && ! spacep (tail)) tail++; *tail = '\0'; decode_percent_string (line); file_item = g_malloc0 (sizeof (*file_item)); file_item->filename_in = g_strdup (line); ctrl->files = g_list_append (ctrl->files, file_item); if (! continued) ctrl->files_finished = TRUE; return assuan_process_done (ctx, err); } static gpg_error_t impl_encrypt_sign_files (assuan_context_t ctx, int encr, int sign) { gpg_error_t err = 0; conn_ctrl_t ctrl = assuan_get_pointer (ctx); GpaFileOperation *op; if (! ctrl->files) { err = set_error (GPG_ERR_ASS_SYNTAX, "no files specified"); return assuan_process_done (ctx, err); } else if (! ctrl->files_finished) { err = set_error (GPG_ERR_ASS_SYNTAX, "more files expected"); return assuan_process_done (ctx, err); } /* FIXME: Needs a root window. Need to set "sign" default. */ if (encr && sign) op = (GpaFileOperation *) gpa_file_encrypt_sign_operation_new (NULL, ctrl->files, FALSE); if (encr) op = (GpaFileOperation *) gpa_file_encrypt_operation_new (NULL, ctrl->files, FALSE); else op = (GpaFileOperation *) gpa_file_sign_operation_new (NULL, ctrl->files, FALSE); /* Ownership of CTRL->files was passed to callee. */ ctrl->files = NULL; ctrl->files_finished = FALSE; g_signal_connect (G_OBJECT (op), "completed", G_CALLBACK (g_object_unref), NULL); return assuan_process_done (ctx, err); } /* ENCRYPT_FILES --nohup */ static gpg_error_t cmd_encrypt_files (assuan_context_t ctx, char *line) { gpg_error_t err; if (! has_option (line, "--nohup")) { err = set_error (GPG_ERR_ASS_PARAMETER, "file ops require --nohup"); return assuan_process_done (ctx, err); } line = skip_options (line); if (*line) { err = set_error (GPG_ERR_ASS_SYNTAX, NULL); return assuan_process_done (ctx, err); } return impl_encrypt_sign_files (ctx, 1, 0); } /* SIGN_FILES --nohup */ static gpg_error_t cmd_sign_files (assuan_context_t ctx, char *line) { gpg_error_t err; if (! has_option (line, "--nohup")) { err = set_error (GPG_ERR_ASS_PARAMETER, "file ops require --nohup"); return assuan_process_done (ctx, err); } line = skip_options (line); if (*line) { err = set_error (GPG_ERR_ASS_SYNTAX, NULL); return assuan_process_done (ctx, err); } return impl_encrypt_sign_files (ctx, 0, 1); } /* ENCRYPT_SIGN_FILES --nohup */ static gpg_error_t cmd_encrypt_sign_files (assuan_context_t ctx, char *line) { gpg_error_t err; if (! has_option (line, "--nohup")) { err = set_error (GPG_ERR_ASS_PARAMETER, "file ops require --nohup"); return assuan_process_done (ctx, err); } line = skip_options (line); if (*line) { err = set_error (GPG_ERR_ASS_SYNTAX, NULL); return assuan_process_done (ctx, err); } return impl_encrypt_sign_files (ctx, 1, 1); } static gpg_error_t impl_decrypt_verify_files (assuan_context_t ctx, int decrypt, int verify) { gpg_error_t err = 0; conn_ctrl_t ctrl = assuan_get_pointer (ctx); GpaFileOperation *op; if (! ctrl->files) { err = set_error (GPG_ERR_ASS_SYNTAX, "no files specified"); return assuan_process_done (ctx, err); } else if (! ctrl->files_finished) { err = set_error (GPG_ERR_ASS_SYNTAX, "more files expected"); return assuan_process_done (ctx, err); } /* FIXME: Needs a root window. Need to enable "verify". */ if (decrypt && verify) op = (GpaFileOperation *) gpa_file_decrypt_verify_operation_new (NULL, ctrl->files); else if (decrypt) op = (GpaFileOperation *) gpa_file_decrypt_operation_new (NULL, ctrl->files); else op = (GpaFileOperation *) gpa_file_verify_operation_new (NULL, ctrl->files); /* Ownership of CTRL->files was passed to callee. */ ctrl->files = NULL; ctrl->files_finished = FALSE; g_signal_connect (G_OBJECT (op), "completed", G_CALLBACK (g_object_unref), NULL); return assuan_process_done (ctx, err); } /* DECRYPT_FILES --nohup */ static gpg_error_t cmd_decrypt_files (assuan_context_t ctx, char *line) { gpg_error_t err; if (! has_option (line, "--nohup")) { err = set_error (GPG_ERR_ASS_PARAMETER, "file ops require --nohup"); return assuan_process_done (ctx, err); } line = skip_options (line); if (*line) { err = set_error (GPG_ERR_ASS_SYNTAX, NULL); return assuan_process_done (ctx, err); } return impl_decrypt_verify_files (ctx, 1, 0); } /* VERIFY_FILES --nohup */ static gpg_error_t cmd_verify_files (assuan_context_t ctx, char *line) { gpg_error_t err; if (! has_option (line, "--nohup")) { err = set_error (GPG_ERR_ASS_PARAMETER, "file ops require --nohup"); return assuan_process_done (ctx, err); } line = skip_options (line); if (*line) { err = set_error (GPG_ERR_ASS_SYNTAX, NULL); return assuan_process_done (ctx, err); } return impl_decrypt_verify_files (ctx, 0, 1); } /* DECRYPT_VERIFY_FILES --nohup */ static gpg_error_t cmd_decrypt_verify_files (assuan_context_t ctx, char *line) { gpg_error_t err; if (! has_option (line, "--nohup")) { err = set_error (GPG_ERR_ASS_PARAMETER, "file ops require --nohup"); return assuan_process_done (ctx, err); } line = skip_options (line); if (*line) { err = set_error (GPG_ERR_ASS_SYNTAX, NULL); return assuan_process_done (ctx, err); } return impl_decrypt_verify_files (ctx, 1, 1); } /* IMPORT_FILES --nohup */ static gpg_error_t cmd_import_files (assuan_context_t ctx, char *line) { gpg_error_t err; if (! has_option (line, "--nohup")) { err = set_error (GPG_ERR_ASS_PARAMETER, "file ops require --nohup"); return assuan_process_done (ctx, err); } line = skip_options (line); if (*line) { err = set_error (GPG_ERR_ASS_SYNTAX, NULL); return assuan_process_done (ctx, err); } err = set_error (GPG_ERR_NOT_IMPLEMENTED, "not implemented"); return assuan_process_done (ctx, err); } /* CHECKSUM_CREATE_FILES --nohup */ static gpg_error_t cmd_checksum_create_files (assuan_context_t ctx, char *line) { gpg_error_t err; if (! has_option (line, "--nohup")) { err = set_error (GPG_ERR_ASS_PARAMETER, "file ops require --nohup"); return assuan_process_done (ctx, err); } line = skip_options (line); if (*line) { err = set_error (GPG_ERR_ASS_SYNTAX, NULL); return assuan_process_done (ctx, err); } err = set_error (GPG_ERR_NOT_IMPLEMENTED, "not implemented"); return assuan_process_done (ctx, err); } /* CHECKSUM_VERIFY_FILES --nohup */ static gpg_error_t cmd_checksum_verify_files (assuan_context_t ctx, char *line) { gpg_error_t err; if (! has_option (line, "--nohup")) { err = set_error (GPG_ERR_ASS_PARAMETER, "file ops require --nohup"); return assuan_process_done (ctx, err); } line = skip_options (line); if (*line) { err = set_error (GPG_ERR_ASS_SYNTAX, NULL); return assuan_process_done (ctx, err); } err = set_error (GPG_ERR_NOT_IMPLEMENTED, "not implemented"); return assuan_process_done (ctx, err); } static gpg_error_t reset_notify (assuan_context_t ctx, char *line) { conn_ctrl_t ctrl = assuan_get_pointer (ctx); reset_prepared_keys (ctrl); release_recipients (ctrl); release_files (ctrl); xfree (ctrl->sender); ctrl->sender = NULL; ctrl->sender_protocol_hint = GPGME_PROTOCOL_UNKNOWN; finish_io_streams (ctx, NULL, NULL, NULL); close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); ctrl->input_fd = -1; ctrl->output_fd = -1; + ctrl->output_binary = 0; if (ctrl->gpa_op) { g_object_unref (ctrl->gpa_op); ctrl->gpa_op = NULL; } ctrl->session_number = 0; xfree (ctrl->session_title); ctrl->session_title = NULL; return 0; } + +static gpg_error_t +output_notify (assuan_context_t ctx, char *line) +{ + conn_ctrl_t ctrl = assuan_get_pointer (ctx); + + if (strstr (line, "--binary")) + ctrl->output_binary = 1; + else + ctrl->output_binary = 0; + /* Note: We also allow --armor and --base64 but because we don't + check for errors we don't need to parse them. */ + return 0; +} + + /* Tell libassuan about our commands. */ static int register_commands (assuan_context_t ctx) { static struct { const char *name; assuan_handler_t handler; const char * const help; } table[] = { { "SESSION", cmd_session, hlp_session }, { "RECIPIENT", cmd_recipient, hlp_recipient }, { "INPUT", NULL }, { "OUTPUT", NULL }, { "MESSAGE", cmd_message, hlp_message }, { "ENCRYPT", cmd_encrypt, hlp_encrypt }, { "PREP_ENCRYPT", cmd_prep_encrypt, hlp_prep_encrypt }, { "SENDER", cmd_sender, hlp_sender }, { "SIGN", cmd_sign, hlp_sign }, { "DECRYPT", cmd_decrypt, hlp_decrypt }, { "VERIFY", cmd_verify, hlp_verify }, { "START_KEYMANAGER", cmd_start_keymanager, hlp_start_keymanager }, { "START_CONFDIALOG", cmd_start_confdialog, hlp_start_confdialog }, { "START_CARDMANAGER", cmd_start_cardmanager, hlp_start_cardmanager }, { "GETINFO", cmd_getinfo, hlp_getinfo }, { "FILE", cmd_file }, { "ENCRYPT_FILES", cmd_encrypt_files }, { "SIGN_FILES", cmd_sign_files }, { "ENCRYPT_SIGN_FILES", cmd_encrypt_sign_files }, { "DECRYPT_FILES", cmd_decrypt_files }, { "VERIFY_FILES", cmd_verify_files }, { "DECRYPT_VERIFY_FILES", cmd_decrypt_verify_files }, { "IMPORT_FILES", cmd_import_files }, { "CHECKSUM_CREATE_FILES", cmd_checksum_create_files }, { "CHECKSUM_VERIFY_FILES", cmd_checksum_verify_files }, { NULL } }; int i, rc; for (i=0; table[i].name; i++) { rc = assuan_register_command (ctx, table[i].name, table[i].handler, table[i].help); if (rc) return rc; } return 0; } /* Prepare for a new connection on descriptor FD. */ static assuan_context_t connection_startup (assuan_fd_t fd) { gpg_error_t err; assuan_context_t ctx; conn_ctrl_t ctrl; /* Get an Assuan context for the already accepted file descriptor FD. Allow descriptor passing. */ err = assuan_new (&ctx); if (err) { g_debug ("failed to initialize the new connection: %s", gpg_strerror (err)); return NULL; } err = assuan_init_socket_server (ctx, fd, (ASSUAN_SOCKET_SERVER_FDPASSING | ASSUAN_SOCKET_SERVER_ACCEPTED)); if (err) { g_debug ("failed to initialize the new connection: %s", gpg_strerror (err)); return NULL; } err = register_commands (ctx); if (err) { g_debug ("failed to register commands with Assuan: %s", gpg_strerror (err)); assuan_release (ctx); return NULL; } ctrl = g_malloc0 (sizeof *ctrl); assuan_set_pointer (ctx, ctrl); assuan_set_log_stream (ctx, stderr); assuan_register_reset_notify (ctx, reset_notify); + assuan_register_output_notify (ctx, output_notify); ctrl->message_fd = -1; connection_counter++; return ctx; } /* Finish a connection. This releases all resources and needs to be called becore the file descriptor is closed. */ static void connection_finish (assuan_context_t ctx) { if (ctx) { conn_ctrl_t ctrl = assuan_get_pointer (ctx); reset_notify (ctx, NULL); assuan_release (ctx); g_free (ctrl); connection_counter--; if (!connection_counter && shutdown_pending) gtk_main_quit (); } } /* If the assuan context CTX has a registered continuation function, run it. */ static void run_server_continuation (assuan_context_t ctx, gpg_error_t err) { conn_ctrl_t ctrl = assuan_get_pointer (ctx); void (*cont_cmd) (assuan_context_t, gpg_error_t); if (!ctrl) { g_debug ("no context in gpa_run_server_continuation"); return; } g_debug ("calling gpa_run_server_continuation (%s)", gpg_strerror (err)); if (!ctrl->cont_cmd) { g_debug ("no continuation defined; using default"); assuan_process_done (ctx, err); } else if (ctrl->client_died) { g_debug ("not running continuation as client has disconnected"); connection_finish (ctx); } else { cont_cmd = ctrl->cont_cmd; ctrl->cont_cmd = NULL; cont_cmd (ctx, err); } g_debug ("leaving gpa_run_server_continuation"); } /* This function is called by the main event loop if data can be read from the status channel. */ static gboolean receive_cb (GIOChannel *channel, GIOCondition condition, void *data) { assuan_context_t ctx = data; conn_ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; assert (ctrl); if (condition & G_IO_IN) { g_debug ("receive_cb"); if (ctrl->cont_cmd) { g_debug (" input received while waiting for continuation"); g_usleep (2000000); } else if (ctrl->in_command) { g_debug (" input received while still processing command"); g_usleep (2000000); } else { int done = 0; ctrl->in_command++; err = assuan_process_next (ctx, &done); ctrl->in_command--; if (err) { g_debug ("assuan_process_next returned: %s <%s>", gpg_strerror (err), gpg_strsource (err)); } else { g_debug ("assuan_process_next returned: %s", done ? "done" : "success"); } if (gpg_err_code (err) == GPG_ERR_EAGAIN) ; /* Ignore. */ else if (!err && done) { if (ctrl->cont_cmd) ctrl->client_died = 1; /* Need to delay the cleanup. */ else connection_finish (ctx); return FALSE; /* Remove from the watch. */ } else if (gpg_err_code (err) == GPG_ERR_UNFINISHED) { if (!ctrl->is_unfinished) { /* It is quite possible that some other subsystem returns that error code. Tell the user about this curiosity and finish the command. */ g_debug ("note: Unfinished error code not emitted by us"); if (ctrl->cont_cmd) g_debug ("OOPS: pending continuation!"); assuan_process_done (ctx, err); } } else assuan_process_done (ctx, err); } } return TRUE; } /* This function is called by the main event loop if the listen fd is readable. The function runs the accept and prepares the connection. */ static gboolean accept_connection_cb (GIOChannel *listen_channel, GIOCondition condition, void *data) { gpg_error_t err; int listen_fd, fd; struct sockaddr_un paddr; socklen_t plen = sizeof paddr; assuan_context_t ctx; GIOChannel *channel; unsigned int source_id; g_debug ("new connection request"); #ifdef HAVE_W32_SYSTEM listen_fd = g_io_channel_win32_get_fd (listen_channel); #else listen_fd = g_io_channel_unix_get_fd (listen_channel); #endif fd = accept (listen_fd, (struct sockaddr *)&paddr, &plen); if (fd == -1) { g_debug ("error accepting connection: %s", strerror (errno)); goto leave; } if (assuan_sock_check_nonce ((assuan_fd_t) fd, &socket_nonce)) { g_debug ("new connection at fd %d refused", fd); goto leave; } g_debug ("new connection at fd %d", fd); ctx = connection_startup ((assuan_fd_t) fd); if (!ctx) goto leave; #ifdef HAVE_W32_SYSTEM channel = g_io_channel_win32_new_socket (fd); #else channel = g_io_channel_unix_new (fd); #endif if (!channel) { g_debug ("error creating a channel for fd %d\n", fd); goto leave; } g_io_channel_set_encoding (channel, NULL, NULL); g_io_channel_set_buffered (channel, FALSE); source_id = g_io_add_watch (channel, G_IO_IN, receive_cb, ctx); if (!source_id) { g_debug ("error creating watch for fd %d", fd); g_io_channel_shutdown (channel, 0, NULL); goto leave; } err = assuan_accept (ctx); if (err) { g_debug ("assuan accept failed: %s", gpg_strerror (err)); g_io_channel_shutdown (channel, 0, NULL); goto leave; } g_debug ("connection at fd %d ready", fd); fd = -1; leave: if (fd != -1) assuan_sock_close ((assuan_fd_t) fd); return TRUE; /* Keep the listen_fd in the event loop. */ } /* Startup the server. */ void gpa_start_server (void) { char *socket_name; gpg_error_t err; int rc; assuan_fd_t fd; struct sockaddr_un serv_addr; socklen_t serv_addr_len = sizeof serv_addr; GIOChannel *channel; unsigned int source_id; assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); err = assuan_sock_init (); if (err) { g_debug ("assuan_sock_init failed: %s <%s>", gpg_strerror (err), gpg_strsource (err)); return; } socket_name = g_build_filename (gnupg_homedir, "S.uiserver", NULL); if (strlen (socket_name)+1 >= sizeof serv_addr.sun_path ) { g_debug ("name of socket too long\n"); g_free (socket_name); return; } g_debug ("using server socket `%s'", socket_name); fd = assuan_sock_new (AF_UNIX, SOCK_STREAM, 0); if (fd == ASSUAN_INVALID_FD) { g_debug ("can't create socket: %s\n", strerror(errno)); g_free (socket_name); return; } memset (&serv_addr, 0, sizeof serv_addr); serv_addr.sun_family = AF_UNIX; strcpy (serv_addr.sun_path, socket_name); serv_addr_len = (offsetof (struct sockaddr_un, sun_path) + strlen(serv_addr.sun_path) + 1); rc = assuan_sock_bind (fd, (struct sockaddr*) &serv_addr, serv_addr_len); if (rc == -1 && errno == EADDRINUSE) { remove (socket_name); rc = assuan_sock_bind (fd, (struct sockaddr*) &serv_addr, serv_addr_len); } if (rc != -1 && (rc=assuan_sock_get_nonce ((struct sockaddr*) &serv_addr, serv_addr_len, &socket_nonce))) g_debug ("error getting nonce for the socket"); if (rc == -1) { g_debug ("error binding socket to `%s': %s\n", serv_addr.sun_path, strerror (errno) ); assuan_sock_close (fd); g_free (socket_name); return; } g_free (socket_name); socket_name = NULL; if (listen ((int) fd, 5) == -1) { g_debug ("listen() failed: %s\n", strerror (errno)); assuan_sock_close (fd); return; } #ifdef HAVE_W32_SYSTEM channel = g_io_channel_win32_new_socket ((int) fd); #else channel = g_io_channel_unix_new (fd); #endif if (!channel) { g_debug ("error creating a new listening channel\n"); assuan_sock_close (fd); return; } g_io_channel_set_encoding (channel, NULL, NULL); g_io_channel_set_buffered (channel, FALSE); source_id = g_io_add_watch (channel, G_IO_IN, accept_connection_cb, NULL); if (!source_id) { g_debug ("error creating watch for listening channel\n"); g_io_channel_shutdown (channel, 0, NULL); assuan_sock_close (fd); return; } } /* Set a flag to shutdown the server in a friendly way. */ void gpa_stop_server (void) { shutdown_pending = TRUE; if (!connection_counter) gtk_main_quit (); }