* keygen.c (ask_key_flags): New. (ask_algo): Call it here in --expert mode
[gnupg.git] / sm / server.c
index 6af69e5..dda1509 100644 (file)
@@ -1,5 +1,5 @@
 /* server.c - Server mode and main entry point 
- *     Copyright (C) 2001 Free Software Foundation, Inc.
+ *     Copyright (C) 2001, 2002, 2003 Free Software Foundation, Inc.
  *
  * This file is part of GnuPG.
  *
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <stdarg.h>
 #include <ctype.h>
 #include <unistd.h>
 
+#include <assuan.h>
+
 #include "gpgsm.h"
-#include "../assuan/assuan.h"
 
 #define set_error(e,t) assuan_set_error (ctx, ASSUAN_ ## e, (t))
 
@@ -39,49 +41,50 @@ static FILE *statusfp;
 struct server_local_s {
   ASSUAN_CONTEXT assuan_ctx;
   int message_fd;
+  int list_internal;
+  int list_external;
   CERTLIST recplist;
+  CERTLIST signerlist;
 };
 
-/* Map GNUPG_xxx error codes to Assuan status codes */
-static int
-rc_to_assuan_status (int rc)
+
+
+/* note, that it is sufficient to allocate the target string D as
+   long as the source string S, i.e.: strlen(s)+1; */
+static void
+strcpy_escaped_plus (char *d, const unsigned char *s)
 {
-  switch (rc)
+  while (*s)
     {
-    case 0: break;
-    case GNUPG_Bad_Certificate:   rc = ASSUAN_Bad_Certificate; break;
-    case GNUPG_Bad_Certificate_Path: rc = ASSUAN_Bad_Certificate_Path; break;
-    case GNUPG_Missing_Certificate: rc = ASSUAN_Missing_Certificate; break;
-    case GNUPG_No_Data:           rc = ASSUAN_No_Data_Available; break;
-    case GNUPG_Bad_Signature:     rc = ASSUAN_Bad_Signature; break;
-    case GNUPG_Not_Implemented:   rc = ASSUAN_Not_Implemented; break;
-    case GNUPG_No_Agent:          rc = ASSUAN_No_Agent; break;
-    case GNUPG_Agent_Error:       rc = ASSUAN_Agent_Error; break;
-    case GNUPG_No_Public_Key:     rc = ASSUAN_No_Public_Key; break;
-    case GNUPG_No_Secret_Key:     rc = ASSUAN_No_Secret_Key; break;
-    case GNUPG_Invalid_Data:      rc = ASSUAN_Invalid_Data; break;
-    case GNUPG_Invalid_Name:      rc = ASSUAN_Invalid_Name; break;
-
-    case GNUPG_Read_Error: 
-    case GNUPG_Write_Error:
-    case GNUPG_IO_Error: 
-      rc = ASSUAN_Server_IO_Error;
-      break;
-    case GNUPG_Out_Of_Core:    
-    case GNUPG_Resource_Limit: 
-      rc = ASSUAN_Server_Resource_Problem;
-      break;
-    case GNUPG_Bug: 
-    case GNUPG_Internal_Error:   
-      rc = ASSUAN_Server_Bug;
-      break;
-    default: 
-      rc = ASSUAN_Server_Fault;
-      break;
+      if (*s == '%' && s[1] && s[2])
+        { 
+          s++;
+          *d++ = xtoi_2 ( s);
+          s += 2;
+        }
+      else if (*s == '+')
+        *d++ = ' ', s++;
+      else
+        *d++ = *s++;
     }
-  return rc;
+  *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)));
+}
+
+
 static void 
 close_message_fd (CTRL ctrl)
 {
@@ -92,14 +95,101 @@ close_message_fd (CTRL ctrl)
     }
 }
 
