gpgme-tool: Fix handling of file descriptors
[gpgme.git] / src / gpgme-tool.c
index 068681f6f9a7b034194d58c137e1a0567d0d57ae..3a02065733d5291330d452afb02c675dc644d5fa 100644 (file)
@@ -1,18 +1,18 @@
-/* gpgme-tool.c - GnuPG Made Easy.
+/* gpgme-tool.c - Assuan server exposing GnuPG Made Easy operations.
    Copyright (C) 2009, 2010 g10 Code GmbH
 
    This file is part of GPGME.
    Copyright (C) 2009, 2010 g10 Code GmbH
 
    This file is part of GPGME.
+
    GPGME is free software; you can redistribute it and/or modify it
    under the terms of the GNU Lesser General Public License as
    published by the Free Software Foundation; either version 2.1 of
    the License, or (at your option) any later version.
    GPGME is free software; you can redistribute it and/or modify it
    under the terms of the GNU Lesser General Public License as
    published by the Free Software Foundation; either version 2.1 of
    the License, or (at your option) any later version.
-   
+
    GPGME is distributed in the hope that it will be useful, but
    WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.
    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/>.
  */
    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/>.
  */
 #include "gpgme.h"
 
 /* GCC attributes.  */
 #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) __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 )
 #endif
 
 #if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5 )
 # define GT_GCC_A_PRINTF(f, a)
 #endif
 
 # 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
 
 
 \f
@@ -154,9 +158,9 @@ struct argp
    | ARGP_HELP_DOC | ARGP_HELP_BUG_ADDR)
 
 
    | 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);
                  const char *fmt, ...) GT_GCC_A_PRINTF(2, 3);
-  
+
 
 
 char *
 
 
 char *
@@ -370,10 +374,10 @@ argp_parse (const struct argp *argp, int argc,
              *arg = '\0';
              arg++;
            }
              *arg = '\0';
              arg++;
            }
-           
+
          if (state.argv[idx][1] != '-')
            key = state.argv[idx][1];
          if (state.argv[idx][1] != '-')
            key = state.argv[idx][1];
-         
+
          while (! found && opt->key)
            {
              if (key == opt->key
          while (! found && opt->key)
            {
              if (key == opt->key
@@ -449,7 +453,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 = argp->parser (ARGP_KEY_FINI, NULL, &state);
   if (rc && rc != ARGP_ERR_UNKNOWN)
     goto argperror;
-  
+
   rc = 0;
   argp->parser (ARGP_KEY_SUCCESS, NULL, &state);
 
   rc = 0;
   argp->parser (ARGP_KEY_SUCCESS, NULL, &state);
 
@@ -477,7 +481,7 @@ char *program_name = "gpgme-tool";
 #define spacep(p)   (*(p) == ' ' || *(p) == '\t')
 
 
 #define spacep(p)   (*(p) == ' ' || *(p) == '\t')
 
 
-void log_error (int status, gpg_error_t errnum, 
+void log_error (int status, gpg_error_t errnum,
                 const char *fmt, ...) GT_GCC_A_PRINTF(3,4);
 
 
                 const char *fmt, ...) GT_GCC_A_PRINTF(3,4);
 
 
@@ -506,6 +510,28 @@ 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)
 /* Check whether the option NAME appears in LINE.  */
 static int
 has_option (const char *line, const char *name)
@@ -604,7 +630,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++;
   state->had_data[state->next_tag] = 0;
   state->indent += 2;
   state->next_tag++;
-  
+
   while (1)
     {
       attr = va_arg (ap, char *);
   while (1)
     {
       attr = va_arg (ap, char *);
@@ -678,7 +704,7 @@ 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)
 
 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);
   char code[20];
   char msg[1024];
   snprintf (code, sizeof (code) - 1, "0x%x", err);
@@ -694,7 +720,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)
 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);
   char code[20];
   char msg[80];
   snprintf (code, sizeof (code) - 1, "0x%x", algo);
@@ -710,7 +736,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)
 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);
   char code[20];
   char msg[80];
   snprintf (code, sizeof (code) - 1, "0x%x", algo);
@@ -725,7 +751,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)
 
 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);
   result_xml_tag_start (state, name, NULL);
   result_xml_tag_data (state, keyid);
   result_xml_tag_end (state);
