gpgme-tool: Use membuf functions to build up strings.
[gpgme.git] / src / gpgme-tool.c
index 59d13e56f9591fa69d9e47a38dc339f240334b1c..eb1fbb86e25f1b6a6ee48b6db4634f8f4ff02869 100644 (file)
@@ -1,18 +1,19 @@
-/* gpgme-tool.c - GnuPG Made Easy.
-   Copyright (C) 2009, 2010 g10 Code GmbH
+/* gpgme-tool.c - Assuan server exposing GnuPG Made Easy operations.
+   Copyright (C) 2009, 2010, 2012 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
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.
-   
+
    You should have received a copy of the GNU Lesser General Public
    License along with this program; if not, see <http://www.gnu.org/licenses/>.
  */
@@ -28,7 +29,9 @@
 #include <getopt.h>
 #include <ctype.h>
 #include <stdarg.h>
+#ifdef HAVE_LOCALE_H
 #include <locale.h>
+#endif
 #ifdef HAVE_ARGP_H
 #include <argp.h>
 #endif
 #include "gpgme.h"
 
 /* GCC attributes.  */
-#if __GNUC__ >= 4 
+#if __GNUC__ >= 4
 # define GT_GCC_A_SENTINEL(a) __attribute__ ((sentinel(a)))
 #else
-# define GT_GCC_A_SENTINEL(a) 
+# define GT_GCC_A_SENTINEL(a)
 #endif
 
 #if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5 )
 # define GT_GCC_A_PRINTF(f, a)
 #endif
 
+#define DIM(v) (sizeof(v)/sizeof((v)[0]))
+#define xtoi_1(p)   (*(p) <= '9'? (*(p)- '0'): \
+                     *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10))
+#define xtoi_2(p)   ((xtoi_1(p) * 16) + xtoi_1((p)+1))
 
 
 \f
@@ -107,7 +114,11 @@ struct argp_state
   void *pstate;
 };
 
-#define ARGP_ERR_UNKNOWN E2BIG
+#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
@@ -148,9 +159,9 @@ struct argp
    | ARGP_HELP_DOC | ARGP_HELP_BUG_ADDR)
 
 
