Make use of the *_NAME etc macros.
[gnupg.git] / g10 / server.c
index 6ca7dfa..8bf7a08 100644 (file)
 #include <ctype.h>
 #include <unistd.h>
 
-#include <assuan.h>
 
 #include "gpg.h"
+#include <assuan.h>
 #include "util.h"
 #include "i18n.h"
 #include "options.h"
 #include "../common/sysutils.h"
+#include "status.h"
 
 
 #define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t))
 
 
 /* Data used to associate an Assuan context with local server data.  */
-struct server_local_s 
+struct server_local_s
 {
   /* Our current Assuan context. */
-  assuan_context_t assuan_ctx;  
+  assuan_context_t assuan_ctx;
   /* File descriptor as set by the MESSAGE command. */
-  gnupg_fd_t message_fd;               
+  gnupg_fd_t message_fd;
+
+  /* List of prepared recipients.  */
+  pk_list_t recplist;
+
+  /* Set if pinentry notifications should be passed back to the
+     client. */
+  int allow_pinentry_notify;
 };
 
 
 \f
 /* Helper to close the message fd if it is open. */
-static void 
+static void
 close_message_fd (ctrl_t ctrl)
 {
   if (ctrl->server_local->message_fd != GNUPG_INVALID_FD)
     {
       assuan_sock_close (ctrl->server_local->message_fd);
       ctrl->server_local->message_fd = GNUPG_INVALID_FD;
-    } 
+    }
+}
+
+
+/* Skip over options.  Blanks after the options are also removed.  */
+static char *
+skip_options (const char *line)
+{
+  while (spacep (line))
+    line++;
+  while ( *line == '-' && line[1] == '-' )
+    {
+      while (*line && !spacep (line))
+        line++;
+      while (spacep (line))
+        line++;
+    }
+  return (char*)line;
+}
+
+
+/* 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);
+  if (s && s >= skip_options (line))
+    return 0;
+  return (s && (s == line || spacep (s-1)) && (!s[n] || spacep (s+n)));
 }
 
 
+
+
 \f
 /* Called by libassuan for Assuan options.  See the Assuan manual for
    details. */