@@ -735,7 +761,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)
 
 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);
   result_xml_tag_start (state, name, NULL);
   result_xml_tag_data (state, fpr);
   result_xml_tag_end (state);
@@ -759,7 +785,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)
 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];
 
   char *mode;
   char code[20];
 
@@ -789,7 +815,7 @@ result_add_sig_mode (struct result_xml_state *state, char *name,
 gpg_error_t
 result_add_value (struct result_xml_state *state,
                  char *name, unsigned int val)
 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);
   char code[20];
 
   snprintf (code, sizeof (code) - 1, "0x%x", val);
@@ -802,7 +828,7 @@ result_add_value (struct result_xml_state *state,
 gpg_error_t
 result_add_string (struct result_xml_state *state,
                   char *name, char *str)
 gpg_error_t
 result_add_string (struct result_xml_state *state,
                   char *name, char *str)
-{                
+{
   result_xml_tag_start (state, name, NULL);
   result_xml_tag_data (state, str);
   result_xml_tag_end (state);
   result_xml_tag_start (state, name, NULL);
   result_xml_tag_data (state, str);
   result_xml_tag_end (state);
@@ -828,7 +854,7 @@ result_encrypt_to_xml (gpgme_ctx_t ctx, int indent,
   if (inv_recp)
     {
       result_xml_tag_start (&state, "invalid-recipients", NULL);
   if (inv_recp)
     {
       result_xml_tag_start (&state, "invalid-recipients", NULL);
-      
+
       while (inv_recp)
        {
          result_xml_tag_start (&state, "invalid-key", NULL);
       while (inv_recp)
        {
          result_xml_tag_start (&state, "invalid-key", NULL);
@@ -841,7 +867,7 @@ result_encrypt_to_xml (gpgme_ctx_t ctx, int indent,
       result_xml_tag_end (&state);
     }
   result_xml_tag_end (&state);
       result_xml_tag_end (&state);
     }
   result_xml_tag_end (&state);
-  
+
   return 0;
 }
 
   return 0;
 }
 
@@ -894,7 +920,7 @@ result_decrypt_to_xml (gpgme_ctx_t ctx, int indent,
       result_xml_tag_end (&state);
     }
   result_xml_tag_end (&state);
       result_xml_tag_end (&state);
     }
   result_xml_tag_end (&state);
-  
+
   return 0;
 }
 
   return 0;
 }
 
@@ -918,7 +944,7 @@ result_sign_to_xml (gpgme_ctx_t ctx, int indent,
   if (inv_key)
     {
       result_xml_tag_start (&state, "invalid-signers", NULL);
   if (inv_key)
     {
       result_xml_tag_start (&state, "invalid-signers", NULL);
-      
+
       while (inv_key)
        {
          result_xml_tag_start (&state, "invalid-key", NULL);
       while (inv_key)
        {
          result_xml_tag_start (&state, "invalid-key", NULL);
@@ -954,7 +980,7 @@ result_sign_to_xml (gpgme_ctx_t ctx, int indent,
     }
 
   result_xml_tag_end (&state);
     }
 
   result_xml_tag_end (&state);
-  
+
   return 0;
 }
 
   return 0;
 }
 
@@ -988,7 +1014,7 @@ result_verify_to_xml (gpgme_ctx_t ctx, int indent,
       while (sig)
        {
          result_xml_tag_start (&state, "signature", NULL);
       while (sig)
        {
          result_xml_tag_start (&state, "signature", NULL);
-         
+
          /* FIXME: Could be done better. */
          result_add_value (&state, "summary", sig->summary);
          if (sig->fpr)
          /* FIXME: Could be done better. */
          result_add_value (&state, "summary", sig->summary);
          if (sig->fpr)
@@ -1006,7 +1032,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_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;
        }
          result_xml_tag_end (&state);
          sig = sig->next;
        }
@@ -1014,7 +1040,7 @@ result_verify_to_xml (gpgme_ctx_t ctx, int indent,
     }
 
   result_xml_tag_end (&state);
     }
 
   result_xml_tag_end (&state);
-  
+
   return 0;
 }
 
   return 0;
 }
 
@@ -1052,7 +1078,7 @@ result_import_to_xml (gpgme_ctx_t ctx, int indent,
   if (stat)
     {
       result_xml_tag_start (&state, "imports", NULL);
   if (stat)
     {
       result_xml_tag_start (&state, "imports", NULL);
-      
+
       while (stat)
        {
          result_xml_tag_start (&state, "import-status", NULL);
       while (stat)
        {
          result_xml_tag_start (&state, "import-status", NULL);
@@ -1070,7 +1096,7 @@ result_import_to_xml (gpgme_ctx_t ctx, int indent,
     }
 
   result_xml_tag_end (&state);
     }
 
   result_xml_tag_end (&state);
-  
+
   return 0;
 }
 
   return 0;
 }
 
@@ -1094,7 +1120,7 @@ result_genkey_to_xml (gpgme_ctx_t ctx, int indent,
     result_add_fpr (&state, "fpr", res->fpr);
 
   result_xml_tag_end (&state);
     result_add_fpr (&state, "fpr", res->fpr);
 
   result_xml_tag_end (&state);
-  
+
   return 0;
 }
 
   return 0;
 }
 
@@ -1115,7 +1141,7 @@ result_keylist_to_xml (gpgme_ctx_t ctx, int indent,
   result_add_value (&state, "truncated", res->truncated);
 
   result_xml_tag_end (&state);
   result_add_value (&state, "truncated", res->truncated);
 
   result_xml_tag_end (&state);
-  
+
   return 0;
 }
 
   return 0;
 }
 
@@ -1136,7 +1162,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);
   result_add_string (&state, "mount-dir", res->mount_dir);
 
   result_xml_tag_end (&state);
-  
+
   return 0;
 }
 
   return 0;
 }
 