-void argp_error (const struct argp_state *state, 
+void argp_error (const struct argp_state *state,
                  const char *fmt, ...) GT_GCC_A_PRINTF(2, 3);
-  
+
 
 
 char *
@@ -364,10 +375,10 @@ argp_parse (const struct argp *argp, int argc,
              *arg = '\0';
              arg++;
            }
-           
+
          if (state.argv[idx][1] != '-')
            key = state.argv[idx][1];
-         
+
          while (! found && opt->key)
            {
              if (key == opt->key
@@ -443,7 +454,7 @@ argp_parse (const struct argp *argp, int argc,
   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);
 
@@ -463,12 +474,157 @@ argp_parse (const struct argp *argp, int argc,
 }
 #endif
 
+\f
+/* 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;
+};
+
+typedef struct private_membuf_s membuf_t;
+
+/* 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}
+
+
+static void
+init_membuf (membuf_t *mb, int initiallen)
+{
+  mb->len = 0;
+  mb->size = initiallen;
+  mb->out_of_core = 0;
+  mb->buf = malloc (initiallen);
+  if (!mb->buf)
+    mb->out_of_core = errno;
+}
+
+
+/* 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)
+{
+  /* 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 */
+
+static void
+put_membuf (membuf_t *mb, const void *buf, size_t len)
+{
+  if (mb->out_of_core || !len)
+    return;
+
+  if (mb->len + len >= mb->size)
+    {
+      char *p;
+
+      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;
+}
+
+
+#if 0 /* Not yet used.  */
+static void
+put_membuf_str (membuf_t *mb, const char *string)
+{
+  put_membuf (mb, string, strlen (string));
+}
+#endif /* unused */
+
+
+static void *
+get_membuf (membuf_t *mb, size_t *len)
+{
+  char *p;
+
+  if (mb->out_of_core)
+    {
+      if (mb->buf)
+        {
+          free (mb->buf);
+          mb->buf = NULL;
+        }
+      gpg_err_set_errno (mb->out_of_core);
+      return NULL;
+    }
+
+  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;
+}
+
+
+/* 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;
+
+  if (mb->out_of_core)
+    {
+      gpg_err_set_errno (mb->out_of_core);
+      return NULL;
+    }
+
+  p = mb->buf;
+  if (len)
+    *len = mb->len;
+  return p;
+}
+#endif /* unused */
+
+
 \f
 /* SUPPORT.  */
 FILE *log_stream;
 char *program_name = "gpgme-tool";
 
-void log_error (int status, gpg_error_t errnum, 
+#define spacep(p)   (*(p) == ' ' || *(p) == '\t')
+
+
+void log_error (int status, gpg_error_t errnum,
                 const char *fmt, ...) GT_GCC_A_PRINTF(3,4);
 
 
@@ -497,10 +653,67 @@ log_error (int status, gpg_error_t errnum, const char *fmt, ...)
 }
 
 
+/* Note that it is sufficient to allocate the target string D as long
+   as the source string S, i.e.: strlen(s)+1;.  D == S is allowed.  */
+static void
+strcpy_escaped_plus (char *d, const char *s)
+{
+  while (*s)
+    {
+      if (*s == '%' && s[1] && s[2])
+        {
+          s++;
+          *d++ = xtoi_2 (s);
+          s += 2;
+        }
+      else if (*s == '+')
+        *d++ = ' ', s++;
+      else
+        *d++ = *s++;
+    }
+  *d = 0;
+}
+
+
+/* Check whether the option NAME appears in LINE.  */
+static int
+has_option (const char *line, const char *name)
+{
+  const char *s;
+  int n = strlen (name);
+
+  s = strstr (line, name);
+  return (s && (s == line || spacep (s-1)) && (!s[n] || spacep (s+n)));
+}
+
+/* Skip over options.  It is assumed that leading spaces have been
+   removed (this is the case for lines passed to a handler from
+   assuan).  Blanks after the options are also removed.  */
+static char *
+skip_options (char *line)
+{
+  while ( *line == '-' && line[1] == '-' )
+    {
+      while (*line && !spacep (line))
+        line++;
+      while (spacep (line))
+        line++;
+    }
+  return line;
+}
+
+
+
 \f
 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;
@@ -566,7 +779,7 @@ result_xml_tag_start (struct result_xml_state *state, char *name, ...)
   state->had_data[state->next_tag] = 0;
   state->indent += 2;
   state->next_tag++;
-  
+
   while (1)
     {
       attr = va_arg (ap, char *);
@@ -587,12 +800,56 @@ 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);
+  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])
     {
@@ -604,7 +861,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;
 }
@@ -640,11 +903,11 @@ result_xml_tag_end (struct result_xml_state *state)
 
 gpg_error_t
 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);
@@ -656,7 +919,7 @@ result_add_error (struct result_xml_state *state, char *name, gpg_error_t err)
 gpg_error_t
 result_add_pubkey_algo (struct result_xml_state *state,
                        char *name, gpgme_pubkey_algo_t algo)
-{                
+{
   char code[20];
   char msg[80];
   snprintf (code, sizeof (code) - 1, "0x%x", algo);
@@ -672,7 +935,7 @@ result_add_pubkey_algo (struct result_xml_state *state,
 gpg_error_t
 result_add_hash_algo (struct result_xml_state *state,
                         char *name, gpgme_hash_algo_t algo)
-{                
+{
   char code[20];
   char msg[80];
   snprintf (code, sizeof (code) - 1, "0x%x", algo);
@@ -687,7 +950,7 @@ result_add_hash_algo (struct result_xml_state *state,
 
 gpg_error_t
 result_add_keyid (struct result_xml_state *state, char *name, char *keyid)
-{                
+{
   result_xml_tag_start (state, name, NULL);
   result_xml_tag_data (state, keyid);
   result_xml_tag_end (state);
@@ -697,7 +960,7 @@ result_add_keyid (struct result_xml_state *state, char *name, char *keyid)
 
 gpg_error_t
 result_add_fpr (struct result_xml_state *state, char *name, char *fpr)
-{                
+{
   result_xml_tag_start (state, name, NULL);
   result_xml_tag_data (state, fpr);
   result_xml_tag_end (state);
@@ -708,11 +971,11 @@ result_add_fpr (struct result_xml_state *state, char *name, char *fpr)
 gpg_error_t
 result_add_timestamp (struct result_xml_state *state, char *name,
                      unsigned int timestamp)
-{                
+{
   char code[20];
 
   snprintf (code, sizeof (code) - 1, "%ui", timestamp);
-  result_xml_tag_start (state, name, "unix", code);
+  result_xml_tag_start (state, name, "unix", code, NULL);
   result_xml_tag_end (state);
   return 0;
 }
@@ -721,7 +984,7 @@ result_add_timestamp (struct result_xml_state *state, char *name,
 gpg_error_t
 result_add_sig_mode (struct result_xml_state *state, char *name,
                     gpgme_sig_mode_t sig_mode)
-{                
+{
   char *mode;
   char code[20];
 
@@ -748,10 +1011,64 @@ 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)
-{                
+{
   char code[20];
 
   snprintf (code, sizeof (code) - 1, "0x%x", val);
@@ -764,7 +1081,9 @@ result_add_value (struct result_xml_state *state,
 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);
@@ -790,11 +1109,12 @@ result_encrypt_to_xml (gpgme_ctx_t ctx, int indent,
   if (inv_recp)
     {
       result_xml_tag_start (&state, "invalid-recipients", NULL);
-      
+
       while (inv_recp)
        {
          result_xml_tag_start (&state, "invalid-key", NULL);
-         result_add_fpr (&state, "fpr", inv_recp->fpr);
+         if (inv_recp->fpr)
+           result_add_fpr (&state, "fpr", inv_recp->fpr);
          result_add_error (&state, "reason", inv_recp->reason);
          result_xml_tag_end (&state);
          inv_recp = inv_recp->next;
@@ -802,7 +1122,7 @@ result_encrypt_to_xml (gpgme_ctx_t ctx, int indent,
       result_xml_tag_end (&state);
     }
   result_xml_tag_end (&state);
-  
+
   return 0;
 }
 
@@ -855,7 +1175,7 @@ result_decrypt_to_xml (gpgme_ctx_t ctx, int indent,
       result_xml_tag_end (&state);
     }
   result_xml_tag_end (&state);
-  
+
   return 0;
 }
 
@@ -879,11 +1199,12 @@ result_sign_to_xml (gpgme_ctx_t ctx, int indent,
   if (inv_key)
     {
       result_xml_tag_start (&state, "invalid-signers", NULL);
-      
+
       while (inv_key)
        {
          result_xml_tag_start (&state, "invalid-key", NULL);
-         result_add_fpr (&state, "fpr", inv_key->fpr);
+         if (inv_key->fpr)
+           result_add_fpr (&state, "fpr", inv_key->fpr);
          result_add_error (&state, "reason", inv_key->reason);
          result_xml_tag_end (&state);
          inv_key = inv_key->next;
@@ -903,7 +1224,8 @@ result_sign_to_xml (gpgme_ctx_t ctx, int indent,
          result_add_pubkey_algo (&state, "pubkey-algo", new_sig->pubkey_algo);
          result_add_hash_algo (&state, "hash-algo", new_sig->hash_algo);
          result_add_timestamp (&state, "timestamp", new_sig->timestamp);
-         result_add_fpr (&state, "fpr", new_sig->fpr);
+         if (new_sig->fpr)
+           result_add_fpr (&state, "fpr", new_sig->fpr);
          result_add_value (&state, "sig-class", new_sig->sig_class);
 
          result_xml_tag_end (&state);
@@ -913,7 +1235,7 @@ result_sign_to_xml (gpgme_ctx_t ctx, int indent,
     }
 
   result_xml_tag_end (&state);
-  
+
   return 0;
 }
 
@@ -947,12 +1269,13 @@ result_verify_to_xml (gpgme_ctx_t ctx, int indent,
       while (sig)
        {
          result_xml_tag_start (&state, "signature", NULL);
-         
-         // FIXME: Could be done better.
+
+         /* FIXME: Could be done better. */
          result_add_value (&state, "summary", sig->summary);
-         result_add_fpr (&state, "fpr", sig->fpr);
+         if (sig->fpr)
+           result_add_fpr (&state, "fpr", sig->fpr);
          result_add_error (&state, "status", sig->status);
-         // FIXME: notations
+         /* FIXME: notations */
          result_add_timestamp (&state, "timestamp", sig->timestamp);
          result_add_timestamp (&state, "exp-timestamp", sig->exp_timestamp);
          result_add_value (&state, "wrong-key-usage", sig->wrong_key_usage);
@@ -964,7 +1287,7 @@ result_verify_to_xml (gpgme_ctx_t ctx, int indent,
          result_add_hash_algo (&state, "hash-algo", sig->hash_algo);
          if (sig->pka_address)
            result_add_string (&state, "pka_address", sig->pka_address);
-         
+
          result_xml_tag_end (&state);
          sig = sig->next;
        }
@@ -972,7 +1295,7 @@ result_verify_to_xml (gpgme_ctx_t ctx, int indent,
     }
 
   result_xml_tag_end (&state);
-  
+
   return 0;
 }
 
@@ -1010,14 +1333,15 @@ result_import_to_xml (gpgme_ctx_t ctx, int indent,
   if (stat)
     {
       result_xml_tag_start (&state, "imports", NULL);
-      
+
       while (stat)
        {
          result_xml_tag_start (&state, "import-status", NULL);
 
-         result_add_fpr (&state, "fpr", stat->fpr);
+         if (stat->fpr)
+           result_add_fpr (&state, "fpr", stat->fpr);
          result_add_error (&state, "result", stat->result);
-         // FIXME: Could be done better.
+         /* FIXME: Could be done better. */
          result_add_value (&state, "status", stat->status);
 
          result_xml_tag_end (&state);
@@ -1027,7 +1351,7 @@ result_import_to_xml (gpgme_ctx_t ctx, int indent,
     }
 
   result_xml_tag_end (&state);
-  
+
   return 0;
 }
 
@@ -1047,10 +1371,11 @@ result_genkey_to_xml (gpgme_ctx_t ctx, int indent,
 
   result_add_value (&state, "primary", res->primary);
   result_add_value (&state, "sub", res->sub);
-  result_add_fpr (&state, "fpr", res->fpr);
+  if (res->fpr)
+    result_add_fpr (&state, "fpr", res->fpr);
 
   result_xml_tag_end (&state);
-  
+
   return 0;
 }
 
@@ -1071,7 +1396,7 @@ result_keylist_to_xml (gpgme_ctx_t ctx, int indent,
   result_add_value (&state, "truncated", res->truncated);
 
   result_xml_tag_end (&state);
-  
+
   return 0;
 }
 
@@ -1092,7 +1417,7 @@ result_vfs_mount_to_xml (gpgme_ctx_t ctx, int indent,
   result_add_string (&state, "mount-dir", res->mount_dir);
 
   result_xml_tag_end (&state);
-  
+
   return 0;
 }
 
@@ -1139,7 +1464,7 @@ typedef struct gpgme_tool *gpgme_tool_t;
 
 
 /* Forward declaration.  */
-void gt_write_status (gpgme_tool_t gt, 
+void gt_write_status (gpgme_tool_t gt,
                       status_t status, ...) GT_GCC_A_SENTINEL(0);
 
 void
@@ -1211,7 +1536,7 @@ gt_get_key (gpgme_tool_t gt, const char *pattern, gpgme_key_t *r_key)
 
   if (!gt || !r_key || !pattern)
     return gpg_error (GPG_ERR_INV_VALUE);
-  
+
   ctx = gt->ctx;
 
   err = gpgme_new (&listctx);
@@ -1273,10 +1598,10 @@ gt_get_key (gpgme_tool_t gt, const char *pattern, gpgme_key_t *r_key)
        }
     }
   gpgme_release (listctx);
-  
+
   if (! err)
-    gt_write_status (gt, STATUS_RECIPIENT, 
-                    ((*r_key)->subkeys && (*r_key)->subkeys->fpr) ? 
+    gt_write_status (gt, STATUS_RECIPIENT,
+                    ((*r_key)->subkeys && (*r_key)->subkeys->fpr) ?
                     (*r_key)->subkeys->fpr : "invalid", NULL);
   return err;
 }
@@ -1320,7 +1645,7 @@ gt_reset (gpgme_tool_t gt)
 {
   gpg_error_t err;
   gpgme_ctx_t ctx;
-  
+
   err = _gt_gpgme_new (gt, &ctx);
   if (err)
     return err;
@@ -1412,7 +1737,7 @@ gt_protocol_from_name (const char *name)
   return GPGME_PROTOCOL_UNKNOWN;
 }
 
-  
+
 gpg_error_t
 gt_set_protocol (gpgme_tool_t gt, gpgme_protocol_t proto)
 {
@@ -1502,7 +1827,7 @@ gt_get_keylist_mode (gpgme_tool_t gt)
   const char *modes[NR_KEYLIST_MODES + 1];
   int idx = 0;
   gpgme_keylist_mode_t mode = gpgme_get_keylist_mode (gt->ctx);
-  
+
   if (mode & GPGME_KEYLIST_MODE_LOCAL)
     modes[idx++] = "local";
   if (mode & GPGME_KEYLIST_MODE_EXTERN)
@@ -1619,22 +1944,22 @@ gt_genkey (gpgme_tool_t gt, const char *parms, gpgme_data_t public,
 gpg_error_t
 gt_import_keys (gpgme_tool_t gt, char *fpr[])
 {
-  gpg_error_t err;
+  gpg_error_t err = 0;
   int cnt;
   int idx;
   gpgme_key_t *keys;
-  
+
   cnt = 0;
   while (fpr[cnt])
     cnt++;
-  
+
   if (! cnt)
     return gpg_error (GPG_ERR_INV_VALUE);
 
   keys = malloc ((cnt + 1) * sizeof (gpgme_key_t));
   if (! keys)
     return gpg_error_from_syserror ();
-  
+
   for (idx = 0; idx < cnt; idx++)
     {
       err = gpgme_get_key (gt->ctx, fpr[idx], &keys[idx], 0);
@@ -1646,7 +1971,7 @@ gt_import_keys (gpgme_tool_t gt, char *fpr[])
       keys[cnt] = NULL;
       err = gpgme_op_import_keys (gt->ctx, keys);
     }
-  
+
   /* Rollback.  */
   while (--idx >= 0)
     gpgme_key_unref (keys[idx]);
@@ -1716,7 +2041,7 @@ gt_vfs_create (gpgme_tool_t gt, const char *container_file, int flags)
 }
 
 
-static const char hlp_passwd[] = 
+static const char hlp_passwd[] =
   "PASSWD <user-id>\n"
   "\n"
   "Ask the backend to change the passphrase for the key\n"
@@ -1750,10 +2075,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));
@@ -1801,7 +2122,15 @@ struct server
 
   gpgme_data_encoding_t input_enc;
   gpgme_data_encoding_t output_enc;
+  assuan_fd_t input_fd;
+  char *input_filename;
+  FILE *input_stream;
+  assuan_fd_t output_fd;
+  char *output_filename;
+  FILE *output_stream;
   assuan_fd_t message_fd;
+  char *message_filename;
+  FILE *message_stream;
   gpgme_data_encoding_t message_enc;
 };
 
@@ -1822,6 +2151,33 @@ server_write_data (void *hook, const void *buf, size_t len)
 }
 
 
+/* 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
+   NULL respective ASSUAN_INVALID_FD.  */
+static gpg_error_t
+server_parse_fd (assuan_context_t ctx, char *line, assuan_fd_t *rfd,
+                char **filename)
+{
+  *rfd = ASSUAN_INVALID_FD;
+  *filename = NULL;
+
+  if (! strncasecmp (line, "file=", 5))
+    {
+      char *term;
+      *filename = strdup (line + 5);
+      if (!*filename)
+       return gpg_error_from_syserror();
+      term = strchr (*filename, ' ');
+      if (term)
+       *term = '\0';
+      return 0;
+    }
+  else
+    return assuan_command_parse_fd (ctx, line, rfd);
+}
+
+
 static gpgme_data_encoding_t
 server_data_encoding (const char *line)
 {
@@ -1842,12 +2198,24 @@ server_data_encoding (const char *line)
 
 
 static gpgme_error_t
-server_data_obj (assuan_fd_t fd, gpgme_data_encoding_t encoding,
-                gpgme_data_t *data)
+server_data_obj (assuan_fd_t fd, char *fn, int out,
+                gpgme_data_encoding_t encoding,
+                gpgme_data_t *data, FILE **fs)
 {
   gpgme_error_t err;
 
-  err = gpgme_data_new_from_fd (data, fd);
+  *fs = NULL;
+  if (fn)
+    {
+      *fs = fopen (fn, out ? "wb" : "rb");
+      if (!*fs)
+       return gpg_error_from_syserror ();
+
+      err = gpgme_data_new_from_stream (data, *fs);
+    }
+  else
+    err = gpgme_data_new_from_fd (data, (int) fd);
+
   if (err)
     return err;
   return gpgme_data_set_encoding (*data, encoding);
@@ -1860,14 +2228,65 @@ server_reset_fds (struct server *server)
   /* assuan closes the input and output FDs for us when doing a RESET,
      but we use this same function after commands, so repeat it
      here.  */
-  assuan_close_input_fd (server->assuan_ctx);
-  assuan_close_output_fd (server->assuan_ctx);
-  if (server->message_fd != -1)
+  if (server->input_fd != ASSUAN_INVALID_FD)
+    {
+#if HAVE_W32_SYSTEM
+      CloseHandle (server->input_fd);
+#else
+      close (server->input_fd);
+#endif
+      server->input_fd = ASSUAN_INVALID_FD;
+    }
+  if (server->output_fd != ASSUAN_INVALID_FD)
+    {
+#if HAVE_W32_SYSTEM
+      CloseHandle (server->output_fd);
+#else
+      close (server->output_fd);
+#endif
+      server->output_fd = ASSUAN_INVALID_FD;
+    }
+  if (server->message_fd != ASSUAN_INVALID_FD)
     {
       /* FIXME: Assuan should provide a close function.  */
+#if HAVE_W32_SYSTEM
+      CloseHandle (server->message_fd);
+#else
       close (server->message_fd);
-      server->message_fd = -1;
+#endif
+      server->message_fd = ASSUAN_INVALID_FD;
     }
+  if (server->input_filename)
+    {
+      free (server->input_filename);
+      server->input_filename = NULL;
+    }
+  if (server->output_filename)
+    {
+      free (server->output_filename);
+      server->output_filename = NULL;
+    }
+  if (server->message_filename)
+    {
+      free (server->message_filename);
+      server->message_filename = NULL;
+    }
+  if (server->input_stream)
+    {
+      fclose (server->input_stream);
+      server->input_stream = NULL;
+    }
+  if (server->output_stream)
+    {
+      fclose (server->output_stream);
+      server->output_stream = NULL;
+    }
+  if (server->message_stream)
+    {
+      fclose (server->message_stream);
+      server->message_stream = NULL;
+    }
+
   server->input_enc = GPGME_DATA_ENCODING_NONE;
   server->output_enc = GPGME_DATA_ENCODING_NONE;
   server->message_enc = GPGME_DATA_ENCODING_NONE;
@@ -1883,7 +2302,8 @@ reset_notify (assuan_context_t ctx, char *line)
   return 0;
 }
 
-static const char hlp_version[] = 
+
+static const char hlp_version[] =
   "VERSION [<string>]\n"
   "\n"
   "Call the function gpgme_check_version.";
@@ -1903,6 +2323,10 @@ cmd_version (assuan_context_t ctx, char *line)
 }
 
 
+static const char hlp_engine[] =
+  "ENGINE [<string>]\n"
+  "\n"
+  "Get information about a GPGME engine (a.k.a. protocol).";
 static gpg_error_t
 cmd_engine (assuan_context_t ctx, char *line)
 {
@@ -1911,10 +2335,11 @@ cmd_engine (assuan_context_t ctx, char *line)
 }
 
 
-static const char hlp_protocol[] = 
+static const char hlp_protocol[] =
   "PROTOCOL [<name>]\n"
   "\n"
-  "With NAME, set the protocol.  Without return the current protocol.";
+  "With NAME, set the protocol.  Without, return the current\n"
+  "protocol.";
 static gpg_error_t
 cmd_protocol (assuan_context_t ctx, char *line)
 {
@@ -1926,6 +2351,11 @@ cmd_protocol (assuan_context_t ctx, char *line)
 }
 
 
+static const char hlp_sub_protocol[] =
+  "SUB_PROTOCOL [<name>]\n"
+  "\n"
+  "With NAME, set the sub-protocol.  Without, return the\n"
+  "current sub-protocol.";
 static gpg_error_t
 cmd_sub_protocol (assuan_context_t ctx, char *line)
 {
@@ -1937,6 +2367,11 @@ cmd_sub_protocol (assuan_context_t ctx, char *line)
 }
 
 
+static const char hlp_armor[] =
+  "ARMOR [true|false]\n"
+  "\n"
+  "With 'true' or 'false', turn output ASCII armoring on or\n"
+  "off.  Without, return the current armoring status.";
 static gpg_error_t
 cmd_armor (assuan_context_t ctx, char *line)
 {
@@ -1944,11 +2379,11 @@ cmd_armor (assuan_context_t ctx, char *line)
   if (line && *line)
     {
       int flag = 0;
-      
+
       if (! strcasecmp (line, "true") || ! strcasecmp (line, "yes")
          || line[0] == '1')
        flag = 1;
-      
+
       return gt_set_armor (server->gt, flag);
     }
   else
@@ -1956,6 +2391,11 @@ cmd_armor (assuan_context_t ctx, char *line)
 }
 
 
+static const char hlp_textmode[] =
+  "TEXTMODE [true|false]\n"
+  "\n"
+  "With 'true' or 'false', turn text mode on or off.\n"
+  "Without, return the current text mode status.";
 static gpg_error_t
 cmd_textmode (assuan_context_t ctx, char *line)
 {
@@ -1967,7 +2407,7 @@ cmd_textmode (assuan_context_t ctx, char *line)
       if (! strcasecmp (line, "true") || ! strcasecmp (line, "yes")
          || line[0] == '1')
        flag = 1;
-      
+
       return gt_set_textmode (server->gt, flag);
     }
   else
@@ -1975,6 +2415,13 @@ cmd_textmode (assuan_context_t ctx, char *line)
 }
 
 
+static const char hlp_include_certs[] =
+  "INCLUDE_CERTS [default|<n>]\n"
+  "\n"
+  "With DEFAULT or N, set how many certificates should be\n"
+  "included in the next S/MIME signed message.  See the\n"
+  "GPGME documentation for details on the meaning of"
+  "various N.  Without either, return the current setting.";
 static gpg_error_t
 cmd_include_certs (assuan_context_t ctx, char *line)
 {
@@ -1983,12 +2430,12 @@ cmd_include_certs (assuan_context_t ctx, char *line)
   if (line && *line)
     {
       int include_certs = 0;
-      
+
       if (! strcasecmp (line, "default"))
        include_certs = GPGME_INCLUDE_CERTS_DEFAULT;
       else
        include_certs = atoi (line);
-      
+
       return gt_set_include_certs (server->gt, include_certs);
     }
   else
@@ -1996,6 +2443,11 @@ cmd_include_certs (assuan_context_t ctx, char *line)
 }
 
 
+static const char hlp_keylist_mode[] =
+  "KEYLIST_MODE [local] [extern] [sigs] [sig_notations]\n"
+  "  [ephemeral] [validate]\n"
+  "\n"
+  "Set the mode for the next KEYLIST command.";
 static gpg_error_t
 cmd_keylist_mode (assuan_context_t ctx, char *line)
 {
@@ -2004,7 +2456,7 @@ cmd_keylist_mode (assuan_context_t ctx, char *line)
   if (line && *line)
     {
       gpgme_keylist_mode_t mode = 0;
-      
+
       if (strstr (line, "local"))
        mode |= GPGME_KEYLIST_MODE_LOCAL;
       if (strstr (line, "extern"))
@@ -2017,7 +2469,7 @@ cmd_keylist_mode (assuan_context_t ctx, char *line)
        mode |= GPGME_KEYLIST_MODE_EPHEMERAL;
       if (strstr (line, "validate"))
        mode |= GPGME_KEYLIST_MODE_VALIDATE;
-      
+
       return gt_set_keylist_mode (server->gt, mode);
     }
   else
@@ -2025,40 +2477,81 @@ cmd_keylist_mode (assuan_context_t ctx, char *line)
 }
 
 
+static const char hlp_input[] =
+  "INPUT [<fd>|FILE=<path>]\n"
+  "\n"
+  "Set the input for the next command.  Use either the\n"
+  "Assuan file descriptor FD or a filesystem PATH.";
 static gpg_error_t
-input_notify (assuan_context_t ctx, char *line)
+cmd_input (assuan_context_t ctx, char *line)
 {
   struct server *server = assuan_get_pointer (ctx);
+  gpg_error_t err;
+  assuan_fd_t sysfd;
+  char *filename;
+
+  err = server_parse_fd (ctx, line, &sysfd, &filename);
+  if (err)
+    return err;
+  server->input_fd = sysfd;
+  server->input_filename = filename;
   server->input_enc = server_data_encoding (line);
   return 0;
 }
 
 
+static const char hlp_output[] =
+  "OUTPUT [<fd>|FILE=<path>]\n"
+  "\n"
+  "Set the output for the next command.  Use either the\n"
+  "Assuan file descriptor FD or a filesystem PATH.";
 static gpg_error_t
-output_notify (assuan_context_t ctx, char *line)
+cmd_output (assuan_context_t ctx, char *line)
 {
   struct server *server = assuan_get_pointer (ctx);
+  gpg_error_t err;
+  assuan_fd_t sysfd;
+  char *filename;
+
+  err = server_parse_fd (ctx, line, &sysfd, &filename);
+  if (err)
+    return err;
+  server->output_fd = sysfd;
+  server->output_filename = filename;
   server->output_enc = server_data_encoding (line);
   return 0;
 }
 
 
+static const char hlp_message[] =
+  "MESSAGE [<fd>|FILE=<path>]\n"
+  "\n"
+  "Set the plaintext message for the next VERIFY command\n"
+  "with a detached signature.  Use either the Assuan file\n"
+  "descriptor FD or a filesystem PATH.";
 static gpg_error_t
 cmd_message (assuan_context_t ctx, char *line)
 {
   struct server *server = assuan_get_pointer (ctx);
   gpg_error_t err;
   assuan_fd_t sysfd;
+  char *filename;
 
-  err = assuan_command_parse_fd (ctx, line, &sysfd);
+  err = server_parse_fd (ctx, line, &sysfd, &filename);
   if (err)
     return err;
   server->message_fd = sysfd;
+  server->message_filename = filename;
   server->message_enc = server_data_encoding (line);
   return 0;
 }
 
 
+static const char hlp_recipient[] =
+  "RECIPIENT <pattern>\n"
+  "\n"
+  "Add the key matching PATTERN to the list of recipients\n"
+  "for the next encryption command.";
 static gpg_error_t
 cmd_recipient (assuan_context_t ctx, char *line)
 {
@@ -2068,6 +2561,11 @@ cmd_recipient (assuan_context_t ctx, char *line)
 }
 
 
+static const char hlp_signer[] =
+  "SIGNER <fingerprint>\n"
+  "\n"
+  "Add the key with FINGERPRINT to the list of signers to\n"
+  "be used for the next signing command.";
 static gpg_error_t
 cmd_signer (assuan_context_t ctx, char *line)
 {
@@ -2077,6 +2575,11 @@ cmd_signer (assuan_context_t ctx, char *line)
 }
 
 
+static const char hlp_signers_clear[] =
+  "SIGNERS_CLEAR\n"
+  "\n"
+  "Clear the list of signers specified by previous SIGNER\n"
+  "commands.";
 static gpg_error_t
 cmd_signers_clear (assuan_context_t ctx, char *line)
 {
@@ -2092,28 +2595,34 @@ _cmd_decrypt_verify (assuan_context_t ctx, char *line, int verify)
   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;
   gpgme_data_t out_data;
 
-  inp_fd = assuan_get_input_fd (ctx);
-  if (inp_fd == ASSUAN_INVALID_FD)
+  inp_fd = server->input_fd;
+  inp_fn = server->input_filename;
+  if (inp_fd == ASSUAN_INVALID_FD && !inp_fn)
     return GPG_ERR_ASS_NO_INPUT;
-  out_fd = assuan_get_output_fd (ctx);
-  if (out_fd == ASSUAN_INVALID_FD)
+  out_fd = server->output_fd;
+  out_fn = server->output_filename;
+  if (out_fd == ASSUAN_INVALID_FD && !out_fn)
     return GPG_ERR_ASS_NO_OUTPUT;
-  
-  err = server_data_obj (inp_fd, server->input_enc, &inp_data);
+
+  err = server_data_obj (inp_fd, inp_fn, 0, server->input_enc, &inp_data,
+                        &server->input_stream);
   if (err)
     return err;
-  err = server_data_obj (out_fd, server->output_enc, &out_data);
+  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_decrypt_verify (server->gt, inp_data, out_data, verify); 
+  err = gt_decrypt_verify (server->gt, inp_data, out_data, verify);
 
   gpgme_data_release (inp_data);
   gpgme_data_release (out_data);
@@ -2124,6 +2633,12 @@ _cmd_decrypt_verify (assuan_context_t ctx, char *line, int verify)
 }
 
 
+static const char hlp_decrypt[] =
+  "DECRYPT\n"
+  "\n"
+  "Decrypt the object set by the last INPUT command and\n"
+  "write the decrypted message to the object set by the\n"
+  "last OUTPUT command.";
 static gpg_error_t
 cmd_decrypt (assuan_context_t ctx, char *line)
 {
@@ -2131,6 +2646,12 @@ cmd_decrypt (assuan_context_t ctx, char *line)
 }
 
 
+static const char hlp_decrypt_verify[] =
+  "DECRYPT_VERIFY\n"
+  "\n"
+  "Decrypt the object set by the last INPUT command and\n"
+  "verify any embedded signatures.  Write the decrypted\n"
+  "message to the object set by the last OUTPUT command.";
 static gpg_error_t
 cmd_decrypt_verify (assuan_context_t ctx, char *line)
 {
@@ -2144,7 +2665,9 @@ _cmd_sign_encrypt (assuan_context_t ctx, char *line, int sign)
   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;
   gpgme_encrypt_flags_t flags = 0;
@@ -2157,18 +2680,22 @@ _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;
-  
-  inp_fd = assuan_get_input_fd (ctx);
-  out_fd = assuan_get_output_fd (ctx);
-  if (inp_fd != ASSUAN_INVALID_FD)
+
+  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, server->input_enc, &inp_data);
+      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)
+  if (out_fd != ASSUAN_INVALID_FD || out_fn)
     {
-      err = server_data_obj (out_fd, server->output_enc, &out_data);
+      err = server_data_obj (out_fd, out_fn, 1, server->output_enc, &out_data,
+                            &server->output_stream);
       if (err)
        {
          gpgme_data_release (inp_data);
@@ -2176,7 +2703,7 @@ _cmd_sign_encrypt (assuan_context_t ctx, char *line, int sign)
        }
     }
 
-  err = gt_sign_encrypt (server->gt, flags, inp_data, out_data, sign); 
+  err = gt_sign_encrypt (server->gt, flags, inp_data, out_data, sign);
 
   gpgme_data_release (inp_data);
   gpgme_data_release (out_data);
@@ -2187,6 +2714,14 @@ _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"
+  "\n"
+  "Encrypt the object set by the last INPUT command to\n"
+  "the keys specified by previous RECIPIENT commands.  \n"
+  "Write the signed and encrypted message to the object\n"
+  "set by the last OUTPUT command.";
 static gpg_error_t
 cmd_encrypt (assuan_context_t ctx, char *line)
 {
@@ -2194,6 +2729,15 @@ 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"
+  "\n"
+  "Sign the object set by the last INPUT command with the\n"
+  "keys specified by previous SIGNER commands and encrypt\n"
+  "it to the keys specified by previous RECIPIENT\n"
+  "commands.  Write the signed and encrypted message to\n"
+  "the object set by the last OUTPUT command.";
 static gpg_error_t
 cmd_sign_encrypt (assuan_context_t ctx, char *line)
 {
@@ -2201,13 +2745,24 @@ cmd_sign_encrypt (assuan_context_t ctx, char *line)
 }
 
 
+static const char hlp_sign[] =
+  "SIGN [--clear|--detach]\n"
+  "\n"
+  "Sign the object set by the last INPUT command with the\n"
+  "keys specified by previous SIGNER commands.  Write the\n"
+  "signed message to the object set by the last OUTPUT\n"
+  "command.  With `--clear`, generate a clear text\n"
+  "signature.  With `--detach`, generate a detached\n"
+  "signature.";
 static gpg_error_t
 cmd_sign (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;
   gpgme_data_t out_data;
   gpgme_sig_mode_t mode = GPGME_SIG_MODE_NORMAL;
@@ -2217,17 +2772,21 @@ cmd_sign (assuan_context_t ctx, char *line)
   if (strstr (line, "--detach"))
     mode = GPGME_SIG_MODE_DETACH;
 
-  inp_fd = assuan_get_input_fd (ctx);
-  if (inp_fd == ASSUAN_INVALID_FD)
+  inp_fd = server->input_fd;
+  inp_fn = server->input_filename;
+  if (inp_fd == ASSUAN_INVALID_FD && !inp_fn)
     return GPG_ERR_ASS_NO_INPUT;
-  out_fd = assuan_get_output_fd (ctx);
-  if (out_fd == ASSUAN_INVALID_FD)
+  out_fd = server->output_fd;
+  out_fn = server->output_filename;
+  if (out_fd == ASSUAN_INVALID_FD && !out_fn)
     return GPG_ERR_ASS_NO_OUTPUT;
-  
-  err = server_data_obj (inp_fd, server->input_enc, &inp_data);
+
+  err = server_data_obj (inp_fd, inp_fn, 0, server->input_enc, &inp_data,
+                        &server->input_stream);
   if (err)
     return err;
-  err = server_data_obj (out_fd, server->output_enc, &out_data);
+  err = server_data_obj (out_fd, out_fn, 1, server->output_enc, &out_data,
+                        &server->output_stream);
   if (err)
     {
       gpgme_data_release (inp_data);
@@ -2244,6 +2803,13 @@ cmd_sign (assuan_context_t ctx, char *line)
 }
 
 
+static const char hlp_verify[] =
+  "VERIFY\n"
+  "\n"
+  "Verify signatures on the object set by the last INPUT\n"
+  "and MESSAGE commands.  If the message was encrypted,\n"
+  "write the plaintext to the object set by the last\n"
+  "OUTPUT command.";
 static gpg_error_t
 cmd_verify (assuan_context_t ctx, char *line)
 {
@@ -2252,31 +2818,40 @@ cmd_verify (assuan_context_t ctx, char *line)
   assuan_fd_t inp_fd;
   assuan_fd_t msg_fd;
   assuan_fd_t out_fd;
+  char *inp_fn;
+  char *msg_fn;
+  char *out_fn;
   gpgme_data_t inp_data;
   gpgme_data_t msg_data = NULL;
   gpgme_data_t out_data = NULL;
 
-  inp_fd = assuan_get_input_fd (ctx);
-  if (inp_fd == ASSUAN_INVALID_FD)
+  inp_fd = server->input_fd;
+  inp_fn = server->input_filename;
+  if (inp_fd == ASSUAN_INVALID_FD && !inp_fn)
     return GPG_ERR_ASS_NO_INPUT;
   msg_fd = server->message_fd;
-  out_fd = assuan_get_output_fd (ctx);
-  
-  err = server_data_obj (inp_fd, server->input_enc, &inp_data);
+  msg_fn = server->message_filename;
+  out_fd = server->output_fd;
+  out_fn = server->output_filename;
+
+  err = server_data_obj (inp_fd, inp_fn, 0, server->input_enc, &inp_data,
+                        &server->input_stream);
   if (err)
     return err;
-  if (msg_fd != ASSUAN_INVALID_FD)
+  if (msg_fd != ASSUAN_INVALID_FD || msg_fn)
     {
-      err = server_data_obj (msg_fd, server->message_enc, &msg_data);
+      err = server_data_obj (msg_fd, msg_fn, 0, server->message_enc, &msg_data,
+                            &server->message_stream);
       if (err)
        {
          gpgme_data_release (inp_data);
          return err;
        }
     }
-  if (out_fd != ASSUAN_INVALID_FD)
+  if (out_fd != ASSUAN_INVALID_FD || out_fn)
     {
-      err = server_data_obj (out_fd, server->output_enc, &out_data);
+      err = server_data_obj (out_fd, out_fn, 1, server->output_enc, &out_data,
+                            &server->output_stream);
       if (err)
        {
          gpgme_data_release (inp_data);
@@ -2299,11 +2874,17 @@ cmd_verify (assuan_context_t ctx, char *line)
 }
 
 
+static const char hlp_import[] =
+  "IMPORT [<pattern>]\n"
+  "\n"
+  "With PATTERN, import the keys described by PATTERN.\n"
+  "Without, read a key (or keys) from the object set by the\n"
+  "last INPUT command.";
 static gpg_error_t
 cmd_import (assuan_context_t ctx, char *line)
 {
   struct server *server = assuan_get_pointer (ctx);
-  
+
   if (line && *line)
     {
       char *fprs[2] = { line, NULL };
@@ -2314,18 +2895,21 @@ cmd_import (assuan_context_t ctx, char *line)
     {
       gpg_error_t err;
       assuan_fd_t inp_fd;
+      char *inp_fn;
       gpgme_data_t inp_data;
-      
-      inp_fd = assuan_get_input_fd (ctx);
-      if (inp_fd == ASSUAN_INVALID_FD)
+
+      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, server->input_enc, &inp_data);
+      err = server_data_obj (inp_fd, inp_fn, 0, server->input_enc, &inp_data,
+                            &server->input_stream);
       if (err)
        return err;
-      
-      err = gt_import (server->gt, inp_data); 
-      
+
+      err = gt_import (server->gt, inp_data);
+
       gpgme_data_release (inp_data);
       server_reset_fds (server);
 
@@ -2334,29 +2918,38 @@ cmd_import (assuan_context_t ctx, char *line)
 }
 
 
+static const char hlp_export[] =
+  "EXPORT [--extern] [--minimal] [<pattern>]\n"
+  "\n"
+  "Export the keys described by PATTERN.  Write the\n"
+  "the output to the object set by the last OUTPUT command.";
 static gpg_error_t
 cmd_export (assuan_context_t ctx, char *line)
 {
   struct server *server = assuan_get_pointer (ctx);
   gpg_error_t err;
   assuan_fd_t out_fd;
+  char *out_fn;
   gpgme_data_t out_data;
   gpgme_export_mode_t mode = 0;
   const char *pattern[2];
-  const char optstr[] = "--extern ";
 
-  out_fd = assuan_get_output_fd (ctx);
-  if (out_fd == ASSUAN_INVALID_FD)
+  out_fd = server->output_fd;
+  out_fn = server->output_filename;
+  if (out_fd == ASSUAN_INVALID_FD && !out_fn)
     return GPG_ERR_ASS_NO_OUTPUT;
-  err = server_data_obj (out_fd, server->output_enc, &out_data);
+  err = server_data_obj (out_fd, out_fn, 1, server->output_enc, &out_data,
+                        &server->output_stream);
   if (err)
     return err;
 
-  if (strncasecmp (line, optstr, strlen (optstr)))
-    {
-      mode |= GPGME_EXPORT_MODE_EXTERN;
-      line += strlen (optstr);
-    }
+  if (has_option (line, "--extern"))
+    mode |= GPGME_EXPORT_MODE_EXTERN;
+  if (has_option (line, "--minimal"))
+    mode |= GPGME_EXPORT_MODE_MINIMAL;
+
+  line = skip_options (line);
+
   pattern[0] = line;
   pattern[1] = NULL;
 
@@ -2393,23 +2986,29 @@ cmd_genkey (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;
   gpgme_data_t out_data = NULL;
   gpgme_data_t parms_data = NULL;
   const char *parms;
 
-  inp_fd = assuan_get_input_fd (ctx);
-  if (inp_fd == ASSUAN_INVALID_FD)
+  inp_fd = server->input_fd;
+  inp_fn = server->input_filename;
+  if (inp_fd == ASSUAN_INVALID_FD && !inp_fn)
     return GPG_ERR_ASS_NO_INPUT;
-  out_fd = assuan_get_output_fd (ctx);
-  
-  err = server_data_obj (inp_fd, server->input_enc, &inp_data);
+  out_fd = server->output_fd;
+  out_fn = server->output_filename;
+
+  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)
+  if (out_fd != ASSUAN_INVALID_FD || out_fn)
     {
-      err = server_data_obj (out_fd, server->output_enc, &out_data);
+      err = server_data_obj (out_fd, out_fn, 1, server->output_enc, &out_data,
+                            &server->output_stream);
       if (err)
        {
          gpgme_data_release (inp_data);
@@ -2460,7 +3059,7 @@ cmd_genkey (assuan_context_t ctx, char *line)
   if (parms_data)
     gpgme_data_release (parms_data);
 
-  return err; 
+  return err;
 }
 
 
@@ -2469,38 +3068,76 @@ cmd_delete (assuan_context_t ctx, char *line)
 {
   struct server *server = assuan_get_pointer (ctx);
   int allow_secret = 0;
-  const char optstr[] = "--allow-secret ";
+  const char optstr[] = "--allow-secret";
 
-  if (strncasecmp (line, optstr, strlen (optstr)))
+  if (!strncasecmp (line, optstr, strlen (optstr)))
     {
       allow_secret = 1;
       line += strlen (optstr);
+      while (*line && !spacep (line))
+       line++;
     }
   return gt_delete (server->gt, line, allow_secret);
 }
 
 
+static const char hlp_keylist[] =
+  "KEYLIST [--secret-only] [<patterns>]\n"
+  "\n"
+  "List all certificates or only those specified by PATTERNS.  Each\n"
+  "pattern shall be a percent-plus escaped certificate specification.";
 static gpg_error_t
 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;
-  const char *pattern[2];
-  const char optstr[] = "--secret-only ";
+  int idx, indent=2;
+  const char *pattern[MAX_CMD_KEYLIST_PATTERN+1];
+  const char optstr[] = "--secret-only";
+  char *p;
 
-  if (strncasecmp (line, optstr, strlen (optstr)))
+  if (!strncasecmp (line, optstr, strlen (optstr)))
     {
       secret_only = 1;
       line += strlen (optstr);
+      while (*line && !spacep (line))
+       line++;
     }
-  pattern[0] = line;
-  pattern[1] = NULL;
+
+  idx = 0;
+  for (p=line; *p; line = p)
+    {
+      while (*p && *p != ' ')
+        p++;
+      if (*p)
+        *p++ = 0;
+      if (*line)
+        {
+          if (idx+1 == DIM (pattern))
+            return gpg_error (GPG_ERR_TOO_MANY);
+          strcpy_escaped_plus (line, line);
+          pattern[idx++] = 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)
@@ -2510,21 +3147,62 @@ 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);
-         assuan_send_data (ctx, buf, strlen (buf));
+         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_fpr (&state, "fpr", subkey->fpr);
+           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;
 }
 
 
-static const char hlp_getauditlog[] = 
+static const char hlp_getauditlog[] =
   "GETAUDITLOG [--html] [--with-help]\n"
   "\n"
   "Call the function gpgme_op_getauditlog with the given flags.  Write\n"
@@ -2535,13 +3213,16 @@ cmd_getauditlog (assuan_context_t ctx, char *line)
   struct server *server = assuan_get_pointer (ctx);
   gpg_error_t err;
   assuan_fd_t out_fd;
+  char *out_fn;
   gpgme_data_t out_data;
   unsigned int flags = 0;
 
-  out_fd = assuan_get_output_fd (ctx);
-  if (out_fd == ASSUAN_INVALID_FD)
+  out_fd = server->output_fd;
+  out_fn = server->output_filename;
+  if (out_fd == ASSUAN_INVALID_FD && !out_fn)
     return GPG_ERR_ASS_NO_OUTPUT;
-  err = server_data_obj (out_fd, server->output_enc, &out_data);
+  err = server_data_obj (out_fd, out_fn, 1, server->output_enc, &out_data,
+                        &server->output_stream);
   if (err)
     return err;
 
@@ -2667,48 +3348,48 @@ register_commands (assuan_context_t ctx)
     assuan_handler_t handler;
     const char * const help;
   } table[] = {
-    // RESET, BYE are implicit.
+    /* RESET, BYE are implicit.  */
     { "VERSION", cmd_version, hlp_version },
-    // TODO: Set engine info.
-    { "ENGINE", cmd_engine },
+    /* TODO: Set engine info.  */
+    { "ENGINE", cmd_engine, hlp_engine },
     { "PROTOCOL", cmd_protocol, hlp_protocol },
-    { "SUB_PROTOCOL", cmd_sub_protocol },
-    { "ARMOR", cmd_armor },
-    { "TEXTMODE", cmd_textmode },
-    { "INCLUDE_CERTS", cmd_include_certs },
-    { "KEYLIST_MODE", cmd_keylist_mode },
-    { "INPUT", NULL }, 
-    { "OUTPUT", NULL }, 
-    { "MESSAGE", cmd_message },
-    { "RECIPIENT", cmd_recipient },
-    { "SIGNER", cmd_signer },
-    { "SIGNERS_CLEAR", cmd_signers_clear },
-    // TODO: SIGNOTATION missing.
-    // TODO: Could add wait interface if we allow more than one context
-    // and add _START variants.
-    // TODO: Could add data interfaces if we allow multiple data objects.
-    { "DECRYPT", cmd_decrypt },
-    { "DECRYPT_VERIFY", cmd_decrypt_verify },
-    { "ENCRYPT", cmd_encrypt },
-    { "ENCRYPT_SIGN", cmd_sign_encrypt },
-    { "SIGN_ENCRYPT", cmd_sign_encrypt },
-    { "SIGN", cmd_sign },
-    { "VERIFY", cmd_verify },
-    { "IMPORT", cmd_import },
-    { "EXPORT", cmd_export },
+    { "SUB_PROTOCOL", cmd_sub_protocol, hlp_sub_protocol },
+    { "ARMOR", cmd_armor, hlp_armor },
+    { "TEXTMODE", cmd_textmode, hlp_textmode },
+    { "INCLUDE_CERTS", cmd_include_certs, hlp_include_certs },
+    { "KEYLIST_MODE", cmd_keylist_mode, hlp_keylist_mode },
+    { "INPUT", cmd_input, hlp_input },
+    { "OUTPUT", cmd_output, hlp_output },
+    { "MESSAGE", cmd_message, hlp_message },
+    { "RECIPIENT", cmd_recipient, hlp_recipient },
+    { "SIGNER", cmd_signer, hlp_signer },
+    { "SIGNERS_CLEAR", cmd_signers_clear, hlp_signers_clear },
+     /* TODO: SIGNOTATION missing. */
+     /* TODO: Could add wait interface if we allow more than one context */
+     /* and add _START variants. */
+     /* TODO: Could add data interfaces if we allow multiple data objects. */
+    { "DECRYPT", cmd_decrypt, hlp_decrypt },
+    { "DECRYPT_VERIFY", cmd_decrypt_verify, hlp_decrypt_verify },
+    { "ENCRYPT", cmd_encrypt, hlp_encrypt },
+    { "ENCRYPT_SIGN", cmd_sign_encrypt, hlp_sign_encrypt },
+    { "SIGN_ENCRYPT", cmd_sign_encrypt, hlp_sign_encrypt },
+    { "SIGN", cmd_sign, hlp_sign },
+    { "VERIFY", cmd_verify, hlp_verify },
+    { "IMPORT", cmd_import, hlp_import },
+    { "EXPORT", cmd_export, hlp_export },
     { "GENKEY", cmd_genkey },
     { "DELETE", cmd_delete },
-    // TODO: EDIT, CARD_EDIT (with INQUIRE)
-    { "KEYLIST", cmd_keylist },
-    { "LISTKEYS", cmd_keylist },
-    // TODO: TRUSTLIST, TRUSTLIST_EXT
+    /* TODO: EDIT, CARD_EDIT (with INQUIRE) */
+    { "KEYLIST", cmd_keylist, hlp_keylist },
+    { "LISTKEYS", cmd_keylist, hlp_keylist },
+    /* TODO: TRUSTLIST, TRUSTLIST_EXT */
     { "GETAUDITLOG", cmd_getauditlog, hlp_getauditlog },
-    // TODO: ASSUAN
+    /* TODO: ASSUAN */
     { "VFS_MOUNT", cmd_vfs_mount },
     { "MOUNT", cmd_vfs_mount },
     { "VFS_CREATE", cmd_vfs_create },
     { "CREATE", cmd_vfs_create },
-    // TODO: GPGCONF
+    /* TODO: GPGCONF  */
     { "RESULT", cmd_result },
     { "STRERROR", cmd_strerror },
     { "PUBKEY_ALGO_NAME", cmd_pubkey_algo_name },
@@ -2724,7 +3405,7 @@ register_commands (assuan_context_t ctx)
                                      table[idx].help);
       if (err)
         return err;
-    } 
+    }
   return 0;
 }
 
@@ -2739,7 +3420,9 @@ gpgme_server (gpgme_tool_t gt)
   static const char hello[] = ("GPGME-Tool " VERSION " ready");
 
   memset (&server, 0, sizeof (server));
-  server.message_fd = -1;
+  server.input_fd = ASSUAN_INVALID_FD;
+  server.output_fd = ASSUAN_INVALID_FD;
+  server.message_fd = ASSUAN_INVALID_FD;
   server.input_enc = GPGME_DATA_ENCODING_NONE;
   server.output_enc = GPGME_DATA_ENCODING_NONE;
   server.message_enc = GPGME_DATA_ENCODING_NONE;
@@ -2753,8 +3436,13 @@ gpgme_server (gpgme_tool_t gt)
   /* We use a pipe based server so that we can work from scripts.
      assuan_init_pipe_server will automagically detect when we are
      called with a socketpair and ignore FIELDES in this case. */
+#ifdef HAVE_W32CE_SYSTEM
+  filedes[0] = ASSUAN_STDIN;
+  filedes[1] = ASSUAN_STDOUT;
+#else
   filedes[0] = assuan_fdopen (0);
   filedes[1] = assuan_fdopen (1);
+#endif
   err = assuan_new (&server.assuan_ctx);
   if (err)
     log_error (1, err, "can't create assuan context");
@@ -2770,8 +3458,6 @@ gpgme_server (gpgme_tool_t gt)
   assuan_set_hello_line (server.assuan_ctx, hello);
 
   assuan_register_reset_notify (server.assuan_ctx, reset_notify);
-  assuan_register_input_notify (server.assuan_ctx, input_notify);
-  assuan_register_output_notify (server.assuan_ctx, output_notify);
 
 #define DBG_ASSUAN 0
   if (DBG_ASSUAN)
@@ -2787,7 +3473,7 @@ gpgme_server (gpgme_tool_t gt)
          log_error (0, err, "assuan accept problem");
          break;
         }
-      
+
       err = assuan_process (server.assuan_ctx);
       if (err)
        log_error (0, err, "assuan processing failed");
@@ -2804,7 +3490,7 @@ 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 -- invoke GPGME operations";
+static char doc[] = "GPGME Tool -- Assuan server exposing GPGME operations";
 static char args_doc[] = "COMMAND [OPTIONS...]";
 
 static struct argp_option options[] = {
@@ -2863,12 +3549,17 @@ main (int argc, char *argv[])
   struct args args;
   struct gpgme_tool gt;
 
+#ifdef HAVE_SETLOCALE
   setlocale (LC_ALL, "");
+#endif
   gpgme_check_version (NULL);
+#ifdef LC_CTYPE
   gpgme_set_locale (NULL, LC_CTYPE, setlocale (LC_CTYPE, NULL));
+#endif
 #ifdef LC_MESSAGES
   gpgme_set_locale (NULL, LC_MESSAGES, setlocale (LC_MESSAGES, NULL));
 #endif
+
   args_init (&args);
 
   argp_parse (&argp, argc, argv, 0, 0, &args);
@@ -2886,6 +3577,11 @@ main (int argc, char *argv[])
 
   gpgme_release (gt.ctx);
 
+#ifdef HAVE_W32CE_SYSTEM
+  /* Give the buggy ssh server time to flush the output buffers.  */
+  Sleep (300);
+#endif
+
   return 0;
 }