Make use of the *_NAME etc macros.
[gnupg.git] / g10 / server.c
index b228595..8bf7a08 100644 (file)
 #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 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)ctx;
   (void)value;
 
   /* Fixme: Implement the tty and locale args. */
@@ -98,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);
 
@@ -106,20 +150,26 @@ 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); */
 
@@ -135,15 +185,16 @@ input_notify (assuan_context_t ctx, const char *line)
     {
       /* 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"))
@@ -152,12 +203,13 @@ output_notify (assuan_context_t ctx, const char *line)
     {
       /* 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
@@ -171,9 +223,26 @@ output_notify (assuan_context_t ctx, const char *line)
 static gpg_error_t
 cmd_recipient (assuan_context_t ctx, char *line)
 {
-  (void)ctx;
-  (void)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;
 }
 
 
@@ -203,39 +272,120 @@ cmd_signer (assuan_context_t ctx, char *line)
 
 
 \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.  */
+   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)
 {
-  (void)ctx;
-  (void)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).   */
+    This performs the decrypt operation.  */
 static gpg_error_t
 cmd_decrypt (assuan_context_t ctx, char *line)
 {
-  (void)ctx;
-  (void)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;
 }
 
 
@@ -245,7 +395,7 @@ 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.
  */
@@ -256,7 +406,10 @@ cmd_verify (assuan_context_t ctx, char *line)
   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;
 
@@ -265,27 +418,23 @@ cmd_verify (assuan_context_t ctx, char *line)
 
   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;
 }
 
@@ -452,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;
-    gpg_error_t (*handler)(assuan_context_t, char *line);
+    assuan_handler_t handler;
+    const char * const help;
   } table[] = {
     { "RECIPIENT",     cmd_recipient },
     { "SIGNER",        cmd_signer    },
@@ -471,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;
 }
 
@@ -509,8 +680,8 @@ gpg_server (ctrl_t ctrl)
   /* 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;
+  filedes[0] = assuan_fdopen (0);
+  filedes[1] = assuan_fdopen (1);
   rc = assuan_new (&ctx);
   if (rc)
     {
@@ -518,7 +689,7 @@ gpg_server (ctrl_t ctrl)
                 gpg_strerror (rc));
       goto leave;
     }
-  
+
   rc = assuan_init_pipe_server (ctx, filedes);
   if (rc)
     {
@@ -538,7 +709,7 @@ gpg_server (ctrl_t ctrl)
   if (opt.verbose || opt.debug)
     {
       char *tmp = NULL;
-      const char *s1 = getenv ("GPG_AGENT_INFO");
+      const char *s1 = getenv (GPG_AGENT_INFO_NAME);
 
       tmp = xtryasprintf ("Home: %s\n"
                           "Config: %s\n"
@@ -570,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);
@@ -586,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)
         {
@@ -596,9 +764,39 @@ gpg_server (ctrl_t ctrl)
     }
 
  leave:
-  xfree (ctrl->server_local);
-  ctrl->server_local = NULL;
+  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);
+}