2009-10-22 Marcus Brinkmann <marcus@g10code.de>
authorMarcus Brinkmann <mb@g10code.com>
Thu, 22 Oct 2009 16:44:07 +0000 (16:44 +0000)
committerMarcus Brinkmann <mb@g10code.com>
Thu, 22 Oct 2009 16:44:07 +0000 (16:44 +0000)
* configure.ac: Add support for G13.

src/
2009-10-22  Marcus Brinkmann  <marcus@g10code.de>

* Makefile.am: Remove @NETLIBS@ from LIBADDs.
(g13_components): New variable.
(main_sources): Add $(g13_components).
* g13.c, engine-g13.c: New files.
* engine.c (engine_ops): Check for assuan for assuan engine, add
g13 engine.
* util.h (_gpgme_get_g13_path, _gpgme_encode_percent_string): New
prototypes.
* conversion.c (_gpgme_encode_percent_string): New function.
* gpgme.h.in (gpgme_protocol_t): Add GPGME_PROTOCOL_G13.
(struct _gpgme_op_g13_result, gpgme_g13_result_t): New types.
(gpgme_op_g13_mount): New function.
* gpgme.def, libgpgme.vers: Add gpgme_op_g13_mount.
* gpgme.c (gpgme_set_protocol): Allow GPGME_PROTOCOL_G13.
(gpgme_get_protocol_name): Add GPGME_PROTOCOL_G13.
* posix-util.c (_gpgme_get_g13_path): New function.
* w32-util.c (_gpgme_get_g13_path): New function.
* engine-backend.h (_gpgme_engine_ops_g13): New declaration.

18 files changed:
ChangeLog
NEWS
configure.ac
src/ChangeLog
src/Makefile.am
src/conversion.c
src/engine-assuan.c
src/engine-backend.h
src/engine-g13.c [new file with mode: 0644]
src/engine.c
src/g13.c [new file with mode: 0644]
src/gpgme.c
src/gpgme.def
src/gpgme.h.in
src/libgpgme.vers
src/posix-util.c
src/util.h
src/w32-util.c

index ff98830..fc92fa1 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+2009-10-22  Marcus Brinkmann  <marcus@g10code.de>
+
+       * configure.ac: Add support for G13.
+
 2009-10-20  Marcus Brinkmann  <marcus@g10code.de>
 
        * configure.ac (AC_CONFIG_FILES): Remove assuan/Makefile.
diff --git a/NEWS b/NEWS
index f22e197..58e91f6 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -6,11 +6,16 @@ Noteworthy changes in version 1.2.1 (unreleased)
    application programmers on systems that can resolve inter-library
    dependencies at runtime, this is a transparent change.
 
+ * New engine GPGME_PROTOCOL_G13 to support the new g13 tool.
+
  * Interface changes relative to the 1.2.0 release:
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- GPGME_STATUS_INV_SGNR    NEW.
- GPGME_STATUS_NO_SGNR     NEW.
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+GPGME_STATUS_INV_SGNR    NEW
+GPGME_STATUS_NO_SGNR     NEW
+GPGME_PROTOCOL_G13      NEW
+gpgme_op_g13_mount      NEW
+gpgme_g13_result_t      NEW
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 Noteworthy changes in version 1.2.0 (2009-06-18)
 ------------------------------------------------
index e685628..44eb5ed 100644 (file)
@@ -120,6 +120,7 @@ AM_CONDITIONAL(HAVE_LD_VERSION_SCRIPT, test "$have_ld_version_script" = "yes")
 GPG_DEFAULT=no
 GPGSM_DEFAULT=no
 GPGCONF_DEFAULT=no
+G13_DEFAULT=no
 component_system=None
 have_dosish_system=no
 have_w32_system=no
@@ -133,6 +134,7 @@ case "${host}" in
         GPG_DEFAULT='c:\\gnupg\\gpg.exe'
        GPGSM_DEFAULT='c:\\gnupg\\gpgsm.exe'
         GPGCONF_DEFAULT='c:\\gnupg\\gpgconf.exe'
+        G13_DEFAULT='c:\\gnupg\\g13.exe'
         #component_system='COM+'
 
        AM_PATH_GLIB_2_0
@@ -166,6 +168,7 @@ case "${host}" in
 #      GPG_DEFAULT='/usr/bin/gpg'
 #      GPGSM_DEFAULT='/usr/bin/gpgsm'
 #      GPGCONF_DEFAULT='/usr/bin/gpgconf'
+#      G13_DEFAULT='/usr/bin/g13'
        ;;
 esac
 
@@ -286,9 +289,11 @@ fi
 NEED_GPG_VERSION_DEFAULT=1.3.0
 NEED_GPGSM_VERSION_DEFAULT=1.9.6
 NEED_GPGCONF_VERSION_DEFAULT=2.0.4
+NEED_G13_VERSION_DEFAULT=2.1.0
 NEED_GPG_VERSION="$NEED_GPG_VERSION_DEFAULT"
 NEED_GPGSM_VERSION="$NEED_GPGSM_VERSION_DEFAULT"
 NEED_GPGCONF_VERSION="$NEED_GPGCONF_VERSION_DEFAULT"
+NEED_G13_VERSION="$NEED_G13_VERSION_DEFAULT"
 AC_ARG_WITH(gpg-version,
            AC_HELP_STRING([--with-gpg-version=VER], [require GnuPG version VER]),
            NEED_GPG_VERSION=$withval)
@@ -316,6 +321,15 @@ fi
 if test "$NEED_GPGCONF_VERSION" = "no"; then
   NEED_GPGCONF_VERSION=0.0.0
 fi
+AC_ARG_WITH(g13-version,
+           AC_HELP_STRING([--with-g13-version=VER], [require G13 version VER]),
+           NEED_G13_VERSION=$withval)
+if test "$NEED_G13_VERSION" = "yes"; then
+  NEED_G13_VERSION="$NEED_G13_VERSION_DEFAULT"
+fi
+if test "$NEED_G13_VERSION" = "no"; then
+  NEED_G13_VERSION=0.0.0
+fi
 
 AC_DEFINE_UNQUOTED(NEED_GPG_VERSION, "$NEED_GPG_VERSION",
                                     [Min. needed GnuPG version.])
@@ -323,6 +337,8 @@ AC_DEFINE_UNQUOTED(NEED_GPGSM_VERSION, "$NEED_GPGSM_VERSION",
                                       [Min. needed GPGSM version.])
 AC_DEFINE_UNQUOTED(NEED_GPGCONF_VERSION, "$NEED_GPGCONF_VERSION",
                                         [Min. needed GPGCONF version.])
+AC_DEFINE_UNQUOTED(NEED_G13_VERSION, "$NEED_G13_VERSION",
+                                        [Min. needed G13 version.])
 
 
 NO_OVERRIDE=no
@@ -518,6 +534,9 @@ require_libassuan=no
 if test "$GPGSM" != "no"; then
   require_libassuan=yes
 fi