@@ -1183,7 +1209,7 @@ typedef struct gpgme_tool *gpgme_tool_t;
 
 
 /* Forward declaration.  */
 
 
 /* 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
                       status_t status, ...) GT_GCC_A_SENTINEL(0);
 
 void
@@ -1255,7 +1281,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);
 
   if (!gt || !r_key || !pattern)
     return gpg_error (GPG_ERR_INV_VALUE);
-  
+
   ctx = gt->ctx;
 
   err = gpgme_new (&listctx);
   ctx = gt->ctx;
 
   err = gpgme_new (&listctx);
@@ -1317,10 +1343,10 @@ gt_get_key (gpgme_tool_t gt, const char *pattern, gpgme_key_t *r_key)
        }
     }
   gpgme_release (listctx);
        }
     }
   gpgme_release (listctx);
-  
+
   if (! err)
   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;
 }
                     (*r_key)->subkeys->fpr : "invalid", NULL);
   return err;
 }
@@ -1364,7 +1390,7 @@ gt_reset (gpgme_tool_t gt)
 {
   gpg_error_t err;
   gpgme_ctx_t ctx;
 {
   gpg_error_t err;
   gpgme_ctx_t ctx;
-  
+
   err = _gt_gpgme_new (gt, &ctx);
   if (err)
     return err;
   err = _gt_gpgme_new (gt, &ctx);
   if (err)
     return err;
@@ -1456,7 +1482,7 @@ gt_protocol_from_name (const char *name)
   return GPGME_PROTOCOL_UNKNOWN;
 }
 
   return GPGME_PROTOCOL_UNKNOWN;
 }
 
-  
+
 gpg_error_t
 gt_set_protocol (gpgme_tool_t gt, gpgme_protocol_t proto)
 {
 gpg_error_t
 gt_set_protocol (gpgme_tool_t gt, gpgme_protocol_t proto)
 {
@@ -1546,7 +1572,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);
   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)
   if (mode & GPGME_KEYLIST_MODE_LOCAL)
     modes[idx++] = "local";
   if (mode & GPGME_KEYLIST_MODE_EXTERN)
@@ -1667,18 +1693,18 @@ gt_import_keys (gpgme_tool_t gt, char *fpr[])
   int cnt;
   int idx;
   gpgme_key_t *keys;
   int cnt;
   int idx;
   gpgme_key_t *keys;
-  
+
   cnt = 0;
   while (fpr[cnt])
     cnt++;
   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 ();
   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);
   for (idx = 0; idx < cnt; idx++)
     {
       err = gpgme_get_key (gt->ctx, fpr[idx], &keys[idx], 0);
@@ -1690,7 +1716,7 @@ gt_import_keys (gpgme_tool_t gt, char *fpr[])
       keys[cnt] = NULL;
       err = gpgme_op_import_keys (gt->ctx, keys);
     }
       keys[cnt] = NULL;
       err = gpgme_op_import_keys (gt->ctx, keys);
     }
-  
+
   /* Rollback.  */
   while (--idx >= 0)
     gpgme_key_unref (keys[idx]);
   /* Rollback.  */
   while (--idx >= 0)
     gpgme_key_unref (keys[idx]);
