* packet.h, build-packet.c (build_attribute_subpkt), exec.c (expand_args),
[gnupg.git] / assuan / assuan-handler.c
index 90f170a..69b34b4 100644 (file)
@@ -25,6 +25,7 @@
 
 #include "assuan-defs.h"
 
+#define spacep(p)  (*(p) == ' ' || *(p) == '\t')
 #define digitp(a) ((a) >= '0' && (a) <= '9')
 
 
@@ -44,12 +45,65 @@ std_handler_nop (ASSUAN_CONTEXT ctx, char *line)
 static int
 std_handler_cancel (ASSUAN_CONTEXT ctx, char *line)
 {
+  if (ctx->cancel_notify_fnc)
+    ctx->cancel_notify_fnc (ctx);
   return set_error (ctx, Not_Implemented, NULL); 
 }
+
+static int
+std_handler_option (ASSUAN_CONTEXT ctx, char *line)
+{
+  char *key, *value, *p;
+
+  for (key=line; spacep (key); key++)
+    ;
+  if (!*key)
+    return set_error (ctx, Syntax_Error, "argument required");
+  if (*key == '=')
+    return set_error (ctx, Syntax_Error, "no option name given");
+  for (value=key; *value && !spacep (value) && *value != '='; value++)
+    ;
+  if (*value)
+    {
+      if (spacep (value))
+        *value++ = 0; /* terminate key */
+      for (; spacep (value); value++)
+        ;
+      if (*value == '=')
+        {
+          *value++ = 0; /* terminate key */
+          for (; spacep (value); value++)
+            ;
+          if (!*value)
+            return set_error (ctx, Syntax_Error, "option argument expected");
+        }
+      if (*value)
+        {
+          for (p = value + strlen(value) - 1; p > value && spacep (p); p--)
+            ;
+          if (p > value)
+            *++p = 0; /* strip trailing spaces */
+        }
+    }
+
+  if (*key == '-' && key[1] == '-' && key[2])
+    key += 2; /* the double dashes are optional */
+  if (*key == '-')
+    return set_error (ctx, Syntax_Error,
+                      "option should not begin with one dash");
+
+  if (ctx->option_handler_fnc)
+    return ctx->option_handler_fnc (ctx, key, value);
+  return 0;
+}
   
 static int
 std_handler_bye (ASSUAN_CONTEXT ctx, char *line)
 {
+  if (ctx->bye_notify_fnc)
+    ctx->bye_notify_fnc (ctx);
+  assuan_close_input_fd (ctx);
+  assuan_close_output_fd (ctx);
   return -1; /* pretty simple :-) */
 }
   
@@ -62,7 +116,11 @@ std_handler_auth (ASSUAN_CONTEXT ctx, char *line)
 static int
 std_handler_reset (ASSUAN_CONTEXT ctx, char *line)
 {
-  return set_error (ctx, Not_Implemented, NULL); 
+  if (ctx->reset_notify_fnc)
+    ctx->reset_notify_fnc (ctx);
+  assuan_close_input_fd (ctx);
+  assuan_close_output_fd (ctx);
+  return 0;
 }
   
 static int
@@ -82,8 +140,9 @@ parse_cmd_input_output (ASSUAN_CONTEXT ctx, char *line, int *rfd)
   if (!digitp (*line))
     return set_error (ctx, Syntax_Error, "number required");
   *rfd = strtoul (line, &endp, 10);
-  if (*endp)
-    return set_error (ctx, Syntax_Error, "garbage found");
+  /* remove that argument so that a notify handler won't see it */
+  memset (line, ' ', endp? (endp-line):strlen(line));
+
   if (*rfd == ctx->inbound.fd)
     return set_error (ctx, Parameter_Conflict, "fd same as inbound fd");
   if (*rfd == ctx->outbound.fd)
@@ -101,6 +160,8 @@ std_handler_input (ASSUAN_CONTEXT ctx, char *line)
   if (rc)
     return rc;
   ctx->input_fd = fd;
