Assuan server mode is now basically usable
authorWerner Koch <wk@gnupg.org>
Wed, 7 Nov 2001 17:43:05 +0000 (17:43 +0000)
committerWerner Koch <wk@gnupg.org>
Wed, 7 Nov 2001 17:43:05 +0000 (17:43 +0000)
assuan/Makefile.am
assuan/assuan-buffer.c
assuan/assuan-connect.c [new file with mode: 0644]
assuan/assuan-defs.h
assuan/assuan-handler.c
assuan/assuan-listen.c [new file with mode: 0644]
assuan/assuan-pipe-server.c
assuan/assuan-util.c
assuan/assuan.h
assuan/mkerrors

index ad415af..b7a1a85 100644 (file)
@@ -33,6 +33,8 @@ libassuan_a_SOURCES = \
        assuan-errors.c \
        assuan-buffer.c \
        assuan-handler.c \
+       assuan-listen.c \
+       assuan-connect.c \
        assuan-pipe-server.c 
 
 
index c767a0e..73786b6 100644 (file)
 #include <config.h>
 #include <stdlib.h>
 #include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <assert.h>
 
 #include "assuan-defs.h"
 
 
+static int
+writen ( int fd, const char *buffer, size_t length )
+{
+  while (length)
+    {
+      int nwritten = write (fd, buffer, length);
+      
+      if (nwritten < 0)
+        {
+          if (errno == EINTR)
+            continue;
+          return -1; /* write error */
+        }
+      length -= nwritten;
+      buffer += nwritten;
+    }
+  return 0;  /* okay */
+}
+
+/* read an entire line */
+static int
+readline (int fd, char *buf, size_t buflen, int *r_nread, int *eof)
+{
+  size_t nleft = buflen;
+  char *p;
+
+  *eof = 0;
+  *r_nread = 0;
+  while (nleft > 0)
+    {
+      int n = read (fd, buf, nleft);
+      if (n < 0)
+        {
+          if (errno == EINTR)
+            continue;
+          return -1; /* read error */
+        }
+      else if (!n)
+        {
+          *eof = 1;
+          break; /* allow incomplete lines */
+        }
+      p = buf;
+      nleft -= n;
+      buf += n;
+      *r_nread += n;
+      
+      for (; n && *p != '\n'; n--, p++)
+        ;
+      if (n)
+        break;
+    }
+
+  return 0;
+}
+
+
 int
 _assuan_read_line (ASSUAN_CONTEXT ctx)
 {
+  char *line = ctx->inbound.line;
+  int n, nread;
+  int rc;
   
+  if (ctx->inbound.eof)
+    return -1;
+
+  rc = readline (ctx->inbound.fd, line, LINELENGTH, &nread, &ctx->inbound.eof);
+  if (rc)
+    return ASSUAN_Read_Error;
+  if (!nread)
+    {
+      assert (ctx->inbound.eof);
+      return -1; 
+    }
 
-  return -1;
+  for (n=nread-1; n>=0 ; n--)
+    {
+      if (line[n] == '\n')
+        {
+          if (n != nread-1)
+            {
+              fprintf (stderr, "DBG-assuan: %d bytes left over after read\n",
+                       nread-1 - n);
+              /* fixme: store them for the next read */
+            }
+          if (n && line[n-1] == '\r')
+            n--;
+          line[n] = 0;
+          ctx->inbound.linelen = n;
+          return 0;
+        }
+    }
+
+  *line = 0;
+  ctx->inbound.linelen = 0;
+  return ctx->inbound.eof? ASSUAN_Line_Not_Terminated : ASSUAN_Line_Too_Long;
 }
 
 
 
 
 int 
-_assuan_write_line (ASSUAN_CONTEXT ctx)
+_assuan_write_line (ASSUAN_CONTEXT ctx, const char *line )
 {
-  return -1;
+  int rc;
+
+  /* fixme: we should do some kind of line buffering */
+  rc = writen (ctx->outbound.fd, line, strlen(line));
+  if (rc)
+    rc = ASSUAN_Write_Error;
+  if (!rc)
+    {
+      rc = writen (ctx->outbound.fd, "\n", 1);
+      if (rc)
+        rc = ASSUAN_Write_Error;
+    }
+
+  return rc;
 }
 
