core: Make sure FD_SET is not used with an out of range fd.
[gpgme.git] / src / gpgme-tool.c
index 19dedb6..557ed64 100644 (file)
@@ -1,12 +1,13 @@
 /* gpgme-tool.c - Assuan server exposing GnuPG Made Easy operations.
-   Copyright (C) 2009, 2010 g10 Code GmbH
+   Copyright (C) 2009, 2010, 2012, 2013 g10 Code GmbH
+   Copyright (C) 2001, 2003, 2009, 2011 Free Software Foundation, Inc.
 
    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.
+   under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 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
 #ifdef HAVE_LOCALE_H
 #include <locale.h>
 #endif
-#ifdef HAVE_ARGP_H
-#include <argp.h>
-#endif
 
 #include <assuan.h>
 
+#include "argparse.h"
 #include "gpgme.h"
 
 /* GCC attributes.  */
 
 
 \f
-#ifndef HAVE_ARGP_H
-/* Minimal argp implementation.  */
-
-/* Differences to ARGP:
-   argp_program_version: Required.
-   argp_program_bug_address: Required.
-   argp_program_version_hook: Not supported.
-   argp_err_exit_status: Required.
-   struct argp: Children and help_filter not supported.
-   argp_domain: Not supported.
-   struct argp_option: Group not supported.  Options are printed in
-   order given.  Flags OPTION_ALIAS, OPTION_DOC and OPTION_NO_USAGE
-   are not supported.
-   argp_parse: No flags are supported (ARGP_PARSE_ARGV0, ARGP_NO_ERRS,
-   ARGP_NO_ARGS, ARGP_IN_ORDER, ARGP_NO_HELP, ARGP_NO_EXIT,
-   ARGP_LONG_ONLY, ARGP_SILENT).  ARGP must not be NULL.
-   argp_help: Flag ARGP_HELP_LONG_ONLY not supported.
-   argp_state: argc, argv, next may not be modified and should not be used.  */
-
-extern const char *argp_program_version;
-extern const char *argp_program_bug_address;
-extern error_t argp_err_exit_status;
-
-struct argp_option
-{
-  const char *name;
-  int key;
-  const char *arg;
-#define OPTION_ARG_OPTIONAL 0x1
-#define OPTION_HIDDEN 0x2
-  int flags;
-  const char *doc;
-  int group;
+/* MEMBUF */
+
+/* A simple implementation of a dynamic buffer.  Use init_membuf() to
+   create a buffer, put_membuf to append bytes and get_membuf to
+   release and return the buffer.  Allocation errors are detected but
+   only returned at the final get_membuf(), this helps not to clutter
+   the code with out-of-core checks.  */
+
+/* The definition of the structure is private, we only need it here,
+   so it can be allocated on the stack. */
+struct private_membuf_s
+{
+  size_t len;
+  size_t size;
+  char *buf;
+  int out_of_core;
 };
 
-struct argp;
-struct argp_state
-{
-  const struct argp *const root_argp;
-  int argc;
-  char **argv;
-  int next;
-  unsigned flags;
-  unsigned arg_num;
-  int quoted;
-  void *input;
-  void **child_inputs;
-  void *hook;
-  char *name;
-  FILE *err_stream;
-  FILE *out_stream;
-  void *pstate;
-};
+typedef struct private_membuf_s membuf_t;
 
-#ifdef EDEADLK
-# define ARGP_ERR_UNKNOWN EDEADLK /* POSIX */
-#else
-# define ARGP_ERR_UNKNOWN EDEADLOCK /* *GNU/kFreebsd does not define this) */
-#endif
-#define ARGP_KEY_ARG 0
-#define ARGP_KEY_ARGS 0x1000006
-#define ARGP_KEY_END 0x1000001
-#define ARGP_KEY_NO_ARGS 0x1000002
-#define ARGP_KEY_INIT 0x1000003
-#define ARGP_KEY_FINI 0x1000007
-#define ARGP_KEY_SUCCESS 0x1000004
-#define ARGP_KEY_ERROR 0x1000005
-typedef error_t (*argp_parser_t) (int key, char *arg, struct argp_state *state);
-
-struct argp
-{
-  const struct argp_option *options;
-  argp_parser_t parser;
-  const char *args_doc;
-  const char *doc;
-
-  const struct argp_child *children;
-  char *(*help_filter) (int key, const char *text, void *input);
-  const char *argp_domain;
-};
+/* Return the current length of the membuf.  */
+#define get_membuf_len(a)  ((a)->len)
+#define is_membuf_ready(a) ((a)->buf || (a)->out_of_core)
+#define MEMBUF_ZERO        { 0, 0, NULL, 0}
 
-#define ARGP_HELP_USAGE ARGP_HELP_SHORT_USAGE
-#define ARGP_HELP_SHORT_USAGE 0x02
-#define ARGP_HELP_SEE 0x04
-#define ARGP_HELP_LONG 0x08
-#define ARGP_HELP_PRE_DOC 0x10
-#define ARGP_HELP_POST_DOC 0x20
-#define ARGP_HELP_DOC (ARGP_HELP_PRE_DOC | ARGP_HELP_POST_DOC)
-#define ARGP_HELP_BUG_ADDR 0x40
-#define ARGP_HELP_EXIT_ERR 0x100
-#define ARGP_HELP_EXIT_OK 0x200
-#define ARGP_HELP_STD_ERR (ARGP_HELP_SEE | ARGP_HELP_EXIT_ERR)
-#define ARGP_HELP_STD_USAGE \
-  (ARGP_HELP_SHORT_USAGE | ARGP_HELP_SEE | ARGP_HELP_EXIT_ERR)
-#define ARGP_HELP_STD_HELP \
-  (ARGP_HELP_SHORT_USAGE | ARGP_HELP_LONG | ARGP_HELP_EXIT_OK  \
-   | ARGP_HELP_DOC | ARGP_HELP_BUG_ADDR)
 
-
-void argp_error (const struct argp_state *state,
-                 const char *fmt, ...) GT_GCC_A_PRINTF(2, 3);
-
-
-
-char *
-_argp_pname (char *name)
+static void
+init_membuf (membuf_t *mb, int initiallen)
 {
-  char *pname = name;
-  char *bname = strrchr (pname, '/');
-  if (! bname)
-    bname = strrchr (pname, '\\');
-  if (bname)
-    pname = bname + 1;
-  return pname;
-}
-
-
-void
-_argp_state_help (const struct argp *argp, const struct argp_state *state,
-                 FILE *stream, unsigned flags, char *name)
-{
-  if (state)
-    name = state->name;
-
-  if (flags & ARGP_HELP_SHORT_USAGE)
-    fprintf (stream, "Usage: %s [OPTIONS...] %s\n", name, argp->args_doc);
-  if (flags & ARGP_HELP_SEE)
-    fprintf (stream, "Try `%s --help' or `%s --usage' for more information.\n",
-            name, name);
-  if (flags & ARGP_HELP_PRE_DOC)
-    {
-      char buf[1024];
-      char *end;
-      strncpy (buf, argp->doc, sizeof (buf));
-      buf[sizeof (buf) - 1] = '\0';
-      end = strchr (buf, '\v');
-      if (end)
-       *end = '\0';
-      fprintf (stream, "%s\n%s", buf, buf[0] ? "\n" : "");
-    }
-  if (flags & ARGP_HELP_LONG)
-    {
-      const struct argp_option *opt = argp->options;
-      while (opt->key)
-       {
-         #define NSPACES 29
-         char spaces[NSPACES + 1] = "                              ";
-         int len = 0;
-         fprintf (stream, "  ");
-         len += 2;
-         if (isascii (opt->key))
-           {
-             fprintf (stream, "-%c", opt->key);
-             len += 2;
-             if (opt->name)
-               {
-                 fprintf (stream, ", ");
-                 len += 2;
-               }
-           }
-         if (opt->name)
-           {
-             fprintf (stream, "--%s", opt->name);
-             len += 2 + strlen (opt->name);
-           }
-         if (opt->arg && (opt->flags & OPTION_ARG_OPTIONAL))
-           {
-             fprintf (stream, "[=%s]", opt->arg);
-             len += 3 + strlen (opt->arg);
-           }
-         else if (opt->arg)
-           {
-             fprintf (stream, "=%s", opt->arg);
-             len += 1 + strlen (opt->arg);
-           }
-         if (len >= NSPACES)
-           len = NSPACES - 1;
-         spaces[NSPACES - len] = '\0';
-         fprintf (stream, "%s%s\n", spaces, opt->doc);
-         opt++;
-       }
-      fprintf (stream, "  -?, --help                 Give this help list\n");
-      fprintf (stream, "      --usage                Give a short usage "
-              "message\n");
-    }
-  if (flags & ARGP_HELP_POST_DOC)
-    {
-      char buf[1024];
-      char *end;
-      strncpy (buf, argp->doc, sizeof (buf));
-      buf[sizeof (buf) - 1] = '\0';
-      end = strchr (buf, '\v');
-      if (end)
-       {
-         end++;
-         if (*end)
-           fprintf (stream, "\n%s\n", end);
-       }
-      fprintf (stream, "\nMandatory or optional arguments to long options are also mandatory or optional\n");
-      fprintf (stream, "for any corresponding short options.\n");
-    }
-  if (flags & ARGP_HELP_BUG_ADDR)
-    fprintf (stream, "\nReport bugs to %s.\n", argp_program_bug_address);
-
-  if (flags & ARGP_HELP_EXIT_ERR)
-    exit (argp_err_exit_status);
-  if (flags & ARGP_HELP_EXIT_OK)
-    exit (0);
+  mb->len = 0;
+  mb->size = initiallen;
+  mb->out_of_core = 0;
+  mb->buf = malloc (initiallen);
+  if (!mb->buf)
+    mb->out_of_core = errno;
 }
 
 
-void
-argp_usage (const struct argp_state *state)
+/* Shift the the content of the membuf MB by AMOUNT bytes.  The next
+   operation will then behave as if AMOUNT bytes had not been put into
+   the buffer.  If AMOUNT is greater than the actual accumulated
+   bytes, the membuf is basically reset to its initial state.  */
+#if 0 /* Not yet used.  */
+static void
+clear_membuf (membuf_t *mb, size_t amount)
 {
-  _argp_state_help (state->root_argp, state, state->err_stream,
-                   ARGP_HELP_STD_USAGE, state->name);
+  /* No need to clear if we are already out of core.  */
+  if (mb->out_of_core)
+    return;
+  if (amount >= mb->len)
+    mb->len = 0;
+  else
+    {
+      mb->len -= amount;
+      memmove (mb->buf, mb->buf+amount, mb->len);
+    }
 }
+#endif /* unused */
 
-
-void
-argp_state_help (const struct argp_state *state, FILE *stream, unsigned flags)
+static void
+put_membuf (membuf_t *mb, const void *buf, size_t len)
 {
-  _argp_state_help (state->root_argp, state, stream, flags, state->name);
-}
-
+  if (mb->out_of_core || !len)
+    return;
 
-void
-argp_error (const struct argp_state *state, const char *fmt, ...)
-{
-  va_list ap;
+  if (mb->len + len >= mb->size)
+    {
+      char *p;
 
-  fprintf (state->err_stream, "%s: ", state->name);
-  va_start (ap, fmt);
-  vfprintf (state->err_stream, fmt, ap);
-  va_end (ap);
-  fprintf (state->err_stream, "\n");
-  argp_state_help (state, state->err_stream, ARGP_HELP_STD_ERR);
-  exit (argp_err_exit_status);
+      mb->size += len + 1024;
+      p = realloc (mb->buf, mb->size);
+      if (!p)
+        {
+          mb->out_of_core = errno ? errno : ENOMEM;
+          return;
+        }
+      mb->buf = p;
+    }
+  memcpy (mb->buf + mb->len, buf, len);
+  mb->len += len;
 }
 
 
-void
-argp_help (const struct argp *argp, FILE *stream, unsigned flags, char *name)
+#if 0 /* Not yet used.  */
+static void
+put_membuf_str (membuf_t *mb, const char *string)
 {
-  _argp_state_help (argp, NULL, stream, flags, name);
+  put_membuf (mb, string, strlen (string));
 }
+#endif /* unused */
 
 
-error_t
-argp_parse (const struct argp *argp, int argc,
-           char **argv, unsigned flags, int *arg_index, void *input)
+static void *
+get_membuf (membuf_t *mb, size_t *len)
 {
-  int rc = 0;
-  struct argp_state state = { argp, argc, argv, 1, flags, 0, 0, input,
-                             NULL, NULL, _argp_pname (argv[0]),
-                             stderr, stdout, NULL };
-  /* All non-option arguments are collected at the beginning of
-     &argv[1] during processing.  This is a counter for their number.  */
-  int non_opt_args = 0;
-
-  rc = argp->parser (ARGP_KEY_INIT, NULL, &state);
-  if (rc && rc != ARGP_ERR_UNKNOWN)
-    goto argperror;
+  char *p;
 
-  while (state.next < state.argc - non_opt_args)
+  if (mb->out_of_core)
     {
-      int idx = state.next;
-      state.next++;
-
-      if (! strcasecmp (state.argv[idx], "--"))
-       {
-         state.quoted = idx;
-         continue;
-       }
-
-      if (state.quoted || state.argv[idx][0] != '-')
-       {
-         char *arg_saved = state.argv[idx];
-         non_opt_args++;
-         memmove (&state.argv[idx], &state.argv[idx + 1],
-                  (state.argc - 1 - idx) * sizeof (char *));
-         state.argv[argc - 1] = arg_saved;
-         state.next--;
-       }
-      else if (! strcasecmp (state.argv[idx], "--help")
-              || !strcmp (state.argv[idx], "-?"))
-       {
-         argp_state_help (&state, state.out_stream, ARGP_HELP_STD_HELP);
-       }
-      else if (! strcasecmp (state.argv[idx], "--usage"))
-       {
-         argp_state_help (&state, state.out_stream,
-                          ARGP_HELP_USAGE | ARGP_HELP_EXIT_OK);
-       }
-      else if (! strcasecmp (state.argv[idx], "--version")
-              || !strcmp (state.argv[idx], "-V"))
-       {
-         fprintf (state.out_stream, "%s\n", argp_program_version);
-         exit (0);
-       }
-      else
-       {
-         /* Search for option and call parser with its KEY.  */
-         int key = ARGP_KEY_ARG; /* Just some dummy value.  */
-         const struct argp_option *opt = argp->options;
-         char *arg = NULL;
-         int found = 0;
-
-         /* Check for --opt=value syntax.  */
-         arg = strchr (state.argv[idx], '=');
-         if (arg)
-           {
-             *arg = '\0';
-             arg++;
-           }
-
-         if (state.argv[idx][1] != '-')
-           key = state.argv[idx][1];
-
-         while (! found && opt->key)
-           {
-             if (key == opt->key
-                 || (key == ARGP_KEY_ARG
-                     && ! strcasecmp (&state.argv[idx][2], opt->name)))
-               {
-                 if (arg && !opt->arg)
-                   argp_error (&state, "Option %s does not take an argument",
-                               state.argv[idx]);
-                 if (opt->arg && state.next < state.argc
-                     && state.argv[idx + 1][0] != '-')
-                   {
-                     arg = state.argv[idx + 1];
-                     state.next++;
-                   }
-                 if (opt->arg && !(opt->flags & OPTION_ARG_OPTIONAL))
-                   argp_error (&state, "Option %s requires an argument",
-                               state.argv[idx]);
-
-                 rc = argp->parser (opt->key, arg, &state);
-                 if (rc == ARGP_ERR_UNKNOWN)
-                   break;
-                 else if (rc)
-                   goto argperror;
-                 found = 1;
-               }
-             opt++;
-           }
-         if (! found)
-           argp_error (&state, "Unknown option %s", state.argv[idx]);
-       }
+      if (mb->buf)
+        {
+          free (mb->buf);
+          mb->buf = NULL;
+        }
+      gpg_err_set_errno (mb->out_of_core);
+      return NULL;
     }
 
-  while (state.next < state.argc)
-    {
-      /* Call parser for all non-option args.  */
-      int idx = state.next;
-      state.next++;
-      rc = argp->parser (ARGP_KEY_ARG, state.argv[idx], &state);
-      if (rc && rc != ARGP_ERR_UNKNOWN)
-       goto argperror;
-      if (rc == ARGP_ERR_UNKNOWN)
-       {
-         int old_next = state.next;
-         rc = argp->parser (ARGP_KEY_ARGS, NULL, &state);
-         if (rc == ARGP_ERR_UNKNOWN)
-           {
-             argp_error (&state, "Too many arguments");
-             goto argperror;
-           }
-         if (! rc && state.next == old_next)
-           {
-             state.arg_num += state.argc - state.next;
-             state.next = state.argc;
-           }
-       }
-      else
-       state.arg_num++;
-    }
+  p = mb->buf;
+  if (len)
+    *len = mb->len;
+  mb->buf = NULL;
+  mb->out_of_core = ENOMEM; /* hack to make sure it won't get reused. */
+  return p;
+}
 
-  if (state.arg_num == 0)
-    {
-      rc = argp->parser (ARGP_KEY_NO_ARGS, NULL, &state);
-      if (rc && rc != ARGP_ERR_UNKNOWN)
-       goto argperror;
-    }
-  if (state.next == state.argc)
-    {
-      rc = argp->parser (ARGP_KEY_END, NULL, &state);
-      if (rc && rc != ARGP_ERR_UNKNOWN)
-       goto argperror;
-    }
-  rc = argp->parser (ARGP_KEY_FINI, NULL, &state);
-  if (rc && rc != ARGP_ERR_UNKNOWN)
-    goto argperror;
 
-  rc = 0;
-  argp->parser (ARGP_KEY_SUCCESS, NULL, &state);
+/* Peek at the membuf MB.  On success a pointer to the buffer is
+   returned which is valid until the next operation on MB.  If LEN is
+   not NULL the current LEN of the buffer is stored there.  On error
+   NULL is returned and ERRNO is set.  */
+#if 0 /* Not yet used.  */
+static const void *
+peek_membuf (membuf_t *mb, size_t *len)
+{
+  const char *p;
 
- argperror:
-  if (rc)
+  if (mb->out_of_core)
     {
-      argp_error (&state, "unexpected error: %s", strerror (rc));
-      argp->parser (ARGP_KEY_ERROR, NULL, &state);
+      gpg_err_set_errno (mb->out_of_core);
+      return NULL;
     }
 
-  argp->parser (ARGP_KEY_FINI, NULL, &state);
-
-  if (arg_index)
-    *arg_index = state.next - 1;
-
-  return 0;
+  p = mb->buf;
+  if (len)
+    *len = mb->len;
+  return p;
 }
-#endif
+#endif /* unused */
+
 
 \f
 /* SUPPORT.  */
@@ -502,8 +228,11 @@ log_error (int status, gpg_error_t errnum, const char *fmt, ...)
   vfprintf (log_stream, fmt, ap);
   va_end (ap);
   if (errnum)
-    fprintf (log_stream, ": %s <%s>", gpg_strerror (errnum),
-            gpg_strsource (errnum));
+    {
+      fprintf (log_stream, ": %s", gpg_strerror (errnum));
+      if (gpg_err_source (errnum) != GPG_ERR_SOURCE_GPGME)
+        fprintf (log_stream, " <%s>", gpg_strsource (errnum));
+    }
   fprintf (log_stream, "\n");
   if (status)
     exit (status);
@@ -565,6 +294,12 @@ skip_options (char *line)
 typedef gpg_error_t (*result_xml_write_cb_t) (void *hook, const void *buf,
                                              size_t len);
 
+static char xml_preamble1[] = "<?xml version=\"1.0\" "
+  "encoding=\"UTF-8\" standalone=\"yes\"?>\n";
+static const char xml_preamble2[] = "<gpgme>\n";
+static const char xml_end[] = "</gpgme>\n";
+
+
 struct result_xml_state
 {
   int indent;
@@ -651,12 +386,59 @@ result_xml_tag_start (struct result_xml_state *state, char *name, ...)
   return 0;
 }
 
+/* Return a constant string with an XML entity for C.  */
+static const char *
+result_xml_escape_replacement(char c)
+{
+  switch (c)
+    {
+    case '<':
+      return "&lt;";
+    case '>':
+      return "&gt;";
+    case '&':
+      return "&amp;";
+    default:
+      return NULL;
+    }
+}
+
+/* Escape DATA by replacing certain characters with their XML
+   entities.  The result is stored in a newly allocated buffer which
+   address will be stored at BUF.   Returns 0 on success. */
+static gpg_error_t
+result_xml_escape (const char *data, char **buf)
+{
+  int data_len, i;
+  const char *r;
+  membuf_t mb;
+
+  init_membuf (&mb, 128);
+  if (data)
+    {
+      data_len = strlen (data);
+      for (i = 0; i < data_len; i++)
+        {
+          r = result_xml_escape_replacement (data[i]);
+          if (r)
+            put_membuf (&mb, r, strlen (r));
+          else
+            put_membuf (&mb, data+i, 1);
+        }
+    }
+  put_membuf (&mb, "", 1);
+  *buf = get_membuf (&mb, NULL);
+  return *buf? 0 : gpg_error_from_syserror ();
+}
+
 
 gpg_error_t
-result_xml_tag_data (struct result_xml_state *state, char *data)
+result_xml_tag_data (struct result_xml_state *state, const char *data)
 {
+  gpg_error_t err;
   result_xml_write_cb_t cb = state->cb;
   void *hook = state->hook;
+  char *buf = NULL;
 
   if (state->had_data[state->next_tag - 1])
     {
@@ -668,7 +450,13 @@ result_xml_tag_data (struct result_xml_state *state, char *data)
     (*cb) (hook, ">", 1);
   state->had_data[state->next_tag - 1] = 2;
 
-  (*cb) (hook, data, strlen (data));
+  err = result_xml_escape (data, &buf);
+  if (err)
+    return err;
+
+  (*cb) (hook, buf, strlen (buf));
+
+  free (buf);
 
   return 0;
 }
@@ -708,7 +496,7 @@ result_add_error (struct result_xml_state *state, char *name, gpg_error_t err)
   char code[20];
   char msg[1024];
   snprintf (code, sizeof (code) - 1, "0x%x", err);
-  snprintf (msg, sizeof (msg) - 1, "%s &lt;%s&gt;",
+  snprintf (msg, sizeof (msg) - 1, "%s <%s>",
            gpg_strerror (err), gpg_strsource (err));
   result_xml_tag_start (state, name, "value", code, NULL);
   result_xml_tag_data (state, msg);
@@ -813,6 +601,60 @@ result_add_sig_mode (struct result_xml_state *state, char *name,
 
 
 gpg_error_t
+result_add_protocol (struct result_xml_state *state, char *name,
+                    gpgme_protocol_t protocol)
+{
+  const char *str;
+  char code[20];
+
+  snprintf (code, sizeof (code) - 1, "%i", protocol);
+  str = gpgme_get_protocol_name(protocol);
+  if (!str)
+    str = "invalid";
+  result_xml_tag_start (state, name, "value", code, NULL);
+  result_xml_tag_data (state, str);
+  result_xml_tag_end (state);
+  return 0;
+}
+
+
+gpg_error_t
+result_add_validity (struct result_xml_state *state, char *name,
+                    gpgme_validity_t validity)
+{
+  const char *str;
+  char code[20];
+
+  snprintf (code, sizeof (code) - 1, "%i", validity);
+  switch (validity)
+    {
+    case GPGME_VALIDITY_UNDEFINED:
+      str ="undefined";
+      break;
+    case GPGME_VALIDITY_NEVER:
+      str ="never";
+      break;
+    case GPGME_VALIDITY_MARGINAL:
+      str ="marginal";
+      break;
+    case GPGME_VALIDITY_FULL:
+      str ="full";
+      break;
+    case GPGME_VALIDITY_ULTIMATE:
+      str ="ultimate";
+      break;
+    default:
+      str ="unknown";
+    }
+
+  result_xml_tag_start (state, name, "value", code, NULL);
+  result_xml_tag_data (state, str);
+  result_xml_tag_end (state);
+  return 0;
+}
+
+
+gpg_error_t
 result_add_value (struct result_xml_state *state,
                  char *name, unsigned int val)
 {
@@ -829,6 +671,8 @@ gpg_error_t
 result_add_string (struct result_xml_state *state,
                   char *name, char *str)
 {
+  if (!str)
+    str = "";
   result_xml_tag_start (state, name, NULL);
   result_xml_tag_data (state, str);
   result_xml_tag_end (state);
@@ -1177,7 +1021,8 @@ typedef enum status
     STATUS_INCLUDE_CERTS,
     STATUS_KEYLIST_MODE,
     STATUS_RECIPIENT,
-    STATUS_ENCRYPT_RESULT
+    STATUS_ENCRYPT_RESULT,
+    STATUS_IDENTIFY_RESULT
   } status_t;
 
 const char *status_string[] =
@@ -1190,7 +1035,8 @@ const char *status_string[] =
     "INCLUDE_CERTS",
     "KEYLIST_MODE",
     "RECIPIENT",
-    "ENCRYPT_RESULT"
+    "ENCRYPT_RESULT",
+    "IDENTIFY_RESULT"
   };
 
 struct gpgme_tool
@@ -1211,6 +1057,10 @@ typedef struct gpgme_tool *gpgme_tool_t;
 /* Forward declaration.  */
 void gt_write_status (gpgme_tool_t gt,
                       status_t status, ...) GT_GCC_A_SENTINEL(0);
+static gpg_error_t
+server_passphrase_cb (void *opaque, const char *uid_hint, const char *info,
+                      int was_bad, int fd);
+
 
 void
 _gt_progress_cb (void *opaque, const char *what,
@@ -1240,9 +1090,10 @@ _gt_gpgme_new (gpgme_tool_t gt, gpgme_ctx_t *ctx)
 void
 gt_init (gpgme_tool_t gt)
 {
-  memset (gt, '\0', sizeof (*gt));
   gpg_error_t err;
 
+  memset (gt, '\0', sizeof (*gt));
+
   err = _gt_gpgme_new (gt, &gt->ctx);
   if (err)
     log_error (1, err, "can't create gpgme context");
@@ -1359,7 +1210,7 @@ gt_recipients_add (gpgme_tool_t gt, const char *pattern)
   gpgme_key_t key;
 
   if (gt->recipients_nr >= MAX_RECIPIENTS)
-    return gpg_error_from_errno (ENOMEM);
+    return gpg_error (GPG_ERR_ENOMEM);
 
   if (gpgme_get_protocol (gt->ctx) == GPGME_PROTOCOL_UISERVER)
     err = gpgme_key_from_uid (&key, pattern);
@@ -1477,6 +1328,8 @@ gt_protocol_from_name (const char *name)
     return GPGME_PROTOCOL_G13;
   if (! strcasecmp (name, gpgme_get_protocol_name (GPGME_PROTOCOL_UISERVER)))
     return GPGME_PROTOCOL_UISERVER;
+  if (! strcasecmp (name, gpgme_get_protocol_name (GPGME_PROTOCOL_SPAWN)))
+    return GPGME_PROTOCOL_SPAWN;
   if (! strcasecmp (name, gpgme_get_protocol_name (GPGME_PROTOCOL_DEFAULT)))
     return GPGME_PROTOCOL_DEFAULT;
   return GPGME_PROTOCOL_UNKNOWN;
@@ -1522,6 +1375,19 @@ gt_get_sub_protocol (gpgme_tool_t gt)
 
 
 gpg_error_t
+gt_set_pinentry_mode (gpgme_tool_t gt, gpgme_pinentry_mode_t mode, void *opaque)
+{
+  gpg_error_t err;
+
+  gpgme_set_passphrase_cb (gt->ctx, NULL, NULL);
+  err = gpgme_set_pinentry_mode (gt->ctx, mode);
+  if (!err && mode == GPGME_PINENTRY_MODE_LOOPBACK)
+    gpgme_set_passphrase_cb (gt->ctx, server_passphrase_cb, opaque);
+  return err;
+}
+
+
+gpg_error_t
 gt_set_armor (gpgme_tool_t gt, int armor)
 {
   gpgme_set_armor (gt->ctx, armor);
@@ -1581,6 +1447,8 @@ gt_get_keylist_mode (gpgme_tool_t gt)
     modes[idx++] = "sigs";
   if (mode & GPGME_KEYLIST_MODE_SIG_NOTATIONS)
     modes[idx++] = "sig_notations";
+  if (mode & GPGME_KEYLIST_MODE_WITH_SECRET)
+    modes[idx++] = "with_secret";
   if (mode & GPGME_KEYLIST_MODE_EPHEMERAL)
     modes[idx++] = "ephemeral";
   if (mode & GPGME_KEYLIST_MODE_VALIDATE)
@@ -1635,11 +1503,14 @@ gt_sign_encrypt (gpgme_tool_t gt, gpgme_encrypt_flags_t flags,
                 gpgme_data_t plain, gpgme_data_t cipher, int sign)
 {
   gpg_error_t err;
+  gpgme_key_t *recp;
+
+  recp = gt->recipients_nr? gt->recipients : NULL;
 
   if (sign)
-    err = gpgme_op_encrypt_sign (gt->ctx, gt->recipients, flags, plain, cipher);
+    err = gpgme_op_encrypt_sign (gt->ctx, recp, flags, plain, cipher);
   else
-    err = gpgme_op_encrypt (gt->ctx, gt->recipients, flags, plain, cipher);
+    err = gpgme_op_encrypt (gt->ctx, recp, flags, plain, cipher);
 
   gt_recipients_clear (gt);
 
@@ -1786,11 +1657,6 @@ gt_vfs_create (gpgme_tool_t gt, const char *container_file, int flags)
 }
 
 
-static const char hlp_passwd[] =
-  "PASSWD <user-id>\n"
-  "\n"
-  "Ask the backend to change the passphrase for the key\n"
-  "specified by USER-ID.";
 gpg_error_t
 gt_passwd (gpgme_tool_t gt, char *fpr)
 {
@@ -1807,6 +1673,41 @@ gt_passwd (gpgme_tool_t gt, char *fpr)
 }
 
 
+gpg_error_t
+gt_identify (gpgme_tool_t gt, gpgme_data_t data)
+{
+  const char *s = "?";
+
+  switch (gpgme_data_identify (data, 0))
+    {
+    case GPGME_DATA_TYPE_INVALID: return gpg_error (GPG_ERR_GENERAL);
+    case GPGME_DATA_TYPE_UNKNOWN      : s = "unknown"; break;
+    case GPGME_DATA_TYPE_PGP_SIGNED   : s = "PGP-signed"; break;
+    case GPGME_DATA_TYPE_PGP_OTHER    : s = "PGP"; break;
+    case GPGME_DATA_TYPE_PGP_KEY      : s = "PGP-key"; break;
+    case GPGME_DATA_TYPE_CMS_SIGNED   : s = "CMS-signed"; break;
+    case GPGME_DATA_TYPE_CMS_ENCRYPTED: s = "CMS-encrypted"; break;
+    case GPGME_DATA_TYPE_CMS_OTHER    : s = "CMS"; break;
+    case GPGME_DATA_TYPE_X509_CERT    : s = "X.509"; break;
+    case GPGME_DATA_TYPE_PKCS12       : s = "PKCS12"; break;
+    }
+  gt_write_status (gt, STATUS_IDENTIFY_RESULT, s, NULL);
+  return 0;
+}
+
+
+gpg_error_t
+gt_spawn (gpgme_tool_t gt, const char *pgm,
+          gpgme_data_t inp, gpgme_data_t outp)
+{
+  gpg_error_t err;
+
+  err = gpgme_op_spawn (gt->ctx, pgm, NULL, inp, outp, outp, 0);
+
+  return err;
+}
+
+
 #define GT_RESULT_ENCRYPT 0x1
 #define GT_RESULT_DECRYPT 0x2
 #define GT_RESULT_SIGN 0x4
@@ -1820,10 +1721,6 @@ gt_passwd (gpgme_tool_t gt, char *fpr)
 gpg_error_t
 gt_result (gpgme_tool_t gt, unsigned int flags)
 {
-  static const char xml_preamble1[] = "<?xml version=\"1.0\" "
-    "encoding=\"UTF-8\" standalone=\"yes\"?>\n";
-  static const char xml_preamble2[] = "<gpgme>\n";
-  static const char xml_end[] = "</gpgme>\n";
   int indent = 2;
 
   gt_write_data (gt, xml_preamble1, sizeof (xml_preamble1));
@@ -1900,6 +1797,41 @@ server_write_data (void *hook, const void *buf, size_t len)
 }
 
 
+static gpg_error_t
+server_passphrase_cb (void *opaque, const char *uid_hint, const char *info,
+                      int was_bad, int fd)
+{
+  struct server *server = opaque;
+  gpg_error_t err;
+  unsigned char *buf = NULL;
+  size_t buflen = 0;
+
+  if (server && server->assuan_ctx)
+    {
+      if (uid_hint)
+        assuan_write_status (server->assuan_ctx, "USERID_HINT", uid_hint);
+      if (info)
+        assuan_write_status (server->assuan_ctx, "NEED_PASSPHRASE", info);
+
+      err = assuan_inquire (server->assuan_ctx, "PASSPHRASE",
+                            &buf, &buflen, 100);
+    }
+  else
+    err = gpg_error (GPG_ERR_NO_PASSPHRASE);
+
+  if (!err)
+    {
+      /* We take care to always send a LF.  */
+      if (gpgme_io_writen (fd, buf, buflen))
+        err = gpg_error_from_syserror ();
+      else if (!memchr (buf, '\n', buflen) && gpgme_io_writen (fd, "\n", 1))
+        err = gpg_error_from_syserror ();
+    }
+  free (buf);
+  return err;
+}
+
+
 /* Wrapper around assuan_command_parse_fd to also handle a
    "file=FILENAME" argument.  On success either a filename is returned
    at FILENAME or a file descriptor at RFD; the other one is set to
@@ -2116,6 +2048,39 @@ cmd_sub_protocol (assuan_context_t ctx, char *line)
 }
 
 
+static const char hlp_pinentry_mode[] =
+  "PINENTRY_MODE <name>\n"
+  "\n"
+  "Set the pinentry mode to NAME.   Allowedvalues for NAME are:\n"
+  "  default  - reset to the default of the engine,\n"
+  "  ask      - force the use of the pinentry,\n"
+  "  cancel   - emulate use of pinentry's cancel button,\n"
+  "  error    - return a pinentry error,\n"
+  "  loopback - redirect pinentry queries to the caller.\n"
+  "Note that only recent versions of GPG support changing the pinentry mode.";
+static gpg_error_t
+cmd_pinentry_mode (assuan_context_t ctx, char *line)
+{
+  struct server *server = assuan_get_pointer (ctx);
+  gpgme_pinentry_mode_t mode;
+
+  if (!line || !*line || !strcmp (line, "default"))
+    mode = GPGME_PINENTRY_MODE_DEFAULT;
+  else if (!strcmp (line, "ask"))
+    mode = GPGME_PINENTRY_MODE_ASK;
+  else if (!strcmp (line, "cancel"))
+    mode = GPGME_PINENTRY_MODE_CANCEL;
+  else if (!strcmp (line, "error"))
+    mode = GPGME_PINENTRY_MODE_ERROR;
+  else if (!strcmp (line, "loopback"))
+    mode = GPGME_PINENTRY_MODE_LOOPBACK;
+  else
+    return gpg_error (GPG_ERR_INV_VALUE);
+
+  return gt_set_pinentry_mode (server->gt, mode, server);
+}
+
+
 static const char hlp_armor[] =
   "ARMOR [true|false]\n"
   "\n"
@@ -2214,6 +2179,8 @@ cmd_keylist_mode (assuan_context_t ctx, char *line)
        mode |= GPGME_KEYLIST_MODE_SIGS;
       if (strstr (line, "sig_notations"))
        mode |= GPGME_KEYLIST_MODE_SIG_NOTATIONS;
+      if (strstr (line, "with_secret"))
+       mode |= GPGME_KEYLIST_MODE_WITH_SECRET;
       if (strstr (line, "ephemeral"))
        mode |= GPGME_KEYLIST_MODE_EPHEMERAL;
       if (strstr (line, "validate"))
@@ -2429,6 +2396,8 @@ _cmd_sign_encrypt (assuan_context_t ctx, char *line, int sign)
     flags |= GPGME_ENCRYPT_PREPARE;
   if (strstr (line, "--expect-sign"))
     flags |= GPGME_ENCRYPT_EXPECT_SIGN;
+  if (strstr (line, "--no-compress"))
+    flags |= GPGME_ENCRYPT_NO_COMPRESS;
 
   inp_fd = server->input_fd;
   inp_fn = server->input_filename;
@@ -2465,7 +2434,7 @@ _cmd_sign_encrypt (assuan_context_t ctx, char *line, int sign)
 
 static const char hlp_encrypt[] =
   "ENCRYPT [--always-trust] [--no-encrypt-to]\n"
-  "  [--prepare] [--expect-sign]\n"
+  "  [--no-compress] [--prepare] [--expect-sign]\n"
   "\n"
   "Encrypt the object set by the last INPUT command to\n"
   "the keys specified by previous RECIPIENT commands.  \n"
@@ -2480,7 +2449,7 @@ cmd_encrypt (assuan_context_t ctx, char *line)
 
 static const char hlp_sign_encrypt[] =
   "SIGN_ENCRYPT [--always-trust] [--no-encrypt-to]\n"
-  "  [--prepare] [--expect-sign]\n"
+  "  [--no-compress] [--prepare] [--expect-sign]\n"
   "\n"
   "Sign the object set by the last INPUT command with the\n"
   "keys specified by previous SIGNER commands and encrypt\n"
@@ -2668,7 +2637,7 @@ cmd_import (assuan_context_t ctx, char *line)
 
 
 static const char hlp_export[] =
-  "EXPORT [--extern] [--minimal] [<pattern>]\n"
+  "EXPORT [--extern] [--minimal] [--secret [--pkcs12] [--raw]] [<pattern>]\n"
   "\n"
   "Export the keys described by PATTERN.  Write the\n"
   "the output to the object set by the last OUTPUT command.";
@@ -2696,6 +2665,12 @@ cmd_export (assuan_context_t ctx, char *line)
     mode |= GPGME_EXPORT_MODE_EXTERN;
   if (has_option (line, "--minimal"))
     mode |= GPGME_EXPORT_MODE_MINIMAL;
+  if (has_option (line, "--secret"))
+    mode |= GPGME_EXPORT_MODE_SECRET;
+  if (has_option (line, "--raw"))
+    mode |= GPGME_EXPORT_MODE_RAW;
+  if (has_option (line, "--pkcs12"))
+    mode |= GPGME_EXPORT_MODE_PKCS12;
 
   line = skip_options (line);
 
@@ -2716,7 +2691,7 @@ _cmd_genkey_write (gpgme_data_t data, const void *buf, size_t size)
 {
   while (size > 0)
     {
-      ssize_t writen = gpgme_data_write (data, buf, size);
+      gpgme_ssize_t writen = gpgme_data_write (data, buf, size);
       if (writen < 0 && errno != EAGAIN)
        return gpg_error_from_syserror ();
       else if (writen > 0)
@@ -2772,7 +2747,7 @@ cmd_genkey (assuan_context_t ctx, char *line)
   do
     {
       char buf[512];
-      ssize_t readn = gpgme_data_read (inp_data, buf, sizeof (buf));
+      gpgme_ssize_t readn = gpgme_data_read (inp_data, buf, sizeof (buf));
       if (readn < 0)
        {
          err = gpg_error_from_syserror ();
@@ -2840,9 +2815,11 @@ cmd_keylist (assuan_context_t ctx, char *line)
 {
 #define MAX_CMD_KEYLIST_PATTERN 20
   struct server *server = assuan_get_pointer (ctx);
+  gpgme_tool_t gt = server->gt;
+  struct result_xml_state state;
   gpg_error_t err;
   int secret_only = 0;
-  int idx;
+  int idx, indent=2;
   const char *pattern[MAX_CMD_KEYLIST_PATTERN+1];
   const char optstr[] = "--secret-only";
   char *p;
@@ -2872,10 +2849,19 @@ cmd_keylist (assuan_context_t ctx, char *line)
     }
   pattern[idx] = NULL;
 
+  gt_write_data (gt, xml_preamble1, sizeof (xml_preamble1));
+  gt_write_data (gt, NULL, 0);
+  gt_write_data (gt, xml_preamble2, sizeof (xml_preamble2));
+  gt_write_data (gt, NULL, 0);
+  result_init (&state, indent, (result_xml_write_cb_t) gt_write_data, gt);
+  result_xml_tag_start (&state, "keylist", NULL);
+
   err = gt_keylist_start (server->gt, pattern, secret_only);
   while (! err)
     {
       gpgme_key_t key;
+      gpgme_subkey_t subkey;
+      gpgme_user_id_t uid;
 
       err = gt_keylist_next (server->gt, &key);
       if (gpg_err_code (err) == GPG_ERR_EOF)
@@ -2885,18 +2871,63 @@ cmd_keylist (assuan_context_t ctx, char *line)
        }
       else if (! err)
        {
-         char buf[100];
-         /* FIXME: More data.  */
-         snprintf (buf, sizeof (buf), "key:%s\n", key->subkeys->fpr);
-          /* Write data and flush so that we see one D line for each
-             key.  This does not change the semantics but is easier to
-             read by organic eyes.  */
-         if (!assuan_send_data (ctx, buf, strlen (buf)))
-            assuan_send_data (ctx, NULL, 0);
+         result_xml_tag_start (&state, "key", NULL);
+         result_add_value (&state, "revoked", key->revoked);
+         result_add_value (&state, "expired", key->expired);
+         result_add_value (&state, "disabled", key->disabled);
+         result_add_value (&state, "invalid", key->invalid);
+         result_add_value (&state, "can-encrypt", key->can_encrypt);
+         result_add_value (&state, "can-sign", key->can_sign);
+         result_add_value (&state, "can-certify", key->can_certify);
+         result_add_value (&state, "can-authenticate", key->can_authenticate);
+         result_add_value (&state, "is-qualified", key->is_qualified);
+         result_add_value (&state, "secret", key->secret);
+         result_add_protocol (&state, "protocol", key->protocol);
+         result_xml_tag_start (&state, "issuer", NULL);
+         result_add_string (&state, "serial", key->issuer_serial);
+         result_add_string (&state, "name", key->issuer_name);
+         result_xml_tag_end (&state);  /* issuer */
+         result_add_string (&state, "chain-id", key->chain_id);
+         result_add_validity (&state, "owner-trust", key->owner_trust);
+         result_xml_tag_start (&state, "subkeys", NULL);
+         subkey = key->subkeys;
+         while (subkey) {
+           result_xml_tag_start (&state, "subkey", NULL);
+           /* FIXME: more data */
+           result_add_keyid (&state, "keyid", subkey->keyid);
+            if (subkey->fpr)
+              result_add_fpr (&state, "fpr", subkey->fpr);
+            result_add_value (&state, "secret", subkey->secret);
+            result_add_value (&state, "is_cardkey", subkey->is_cardkey);
+            if (subkey->card_number)
+              result_add_string (&state, "card_number", subkey->card_number);
+            if (subkey->curve)
+              result_add_string (&state, "curve", subkey->curve);
+           result_xml_tag_end (&state);  /* subkey */
+           subkey = subkey->next;
+         }
+         result_xml_tag_end (&state);  /* subkeys */
+         result_xml_tag_start (&state, "uids", NULL);
+         uid = key->uids;
+         while (uid) {
+           result_xml_tag_start (&state, "uid", NULL);
+           /* FIXME: more data */
+           result_add_string (&state, "uid", uid->uid);
+           result_add_string (&state, "name", uid->name);
+           result_add_string (&state, "email", uid->email);
+           result_add_string (&state, "comment", uid->comment);
+           result_xml_tag_end (&state);  /* uid */
+           uid = uid->next;
+         }
+         result_xml_tag_end (&state);  /* uids */
+         result_xml_tag_end (&state);  /* key */
          gpgme_key_unref (key);
        }
     }
 
+  result_xml_tag_end (&state);  /* keylist */
+  gt_write_data (gt, xml_end, sizeof (xml_end));
+
   server_reset_fds (server);
 
   return err;
@@ -2983,6 +3014,11 @@ cmd_vfs_create (assuan_context_t ctx, char *line)
 }
 
 
+static const char hlp_passwd[] =
+  "PASSWD <user-id>\n"
+  "\n"
+  "Ask the backend to change the passphrase for the key\n"
+  "specified by USER-ID.";
 static gpg_error_t
 cmd_passwd (assuan_context_t ctx, char *line)
 {
@@ -3039,6 +3075,88 @@ cmd_hash_algo_name (assuan_context_t ctx, char *line)
 }
 
 
+static const char hlp_identify[] =
+  "IDENTIY\n"
+  "\n"
+  "Identify the type of data set with the INPUT command.";
+static gpg_error_t
+cmd_identify (assuan_context_t ctx, char *line)
+{
+  struct server *server = assuan_get_pointer (ctx);
+  gpg_error_t err;
+  assuan_fd_t inp_fd;
+  char *inp_fn;
+  gpgme_data_t inp_data;
+
+  inp_fd = server->input_fd;
+  inp_fn = server->input_filename;
+  if (inp_fd == ASSUAN_INVALID_FD && !inp_fn)
+    return GPG_ERR_ASS_NO_INPUT;
+
+  err = server_data_obj (inp_fd, inp_fn, 0, server->input_enc, &inp_data,
+                         &server->input_stream);
+  if (err)
+    return err;
+
+  err = gt_identify (server->gt, inp_data);
+
+  gpgme_data_release (inp_data);
+  server_reset_fds (server);
+
+  return err;
+}
+
+
+static const char hlp_spawn[] =
+  "SPAWN PGM [args]\n"
+  "\n"
+  "Run program PGM with stdin connected to the INPUT source;\n"
+  "stdout and stderr to the OUTPUT source.";
+static gpg_error_t
+cmd_spawn (assuan_context_t ctx, char *line)
+{
+  struct server *server = assuan_get_pointer (ctx);
+  gpg_error_t err;
+  assuan_fd_t inp_fd;
+  char *inp_fn;
+  assuan_fd_t out_fd;
+  char *out_fn;
+  gpgme_data_t inp_data = NULL;
+  gpgme_data_t out_data = NULL;
+
+  inp_fd = server->input_fd;
+  inp_fn = server->input_filename;
+  out_fd = server->output_fd;
+  out_fn = server->output_filename;
+  if (inp_fd != ASSUAN_INVALID_FD || inp_fn)
+    {
+      err = server_data_obj (inp_fd, inp_fn, 0, server->input_enc, &inp_data,
+                            &server->input_stream);
+      if (err)
+       return err;
+    }
+  if (out_fd != ASSUAN_INVALID_FD || out_fn)
+    {
+      err = server_data_obj (out_fd, out_fn, 1, server->output_enc, &out_data,
+                            &server->output_stream);
+      if (err)
+       {
+         gpgme_data_release (inp_data);
+         return err;
+       }
+    }
+
+  err = gt_spawn (server->gt, line, inp_data, out_data);
+
+  gpgme_data_release (inp_data);
+  gpgme_data_release (out_data);
+
+  server_reset_fds (server);
+
+  return err;
+}
+
+
 /* Tell the assuan library about our commands.  */
 static gpg_error_t
 register_commands (assuan_context_t ctx)
@@ -3055,6 +3173,7 @@ register_commands (assuan_context_t ctx)
     { "ENGINE", cmd_engine, hlp_engine },
     { "PROTOCOL", cmd_protocol, hlp_protocol },
     { "SUB_PROTOCOL", cmd_sub_protocol, hlp_sub_protocol },
+    { "PINENTRY_MODE", cmd_pinentry_mode, hlp_pinentry_mode },
     { "ARMOR", cmd_armor, hlp_armor },
     { "TEXTMODE", cmd_textmode, hlp_textmode },
     { "INCLUDE_CERTS", cmd_include_certs, hlp_include_certs },
@@ -3096,6 +3215,8 @@ register_commands (assuan_context_t ctx)
     { "PUBKEY_ALGO_NAME", cmd_pubkey_algo_name },
     { "HASH_ALGO_NAME", cmd_hash_algo_name },
     { "PASSWD", cmd_passwd, hlp_passwd },
+    { "IDENTIFY", cmd_identify, hlp_identify },
+    { "SPAWN", cmd_spawn, hlp_spawn },
     { NULL }
   };
   int idx;
@@ -3111,7 +3232,6 @@ register_commands (assuan_context_t ctx)
 }
 
 
-/* TODO: password callback can do INQUIRE.  */
 void
 gpgme_server (gpgme_tool_t gt)
 {
@@ -3185,70 +3305,50 @@ gpgme_server (gpgme_tool_t gt)
 
 
 \f
-/* MAIN PROGRAM STARTS HERE.  */
-
-const char *argp_program_version = VERSION;
-const char *argp_program_bug_address = "bug-gpgme@gnupg.org";
-error_t argp_err_exit_status = 1;
-
-static char doc[] = "GPGME Tool -- Assuan server exposing GPGME operations";
-static char args_doc[] = "COMMAND [OPTIONS...]";
-
-static struct argp_option options[] = {
-  { "server", 's', 0, 0, "Server mode" },
-  { 0 }
-};
-
-static error_t parse_options (int key, char *arg, struct argp_state *state);
-static struct argp argp = { options, parse_options, args_doc, doc };
-
-struct args
+static const char *
+my_strusage( int level )
 {
-  enum { CMD_DEFAULT, CMD_SERVER } cmd;
-};
+  const char *p;
 
-void
-args_init (struct args *args)
-{
-  memset (args, '\0', sizeof (*args));
-  args->cmd = CMD_DEFAULT;
-}
-
-
-static error_t
-parse_options (int key, char *arg, struct argp_state *state)
-{
-  struct args *args = state->input;
-
-  switch (key)
+  switch (level)
     {
-    case 's':
-      args->cmd = CMD_SERVER;
+    case 11: p = "gpgme-tool"; break;
+    case 13: p = PACKAGE_VERSION; break;
+    case 14: p = "Copyright (C) 2015 g10 Code GmbH"; break;
+    case 19: p = "Please report bugs to <" PACKAGE_BUGREPORT ">.\n"; break;
+    case 1:
+    case 40:
+      p = "Usage: gpgme-tool [OPTIONS] [COMMANDS]";
       break;
-#if 0
-    case ARGP_KEY_ARG:
-      if (state->arg_num >= 2)
-       argp_usage (state);
-      printf ("Arg[%i] = %s\n", state->arg_num, arg);
+    case 41:
+      p = "GPGME Tool -- Assuan server exposing GPGME operations\n";
       break;
-    case ARGP_KEY_END:
-      if (state->arg_num < 2)
-       argp_usage (state);
+    case 42:
+      p = "1"; /* Flag print 40 as part of 41. */
       break;
-#endif
-
-    default:
-      return ARGP_ERR_UNKNOWN;
+    default: p = NULL; break;
     }
-  return 0;
+  return p;
 }
 
-\f
+
 int
 main (int argc, char *argv[])
 {
-  struct args args;
+  static ARGPARSE_OPTS opts[] = {
+    ARGPARSE_c  ('s', "server",      "Server mode"),
+    ARGPARSE_s_s(501, "gpg-binary",  "|FILE|Use FILE for the GPG backend"),
+    ARGPARSE_c  (502, "lib-version", "Show library version"),
+    ARGPARSE_end()
+  };
+  ARGPARSE_ARGS pargs = { &argc, &argv, 0 };
+  enum { CMD_DEFAULT, CMD_SERVER, CMD_LIBVERSION } cmd = CMD_DEFAULT;
+  const char *gpg_binary = NULL;
   struct gpgme_tool gt;
+  gpg_error_t err;
+  int needgt = 1;
+
+  set_strusage (my_strusage);
 
 #ifdef HAVE_SETLOCALE
   setlocale (LC_ALL, "");
@@ -3261,22 +3361,56 @@ main (int argc, char *argv[])
   gpgme_set_locale (NULL, LC_MESSAGES, setlocale (LC_MESSAGES, NULL));
 #endif
 
-  args_init (&args);
-
-  argp_parse (&argp, argc, argv, 0, 0, &args);
   log_init ();
 
-  gt_init (&gt);
+  while (arg_parse  (&pargs, opts))
+    {
+      switch (pargs.r_opt)
+        {
+        case 's': cmd = CMD_SERVER; break;
+        case 501: gpg_binary = pargs.r.ret_str; break;
+        case 502: cmd = CMD_LIBVERSION; break;
+        default:
+          pargs.err = ARGPARSE_PRINT_WARNING;
+         break;
+        }
+    }
 
-  switch (args.cmd)
+  if (cmd == CMD_LIBVERSION)
+    needgt = 0;
+
+  if (needgt && gpg_binary)
+    {
+      if (access (gpg_binary, X_OK))
+        err = gpg_error_from_syserror ();
+      else
+        err = gpgme_set_engine_info (GPGME_PROTOCOL_OpenPGP,
+                                     gpg_binary, NULL);
+      if (err)
+        log_error (1, err, "error witching OpenPGP engine to '%s'",
+                   gpg_binary);
+    }
+
+  if (needgt)
+    gt_init (&gt);
+
+  switch (cmd)
     {
     case CMD_DEFAULT:
     case CMD_SERVER:
       gpgme_server (&gt);
       break;
+
+    case CMD_LIBVERSION:
+      printf ("Version from header: %s (0x%06x)\n",
+              GPGME_VERSION, GPGME_VERSION_NUMBER);
+      printf ("Version from binary: %s\n", gpgme_check_version (NULL));
+      printf ("Copyright blurb ...:%s\n", gpgme_check_version ("\x01\x01"));
+      break;
     }
 
-  gpgme_release (gt.ctx);
+  if (needgt)
+    gpgme_release (gt.ctx);
 
 #ifdef HAVE_W32CE_SYSTEM
   /* Give the buggy ssh server time to flush the output buffers.  */
@@ -3285,4 +3419,3 @@ main (int argc, char *argv[])
 
   return 0;
 }
-