+  if (ctx->input_notify_fnc)
+    ctx->input_notify_fnc (ctx, line);
   return 0;
 }
 
@@ -114,6 +175,8 @@ std_handler_output (ASSUAN_CONTEXT ctx, char *line)
   if (rc)
     return rc;
   ctx->output_fd = fd;
+  if (ctx->output_notify_fnc)
+    ctx->output_notify_fnc (ctx, line);
   return 0;
 }
 
@@ -128,10 +191,11 @@ static struct {
   const char *name;
   int cmd_id;
   int (*handler)(ASSUAN_CONTEXT, char *line);
-  int always; /* always initializethis command */
+  int always; /* always initialize this command */
 } std_cmd_table[] = {
   { "NOP",    ASSUAN_CMD_NOP,    std_handler_nop, 1 },
   { "CANCEL", ASSUAN_CMD_CANCEL, std_handler_cancel, 1 },
+  { "OPTION", ASSUAN_CMD_OPTION, std_handler_option, 1 },
   { "BYE",    ASSUAN_CMD_BYE,    std_handler_bye, 1 },
   { "AUTH",   ASSUAN_CMD_AUTH,   std_handler_auth, 1 },
   { "RESET",  ASSUAN_CMD_RESET,  std_handler_reset, 1 },
@@ -139,6 +203,7 @@ static struct {
 
   { "INPUT",  ASSUAN_CMD_INPUT,  std_handler_input },
   { "OUTPUT", ASSUAN_CMD_OUTPUT, std_handler_output },
+  { "OPTION", ASSUAN_CMD_OPTION, std_handler_option, 1 },
   { NULL }
 };
 
@@ -192,11 +257,11 @@ assuan_register_command (ASSUAN_CONTEXT ctx,
   if (!cmd_name)
     return ASSUAN_Invalid_Value;
 
-  fprintf (stderr, "DBG-assuan: registering %d as `%s'\n", cmd_id, cmd_name);
+/*    fprintf (stderr, "DBG-assuan: registering %d as `%s'\n", cmd_id, cmd_name); */
 
   if (!ctx->cmdtbl)
     {
-      ctx->cmdtbl_size = 10;
+      ctx->cmdtbl_size = 50;
       ctx->cmdtbl = xtrycalloc ( ctx->cmdtbl_size, sizeof *ctx->cmdtbl);
       if (!ctx->cmdtbl)
         return ASSUAN_Out_Of_Core;
@@ -206,13 +271,11 @@ assuan_register_command (ASSUAN_CONTEXT ctx,
     {
       struct cmdtbl_s *x;
 
-      fprintf (stderr, "DBG-assuan: enlarging cmdtbl\n");
-      
       x = xtryrealloc ( ctx->cmdtbl, (ctx->cmdtbl_size+10) * sizeof *x);
       if (!x)
         return ASSUAN_Out_Of_Core;
       ctx->cmdtbl = x;
-      ctx->cmdtbl_size += 10;
+      ctx->cmdtbl_size += 50;
     }
 
   ctx->cmdtbl[ctx->cmdtbl_used].name = cmd_name;
@@ -222,6 +285,65 @@ assuan_register_command (ASSUAN_CONTEXT ctx,
   return 0;
 }
 
+int
+assuan_register_bye_notify (ASSUAN_CONTEXT ctx, void (*fnc)(ASSUAN_CONTEXT))
+{
+  if (!ctx)
+    return ASSUAN_Invalid_Value;
+  ctx->bye_notify_fnc = fnc;
+  return 0;
+}
+
+int
+assuan_register_reset_notify (ASSUAN_CONTEXT ctx, void (*fnc)(ASSUAN_CONTEXT))
+{
+  if (!ctx)
+    return ASSUAN_Invalid_Value;
+  ctx->reset_notify_fnc = fnc;
+  return 0;
+}
+
+int
+assuan_register_cancel_notify (ASSUAN_CONTEXT ctx, void (*fnc)(ASSUAN_CONTEXT))
+{
+  if (!ctx)
+    return ASSUAN_Invalid_Value;
+  ctx->cancel_notify_fnc = fnc;
+  return 0;
+}
+
+int
+assuan_register_option_handler (ASSUAN_CONTEXT ctx,
+                               int (*fnc)(ASSUAN_CONTEXT,
+                                          const char*, const char*))
+{
+  if (!ctx)
+    return ASSUAN_Invalid_Value;
+  ctx->option_handler_fnc = fnc;
+  return 0;
+}
+
+int
+assuan_register_input_notify (ASSUAN_CONTEXT ctx,
+                              void (*fnc)(ASSUAN_CONTEXT, const char *))
+{
+  if (!ctx)
+    return ASSUAN_Invalid_Value;
+  ctx->input_notify_fnc = fnc;
+  return 0;
+}
+
+int
+assuan_register_output_notify (ASSUAN_CONTEXT ctx,
+                              void (*fnc)(ASSUAN_CONTEXT, const char *))
+{
+  if (!ctx)
+    return ASSUAN_Invalid_Value;
+  ctx->output_notify_fnc = fnc;
+  return 0;
+}
+
+
 /* Helper to register the standards commands */
 int
 _assuan_register_std_commands (ASSUAN_CONTEXT ctx)
@@ -251,6 +373,20 @@ handle_data_line (ASSUAN_CONTEXT ctx, char *line, int linelen)
   return set_error (ctx, Not_Implemented, NULL);
 }
 
+/* like ascii_strcasecmp but assume that B is already uppercase */
+static int
+my_strcasecmp (const char *a, const char *b)
+{
+    if (a == b)
+        return 0;
+
+    for (; *a && *b; a++, b++)
+      {
+       if (((*a >= 'a' && *a <= 'z')? (*a&~0x20):*a) != *b)
+           break;
+      }
+    return *a == *b? 0 : (((*a >= 'a' && *a <= 'z')? (*a&~0x20):*a) - *b);
+}
 
 /* Parse the line, break out the command, find it in the command
    table, remove leading and white spaces from the arguments, all the
@@ -278,19 +414,97 @@ dispatch_command (ASSUAN_CONTEXT ctx, char *line, int linelen)
   shift = p - line;
 
   for (i=0; (s=ctx->cmdtbl[i].name); i++)
-    if (!strcmp (line, s))
-      break;
+    {
+      if (!strcmp (line, s))
+        break;
+    }
+  if (!s)
+    { /* and try case insensitive */
+      for (i=0; (s=ctx->cmdtbl[i].name); i++)
+        {
+          if (!my_strcasecmp (line, s))
+            break;
+        }
+    }
   if (!s)
     return set_error (ctx, Unknown_Command, NULL);
   line += shift;
   linelen -= shift;
 
-  fprintf (stderr, "DBG-assuan: processing %s `%s'\n", s, line);
+/*    fprintf (stderr, "DBG-assuan: processing %s `%s'\n", s, line); */
   return ctx->cmdtbl[i].handler (ctx, line);
 }
 
 
 
+\f
+static int
+process_request (ASSUAN_CONTEXT ctx)
+{
+  int rc;
+
+  if (ctx->in_inquire)
+    return ASSUAN_Nested_Commands;
+
+  rc = _assuan_read_line (ctx);
+  if (rc)
+    return rc;
+  if (*ctx->inbound.line == '#' || !ctx->inbound.linelen)
+    return 0; /* comment line - ignore */
+
+  ctx->outbound.data.error = 0;
+  ctx->outbound.data.linelen = 0;
+  /* dispatch command and return reply */
+  rc = dispatch_command (ctx, ctx->inbound.line, ctx->inbound.linelen);
+  /* check from data write errors */
+  if (ctx->outbound.data.fp)
+    { /* Flush the data lines */
+      fclose (ctx->outbound.data.fp);
+      ctx->outbound.data.fp = NULL;
+      if (!rc && ctx->outbound.data.error)
+        rc = ctx->outbound.data.error;
+    }
+  else /* flush any data send w/o using the data fp */
+    {
+      assuan_send_data (ctx, NULL, 0);
+      if (!rc && ctx->outbound.data.error)
+        rc = ctx->outbound.data.error;
+    }
+  /* Error handling */
+  if (!rc)
+    {
+      rc = assuan_write_line (ctx, ctx->okay_line? ctx->okay_line : "OK");
+    }
+  else if (rc == -1)
+    { /* No error checking because the peer may have already disconnect */ 
+      assuan_write_line (ctx, "OK closing connection");
+      ctx->finish_handler (ctx);
+    }
+  else 
+    {
+      char errline[256];
+
+      if (rc < 100)
+        sprintf (errline, "ERR %d server fault (%.50s)",
+                 ASSUAN_Server_Fault, assuan_strerror (rc));
+      else
+        {
+          const char *text = ctx->err_no == rc? ctx->err_str:NULL;
+
+          sprintf (errline, "ERR %d %.50s%s%.100s",
+                   rc, assuan_strerror (rc), text? " - ":"", text?text:"");
+        }
+      rc = assuan_write_line (ctx, errline);
+    }
+
+  ctx->confidential = 0;
+  if (ctx->okay_line)
+    {
+      xfree (ctx->okay_line);
+      ctx->okay_line = NULL;
+    }
+  return rc;
+}
 
 /**
  * assuan_process:
@@ -309,42 +523,7 @@ assuan_process (ASSUAN_CONTEXT ctx)
   int rc;
 
   do {
-    /* Read the line but skip comments */
-    do
-      {
-        rc = _assuan_read_line (ctx);
-        if (rc)
-          return rc;
-      
-        fprintf (stderr, "DBG-assuan: got %d bytes `%s'\n",
-                 ctx->inbound.linelen, ctx->inbound.line);
-      }
-    while ( *ctx->inbound.line == '#' || !ctx->inbound.linelen);
-  
-    /* dispatch comamnd and return reply */
-    rc = dispatch_command (ctx, ctx->inbound.line, ctx->inbound.linelen);
-    if (!rc)
-      rc = _assuan_write_line (ctx, "OK");
-    else if (rc == -1)
-      { /* No error checking because the peer may have already disconnect */ 
-        _assuan_write_line (ctx, "OK  Bye, bye - hope to meet you again");
-      }
-    else 
-      {
-        char errline[256];
-
-        if (rc < 100)
-          sprintf (errline, "ERR %d server fault (%.50s)",
-                   ASSUAN_Server_Fault, assuan_strerror (rc));
-        else
-          {
-            const char *text = ctx->err_no == rc? ctx->err_str:NULL;
-
-            sprintf (errline, "ERR %d %.50s%s%.100s",
-                     rc, assuan_strerror (rc), text? " - ":"", text?text:"");
-          }
-        rc = _assuan_write_line (ctx, errline);
-      }
+    rc = process_request (ctx);
   } while (!rc);
 
   if (rc == -1)
