diff options
author | Marcus Brinkmann <[email protected]> | 2009-10-22 16:44:07 +0000 |
---|---|---|
committer | Marcus Brinkmann <[email protected]> | 2009-10-22 16:44:07 +0000 |
commit | a6f3857128220cc413434d5fb56cdd7f9a890c4c (patch) | |
tree | 50478606bd9a1f87a61e8df224f8c4260ad2e5a2 /src/engine-g13.c | |
parent | Really add file. (diff) | |
download | gpgme-a6f3857128220cc413434d5fb56cdd7f9a890c4c.tar.gz gpgme-a6f3857128220cc413434d5fb56cdd7f9a890c4c.zip |
2009-10-22 Marcus Brinkmann <[email protected]>
* configure.ac: Add support for G13.
src/
2009-10-22 Marcus Brinkmann <[email protected]>
* Makefile.am: Remove @NETLIBS@ from LIBADDs.
(g13_components): New variable.
(main_sources): Add $(g13_components).
* g13.c, engine-g13.c: New files.
* engine.c (engine_ops): Check for assuan for assuan engine, add
g13 engine.
* util.h (_gpgme_get_g13_path, _gpgme_encode_percent_string): New
prototypes.
* conversion.c (_gpgme_encode_percent_string): New function.
* gpgme.h.in (gpgme_protocol_t): Add GPGME_PROTOCOL_G13.
(struct _gpgme_op_g13_result, gpgme_g13_result_t): New types.
(gpgme_op_g13_mount): New function.
* gpgme.def, libgpgme.vers: Add gpgme_op_g13_mount.
* gpgme.c (gpgme_set_protocol): Allow GPGME_PROTOCOL_G13.
(gpgme_get_protocol_name): Add GPGME_PROTOCOL_G13.
* posix-util.c (_gpgme_get_g13_path): New function.
* w32-util.c (_gpgme_get_g13_path): New function.
* engine-backend.h (_gpgme_engine_ops_g13): New declaration.
Diffstat (limited to 'src/engine-g13.c')
-rw-r--r-- | src/engine-g13.c | 796 |
1 files changed, 796 insertions, 0 deletions
diff --git a/src/engine-g13.c b/src/engine-g13.c new file mode 100644 index 00000000..f957691a --- /dev/null +++ b/src/engine-g13.c @@ -0,0 +1,796 @@ +/* engine-g13.c - G13 engine. + Copyright (C) 2000 Werner Koch (dd9jn) + Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, 2009 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 Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of + the License, or (at your option) any later version. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this program; if not, 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 + +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <assert.h> +#include <unistd.h> +#include <locale.h> +#include <fcntl.h> /* FIXME */ +#include <errno.h> + +#include "gpgme.h" +#include "util.h" +#include "ops.h" +#include "wait.h" +#include "priv-io.h" +#include "sema.h" + +#include "assuan.h" +#include "debug.h" + +#include "engine-backend.h" + + +typedef struct +{ + int fd; /* FD we talk about. */ + int server_fd;/* Server FD for this connection. */ + int dir; /* Inbound/Outbound, maybe given implicit? */ + void *data; /* Handler-specific data. */ + void *tag; /* ID from the user for gpgme_remove_io_callback. */ + char server_fd_str[15]; /* Same as SERVER_FD but as a string. We + need this because _gpgme_io_fd2str can't + be used on a closed descriptor. */ +} iocb_data_t; + + +struct engine_g13 +{ + assuan_context_t assuan_ctx; + + int lc_ctype_set; + int lc_messages_set; + + iocb_data_t status_cb; + + struct gpgme_io_cbs io_cbs; + + /* Internal callbacks. */ + engine_assuan_result_cb_t result_cb; + void *result_cb_value; + + /* User provided callbacks. */ + struct { + gpgme_assuan_data_cb_t data_cb; + void *data_cb_value; + + gpgme_assuan_inquire_cb_t inq_cb; + void *inq_cb_value; + + gpgme_assuan_status_cb_t status_cb; + void *status_cb_value; + } user; +}; + +typedef struct engine_g13 *engine_g13_t; + + +static void g13_io_event (void *engine, + gpgme_event_io_t type, void *type_data); + + + +static char * +g13_get_version (const char *file_name) +{ + return _gpgme_get_program_version (file_name ? file_name + : _gpgme_get_g13_path ()); +} + + +static const char * +g13_get_req_version (void) +{ + return NEED_G13_VERSION; +} + + +static void +close_notify_handler (int fd, void *opaque) +{ + engine_g13_t g13 = opaque; + + assert (fd != -1); + if (g13->status_cb.fd == fd) + { + if (g13->status_cb.tag) + (*g13->io_cbs.remove) (g13->status_cb.tag); + g13->status_cb.fd = -1; + g13->status_cb.tag = NULL; + } +} + + +/* This is the default inquiry callback. We use it to handle the + Pinentry notifications. */ +static gpgme_error_t +default_inq_cb (engine_g13_t g13, const char *keyword, const char *args) +{ + gpg_error_t err; + + if (!strcmp (keyword, "PINENTRY_LAUNCHED")) + { + _gpgme_allow_set_foreground_window ((pid_t)strtoul (args, NULL, 10)); + } + + if (g13->user.inq_cb) + { + gpgme_data_t data = NULL; + + err = g13->user.inq_cb (g13->user.inq_cb_value, + keyword, args, &data); + if (!err && data) + { + /* FIXME: Returning data is not yet implemented. However we + need to allow the caller to cleanup his data object. + Thus we run the callback in finish mode immediately. */ + err = g13->user.inq_cb (g13->user.inq_cb_value, + NULL, NULL, &data); + } + } + else + err = 0; + + return err; +} + + +static gpgme_error_t +g13_cancel (void *engine) +{ + engine_g13_t g13 = engine; + + if (!g13) + return gpg_error (GPG_ERR_INV_VALUE); + + if (g13->status_cb.fd != -1) + _gpgme_io_close (g13->status_cb.fd); + + if (g13->assuan_ctx) + { + assuan_release (g13->assuan_ctx); + g13->assuan_ctx = NULL; + } + + return 0; +} + + +static void +g13_release (void *engine) +{ + engine_g13_t g13 = engine; + + if (!g13) + return; + + g13_cancel (engine); + + free (g13); +} + + +static gpgme_error_t +g13_new (void **engine, const char *file_name, const char *home_dir) +{ + gpgme_error_t err = 0; + engine_g13_t g13; + int argc; + char *argv[5]; + char *dft_display = NULL; + char dft_ttyname[64]; + char *dft_ttytype = NULL; + char *optstr; + + g13 = calloc (1, sizeof *g13); + if (!g13) + return gpg_error_from_errno (errno); + + g13->status_cb.fd = -1; + g13->status_cb.dir = 1; + g13->status_cb.tag = 0; + g13->status_cb.data = g13; + + err = assuan_new (&g13->assuan_ctx); + if (err) + goto leave; + + argc = 0; + argv[argc++] = "g13"; + if (home_dir) + { + argv[argc++] = "--homedir"; + argv[argc++] = home_dir; + } + argv[argc++] = "--server"; + argv[argc++] = NULL; + + err = assuan_new_ext (&g13->assuan_ctx, GPG_ERR_SOURCE_GPGME, + &_gpgme_assuan_malloc_hooks, _gpgme_assuan_log_cb, + NULL); + if (err) + goto leave; + assuan_ctx_set_system_hooks (g13->assuan_ctx, &_gpgme_assuan_system_hooks); + +#if USE_DESCRIPTOR_PASSING + err = assuan_pipe_connect_ext + (g13->assuan_ctx, file_name ? file_name : _gpgme_get_g13_path (), + argv, NULL, NULL, NULL, 1); +#else + err = assuan_pipe_connect + (g13->assuan_ctx, file_name ? file_name : _gpgme_get_g13_path (), + argv, NULL); +#endif + if (err) + goto leave; + + err = _gpgme_getenv ("DISPLAY", &dft_display); + if (err) + goto leave; + if (dft_display) + { + if (asprintf (&optstr, "OPTION display=%s", dft_display) < 0) + { + free (dft_display); + err = gpg_error_from_errno (errno); + goto leave; + } + free (dft_display); + + err = assuan_transact (g13->assuan_ctx, optstr, NULL, NULL, NULL, + NULL, NULL, NULL); + free (optstr); + if (err) + goto leave; + } + + if (isatty (1)) + { + int rc; + + rc = ttyname_r (1, dft_ttyname, sizeof (dft_ttyname)); + if (rc) + { + err = gpg_error_from_errno (rc); + goto leave; + } + else + { + if (asprintf (&optstr, "OPTION ttyname=%s", dft_ttyname) < 0) + { + err = gpg_error_from_errno (errno); + goto leave; + } + err = assuan_transact (g13->assuan_ctx, optstr, NULL, NULL, NULL, + NULL, NULL, NULL); + free (optstr); + if (err) + goto leave; + + err = _gpgme_getenv ("TERM", &dft_ttytype); + if (err) + goto leave; + if (dft_ttytype) + { + if (asprintf (&optstr, "OPTION ttytype=%s", dft_ttytype) < 0) + { + free (dft_ttytype); + err = gpg_error_from_errno (errno); + goto leave; + } + free (dft_ttytype); + + err = assuan_transact (g13->assuan_ctx, optstr, NULL, NULL, + NULL, NULL, NULL, NULL); + free (optstr); + if (err) + goto leave; + } + } + } + +#ifdef HAVE_W32_SYSTEM + /* Under Windows we need to use AllowSetForegroundWindow. Tell + g13 to tell us when it needs it. */ + if (!err) + { + err = assuan_transact (g13->assuan_ctx, "OPTION allow-pinentry-notify", + NULL, NULL, NULL, NULL, NULL, NULL); + if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION) + err = 0; /* This is a new feature of g13. */ + } +#endif /*HAVE_W32_SYSTEM*/ + + leave: + + if (err) + g13_release (g13); + else + *engine = g13; + + return err; +} + + +static gpgme_error_t +g13_set_locale (void *engine, int category, const char *value) +{ + engine_g13_t g13 = engine; + gpgme_error_t err; + char *optstr; + char *catstr; + + /* FIXME: If value is NULL, we need to reset the option to default. + But we can't do this. So we error out here. G13 needs support + for this. */ + if (category == LC_CTYPE) + { + catstr = "lc-ctype"; + if (!value && g13->lc_ctype_set) + return gpg_error (GPG_ERR_INV_VALUE); + if (value) + g13->lc_ctype_set = 1; + } +#ifdef LC_MESSAGES + else if (category == LC_MESSAGES) + { + catstr = "lc-messages"; + if (!value && g13->lc_messages_set) + return gpg_error (GPG_ERR_INV_VALUE); + if (value) + g13->lc_messages_set = 1; + } +#endif /* LC_MESSAGES */ + else + return gpg_error (GPG_ERR_INV_VALUE); + + /* FIXME: Reset value to default. */ + if (!value) + return 0; + + if (asprintf (&optstr, "OPTION %s=%s", catstr, value) < 0) + err = gpg_error_from_errno (errno); + else + { + err = assuan_transact (g13->assuan_ctx, optstr, NULL, NULL, + NULL, NULL, NULL, NULL); + free (optstr); + } + + return err; +} + + +/* Forward declaration. */ +static gpgme_status_code_t parse_status (const char *name); + +static gpgme_error_t +g13_assuan_simple_command (assuan_context_t ctx, char *cmd, + engine_status_handler_t status_fnc, + void *status_fnc_value) +{ + gpg_error_t err; + char *line; + size_t linelen; + + err = assuan_write_line (ctx, cmd); + if (err) + return err; + + do + { + err = assuan_read_line (ctx, &line, &linelen); + if (err) + return 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 = atoi (&line[4]); + else if (linelen >= 2 + && line[0] == 'S' && line[1] == ' ') + { + char *rest; + gpgme_status_code_t 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) + err = status_fnc (status_fnc_value, r, rest); + else + err = gpg_error (GPG_ERR_GENERAL); + } + else + err = gpg_error (GPG_ERR_GENERAL); + } + while (!err); + + return err; +} + + +static gpgme_error_t +status_handler (void *opaque, int fd) +{ + gpgme_error_t err = 0; + engine_g13_t g13 = opaque; + char *line; + size_t linelen; + + do + { + err = assuan_read_line (g13->assuan_ctx, &line, &linelen); + if (err) + { + /* Try our best to terminate the connection friendly. */ + /* assuan_write_line (g13->assuan_ctx, "BYE"); */ + TRACE2 (DEBUG_CTX, "gpgme:status_handler", g13, + "fd 0x%x: error reading assuan line: %s", + fd, gpg_strerror (err)); + } + else if (linelen >= 3 + && line[0] == 'E' && line[1] == 'R' && line[2] == 'R' + && (line[3] == '\0' || line[3] == ' ')) + { + if (line[3] == ' ') + err = atoi (&line[4]); + if (! err) + err = gpg_error (GPG_ERR_GENERAL); + TRACE2 (DEBUG_CTX, "gpgme:status_handler", g13, + "fd 0x%x: ERR line: %s", + fd, err ? gpg_strerror (err) : "ok"); + + /* In g13, command execution errors are not fatal, as we use + a session based protocol. */ + if (g13->result_cb) + err = g13->result_cb (g13->result_cb_value, err); + else + err = 0; + if (!err) + { + _gpgme_io_close (g13->status_cb.fd); + return 0; + } + } + else if (linelen >= 2 + && line[0] == 'O' && line[1] == 'K' + && (line[2] == '\0' || line[2] == ' ')) + { + TRACE1 (DEBUG_CTX, "gpgme:status_handler", g13, + "fd 0x%x: OK line", fd); + if (g13->result_cb) + err = g13->result_cb (g13->result_cb_value, 0); + else + err = 0; + if (!err) + { + _gpgme_io_close (g13->status_cb.fd); + return 0; + } + } + else if (linelen > 2 + && line[0] == 'D' && line[1] == ' ') + { + /* 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. */ + char *src = line + 2; + char *end = line + linelen; + char *dst = src; + + linelen = 0; + while (src < end) + { + if (*src == '%' && src + 2 < end) + { + /* Handle escaped characters. */ + ++src; + *dst++ = _gpgme_hextobyte (src); + src += 2; + } + else + *dst++ = *src++; + + linelen++; + } + + src = line + 2; + if (linelen && g13->user.data_cb) + err = g13->user.data_cb (g13->user.data_cb_value, + src, linelen); + else + err = 0; + + TRACE2 (DEBUG_CTX, "gpgme:g13_status_handler", g13, + "fd 0x%x: D inlinedata; status from cb: %s", + fd, (g13->user.data_cb ? + (err? gpg_strerror (err):"ok"):"no callback")); + + } + else if (linelen > 2 + && line[0] == 'S' && line[1] == ' ') + { + char *src; + char *args; + + src = line + 2; + while (*src == ' ') + src++; + + args = strchr (line + 2, ' '); + if (!args) + args = line + linelen; /* set to an empty string */ + else + *(args++) = 0; + + while (*args == ' ') + args++; + + if (g13->user.status_cb) + err = g13->user.status_cb (g13->user.status_cb_value, + src, args); + else + err = 0; + + TRACE3 (DEBUG_CTX, "gpgme:g13_status_handler", g13, + "fd 0x%x: S line (%s) - status from cb: %s", + fd, line+2, (g13->user.status_cb ? + (err? gpg_strerror (err):"ok"):"no callback")); + } + else if (linelen >= 7 + && line[0] == 'I' && line[1] == 'N' && line[2] == 'Q' + && line[3] == 'U' && line[4] == 'I' && line[5] == 'R' + && line[6] == 'E' + && (line[7] == '\0' || line[7] == ' ')) + { + char *src; + char *args; + + for (src=line+7; *src == ' '; src++) + ; + + args = strchr (src, ' '); + if (!args) + args = line + linelen; /* Let it point to an empty string. */ + else + *(args++) = 0; + + while (*args == ' ') + args++; + + err = default_inq_cb (g13, src, args); + if (!err) + { + /* Flush and send END. */ + err = assuan_send_data (g13->assuan_ctx, NULL, 0); + } + else if (gpg_err_code (err) == GPG_ERR_ASS_CANCELED) + { + /* Flush and send CANcel. */ + err = assuan_send_data (g13->assuan_ctx, NULL, 1); + } + assuan_write_line (g13->assuan_ctx, "END"); + } + } + while (!err && assuan_pending_line (g13->assuan_ctx)); + + return err; +} + + +static gpgme_error_t +add_io_cb (engine_g13_t g13, iocb_data_t *iocbd, gpgme_io_cb_t handler) +{ + gpgme_error_t err; + + TRACE_BEG2 (DEBUG_ENGINE, "engine-g13:add_io_cb", g13, + "fd %d, dir %d", iocbd->fd, iocbd->dir); + err = (*g13->io_cbs.add) (g13->io_cbs.add_priv, + iocbd->fd, iocbd->dir, + handler, iocbd->data, &iocbd->tag); + if (err) + return TRACE_ERR (err); + if (!iocbd->dir) + /* FIXME Kludge around poll() problem. */ + err = _gpgme_io_set_nonblocking (iocbd->fd); + return TRACE_ERR (err); +} + + +static gpgme_error_t +start (engine_g13_t g13, const char *command) +{ + gpgme_error_t err; + int fdlist[5]; + int nfds; + + /* 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 (g13->assuan_ctx, 0 /* read fds */, + fdlist, DIM (fdlist)); + if (nfds < 1) + return gpg_error (GPG_ERR_GENERAL); /* FIXME */ + + /* We "duplicate" the file descriptor, so we can close it here (we + can't close fdlist[0], as that is closed by libassuan, and + closing it here might cause libassuan to close some unrelated FD + later). 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. A third alternative would be to let + Assuan know that we closed the FD, but that complicates the + Assuan interface. */ + + g13->status_cb.fd = _gpgme_io_dup (fdlist[0]); + if (g13->status_cb.fd < 0) + return gpg_error_from_syserror (); + + if (_gpgme_io_set_close_notify (g13->status_cb.fd, + close_notify_handler, g13)) + { + _gpgme_io_close (g13->status_cb.fd); + g13->status_cb.fd = -1; + return gpg_error (GPG_ERR_GENERAL); + } + + err = add_io_cb (g13, &g13->status_cb, status_handler); + if (!err) + err = assuan_write_line (g13->assuan_ctx, command); + + if (!err) + g13_io_event (g13, GPGME_EVENT_START, NULL); + + return err; +} + + +#if USE_DESCRIPTOR_PASSING +static gpgme_error_t +g13_reset (void *engine) +{ + engine_g13_t g13 = engine; + + /* We must send a reset because we need to reset the list of + signers. Note that RESET does not reset OPTION commands. */ + return g13_assuan_simple_command (g13->assuan_ctx, "RESET", NULL, NULL); +} +#endif + + +static gpgme_error_t +g13_transact (void *engine, + const char *command, + engine_assuan_result_cb_t result_cb, + void *result_cb_value, + gpgme_assuan_data_cb_t data_cb, + void *data_cb_value, + gpgme_assuan_inquire_cb_t inq_cb, + void *inq_cb_value, + gpgme_assuan_status_cb_t status_cb, + void *status_cb_value) +{ + engine_g13_t g13 = engine; + gpgme_error_t err; + + if (!g13 || !command || !*command) + return gpg_error (GPG_ERR_INV_VALUE); + + g13->result_cb = result_cb; + g13->result_cb_value = result_cb_value; + g13->user.data_cb = data_cb; + g13->user.data_cb_value = data_cb_value; + g13->user.inq_cb = inq_cb; + g13->user.inq_cb_value = inq_cb_value; + g13->user.status_cb = status_cb; + g13->user.status_cb_value = status_cb_value; + + err = start (g13, command); + return err; +} + + + +static void +g13_set_io_cbs (void *engine, gpgme_io_cbs_t io_cbs) +{ + engine_g13_t g13 = engine; + g13->io_cbs = *io_cbs; +} + + +static void +g13_io_event (void *engine, gpgme_event_io_t type, void *type_data) +{ + engine_g13_t g13 = engine; + + TRACE3 (DEBUG_ENGINE, "gpgme:g13_io_event", g13, + "event %p, type %d, type_data %p", + g13->io_cbs.event, type, type_data); + if (g13->io_cbs.event) + (*g13->io_cbs.event) (g13->io_cbs.event_priv, type, type_data); +} + + +struct engine_ops _gpgme_engine_ops_g13 = + { + /* Static functions. */ + _gpgme_get_g13_path, + NULL, + g13_get_version, + g13_get_req_version, + g13_new, + + /* Member functions. */ + g13_release, +#if USE_DESCRIPTOR_PASSING + g13_reset, +#else + NULL, /* reset */ +#endif + NULL, /* set_status_handler */ + NULL, /* set_command_handler */ + NULL, /* set_colon_line_handler */ + g13_set_locale, + NULL, /* decrypt */ + NULL, /* delete */ + NULL, /* edit */ + NULL, /* encrypt */ + NULL, /* encrypt_sign */ + NULL, /* export */ + NULL, /* export_ext */ + NULL, /* genkey */ + NULL, /* import */ + NULL, /* keylist */ + NULL, /* keylist_ext */ + NULL, /* sign */ + NULL, /* trustlist */ + NULL, /* verify */ + NULL, /* getauditlog */ + g13_transact, + NULL, /* conf_load */ + NULL, /* conf_save */ + g13_set_io_cbs, + g13_io_event, + g13_cancel + }; |