+if test "$G13" != "no"; then
+  require_libassuan=yes
+fi
 
 
 NO_OVERRIDE=no
@@ -623,6 +642,109 @@ if test "$GPGCONF" != "no"; then
 fi
 
 
+NO_OVERRIDE=no
+AC_ARG_WITH(g13,
+           AC_HELP_STRING([--with-g13=PATH],
+                           [use g13 binary at PATH]),
+           G13=$withval, NO_OVERRIDE=yes)
+if test "$NO_OVERRIDE" = "yes" || test "$G13" = "yes"; then
+  G13=
+  NO_OVERRIDE=yes
+  if test "$cross_compiling" != "yes"; then
+    AC_PATH_PROG(G13, g13)
+  fi
+  if test -z "$G13"; then
+    G13="$G13_DEFAULT"
+  fi
+fi
+if test "$G13" = no; then
+  if test "$NO_OVERRIDE" = "yes"; then
+    if test "$cross_compiling" != "yes"; then
+      AC_MSG_WARN([
+***
+*** Could not find g13, install g13 or use --with-g13=PATH to enable it
+***])
+    else
+      AC_MSG_ERROR([
+***
+*** Can not determine path to g13 when cross-compiling, use --with-g13=PATH
+***])
+    fi
+  fi
+else
+  AC_DEFINE_UNQUOTED(G13_PATH, "$G13", [Path to the G13 binary.])
+  AC_DEFINE(ENABLE_G13,1,[Whether G13 support is enabled])
+fi
+AM_CONDITIONAL(HAVE_G13, test "$G13" != "no")
+
+dnl Check for G13 version requirement.
+G13_VERSION=unknown
+ok=maybe
+if test -z "$G13" -o "x$G13" = "xno"; then
+  ok=no
+else
+  if test "$cross_compiling" = "yes"; then
+    AC_MSG_WARN([G13 version can not be checked when cross compiling])
+    ok=no
+  else
+    if test ! -x "$G13"; then
+      AC_MSG_WARN([G13 not executable, version check disabled])
+      ok=no
+    fi
+  fi
+fi
+if test "$ok" = "maybe"; then
+  AC_MSG_CHECKING(for G13 >= $NEED_G13_VERSION)
+  req_major=`echo $NEED_G13_VERSION | \
+             sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\1/'`
+  req_minor=`echo $NEED_G13_VERSION | \
+            sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\2/'`
+  req_micro=`echo $NEED_G13_VERSION | \
+            sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\3/'`
+  G13_VERSION=`$G13 --version | sed -n '1 s/.*\ \([[0-9]].*\)/\1/p'`
+  major=`echo $G13_VERSION | \
+        sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\1/'`
+  minor=`echo $G13_VERSION | \
+        sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\2/'`
+  micro=`echo $G13_VERSION | \
+        sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\3/'`
+
+  if test "$major" -gt "$req_major"; then
+    ok=yes
+  else
+    if test "$major" -eq "$req_major"; then
+      if test "$minor" -gt "$req_minor"; then
+        ok=yes
+      else
+        if test "$minor" -eq "$req_minor"; then
+          if test "$micro" -ge "$req_micro"; then
+            ok=yes
+         fi
+        fi
+      fi
+    fi
+  fi
+  if test "$ok" = "yes"; then
+    AC_MSG_RESULT(yes)
+  else
+    AC_MSG_RESULT(no)
+    AC_MSG_WARN([G13 must be at least version $NEED_G13_VERSION])
+  fi
+fi
+run_g13_test="$ok"
+AC_ARG_ENABLE(g13-test,
+  AC_HELP_STRING([--disable-g13-test], [disable G13 run test]),
+         run_g13_test=$enableval)
+AM_CONDITIONAL(RUN_G13_TESTS, test "$run_g13_test" = "yes")
+
+# Only build if supported.
+AM_CONDITIONAL(BUILD_G13, test "$G13" != "no")
+if test "$G13" != "no"; then
+  AC_DEFINE(HAVE_G13, 1,
+            [Defined if we are building with g13 support.])
+fi
+
+
 # Check for funopen
 AC_CHECK_FUNCS(funopen)
 if test $ac_cv_func_funopen != yes; then
@@ -777,6 +899,9 @@ echo "
        GpgConf path:    $GPGCONF
         GpgConf version: $GPGCONF_VERSION, min. $NEED_GPGCONF_VERSION
 
+       G13 path:        $G13
+        G13 version:     $G13_VERSION, min. $NEED_G13_VERSION
+
         Assuan version:  $LIBASSUAN_VERSION
 
        GPGME Pthread:   $have_pthread
index ee32a25..4214023 100644 (file)
@@ -1,3 +1,24 @@
+2009-10-22  Marcus Brinkmann  <marcus@g10code.de>
+
+       * Makefile.am: Remove @NETLIBS@ from LIBADDs.
+       (g13_components): New variable.
+       (main_sources): Add $(g13_components).
+       * g13.c, engine-g13.c: New files.
+       * engine.c (engine_ops): Check for assuan for assuan engine, add
+       g13 engine.
+       * util.h (_gpgme_get_g13_path, _gpgme_encode_percent_string): New
+       prototypes.
+       * conversion.c (_gpgme_encode_percent_string): New function.
+       * gpgme.h.in (gpgme_protocol_t): Add GPGME_PROTOCOL_G13.
+       (struct _gpgme_op_g13_result, gpgme_g13_result_t): New types.
+       (gpgme_op_g13_mount): New function.
+       * gpgme.def, libgpgme.vers: Add gpgme_op_g13_mount.
+       * gpgme.c (gpgme_set_protocol): Allow GPGME_PROTOCOL_G13.
+       (gpgme_get_protocol_name): Add GPGME_PROTOCOL_G13.
+       * posix-util.c (_gpgme_get_g13_path): New function.
+       * w32-util.c (_gpgme_get_g13_path): New function.
+       * engine-backend.h (_gpgme_engine_ops_g13): New declaration.
+
 2009-10-20  Marcus Brinkmann  <marcus@g10code.de>
 
        * gpgme-config.in (netlibs): Remove.
index 73eeef2..cf88ab1 100644 (file)
@@ -86,6 +86,12 @@ else
 gpgconf_components =
 endif
 
+if HAVE_G13
+g13_components = engine-g13.c
+else
+g13_components =
+endif
+
 # These are the source files common to all library versions.  We used
 # to build a non-installed library for that, but that does not work
 # correctly on all platforms (in particular, one can not specify the
@@ -105,6 +111,7 @@ main_sources =                                                              \
        opassuan.c                                                      \
        engine.h engine-backend.h engine.c engine-gpg.c status-table.h  \
        $(gpgsm_components) $(assuan_components) $(gpgconf_components)  \
+       $(g13_components) g13.c                                         \
        gpgconf.c                                                       \
        sema.h priv-io.h $(system_components) dirinfo.c                 \
        debug.c debug.h gpgme.c version.c error.c