@@ -354,6 +533,122 @@ assuan_process (ASSUAN_CONTEXT ctx)
 }
 
 
+/**
+ * assuan_process_next:
+ * @ctx: Assuan context
+ * 
+ * Same as assuan_process() but the user has to provide the outer
+ * loop.  He should loop as long as the return code is zero and stop
+ * otherwise; -1 is regular end.
+ * 
+ * See also: assuan_get_active_fds()
+ * Return value: -1 for end of server, 0 on success or an error code
+ **/
+int 
+assuan_process_next (ASSUAN_CONTEXT ctx)
+{
+  return process_request (ctx);
+}
+
+
+/**
+ * assuan_get_active_fds:
+ * @ctx: Assuan context
+ * @what: 0 for read fds, 1 for write fds
+ * @fdarray: Caller supplied array to store the FDs
+ * @fdarraysize: size of that array
+ * 
+ * Return all active filedescriptors for the given context.  This
+ * function can be used to select on the fds and call
+ * assuan_process_next() if there is an active one.  The first fd in
+ * the array is the one used for the command connection.
+ *
+ * Note, that write FDs are not yet supported.
+ * 
+ * Return value: number of FDs active and put into @fdarray or -1 on
+ * error which is most likely a too small fdarray.
+ **/
+int 
+assuan_get_active_fds (ASSUAN_CONTEXT ctx, int what,
+                       int *fdarray, int fdarraysize)
+{
+  int n = 0;
+
+  if (!ctx || fdarraysize < 2 || what < 0 || what > 1)
+    return -1;
+
+  if (!what)
+    {
+      if (ctx->inbound.fd != -1)
+        fdarray[n++] = ctx->inbound.fd;
+    }
+  else
+    {
+      if (ctx->outbound.fd != -1)
+        fdarray[n++] = ctx->outbound.fd;
+      if (ctx->outbound.data.fp)
+        fdarray[n++] = fileno (ctx->outbound.data.fp);
+    }
+
+  return n;
+}
+
+/* Return a FP to be used for data output.  The FILE pointer is valid
+   until the end of a handler.  So a close is not needed.  Assuan does
+   all the buffering needed to insert the status line as well as the
+   required line wappping and quoting for data lines.
+
+   We use GNU's custom streams here.  There should be an alternative
+   implementaion for systems w/o a glibc, a simple implementation
+   could use a child process */
+FILE *
+assuan_get_data_fp (ASSUAN_CONTEXT ctx)
+{
+  cookie_io_functions_t cookie_fnc;
+
+  if (ctx->outbound.data.fp)
+    return ctx->outbound.data.fp;
+  
+  cookie_fnc.read = NULL; 
+  cookie_fnc.write = _assuan_cookie_write_data;
+  cookie_fnc.seek = NULL;
+  cookie_fnc.close = _assuan_cookie_write_flush;
+
+  ctx->outbound.data.fp = fopencookie (ctx, "wb", cookie_fnc);
+  ctx->outbound.data.error = 0;
+  return ctx->outbound.data.fp;
+}
+
+
+/* Set the text used for the next OK reponse.  This string is
+   automatically reset to NULL after the next command. */
+AssuanError
+assuan_set_okay_line (ASSUAN_CONTEXT ctx, const char *line)
+{
+  if (!ctx)
+    return ASSUAN_Invalid_Value;
+  if (!line)
+    {
+      xfree (ctx->okay_line);
+      ctx->okay_line = NULL;
+    }
+  else
+    {
+      /* FIXME: we need to use gcry_is_secure() to test whether
+         we should allocate the entire line in secure memory */
+      char *buf = xtrymalloc (3+strlen(line)+1);
+      if (!buf)
+        return ASSUAN_Out_Of_Core;
+      strcpy (buf, "OK ");
+      strcpy (buf+3, line);
+      xfree (ctx->okay_line);
+      ctx->okay_line = buf;
+    }
+  return 0;
+}
+
+
+
 void
 assuan_write_status (ASSUAN_CONTEXT ctx, const char *keyword, const char *text)
 {
@@ -376,7 +671,7 @@ assuan_write_status (ASSUAN_CONTEXT ctx, const char *keyword, const char *text)
           strcat (buffer, " ");
           strcat (buffer, text);
         }
-      _assuan_write_line (ctx, buffer);
+      assuan_write_line (ctx, buffer);
     }
   else if ( (helpbuf = xtrymalloc (n)) )
     {
@@ -387,12 +682,7 @@ assuan_write_status (ASSUAN_CONTEXT ctx, const char *keyword, const char *text)
           strcat (helpbuf, " ");
           strcat (helpbuf, text);
         }
-      _assuan_write_line (ctx, helpbuf);
+      assuan_write_line (ctx, helpbuf);
       xfree (helpbuf);
     }
 }
-
-
-
-
-