+
+static int
+option_handler (ASSUAN_CONTEXT ctx, const char *key, const char *value)
+{
+  CTRL ctrl = assuan_get_pointer (ctx);
+
+  if (!strcmp (key, "include-certs"))
+    {
+      int i = *value? atoi (value) : -1;
+      if (ctrl->include_certs < -2)
+        return ASSUAN_Parameter_Error;
+      ctrl->include_certs = i;
+    }
+  else   if (!strcmp (key, "display"))
+    {
+      if (opt.display)
+        free (opt.display);
+      opt.display = strdup (value);
+      if (!opt.display)
+        return ASSUAN_Out_Of_Core;
+    }
+  else if (!strcmp (key, "ttyname"))
+    {
+      if (opt.ttyname)
+        free (opt.ttyname);
+      opt.ttyname = strdup (value);
+      if (!opt.ttyname)
+        return ASSUAN_Out_Of_Core;
+    }
+  else if (!strcmp (key, "ttytype"))
+    {
+      if (opt.ttytype)
+        free (opt.ttytype);
+      opt.ttytype = strdup (value);
+      if (!opt.ttytype)
+        return ASSUAN_Out_Of_Core;
+    }
+  else if (!strcmp (key, "lc-ctype"))
+    {
+      if (opt.lc_ctype)
+        free (opt.lc_ctype);
+      opt.lc_ctype = strdup (value);
+      if (!opt.lc_ctype)
+        return ASSUAN_Out_Of_Core;
+    }
+  else if (!strcmp (key, "lc-messages"))
+    {
+      if (opt.lc_messages)
+        free (opt.lc_messages);
+      opt.lc_messages = strdup (value);
+      if (!opt.lc_messages)
+        return ASSUAN_Out_Of_Core;
+    }
+  else if (!strcmp (key, "list-mode"))
+    {
+      int i = *value? atoi (value) : 0;
+      if (!i || i == 1) /* default and mode 1 */
+        {
+          ctrl->server_local->list_internal = 1;
+          ctrl->server_local->list_external = 0;
+        }
+      else if (i == 2)
+        {
+          ctrl->server_local->list_internal = 0;
+          ctrl->server_local->list_external = 1;
+        }
+      else if (i == 3)
+        {
+          ctrl->server_local->list_internal = 1;
+          ctrl->server_local->list_external = 1;
+        }
+      else
+        return ASSUAN_Parameter_Error;
+    }
+  else
+    return ASSUAN_Invalid_Option;
+
+  return 0;
+}
+
+
+
+
 static void
 reset_notify (ASSUAN_CONTEXT ctx)
 {
   CTRL ctrl = assuan_get_pointer (ctx);
 
   gpgsm_release_certlist (ctrl->server_local->recplist);
+  gpgsm_release_certlist (ctrl->server_local->signerlist);
   ctrl->server_local->recplist = NULL;
+  ctrl->server_local->signerlist = NULL;
   close_message_fd (ctrl);
+  assuan_close_input_fd (ctx);
+  assuan_close_output_fd (ctx);
 }
 
 
@@ -153,9 +243,68 @@ cmd_recipient (ASSUAN_CONTEXT ctx, char *line)
   CTRL ctrl = assuan_get_pointer (ctx);
   int rc;
 