@@ -185,21 +192,20 @@ libgpgme_la_LDFLAGS = $(gpgme_res_ldflag) $(no_undefined) $(export_symbols) \
        $(libgpgme_version_script_cmd) -version-info \
        @LIBGPGME_LT_CURRENT@:@LIBGPGME_LT_REVISION@:@LIBGPGME_LT_AGE@
 libgpgme_la_DEPENDENCIES = @LTLIBOBJS@ $(srcdir)/libgpgme.vers $(gpgme_deps)
-libgpgme_la_LIBADD = @LIBASSUAN_LIBS@ @LTLIBOBJS@ \
-       @GPG_ERROR_LIBS@ @NETLIBS@
+libgpgme_la_LIBADD = @LIBASSUAN_LIBS@ @LTLIBOBJS@ @GPG_ERROR_LIBS@
 
 libgpgme_pthread_la_LDFLAGS = $(libgpgme_version_script_cmd) -version-info \
        @LIBGPGME_LT_CURRENT@:@LIBGPGME_LT_REVISION@:@LIBGPGME_LT_AGE@
 libgpgme_pthread_la_DEPENDENCIES = @LTLIBOBJS@ $(srcdir)/libgpgme.vers
 libgpgme_pthread_la_LIBADD = @LIBASSUAN_LIBS@ @LTLIBOBJS@ \
-       -lpthread @GPG_ERROR_LIBS@ @NETLIBS@
+       -lpthread @GPG_ERROR_LIBS@
 
 libgpgme_pth_la_LDFLAGS = @PTH_LDFLAGS@ \
        $(libgpgme_version_script_cmd) -version-info \
        @LIBGPGME_LT_CURRENT@:@LIBGPGME_LT_REVISION@:@LIBGPGME_LT_AGE@
 libgpgme_pth_la_DEPENDENCIES = @LTLIBOBJS@ $(srcdir)/libgpgme.vers
 libgpgme_pth_la_LIBADD = @LIBASSUAN_LIBS@ @LTLIBOBJS@ \
-       @PTH_LIBS@ @GPG_ERROR_LIBS@ @NETLIBS@
+       @PTH_LIBS@ @GPG_ERROR_LIBS@
 
 if BUILD_W32_GLIB
 libgpgme_glib_la_LDFLAGS = $(gpgme_res_ldflag) $(no_undefined) \
@@ -208,7 +214,7 @@ libgpgme_glib_la_LDFLAGS = $(gpgme_res_ldflag) $(no_undefined) \
 libgpgme_glib_la_DEPENDENCIES =        @LTLIBOBJS@ \
        $(srcdir)/libgpgme.vers $(gpgme_deps)
 libgpgme_glib_la_LIBADD = @LIBASSUAN_LIBS@ @LTLIBOBJS@ \
-       @GPG_ERROR_LIBS@ @GLIB_LIBS@ @NETLIBS@
+       @GPG_ERROR_LIBS@ @GLIB_LIBS@
 endif
 
 if BUILD_W32_QT
@@ -217,7 +223,7 @@ libgpgme_qt_la_LDFLAGS = $(gpgme_res_ldflag) $(no_undefined) \
        @LIBGPGME_LT_CURRENT@:@LIBGPGME_LT_REVISION@:@LIBGPGME_LT_AGE@
 libgpgme_qt_la_DEPENDENCIES = @LTLIBOBJS@ $(srcdir)/libgpgme.vers $(gpgme_deps)
 libgpgme_qt_la_LIBADD = @LIBASSUAN_LIBS@ @LTLIBOBJS@ \
-       @GPG_ERROR_LIBS@ @QT4_CORE_LIBS@ @NETLIBS@
+       @GPG_ERROR_LIBS@ @QT4_CORE_LIBS@
 endif
 
 status-table.h : gpgme.h
index f431238..8b3b715 100644 (file)
@@ -245,6 +245,75 @@ _gpgme_decode_percent_string (const char *src, char **destp, size_t len,
 }
 
 
+/* Encode the string SRC with percent escaping and store the result in
+   the buffer *DESTP which is LEN bytes long.  If LEN is zero, then a
+   large enough buffer is allocated with malloc and *DESTP is set to
+   the result.  Currently, LEN is only used to specify if allocation
+   is desired or not, the caller is expected to make sure that *DESTP
+   is large enough if LEN is not zero.  If BINARY is 1, then '\0'
+   characters are allowed in the output.  */
+gpgme_error_t
+_gpgme_encode_percent_string (const char *src, char **destp, size_t len)
+{
+  size_t destlen;
+  char *dest;
+  const char *str;
+
+  destlen = 0;
+  str = src;
+  /* We percent-escape the + character because the user might need a
+     "percent plus" escaped string (special gpg format).  But we
+     percent-escape the space character, which works with and without
+     the special plus format.  */
+  while (*str)
+    {
+      if (*str == '+' || *str == '\"' || *str == '%' 
+          || *(const unsigned char *)str <= 0x20)
+        destlen += 3;
+      else
+        destlen++;
+    }
+  /* Terminating nul byte.  */
+  destlen++;
+
+  /* Set up the destination buffer.  */
+  if (len)
+    {
+      if (len < destlen);
+       return gpg_error (GPG_ERR_INTERNAL);
+
+      dest = *destp;
+    }
+  else
+    {
+      /* The converted string will never be larger than the original
+        string.  */
+      dest = malloc (destlen);
+      if (!dest)
+       return gpg_error_from_errno (errno);
+
+      *destp = dest;
+    }
+
+  /* Convert the string.  */
+  while (*src)
+    {
+      if (*src == '+' || *src == '\"' || *src == '%' 
+          || *(const unsigned char *)src <= 0x20)
+        {
+          snprintf (dest, 4, "%%%02X", *(unsigned char *)src);
+         dest += 3;
+       }
+      else
+       *(dest++) = *src;
+      src++;
+    }
+  *(dest++) = 0;
+
+  return 0;
+}
+
+
 /* Parse the string TIMESTAMP into a time_t.  The string may either be
    seconds since Epoch or in the ISO 8601 format like
    "20390815T143012".  Returns 0 for an empty string or seconds since
index 74cde99..07a21fa 100644 (file)
@@ -554,6 +554,8 @@ llass_status_handler (void *opaque, int fd)
           TRACE2 (DEBUG_CTX, "gpgme:llass_status_handler", llass,
                  "fd 0x%x: ERR line: %s",
                   fd, err ? gpg_strerror (err) : "ok");
+         /* Command execution errors are not fatal, as we use
+            a session based protocol.  */
           if (llass->result_cb)
             err = llass->result_cb (llass->result_cb_value, err);
           else
index 0a30f34..fa539a3 100644 (file)
 
 #include "engine.h"
 