-static int
+static gpg_error_t
 option_handler (assuan_context_t ctx, const char *key, const char *value)
 {
-/*   ctrl_t ctrl = assuan_get_pointer (ctx); */
+  ctrl_t ctrl = assuan_get_pointer (ctx);
+
+  (void)value;
 
   /* Fixme: Implement the tty and locale args. */
   if (!strcmp (key, "display"))
@@ -95,6 +138,10 @@ option_handler (assuan_context_t ctx, const char *key, const char *value)
     {
       /* This is for now a dummy option. */
     }
+  else if (!strcmp (key, "allow-pinentry-notify"))
+    {
+      ctrl->server_local->allow_pinentry_notify = 1;
+    }
   else
     return gpg_error (GPG_ERR_UNKNOWN_OPTION);
 
@@ -103,23 +150,31 @@ option_handler (assuan_context_t ctx, const char *key, const char *value)
 
 
 /* Called by libassuan for RESET commands. */
-static void
-reset_notify (assuan_context_t ctx)
+static gpg_error_t
+reset_notify (assuan_context_t ctx, char *line)
 {
   ctrl_t ctrl = assuan_get_pointer (ctx);
 
+  (void)line;
+
+  release_pk_list (ctrl->server_local->recplist);
+  ctrl->server_local->recplist = NULL;
+
   close_message_fd (ctrl);
   assuan_close_input_fd (ctx);
   assuan_close_output_fd (ctx);
+  return 0;
 }
 
 
 /* Called by libassuan for INPUT commands. */
-static void
-input_notify (assuan_context_t ctx, const char *line)
+static gpg_error_t
+input_notify (assuan_context_t ctx, char *line)
 {
 /*   ctrl_t ctrl = assuan_get_pointer (ctx); */
 
+  (void)ctx;
+
   if (strstr (line, "--armor"))
     ; /* FIXME */
   else if (strstr (line, "--base64"))
@@ -127,26 +182,34 @@ input_notify (assuan_context_t ctx, const char *line)
   else if (strstr (line, "--binary"))
     ;
   else
-    ; /* FIXME (autodetect encoding) */
+    {
+      /* FIXME (autodetect encoding) */
+    }
+  return 0;
 }
 
 
 /* Called by libassuan for OUTPUT commands. */
-static void
-output_notify (assuan_context_t ctx, const char *line)
+static gpg_error_t
+output_notify (assuan_context_t ctx, char *line)
 {
 /*   ctrl_t ctrl = assuan_get_pointer (ctx); */
 
+  (void)ctx;
+
   if (strstr (line, "--armor"))
     ; /* FIXME */
   else if (strstr (line, "--base64"))
-    ; /* FIXME */
+    {
+      /* FIXME */
+    }
+  return 0;
 }
 
 
 
 \f
-/*  RECIPIENT <userID>
+/*  RECIPIENT [--hidden] <userID>
 
    Set the recipient for the encryption.  <userID> should be the
    internal representation of the key; the server may accept any other
@@ -157,10 +220,29 @@ output_notify (assuan_context_t ctx, const char *line)
    encrypt at all if not all recipients are valid, the client has to
    take care of this.  All RECIPIENT commands are cumulative until a
    RESET or an successful ENCRYPT command.  */
-static int 
+static gpg_error_t
 cmd_recipient (assuan_context_t ctx, char *line)
 {
-  return gpg_error (GPG_ERR_NOT_SUPPORTED);
+  ctrl_t ctrl = assuan_get_pointer (ctx);
+  gpg_error_t err;
+  int hidden;
+
+  hidden = has_option (line,"--hidden");
+  line = skip_options (line);
+
+  /* FIXME: Expand groups
+  if (opt.grouplist)
+    remusr = expand_group (rcpts);
+  else
+    remusr = rcpts;
+  */
+
+  err = find_and_check_key (ctrl, line, PUBKEY_USAGE_ENC, hidden,
+                            &ctrl->server_local->recplist);
+
+  if (err)
+    log_error ("command '%s' failed: %s\n", "RECIPIENT", gpg_strerror (err));
+  return err;
 }
 
 
@@ -180,43 +262,130 @@ cmd_recipient (assuan_context_t ctx, char *line)
 
    Note that this command returns an INV_RECP status which is a bit
    strange, but they are very similar.  */
-static int 
+static gpg_error_t
 cmd_signer (assuan_context_t ctx, char *line)
 {
+  (void)ctx;
+  (void)line;
   return gpg_error (GPG_ERR_NOT_SUPPORTED);
 }
 
 
 \f
-/*  ENCRYPT 
+/*  ENCRYPT
 
    Do the actual encryption process.  Takes the plaintext from the
-   INPUT command, writes to the ciphertext to the file descriptor set
-   with the OUTPUT command, take the recipients form all the
-   recipients set so far.  If this command fails the clients should
-   try to delete all output currently done or otherwise mark it as
-   invalid.  GPG does ensure that there won't be any security problem
-   with leftover data on the output in this case.
-
-   This command should in general not fail, as all necessary checks
-   have been done while setting the recipients.  The input and output
-   pipes are closed.  */
-static int 
+   INPUT command, writes the ciphertext to the file descriptor set
+   with the OUTPUT command, take the recipients from all the
+   recipients set so far with RECIPIENTS.
+
+   If this command fails the clients should try to delete all output
+   currently done or otherwise mark it as invalid.  GPG does ensure
+   that there won't be any security problem with leftover data on the
+   output in this case.
+
+   In most cases this command won't fail because most necessary checks
+   have been done while setting the recipients.  However some checks
+   can only be done right here and thus error may occur anyway (for
+   example, no recipients at all).
+
+   The input, output and message pipes are closed after this
+   command.  */
+static gpg_error_t
 cmd_encrypt (assuan_context_t ctx, char *line)
 {
-  return gpg_error (GPG_ERR_NOT_SUPPORTED);
+  ctrl_t ctrl = assuan_get_pointer (ctx);
+  gpg_error_t err;
+  int inp_fd, out_fd;
+
+  (void)line; /* LINE is not used.  */
+
+  if ( !ctrl->server_local->recplist )
+    {
+      write_status_text (STATUS_NO_RECP, "0");
+      err = gpg_error (GPG_ERR_NO_USER_ID);
+      goto leave;
+    }
+
+  inp_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0);
+  if (inp_fd == -1)
+    {
+      err = set_error (GPG_ERR_ASS_NO_INPUT, NULL);
+      goto leave;
+    }
+  out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1);
+  if (out_fd == -1)
+    {
+      err = set_error (GPG_ERR_ASS_NO_OUTPUT, NULL);
+      goto leave;
+    }
+
+  /* Fixme: Check that we are using real files and not pipes if in
+     PGP-2 mode.  Do all the other checks we do in gpg.c for aEncr.
+     Maybe we should drop the PGP2 compatibility. */
+
+
+  /* FIXME: GPGSM does this here: Add all encrypt-to marked recipients
+     from the default list. */
+
+  /* fixme: err = ctrl->audit? 0 : start_audit_session (ctrl);*/
+
+  err = encrypt_crypt (ctrl, inp_fd, NULL, NULL, 0,
+                       ctrl->server_local->recplist,
+                       out_fd);
+
+ leave:
+  /* Release the recipient list on success.  */
+  if (!err)
+    {
+      release_pk_list (ctrl->server_local->recplist);
+      ctrl->server_local->recplist = NULL;
+    }
+
+  /* Close and reset the fds. */
+  close_message_fd (ctrl);
+  assuan_close_input_fd (ctx);
+  assuan_close_output_fd (ctx);
+
+  if (err)
+    log_error ("command '%s' failed: %s\n", "ENCRYPT", gpg_strerror (err));
+  return err;
 }
 
 
 \f
 /*  DECRYPT
 
-   This performs the decrypt operation after doing some checks on the
-   internal state (e.g. that only needed data has been set).   */
-static int 
+    This performs the decrypt operation.  */
+static gpg_error_t
 cmd_decrypt (assuan_context_t ctx, char *line)
 {
-  return gpg_error (GPG_ERR_NOT_SUPPORTED);
+  ctrl_t ctrl = assuan_get_pointer (ctx);
+  gpg_error_t err;
+  int inp_fd, out_fd;
+
+  (void)line; /* LINE is not used.  */
+
+  inp_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0);
+  if (inp_fd == -1)
+    return set_error (GPG_ERR_ASS_NO_INPUT, NULL);
+  out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1);
+  if (out_fd == -1)
+    return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL);
+
+  glo_ctrl.lasterr = 0;
+  err = decrypt_message_fd (ctrl, inp_fd, out_fd);
+  if (!err)
+    err = glo_ctrl.lasterr;
+
+  /* Close and reset the fds. */
+  close_message_fd (ctrl);
+  assuan_close_input_fd (ctx);
+  assuan_close_output_fd (ctx);
+
+  if (err)
+    log_error ("command '%s' failed: %s\n", "DECRYPT", gpg_strerror (err));
+  return err;
 }
 
 