-  rc = gpgsm_add_to_certlist (line, &ctrl->server_local->recplist);
+  rc = gpgsm_add_to_certlist (ctrl, line, 0, &ctrl->server_local->recplist);
+  if (rc)
+    {
+      gpg_err_code_t r = gpg_err_code (rc);
+      gpgsm_status2 (ctrl, STATUS_INV_RECP,
+                   r == -1? "1":
+                   r == GPG_ERR_NO_PUBKEY?       "1":
+                   r == GPG_ERR_AMBIGUOUS_NAME?  "2":
+                   r == GPG_ERR_WRONG_KEY_USAGE? "3":
+                   r == GPG_ERR_CERT_REVOKED?    "4":
+                   r == GPG_ERR_CERT_EXPIRED?    "5":
+                   r == GPG_ERR_NO_CRL_KNOWN?    "6":
+                   r == GPG_ERR_CRL_TOO_OLD?     "7":
+                   r == GPG_ERR_NO_POLICY_MATCH? "8":
+                   "0",
+                   line, NULL);
+    }
+
+  return map_to_assuan_status (rc);
+}
+
+/*  SIGNER <userID>
+
+  Set the signer's keys for the signature creation.  <userID> should
+  be the internal representation of the key; the server may accept any
+  other way of specification [we will support this].  If this is a
+  valid and usable signing key the server does respond with OK,
+  otherwise it returns an ERR with the reason why the key can't be
+  used, the signing will then not be done for this key.  If the policy
+  is not to sign at all if not all signer keys are valid, the client
+  has to take care of this.  All SIGNER commands are cumulative until
+  a RESET but they are *not* reset by an SIGN command becuase it can
+  be expected that set of signers are used for more than one sign
+  operation.  
+
+  Note that this command returns an INV_RECP status which is a bit
+  strange, but they are very similar.  */
+static int 
+cmd_signer (ASSUAN_CONTEXT ctx, char *line)
+{
+  CTRL ctrl = assuan_get_pointer (ctx);
+  int rc;
 
-  return rc_to_assuan_status (rc);
+  rc = gpgsm_add_to_certlist (ctrl, line, 1, &ctrl->server_local->signerlist);
+  if (rc)
+    {
+      gpg_err_code_t r = gpg_err_code (rc);
+      gpgsm_status2 (ctrl, STATUS_INV_RECP,
+                   r == -1?                          "1":
+                   r == GPG_ERR_NO_PUBKEY?           "1":
+                   r == GPG_ERR_AMBIGUOUS_NAME?      "2":
+                   r == GPG_ERR_WRONG_KEY_USAGE?     "3":
+                   r == GPG_ERR_CERT_REVOKED?        "4":
+                   r == GPG_ERR_CERT_EXPIRED?        "5":
+                   r == GPG_ERR_NO_CRL_KNOWN?        "6":
+                   r == GPG_ERR_CRL_TOO_OLD?         "7":
+                   r == GPG_ERR_NO_POLICY_MATCH?     "8":
+                   r == GPG_ERR_NO_SECKEY?           "9":
+                   "0",
+                  line, NULL);
+    }
+  return map_to_assuan_status (rc);
 }
 
 
@@ -195,16 +344,13 @@ cmd_encrypt (ASSUAN_CONTEXT ctx, char *line)
                       inp_fd, out_fp);
   fclose (out_fp);
 
-  if (!rc)
-    {
-      gpgsm_release_certlist (ctrl->server_local->recplist);
-      ctrl->server_local->recplist = NULL;
-      /* close and reset the fd */
-      close_message_fd (ctrl);
-      assuan_close_input_fd (ctx);
-      assuan_close_output_fd (ctx);
-    }
-  return rc_to_assuan_status (rc);
+  gpgsm_release_certlist (ctrl->server_local->recplist);
+  ctrl->server_local->recplist = NULL;
+  /* close and reset the fd */
+  close_message_fd (ctrl);
+  assuan_close_input_fd (ctx);
+  assuan_close_output_fd (ctx);
+  return map_to_assuan_status (rc);
 }
 
 /* DECRYPT
@@ -235,15 +381,12 @@ cmd_decrypt (ASSUAN_CONTEXT ctx, char *line)
   rc = gpgsm_decrypt (ctrl, inp_fd, out_fp); 
   fclose (out_fp);
 
-  if (!rc)
-    {
-      /* close and reset the fd */
-      close_message_fd (ctrl);
-      assuan_close_input_fd (ctx);
-      assuan_close_output_fd (ctx);
-    }
+  /* close and reset the fd */
+  close_message_fd (ctrl);
+  assuan_close_input_fd (ctx);
+  assuan_close_output_fd (ctx);
 
-  return rc_to_assuan_status (rc);
+  return map_to_assuan_status (rc);
 }
 
 