-/* FIXME: Correct check?  */
-#ifdef GPGSM_PATH
-#define ENABLE_GPGSM 1
-#endif
-
 struct engine_ops
 {
   /* Static functions.  */
@@ -132,5 +127,8 @@ extern struct engine_ops _gpgme_engine_ops_gpgconf; /* gpg-conf.  */
 #ifdef ENABLE_ASSUAN
 extern struct engine_ops _gpgme_engine_ops_assuan;     /* Low-level Assuan. */
 #endif
+#ifdef ENABLE_G13
+extern struct engine_ops _gpgme_engine_ops_g13;         /* Crypto VFS. */
+#endif
 
 #endif /* ENGINE_BACKEND_H */
diff --git a/src/engine-g13.c b/src/engine-g13.c
new file mode 100644 (file)
index 0000000..f957691
--- /dev/null
@@ -0,0 +1,796 @@
+/* engine-g13.c - G13 engine.
+   Copyright (C) 2000 Werner Koch (dd9jn)
+   Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, 2009 g10 Code GmbH
+   This file is part of GPGME.
+
+   GPGME is free software; you can redistribute it and/or modify it
+   under the terms of the GNU Lesser General Public License as
+   published by the Free Software Foundation; either version 2.1 of
+   the License, or (at your option) any later version.
+   
+   GPGME is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+   
+   You should have received a copy of the GNU Lesser General Public
+   License along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA.  */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <assert.h>
+#include <unistd.h>
+#include <locale.h>
+#include <fcntl.h> /* FIXME */
+#include <errno.h>
+
+#include "gpgme.h"
+#include "util.h"
+#include "ops.h"
+#include "wait.h"
+#include "priv-io.h"
+#include "sema.h"
+
+#include "assuan.h"
+#include "debug.h"
+
+#include "engine-backend.h"
+
+\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.  */
+  char server_fd_str[15]; /* Same as SERVER_FD but as a string.  We
+                             need this because _gpgme_io_fd2str can't
+                             be used on a closed descriptor.  */
+} iocb_data_t;
+
+
+struct engine_g13
+{
+  assuan_context_t assuan_ctx;
+
+  int lc_ctype_set;
+  int lc_messages_set;
+
+  iocb_data_t status_cb;
+
+  struct gpgme_io_cbs io_cbs;
+
+  /* Internal callbacks.  */
+  engine_assuan_result_cb_t result_cb;
+  void *result_cb_value; 
+
+  /* User provided callbacks.  */
+  struct {
+    gpgme_assuan_data_cb_t data_cb;
+    void *data_cb_value;
+
+    gpgme_assuan_inquire_cb_t inq_cb;
+    void *inq_cb_value;
+
+    gpgme_assuan_status_cb_t status_cb;
+    void *status_cb_value;
+  } user;
+};
+
+typedef struct engine_g13 *engine_g13_t;
+
+
+static void g13_io_event (void *engine, 
+                            gpgme_event_io_t type, void *type_data);
+
+
+\f
+static char *
+g13_get_version (const char *file_name)
+{
+  return _gpgme_get_program_version (file_name ? file_name
+                                    : _gpgme_get_g13_path ());
+}
+
+
+static const char *
+g13_get_req_version (void)
+{
+  return NEED_G13_VERSION;
+}
+
+\f
+static void
+close_notify_handler (int fd, void *opaque)
+{
+  engine_g13_t g13 = opaque;
+
+  assert (fd != -1);
+  if (g13->status_cb.fd == fd)
+    {
+      if (g13->status_cb.tag)
+       (*g13->io_cbs.remove) (g13->status_cb.tag);
+      g13->status_cb.fd = -1;
+      g13->status_cb.tag = NULL;
+    }
+}
+
+
+/* This is the default inquiry callback.  We use it to handle the
+   Pinentry notifications.  */
+static gpgme_error_t
+default_inq_cb (engine_g13_t g13, const char *keyword, const char *args)
+{
+  gpg_error_t err;
+
+  if (!strcmp (keyword, "PINENTRY_LAUNCHED"))
+    {
+      _gpgme_allow_set_foreground_window ((pid_t)strtoul (args, NULL, 10));
+    }
+
+  if (g13->user.inq_cb)
+    {
+      gpgme_data_t data = NULL;
+
+      err = g13->user.inq_cb (g13->user.inq_cb_value,
+                                keyword, args, &data);
+      if (!err && data)
+        {
+          /* FIXME: Returning data is not yet implemented.  However we
+             need to allow the caller to cleanup his data object.
+             Thus we run the callback in finish mode immediately.  */
+          err = g13->user.inq_cb (g13->user.inq_cb_value,
+                                 NULL, NULL, &data);
+        }
+    }
+  else
+    err = 0;
+
+  return err;
+}
+
+
+static gpgme_error_t
+g13_cancel (void *engine)
+{
+  engine_g13_t g13 = engine;
+
+  if (!g13)
+    return gpg_error (GPG_ERR_INV_VALUE);
+
+  if (g13->status_cb.fd != -1)
+    _gpgme_io_close (g13->status_cb.fd);
+
+  if (g13->assuan_ctx)
+    {
+      assuan_release (g13->assuan_ctx);
+      g13->assuan_ctx = NULL;
+    }
+
+  return 0;
+}
+
+
+static void
+g13_release (void *engine)
+{
+  engine_g13_t g13 = engine;
+
+  if (!g13)
+    return;
+
+  g13_cancel (engine);
+
+  free (g13);
+}
+
+
+static gpgme_error_t
+g13_new (void **engine, const char *file_name, const char *home_dir)
+{
+  gpgme_error_t err = 0;
+  engine_g13_t g13;
+  int argc;
+  char *argv[5];
+  char *dft_display = NULL;
+  char dft_ttyname[64];
+  char *dft_ttytype = NULL;
+  char *optstr;
+
+  g13 = calloc (1, sizeof *g13);
+  if (!g13)
+    return gpg_error_from_errno (errno);
+
+  g13->status_cb.fd = -1;
+  g13->status_cb.dir = 1;
+  g13->status_cb.tag = 0;
+  g13->status_cb.data = g13;
+
+  err = assuan_new (&g13->assuan_ctx);
+  if (err)
+    goto leave;
+
+  argc = 0;
+  argv[argc++] = "g13";
+  if (home_dir)
+    {
+      argv[argc++] = "--homedir";
+      argv[argc++] = home_dir;
+    }
+  argv[argc++] = "--server";
+  argv[argc++] = NULL;
+
+  err = assuan_new_ext (&g13->assuan_ctx, GPG_ERR_SOURCE_GPGME,
+                       &_gpgme_assuan_malloc_hooks, _gpgme_assuan_log_cb,
+                       NULL);
+  if (err)
+    goto leave;
+  assuan_ctx_set_system_hooks (g13->assuan_ctx, &_gpgme_assuan_system_hooks);
+
+#if USE_DESCRIPTOR_PASSING
+  err = assuan_pipe_connect_ext
+    (g13->assuan_ctx, file_name ? file_name : _gpgme_get_g13_path (),
+     argv, NULL, NULL, NULL, 1);
+#else
+  err = assuan_pipe_connect
+    (g13->assuan_ctx, file_name ? file_name : _gpgme_get_g13_path (),
+     argv, NULL);
+#endif
+  if (err)
+    goto leave;
+
+  err = _gpgme_getenv ("DISPLAY", &dft_display);
+  if (err)
+    goto leave;
+  if (dft_display)
+    {
+      if (asprintf (&optstr, "OPTION display=%s", dft_display) < 0)
+        {
+         free (dft_display);
+         err = gpg_error_from_errno (errno);
+         goto leave;
+       }
+      free (dft_display);
+
+      err = assuan_transact (g13->assuan_ctx, optstr, NULL, NULL, NULL,
+                            NULL, NULL, NULL);
+      free (optstr);
+      if (err)
+       goto leave;
+    }
+
+  if (isatty (1))
+    {
+      int rc;
+
+      rc = ttyname_r (1, dft_ttyname, sizeof (dft_ttyname));
+      if (rc)
+       {
+         err = gpg_error_from_errno (rc);
+         goto leave;
+       }
+      else
+       {
+         if (asprintf (&optstr, "OPTION ttyname=%s", dft_ttyname) < 0)
+           {
+             err = gpg_error_from_errno (errno);
+             goto leave;
+           }
+         err = assuan_transact (g13->assuan_ctx, optstr, NULL, NULL, NULL,
+                                NULL, NULL, NULL);
+         free (optstr);
+         if (err)
+           goto leave;
+
+         err = _gpgme_getenv ("TERM", &dft_ttytype);
+         if (err)
+           goto leave;
+         if (dft_ttytype)
+           {
+             if (asprintf (&optstr, "OPTION ttytype=%s", dft_ttytype) < 0)
+               {
+                 free (dft_ttytype);
+                 err = gpg_error_from_errno (errno);
+                 goto leave;
+               }
+             free (dft_ttytype);
+
+             err = assuan_transact (g13->assuan_ctx, optstr, NULL, NULL,
+                                    NULL, NULL, NULL, NULL);
+             free (optstr);
+             if (err)
+               goto leave;
+           }
+       }
+    }
+
+#ifdef HAVE_W32_SYSTEM
+  /* Under Windows we need to use AllowSetForegroundWindow.  Tell
+     g13 to tell us when it needs it.  */
+  if (!err)
+    {
+      err = assuan_transact (g13->assuan_ctx, "OPTION allow-pinentry-notify",
+                             NULL, NULL, NULL, NULL, NULL, NULL);
+      if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION)
+        err = 0; /* This is a new feature of g13.  */
+    }
+#endif /*HAVE_W32_SYSTEM*/
+
+ leave:
+
+  if (err)
+    g13_release (g13);
+  else
+    *engine = g13;
+
+  return err;
+}
+
+
+static gpgme_error_t
+g13_set_locale (void *engine, int category, const char *value)
+{
+  engine_g13_t g13 = engine;
+  gpgme_error_t err;
+  char *optstr;
+  char *catstr;
+
+  /* FIXME: If value is NULL, we need to reset the option to default.
+     But we can't do this.  So we error out here.  G13 needs support
+     for this.  */
+  if (category == LC_CTYPE)
+    {
+      catstr = "lc-ctype";
+      if (!value && g13->lc_ctype_set)
+       return gpg_error (GPG_ERR_INV_VALUE);
+      if (value)
+       g13->lc_ctype_set = 1;
+    }
+#ifdef LC_MESSAGES
+  else if (category == LC_MESSAGES)
+    {
+      catstr = "lc-messages";
+      if (!value && g13->lc_messages_set)
+       return gpg_error (GPG_ERR_INV_VALUE);
+      if (value)
+       g13->lc_messages_set = 1;
+    }
+#endif /* LC_MESSAGES */
+  else
+    return gpg_error (GPG_ERR_INV_VALUE);
+
+  /* FIXME: Reset value to default.  */
+  if (!value) 
+    return 0;
+
+  if (asprintf (&optstr, "OPTION %s=%s", catstr, value) < 0)
+    err = gpg_error_from_errno (errno);
+  else
+    {
+      err = assuan_transact (g13->assuan_ctx, optstr, NULL, NULL,
+                            NULL, NULL, NULL, NULL);
+      free (optstr);
+    }
+
+  return err;
+}
+
+
+/* Forward declaration.  */
+static gpgme_status_code_t parse_status (const char *name);
+
+static gpgme_error_t
+g13_assuan_simple_command (assuan_context_t ctx, char *cmd,
+                            engine_status_handler_t status_fnc,
+                            void *status_fnc_value)
+{
+  gpg_error_t err;
+  char *line;
+  size_t linelen;
+
+  err = assuan_write_line (ctx, cmd);
+  if (err)
+    return err;
+
+  do
+    {
+      err = assuan_read_line (ctx, &line, &linelen);
+      if (err)
+       return err;
+
+      if (*line == '#' || !linelen)
+       continue;
+
+      if (linelen >= 2
+         && line[0] == 'O' && line[1] == 'K'
+         && (line[2] == '\0' || line[2] == ' '))
+       return 0;
+      else if (linelen >= 4
+         && line[0] == 'E' && line[1] == 'R' && line[2] == 'R'
+         && line[3] == ' ')
+       err = atoi (&line[4]);
+      else if (linelen >= 2
+              && line[0] == 'S' && line[1] == ' ')
+       {
+         char *rest;
+         gpgme_status_code_t r;
+
+         rest = strchr (line + 2, ' ');
+         if (!rest)
+           rest = line + linelen; /* set to an empty string */
+         else
+           *(rest++) = 0;
+
+         r = parse_status (line + 2);
+
+         if (r >= 0 && status_fnc)
+           err = status_fnc (status_fnc_value, r, rest);
+         else
+           err = gpg_error (GPG_ERR_GENERAL);
+       }
+      else
+       err = gpg_error (GPG_ERR_GENERAL);
+    }
+  while (!err);
+
+  return err;
+}
+
+
+static gpgme_error_t
+status_handler (void *opaque, int fd)
+{
+  gpgme_error_t err = 0;
+  engine_g13_t g13 = opaque;
+  char *line;
+  size_t linelen;
+
+  do
+    {
+      err = assuan_read_line (g13->assuan_ctx, &line, &linelen);
+      if (err)
+       {
+         /* Try our best to terminate the connection friendly.  */
+         /*      assuan_write_line (g13->assuan_ctx, "BYE"); */
+          TRACE2 (DEBUG_CTX, "gpgme:status_handler", g13,
+                 "fd 0x%x: error reading assuan line: %s",
+                  fd, gpg_strerror (err));
+       }
+      else if (linelen >= 3
+              && line[0] == 'E' && line[1] == 'R' && line[2] == 'R'
+              && (line[3] == '\0' || line[3] == ' '))
+       {
+         if (line[3] == ' ')
+           err = atoi (&line[4]);
+         if (! err)
+           err = gpg_error (GPG_ERR_GENERAL);
+          TRACE2 (DEBUG_CTX, "gpgme:status_handler", g13,
+                 "fd 0x%x: ERR line: %s",
+                  fd, err ? gpg_strerror (err) : "ok");
+         
+         /* In g13, command execution errors are not fatal, as we use
+            a session based protocol.  */
+          if (g13->result_cb)
+            err = g13->result_cb (g13->result_cb_value, err);
+          else
+            err = 0;
+          if (!err)
+            {
+              _gpgme_io_close (g13->status_cb.fd);
+              return 0;
+           }
+       }
+      else if (linelen >= 2
+              && line[0] == 'O' && line[1] == 'K'
+              && (line[2] == '\0' || line[2] == ' '))
+       {
+          TRACE1 (DEBUG_CTX, "gpgme:status_handler", g13,
+                 "fd 0x%x: OK line", fd);
+          if (g13->result_cb)
+            err = g13->result_cb (g13->result_cb_value, 0);
+          else
+            err = 0;
+         if (!err)
+            {
+              _gpgme_io_close (g13->status_cb.fd);
+              return 0;
+            }
+       }
+      else if (linelen > 2
+              && line[0] == 'D' && line[1] == ' ')
+        {
+         /* We are using the colon handler even for plain inline data
+             - strange name for that function but for historic reasons
+             we keep it.  */
+          /* FIXME We can't use this for binary data because we
+             assume this is a string.  For the current usage of colon
+             output it is correct.  */
+          char *src = line + 2;
+         char *end = line + linelen;
+         char *dst = src;
+
+          linelen = 0;
+          while (src < end)
+            {
+              if (*src == '%' && src + 2 < end)
+                {
+                  /* Handle escaped characters.  */
+                  ++src;
+                  *dst++ = _gpgme_hextobyte (src);
+                  src += 2;
+                }
+              else
+                *dst++ = *src++;
+
+              linelen++;
+            }
+
+          src = line + 2;
+          if (linelen && g13->user.data_cb)
+            err = g13->user.data_cb (g13->user.data_cb_value,
+                                       src, linelen);
+          else
+            err = 0;
+
+          TRACE2 (DEBUG_CTX, "gpgme:g13_status_handler", g13,
+                 "fd 0x%x: D inlinedata; status from cb: %s",
+                  fd, (g13->user.data_cb ?
+                       (err? gpg_strerror (err):"ok"):"no callback"));
+
+        }
+      else if (linelen > 2
+              && line[0] == 'S' && line[1] == ' ')
+       {
+         char *src;
+         char *args;
+
+         src = line + 2;
+          while (*src == ' ')
+            src++;
+         
+         args = strchr (line + 2, ' ');
+         if (!args)
+           args = line + linelen; /* set to an empty string */
+         else
+           *(args++) = 0;
+
+          while (*args == ' ')
+            args++;
+
+          if (g13->user.status_cb)
+            err = g13->user.status_cb (g13->user.status_cb_value,
+                                      src, args);
+          else
+            err = 0;
+
+          TRACE3 (DEBUG_CTX, "gpgme:g13_status_handler", g13,
+                 "fd 0x%x: S line (%s) - status from cb: %s",
+                  fd, line+2, (g13->user.status_cb ?
+                               (err? gpg_strerror (err):"ok"):"no callback"));
+       }
+      else if (linelen >= 7
+               && line[0] == 'I' && line[1] == 'N' && line[2] == 'Q'
+               && line[3] == 'U' && line[4] == 'I' && line[5] == 'R'
+               && line[6] == 'E' 
+               && (line[7] == '\0' || line[7] == ' '))
+        {
+          char *src;
+         char *args;
+         
+          for (src=line+7; *src == ' '; src++)
+            ;
+
+         args = strchr (src, ' ');
+         if (!args)
+           args = line + linelen; /* Let it point to an empty string.  */
+         else
+           *(args++) = 0;
+
+          while (*args == ' ')
+            args++;
+
+          err = default_inq_cb (g13, src, args);
+          if (!err) 
+            {
+              /* Flush and send END.  */
+              err = assuan_send_data (g13->assuan_ctx, NULL, 0);
+            }
+          else if (gpg_err_code (err) == GPG_ERR_ASS_CANCELED)
+            {
+              /* Flush and send CANcel.  */
+              err = assuan_send_data (g13->assuan_ctx, NULL, 1);
+            }
+          assuan_write_line (g13->assuan_ctx, "END");
+        }
+    }
+  while (!err && assuan_pending_line (g13->assuan_ctx));
+  
+  return err;
+}
+  
+
+static gpgme_error_t
+add_io_cb (engine_g13_t g13, iocb_data_t *iocbd, gpgme_io_cb_t handler)
+{
+  gpgme_error_t err;
+
+  TRACE_BEG2 (DEBUG_ENGINE, "engine-g13:add_io_cb", g13,
+              "fd %d, dir %d", iocbd->fd, iocbd->dir);
+  err = (*g13->io_cbs.add) (g13->io_cbs.add_priv,
+                             iocbd->fd, iocbd->dir,
+                             handler, iocbd->data, &iocbd->tag);
+  if (err)
+    return TRACE_ERR (err);
+  if (!iocbd->dir)
+    /* FIXME Kludge around poll() problem.  */
+    err = _gpgme_io_set_nonblocking (iocbd->fd);
+  return TRACE_ERR (err);
+}
+
+
+static gpgme_error_t
+start (engine_g13_t g13, const char *command)
+{
+  gpgme_error_t err;
+  int fdlist[5];
+  int nfds;
+
+  /* We need to know the fd used by assuan for reads.  We do this by
+     using the assumption that the first returned fd from
+     assuan_get_active_fds() is always this one.  */
+  nfds = assuan_get_active_fds (g13->assuan_ctx, 0 /* read fds */,
+                                fdlist, DIM (fdlist));
+  if (nfds < 1)
+    return gpg_error (GPG_ERR_GENERAL);        /* FIXME */
+
+  /* We "duplicate" the file descriptor, so we can close it here (we
+     can't close fdlist[0], as that is closed by libassuan, and
+     closing it here might cause libassuan to close some unrelated FD
+     later).  Alternatively, we could special case status_fd and
+     register/unregister it manually as needed, but this increases
+     code duplication and is more complicated as we can not use the
+     close notifications etc.  A third alternative would be to let
+     Assuan know that we closed the FD, but that complicates the
+     Assuan interface.  */
+
+  g13->status_cb.fd = _gpgme_io_dup (fdlist[0]);
+  if (g13->status_cb.fd < 0)
+    return gpg_error_from_syserror ();
+
+  if (_gpgme_io_set_close_notify (g13->status_cb.fd,
+                                 close_notify_handler, g13))
+    {
+      _gpgme_io_close (g13->status_cb.fd);
+      g13->status_cb.fd = -1;
+      return gpg_error (GPG_ERR_GENERAL);
+    }
+
+  err = add_io_cb (g13, &g13->status_cb, status_handler);
+  if (!err)
+    err = assuan_write_line (g13->assuan_ctx, command);
+
+  if (!err)
+    g13_io_event (g13, GPGME_EVENT_START, NULL);
+
+  return err;
+}
+
+
+#if USE_DESCRIPTOR_PASSING
+static gpgme_error_t
+g13_reset (void *engine)
+{
+  engine_g13_t g13 = engine;
+
+  /* We must send a reset because we need to reset the list of
+     signers.  Note that RESET does not reset OPTION commands. */
+  return g13_assuan_simple_command (g13->assuan_ctx, "RESET", NULL, NULL);
+}
+#endif
+
+
+static gpgme_error_t
+g13_transact (void *engine,
+                const char *command,
+                engine_assuan_result_cb_t result_cb,
+                void *result_cb_value,
+                gpgme_assuan_data_cb_t data_cb,
+                void *data_cb_value,
+                gpgme_assuan_inquire_cb_t inq_cb,
+                void *inq_cb_value,
+                gpgme_assuan_status_cb_t status_cb,
+                void *status_cb_value)
+{
+  engine_g13_t g13 = engine;
+  gpgme_error_t err;
+
+  if (!g13 || !command || !*command)
+    return gpg_error (GPG_ERR_INV_VALUE);
+
+  g13->result_cb = result_cb;
+  g13->result_cb_value = result_cb_value;
+  g13->user.data_cb = data_cb;
+  g13->user.data_cb_value = data_cb_value;
+  g13->user.inq_cb = inq_cb;
+  g13->user.inq_cb_value = inq_cb_value;
+  g13->user.status_cb = status_cb;
+  g13->user.status_cb_value = status_cb_value;
+
+  err = start (g13, command);
+  return err;
+}
+
+
+
+static void
+g13_set_io_cbs (void *engine, gpgme_io_cbs_t io_cbs)
+{
+  engine_g13_t g13 = engine;
+  g13->io_cbs = *io_cbs;
+}
+
+
+static void
+g13_io_event (void *engine, gpgme_event_io_t type, void *type_data)
+{
+  engine_g13_t g13 = engine;
+
+  TRACE3 (DEBUG_ENGINE, "gpgme:g13_io_event", g13,
+          "event %p, type %d, type_data %p",
+          g13->io_cbs.event, type, type_data);
+  if (g13->io_cbs.event)
+    (*g13->io_cbs.event) (g13->io_cbs.event_priv, type, type_data);
+}
+
+
+struct engine_ops _gpgme_engine_ops_g13 =
+  {
+    /* Static functions.  */
+    _gpgme_get_g13_path,
+    NULL,
+    g13_get_version,
+    g13_get_req_version,
+    g13_new,
+
+    /* Member functions.  */
+    g13_release,
+#if USE_DESCRIPTOR_PASSING
+    g13_reset,
+#else
+    NULL,                      /* reset */
+#endif
+    NULL,               /* set_status_handler */
+    NULL,              /* set_command_handler */
+    NULL,               /* set_colon_line_handler */
+    g13_set_locale,
+    NULL,               /* decrypt */
+    NULL,               /* delete */
+    NULL,              /* edit */
+    NULL,               /* encrypt */
+    NULL,              /* encrypt_sign */
+    NULL,               /* export */
+    NULL,               /* export_ext */
+    NULL,               /* genkey */
+    NULL,               /* import */
+    NULL,               /* keylist */
+    NULL,               /* keylist_ext */
+    NULL,               /* sign */
+    NULL,              /* trustlist */
+    NULL,               /* verify */
+    NULL,               /* getauditlog */
+    g13_transact,
+    NULL,              /* conf_load */
+    NULL,              /* conf_save */
+    g13_set_io_cbs,
+    g13_io_event,
+    g13_cancel
+  };
index c8ff012..b1d815a 100644 (file)
@@ -55,8 +55,13 @@ static struct engine_ops *engine_ops[] =
 #else
     NULL,
 #endif