diff --git a/assuan/assuan-connect.c b/assuan/assuan-connect.c
new file mode 100644 (file)
index 0000000..d1c87fc
--- /dev/null
@@ -0,0 +1,27 @@
+/* assuan-connect.c - Establish a connection (client) 
+ *     Copyright (C) 2001 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "assuan-defs.h"
+
index 3402a91..d3e0439 100644 (file)
 
 #include "assuan.h"
 
+#define LINELENGTH 1002 /* 1000 + [CR,]LF */
+
+struct cmdtbl_s {
+  const char *name;
+  int cmd_id;
+  int (*handler)(ASSUAN_CONTEXT, char *line);
+};
 
 struct assuan_context_s {
   AssuanError err_no;
@@ -30,12 +37,25 @@ struct assuan_context_s {
 
   struct {
     int fd;
+    int eof;
+    char line[LINELENGTH];
+    int linelen;  /* w/o CR, LF - might not be the same as
+                     strlen(line) due to embedded nuls. However a nul
+                     is always written at this pos */
   } inbound;
 
   struct {
     int fd;
   } outbound;
 
+  int pipe_mode;  /* We are in pipe mode, i.e. we can handle just one
+                     connection and must terminate then */
+
+  struct cmdtbl_s *cmdtbl;
+  size_t cmdtbl_used; /* used entries */
+  size_t cmdtbl_size; /* allocated size of table */
+
+
   int input_fd;   /* set by INPUT command */
   int output_fd;  /* set by OUTPUT command */
 
@@ -47,6 +67,11 @@ struct assuan_context_s {
 /*-- assuan-handler.c --*/
 int _assuan_register_std_commands (ASSUAN_CONTEXT ctx);
 
+/*-- assuan-buffer.c --*/
+int _assuan_write_line (ASSUAN_CONTEXT ctx, const char *line);
+int _assuan_read_line (ASSUAN_CONTEXT ctx);
+
+
 
 /*-- assuan-util.c --*/
 void *_assuan_malloc (size_t n);
@@ -59,8 +84,7 @@ void  _assuan_free (void *p);
 #define xtryrealloc(a,b) _assuan_realloc((a),(b))
 #define xfree(a)         _assuan_free ((a))
 
-int _assuan_set_error (ASSUAN_CONTEXT ctx, int err, const char *text);
-#define set_error(c,e,t) _assuan_set_error ((c), ASSUAN_ ## e, (t))
+#define set_error(c,e,t) assuan_set_error ((c), ASSUAN_ ## e, (t))
 
 
 #endif /*ASSUAN_DEFS_H*/
index aeeb336..cebb786 100644 (file)
@@ -31,7 +31,6 @@
 static int
 dummy_handler (ASSUAN_CONTEXT ctx, char *line)
 {
-  fprintf (stderr, "DBG-assuan: dummy handler called\n");
   return set_error (ctx, Server_Fault, "no handler registered");
 }
 
@@ -39,42 +38,36 @@ dummy_handler (ASSUAN_CONTEXT ctx, char *line)
 static int
 std_handler_nop (ASSUAN_CONTEXT ctx, char *line)
 {
-  fprintf (stderr, "DBG-assuan: processing a NOP `%s'\n", line);
   return 0; /* okay */
 }
   
 static int
 std_handler_cancel (ASSUAN_CONTEXT ctx, char *line)
 {
-  fprintf (stderr, "DBG-assuan: processing a CANCEL `%s'\n", line);
   return set_error (ctx, Not_Implemented, NULL); 
 }
   
 static int
 std_handler_bye (ASSUAN_CONTEXT ctx, char *line)
 {
-  fprintf (stderr, "DBG-assuan: processing a BYE `%s'\n", line);
-  return set_error (ctx, Not_Implemented, NULL); 
+  return -1; /* pretty simple :-) */
 }
   
 static int
 std_handler_auth (ASSUAN_CONTEXT ctx, char *line)
 {
-  fprintf (stderr, "DBG-assuan: processing a AUTH `%s'\n", line);
   return set_error (ctx, Not_Implemented, NULL); 
 }
   
 static int
 std_handler_reset (ASSUAN_CONTEXT ctx, char *line)
 {
-  fprintf (stderr, "DBG-assuan: processing a RESET `%s'\n", line);
   return set_error (ctx, Not_Implemented, NULL); 
 }
   
 static int
 std_handler_end (ASSUAN_CONTEXT ctx, char *line)
 {
-  fprintf (stderr, "DBG-assuan: processing a END `%s'\n", line);
   return set_error (ctx, Not_Implemented, NULL); 
 }
 
@@ -104,8 +97,6 @@ std_handler_input (ASSUAN_CONTEXT ctx, char *line)
 {
   int rc, fd;
 
-  fprintf (stderr, "DBG-assuan: processing a INPUT `%s'\n", line);
-
   rc = parse_cmd_input_output (ctx, line, &fd);
   if (rc)
     return rc;
@@ -152,20 +143,6 @@ static struct {
 };
 
 
-
-static const char *
-std_cmd_name (int cmd_id)
-{
-  int i;
-
-  for (i=0; std_cmd_table[i].name; i++)
-    if (std_cmd_table[i].cmd_id == cmd_id)
-      return std_cmd_table[i].name;
-  return NULL;
-}
-
-
-
 /**
  * assuan_register_command:
  * @ctx: the server context
@@ -175,8 +152,8 @@ std_cmd_name (int cmd_id)
  * 
  * Register a handler to be used for a given command.
  * 
- * The @cmd_name must be %NULL for all @cmd_ids below
- * %ASSUAN_CMD_USER becuase predefined values are used.
+ * The @cmd_name must be %NULL or an empty string for all @cmd_ids
+ * below %ASSUAN_CMD_USER because predefined values are used.
  * 
  * Return value: 
  **/
@@ -185,17 +162,63 @@ assuan_register_command (ASSUAN_CONTEXT ctx,
                          int cmd_id, const char *cmd_name,
                          int (*handler)(ASSUAN_CONTEXT, char *))
 {
-  if (cmd_name && cmd_id < ASSUAN_CMD_USER)
-    return ASSUAN_Invalid_Value; 
+  int i;
+
+  if (cmd_name && !*cmd_name)
+    cmd_name = NULL;
+
+  if (cmd_id < ASSUAN_CMD_USER)
+    { 
+      if (cmd_name)
+        return ASSUAN_Invalid_Value; /* must be NULL for these values*/
+
+      for (i=0; std_cmd_table[i].name; i++)
+        {
+          if (std_cmd_table[i].cmd_id == cmd_id)
+            {
+              cmd_name = std_cmd_table[i].name;
+              if (!handler)
+                handler = std_cmd_table[i].handler;
+              break;
+            }
+        }
+      if (!std_cmd_table[i].name)
+        return ASSUAN_Invalid_Value; /* not a pre-registered one */
+    }
   
-  if (!cmd_name)
-    cmd_name = std_cmd_name (cmd_id);
+  if (!handler)
+    handler = dummy_handler;
 
   if (!cmd_name)
-    return ASSUAN_Invalid_Value; 
-  
+    return ASSUAN_Invalid_Value;
+
   fprintf (stderr, "DBG-assuan: registering %d as `%s'\n", cmd_id, cmd_name);
 
+  if (!ctx->cmdtbl)
+    {
+      ctx->cmdtbl_size = 10;
+      ctx->cmdtbl = xtrycalloc ( ctx->cmdtbl_size, sizeof *ctx->cmdtbl);
+      if (!ctx->cmdtbl)
+        return ASSUAN_Out_Of_Core;
+      ctx->cmdtbl_used = 0;
+    }
+  else if (ctx->cmdtbl_used >= ctx->cmdtbl_size)
+    {
+      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[ctx->cmdtbl_used].name = cmd_name;
+  ctx->cmdtbl[ctx->cmdtbl_used].cmd_id = cmd_id;
+  ctx->cmdtbl[ctx->cmdtbl_used].handler = handler;
+  ctx->cmdtbl_used++;
   return 0;
 }
 
@@ -209,8 +232,8 @@ _assuan_register_std_commands (ASSUAN_CONTEXT ctx)
     {
       if (std_cmd_table[i].always)
         {
-          rc = assuan_register_command (ctx, std_cmd_table[i].cmd_id, NULL,
-                                        std_cmd_table[i].handler);
+          rc = assuan_register_command (ctx, std_cmd_table[i].cmd_id,
+                                        NULL, NULL);
           if (rc)
             return rc;
         }
@@ -223,7 +246,7 @@ _assuan_register_std_commands (ASSUAN_CONTEXT ctx)
 /* Process the special data lines.  The "D " has already been removed
    from the line.  As all handlers this function may modify the line.  */
 static int
-handle_data_line (ASSUAN_CONTEXT ctx, char *line)
+handle_data_line (ASSUAN_CONTEXT ctx, char *line, int linelen)
 {
   return set_error (ctx, Not_Implemented, NULL);
 }
@@ -233,20 +256,102 @@ handle_data_line (ASSUAN_CONTEXT ctx, char *line)
    table, remove leading and white spaces from the arguments, all the
    handler with the argument line and return the error */
 static int 
-dispatch_command (ASSUAN_CONTEXT ctx, char *line)
+dispatch_command (ASSUAN_CONTEXT ctx, char *line, int linelen)
 {
-  if (*line == 'D' && line[1] == ' ') /* divert to special handler */
-    return handle_data_line (ctx, line+2);
+  char *p;
+  const char *s;
+  int shift, i;
 
-
-  return set_error (ctx, Not_Implemented, NULL);
+  if (*line == 'D' && line[1] == ' ') /* divert to special handler */
+    return handle_data_line (ctx, line+2, linelen-2);
+
+  for (p=line; *p && *p != ' ' && *p != '\t'; p++)
+    ;
+  if (p==line)
+    return set_error (ctx, Invalid_Command, "leading white-space"); 
+  if (*p) 
+    { /* Skip over leading WS after the keyword */
+      *p++ = 0;
+      while ( *p == ' ' || *p == '\t')
+        p++;
+    }
+  shift = p - line;
+
+  for (i=0; (s=ctx->cmdtbl[i].name); i++)
+    if (!strcmp (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);
+  return ctx->cmdtbl[i].handler (ctx, line);
 }
 
 
 
 
-
-
+/**
+ * assuan_process:
+ * @ctx: assuan context
+ * 
+ * This fucntion is used to handle the assuan protocol after a
+ * connection has been established using assuan_accept().  This is the
+ * main protocol handler.
+ * 
+ * Return value: 0 on success or an error code if the assuan operation
+ * failed.  Note, that no error is returned for operational errors.
+ **/
+int
+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);
+      }
+  } while (!rc);
+
+  if (rc == -1)
+    rc = 0;
+
+  return rc;
+}
 
 
 
diff --git a/assuan/assuan-listen.c b/assuan/assuan-listen.c
new file mode 100644 (file)
index 0000000..f8ccb27
--- /dev/null
@@ -0,0 +1,86 @@
+/* assuan-listen.c - Wait for a connection (server) 
+ *     Copyright (C) 2001 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "assuan-defs.h"
+
+
+
+/**
+ * assuan_accept:
+ * @ctx: context
+ * 
+ * Cancel any existing connectiion and wait for a connection from a
+ * client.  The initial handshake is performed which may include an
+ * initial authentication or encryption negotiation.
+ * 
+ * Return value: 0 on success or an error if the connection could for
+ * some reason not be established.
+ **/
+int
+assuan_accept (ASSUAN_CONTEXT ctx)
+{
+  int rc;
+
+  if (!ctx)
+    return ASSUAN_Invalid_Value;
+
+  /* fixme: cancel existing connection */
+  if (ctx->pipe_mode > 1)
+    return -1; /* second invocation for pipemode -> terminate */
+
+  if (!ctx->pipe_mode)
+    {
+
+      /* fixme: wait for request */
+    }
+
+  /* send the hello */
+  
+  rc = _assuan_write_line (ctx,
+                           "OK Hello dear client - what can I do for you?");
+  if (rc)
+    return rc;
+  
+  if (ctx->pipe_mode)
+    ctx->pipe_mode = 2;
+  
+  return 0;
+}
+
+
+int
+assuan_get_input_fd (ASSUAN_CONTEXT ctx)
+{
+  return ctx? ctx->input_fd : -1;
+}
+
+
+int
+assuan_get_output_fd (ASSUAN_CONTEXT ctx)
+{
+  return ctx? ctx->output_fd : -1;
+}
+
+
index 3dd0ab0..018d05d 100644 (file)
@@ -39,7 +39,9 @@ assuan_init_pipe_server (ASSUAN_CONTEXT *r_ctx, int filedes[2])
   ctx->output_fd = -1;
 
   ctx->inbound.fd = filedes[0];
-  ctx->outbound.fd = filedes[0];
+  ctx->outbound.fd = filedes[1];
+
+  ctx->pipe_mode = 1;
 
   rc = _assuan_register_std_commands (ctx);
   if (rc)
index 849cd3f..3a9496e 100644 (file)
@@ -21,6 +21,7 @@
 #include <config.h>
 #include <stdlib.h>
 #include <stdio.h>
+#include <string.h>
 
 #include "assuan-defs.h"
 
@@ -72,11 +73,10 @@ _assuan_free (void *p)
 
 \f
 /* Store the error in the context so that the error sending function
-  can take out a descriptive text.  We wight also want to store a
-  standard text when TEXT is NULL.  Use the macro set_error instead of
-  this function.  */
+  can take out a descriptive text.  Inside the assuan code, use the
+  macro set_error instead of this function. */
 int
-_assuan_set_error (ASSUAN_CONTEXT ctx, int err, const char *text)
+assuan_set_error (ASSUAN_CONTEXT ctx, int err, const char *text)
 {
   ctx->err_no = err;
   ctx->err_str = text;
index b70b161..3806d5d 100644 (file)
@@ -33,15 +33,22 @@ typedef enum {
   ASSUAN_General_Error = 1,
   ASSUAN_Out_Of_Core = 2,
   ASSUAN_Invalid_Value = 3,
+  ASSUAN_Timeout = 4,  
+  ASSUAN_Read_Error = 5,
+  ASSUAN_Write_Error = 6,
 
   /* error codes above 99 are meant as status codes */
-  ASSUAN_Unknown_Command = 100,
-  ASSUAN_Not_Implemented = 101,
-  ASSUAN_Server_Fault    = 102,
-  ASSUAN_Syntax_Error    = 103,
-  ASSUAN_Parameter_Error = 104,
-  ASSUAN_Parameter_Conflict = 105,
-
+  ASSUAN_Not_Implemented = 100,
+  ASSUAN_Server_Fault    = 101,
+  ASSUAN_Invalid_Command = 102,
+  ASSUAN_Unknown_Command = 103,
+  ASSUAN_Syntax_Error    = 104,
+  ASSUAN_Parameter_Error = 105,
+  ASSUAN_Parameter_Conflict = 106,
+  ASSUAN_Line_Too_Long = 107,
+  ASSUAN_Line_Not_Terminated = 108,
+  ASSUAN_No_Input = 109,
+  ASSUAN_No_Output = 110,
 
   ASSUAN_Cert_Revoked = 301,
   ASSUAN_No_CRL_For_Cert = 302,
@@ -68,11 +75,17 @@ typedef enum {
 struct assuan_context_s;
 typedef struct assuan_context_s *ASSUAN_CONTEXT;
 
-/*-- assuan-handler --*/
+/*-- assuan-handler.c --*/
 int assuan_register_command (ASSUAN_CONTEXT ctx,
                              int cmd_id, const char *cmd_string,
                              int (*handler)(ASSUAN_CONTEXT, char *));
+int assuan_process (ASSUAN_CONTEXT ctx);
+
 
+/*-- assuan-listen.c --*/
+int assuan_accept (ASSUAN_CONTEXT ctx);
+int assuan_get_input_fd (ASSUAN_CONTEXT ctx);
+int assuan_get_output_fd (ASSUAN_CONTEXT ctx);
 
 
 /*-- assuan-pipe-server.c --*/
@@ -84,6 +97,7 @@ void assuan_deinit_pipe_server (ASSUAN_CONTEXT ctx);
 void assuan_set_malloc_hooks ( void *(*new_alloc_func)(size_t n),
                                void *(*new_realloc_func)(void *p, size_t n),
                                void (*new_free_func)(void*) );
+int assuan_set_error (ASSUAN_CONTEXT ctx, int err, const char *text);
 
 /*-- assuan-errors.c (built) --*/
 const char *assuan_strerror (AssuanError err);
index 0906cda..13eabde 100755 (executable)
@@ -57,7 +57,7 @@ function print_code( s )
 {
 printf "    case %s: s=\"", s ;
 gsub(/_/, " ", s );
-printf "%s\"; break;\n", substr(s,8);
+printf "%s\"; break;\n", tolower(substr(s,8));
 }
 '