@@ -280,15 +423,12 @@ cmd_verify (ASSUAN_CONTEXT ctx, char *line)
   if (out_fp)
     fclose (out_fp);
 
-  if (!rc)
-    {
-      /* close and reset the fd */
-      close_message_fd (ctrl);
-      assuan_close_input_fd (ctx);
-      assuan_close_output_fd (ctx);
-    }
+  /* close and reset the fd */
+  close_message_fd (ctrl);
+  assuan_close_input_fd (ctx);
+  assuan_close_output_fd (ctx);
 
-  return rc_to_assuan_status (rc);
+  return map_to_assuan_status (rc);
 }
 
 
@@ -313,23 +453,22 @@ cmd_sign (ASSUAN_CONTEXT ctx, char *line)
   if (out_fd == -1)
     return set_error (No_Output, NULL);
 
-  detached = !!strstr (line, "--detached");  /* fixme: this is ambiguous */
+  detached = has_option (line, "--detached"); 
 
   out_fp = fdopen ( dup(out_fd), "w");
   if (!out_fp)
     return set_error (General_Error, "fdopen() failed");
-  rc = gpgsm_sign (assuan_get_pointer (ctx), inp_fd, detached, out_fp);
+
+  rc = gpgsm_sign (assuan_get_pointer (ctx), ctrl->server_local->signerlist,
+                   inp_fd, detached, out_fp);
   fclose (out_fp);
 
-  if (!rc)
-    {
-      /* close and reset the fd */
-      close_message_fd (ctrl);
-      assuan_close_input_fd (ctx);
-      assuan_close_output_fd (ctx);
-    }
+  /* close and reset the fd */
+  close_message_fd (ctrl);
+  assuan_close_input_fd (ctx);
+  assuan_close_output_fd (ctx);
 
-  return rc_to_assuan_status (rc);
+  return map_to_assuan_status (rc);
 }
 
 
@@ -337,8 +476,8 @@ cmd_sign (ASSUAN_CONTEXT ctx, char *line)
 
   Import the certificates read form the input-fd, return status
   message for each imported one.  The import checks the validity of
-  the certificate but not of the path.  It is possible to import
-  expired certificates.  */
+  the certificate but not of the entire chain.  It is possible to
+  import expired certificates.  */
 static int 
 cmd_import (ASSUAN_CONTEXT ctx, char *line)
 {
@@ -351,16 +490,112 @@ cmd_import (ASSUAN_CONTEXT ctx, char *line)
 
   rc = gpgsm_import (assuan_get_pointer (ctx), fd);
 
-  if (!rc)
+  /* close and reset the fd */
+  close_message_fd (ctrl);
+  assuan_close_input_fd (ctx);
+  assuan_close_output_fd (ctx);
+
+  return map_to_assuan_status (rc);
+}
+
+
+static int 
+cmd_export (ASSUAN_CONTEXT ctx, char *line)
+{
+  CTRL ctrl = assuan_get_pointer (ctx);
+  int fd = assuan_get_output_fd (ctx);
+  FILE *out_fp;
+  char *p;
+  STRLIST list, sl;
+
+  if (fd == -1)
+    return set_error (No_Output, NULL);
+  
+  /* break the line down into an STRLIST */
+  list = NULL;
+  for (p=line; *p; line = p)
+    {
+      while (*p && *p != ' ')
+        p++;
+      if (*p)
+        *p++ = 0;
+      if (*line)
+        {
+          sl = xtrymalloc (sizeof *sl + strlen (line));
+          if (!sl)
+            {
+              free_strlist (list);
+              return ASSUAN_Out_Of_Core;
+            }
+          sl->flags = 0;
+          strcpy_escaped_plus (sl->d, line);
+          sl->next = list;
+          list = sl;
+        }
+    }
+
+  out_fp = fdopen ( dup(fd), "w");
+  if (!out_fp)
     {
-      /* close and reset the fd */
-      close_message_fd (ctrl);
-      assuan_close_input_fd (ctx);
-      assuan_close_output_fd (ctx);
+      free_strlist (list);
+      return set_error (General_Error, "fdopen() failed");
     }
-  return rc_to_assuan_status (rc);
+
+  gpgsm_export (ctrl, list, out_fp);
+  fclose (out_fp);
+  free_strlist (list);
+  /* close and reset the fd */
+  close_message_fd (ctrl);
+  assuan_close_input_fd (ctx);
+  assuan_close_output_fd (ctx);
+  return 0;
 }
 
