First take on the low-level assuan interface.
authorWerner Koch <wk@gnupg.org>
Mon, 26 Jan 2009 10:21:10 +0000 (10:21 +0000)
committerWerner Koch <wk@gnupg.org>
Mon, 26 Jan 2009 10:21:10 +0000 (10:21 +0000)
27 files changed:
ChangeLog
Makefile.am
NEWS
autogen.sh
configure.ac
src/ChangeLog
src/Makefile.am
src/context.h
src/dirinfo.c [new file with mode: 0644]
src/engine-assuan.c [new file with mode: 0644]
src/engine-backend.h
src/engine-gpg.c
src/engine-gpgconf.c
src/engine-gpgsm.c
src/engine.c
src/engine.h
src/gpgme.c
src/gpgme.def
src/gpgme.h.in
src/libgpgme.vers
src/opassuan.c [new file with mode: 0644]
src/util.h
src/version.c
tests/ChangeLog
tests/Makefile.am
tests/opassuan/Makefile.am [new file with mode: 0644]
tests/opassuan/t-command.c [new file with mode: 0644]

index 4b1a26c0ab6934c6021f9818348d7603643e22f8..40966da007ab1f48e3937530e3cd10f9815c41e9 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+2009-01-26  Werner Koch  <wk@g10code.com>
+
+       * configure.ac (AC_CONFIG_FILES): Add tests/opassuan/Makefile.
+
 2008-12-08  Marcus Brinkmann  <marcus@g10code.de>
 
        Release GPGME 1.1.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  <marcus@g10code.de>
 
index 432bfca406fec6723eca1103762ead8fe844b2a3..d22ceaec1a151950e5242750cee67aaff3c0f931 100644 (file)
@@ -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 <http://www.gnu.org/licenses/>.
 
 ## Process this file with automake to produce Makefile.in
 
diff --git a/NEWS b/NEWS
index 921879406f72ff5c0f770ef5a3af17189163c4fe..551f117d1027d9506c2d0978a55e6137ed876c3a 100644 (file)
--- 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)
 ------------------------------------------------
 
index b88b6722fbf0f43602ac93deb74f413a29914724..60e52d7440afb448c0444e6d16645ad5c8c62b05 100755 (executable)
@@ -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
+"
index f4b9282068274e700e1817c90319892f765bbbad..5d49c08f043b766a02732ef91104bb865d279e58 100644 (file)
@@ -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)
index 95dc804c0c5e5eff344ae9c10688f6908c450d4c..bf272bcaa6b73b3b12c263b17a9907a02f110809 100644 (file)
@@ -1,3 +1,36 @@
+2009-01-26  Werner Koch  <wk@g10code.com>
+
+       * 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  <wk@g10code.com>
 
        * rungpg.c: Rename to engine-gpg.c
 
        * 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.
 2005-11-27  Marcus Brinkmann  <marcus@g10code.de>
 
        * 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  <marcus@g10code.de>
 
 
        * 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  <marcus@g10code.de>
 
 2002-09-28  Marcus Brinkmann  <marcus@g10code.de>
 
        * 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.
        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  <wk@gnupg.org>
 
        * 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 <stddef.h>.
        (encrypt_sign_status_handler): Change type of ENCRYPT_INFO_LEN to
 
 2002-07-25  Marcus Brinkmann  <marcus@g10code.de>
 
-       * 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.
 
 
 2001-12-19  Marcus Brinkmann  <marcus@g10code.de>
 
-       * 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.
 
        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  <wk@gnupg.org>
 
        * 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 <jose@jaimedelamo.eu.org>
+2001-05-01  José Carlos García Sogo <jose@jaimedelamo.eu.org>
 
        * encrypt.c (gpgme_op_encrypt_start): Deleted the assert ( !c->gpg )
        line, because it gave an error if another operation had been made 
        * 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