@@ -226,45 +395,46 @@ cmd_decrypt (assuan_context_t ctx, char *line)
    This does a verify operation on the message send to the input-FD.
    The result is written out using status lines.  If an output FD was
    given, the signed text will be written to that.
-  
+
    If the signature is a detached one, the server will inquire about
    the signed material and the client must provide it.
  */
-static int 
+static gpg_error_t
 cmd_verify (assuan_context_t ctx, char *line)
 {
   int rc;
   ctrl_t ctrl = assuan_get_pointer (ctx);
   gnupg_fd_t fd = assuan_get_input_fd (ctx);
   gnupg_fd_t out_fd = assuan_get_output_fd (ctx);
-  FILE *out_fp = NULL;
+  estream_t out_fp = NULL;
+
+  /* FIXME: Revamp this code it is nearly to 3 years old and was only
+     intended as a quick test.  */
+
+  (void)line;
 
   if (fd == GNUPG_INVALID_FD)
     return gpg_error (GPG_ERR_ASS_NO_INPUT);
 
   if (out_fd != GNUPG_INVALID_FD)
     {
-      out_fp = fdopen ( dup (FD2INT (out_fd)), "w");
+      out_fp = es_fdopen_nc (out_fd, "w");
       if (!out_fp)
-        return set_error (GPG_ERR_ASS_GENERAL, "fdopen() failed");
+        return set_error (gpg_err_code_from_syserror (), "fdopen() failed");
     }
 
-  log_debug ("WARNING: The server mode work "
-             "in progress and not ready for use\n");
+  log_debug ("WARNING: The server mode is WORK "
+             "iN PROGRESS and not ready for use\n");
 
-  /* Need to dup it because it might get closed and libassuan won't
-     know about it then. */
-  rc = gpg_verify (ctrl,
-                   dup ( FD2INT (fd)), 
-                   dup ( FD2INT (ctrl->server_local->message_fd)),
-                   out_fp);
+  rc = gpg_verify (ctrl, fd, ctrl->server_local->message_fd, out_fp);
 
-  if (out_fp)
-    fclose (out_fp);
+  es_fclose (out_fp);
   close_message_fd (ctrl);
   assuan_close_input_fd (ctx);
   assuan_close_output_fd (ctx);
 
+  if (rc)
+    log_error ("command '%s' failed: %s\n", "VERIFY", gpg_strerror (rc));
   return rc;
 }
 
