diff options
Diffstat (limited to 'branches/gpgme-0-3-branch/gpgme/engine-gpgsm.c')
-rw-r--r-- | branches/gpgme-0-3-branch/gpgme/engine-gpgsm.c | 1596 |
1 files changed, 1596 insertions, 0 deletions
diff --git a/branches/gpgme-0-3-branch/gpgme/engine-gpgsm.c b/branches/gpgme-0-3-branch/gpgme/engine-gpgsm.c new file mode 100644 index 00000000..6d98a9d5 --- /dev/null +++ b/branches/gpgme-0-3-branch/gpgme/engine-gpgsm.c @@ -0,0 +1,1596 @@ +/* engine-gpgsm.c - GpgSM engine + * Copyright (C) 2000 Werner Koch (dd9jn) + * Copyright (C) 2001, 2002 g10 Code GmbH + * + * This file is part of GPGME. + * + * GPGME 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. + * + * GPGME 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 + */ + +#if HAVE_CONFIG_H +#include <config.h> +#endif + +/* FIXME: Correct check? */ +#ifdef GPGSM_PATH +#define ENABLE_GPGSM 1 +#endif + +#ifdef ENABLE_GPGSM + +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <assert.h> +#include <unistd.h> +#include <locale.h> +#include <fcntl.h> /* FIXME */ + +#include "rungpg.h" +#include "status-table.h" + +#include "gpgme.h" +#include "util.h" +#include "types.h" +#include "ops.h" +#include "wait.h" +#include "io.h" +#include "key.h" +#include "sema.h" + +#include "engine-gpgsm.h" + +#include "assuan.h" + +#define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \ + *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10)) +#define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1)) + + + +typedef struct +{ + int fd; /* FD we talk about. */ + int dir; /* Inbound/Outbound, maybe given implicit? */ + void *data; /* Handler-specific data. */ + void *tag; /* ID from the user for gpgme_remove_io_callback. */ +} iocb_data_t; + +struct gpgsm_object_s +{ + ASSUAN_CONTEXT assuan_ctx; + + iocb_data_t status_cb; + + /* Input, output etc are from the servers perspective. */ + iocb_data_t input_cb; + int input_fd_server; + + iocb_data_t output_cb; + int output_fd_server; + + iocb_data_t message_cb; + int message_fd_server; + + char *command; + + struct + { + GpgStatusHandler fnc; + void *fnc_value; + } status; + + struct + { + GpgColonLineHandler fnc; + void *fnc_value; + struct + { + unsigned char *line; + int linesize; + int linelen; + } attic; + int any; /* any data line seen */ + } colon; + + struct GpgmeIOCbs io_cbs; +}; + + +const char * +_gpgme_gpgsm_get_version (void) +{ + static const char *gpgsm_version; + DEFINE_STATIC_LOCK (gpgsm_version_lock); + + LOCK (gpgsm_version_lock); + if (!gpgsm_version) + gpgsm_version = _gpgme_get_program_version (_gpgme_get_gpgsm_path ()); + UNLOCK (gpgsm_version_lock); + + return gpgsm_version; +} + + +GpgmeError +_gpgme_gpgsm_check_version (void) +{ + return _gpgme_compare_versions (_gpgme_gpgsm_get_version (), + NEED_GPGSM_VERSION) + ? 0 : mk_error (Invalid_Engine); +} + + +static void +close_notify_handler (int fd, void *opaque) +{ + GpgsmObject gpgsm = opaque; + int possibly_done = 0; + + assert (fd != -1); + if (gpgsm->status_cb.fd == fd) + { + if (gpgsm->status_cb.tag) + { + (*gpgsm->io_cbs.remove) (gpgsm->status_cb.tag); + possibly_done = 1; + } + gpgsm->status_cb.fd = -1; + } + else if (gpgsm->input_cb.fd == fd) + { + if (gpgsm->input_cb.tag) + { + (*gpgsm->io_cbs.remove) (gpgsm->input_cb.tag); + possibly_done = 1; + } + gpgsm->input_cb.fd = -1; + } + else if (gpgsm->output_cb.fd == fd) + { + if (gpgsm->output_cb.tag) + { + (*gpgsm->io_cbs.remove) (gpgsm->output_cb.tag); + possibly_done = 1; + } + gpgsm->output_cb.fd = -1; + } + else if (gpgsm->message_cb.fd == fd) + { + if (gpgsm->message_cb.tag) + { + (*gpgsm->io_cbs.remove) (gpgsm->message_cb.tag); + possibly_done = 1; + } + gpgsm->message_cb.fd = -1; + } + if (possibly_done && gpgsm->io_cbs.event + && gpgsm->status_cb.fd == -1 && gpgsm->input_cb.fd == -1 + && gpgsm->output_cb.fd == -1 && gpgsm->message_cb.fd == -1) + (*gpgsm->io_cbs.event) (gpgsm->io_cbs.event_priv, GPGME_EVENT_DONE, NULL); +} + + +static GpgmeError +map_assuan_error (AssuanError err) +{ + switch (err) + { + case ASSUAN_No_Error: + return mk_error (No_Error); + case ASSUAN_General_Error: + return mk_error (General_Error); + case ASSUAN_Out_Of_Core: + return mk_error (Out_Of_Core); + case ASSUAN_Invalid_Value: + return mk_error (Invalid_Value); + case ASSUAN_Read_Error: + return mk_error (Read_Error); + case ASSUAN_Write_Error: + return mk_error (Write_Error); + + case ASSUAN_Timeout: + case ASSUAN_Problem_Starting_Server: + case ASSUAN_Not_A_Server: + case ASSUAN_Not_A_Client: + case ASSUAN_Nested_Commands: + case ASSUAN_Invalid_Response: + case ASSUAN_No_Data_Callback: + case ASSUAN_No_Inquire_Callback: + case ASSUAN_Connect_Failed: + case ASSUAN_Accept_Failed: + return mk_error (General_Error); + + /* The following error codes are meant as status codes. */ + case ASSUAN_Not_Implemented: + return mk_error (Not_Implemented); + case ASSUAN_Canceled: + return mk_error (Canceled); + case ASSUAN_Unsupported_Algorithm: + return mk_error (Not_Implemented); /* XXX Argh. */ + + case ASSUAN_No_Data_Available: + return mk_error (EOF); + + /* These are errors internal to GPGME. */ + case ASSUAN_No_Input: + case ASSUAN_No_Output: + case ASSUAN_Invalid_Command: + case ASSUAN_Unknown_Command: + case ASSUAN_Syntax_Error: + case ASSUAN_Parameter_Error: + case ASSUAN_Parameter_Conflict: + case ASSUAN_Line_Too_Long: + case ASSUAN_Line_Not_Terminated: + case ASSUAN_Invalid_Data: + case ASSUAN_Unexpected_Command: + case ASSUAN_Too_Much_Data: + case ASSUAN_Inquire_Unknown: + case ASSUAN_Inquire_Error: + case ASSUAN_Invalid_Option: + case ASSUAN_Invalid_Index: + case ASSUAN_Unexpected_Status: + case ASSUAN_Unexpected_Data: + case ASSUAN_Invalid_Status: + case ASSUAN_Not_Confirmed: + return mk_error (General_Error); + + /* These are errors in the server. */ + case ASSUAN_Server_Fault: + case ASSUAN_Server_Resource_Problem: + case ASSUAN_Server_IO_Error: + case ASSUAN_Server_Bug: + case ASSUAN_No_Agent: + case ASSUAN_Agent_Error: + return mk_error (Invalid_Engine); /* XXX: Need something more useful. */ + + case ASSUAN_Bad_Certificate: + case ASSUAN_Bad_Certificate_Path: + case ASSUAN_Missing_Certificate: + case ASSUAN_No_Public_Key: + case ASSUAN_No_Secret_Key: + case ASSUAN_Invalid_Name: + case ASSUAN_Card_Error: /* XXX: Oh well. */ + case ASSUAN_Invalid_Card: /* XXX: Oh well. */ + case ASSUAN_No_PKCS15_App: /* XXX: Oh well. */ + case ASSUAN_Card_Not_Present: /* XXX: Oh well. */ + case ASSUAN_Invalid_Id: /* XXX: Oh well. */ + return mk_error (Invalid_Key); + + case ASSUAN_Bad_Signature: + return mk_error (Invalid_Key); /* XXX: This is wrong. */ + + case ASSUAN_Cert_Revoked: + case ASSUAN_No_CRL_For_Cert: + case ASSUAN_CRL_Too_Old: + case ASSUAN_Not_Trusted: + return mk_error (Invalid_Key); /* XXX Some more details would be good. */ + + default: + return mk_error (General_Error); + } +} + + +GpgmeError +_gpgme_gpgsm_new (GpgsmObject *r_gpgsm) +{ + GpgmeError err = 0; + GpgsmObject gpgsm; + char *argv[3]; + int fds[2]; + int child_fds[4]; + char *dft_display = NULL; + char *dft_ttyname = NULL; + char *dft_ttytype = NULL; + char *old_lc = NULL; + char *dft_lc = NULL; + char *optstr; + int fdlist[5]; + int nfds; + + *r_gpgsm = NULL; + gpgsm = xtrycalloc (1, sizeof *gpgsm); + if (!gpgsm) + { + err = mk_error (Out_Of_Core); + return err; + } + + gpgsm->status_cb.fd = -1; + gpgsm->status_cb.tag = 0; + + gpgsm->input_cb.fd = -1; + gpgsm->input_cb.tag = 0; + gpgsm->input_fd_server = -1; + gpgsm->output_cb.fd = -1; + gpgsm->output_cb.tag = 0; + gpgsm->output_fd_server = -1; + gpgsm->message_cb.fd = -1; + gpgsm->message_cb.tag = 0; + gpgsm->message_fd_server = -1; + + gpgsm->status.fnc = 0; + gpgsm->colon.fnc = 0; + gpgsm->colon.attic.line = 0; + gpgsm->colon.attic.linesize = 0; + gpgsm->colon.attic.linelen = 0; + gpgsm->colon.any = 0; + + gpgsm->io_cbs.add = NULL; + gpgsm->io_cbs.add_priv = NULL; + gpgsm->io_cbs.remove = NULL; + gpgsm->io_cbs.event = NULL; + gpgsm->io_cbs.event_priv = NULL; + + if (_gpgme_io_pipe (fds, 0) < 0) + { + err = mk_error (Pipe_Error); + goto leave; + } + gpgsm->input_cb.fd = fds[1]; + gpgsm->input_cb.dir = 0; + gpgsm->input_fd_server = fds[0]; + + if (_gpgme_io_pipe (fds, 1) < 0) + { + err = mk_error (Pipe_Error); + goto leave; + } + gpgsm->output_cb.fd = fds[0]; + gpgsm->output_cb.dir = 1; + gpgsm->output_fd_server = fds[1]; + + if (_gpgme_io_pipe (fds, 0) < 0) + { + err = mk_error (Pipe_Error); + goto leave; + } + gpgsm->message_cb.fd = fds[1]; + gpgsm->message_cb.dir = 0; + gpgsm->message_fd_server = fds[0]; + + child_fds[0] = gpgsm->input_fd_server; + child_fds[1] = gpgsm->output_fd_server; + child_fds[2] = gpgsm->message_fd_server; + child_fds[3] = -1; + + argv[0] = "gpgsm"; + argv[1] = "--server"; + argv[2] = NULL; + + err = assuan_pipe_connect2 (&gpgsm->assuan_ctx, + _gpgme_get_gpgsm_path (), argv, child_fds, + 1 /* dup stderr to /dev/null */); + + /* We need to know the fd used by assuan for reads. We do this by + using the assumption that the first returned fd from + assuan_get_active_fds() is always this one. */ + nfds = assuan_get_active_fds (gpgsm->assuan_ctx, 0 /* read fds */, + fdlist, DIM (fdlist)); + if (nfds < 1) + { + err = mk_error (General_Error); /* FIXME */ + goto leave; + } + /* We duplicate the file descriptor, so we can close it without + disturbing assuan. Alternatively, we could special case + status_fd and register/unregister it manually as needed, but this + increases code duplication and is more complicated as we can not + use the close notifications etc. */ + gpgsm->status_cb.fd = dup (fdlist[0]); + if (gpgsm->status_cb.fd < 0) + { + err = mk_error (General_Error); /* FIXME */ + goto leave; + } + gpgsm->status_cb.dir = 1; + gpgsm->status_cb.data = gpgsm; + + dft_display = getenv ("DISPLAY"); + if (dft_display) + { + if (asprintf (&optstr, "OPTION display=%s", dft_display) < 0) + { + err = mk_error (Out_Of_Core); + goto leave; + } + err = assuan_transact (gpgsm->assuan_ctx, optstr, NULL, NULL, NULL, + NULL, NULL, NULL); + free (optstr); + if (err) + { + err = map_assuan_error (err); + goto leave; + } + } + dft_ttyname = ttyname (1); + if (dft_ttyname) + { + if (asprintf (&optstr, "OPTION ttyname=%s", dft_ttyname) < 0) + { + err = mk_error (Out_Of_Core); + goto leave; + } + err = assuan_transact (gpgsm->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL, + NULL); + free (optstr); + if (err) + { + err = map_assuan_error (err); + goto leave; + } + + dft_ttytype = getenv ("TERM"); + if (dft_ttytype) + { + if (asprintf (&optstr, "OPTION ttytype=%s", dft_ttytype) < 0) + { + err = mk_error (Out_Of_Core); + goto leave; + } + err = assuan_transact (gpgsm->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL, + NULL); + free (optstr); + if (err) + { + err = map_assuan_error (err); + goto leave; + } + } + old_lc = setlocale (LC_CTYPE, NULL); + dft_lc = setlocale (LC_CTYPE, ""); + if (dft_lc) + { + if (asprintf (&optstr, "OPTION lc-ctype=%s", dft_lc) < 0) + err = mk_error (Out_Of_Core); + else + { + err = assuan_transact (gpgsm->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL, + NULL); + free (optstr); + if (err) + err = map_assuan_error (err); + } + } + if (old_lc) + setlocale (LC_CTYPE, old_lc); + if (err) + goto leave; + + old_lc = setlocale (LC_MESSAGES, NULL); + dft_lc = setlocale (LC_MESSAGES, ""); + if (dft_lc) + { + if (asprintf (&optstr, "OPTION lc-messages=%s", dft_lc) < 0) + err = mk_error (Out_Of_Core); + else + { + err = assuan_transact (gpgsm->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL, + NULL); + free (optstr); + if (err) + err = map_assuan_error (err); + } + } + if (old_lc) + setlocale (LC_MESSAGES, old_lc); + if (err) + goto leave; + } + + if (!err && + (_gpgme_io_set_close_notify (gpgsm->status_cb.fd, + close_notify_handler, gpgsm) + || _gpgme_io_set_close_notify (gpgsm->input_cb.fd, + close_notify_handler, gpgsm) + || _gpgme_io_set_close_notify (gpgsm->output_cb.fd, + close_notify_handler, gpgsm) + || _gpgme_io_set_close_notify (gpgsm->message_cb.fd, + close_notify_handler, gpgsm))) + { + err = mk_error (General_Error); + goto leave; + } + + leave: + /* Close the server ends of the pipes. Our ends are closed in + _gpgme_gpgsm_release. */ + if (gpgsm->input_fd_server != -1) + _gpgme_io_close (gpgsm->input_fd_server); + if (gpgsm->output_fd_server != -1) + _gpgme_io_close (gpgsm->output_fd_server); + if (gpgsm->message_fd_server != -1) + _gpgme_io_close (gpgsm->message_fd_server); + + if (err) + _gpgme_gpgsm_release (gpgsm); + else + *r_gpgsm = gpgsm; + + return err; +} + + +void +_gpgme_gpgsm_release (GpgsmObject gpgsm) +{ + if (!gpgsm) + return; + + if (gpgsm->status_cb.fd != -1) + _gpgme_io_close (gpgsm->status_cb.fd); + if (gpgsm->input_cb.fd != -1) + _gpgme_io_close (gpgsm->input_cb.fd); + if (gpgsm->output_cb.fd != -1) + _gpgme_io_close (gpgsm->output_cb.fd); + if (gpgsm->message_cb.fd != -1) + _gpgme_io_close (gpgsm->message_cb.fd); + + assuan_disconnect (gpgsm->assuan_ctx); + + xfree (gpgsm->colon.attic.line); + xfree (gpgsm->command); + xfree (gpgsm); +} + +/* Forward declaration. */ +static GpgmeStatusCode parse_status (const char *name); + +static GpgmeError +gpgsm_assuan_simple_command (ASSUAN_CONTEXT ctx, char *cmd, GpgStatusHandler status_fnc, + void *status_fnc_value) +{ + AssuanError err; + char *line; + size_t linelen; + + err = assuan_write_line (ctx, cmd); + if (err) + return map_assuan_error (err); + + do + { + err = assuan_read_line (ctx, &line, &linelen); + if (err) + return map_assuan_error (err); + + if (*line == '#' || !linelen) + continue; + + if (linelen >= 2 + && line[0] == 'O' && line[1] == 'K' + && (line[2] == '\0' || line[2] == ' ')) + return 0; + else if (linelen >= 4 + && line[0] == 'E' && line[1] == 'R' && line[2] == 'R' + && line[3] == ' ') + err = map_assuan_error (atoi (&line[4])); + else if (linelen >= 2 + && line[0] == 'S' && line[1] == ' ') + { + char *rest; + GpgmeStatusCode r; + + rest = strchr (line + 2, ' '); + if (!rest) + rest = line + linelen; /* set to an empty string */ + else + *(rest++) = 0; + + r = parse_status (line + 2); + + if (r >= 0 && status_fnc) + status_fnc (status_fnc_value, r, rest); + else + err = mk_error (General_Error); + } + else + err = mk_error (General_Error); + } + while (!err); + + return err; +} + + +#define COMMANDLINELEN 40 +static GpgmeError +gpgsm_set_fd (ASSUAN_CONTEXT ctx, const char *which, int fd, const char *opt) +{ + char line[COMMANDLINELEN]; + + if (opt) + snprintf (line, COMMANDLINELEN, "%s FD=%i %s", which, fd, opt); + else + snprintf (line, COMMANDLINELEN, "%s FD=%i", which, fd); + + return gpgsm_assuan_simple_command (ctx, line, NULL, NULL); +} + + +static const char * +map_input_enc (GpgmeData d) +{ + switch (gpgme_data_get_encoding (d)) + { + case GPGME_DATA_ENCODING_NONE: + break; + case GPGME_DATA_ENCODING_BINARY: + return "--binary"; + case GPGME_DATA_ENCODING_BASE64: + return "--base64"; + case GPGME_DATA_ENCODING_ARMOR: + return "--armor"; + default: + break; + } + return NULL; +} + + +GpgmeError +_gpgme_gpgsm_op_decrypt (GpgsmObject gpgsm, GpgmeData ciph, GpgmeData plain) +{ + GpgmeError err; + + if (!gpgsm) + return mk_error (Invalid_Value); + + gpgsm->command = xtrystrdup ("DECRYPT"); + if (!gpgsm->command) + return mk_error (Out_Of_Core); + + gpgsm->input_cb.data = ciph; + err = gpgsm_set_fd (gpgsm->assuan_ctx, "INPUT", gpgsm->input_fd_server, + map_input_enc (gpgsm->input_cb.data)); + if (err) + return mk_error (General_Error); /* FIXME */ + gpgsm->output_cb.data = plain; + err = gpgsm_set_fd (gpgsm->assuan_ctx, "OUTPUT", gpgsm->output_fd_server, 0); + if (err) + return mk_error (General_Error); /* FIXME */ + _gpgme_io_close (gpgsm->message_cb.fd); + + return 0; +} + + +GpgmeError +_gpgme_gpgsm_op_delete (GpgsmObject gpgsm, GpgmeKey key, int allow_secret) +{ + char *fpr = (char *) gpgme_key_get_string_attr (key, GPGME_ATTR_FPR, NULL, 0); + char *linep = fpr; + char *line; + int length = 8; /* "DELKEYS " */ + + if (!fpr) + return mk_error (Invalid_Key); + + while (*linep) + { + length++; + if (*linep == '%' || *linep == ' ' || *linep == '+') + length += 2; + linep++; + } + length++; + + line = xtrymalloc (length); + if (!line) + return mk_error (Out_Of_Core); + + strcpy (line, "DELKEYS "); + linep = &line[8]; + + while (*fpr) + { + switch (*fpr) + { + case '%': + *(linep++) = '%'; + *(linep++) = '2'; + *(linep++) = '5'; + break; + case ' ': + *(linep++) = '%'; + *(linep++) = '2'; + *(linep++) = '0'; + break; + case '+': + *(linep++) = '%'; + *(linep++) = '2'; + *(linep++) = 'B'; + break; + default: + *(linep++) = *fpr; + break; + } + fpr++; + } + *linep = '\0'; + + gpgsm->command = line; + _gpgme_io_close (gpgsm->output_cb.fd); + _gpgme_io_close (gpgsm->input_cb.fd); + _gpgme_io_close (gpgsm->message_cb.fd); + + return 0; +} + + +static GpgmeError +gpgsm_set_recipients (GpgsmObject gpgsm, GpgmeRecipients recp) +{ + GpgmeError err; + ASSUAN_CONTEXT ctx = gpgsm->assuan_ctx; + char *line; + int linelen; + struct user_id_s *r; + int valid_recipients = 0; + + linelen = 10 + 40 + 1; /* "RECIPIENT " + guess + '\0'. */ + line = xtrymalloc (10 + 40 + 1); + if (!line) + return mk_error (Out_Of_Core); + strcpy (line, "RECIPIENT "); + for (r = recp->list; r; r = r->next) + { + int newlen = 11 + strlen (r->name); + if (linelen < newlen) + { + char *newline = xtryrealloc (line, newlen); + if (! newline) + { + xfree (line); + return mk_error (Out_Of_Core); + } + line = newline; + linelen = newlen; + } + strcpy (&line[10], r->name); + + err = gpgsm_assuan_simple_command (ctx, line, gpgsm->status.fnc, + gpgsm->status.fnc_value); + if (!err) + valid_recipients = 1; + else if (err != GPGME_Invalid_Key) + { + xfree (line); + return err; + } + } + xfree (line); + if (!valid_recipients && gpgsm->status.fnc) + gpgsm->status.fnc (gpgsm->status.fnc_value, GPGME_STATUS_NO_RECP, ""); + return 0; +} + + +GpgmeError +_gpgme_gpgsm_op_encrypt (GpgsmObject gpgsm, GpgmeRecipients recp, + GpgmeData plain, GpgmeData ciph, int use_armor) +{ + GpgmeError err; + + if (!gpgsm) + return mk_error (Invalid_Value); + if (!recp) + return mk_error (Not_Implemented); + + gpgsm->command = xtrystrdup ("ENCRYPT"); + if (!gpgsm->command) + return mk_error (Out_Of_Core); + + gpgsm->input_cb.data = plain; + err = gpgsm_set_fd (gpgsm->assuan_ctx, "INPUT", gpgsm->input_fd_server, + map_input_enc (gpgsm->input_cb.data)); + if (err) + return err; + gpgsm->output_cb.data = ciph; + err = gpgsm_set_fd (gpgsm->assuan_ctx, "OUTPUT", gpgsm->output_fd_server, + use_armor ? "--armor" : 0); + if (err) + return err; + _gpgme_io_close (gpgsm->message_cb.fd); + + err = gpgsm_set_recipients (gpgsm, recp); + if (err) + return err; + + return 0; +} + + +GpgmeError +_gpgme_gpgsm_op_export (GpgsmObject gpgsm, GpgmeRecipients recp, + GpgmeData keydata, int use_armor) +{ + GpgmeError err = 0; + char *cmd = NULL; + int cmdi; + int cmdlen = 32; + + if (!gpgsm) + return mk_error (Invalid_Value); + + cmd = malloc (cmdlen); + if (!cmd) + return mk_error (Out_Of_Core); + strcpy (cmd, "EXPORT"); + cmdi = 6; + + if (recp) + { + void *ec; + const char *s; + + err = gpgme_recipients_enum_open (recp, &ec); + while (!err && (s = gpgme_recipients_enum_read (recp, &ec))) + { + int slen = strlen (s); + /* New string is old string + ' ' + s + '\0'. */ + if (cmdlen < cmdi + 1 + slen + 1) + { + char *newcmd = xtryrealloc (cmd, cmdlen * 2); + if (!newcmd) + { + xfree (cmd); + return mk_error (Out_Of_Core); + } + cmd = newcmd; + cmdlen *= 2; + } + cmd[cmdi++] = ' '; + strcpy (cmd + cmdi, s); + cmdi += slen; + } + if (!err) + err = gpgme_recipients_enum_close (recp, &ec); + if (err) + return err; + } + + gpgsm->command = cmd; + + gpgsm->output_cb.data = keydata; + err = gpgsm_set_fd (gpgsm->assuan_ctx, "OUTPUT", gpgsm->output_fd_server, + use_armor ? "--armor" : 0); + if (err) + return err; + _gpgme_io_close (gpgsm->input_cb.fd); + _gpgme_io_close (gpgsm->message_cb.fd); + + return 0; +} + + +GpgmeError +_gpgme_gpgsm_op_genkey (GpgsmObject gpgsm, GpgmeData help_data, int use_armor, + GpgmeData pubkey, GpgmeData seckey) +{ + GpgmeError err; + + if (!gpgsm || !pubkey || seckey) + return mk_error (Invalid_Value); + + gpgsm->command = xtrystrdup ("GENKEY"); + if (!gpgsm->command) + return mk_error (Out_Of_Core); + + gpgsm->input_cb.data = help_data; + err = gpgsm_set_fd (gpgsm->assuan_ctx, "INPUT", gpgsm->input_fd_server, + map_input_enc (gpgsm->input_cb.data)); + if (err) + return err; + gpgsm->output_cb.data = pubkey; + err = gpgsm_set_fd (gpgsm->assuan_ctx, "OUTPUT", gpgsm->output_fd_server, + use_armor ? "--armor" : 0); + if (err) + return err; + _gpgme_io_close (gpgsm->message_cb.fd); + + return 0; +} + + +GpgmeError +_gpgme_gpgsm_op_import (GpgsmObject gpgsm, GpgmeData keydata) +{ + GpgmeError err; + + if (!gpgsm) + return mk_error (Invalid_Value); + + gpgsm->command = xtrystrdup ("IMPORT"); + if (!gpgsm->command) + return mk_error (Out_Of_Core); + + gpgsm->input_cb.data = keydata; + err = gpgsm_set_fd (gpgsm->assuan_ctx, "INPUT", gpgsm->input_fd_server, + map_input_enc (gpgsm->input_cb.data)); + if (err) + return err; + _gpgme_io_close (gpgsm->output_cb.fd); + _gpgme_io_close (gpgsm->message_cb.fd); + + return 0; +} + + +GpgmeError +_gpgme_gpgsm_op_keylist (GpgsmObject gpgsm, const char *pattern, + int secret_only, int keylist_mode) +{ + char *line; + GpgmeError err; + + if (!pattern) + pattern = ""; + + if (asprintf (&line, "OPTION list-mode=%d", (keylist_mode & 3)) < 0) + return mk_error (Out_Of_Core); + err = gpgsm_assuan_simple_command (gpgsm->assuan_ctx, line, NULL, NULL); + free (line); + if (err) + return err; + + /* Length is "LISTSECRETKEYS " + p + '\0'. */ + line = xtrymalloc (15 + strlen (pattern) + 1); + if (!line) + return mk_error (Out_Of_Core); + if (secret_only) + { + strcpy (line, "LISTSECRETKEYS "); + strcpy (&line[15], pattern); + } + else + { + strcpy (line, "LISTKEYS "); + strcpy (&line[9], pattern); + } + + _gpgme_io_close (gpgsm->input_cb.fd); + _gpgme_io_close (gpgsm->output_cb.fd); + _gpgme_io_close (gpgsm->message_cb.fd); + + gpgsm->command = line; + return 0; +} + + +GpgmeError +_gpgme_gpgsm_op_keylist_ext (GpgsmObject gpgsm, const char *pattern[], + int secret_only, int reserved, int keylist_mode) +{ + char *line; + GpgmeError err; + /* Length is "LISTSECRETKEYS " + p + '\0'. */ + int length = 15 + 1; + char *linep; + + if (reserved) + return mk_error (Invalid_Value); + + if (asprintf (&line, "OPTION list-mode=%d", (keylist_mode & 3)) < 0) + return mk_error (Out_Of_Core); + err = gpgsm_assuan_simple_command (gpgsm->assuan_ctx, line, NULL, NULL); + free (line); + if (err) + return err; + + if (pattern && *pattern) + { + const char **pat = pattern; + + while (*pat) + { + const char *patlet = *pat; + + while (*patlet) + { + length++; + if (*patlet == '%' || *patlet == ' ' || *patlet == '+') + length += 2; + patlet++; + } + pat++; + /* This will allocate one byte more than necessary. */ + length++; + } + } + line = xtrymalloc (length); + if (!line) + return mk_error (Out_Of_Core); + if (secret_only) + { + strcpy (line, "LISTSECRETKEYS "); + linep = &line[15]; + } + else + { + strcpy (line, "LISTKEYS "); + linep = &line[9]; + } + + if (pattern && *pattern) + { + while (*pattern) + { + const char *patlet = *pattern; + + while (*patlet) + { + switch (*patlet) + { + case '%': + *(linep++) = '%'; + *(linep++) = '2'; + *(linep++) = '5'; + break; + case ' ': + *(linep++) = '%'; + *(linep++) = '2'; + *(linep++) = '0'; + break; + case '+': + *(linep++) = '%'; + *(linep++) = '2'; + *(linep++) = 'B'; + break; + default: + *(linep++) = *patlet; + break; + } + patlet++; + } + pattern++; + } + } + *linep = '\0'; + + _gpgme_io_close (gpgsm->input_cb.fd); + _gpgme_io_close (gpgsm->output_cb.fd); + _gpgme_io_close (gpgsm->message_cb.fd); + + gpgsm->command = line; + return 0; +} + + +GpgmeError +_gpgme_gpgsm_op_sign (GpgsmObject gpgsm, GpgmeData in, GpgmeData out, + GpgmeSigMode mode, int use_armor, + int use_textmode, int include_certs, + GpgmeCtx ctx /* FIXME */) +{ + GpgmeError err; + char *assuan_cmd; + int i; + GpgmeKey key; + + if (!gpgsm) + return mk_error (Invalid_Value); + + gpgsm->command = xtrystrdup (mode == GPGME_SIG_MODE_DETACH + ? "SIGN --detached" : "SIGN"); + if (!gpgsm->command) + return mk_error (Out_Of_Core); + + if (asprintf (&assuan_cmd, "OPTION include-certs %i", include_certs) < 0) + return mk_error (Out_Of_Core); + err = gpgsm_assuan_simple_command (gpgsm->assuan_ctx, assuan_cmd, NULL,NULL); + free (assuan_cmd); + if (err) + return err; + + /* We must do a reset becuase we need to reset the list of signers. Note + that RESET does not reset OPTION commands. */ + err = gpgsm_assuan_simple_command (gpgsm->assuan_ctx, "RESET", NULL, NULL); + if (err) + return err; + + for (i = 0; (key = gpgme_signers_enum (ctx, i)); i++) + { + const char *s = gpgme_key_get_string_attr (key, GPGME_ATTR_FPR, + NULL, 0); + if (s && strlen (s) < 80) + { + char buf[100]; + + strcpy (stpcpy (buf, "SIGNER "), s); + err = gpgsm_assuan_simple_command (gpgsm->assuan_ctx, buf, + NULL, NULL); + } + else + err = GPGME_Invalid_Key; + gpgme_key_unref (key); + if (err) + return err; + } + + gpgsm->input_cb.data = in; + err = gpgsm_set_fd (gpgsm->assuan_ctx, "INPUT", gpgsm->input_fd_server, + map_input_enc (gpgsm->input_cb.data)); + if (err) + return err; + gpgsm->output_cb.data = out; + err = gpgsm_set_fd (gpgsm->assuan_ctx, "OUTPUT", gpgsm->output_fd_server, + use_armor ? "--armor" : 0); + if (err) + return err; + _gpgme_io_close (gpgsm->message_cb.fd); + + return 0; +} + + +GpgmeError +_gpgme_gpgsm_op_trustlist (GpgsmObject gpgsm, const char *pattern) +{ + /* FIXME */ + return mk_error (Not_Implemented); +} + + +GpgmeError +_gpgme_gpgsm_op_verify (GpgsmObject gpgsm, GpgmeData sig, GpgmeData text) +{ + GpgmeError err; + + if (!gpgsm) + return mk_error (Invalid_Value); + + gpgsm->command = xtrystrdup ("VERIFY"); + if (!gpgsm->command) + return mk_error (Out_Of_Core); + + gpgsm->input_cb.data = sig; + err = gpgsm_set_fd (gpgsm->assuan_ctx, "INPUT", gpgsm->input_fd_server, + map_input_enc (gpgsm->input_cb.data)); + if (err) + return err; + if (_gpgme_data_get_mode (text) == GPGME_DATA_MODE_IN) + { + /* Normal or cleartext signature. */ + gpgsm->output_cb.data = text; + err = gpgsm_set_fd (gpgsm->assuan_ctx, "OUTPUT", gpgsm->output_fd_server, + 0); + _gpgme_io_close (gpgsm->message_cb.fd); + } + else + { + /* Detached signature. */ + gpgsm->message_cb.data = text; + err = gpgsm_set_fd (gpgsm->assuan_ctx, "MESSAGE", + gpgsm->message_fd_server, 0); + _gpgme_io_close (gpgsm->output_cb.fd); + } + if (err) + return err; + + return 0; +} + + +static int +status_cmp (const void *ap, const void *bp) +{ + const struct status_table_s *a = ap; + const struct status_table_s *b = bp; + + return strcmp (a->name, b->name); +} + + +static GpgmeStatusCode +parse_status (const char *name) +{ + struct status_table_s t, *r; + t.name = name; + r = bsearch (&t, status_table, DIM(status_table) - 1, + sizeof t, status_cmp); + return r ? r->code : -1; +} + + +static void +gpgsm_status_handler (void *opaque, int fd) +{ + AssuanError err; + GpgsmObject gpgsm = opaque; + char *line; + size_t linelen; + + do + { + err = assuan_read_line (gpgsm->assuan_ctx, &line, &linelen); + + if (err + || (linelen >= 2 + && line[0] == 'O' && line[1] == 'K' + && (line[2] == '\0' || line[2] == ' ')) + || (linelen >= 3 + && line[0] == 'E' && line[1] == 'R' && line[2] == 'R' + && (line[3] == '\0' || line[3] == ' '))) + { + /* XXX: If an error occured, find out what happened, then + save the error value before running the status handler + (so it takes precedence). */ + if (!err && line[0] == 'E' && line[3] == ' ') + { + err = map_assuan_error (atoi (&line[4])); + if (!err) + err = mk_error (General_Error); + } + if (err) + { + /* XXX Kludge ahead. We really, really, really must not + make use of status.fnc_value. */ + GpgmeCtx ctx = (GpgmeCtx) gpgsm->status.fnc_value; + if (!ctx->error) + ctx->error = err; + } + + if (gpgsm->status.fnc) + gpgsm->status.fnc (gpgsm->status.fnc_value, GPGME_STATUS_EOF, ""); + if (gpgsm->colon.fnc && gpgsm->colon.any ) + { + /* We must tell a colon fucntion about the EOF. We do + this only when we have seen any data lines. Note + that this inlined use of colon data lines will + eventually be changed into using a regular data + channel. */ + gpgsm->colon.any = 0; + gpgsm->colon.fnc (gpgsm->colon.fnc_value, NULL); + } + + /* XXX: Try our best to terminate the connection. */ + if (err) + assuan_write_line (gpgsm->assuan_ctx, "BYE"); + + _gpgme_io_close (gpgsm->status_cb.fd); + return; + } + + if (linelen > 2 + && line[0] == 'D' && line[1] == ' ' + && gpgsm->colon.fnc) + { + /* We are using the colon handler even for plain inline data + - strange name for that function but for historic reasons + we keep it. */ + /* FIXME We can't use this for binary data because we + assume this is a string. For the current usage of colon + output it is correct. */ + unsigned char *src = line + 2; + unsigned char *end = line + linelen; + unsigned char *dst; + unsigned char **aline = &gpgsm->colon.attic.line; + int *alinelen = &gpgsm->colon.attic.linelen; + + if (gpgsm->colon.attic.linesize + < *alinelen + linelen + 1) + { + unsigned char *newline = xtryrealloc (*aline, + *alinelen + linelen + 1); + if (!newline) + { + _gpgme_io_close (gpgsm->status_cb.fd); + return; + } + *aline = newline; + gpgsm->colon.attic.linesize += linelen + 1; + } + + dst = *aline + *alinelen; + + while (src < end) + { + if (*src == '%' && src + 2 < end) + { + /* Handle escaped characters. */ + ++src; + *dst = xtoi_2 (src); + (*alinelen)++; + src += 2; + } + else + { + *dst = *src++; + (*alinelen)++; + } + + if (*dst == '\n') + { + /* Terminate the pending line, pass it to the colon + handler and reset it. */ + + gpgsm->colon.any = 1; + if (*alinelen > 1 && *(dst - 1) == '\r') + dst--; + *dst = '\0'; + + /* FIXME How should we handle the return code? */ + gpgsm->colon.fnc (gpgsm->colon.fnc_value, *aline); + dst = *aline; + *alinelen = 0; + } + else + dst++; + } + } + else if (linelen > 2 + && line[0] == 'S' && line[1] == ' ') + { + char *rest; + GpgmeStatusCode r; + + rest = strchr (line + 2, ' '); + if (!rest) + rest = line + linelen; /* set to an empty string */ + else + *(rest++) = 0; + + r = parse_status (line + 2); + + if (r >= 0) + { + if (gpgsm->status.fnc) + gpgsm->status.fnc (gpgsm->status.fnc_value, r, rest); + } + else + fprintf (stderr, "[UNKNOWN STATUS]%s %s", line + 2, rest); + } + } + while (assuan_pending_line (gpgsm->assuan_ctx)); +} + + +void +_gpgme_gpgsm_set_status_handler (GpgsmObject gpgsm, + GpgStatusHandler fnc, void *fnc_value) +{ + assert (gpgsm); + + gpgsm->status.fnc = fnc; + gpgsm->status.fnc_value = fnc_value; +} + + +void +_gpgme_gpgsm_set_colon_line_handler (GpgsmObject gpgsm, + GpgColonLineHandler fnc, void *fnc_value) +{ + assert (gpgsm); + + gpgsm->colon.fnc = fnc; + gpgsm->colon.fnc_value = fnc_value; + gpgsm->colon.any = 0; +} + + +static GpgmeError +_gpgme_gpgsm_add_io_cb (GpgsmObject gpgsm, iocb_data_t *iocbd, + GpgmeIOCb handler) +{ + GpgmeError err; + + err = (*gpgsm->io_cbs.add) (gpgsm->io_cbs.add_priv, + iocbd->fd, iocbd->dir, + handler, iocbd->data, &iocbd->tag); + if (err) + return err; + if (!iocbd->dir) + /* FIXME Kludge around poll() problem. */ + err = _gpgme_io_set_nonblocking (iocbd->fd); + return err; +} + +GpgmeError +_gpgme_gpgsm_start (GpgsmObject gpgsm, void *opaque) +{ + GpgmeError err = 0; + pid_t pid; + + if (!gpgsm) + return mk_error (Invalid_Value); + + pid = assuan_get_pid (gpgsm->assuan_ctx); + + err = _gpgme_gpgsm_add_io_cb (gpgsm, &gpgsm->status_cb, + gpgsm_status_handler); + if (gpgsm->input_cb.fd != -1) + err = _gpgme_gpgsm_add_io_cb (gpgsm, &gpgsm->input_cb, + _gpgme_data_outbound_handler); + if (!err && gpgsm->output_cb.fd != -1) + err = _gpgme_gpgsm_add_io_cb (gpgsm, &gpgsm->output_cb, + _gpgme_data_inbound_handler); + if (!err && gpgsm->message_cb.fd != -1) + err = _gpgme_gpgsm_add_io_cb (gpgsm, &gpgsm->message_cb, + _gpgme_data_outbound_handler); + + if (!err) + err = assuan_write_line (gpgsm->assuan_ctx, gpgsm->command); + + return err; +} + +void +_gpgme_gpgsm_set_io_cbs (GpgsmObject gpgsm, struct GpgmeIOCbs *io_cbs) +{ + gpgsm->io_cbs = *io_cbs; +} + +void +_gpgme_gpgsm_io_event (GpgsmObject gpgsm, GpgmeEventIO type, void *type_data) +{ + if (gpgsm->io_cbs.event) + (*gpgsm->io_cbs.event) (gpgsm->io_cbs.event_priv, type, type_data); +} + +#else /* ENABLE_GPGSM */ + + +#include <stddef.h> +#include "util.h" + +#include "engine-gpgsm.h" + + +const char * +_gpgme_gpgsm_get_version (void) +{ + return NULL; +} + + +GpgmeError +_gpgme_gpgsm_check_version (void) +{ + return mk_error (Invalid_Engine); +} + + +GpgmeError +_gpgme_gpgsm_new (GpgsmObject *r_gpgsm) +{ + return mk_error (Invalid_Engine); +} + + +void +_gpgme_gpgsm_release (GpgsmObject gpgsm) +{ + return; +} + + +void +_gpgme_gpgsm_set_status_handler (GpgsmObject gpgsm, + GpgStatusHandler fnc, void *fnc_value) +{ + return; +} + + +GpgmeError +_gpgme_gpgsm_op_decrypt (GpgsmObject gpgsm, GpgmeData ciph, GpgmeData plain) +{ + return mk_error (Invalid_Engine); +} + + +GpgmeError +_gpgme_gpgsm_op_delete (GpgsmObject gpgsm, GpgmeKey key, int allow_secret) +{ + return mk_error (Invalid_Engine); +} + + +GpgmeError +_gpgme_gpgsm_op_encrypt (GpgsmObject gpgsm, GpgmeRecipients recp, + GpgmeData plain, GpgmeData ciph, int use_armor) +{ + return mk_error (Invalid_Engine); +} + + +GpgmeError +_gpgme_gpgsm_op_export (GpgsmObject gpgsm, GpgmeRecipients recp, + GpgmeData keydata, int use_armor) +{ + return mk_error (Invalid_Engine); +} + + +GpgmeError +_gpgme_gpgsm_op_genkey (GpgsmObject gpgsm, GpgmeData help_data, int use_armor, + GpgmeData pubkey, GpgmeData seckey) +{ + return mk_error (Invalid_Engine); +} + + +GpgmeError +_gpgme_gpgsm_op_import (GpgsmObject gpgsm, GpgmeData keydata) +{ + return mk_error (Invalid_Engine); +} + + +GpgmeError +_gpgme_gpgsm_op_keylist (GpgsmObject gpgsm, const char *pattern, + int secret_only, int keylist_mode) +{ + return mk_error (Invalid_Engine); +} + + +GpgmeError +_gpgme_gpgsm_op_keylist_ext (GpgsmObject gpgsm, const char *pattern[], + int secret_only, int reserved, int keylist_mode) +{ + return mk_error (Invalid_Engine); +} + +GpgmeError +_gpgme_gpgsm_op_sign (GpgsmObject gpgsm, GpgmeData in, GpgmeData out, + GpgmeSigMode mode, int use_armor, + int use_textmode, int include_certs, + GpgmeCtx ctx /* FIXME */) +{ + return mk_error (Invalid_Engine); +} + + +GpgmeError +_gpgme_gpgsm_op_trustlist (GpgsmObject gpgsm, const char *pattern) +{ + return mk_error (Invalid_Engine); +} + + +GpgmeError +_gpgme_gpgsm_op_verify (GpgsmObject gpgsm, GpgmeData sig, GpgmeData text) +{ + return mk_error (Invalid_Engine); +} + + +void +_gpgme_gpgsm_set_colon_line_handler (GpgsmObject gpgsm, + GpgColonLineHandler fnc, void *fnc_value) +{ +} + + +GpgmeError +_gpgme_gpgsm_start (GpgsmObject gpgsm, void *opaque) +{ + return mk_error (Invalid_Engine); +} + +void +_gpgme_gpgsm_set_io_cbs (GpgsmObject gpgsm, struct GpgmeIOCbs *io_cbs) +{ +} + +void +_gpgme_gpgsm_io_event (GpgsmObject gpgsm, GpgmeEventIO type, void *type_data) +{ +} + +#endif /* ! ENABLE_GPGSM */ |