-#ifdef ENABLE_GPGSM  /* This indicates that we have assuan support.  */
-    &_gpgme_engine_ops_assuan          /* Low-Level Assuan.  */
+#ifdef ENABLE_ASSUAN
+    &_gpgme_engine_ops_assuan,         /* Low-Level Assuan.  */
+#else
+    NULL
+#endif
+#ifdef ENABLE_G13
+    &_gpgme_engine_ops_g13             /* Crypto VFS.  */
 #else
     NULL
 #endif
@@ -194,7 +199,8 @@ gpgme_get_engine_info (gpgme_engine_info_t *info)
       gpgme_protocol_t proto_list[] = { GPGME_PROTOCOL_OpenPGP,
                                        GPGME_PROTOCOL_CMS,
                                        GPGME_PROTOCOL_GPGCONF,
-                                       GPGME_PROTOCOL_ASSUAN };
+                                       GPGME_PROTOCOL_ASSUAN,
+                                       GPGME_PROTOCOL_G13 };
       unsigned int proto;
 
       for (proto = 0; proto < DIM (proto_list); proto++)
diff --git a/src/g13.c b/src/g13.c
new file mode 100644 (file)
index 0000000..b3f5fee
--- /dev/null
+++ b/src/g13.c
@@ -0,0 +1,229 @@
+/* g13.c - g13 support in GPGME
+   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, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA.  */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+
+#include "gpgme.h"
+#include "context.h"
+#include "ops.h"
+#include "util.h"
+
+typedef struct
+{
+  struct _gpgme_op_g13_result result;
+} *op_data_t;
+
+
+\f
+/* This callback is used to return the status of the assuan command
+   back rather than transmission errors.  */
+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.err = result;
+  return 0;
+}
+
+
+gpgme_g13_result_t
+gpgme_op_g13_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;
+  /* Check in case this function is used without having run a command
+     before.  */
+  if (err || !opd)
+    return NULL;
+
+  return &opd->result;
+}
+
+
+static gpgme_error_t
+opg13_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.err = 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. */
+static gpgme_error_t
+gpgme_op_g13_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 opg13_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. */
+static gpgme_error_t
+gpgme_op_g13_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 = opg13_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;
+}
+
+\f
+/* The actual exported interface follows.  */
+
+static gpg_error_t
+get_err (gpgme_ctx_t ctx)
+{
+  gpgme_g13_result_t res;
+
+  res = gpgme_op_g13_result (ctx);
+  if (! res)
+    return gpg_error (GPG_ERR_GENERAL);
+
+  return res->err;
+}
+
+
+/* The container is automatically unmounted when the context is reset
+   or destroyed.  This is a synchronous convenience interface, which
+   automatically returns an operation error if there is no
+   transmission error.  */
+gpgme_error_t
+gpgme_op_g13_mount (gpgme_ctx_t ctx, const char *container_file,
+                   const char *mount_dir, int flags)
+{
+  gpg_error_t err;
+  char *cmd;
+  char *container_file_esc = NULL;
+  
+  err = _gpgme_encode_percent_string (container_file, &container_file_esc, 0);
+  if (err)
+    return err;
+
+  if (asprintf (&cmd, "OPEN -- %s", container_file_esc) < 0)
+    {
+      err = gpg_error_from_syserror ();
+      free (container_file_esc);
+      return err;
+    }
+  free (container_file_esc);
+
+  err = gpgme_op_g13_transact (ctx, cmd, NULL, NULL, NULL, NULL,
+                              NULL, NULL);
+  free (cmd);
+  err = err || get_err (ctx);
+  if (err)
+    return err;
+
+  if (mount_dir)
+    {
+      char *mount_dir_esc = NULL;
+
+      err = _gpgme_encode_percent_string (mount_dir, &mount_dir_esc, 0);
+      if (err)
+       return err;
+
+      if (asprintf (&cmd, "MOUNT -- %s", mount_dir_esc) < 0)
+       {
+         err = gpg_error_from_syserror ();
+         free (mount_dir_esc);
+         return err;
+       }
+      free (mount_dir_esc);
+    }
+  else
+    {
+      if (asprintf (&cmd, "MOUNT") < 0)
+       return gpg_error_from_syserror ();
+    }
+    
+  err = gpgme_op_g13_transact (ctx, cmd, NULL, NULL, NULL, NULL,
+                              NULL, NULL);
+  free (cmd);
+
+  /* Note: in symmetry with the asynchronous variant, we don't return 
+     the error in the result structure here, if any.  */
+  return err;
+}
index 56caf59..071b394 100644 (file)
@@ -248,7 +248,8 @@ gpgme_set_protocol (gpgme_ctx_t ctx, gpgme_protocol_t protocol)
 
   if (protocol != GPGME_PROTOCOL_OpenPGP
       && protocol != GPGME_PROTOCOL_CMS
-      && protocol != GPGME_PROTOCOL_ASSUAN)
+      && protocol != GPGME_PROTOCOL_ASSUAN
+      && protocol != GPGME_PROTOCOL_G13)
     return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE));
 
   if (ctx->protocol != protocol)