@@ -275,9 +445,11 @@ cmd_verify (assuan_context_t ctx, char *line)
    Sign the data set with the INPUT command and write it to the sink
    set by OUTPUT.  With "--detached" specified, a detached signature
    is created.  */
-static int 
+static gpg_error_t
 cmd_sign (assuan_context_t ctx, char *line)
 {
+  (void)ctx;
+  (void)line;
   return gpg_error (GPG_ERR_NOT_SUPPORTED);
 }
 
@@ -287,9 +459,11 @@ cmd_sign (assuan_context_t ctx, char *line)
 
   Import keys as read from the input-fd, return status message for
   each imported one.  The import checks the validity of the key.  */
-static int 
+static gpg_error_t
 cmd_import (assuan_context_t ctx, char *line)
 {
+  (void)ctx;
+  (void)line;
   return gpg_error (GPG_ERR_NOT_SUPPORTED);
 }
 
@@ -305,9 +479,11 @@ cmd_import (assuan_context_t ctx, char *line)
    Recall that in general the output format is set with the OUTPUT
    command.
  */
-static int 
+static gpg_error_t
 cmd_export (assuan_context_t ctx, char *line)
 {
+  (void)ctx;
+  (void)line;
   return gpg_error (GPG_ERR_NOT_SUPPORTED);
 }
 
@@ -317,9 +493,11 @@ cmd_export (assuan_context_t ctx, char *line)
 
     Fixme
 */
-static int 
+static gpg_error_t
 cmd_delkeys (assuan_context_t ctx, char *line)
 {
+  (void)ctx;
+  (void)line;
   return gpg_error (GPG_ERR_NOT_SUPPORTED);
 }
 
@@ -329,7 +507,7 @@ cmd_delkeys (assuan_context_t ctx, char *line)
 
    Set the file descriptor to read a message which is used with
    detached signatures.  */