+
+static int 
+cmd_delkeys (ASSUAN_CONTEXT ctx, char *line)
+{
+  CTRL ctrl = assuan_get_pointer (ctx);
+  char *p;
+  STRLIST list, sl;
+  int rc;
+
+  /* break the line down into an STRLIST */
+  list = NULL;
+  for (p=line; *p; line = p)
+    {
+      while (*p && *p != ' ')
+        p++;
+      if (*p)
+        *p++ = 0;
+      if (*line)
+        {
+          sl = xtrymalloc (sizeof *sl + strlen (line));
+          if (!sl)
+            {
+              free_strlist (list);
+              return ASSUAN_Out_Of_Core;
+            }
+          sl->flags = 0;
+          strcpy_escaped_plus (sl->d, line);
+          sl->next = list;
+          list = sl;
+        }
+    }
+
+  rc = gpgsm_delete (ctrl, list);
+  free_strlist (list);
+
+  /* close and reset the fd */
+  close_message_fd (ctrl);
+  assuan_close_input_fd (ctx);
+  assuan_close_output_fd (ctx);
+
+  return map_to_assuan_status (rc);
+}
+
+
+
 /* MESSAGE FD=<n>
 
    Set the file descriptor to read a message which is used with
@@ -387,19 +622,99 @@ cmd_message (ASSUAN_CONTEXT ctx, char *line)
   return 0;
 }
 
+
 static int 
-cmd_listkeys (ASSUAN_CONTEXT ctx, char *line)
+do_listkeys (ASSUAN_CONTEXT ctx, char *line, int mode)
 {
   CTRL ctrl = assuan_get_pointer (ctx);
+  FILE *fp = assuan_get_data_fp (ctx);
+  char *p;
+  STRLIST list, sl;
+  unsigned int listmode;
 
-  ctrl->with_colons = 1;
-  /* fixme: check that the returned data_fp is not NULL */
-  gpgsm_list_keys (assuan_get_pointer (ctx), NULL,
-                        assuan_get_data_fp (ctx));
+  if (!fp)
+    return set_error (General_Error, "no data stream");
+  
+  /* break the line down into an STRLIST */
+  list = NULL;
+  for (p=line; *p; line = p)
+    {
+      while (*p && *p != ' ')
+        p++;
+      if (*p)
+        *p++ = 0;
+      if (*line)
+        {
+          sl = xtrymalloc (sizeof *sl + strlen (line));
+          if (!sl)
+            {
+              free_strlist (list);
+              return ASSUAN_Out_Of_Core;
+            }
+          sl->flags = 0;
+          strcpy_escaped_plus (sl->d, line);
+          sl->next = list;
+          list = sl;
+        }
+    }
 
+  ctrl->with_colons = 1;
+  listmode = mode; 
+  if (ctrl->server_local->list_internal)
+    listmode |= (1<<6);
+  if (ctrl->server_local->list_external)
+    listmode |= (1<<7);
+  gpgsm_list_keys (assuan_get_pointer (ctx), list, fp, listmode);
+  free_strlist (list);
   return 0;
 }
 
