From d951cb713fdc3a1310534acdce84e3e005ad1d04 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 26 Jan 2009 10:21:10 +0000 Subject: [PATCH] First take on the low-level assuan interface. --- ChangeLog | 8 +- Makefile.am | 3 +- NEWS | 16 + autogen.sh | 4 +- configure.ac | 9 +- src/ChangeLog | 63 +++- src/Makefile.am | 5 +- src/context.h | 4 +- src/dirinfo.c | 189 ++++++++++ src/engine-assuan.c | 744 +++++++++++++++++++++++++++++++++++++ src/engine-backend.h | 25 +- src/engine-gpg.c | 2 + src/engine-gpgconf.c | 2 + src/engine-gpgsm.c | 2 + src/engine.c | 80 +++- src/engine.h | 14 + src/gpgme.c | 7 +- src/gpgme.def | 4 + src/gpgme.h.in | 50 ++- src/libgpgme.vers | 9 +- src/opassuan.c | 158 ++++++++ src/util.h | 11 + src/version.c | 3 +- tests/ChangeLog | 6 + tests/Makefile.am | 5 +- tests/opassuan/Makefile.am | 35 ++ tests/opassuan/t-command.c | 121 ++++++ 27 files changed, 1531 insertions(+), 48 deletions(-) create mode 100644 src/dirinfo.c create mode 100644 src/engine-assuan.c create mode 100644 src/opassuan.c create mode 100644 tests/opassuan/Makefile.am create mode 100644 tests/opassuan/t-command.c diff --git a/ChangeLog b/ChangeLog index 4b1a26c..40966da 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +2009-01-26 Werner Koch + + * configure.ac (AC_CONFIG_FILES): Add tests/opassuan/Makefile. + 2008-12-08 Marcus Brinkmann Release GPGME 1.1.8. @@ -720,8 +724,8 @@ * configure.ac (AC_INIT): Bump version to 0.3.3. * jnlib/Makefile.am: Rever to older version that includes xmalloc - but not dotlock and some other files. Reported by Stéphane - Corthésy. + but not dotlock and some other files. Reported by Stéphane + Corthésy. 2002-02-10 Marcus Brinkmann diff --git a/Makefile.am b/Makefile.am index 432bfca..d22ceae 100644 --- a/Makefile.am +++ b/Makefile.am @@ -15,8 +15,7 @@ # 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 +# License along with this program; if not, see . ## Process this file with automake to produce Makefile.in diff --git a/NEWS b/NEWS index 9218794..551f117 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,19 @@ +Noteworthy changes in version 1.1.9 +------------------------------------------------ + + * Interface changes relative to the 1.1.7 release: + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + GPGME_PROTOCOL_ASSUAN NEW. + gpgme_assuan_data_cb_t NEW. + gpgme_assuan_sendfnc_ctx_t NEW. + gpgme_assuan_inquire_cb_t NEW. + gpgme_assuan_status_cb_t NEW. + gpgme_op_assuan_transact_start NEW. + gpgme_op_assuan_transact NEW. + gpgme_op_assuan_result NEW. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Noteworthy changes in version 1.1.8 (2008-12-08) ------------------------------------------------ diff --git a/autogen.sh b/autogen.sh index b88b672..60e52d7 100755 --- a/autogen.sh +++ b/autogen.sh @@ -159,4 +159,6 @@ $AUTOMAKE --gnu; echo "Running autoconf${FORCE} ..." $AUTOCONF${FORCE} -echo "You may now run \"./configure --enable-maintainer-mode && make\"." +echo "You may now run: + ./configure --enable-maintainer-mode && make +" diff --git a/configure.ac b/configure.ac index f4b9282..5d49c08 100644 --- a/configure.ac +++ b/configure.ac @@ -31,8 +31,8 @@ min_automake_version="1.10" # specific feature can already be done under the assumption that the # SVN version is the most recent one in a branch. To disable the SVN # version for the real release, set the my_issvn macro to no. -m4_define(my_version, [1.1.8]) -m4_define(my_issvn, [no]) +m4_define(my_version, [1.1.9]) +m4_define(my_issvn, [yes]) m4_define([svn_revision], m4_esyscmd([echo -n $( (svn info 2>/dev/null \ || echo 'Revision: 0')|sed -n '/^Revision:/ {s/[^0-9]//gp;q;}')])) @@ -744,7 +744,10 @@ AC_SUBST(LTLIBOBJS) # Create config files AC_CONFIG_FILES(Makefile assuan/Makefile src/Makefile - tests/Makefile tests/gpg/Makefile tests/gpgsm/Makefile + tests/Makefile + tests/gpg/Makefile + tests/gpgsm/Makefile + tests/opassuan/Makefile doc/Makefile complus/Makefile src/versioninfo.rc src/gpgme.h) diff --git a/src/ChangeLog b/src/ChangeLog index 95dc804..bf272bc 100644 --- a/src/ChangeLog +++ b/src/ChangeLog @@ -1,3 +1,36 @@ +2009-01-26 Werner Koch + + * opassuan.c, dirinfo.c, engine-assuan.c: New. + * Makefile.am: Add them. + * engine-backend.h: Add _gpgme_engine_ops_assuan. + (struct engine_ops): Add field OPASSUAN_TRANSACT. Update all + engine intializers. + * Makefile.am (gpgsm_components): Add engine-assuan.c. + * gpgme.h.in (gpgme_protocol_t): Add GPGME_PROTOCOL_ASSUAN. + (gpgme_assuan_data_cb_t, gpgme_assuan_sendfnc_ctx_t) + (gpgme_assuan_inquire_cb_t, gpgme_assuan_status_cb_t): New. + (gpgme_op_assuan_transact_start, gpgme_op_assuan_transact): New. + * gpgme.c (gpgme_get_protocol_name): Ditto. + (gpgme_set_protocol): Support it. + * engine.c (gpgme_get_engine_info): Ditto. + (engine_ops): Register it. + (_gpgme_engine_op_assuan_transact): New. + * libgpgme.vers (gpgme_op_assuan_transact_start) + (gpgme_op_assuan_transact): New. + * gpgme.def (gpgme_op_assuan_transact_start) + (gpgme_op_assuan_transact): New. + * engine-backend.h (struct engine_ops): Add GET_HOME_DIR and + initialize to NULL for all engines. + * engine.c (engine_get_home_dir): New. + (gpgme_get_engine_info): Use it. + (_gpgme_set_engine_info): Use it. + * engine.h (engine_assuan_result_cb_t): New. + * context.h (ctx_op_data_id_t): Add OPDATA_ASSUAN. + + * util.h (GPG_ERR_UNFINISHED): Define if not yet defined. + + * version.c (gpgme_check_version): Protect trace arg against NULL. + 2009-01-19 Werner Koch * rungpg.c: Rename to engine-gpg.c @@ -803,7 +836,7 @@ * engine.c (gpgme_engine_check_version): Reimplemented to allow checking the version correctly even after changing the engine - information. Bug reported by Stéphane Corthésy. + information. Bug reported by Stéphane Corthésy. * rungpg.c (read_colon_line): Invoke colon preprocess handler if it is set. @@ -868,7 +901,7 @@ 2005-11-27 Marcus Brinkmann * engine.c (_gpgme_set_engine_info): Use new_file_name in - engine_get_version invocation. Reported by Stéphane Corthésy. + engine_get_version invocation. Reported by Stéphane Corthésy. 2005-11-24 Marcus Brinkmann @@ -1818,7 +1851,7 @@ * gpgme-config.in (gpg_error_libs): Quote GPG_ERROR_CFLAGS and GPG_ERROR_LIBS when setting the corresponding variables. - Reported by Stéphane Corthésy. + Reported by Stéphane Corthésy. 2003-07-22 Marcus Brinkmann @@ -3707,7 +3740,7 @@ 2002-09-28 Marcus Brinkmann * conversion.c (_gpgme_hextobyte): Prevent superfluous - multiplication with base. Reported by Stéphane Corthésy. + multiplication with base. Reported by Stéphane Corthésy. * keylist.c (gpgme_op_keylist_ext_start): Use private asynchronous operation type in invocation of _gpgme_op_reset. @@ -3820,7 +3853,7 @@ variables encrypt_info and encrypt_info_len. * trustlist.c (gpgme_op_trustlist_start): Set colon line handler. * posix-sema.c (sema_fatal): Remove function. - All these reported by Stéphane Corthésy. + All these reported by Stéphane Corthésy. 2002-08-23 Werner Koch @@ -3993,7 +4026,7 @@ * vasprintf.c: Update to more recent libiberty version. * debug.h: Replace #elsif with #elif. - Submitted by Stéphane Corthésy: + Submitted by Stéphane Corthésy: * util.h (vasprintf): Correct prototype. * encrypt-sign.c: Include . (encrypt_sign_status_handler): Change type of ENCRYPT_INFO_LEN to @@ -4003,14 +4036,14 @@ 2002-07-25 Marcus Brinkmann - * wait.c (fdt_global): Make static. Reported by Stéphane - Corthésy. + * wait.c (fdt_global): Make static. Reported by Stéphane + Corthésy. * rungpg.c (_gpgme_gpg_op_keylist_ext): Skip empty string - patterns. Reported by Stéphane Corthésy. + patterns. Reported by Stéphane Corthésy. * key.c (gpgme_key_get_as_xml): Add OTRUST attribute. Requested - by Stéphane Corthésy. + by Stéphane Corthésy. (gpgme_key_get_string_attr): Add GPGME_ATTR_SIG_SUMMARY case to silence gcc warning. @@ -5060,7 +5093,7 @@ 2001-12-19 Marcus Brinkmann - * engine.c: Include `string.h'. Reported by Stéphane Corthésy. + * engine.c: Include `string.h'. Reported by Stéphane Corthésy. * version.c (get_engine_info): Remove prototype. @@ -5597,7 +5630,7 @@ callers to use this function without a check for tmp_key. * keylist.c (gpgme_op_keylist_next): Reset the key_cond after - emptying the queue. Bug reported by Stéphane Corthésy. + emptying the queue. Bug reported by Stéphane Corthésy. 2001-09-12 Werner Koch @@ -5679,7 +5712,7 @@ * version.c (gpgme_check_engine): Stop version number parsing at the opening angle and not the closing one. By Tommy Reynolds. -2001-05-01 José Carlos García Sogo +2001-05-01 José Carlos García Sogo * encrypt.c (gpgme_op_encrypt_start): Deleted the assert ( !c->gpg ) line, because it gave an error if another operation had been made @@ -5865,8 +5898,8 @@ * rungpg.c (_gpgme_gpg_spawn): Use new function to get GPG's path. * signers.c (gpgme_signers_add): Ooops, one should test code and - not just write it; the newarr was not assigned. Thanks to José - for pointing this out. Hmmm, still not tested, why shoudl a coder + not just write it; the newarr was not assigned. Thanks to José + for pointing this out. Hmmm, still not tested, why should a coder test his fix :-) * w32-io.c: Does now use reader threads, so that we can use diff --git a/src/Makefile.am b/src/Makefile.am index 5a7ca59..22254f0 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -78,7 +78,7 @@ system_components_not_extra = endif if HAVE_GPGSM -gpgsm_components = engine-gpgsm.c +gpgsm_components = engine-gpgsm.c engine-assuan.c else gpgsm_components = endif @@ -105,9 +105,10 @@ main_sources = \ sign.c passphrase.c progress.c \ key.c keylist.c trust-item.c trustlist.c \ import.c export.c genkey.c delete.c edit.c getauditlog.c \ + opassuan.c \ engine.h engine-backend.h engine.c engine-gpg.c status-table.h \ $(gpgsm_components) $(gpgconf_components) gpgconf.c \ - sema.h priv-io.h $(system_components) \ + sema.h priv-io.h $(system_components) dirinfo.c \ debug.c debug.h gpgme.c version.c error.c libgpgme_la_SOURCES = $(main_sources) \ diff --git a/src/context.h b/src/context.h index ed5d850..76aeb33 100644 --- a/src/context.h +++ b/src/context.h @@ -36,7 +36,7 @@ typedef enum { OPDATA_DECRYPT, OPDATA_SIGN, OPDATA_ENCRYPT, OPDATA_PASSPHRASE, OPDATA_IMPORT, OPDATA_GENKEY, OPDATA_KEYLIST, OPDATA_EDIT, - OPDATA_VERIFY, OPDATA_TRUSTLIST + OPDATA_VERIFY, OPDATA_TRUSTLIST, OPDATA_ASSUAN } ctx_op_data_id_t; @@ -51,7 +51,7 @@ struct ctx_op_data ctx_op_data_id_t type; /* The function to release HOOK and all its associated resources. - Can be NULL if no special dealllocation routine is necessary. */ + Can be NULL if no special deallocation routine is necessary. */ void (*cleanup) (void *hook); /* The hook that points to the operation data. */ diff --git a/src/dirinfo.c b/src/dirinfo.c new file mode 100644 index 0000000..45f09c0 --- /dev/null +++ b/src/dirinfo.c @@ -0,0 +1,189 @@ +/* dirinfo.c - Get directory information + * Copyright (C) 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, see . + */ + +#if HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include "gpgme.h" +#include "util.h" +#include "priv-io.h" +#include "debug.h" +#include "sema.h" + +DEFINE_STATIC_LOCK (dirinfo_lock); + +/* Constants used internally to select the data. */ +enum + { + WANT_HOMEDIR, + WANT_AGENT_SOCKET + }; + +/* Values retrieved via gpgconf and cached here. */ +static struct { + int valid; /* Cached information is valid. */ + char *homedir; + char *agent_socket; +} dirinfo; + + +/* Parse the output of "gpgconf --list-dirs". This function expects + that DIRINFO_LOCK is held by the caller. */ +static void +parse_output (char *line) +{ + char *value, *p; + + value = strchr (line, ':'); + if (!value) + return; + *value++ = 0; + p = strchr (value, ':'); + if (p) + *p = 0; + if (_gpgme_decode_percent_string (value, &value, strlen (value)+1, 0)) + return; + if (!*value) + return; + + if (!strcmp (line, "homedir") && !dirinfo.homedir) + dirinfo.homedir = strdup (value); + else if (!strcmp (line, "agent-socket") && !dirinfo.agent_socket) + dirinfo.agent_socket = strdup (value); +} + + +/* Read the directory information from gpgconf. This function expects + that DIRINFO_LOCK is held by the caller. */ +static void +read_gpgconf_dirs (void) +{ + const char *pgmname; + char linebuf[1024] = {0}; + int linelen = 0; + char * argv[3]; + int rp[2]; + struct spawn_fd_item_s cfd[] = { {-1, 1 /* STDOUT_FILENO */, -1, 0}, + {-1, -1} }; + int status; + int nread; + char *mark = NULL; + + pgmname = _gpgme_get_gpgconf_path (); + if (!pgmname) + return; /* No way. */ + + argv[0] = (char *)pgmname; + argv[1] = "--list-dirs"; + argv[2] = NULL; + + if (_gpgme_io_pipe (rp, 1) < 0) + return; + + cfd[0].fd = rp[1]; + + status = _gpgme_io_spawn (pgmname, argv, cfd, NULL); + if (status < 0) + { + _gpgme_io_close (rp[0]); + _gpgme_io_close (rp[1]); + return; + } + + do + { + nread = _gpgme_io_read (rp[0], + linebuf + linelen, + sizeof linebuf - linelen - 1); + if (nread > 0) + { + char *line; + const char *lastmark = NULL; + size_t nused; + + linelen += nread; + linebuf[linelen] = '\0'; + + for (line=linebuf; (mark = strchr (line, '\n')); line = mark+1 ) + { + lastmark = mark; + if (mark > line && mark[-1] == '\r') + mark[-1] = '\0'; + else + mark[0] = '\0'; + + parse_output (line); + } + + nused = lastmark? (lastmark + 1 - linebuf) : 0; + memmove (linebuf, linebuf + nused, linelen - nused); + linelen -= nused; + } + } + while (nread > 0 && linelen < sizeof linebuf - 1); + + _gpgme_io_close (rp[0]); +} + + +static const char * +get_gpgconf_dir (int what) +{ + const char *result = NULL; + + LOCK (dirinfo_lock); + if (!dirinfo.valid) + { + read_gpgconf_dirs (); + /* Even if the reading of the directories failed (e.g. due to an + too old version gpgconf or no gpgconf at all), we need to + mark the entries as valid so that we won't try over and over + to read them. Note further that we are not able to change + the read values later because they are practically statically + allocated. */ + dirinfo.valid = 1; + } + switch (what) + { + case WANT_HOMEDIR: result = dirinfo.homedir; break; + case WANT_AGENT_SOCKET: result = dirinfo.agent_socket; break; + } + UNLOCK (dirinfo_lock); + return result; +} + + +/* Return the default home directory. Returns NULL if not known. */ +const char * +_gpgme_get_default_homedir (void) +{ + return get_gpgconf_dir (WANT_HOMEDIR); +} + +/* Return the default gpg-agent socket name. Returns NULL if not known. */ +const char * +_gpgme_get_default_agent_socket (void) +{ + return get_gpgconf_dir (WANT_AGENT_SOCKET); +} + diff --git a/src/engine-assuan.c b/src/engine-assuan.c new file mode 100644 index 0000000..fb74868 --- /dev/null +++ b/src/engine-assuan.c @@ -0,0 +1,744 @@ +/* engine-assuan.c - Low-level Assuan protocol engine + * Copyright (C) 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, see . + */ + +/* + Note: This engine requires a modern Assuan server which uses + gpg-error codes. In particular there is no backward compatible + mapping of old Assuan error codes implemented. +*/ + + +#if HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#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. */ +} iocb_data_t; + +/* Engine instance data. */ +struct engine_llass +{ + 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; + + /* Option flags. */ + struct { + int gpg_agent:1; /* Assume this is a gpg-agent connection. */ + } opt; + +}; +typedef struct engine_llass *engine_llass_t; + +/* Helper to pass data to a callback. */ +struct _gpgme_assuan_sendfnc_ctx +{ + assuan_context_t assuan_ctx; +}; + + + +/* Prototypes. */ +static void llass_io_event (void *engine, + gpgme_event_io_t type, void *type_data); + + + + + +/* return the default home directory. */ +static const char * +llass_get_home_dir (void) +{ + /* For this engine the home directory is not a filename but a string + used to convey options. The exclamation mark is a marker to show + that this is not a directory name. Options are strings delimited + by a space. The only option defined for now is GPG_AGENT to + enable GPG_AGENT specific commands to send to the server at + connection startup. */ + return "!GPG_AGENT"; +} + +static char * +llass_get_version (const char *file_name) +{ + return strdup ("1.0"); +} + + +static const char * +llass_get_req_version (void) +{ + return "1.0"; +} + + +static void +close_notify_handler (int fd, void *opaque) +{ + engine_llass_t llass = opaque; + + assert (fd != -1); + if (llass->status_cb.fd == fd) + { + if (llass->status_cb.tag) + llass->io_cbs.remove (llass->status_cb.tag); + llass->status_cb.fd = -1; + llass->status_cb.tag = NULL; + } +} + + + +static gpgme_error_t +llass_cancel (void *engine) +{ + engine_llass_t llass = engine; + + if (!llass) + return gpg_error (GPG_ERR_INV_VALUE); + + if (llass->status_cb.fd != -1) + _gpgme_io_close (llass->status_cb.fd); + + if (llass->assuan_ctx) + { + assuan_disconnect (llass->assuan_ctx); + llass->assuan_ctx = NULL; + } + + return 0; +} + + +static void +llass_release (void *engine) +{ + engine_llass_t llass = engine; + + if (!llass) + return; + + llass_cancel (engine); + + free (llass); +} + + +/* Create a new instance. If HOME_DIR is NULL standard options for use + with gpg-agent are issued. */ +static gpgme_error_t +llass_new (void **engine, const char *file_name, const char *home_dir) +{ + gpgme_error_t err = 0; + engine_llass_t llass; + char *optstr; + + llass = calloc (1, sizeof *llass); + if (!llass) + return gpg_error_from_syserror (); + + llass->status_cb.fd = -1; + llass->status_cb.dir = 1; + llass->status_cb.tag = 0; + llass->status_cb.data = llass; + + /* Parse_options. */ + if (home_dir && *home_dir == '!') + { + home_dir++; + /* Very simple parser only working for the one option we support. */ + if (!strncmp (home_dir, "GPG_AGENT", 9) + && (!home_dir[9] || home_dir[9] == ' ')) + llass->opt.gpg_agent = 1; + } + + err = assuan_socket_connect (&llass->assuan_ctx, file_name, 0); + if (err) + goto leave; + + if (llass->opt.gpg_agent) + { + char *dft_display = NULL; + + err = _gpgme_getenv ("DISPLAY", &dft_display); + if (err) + goto leave; + if (dft_display) + { + if (asprintf (&optstr, "OPTION display=%s", dft_display) < 0) + { + err = gpg_error_from_syserror (); + free (dft_display); + goto leave; + } + free (dft_display); + + err = assuan_transact (llass->assuan_ctx, optstr, NULL, NULL, NULL, + NULL, NULL, NULL); + free (optstr); + if (err) + goto leave; + } + } + + if (llass->opt.gpg_agent && isatty (1)) + { + int rc; + char dft_ttyname[64]; + char *dft_ttytype = NULL; + + 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_syserror (); + goto leave; + } + err = assuan_transact (llass->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) + { + err = gpg_error_from_syserror (); + free (dft_ttytype); + goto leave; + } + free (dft_ttytype); + + err = assuan_transact (llass->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 + llass to tell us when it needs it. */ + if (!err && llass->opt.gpg_agent) + { + err = assuan_transact (llass->assuan_ctx, "OPTION allow-pinentry-notify", + NULL, NULL, NULL, NULL, NULL, NULL); + if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION) + err = 0; /* This work only with recent gpg-agents. */ + } +#endif /*HAVE_W32_SYSTEM*/ + + + leave: + /* Close the server ends of the pipes (because of this, we must use + the stored server_fd_str in the function start). Our ends are + closed in llass_release(). */ + + if (err) + llass_release (llass); + else + *engine = llass; + + return err; +} + + +static gpgme_error_t +llass_set_locale (void *engine, int category, const char *value) +{ + gpgme_error_t err; + engine_llass_t llass = engine; + char *optstr; + char *catstr; + + if (!llass->opt.gpg_agent) + return 0; + + /* FIXME: If value is NULL, we need to reset the option to default. + But we can't do this. So we error out here. gpg-agent needs + support for this. */ + if (category == LC_CTYPE) + { + catstr = "lc-ctype"; + if (!value && llass->lc_ctype_set) + return gpg_error (GPG_ERR_INV_VALUE); + if (value) + llass->lc_ctype_set = 1; + } +#ifdef LC_MESSAGES + else if (category == LC_MESSAGES) + { + catstr = "lc-messages"; + if (!value && llass->lc_messages_set) + return gpg_error (GPG_ERR_INV_VALUE); + if (value) + llass->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 (llass->assuan_ctx, optstr, NULL, NULL, + NULL, NULL, NULL, NULL); + free (optstr); + } + return err; +} + + + +static gpgme_error_t +inquire_cb_sendfnc (gpgme_assuan_sendfnc_ctx_t ctx, + const void *data, size_t datalen) +{ + if (data && datalen) + return assuan_send_data (ctx->assuan_ctx, data, datalen); + else + return 0; /* Don't allow an inquire to send a flush. */ +} + + +/* This is the inquiry callback. It handles stuff which ee need to + handle here and passes everything on to the user callback. */ +static gpgme_error_t +inquire_cb (engine_llass_t llass, const char *keyword, const char *args) +{ + gpg_error_t err; + + if (llass->opt.gpg_agent && !strcmp (keyword, "PINENTRY_LAUNCHED")) + { + _gpgme_allow_set_foregound_window ((pid_t)strtoul (args, NULL, 10)); + } + + if (llass->user.inq_cb) + { + struct _gpgme_assuan_sendfnc_ctx sendfnc_ctx; + + sendfnc_ctx.assuan_ctx = llass->assuan_ctx; + err = llass->user.inq_cb (llass->user.inq_cb_value, + keyword, args, + inquire_cb_sendfnc, &sendfnc_ctx); + } + else + err = 0; + + return err; +} + + +static gpgme_error_t +llass_status_handler (void *opaque, int fd) +{ + gpgme_error_t err = 0; + engine_llass_t llass = opaque; + char *line; + size_t linelen; + + do + { + err = assuan_read_line (llass->assuan_ctx, &line, &linelen); + if (err) + { + TRACE2 (DEBUG_CTX, "gpgme:llass_status_handler", llass, + "fd 0x%x: error reading assuan line: %s", + fd, gpg_strerror (err)); + } + else if (linelen >= 2 && line[0] == 'D' && line[1] == ' ') + { + 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 && llass->user.data_cb) + err = llass->user.data_cb (llass->user.data_cb_value, + src, linelen); + else + err = 0; + + TRACE2 (DEBUG_CTX, "gpgme:llass_status_handler", llass, + "fd 0x%x: D inlinedata; status from cb: %s", + fd, (llass->user.data_cb ? + (err? gpg_strerror (err):"ok"):"no callback")); + } + else if (linelen >= 3 + && line[0] == 'E' && line[1] == 'N' && line[2] == 'D' + && (line[3] == '\0' || line[3] == ' ')) + { + /* END received. Tell the data callback. */ + if (llass->user.data_cb) + err = llass->user.data_cb (llass->user.data_cb_value, NULL, 0); + else + err = 0; + + TRACE2 (DEBUG_CTX, "gpgme:llass_status_handler", llass, + "fd 0x%x: END line; status from cb: %s", + fd, (llass->user.data_cb ? + (err? gpg_strerror (err):"ok"):"no callback")); + } + else if (linelen > 2 && line[0] == 'S' && line[1] == ' ') + { + char *args; + char *src; + + for (src=line+2; *src == ' '; src++) + ; + + args = strchr (src, ' '); + if (!args) + args = line + linelen; /* Let it point to an empty string. */ + else + *(args++) = 0; + + while (*args == ' ') + args++; + + if (llass->user.status_cb) + err = llass->user.status_cb (llass->user.status_cb_value, + src, args); + else + err = 0; + + TRACE3 (DEBUG_CTX, "gpgme:llass_status_handler", llass, + "fd 0x%x: S line (%s) - status from cb: %s", + fd, line+2, (llass->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 = inquire_cb (llass, src, args); + if (!err) /* Flush and send END. */ + err = assuan_send_data (llass->assuan_ctx, NULL, 0); + } + 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); + else + err = gpg_error (GPG_ERR_GENERAL); + TRACE2 (DEBUG_CTX, "gpgme:llass_status_handler", llass, + "fd 0x%x: ERR line: %s", + fd, err ? gpg_strerror (err) : "ok"); + if (llass->result_cb) + err = llass->result_cb (llass->result_cb_value, err); + else + err = 0; + if (!err) + { + _gpgme_io_close (llass->status_cb.fd); + return 0; + } + } + else if (linelen >= 2 + && line[0] == 'O' && line[1] == 'K' + && (line[2] == '\0' || line[2] == ' ')) + { + TRACE1 (DEBUG_CTX, "gpgme:llass_status_handler", llass, + "fd 0x%x: OK line", fd); + if (llass->result_cb) + err = llass->result_cb (llass->result_cb_value, 0); + else + err = 0; + if (!err) + { + _gpgme_io_close (llass->status_cb.fd); + return 0; + } + } + else + { + /* Comment line or invalid line. */ + } + + } + while (!err && assuan_pending_line (llass->assuan_ctx)); + + return err; +} + + +static gpgme_error_t +add_io_cb (engine_llass_t llass, iocb_data_t *iocbd, gpgme_io_cb_t handler) +{ + gpgme_error_t err; + + TRACE_BEG2 (DEBUG_ENGINE, "engine-assuan:add_io_cb", llass, + "fd %d, dir %d", iocbd->fd, iocbd->dir); + err = (*llass->io_cbs.add) (llass->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_llass_t llass, 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 (llass->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. */ + + llass->status_cb.fd = _gpgme_io_dup (fdlist[0]); + if (llass->status_cb.fd < 0) + return gpg_error_from_syserror (); + + if (_gpgme_io_set_close_notify (llass->status_cb.fd, + close_notify_handler, llass)) + { + _gpgme_io_close (llass->status_cb.fd); + llass->status_cb.fd = -1; + return gpg_error (GPG_ERR_GENERAL); + } + + err = add_io_cb (llass, &llass->status_cb, llass_status_handler); + if (!err) + err = assuan_write_line (llass->assuan_ctx, command); + + /* FIXME: If *command == '#' no answer is expected. */ + + if (!err) + llass_io_event (llass, GPGME_EVENT_START, NULL); + + return err; +} + + + +static gpgme_error_t +llass_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_llass_t llass = engine; + gpgme_error_t err; + + if (!llass || !command || !*command) + return gpg_error (GPG_ERR_INV_VALUE); + + llass->result_cb = result_cb; + llass->result_cb_value = result_cb_value; + llass->user.data_cb = data_cb; + llass->user.data_cb_value = data_cb_value; + llass->user.inq_cb = inq_cb; + llass->user.inq_cb_value = inq_cb_value; + llass->user.status_cb = status_cb; + llass->user.status_cb_value = status_cb_value; + + err = start (llass, command); + return err; +} + + + +static void +llass_set_io_cbs (void *engine, gpgme_io_cbs_t io_cbs) +{ + engine_llass_t llass = engine; + llass->io_cbs = *io_cbs; +} + + +static void +llass_io_event (void *engine, gpgme_event_io_t type, void *type_data) +{ + engine_llass_t llass = engine; + + TRACE3 (DEBUG_ENGINE, "gpgme:llass_io_event", llass, + "event %p, type %d, type_data %p", + llass->io_cbs.event, type, type_data); + if (llass->io_cbs.event) + (*llass->io_cbs.event) (llass->io_cbs.event_priv, type, type_data); +} + + +struct engine_ops _gpgme_engine_ops_assuan = + { + /* Static functions. */ + _gpgme_get_default_agent_socket, + llass_get_home_dir, + llass_get_version, + llass_get_req_version, + llass_new, + + /* Member functions. */ + llass_release, + NULL, /* reset */ + NULL, /* set_status_handler */ + NULL, /* set_command_handler */ + NULL, /* set_colon_line_handler */ + llass_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 */ + llass_transact, /* opassuan_transact */ + NULL, /* conf_load */ + NULL, /* conf_save */ + llass_set_io_cbs, + llass_io_event, + llass_cancel + }; diff --git a/src/engine-backend.h b/src/engine-backend.h index 2e2ef5e..d656d9d 100644 --- a/src/engine-backend.h +++ b/src/engine-backend.h @@ -1,5 +1,5 @@ /* engine-backend.h - A crypto backend for the engine interface. - Copyright (C) 2002, 2003, 2004 g10 Code GmbH + Copyright (C) 2002, 2003, 2004, 2009 g10 Code GmbH This file is part of GPGME. @@ -14,9 +14,8 @@ 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. */ + License along with this program; if not, see . + */ #ifndef ENGINE_BACKEND_H #define ENGINE_BACKEND_H @@ -35,6 +34,11 @@ struct engine_ops /* Return the default file name for the binary of this engine. */ const char *(*get_file_name) (void); + /* Return the default home dir for the binary of this engine. If + this function pointer is not set, the standard default home dir + of the engine is used. */ + const char *(*get_home_dir) (void); + /* Returns a malloced string containing the version of the engine with the given binary file name (or the default if FILE_NAME is NULL. */ @@ -96,6 +100,16 @@ struct engine_ops gpgme_data_t plaintext); gpgme_error_t (*getauditlog) (void *engine, gpgme_data_t output, unsigned int flags); + gpgme_error_t (*opassuan_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); gpgme_error_t (*conf_load) (void *engine, gpgme_conf_comp_t *conf_p); gpgme_error_t (*conf_save) (void *engine, gpgme_conf_comp_t conf); @@ -114,5 +128,8 @@ extern struct engine_ops _gpgme_engine_ops_gpgsm; /* CMS. */ #ifdef ENABLE_GPGCONF extern struct engine_ops _gpgme_engine_ops_gpgconf; /* gpg-conf. */ #endif +#ifdef ENABLE_GPGSM /* If this is enabled we also have assuan support. */ +extern struct engine_ops _gpgme_engine_ops_assuan; /* Low-level Assuan. */ +#endif #endif /* ENGINE_BACKEND_H */ diff --git a/src/engine-gpg.c b/src/engine-gpg.c index 8be7600..e4334d1 100644 --- a/src/engine-gpg.c +++ b/src/engine-gpg.c @@ -2158,6 +2158,7 @@ struct engine_ops _gpgme_engine_ops_gpg = { /* Static functions. */ _gpgme_get_gpg_path, + NULL, gpg_get_version, gpg_get_req_version, gpg_new, @@ -2184,6 +2185,7 @@ struct engine_ops _gpgme_engine_ops_gpg = gpg_trustlist, gpg_verify, NULL, /* getauditlog */ + NULL, /* opassuan_transact */ NULL, /* conf_load */ NULL, /* conf_save */ gpg_set_io_cbs, diff --git a/src/engine-gpgconf.c b/src/engine-gpgconf.c index 3d107d4..d1f27c2 100644 --- a/src/engine-gpgconf.c +++ b/src/engine-gpgconf.c @@ -885,6 +885,7 @@ struct engine_ops _gpgme_engine_ops_gpgconf = { /* Static functions. */ _gpgme_get_gpgconf_path, + NULL, gpgconf_get_version, gpgconf_get_req_version, gpgconf_new, @@ -911,6 +912,7 @@ struct engine_ops _gpgme_engine_ops_gpgconf = NULL, /* trustlist */ NULL, /* verify */ NULL, /* getauditlog */ + NULL, /* opassuan_transact */ gpgconf_conf_load, gpgconf_conf_save, gpgconf_set_io_cbs, diff --git a/src/engine-gpgsm.c b/src/engine-gpgsm.c index 936ac2e..7179b3c 100644 --- a/src/engine-gpgsm.c +++ b/src/engine-gpgsm.c @@ -1922,6 +1922,7 @@ struct engine_ops _gpgme_engine_ops_gpgsm = { /* Static functions. */ _gpgme_get_gpgsm_path, + NULL, gpgsm_get_version, gpgsm_get_req_version, gpgsm_new, @@ -1952,6 +1953,7 @@ struct engine_ops _gpgme_engine_ops_gpgsm = NULL, /* trustlist */ gpgsm_verify, gpgsm_getauditlog, + NULL, /* opassuan_transact */ NULL, /* conf_load */ NULL, /* conf_save */ gpgsm_set_io_cbs, diff --git a/src/engine.c b/src/engine.c index cf3fe9f..87d3939 100644 --- a/src/engine.c +++ b/src/engine.c @@ -1,6 +1,6 @@ /* engine.c - GPGME engine support. Copyright (C) 2000 Werner Koch (dd9jn) - Copyright (C) 2001, 2002, 2003, 2004, 2006 g10 Code GmbH + Copyright (C) 2001, 2002, 2003, 2004, 2006, 2009 g10 Code GmbH This file is part of GPGME. @@ -15,9 +15,8 @@ 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. */ + License along with this program; if not, see . +*/ #ifdef HAVE_CONFIG_H #include @@ -52,7 +51,12 @@ static struct engine_ops *engine_ops[] = NULL, #endif #ifdef ENABLE_GPGCONF - &_gpgme_engine_ops_gpgconf /* gpg-conf. */ + &_gpgme_engine_ops_gpgconf, /* gpg-conf. */ +#else + NULL, +#endif +#ifdef ENABLE_GPGSM /* This indicates that we have assuan support. */ + &_gpgme_engine_ops_assuan /* Low-Level Assuan. */ #else NULL #endif @@ -78,6 +82,20 @@ engine_get_file_name (gpgme_protocol_t proto) } +/* Get the standard home dir of the engine for PROTOCOL. */ +static const char * +engine_get_home_dir (gpgme_protocol_t proto) +{ + if (proto > DIM (engine_ops)) + return NULL; + + if (engine_ops[proto] && engine_ops[proto]->get_home_dir) + return (*engine_ops[proto]->get_home_dir) (); + else + return NULL; +} + + /* Get a malloced string containing the version number of the engine for PROTOCOL. */ static char * @@ -175,18 +193,22 @@ gpgme_get_engine_info (gpgme_engine_info_t *info) gpgme_engine_info_t *lastp = &engine_info; gpgme_protocol_t proto_list[] = { GPGME_PROTOCOL_OpenPGP, GPGME_PROTOCOL_CMS, - GPGME_PROTOCOL_GPGCONF }; + GPGME_PROTOCOL_GPGCONF, + GPGME_PROTOCOL_ASSUAN }; unsigned int proto; for (proto = 0; proto < DIM (proto_list); proto++) { const char *ofile_name = engine_get_file_name (proto_list[proto]); + const char *ohome_dir = engine_get_home_dir (proto_list[proto]); char *file_name; + char *home_dir; if (!ofile_name) continue; file_name = strdup (ofile_name); + home_dir = ohome_dir? strdup (ohome_dir): NULL; *lastp = malloc (sizeof (*engine_info)); if (!*lastp || !file_name) @@ -198,6 +220,8 @@ gpgme_get_engine_info (gpgme_engine_info_t *info) if (file_name) free (file_name); + if (home_dir) + free (home_dir); UNLOCK (engine_info_lock); return gpg_error_from_errno (saved_errno); @@ -205,7 +229,7 @@ gpgme_get_engine_info (gpgme_engine_info_t *info) (*lastp)->protocol = proto_list[proto]; (*lastp)->file_name = file_name; - (*lastp)->home_dir = NULL; + (*lastp)->home_dir = home_dir; (*lastp)->version = engine_get_version (proto_list[proto], NULL); (*lastp)->req_version = engine_get_req_version (proto_list[proto]); (*lastp)->next = NULL; @@ -347,7 +371,20 @@ _gpgme_set_engine_info (gpgme_engine_info_t info, gpgme_protocol_t proto, } } else - new_home_dir = NULL; + { + const char *ohome_dir = engine_get_home_dir (proto); + if (ohome_dir) + { + new_home_dir = strdup (ohome_dir); + if (!new_home_dir) + { + free (new_file_name); + return gpg_error_from_errno (errno); + } + } + else + new_home_dir = NULL; + } /* Remove the old members. */ assert (info->file_name); @@ -730,6 +767,33 @@ _gpgme_engine_op_getauditlog (engine_t engine, gpgme_data_t output, } +gpgme_error_t +_gpgme_engine_op_assuan_transact (engine_t 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) +{ + if (!engine) + return gpg_error (GPG_ERR_INV_VALUE); + + if (!engine->ops->opassuan_transact) + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); + + return (*engine->ops->opassuan_transact) (engine->engine, + command, + result_cb, result_cb_value, + data_cb, data_cb_value, + inq_cb, inq_cb_value, + status_cb, status_cb_value); +} + + gpgme_error_t _gpgme_engine_op_conf_load (engine_t engine, gpgme_conf_comp_t *conf_p) { diff --git a/src/engine.h b/src/engine.h index e67399e..a043b3e 100644 --- a/src/engine.h +++ b/src/engine.h @@ -35,6 +35,9 @@ typedef gpgme_error_t (*engine_command_handler_t) (void *priv, gpgme_status_code_t code, const char *keyword, int fd, int *processed); +typedef gpgme_error_t (*engine_assuan_result_cb_t) (void *priv, + gpgme_error_t result); + /* Get a deep copy of the engine info and return it in INFO. */ gpgme_error_t _gpgme_engine_info_copy (gpgme_engine_info_t *r_info); @@ -126,6 +129,17 @@ gpgme_error_t _gpgme_engine_op_verify (engine_t engine, gpgme_data_t sig, gpgme_error_t _gpgme_engine_op_getauditlog (engine_t engine, gpgme_data_t output, unsigned int flags); +gpgme_error_t _gpgme_engine_op_assuan_transact + (engine_t 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); gpgme_error_t _gpgme_engine_op_conf_load (engine_t engine, gpgme_conf_comp_t *conf_p); diff --git a/src/gpgme.c b/src/gpgme.c index 7fbe5c3..99d27ce 100644 --- a/src/gpgme.c +++ b/src/gpgme.c @@ -192,7 +192,9 @@ gpgme_set_protocol (gpgme_ctx_t ctx, gpgme_protocol_t protocol) protocol, gpgme_get_protocol_name (protocol) ? gpgme_get_protocol_name (protocol) : "unknown"); - if (protocol != GPGME_PROTOCOL_OpenPGP && protocol != GPGME_PROTOCOL_CMS) + if (protocol != GPGME_PROTOCOL_OpenPGP + && protocol != GPGME_PROTOCOL_CMS + && protocol != GPGME_PROTOCOL_ASSUAN) return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); if (ctx->protocol != protocol) @@ -233,6 +235,9 @@ gpgme_get_protocol_name (gpgme_protocol_t protocol) case GPGME_PROTOCOL_CMS: return "CMS"; + case GPGME_PROTOCOL_ASSUAN: + return "Assuan"; + case GPGME_PROTOCOL_UNKNOWN: return "unknown"; diff --git a/src/gpgme.def b/src/gpgme.def index c54549e..835177e 100644 --- a/src/gpgme.def +++ b/src/gpgme.def @@ -167,5 +167,9 @@ EXPORTS gpgme_op_conf_save @130 gpgme_cancel_async @131 + + gpgme_op_assuan_result @132 + gpgme_op_assuan_transact_start @133 + gpgme_op_assuan_transact @134 ; END diff --git a/src/gpgme.h.in b/src/gpgme.h.in index 9dc230c..4b68d80 100644 --- a/src/gpgme.h.in +++ b/src/gpgme.h.in @@ -1,6 +1,6 @@ /* gpgme.h - Public interface to GnuPG Made Easy. -*- c -*- Copyright (C) 2000 Werner Koch (dd9jn) - Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007 g10 Code GmbH + Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, 2009 g10 Code GmbH This file is part of GPGME. @@ -301,6 +301,7 @@ typedef enum GPGME_PROTOCOL_OpenPGP = 0, /* The default mode. */ GPGME_PROTOCOL_CMS = 1, GPGME_PROTOCOL_GPGCONF = 2, /* Special code for gpgconf. */ + GPGME_PROTOCOL_ASSUAN = 3, /* Low-level access to an Assuan server. */ GPGME_PROTOCOL_UNKNOWN = 255 } gpgme_protocol_t; @@ -746,6 +747,8 @@ typedef gpgme_error_t (*gpgme_edit_cb_t) (void *opaque, gpgme_status_code_t status, const char *args, int fd); + + /* Context management functions. */ @@ -1656,6 +1659,51 @@ gpgme_error_t gpgme_op_getauditlog_start (gpgme_ctx_t ctx, gpgme_data_t output, gpgme_error_t gpgme_op_getauditlog (gpgme_ctx_t ctx, gpgme_data_t output, unsigned int flags); + + +/* Low-level Assuan protocol access. */ +typedef gpgme_error_t (*gpgme_assuan_data_cb_t) + (void *opaque, const void *data, size_t datalen); + +struct _gpgme_assuan_sendfnc_ctx; +typedef struct _gpgme_assuan_sendfnc_ctx *gpgme_assuan_sendfnc_ctx_t; +typedef gpgme_error_t (*gpgme_assuan_sendfnc_t) + (gpgme_assuan_sendfnc_ctx_t ctx, const void *data, size_t datalen); + +typedef gpgme_error_t (*gpgme_assuan_inquire_cb_t) + (void *opaque, const char *name, const char *args, + gpgme_assuan_sendfnc_t sendfnc, + gpgme_assuan_sendfnc_ctx_t sendfnc_ctx); + +typedef gpgme_error_t (*gpgme_assuan_status_cb_t) + (void *opaque, const char *status, const char *args); + +/* Return the result of the last Assuan command. */ +gpgme_error_t gpgme_op_assuan_result (gpgme_ctx_t ctx); + +/* Send the Assuan COMMAND and return results via the callbacks. + Asynchronous variant. */ +gpgme_error_t gpgme_op_assuan_transact_start (gpgme_ctx_t ctx, + const char *command, + 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 stat_cb, + void *stat_cb_value); + +/* Send the Assuan COMMAND and return results via the callbacks. + Synchronous variant. */ +gpgme_error_t gpgme_op_assuan_transact (gpgme_ctx_t ctx, + const char *command, + 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 stat_cb, + void *stat_cb_value); + + /* Interface to gpgconf(1). */ diff --git a/src/libgpgme.vers b/src/libgpgme.vers index f0de90e..1653a63 100644 --- a/src/libgpgme.vers +++ b/src/libgpgme.vers @@ -1,5 +1,5 @@ # libgpgme.vers - List of symbols to export. -# Copyright (C) 2002, 2004, 2005 g10 Code GmbH +# Copyright (C) 2002, 2004, 2005, 2009 g10 Code GmbH # # This file is part of GPGME. # @@ -14,8 +14,7 @@ # 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 +# License along with this program; if not, see . #------------------------------------------------------- # Please remember to add new functions also to gpgme.def @@ -48,6 +47,10 @@ GPGME_1.1 { gpgme_op_conf_save; gpgme_cancel_async; + + gpgme_op_assuan_result; + gpgme_op_assuan_transact; + gpgme_op_assuan_transact_start; }; diff --git a/src/opassuan.c b/src/opassuan.c new file mode 100644 index 0000000..f07dade --- /dev/null +++ b/src/opassuan.c @@ -0,0 +1,158 @@ +/* opassuan.c - Low-level Assuan operations. + Copyright (C) 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, see . + */ + +#if HAVE_CONFIG_H +#include +#endif + +#include "gpgme.h" +#include "context.h" +#include "ops.h" +#include "util.h" + +typedef struct +{ + /* The result of the assuan command with 0 for OK and an error value + for ERR. */ + gpgme_error_t result; +} *op_data_t; + + + + +/* This callback is used to return the status of the assuan command + back. Note that this is different from the error code returned + from gpgme_op_assuan_transact because the later only reflects error + with the connection. */ +static gpgme_error_t +result_cb (void *priv, gpgme_error_t result) +{ + gpgme_ctx_t ctx = (gpgme_ctx_t)priv; + gpgme_error_t err; + void *hook; + op_data_t opd; + + err = _gpgme_op_data_lookup (ctx, OPDATA_ASSUAN, &hook, -1, NULL); + opd = hook; + if (err) + return err; + if (!opd) + return gpg_error (GPG_ERR_INTERNAL); + + opd->result = result; + return 0; +} + + +gpgme_error_t +gpgme_op_assuan_result (gpgme_ctx_t ctx) +{ + gpgme_error_t err; + void *hook; + op_data_t opd; + + err = _gpgme_op_data_lookup (ctx, OPDATA_ASSUAN, &hook, -1, NULL); + opd = hook; + if (err) + return err; + if (!opd) + return gpg_error (GPG_ERR_INTERNAL); + + return opd->result; +} + + +static gpgme_error_t +opassuan_start (gpgme_ctx_t ctx, int synchronous, + const char *command, + 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) +{ + gpgme_error_t err; + void *hook; + op_data_t opd; + + if (!command || !*command) + return gpg_error (GPG_ERR_INV_VALUE); + + /* The flag value 256 is used to suppress an engine reset. This is + required to keep the connection running. */ + err = _gpgme_op_reset (ctx, ((synchronous&255) | 256)); + if (err) + return err; + + err = _gpgme_op_data_lookup (ctx, OPDATA_ASSUAN, &hook, sizeof (*opd), NULL); + opd = hook; + if (err) + return err; + opd->result = gpg_error (GPG_ERR_UNFINISHED); + + return _gpgme_engine_op_assuan_transact (ctx->engine, command, + result_cb, ctx, + data_cb, data_cb_value, + inq_cb, inq_cb_value, + status_cb, status_cb_value); +} + + + +/* XXXX. This is the asynchronous variant. */ +gpgme_error_t +gpgme_op_assuan_transact_start (gpgme_ctx_t ctx, + const char *command, + 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) +{ + return opassuan_start (ctx, 0, command, + data_cb, data_cb_value, + inq_cb, inq_cb_value, + status_cb, status_cb_value); +} + + +/* XXXX. This is the synchronous variant. */ +gpgme_error_t +gpgme_op_assuan_transact (gpgme_ctx_t ctx, + const char *command, + 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) +{ + gpgme_error_t err; + + err = opassuan_start (ctx, 1, command, + data_cb, data_cb_value, + inq_cb, inq_cb_value, + status_cb, status_cb_value); + if (!err) + err = _gpgme_wait_one (ctx); + return err; +} + diff --git a/src/util.h b/src/util.h index 0d6a254..a6820e9 100644 --- a/src/util.h +++ b/src/util.h @@ -35,6 +35,11 @@ const char *_gpgme_get_gpgconf_path (void); int _gpgme_get_conf_int (const char *key, int *value); void _gpgme_allow_set_foregound_window (pid_t pid); +/*-- dirinfo.c --*/ +const char *_gpgme_get_default_homedir (void); +const char *_gpgme_get_default_agent_socket (void); + + /*-- replacement functions in .c --*/ #ifdef HAVE_CONFIG_H @@ -111,4 +116,10 @@ int _gpgme_mkstemp (int *fd, char **name); const char *_gpgme_get_w32spawn_path (void); #endif +/*-- Error codes not yet available in current gpg-error.h. --*/ +#ifndef GPG_ERR_UNFINISHED +#define GPG_ERR_UNFINISHED 199 +#endif + + #endif /* UTIL_H */ diff --git a/src/version.c b/src/version.c index 084f2a5..879e2f3 100644 --- a/src/version.c +++ b/src/version.c @@ -179,7 +179,8 @@ gpgme_check_version (const char *req_version) automagically initialize the debug system with out the locks being initialized and missing the assuan log level setting. */ TRACE2 (DEBUG_INIT, "gpgme_check_version: ", 0, - "req_version=%s, VERSION=%s", req_version, VERSION); + "req_version=%s, VERSION=%s", + req_version? req_version:"(null)", VERSION); return _gpgme_compare_versions (VERSION, req_version) ? VERSION : NULL; } diff --git a/tests/ChangeLog b/tests/ChangeLog index a4f6d4f..1248a8f 100644 --- a/tests/ChangeLog +++ b/tests/ChangeLog @@ -1,3 +1,9 @@ +2009-01-26 Werner Koch + + * opassuan/: New. + * opassuan/Makefile.am: New. + * opassuan/t-command.c: New. + 2008-12-03 Marcus Brinkmann * Makefile.am (INCLUDES): Fix path to include file. diff --git a/tests/Makefile.am b/tests/Makefile.am index 19307f2..dc2b037 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -15,8 +15,7 @@ # 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 +# License along with this program; if not, see . ## Process this file with automake to produce Makefile.in @@ -40,7 +39,7 @@ gpgtests = endif if RUN_GPGSM_TESTS -gpgsmtests = gpgsm +gpgsmtests = gpgsm opassuan else gpgsmtests = endif diff --git a/tests/opassuan/Makefile.am b/tests/opassuan/Makefile.am new file mode 100644 index 0000000..2446007 --- /dev/null +++ b/tests/opassuan/Makefile.am @@ -0,0 +1,35 @@ +# Copyright (C) 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, see . + +## Process this file with automake to produce Makefile.in + +TESTS_ENVIRONMENT = GNUPGHOME=$(abs_builddir) GPG_AGENT_INFO= + +noinst_HEADERS = +TESTS = + +EXTRA_DIST = + +INCLUDES = -I$(top_builddir)/src + +AM_CPPFLAGS = @GPG_ERROR_CFLAGS@ +LDADD = ../../src/libgpgme.la + +noinst_PROGRAMS = $(TESTS) t-command + +DISTCLEANFILES = + diff --git a/tests/opassuan/t-command.c b/tests/opassuan/t-command.c new file mode 100644 index 0000000..b6a2d90 --- /dev/null +++ b/tests/opassuan/t-command.c @@ -0,0 +1,121 @@ +/* t-command.c - Regression test. + Copyright (C) 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, see . + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#include + +#define fail_if_err(err) \ + do \ + { \ + if (err) \ + { \ + fprintf (stderr, "%s:%d: %s: %s (%d.%d)\n", \ + __FILE__, __LINE__, gpg_strsource (err), \ + gpg_strerror (err), \ + gpg_err_source (err), gpg_err_code (err)); \ + exit (1); \ + } \ + } \ + while (0) + + +static gpg_error_t +data_cb (void *opaque, const void *data, size_t datalen) +{ + printf ("DATA_CB: datalen=%d\n", (int)datalen); + return 0; +} + + +static gpg_error_t +inq_cb (void *opaque, const char *name, const char *args, + gpgme_assuan_sendfnc_t sendfnc, + gpgme_assuan_sendfnc_ctx_t sendfnc_value) +{ + printf ("INQ_CB: name=`%s' args=`%s'\n", name, args); + + return 0; +} + + +static gpg_error_t +status_cb (void *opaque, const char *status, const char *args) +{ + printf ("STATUS_CB: status=`%s' args=`%s'\n", status, args); + return 0; +} + + + + + + +int +main (int argc, char **argv) +{ + gpgme_error_t err; + gpgme_ctx_t ctx; + const char *command; + + gpgme_check_version (NULL); +#ifndef HAVE_W32_SYSTEM + setlocale (LC_ALL, ""); + gpgme_set_locale (NULL, LC_CTYPE, setlocale (LC_CTYPE, NULL)); + gpgme_set_locale (NULL, LC_MESSAGES, setlocale (LC_MESSAGES, NULL)); +#endif + + if (argc) + { + argc--; + argv++; + } + command = argc? *argv : "NOP"; + + + err = gpgme_new (&ctx); + fail_if_err (err); + + err = gpgme_set_protocol (ctx, GPGME_PROTOCOL_ASSUAN); + fail_if_err (err); + + err = gpgme_op_assuan_transact (ctx, command, + data_cb, NULL, + inq_cb, NULL, + status_cb, NULL); + fail_if_err (err); + err = gpgme_op_assuan_result (ctx); + if (err) + fprintf (stderr, "assuan command `%s' failed: %s <%s> (%d)\n", + command, gpg_strerror (err), gpg_strsource (err), err); + else + fprintf (stderr, "assuan command `%s' succeeded\n", command); + + + gpgme_release (ctx); + + return 0; +} + -- 2.26.2