@@ -1760,7 +1786,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"
   "PASSWD <user-id>\n"
   "\n"
   "Ask the backend to change the passphrase for the key\n"
@@ -1874,7 +1900,10 @@ 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)
 static gpg_error_t
 server_parse_fd (assuan_context_t ctx, char *line, assuan_fd_t *rfd,
                 char **filename)
@@ -1896,7 +1925,7 @@ server_parse_fd (assuan_context_t ctx, char *line, assuan_fd_t *rfd,
   else
     return assuan_command_parse_fd (ctx, line, rfd);
 }
   else
     return assuan_command_parse_fd (ctx, line, rfd);
 }
-    
+
 
 static gpgme_data_encoding_t
 server_data_encoding (const char *line)
 
 static gpgme_data_encoding_t
 server_data_encoding (const char *line)
@@ -1948,8 +1977,24 @@ 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 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->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 (server->message_fd != ASSUAN_INVALID_FD)
     {
       /* FIXME: Assuan should provide a close function.  */
@@ -2007,7 +2052,7 @@ reset_notify (assuan_context_t ctx, char *line)
 }
 
 
 }
 
 
-static const char hlp_version[] = 
+static const char hlp_version[] =
   "VERSION [<string>]\n"
   "\n"
   "Call the function gpgme_check_version.";
   "VERSION [<string>]\n"
   "\n"
   "Call the function gpgme_check_version.";
@@ -2027,6 +2072,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)
 {
 static gpg_error_t
 cmd_engine (assuan_context_t ctx, char *line)
 {
@@ -2035,10 +2084,11 @@ cmd_engine (assuan_context_t ctx, char *line)
 }
 
 
 }
 
 