+static int 
+cmd_listkeys (ASSUAN_CONTEXT ctx, char *line)
+{
+  return do_listkeys (ctx, line, 3);
+}
+
+static int 
+cmd_listsecretkeys (ASSUAN_CONTEXT ctx, char *line)
+{
+  return do_listkeys (ctx, line, 2);
+}
+
+\f
+/* GENKEY
+
+   Read the parameters in native format from the input fd and write a
+   certificate request to the output.
+ */
+static int 
+cmd_genkey (ASSUAN_CONTEXT ctx, char *line)
+{
+  CTRL ctrl = assuan_get_pointer (ctx);
+  int inp_fd, out_fd;
+  FILE *out_fp;
+  int rc;
+
+  inp_fd = assuan_get_input_fd (ctx);
+  if (inp_fd == -1)
+    return set_error (No_Input, NULL);
+  out_fd = assuan_get_output_fd (ctx);
+  if (out_fd == -1)
+    return set_error (No_Output, NULL);
+
+  out_fp = fdopen ( dup(out_fd), "w");
+  if (!out_fp)
+    return set_error (General_Error, "fdopen() failed");
+  rc = gpgsm_genkey (ctrl, inp_fd, out_fp);
+  fclose (out_fp);
+
+  /* close and reset the fds */
+  assuan_close_input_fd (ctx);
+  assuan_close_output_fd (ctx);
+
+  return map_to_assuan_status (rc);
+}
+
 
 
 