index 5a7ca59b1dccfd7d0d7dd5c559aab7d3bb19b5e2..22254f0cdeade0882ab3a328a6fffc5b99c37bfb 100644 (file)
@@ -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)                                  \
index ed5d850211121ee9ef0147ac7875f3d210affe6a..76aeb337f444abfe9635d94dd6897169ed9f3f27 100644 (file)
@@ -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 (file)
index 0000000..45f09c0
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#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 (file)
index 0000000..fb74868
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+/*
+   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 <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <assert.h>
+#include <unistd.h>
+#include <locale.h>
+#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"
+
+\f
+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);
+
+
+
+
+\f
+/* 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";
+}
+
+\f
+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
+  };
index 2e2ef5ecb8016c87b701aa39d42ca5e62d0f85ef..d656d9d6792a77504be410a3c5b8196da88739c3 100644 (file)
@@ -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 <http://www.gnu.org/licenses/>.
+ */
 
 #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 */
index 8be7600016b13ee2faa8024cfab552eb0c7d952e..e4334d14a891b8735e1eb48bce688a9364290d92 100644 (file)
@@ -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,
index 3d107d45b130bbc457dd9eb2454b25c11278336a..d1f27c2bbac64fd578b51b76856ce7385308efc9 100644 (file)
@@ -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,
index 936ac2e2bc59bc51ba8a7ae178e02b1c9bb9a1c1..7179b3c4f921726631f84ef898030c68ba41aec5 100644 (file)
@@ -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,
index cf3fe9fe8f65f6d09dfb1092aa849fd41756e2ed..87d39392fe13a45f3e5accdc005b54ae4108e5ef 100644 (file)
@@ -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 <http://www.gnu.org/licenses/>.
+*/
 
 #ifdef HAVE_CONFIG_H
 #include <config.h>
@@ -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)
 {
index e67399ec00ed3c75a3587707035d007210b85b46..a043b3e232da2130e121d1a5d19383bd3ffdb510 100644 (file)
@@ -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);
index 7fbe5c38f608d7b21a9182e7f3859dc8ffb66f37..99d27ce6a5a3d9f175a0e5de4f8ee2d2f9e99a6c 100644 (file)
@@ -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";
 
index c54549eb15208d6ecbe8e198dde4633dda01ebce..835177efcacb7a1ae0af7c2653859a0f54fa8286 100644 (file)
@@ -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
 
index 9dc230cb0c21c40765224747401531685d9b9251..4b68d801b59d851516fd5563f98ce244f18b56f6 100644 (file)
@@ -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);
 
+
+
 \f
 /* 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);
 
+
+\f
+/* 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);
+
+
 \f
 /* Interface to gpgconf(1).  */
 
index f0de90ef59f946d7f1daaf086e6d4d1f8dbe0b23..1653a63c00eed859d9925d235eb9fade0d5d3463 100644 (file)
@@ -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 <http://www.gnu.org/licenses/>.
 
 #-------------------------------------------------------
 # 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 (file)
index 0000000..f07dade
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#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;
+
+
+
+\f
+/* 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;
+}
+
index 0d6a2543ade10cca22de76f9ed426ed165ef0440..a6820e9774484e0f94cdeacf9c0b53f1accbdb78 100644 (file)
@@ -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);
+
+
 \f
 /*-- replacement functions in <funcname>.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 */
index 084f2a52f8467e8e6cff66f01d846d1eae2af32b..879e2f3ed4606bc8190373cc85ce60ad24124696 100644 (file)
@@ -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;
 }
index a4f6d4f531f328bd0e3860235d17ce1b0bd02f1d..1248a8f2f8f368daf86a4255f393a00513188d6a 100644 (file)
@@ -1,3 +1,9 @@
+2009-01-26  Werner Koch  <wk@g10code.com>
+
+       * opassuan/: New.
+       * opassuan/Makefile.am: New.
+       * opassuan/t-command.c: New.
+
 2008-12-03  Marcus Brinkmann  <marcus@g10code.de>
 
        * Makefile.am (INCLUDES): Fix path to include file.
index 19307f289224d1733d9b25ec24dac146b87a90a7..dc2b03707ff7a27f6ef956c539a49d1599ee0524 100644 (file)
@@ -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 <http://www.gnu.org/licenses/>.
 
 ## 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 (file)
index 0000000..2446007
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+
+## 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 (file)
index 0000000..b6a2d90
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <locale.h>
+
+#include <gpgme.h>
+
+#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;
+}
+