@@ -292,6 +293,9 @@ gpgme_get_protocol_name (gpgme_protocol_t protocol)
     case GPGME_PROTOCOL_ASSUAN:
       return "Assuan";
 
+    case GPGME_PROTOCOL_G13:
+      return "G13";
+
     case GPGME_PROTOCOL_UNKNOWN:
       return "unknown";
 
index d5195eb..5089a27 100644 (file)
@@ -185,5 +185,7 @@ EXPORTS
     gpgme_op_export_keys                  @142
     gpgme_op_export_keys_start            @143
 
+    gpgme_op_g13_mount                   @144
+
 ; END
 
index 0b42798..45f5c12 100644 (file)
@@ -324,6 +324,7 @@ typedef enum
     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_G13     = 4,
     GPGME_PROTOCOL_UNKNOWN = 255
   }
 gpgme_protocol_t;
@@ -1735,7 +1736,10 @@ typedef gpgme_error_t (*gpgme_assuan_status_cb_t)
 struct _gpgme_op_assuan_result
 {
   /* The result of the actual assuan command.  An OK is indicated by a
-     value of 0 and an ERR by the respective error error value.  */
+     value of 0 and an ERR by the respective error error value.  This
+     is required because assuan operations use a session-based
+     interface.  The error code of the GPGME function calls just
+     reflects transmission errors.  */
   gpgme_error_t err;
 };
 typedef struct _gpgme_op_assuan_result *gpgme_assuan_result_t;