@@ -410,29 +725,30 @@ register_commands (ASSUAN_CONTEXT ctx)
 {
   static struct {
     const char *name;
-    int cmd_id;
     int (*handler)(ASSUAN_CONTEXT, char *line);
   } table[] = {
-    { "RECIPIENT",  0,  cmd_recipient },
-    { "ENCRYPT",    0,  cmd_encrypt },
-    { "DECRYPT",    0,  cmd_decrypt },
-    { "VERIFY",     0,  cmd_verify },
-    { "SIGN",       0,  cmd_sign },
-    { "IMPORT",     0,  cmd_import },
-    { "",     ASSUAN_CMD_INPUT, NULL }, 
-    { "",     ASSUAN_CMD_OUTPUT, NULL }, 
-    { "MESSAGE",    0,  cmd_message },
-    { "LISTKEYS",   0,  cmd_listkeys },
+    { "RECIPIENT",     cmd_recipient },
+    { "SIGNER",        cmd_signer },
+    { "ENCRYPT",       cmd_encrypt },
+    { "DECRYPT",       cmd_decrypt },
+    { "VERIFY",        cmd_verify },
+    { "SIGN",          cmd_sign },
+    { "IMPORT",        cmd_import },
+    { "EXPORT",        cmd_export },
+    { "INPUT",         NULL }, 
+    { "OUTPUT",        NULL }, 
+    { "MESSAGE",       cmd_message },
+    { "LISTKEYS",      cmd_listkeys },
+    { "LISTSECRETKEYS",cmd_listsecretkeys },
+    { "GENKEY",        cmd_genkey },
+    { "DELKEYS",       cmd_delkeys },
     { NULL }
   };
-  int i, j, rc;
+  int i, rc;
 
-  for (i=j=0; table[i].name; i++)
+  for (i=0; table[i].name; i++)
     {
-      rc = assuan_register_command (ctx,
-                                    table[i].cmd_id? table[i].cmd_id
-                                                   : (ASSUAN_CMD_USER + j++),
-                                    table[i].name, table[i].handler);
+      rc = assuan_register_command (ctx, table[i].name, table[i].handler);
       if (rc)
         return rc;
     } 
@@ -449,6 +765,7 @@ gpgsm_server (void)
   struct server_control_s ctrl;
 
   memset (&ctrl, 0, sizeof ctrl);
+  gpgsm_init_default_ctrl (&ctrl);
 
   /* For now we use a simple pipe based server so that we can work
      from scripts.  We will later add options to run as a daemon and
@@ -474,11 +791,17 @@ gpgsm_server (void)
   assuan_register_reset_notify (ctx, reset_notify);
   assuan_register_input_notify (ctx, input_notify);
   assuan_register_output_notify (ctx, output_notify);
+  assuan_register_option_handler (ctx, option_handler);
 
   assuan_set_pointer (ctx, &ctrl);
   ctrl.server_local = xcalloc (1, sizeof *ctrl.server_local);
   ctrl.server_local->assuan_ctx = ctx;
   ctrl.server_local->message_fd = -1;
+  ctrl.server_local->list_internal = 1;
+  ctrl.server_local->list_external = 0;
+
+  if (DBG_ASSUAN)
+    assuan_set_log_stream (ctx, log_get_stream ());
 
   for (;;)
     {
@@ -503,8 +826,10 @@ gpgsm_server (void)
 
   gpgsm_release_certlist (ctrl.server_local->recplist);
   ctrl.server_local->recplist = NULL;
+  gpgsm_release_certlist (ctrl.server_local->signerlist);
+  ctrl.server_local->signerlist = NULL;
 
-  assuan_deinit_pipe_server (ctx);
+  assuan_deinit_server (ctx);
 }
 
 
@@ -577,16 +902,25 @@ get_status_string ( int no )
     case STATUS_INV_RECP       : s = "INV_RECP"; break;
     case STATUS_NO_RECP        : s = "NO_RECP"; break;
     case STATUS_ALREADY_SIGNED : s = "ALREADY_SIGNED"; break;
+    case STATUS_EXPSIG         : s = "EXPSIG"; break;
+    case STATUS_EXPKEYSIG      : s = "EXPKEYSIG"; break;
+    case STATUS_TRUNCATED      : s = "TRUNCATED"; break;
+    case STATUS_ERROR          : s = "ERROR"; break;
+    case STATUS_IMPORT_PROBLEM : s = "IMPORT_PROBLEM"; break;
     default: s = "?"; break;
     }
   return s;
 }
 
 
-
 void
-gpgsm_status (CTRL ctrl, int no, const char *text)
+gpgsm_status2 (CTRL ctrl, int no, ...)
 {
+  va_list arg_ptr;
+  const char *text;
+
+  va_start (arg_ptr, no);
+
   if (ctrl->no_server)
     {
       if (ctrl->status_fd == -1)
@@ -610,7 +944,7 @@ gpgsm_status (CTRL ctrl, int no, const char *text)
       fputs ("[GNUPG:] ", statusfp);
       fputs (get_status_string (no), statusfp);
     
-      if (text)
+      while ( (text = va_arg (arg_ptr, const char*) ))
         {
           putc ( ' ', statusfp );
           for (; *text; text++) 
@@ -629,11 +963,46 @@ gpgsm_status (CTRL ctrl, int no, const char *text)
   else 
     {
       ASSUAN_CONTEXT ctx = ctrl->server_local->assuan_ctx;
+      char buf[950], *p;
+      size_t n;
 
-      assuan_write_status (ctx, get_status_string (no), text);
+      p = buf; 
+      n = 0;
+      while ( (text = va_arg (arg_ptr, const char *)) )
+        {
+          if (n)
+            {
+              *p++ = ' ';
+              n++;
+            }
+          for ( ; *text && n < DIM (buf)-2; n++)
+            *p++ = *text++;
+        }
+      *p = 0;
+      assuan_write_status (ctx, get_status_string (no), buf);
     }
+
+  va_end (arg_ptr);
+}
+
+void
+gpgsm_status (CTRL ctrl, int no, const char *text)
+{
+  gpgsm_status2 (ctrl, no, text, NULL);
 }
 
+void
+gpgsm_status_with_err_code (CTRL ctrl, int no, const char *text,
+                            gpg_err_code_t ec)
+{
+  char buf[30];
+
+  sprintf (buf, "%u", (unsigned int)ec);
+  if (text)
+    gpgsm_status2 (ctrl, no, text, buf, NULL);
+  else
+    gpgsm_status2 (ctrl, no, buf, NULL);
+}
 
 #if 0
 /*
@@ -699,10 +1068,3 @@ write_status_text_and_buffer ( int no, const char *string,
     fflush (statusfp);
 }
 #endif
-
-
-
-
-
-
-