-static int 
+static gpg_error_t
 cmd_message (assuan_context_t ctx, char *line)
 {
   int rc;
@@ -352,21 +530,25 @@ cmd_message (assuan_context_t ctx, char *line)
 
    fixme
 */
-static int 
+static gpg_error_t
 do_listkeys (assuan_context_t ctx, char *line, int mode)
 {
+  (void)ctx;
+  (void)line;
+  (void)mode;
+
   return gpg_error (GPG_ERR_NOT_SUPPORTED);
 }
 
 
-static int 
+static gpg_error_t
 cmd_listkeys (assuan_context_t ctx, char *line)
 {
   return do_listkeys (ctx, line, 3);
 }
 
 
-static int 
+static gpg_error_t
 cmd_listsecretkeys (assuan_context_t ctx, char *line)
 {
   return do_listkeys (ctx, line, 2);
@@ -379,9 +561,11 @@ cmd_listsecretkeys (assuan_context_t ctx, char *line)
    Read the parameters in native format from the input fd and create a
    new OpenPGP key.
  */
-static int 
+static gpg_error_t
 cmd_genkey (assuan_context_t ctx, char *line)
 {
+  (void)ctx;
+  (void)line;
   return gpg_error (GPG_ERR_NOT_SUPPORTED);
 }
 
@@ -395,7 +579,7 @@ cmd_genkey (assuan_context_t ctx, char *line)
      pid         - Return the process id of the server.
 
  */
-static int
+static gpg_error_t
 cmd_getinfo (assuan_context_t ctx, char *line)
 {
   int rc;
@@ -417,16 +601,36 @@ cmd_getinfo (assuan_context_t ctx, char *line)
   return rc;
 }
 
+static const char hlp_passwd[] =
+  "PASSWD <userID>\n"
+  "\n"
+  "Change the passphrase of the secret key for USERID.";
+static gpg_error_t
+cmd_passwd (assuan_context_t ctx, char *line)
+{
+  /* ctrl_t ctrl = assuan_get_pointer (ctx); */
+  gpg_error_t err;
+
+  (void)ctx;
+  line = skip_options (line);
+
+  err = gpg_error (GPG_ERR_NOT_SUPPORTED);
+
+  return err;
+}
+
+
 
 \f
 /* Helper to register our commands with libassuan. */
 static int
 register_commands (assuan_context_t ctx)
 {
-  static struct 
+  static struct
   {
     const char *name;
-    int (*handler)(assuan_context_t, char *line);
+    assuan_handler_t handler;
+    const char * const help;
   } table[] = {
     { "RECIPIENT",     cmd_recipient },
     { "SIGNER",        cmd_signer    },
@@ -436,24 +640,26 @@ register_commands (assuan_context_t ctx)
     { "SIGN",          cmd_sign      },
     { "IMPORT",        cmd_import    },
     { "EXPORT",        cmd_export    },
-    { "INPUT",         NULL          }, 
-    { "OUTPUT",        NULL          }, 
+    { "INPUT",         NULL          },
+    { "OUTPUT",        NULL          },
     { "MESSAGE",       cmd_message   },
     { "LISTKEYS",      cmd_listkeys  },
     { "LISTSECRETKEYS",cmd_listsecretkeys },
     { "GENKEY",        cmd_genkey    },
     { "DELKEYS",       cmd_delkeys   },
     { "GETINFO",       cmd_getinfo   },
+    { "PASSWD",        cmd_passwd,  hlp_passwd},
     { NULL }
   };
   int i, rc;
 
   for (i=0; table[i].name; i++)
     {
-      rc = assuan_register_command (ctx, table[i].name, table[i].handler);
+      rc = assuan_register_command (ctx, table[i].name,
+                                    table[i].handler, table[i].help);
       if (rc)
         return rc;
-    } 
+    }
   return 0;
 }
 
@@ -467,16 +673,24 @@ gpg_server (ctrl_t ctrl)
 {
   int rc;
   int filedes[2];
-  assuan_context_t ctx;
+  assuan_context_t ctx = NULL;
   static const char hello[] = ("GNU Privacy Guard's OpenPGP server "
                                VERSION " ready");
 
   /* 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 FILEDES in this case.  */
-  filedes[0] = 0;
-  filedes[1] = 1;
-  rc = assuan_init_pipe_server (&ctx, filedes);
+  filedes[0] = assuan_fdopen (0);
+  filedes[1] = assuan_fdopen (1);
+  rc = assuan_new (&ctx);
+  if (rc)
+    {
+      log_error ("failed to allocate the assuan context: %s\n",
+                gpg_strerror (rc));
+      goto leave;
+    }
+
+  rc = assuan_init_pipe_server (ctx, filedes);
   if (rc)
     {
       log_error ("failed to initialize the server: %s\n", gpg_strerror (rc));
@@ -495,20 +709,20 @@ gpg_server (ctrl_t ctrl)
   if (opt.verbose || opt.debug)
     {
       char *tmp = NULL;
-      const char *s1 = getenv ("GPG_AGENT_INFO");
-
-      if (asprintf (&tmp,
-                    "Home: %s\n"
-                    "Config: %s\n"
-                    "AgentInfo: %s\n"
-                    "%s",
-                    opt.homedir,
-                    "fixme: need config filename",
-                    s1?s1:"[not set]",
-                    hello) > 0)
+      const char *s1 = getenv (GPG_AGENT_INFO_NAME);
+
+      tmp = xtryasprintf ("Home: %s\n"
+                          "Config: %s\n"
+                          "AgentInfo: %s\n"
+                          "%s",
+                          opt.homedir,
+                          "fixme: need config filename",
+                          s1?s1:"[not set]",
+                          hello);
+      if (tmp)
         {
           assuan_set_hello_line (ctx, tmp);
-          free (tmp);
+          xfree (tmp);
         }
     }
   else
@@ -527,9 +741,6 @@ gpg_server (ctrl_t ctrl)
   ctrl->server_local->assuan_ctx = ctx;
   ctrl->server_local->message_fd = GNUPG_INVALID_FD;
 
-  if (DBG_ASSUAN)
-    assuan_set_log_stream (ctx, log_get_stream ());
-
   for (;;)
     {
       rc = assuan_accept (ctx);
@@ -543,7 +754,7 @@ gpg_server (ctrl_t ctrl)
           log_info ("Assuan accept problem: %s\n", gpg_strerror (rc));
           break;
         }
-      
+
       rc = assuan_process (ctx);
       if (rc)
         {
@@ -553,9 +764,39 @@ gpg_server (ctrl_t ctrl)
     }
 
  leave:
-  xfree (ctrl->server_local);
-  ctrl->server_local = NULL;
-  assuan_deinit_server (ctx);
+  if (ctrl->server_local)
+    {
+      release_pk_list (ctrl->server_local->recplist);
+
+      xfree (ctrl->server_local);
+      ctrl->server_local = NULL;
+    }
+  assuan_release (ctx);
   return rc;
 }
 
+
+/* Helper to notify the client about Pinentry events.  Because that
+   might disturb some older clients, this is only done when enabled
+   via an option.  If it is not enabled we tell Windows to allow
+   setting the foreground window right here.  Returns an gpg error
+   code. */
+gpg_error_t
+gpg_proxy_pinentry_notify (ctrl_t ctrl, const unsigned char *line)
+{
+  if (!ctrl || !ctrl->server_local
+      || !ctrl->server_local->allow_pinentry_notify)
+    {
+      gnupg_allow_set_foregound_window ((pid_t)strtoul (line+17, NULL, 10));
+      /* Client might be interested in that event - send as status line.  */
+      if (!strncmp (line, "PINENTRY_LAUNCHED", 17)
+          && (line[17]==' '||!line[17]))
+        {
+          for (line += 17; *line && spacep (line); line++)
+            ;
+          write_status_text (STATUS_PINENTRY_LAUNCHED, line);
+        }
+      return 0;
+    }
+  return assuan_inquire (ctrl->server_local->assuan_ctx, line, NULL, NULL, 0);
+}