@@ -1768,6 +1772,26 @@ gpgme_error_t gpgme_op_assuan_transact (gpgme_ctx_t ctx,
 
 
 \f
+/* Crypto container support.  */
+struct _gpgme_op_g13_result
+{
+  /* The result of the actual assuan command.  An OK is indicated by a
+     value of 0 and an ERR by the respective error error value.  This
+     is required because assuan operations use a session-based
+     interface.  The error code of the GPGME function calls just
+     reflects transmission errors.  */
+  gpgme_error_t err;
+};
+typedef struct _gpgme_op_g13_result *gpgme_g13_result_t;
+
+/* The container is automatically unmounted when the context is reset
+   or destroyed.  This is a synchronous convenience interface, which
+   automatically returns an operation error if there is no
+   transmission error.  */
+gpgme_error_t gpgme_op_g13_mount (gpgme_ctx_t ctx, const char *container_file,
+                                 const char *mount_dir, int flags);
+
+\f
 /* Interface to gpgconf(1).  */
 
 /* The expert level at which a configuration option or group of
index 41fa1be..7f65d6e 100644 (file)
@@ -64,6 +64,8 @@ GPGME_1.1 {
     gpgme_op_import_keys_start;
     gpgme_op_export_keys;
     gpgme_op_export_keys_start;
+
+    gpgme_op_g13_mount;
 };
 
 
index b0d17b0..86ef5e7 100644 (file)
@@ -59,6 +59,16 @@ _gpgme_get_gpgconf_path (void)
 #endif
 }
 
+const char *
+_gpgme_get_g13_path (void)
+{
+#ifdef G13_PATH
+  return G13_PATH;
+#else
+  return NULL;
+#endif
+}
+
 /* See w32-util.c */
 int
 _gpgme_get_conf_int (const char *key, int *value)
index 5c61e54..c24a592 100644 (file)
@@ -32,6 +32,7 @@
 const char *_gpgme_get_gpg_path (void);
 const char *_gpgme_get_gpgsm_path (void);
 const char *_gpgme_get_gpgconf_path (void);
+const char *_gpgme_get_g13_path (void);
 int _gpgme_get_conf_int (const char *key, int *value);
 void _gpgme_allow_set_foreground_window (pid_t pid);
 
@@ -93,6 +94,9 @@ gpgme_error_t _gpgme_decode_c_string (const char *src, char **destp,
 gpgme_error_t _gpgme_decode_percent_string (const char *src, char **destp,
                                            size_t len, int binary);
 
+gpgme_error_t _gpgme_encode_percent_string (const char *src, char **destp,
+                                           size_t len);
+
 
 /* Parse the string TIMESTAMP into a time_t.  The string may either be
    seconds since Epoch or in the ISO 8601 format like
index a4b713e..d7fc06f 100644 (file)
@@ -362,6 +362,25 @@ _gpgme_get_gpgconf_path (void)
 
 
 const char *
+_gpgme_get_g13_path (void)
+{
+  static char *g13_program;
+
+  LOCK (get_path_lock);
+#if 0
+  if (!g13_program)
+    g13_program = find_program_in_registry ("g13Program");
+#endif
+  if (!g13_program)
+    g13_program = find_program_in_inst_dir ("g13.exe");
+  if (!g13_program)
+    g13_program = find_program_at_standard_place ("GNU\\GnuPG\\g13.exe");
+  UNLOCK (get_path_lock);
+  return g13_program;
+}
+
+
+const char *
 _gpgme_get_w32spawn_path (void)
 {
   static char *w32spawn_program;