-static const char hlp_protocol[] = 
+static const char hlp_protocol[] =
   "PROTOCOL [<name>]\n"
   "\n"
   "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)
 {
 static gpg_error_t
 cmd_protocol (assuan_context_t ctx, char *line)
 {
@@ -2050,6 +2100,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)
 {
 static gpg_error_t
 cmd_sub_protocol (assuan_context_t ctx, char *line)
 {
@@ -2061,6 +2116,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)
 {
 static gpg_error_t
 cmd_armor (assuan_context_t ctx, char *line)
 {
@@ -2068,11 +2128,11 @@ cmd_armor (assuan_context_t ctx, char *line)
   if (line && *line)
     {
       int flag = 0;
   if (line && *line)
     {
       int flag = 0;
-      
+
       if (! strcasecmp (line, "true") || ! strcasecmp (line, "yes")
          || line[0] == '1')
        flag = 1;
       if (! strcasecmp (line, "true") || ! strcasecmp (line, "yes")
          || line[0] == '1')
        flag = 1;
-      
+
       return gt_set_armor (server->gt, flag);
     }
   else
       return gt_set_armor (server->gt, flag);
     }
   else
@@ -2080,6 +2140,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)
 {
 static gpg_error_t
 cmd_textmode (assuan_context_t ctx, char *line)
 {
@@ -2091,7 +2156,7 @@ cmd_textmode (assuan_context_t ctx, char *line)
       if (! strcasecmp (line, "true") || ! strcasecmp (line, "yes")
          || line[0] == '1')
        flag = 1;
       if (! strcasecmp (line, "true") || ! strcasecmp (line, "yes")
          || line[0] == '1')
        flag = 1;
-      
+
       return gt_set_textmode (server->gt, flag);
     }
   else
       return gt_set_textmode (server->gt, flag);
     }
   else
@@ -2099,6 +2164,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)
 {
 static gpg_error_t
 cmd_include_certs (assuan_context_t ctx, char *line)
 {
@@ -2107,12 +2179,12 @@ cmd_include_certs (assuan_context_t ctx, char *line)
   if (line && *line)
     {
       int include_certs = 0;
   if (line && *line)
     {
       int include_certs = 0;
-      
+
       if (! strcasecmp (line, "default"))
        include_certs = GPGME_INCLUDE_CERTS_DEFAULT;
       else
        include_certs = atoi (line);
       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
       return gt_set_include_certs (server->gt, include_certs);
     }
   else
@@ -2120,6 +2192,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)
 {
 static gpg_error_t
 cmd_keylist_mode (assuan_context_t ctx, char *line)
 {
@@ -2128,7 +2205,7 @@ cmd_keylist_mode (assuan_context_t ctx, char *line)
   if (line && *line)
     {
       gpgme_keylist_mode_t mode = 0;
   if (line && *line)
     {
       gpgme_keylist_mode_t mode = 0;
-      
+
       if (strstr (line, "local"))
        mode |= GPGME_KEYLIST_MODE_LOCAL;
       if (strstr (line, "extern"))
       if (strstr (line, "local"))
        mode |= GPGME_KEYLIST_MODE_LOCAL;
       if (strstr (line, "extern"))
@@ -2141,7 +2218,7 @@ cmd_keylist_mode (assuan_context_t ctx, char *line)
        mode |= GPGME_KEYLIST_MODE_EPHEMERAL;
       if (strstr (line, "validate"))
        mode |= GPGME_KEYLIST_MODE_VALIDATE;
        mode |= GPGME_KEYLIST_MODE_EPHEMERAL;
       if (strstr (line, "validate"))
        mode |= GPGME_KEYLIST_MODE_VALIDATE;
-      
+
       return gt_set_keylist_mode (server->gt, mode);
     }
   else
       return gt_set_keylist_mode (server->gt, mode);
     }
   else
@@ -2149,6 +2226,11 @@ 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
 cmd_input (assuan_context_t ctx, char *line)
 {
 static gpg_error_t
 cmd_input (assuan_context_t ctx, char *line)
 {
@@ -2167,6 +2249,11 @@ cmd_input (assuan_context_t ctx, char *line)
 }
 
 
 }
 
 
+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
 cmd_output (assuan_context_t ctx, char *line)
 {
 static gpg_error_t
 cmd_output (assuan_context_t ctx, char *line)
 {
@@ -2185,6 +2272,12 @@ cmd_output (assuan_context_t ctx, char *line)
 }
 
 
 }
 
 
+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)
 {
 static gpg_error_t
 cmd_message (assuan_context_t ctx, char *line)
 {
@@ -2203,6 +2296,11 @@ cmd_message (assuan_context_t ctx, char *line)
 }
 
 
 }
 
 
+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)
 {
 static gpg_error_t
 cmd_recipient (assuan_context_t ctx, char *line)
 {
@@ -2212,6 +2310,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)
 {
 static gpg_error_t
 cmd_signer (assuan_context_t ctx, char *line)
 {
@@ -2221,6 +2324,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)
 {
 static gpg_error_t
 cmd_signers_clear (assuan_context_t ctx, char *line)
 {
@@ -2242,15 +2350,15 @@ _cmd_decrypt_verify (assuan_context_t ctx, char *line, int verify)
   gpgme_data_t inp_data;
   gpgme_data_t out_data;
 
   gpgme_data_t inp_data;
   gpgme_data_t out_data;
 
-  inp_fd = assuan_get_input_fd (ctx);
+  inp_fd = server->input_fd;
   inp_fn = server->input_filename;
   if (inp_fd == ASSUAN_INVALID_FD && !inp_fn)
     return GPG_ERR_ASS_NO_INPUT;
   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);
+  out_fd = server->output_fd;
   out_fn = server->output_filename;
   if (out_fd == ASSUAN_INVALID_FD && !out_fn)
     return GPG_ERR_ASS_NO_OUTPUT;
   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, inp_fn, 0, server->input_enc, &inp_data,
                         &server->input_stream);
   if (err)
   err = server_data_obj (inp_fd, inp_fn, 0, server->input_enc, &inp_data,
                         &server->input_stream);
   if (err)
@@ -2263,7 +2371,7 @@ _cmd_decrypt_verify (assuan_context_t ctx, char *line, int verify)
       return err;
     }
 
       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);
 
   gpgme_data_release (inp_data);
   gpgme_data_release (out_data);
@@ -2274,6 +2382,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)
 {
 static gpg_error_t
 cmd_decrypt (assuan_context_t ctx, char *line)
 {
@@ -2281,6 +2395,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)
 {
 static gpg_error_t
 cmd_decrypt_verify (assuan_context_t ctx, char *line)
 {
@@ -2309,10 +2429,10 @@ _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;
     flags |= GPGME_ENCRYPT_PREPARE;
   if (strstr (line, "--expect-sign"))
     flags |= GPGME_ENCRYPT_EXPECT_SIGN;
-  
-  inp_fd = assuan_get_input_fd (ctx);
+
+  inp_fd = server->input_fd;
   inp_fn = server->input_filename;
   inp_fn = server->input_filename;
-  out_fd = assuan_get_output_fd (ctx);
+  out_fd = server->output_fd;
   out_fn = server->output_filename;
   if (inp_fd != ASSUAN_INVALID_FD || inp_fn)
     {
   out_fn = server->output_filename;
   if (inp_fd != ASSUAN_INVALID_FD || inp_fn)
     {
@@ -2332,7 +2452,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);
 
   gpgme_data_release (inp_data);
   gpgme_data_release (out_data);
@@ -2343,6 +2463,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)
 {
 static gpg_error_t
 cmd_encrypt (assuan_context_t ctx, char *line)
 {
@@ -2350,6 +2478,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)
 {
 static gpg_error_t
 cmd_sign_encrypt (assuan_context_t ctx, char *line)
 {
@@ -2357,6 +2494,15 @@ 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)
 {
 static gpg_error_t
 cmd_sign (assuan_context_t ctx, char *line)
 {
@@ -2375,15 +2521,15 @@ cmd_sign (assuan_context_t ctx, char *line)
   if (strstr (line, "--detach"))
     mode = GPGME_SIG_MODE_DETACH;
 
   if (strstr (line, "--detach"))
     mode = GPGME_SIG_MODE_DETACH;
 
-  inp_fd = assuan_get_input_fd (ctx);
+  inp_fd = server->input_fd;
   inp_fn = server->input_filename;
   if (inp_fd == ASSUAN_INVALID_FD && !inp_fn)
     return GPG_ERR_ASS_NO_INPUT;
   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);
+  out_fd = server->output_fd;
   out_fn = server->output_filename;
   if (out_fd == ASSUAN_INVALID_FD && !out_fn)
     return GPG_ERR_ASS_NO_OUTPUT;
   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, inp_fn, 0, server->input_enc, &inp_data,
                         &server->input_stream);
   if (err)
   err = server_data_obj (inp_fd, inp_fn, 0, server->input_enc, &inp_data,
                         &server->input_stream);
   if (err)
@@ -2406,6 +2552,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)
 {
 static gpg_error_t
 cmd_verify (assuan_context_t ctx, char *line)
 {
@@ -2421,13 +2574,13 @@ cmd_verify (assuan_context_t ctx, char *line)
   gpgme_data_t msg_data = NULL;
   gpgme_data_t out_data = NULL;
 
   gpgme_data_t msg_data = NULL;
   gpgme_data_t out_data = NULL;
 
-  inp_fd = assuan_get_input_fd (ctx);
+  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;
   msg_fn = server->message_filename;
   inp_fn = server->input_filename;
   if (inp_fd == ASSUAN_INVALID_FD && !inp_fn)
     return GPG_ERR_ASS_NO_INPUT;
   msg_fd = server->message_fd;
   msg_fn = server->message_filename;
-  out_fd = assuan_get_output_fd (ctx);
+  out_fd = server->output_fd;
   out_fn = server->output_filename;
 
   err = server_data_obj (inp_fd, inp_fn, 0, server->input_enc, &inp_data,
   out_fn = server->output_filename;
 
   err = server_data_obj (inp_fd, inp_fn, 0, server->input_enc, &inp_data,
@@ -2470,11 +2623,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);
 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 };
   if (line && *line)
     {
       char *fprs[2] = { line, NULL };
@@ -2487,8 +2646,8 @@ cmd_import (assuan_context_t ctx, char *line)
       assuan_fd_t inp_fd;
       char *inp_fn;
       gpgme_data_t inp_data;
       assuan_fd_t inp_fd;
       char *inp_fn;
       gpgme_data_t inp_data;
-      
-      inp_fd = assuan_get_input_fd (ctx);
+
+      inp_fd = server->input_fd;
       inp_fn = server->input_filename;
       if (inp_fd == ASSUAN_INVALID_FD && !inp_fn)
        return GPG_ERR_ASS_NO_INPUT;
       inp_fn = server->input_filename;
       if (inp_fd == ASSUAN_INVALID_FD && !inp_fn)
        return GPG_ERR_ASS_NO_INPUT;
@@ -2497,9 +2656,9 @@ cmd_import (assuan_context_t ctx, char *line)
                             &server->input_stream);
       if (err)
        return err;
                             &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);
 
       gpgme_data_release (inp_data);
       server_reset_fds (server);
 
@@ -2508,7 +2667,7 @@ cmd_import (assuan_context_t ctx, char *line)
 }
 
 
 }
 
 
-static const char hlp_export[] = 
+static const char hlp_export[] =
   "EXPORT [--extern] [--minimal] [<pattern>]\n"
   "\n"
   "Export the keys described by PATTERN.  Write the\n"
   "EXPORT [--extern] [--minimal] [<pattern>]\n"
   "\n"
   "Export the keys described by PATTERN.  Write the\n"
@@ -2524,7 +2683,7 @@ cmd_export (assuan_context_t ctx, char *line)
   gpgme_export_mode_t mode = 0;
   const char *pattern[2];
 
   gpgme_export_mode_t mode = 0;
   const char *pattern[2];
 
-  out_fd = assuan_get_output_fd (ctx);
+  out_fd = server->output_fd;
   out_fn = server->output_filename;
   if (out_fd == ASSUAN_INVALID_FD && !out_fn)
     return GPG_ERR_ASS_NO_OUTPUT;
   out_fn = server->output_filename;
   if (out_fd == ASSUAN_INVALID_FD && !out_fn)
     return GPG_ERR_ASS_NO_OUTPUT;
@@ -2584,13 +2743,13 @@ cmd_genkey (assuan_context_t ctx, char *line)
   gpgme_data_t parms_data = NULL;
   const char *parms;
 
   gpgme_data_t parms_data = NULL;
   const char *parms;
 
-  inp_fd = assuan_get_input_fd (ctx);
+  inp_fd = server->input_fd;
   inp_fn = server->input_filename;
   if (inp_fd == ASSUAN_INVALID_FD && !inp_fn)
     return GPG_ERR_ASS_NO_INPUT;
   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);
+  out_fd = server->output_fd;
   out_fn = server->output_filename;
   out_fn = server->output_filename;
-  
+
   err = server_data_obj (inp_fd, inp_fn, 0, server->input_enc, &inp_data,
                         &server->input_stream);
   if (err)
   err = server_data_obj (inp_fd, inp_fn, 0, server->input_enc, &inp_data,
                         &server->input_stream);
   if (err)
@@ -2649,7 +2808,7 @@ cmd_genkey (assuan_context_t ctx, char *line)
   if (parms_data)
     gpgme_data_release (parms_data);
 
   if (parms_data)
     gpgme_data_release (parms_data);
 
-  return err; 
+  return err;
 }
 
 
 }
 
 
@@ -2671,14 +2830,22 @@ cmd_delete (assuan_context_t ctx, char *line)
 }
 
 
 }
 
 
+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)
 {
 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);
   gpg_error_t err;
   int secret_only = 0;
   struct server *server = assuan_get_pointer (ctx);
   gpg_error_t err;
   int secret_only = 0;
-  const char *pattern[2];
+  int idx;
+  const char *pattern[MAX_CMD_KEYLIST_PATTERN+1];
   const char optstr[] = "--secret-only";
   const char optstr[] = "--secret-only";
+  char *p;
 
   if (!strncasecmp (line, optstr, strlen (optstr)))
     {
 
   if (!strncasecmp (line, optstr, strlen (optstr)))
     {
@@ -2687,8 +2854,23 @@ cmd_keylist (assuan_context_t ctx, char *line)
       while (*line && !spacep (line))
        line++;
     }
       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;
 
   err = gt_keylist_start (server->gt, pattern, secret_only);
   while (! err)
 
   err = gt_keylist_start (server->gt, pattern, secret_only);
   while (! err)
@@ -2706,18 +2888,22 @@ cmd_keylist (assuan_context_t ctx, char *line)
          char buf[100];
          /* FIXME: More data.  */
          snprintf (buf, sizeof (buf), "key:%s\n", key->subkeys->fpr);
          char buf[100];
          /* FIXME: More data.  */
          snprintf (buf, sizeof (buf), "key:%s\n", key->subkeys->fpr);
-         assuan_send_data (ctx, buf, strlen (buf));
+          /* 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);
          gpgme_key_unref (key);
        }
     }
          gpgme_key_unref (key);
        }
     }
-  
+
   server_reset_fds (server);
 
   return err;
 }
 
 
   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"
   "GETAUDITLOG [--html] [--with-help]\n"
   "\n"
   "Call the function gpgme_op_getauditlog with the given flags.  Write\n"
@@ -2732,7 +2918,7 @@ cmd_getauditlog (assuan_context_t ctx, char *line)
   gpgme_data_t out_data;
   unsigned int flags = 0;
 
   gpgme_data_t out_data;
   unsigned int flags = 0;
 
-  out_fd = assuan_get_output_fd (ctx);
+  out_fd = server->output_fd;
   out_fn = server->output_filename;
   if (out_fd == ASSUAN_INVALID_FD && !out_fn)
     return GPG_ERR_ASS_NO_OUTPUT;
   out_fn = server->output_filename;
   if (out_fd == ASSUAN_INVALID_FD && !out_fn)
     return GPG_ERR_ASS_NO_OUTPUT;
@@ -2866,37 +3052,37 @@ register_commands (assuan_context_t ctx)
     /* RESET, BYE are implicit.  */
     { "VERSION", cmd_version, hlp_version },
     /* TODO: Set engine info.  */
     /* RESET, BYE are implicit.  */
     { "VERSION", cmd_version, hlp_version },
     /* TODO: Set engine info.  */
-    { "ENGINE", cmd_engine },
+    { "ENGINE", cmd_engine, hlp_engine },
     { "PROTOCOL", cmd_protocol, hlp_protocol },
     { "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", cmd_input }, 
-    { "OUTPUT", cmd_output }, 
-    { "MESSAGE", cmd_message },
-    { "RECIPIENT", cmd_recipient },
-    { "SIGNER", cmd_signer },
-    { "SIGNERS_CLEAR", cmd_signers_clear },
+    { "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. */
      /* 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 },
+    { "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) */
     { "EXPORT", cmd_export, hlp_export },
     { "GENKEY", cmd_genkey },
     { "DELETE", cmd_delete },
     /* TODO: EDIT, CARD_EDIT (with INQUIRE) */
-    { "KEYLIST", cmd_keylist },
-    { "LISTKEYS", cmd_keylist },
+    { "KEYLIST", cmd_keylist, hlp_keylist },
+    { "LISTKEYS", cmd_keylist, hlp_keylist },
     /* TODO: TRUSTLIST, TRUSTLIST_EXT */
     { "GETAUDITLOG", cmd_getauditlog, hlp_getauditlog },
     /* TODO: ASSUAN */
     /* TODO: TRUSTLIST, TRUSTLIST_EXT */
     { "GETAUDITLOG", cmd_getauditlog, hlp_getauditlog },
     /* TODO: ASSUAN */
@@ -2920,7 +3106,7 @@ register_commands (assuan_context_t ctx)
                                      table[idx].help);
       if (err)
         return err;
                                      table[idx].help);
       if (err)
         return err;
-    } 
+    }
   return 0;
 }
 
   return 0;
 }
 
@@ -2986,7 +3172,7 @@ gpgme_server (gpgme_tool_t gt)
          log_error (0, err, "assuan accept problem");
          break;
         }
          log_error (0, err, "assuan accept problem");
          break;
         }
-      
+
       err = assuan_process (server.assuan_ctx);
       if (err)
        log_error (0, err, "assuan processing failed");
       err = assuan_process (server.assuan_ctx);
       if (err)
        log_error (0, err, "assuan processing failed");
@@ -3003,7 +3189,7 @@ const char *argp_program_version = VERSION;
 const char *argp_program_bug_address = "bug-gpgme@gnupg.org";
 error_t argp_err_exit_status = 1;
 
 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[] = {
 static char args_doc[] = "COMMAND [OPTIONS...]";
 
 static struct argp_option options[] = {