(Log entry for previous version is bogus.) Added support for multiple
[become] / src / check.c
index 9ed0b6f..a9f8ec4 100644 (file)
@@ -1,13 +1,13 @@
 /* -*-c-*-
  *
- * $Id: check.c,v 1.1 1997/07/21 13:47:53 mdw Exp $
+ * $Id: check.c,v 1.4 1997/08/07 09:52:05 mdw Exp $
  *
  * Check validity of requests
  *
  * (c) 1997 EBI
  */
 
-/*----- Licencing notice --------------------------------------------------*
+/*----- Licensing notice --------------------------------------------------*
  *
  * This file is part of `become'
  *
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with `become'; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ * along with `become'; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  */
 
 /*----- Revision history --------------------------------------------------*
  *
  * $Log: check.c,v $
- * Revision 1.1  1997/07/21 13:47:53  mdw
+ * Revision 1.4  1997/08/07 09:52:05  mdw
+ * (Log entry for previous version is bogus.)  Added support for multiple
+ * servers.
+ *
+ * Revision 1.2  1997/08/04 10:24:20  mdw
+ * Sources placed under CVS control.
+ *
+ * Revision 1.1  1997/07/21  13:47:53  mdw
  * Initial revision
  *
  */
 #include "idea.h"
 #include "lexer.h"
 #include "name.h"
+#include "netg.h"
 #include "rule.h"
 #include "parser.h"
 #include "tx.h"
+#include "userdb.h"
 #include "utils.h"
 
 /*----- Main code ---------------------------------------------------------*/
 
-/* --- @check__client@ --- *
+/* --- @check__send@ --- *
+ *
+ * Arguments:  @unsigned char *crq@ = pointer to encrypted request
+ *             @int fd@ = socket to send from
+ *             @struct sockaddr_in *serv@ = pointer to table of servers
+ *             @size_t n_serv@ = number of servers
+ *
+ * Returns:    ---
+ *
+ * Use:                Sends the request packet to the list of servers.  If the
+ *             message couldn't be sent to any of them, an error is
+ *             reported.
+ */
+
+static void check__send(unsigned char *crq, int fd,
+                       struct sockaddr_in *serv, size_t n_serv)
+{
+  size_t i;
+  int ok = 0;
+  int err = 0;
+
+  for (i = 0; i < n_serv; i++) {
+    if (sendto(fd, (char *)crq, crq_size, 0,
+              (struct sockaddr *)(serv + i), sizeof(serv[i])) < 0) {
+      T( trace(TRACE_CLIENT, "client: send to %s failed: %s",
+              inet_ntoa(serv[i].sin_addr), strerror(errno)); )
+      err = errno;
+    } else
+      ok = 1;
+  }
+
+  if (!ok)
+    die("couldn't send request to server: %s", strerror(err));
+}
+
+/* --- @check__ask@ --- *
  *
  * Arguments:  @request *rq@ = pointer to request buffer
- *             @const char *serv@ = pointer to the server
- *             @int port@ = port number to use
+ *             @struct sockaddr_in *serv@ = pointer to table of servers
+ *             @size_t n_serv@ = number of servers
  *
  * Returns:    Nonzero if OK, zero if forbidden
  *
- * Use:                Contacts a server to decide whether the request is OK.
+ * Use:                Contacts a number of servers to decide whether the request
+ *             is OK.
  */
 
-static int check__client(request *rq, const char *serv, int port)
+static int check__ask(request *rq, struct sockaddr_in *serv, size_t n_serv)
 {
   int fd;
-  struct sockaddr_in ssin;
   unsigned char crq[crq_size];
-  unsigned char k[IDEA_KEYSIZE];
   unsigned char sk[IDEA_KEYSIZE];
   time_t t;
   pid_t pid;
 
-  /* --- Create my socket --- */
+  /* --- First, build the encrypted request packet --- */
 
-  if ((fd = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
-    die("couldn't create socket: %s", strerror(errno));
+  {
+    unsigned char k[IDEA_KEYSIZE];
+    FILE *fp;
 
-  /* --- Bind myself to some address --- */
+    /* --- Read in the encryption key --- */
 
-  {
-    struct sockaddr_in sin;
+    if ((fp = fopen(file_KEY, "r")) == 0) {
+      die("couldn't open key file `%s': %s", file_KEY,
+         strerror(errno));
+    }
+    tx_getBits(k, 128, fp);
 
-    sin.sin_family = AF_INET;
-    sin.sin_port = 0;
-    sin.sin_addr.s_addr = htonl(INADDR_ANY);
+    /* --- Now build a request packet --- */
 
-    if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0)
-      die("couldn't bind socket to address: %s", strerror(errno));
+    t = time(0);
+    pid = getpid();
+    crypt_packRequest(rq, crq, t, pid, k, sk);
+    burn(k);
+    T( trace(TRACE_CLIENT, "client: encrypted request packet"); )
   }
 
-  /* --- Find the server's address --- */
+  /* --- Create my socket --- */
 
   {
-    struct hostent *he;
+    struct sockaddr_in sin;
 
-    /* --- Resolve the server's name --- */
+    if ((fd = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
+      die("couldn't create socket: %s", strerror(errno));
 
-    if ((he = gethostbyname(serv)) == 0)
-      die("couldn't find server host `%s'", serv);
+    /* --- Bind myself to some address --- */
 
-    /* --- Build the address block --- */
+    sin.sin_family = AF_INET;
+    sin.sin_port = 0;
+    sin.sin_addr.s_addr = htonl(INADDR_ANY);
 
-    ssin.sin_family = AF_INET;
-    ssin.sin_port = htons(port);
-    memcpy(&ssin.sin_addr, he->h_addr, sizeof(struct in_addr));
+    if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0)
+      die("couldn't bind socket to address: %s", strerror(errno));
   }
 
-  /* --- Read in the encryption key --- */
+  /* --- Now wait for a reply --- */
 
   {
-    FILE *fp;
-
-    if ((fp = fopen(file_KEY, "r")) == 0) {
-      die("couldn't open key file `%s': %s", file_KEY,
-         strerror(errno));
-    }
-    tx_getBits(k, 128, fp);
-  }
-
-  /* --- Now build a request packet --- */
-
-  t = time(0);
-  pid = getpid();
-  crypt_packRequest(rq, crq, t, pid, k, sk);
+    fd_set fds;
+    struct timeval start, now, tv;
+    int ind;
+    size_t i;
 
-  /* --- Send the packet to the server --- */
+    /* --- State table for waiting for replies --- *
+     *
+     * For each number, send off the request to our servers, and wait for
+     * that many seconds to have elapsed since we started.  If the number is
+     * %$-1$% then it's time to give up.
+     */
 
-  if (sendto(fd, (char *)crq, sizeof(crq), 0,
-            (struct sockaddr *)&ssin, sizeof(ssin)) < 0) {
-    burn(k);
-    die("couldn't send request to server: %s", strerror(errno));
-  }
-  burn(k);
+    static int tbl[] = { 0, 5, 10, 20, -1 };
 
-  /* --- Now wait for a reply --- */
+    /* --- Find out when we are --- */
 
-  {
-    fd_set fds;
-    struct timeval when, now, tv;
+    gettimeofday(&start, 0);
+    ind = 0;
 
-    gettimeofday(&when, 0);
-    when.tv_sec += 10;
+    /* --- Now loop until everything's done --- */
 
     for (;;) {
-      int i;
+      gettimeofday(&now, 0);
+
+      /* --- If the current timer has expired, find one that hasn't --- *
+       *
+       * Also resend the request after I've found a timer which is still
+       * extant.  If there aren't any, report an error.
+       */
+
+      if (now.tv_sec >= start.tv_sec + tbl[ind] &&
+         now.tv_usec >= start.tv_usec) {
+       do {
+         ind++;
+         if (tbl[ind] < 0)
+           die("no reply from servers");
+       } while (now.tv_sec >= start.tv_sec + tbl[ind] &&
+                now.tv_usec >= start.tv_usec);
+       check__send(crq, fd, serv, n_serv);
+       T( trace(TRACE_CLIENT, "client: send request to servers"); )
+      }
 
-      /* --- Sort out when to return --- */
+      /* --- Now wait for a packet to arrive --- */
 
-      gettimeofday(&now, 0);
-      if (now.tv_usec > when.tv_usec) {
+      if (now.tv_usec > start.tv_usec) {
        now.tv_usec -= 1000000;
        now.tv_sec += 1;
       }
-      tv.tv_sec = when.tv_sec - now.tv_sec;
-      tv.tv_usec = when.tv_usec - now.tv_usec;
+      tv.tv_sec = start.tv_sec + tbl[ind] - now.tv_sec;
+      tv.tv_usec = start.tv_usec - now.tv_usec;
 
       /* --- Sort out file descriptors to watch --- */
 
@@ -186,8 +240,8 @@ static int check__client(request *rq, const char *serv, int port)
       /* --- Wait for them --- */
 
       i = select(FD_SETSIZE, &fds, 0, 0, &tv);
-      if (i == 0)
-       die("no answer from server");
+      if (i == 0 || (i < 0 && errno == EINTR))
+       continue;
       if (i < 0)
        die("error waiting for reply: %s", strerror(errno));
 
@@ -205,26 +259,245 @@ static int check__client(request *rq, const char *serv, int port)
                     (struct sockaddr *)&sin, &slen) < 0)
          die("error reading server's reply: %s", strerror(errno));
 
-       /* --- Verify the sender --- */
-
-       if (sin.sin_addr.s_addr != ssin.sin_addr.s_addr ||
-           sin.sin_port != ssin.sin_port)
+       IF_TRACING(TRACE_CLIENT, {
+         struct hostent *h = gethostbyaddr((char *)&sin.sin_addr,
+                                           sizeof(sin.sin_addr), AF_INET);
+         trace(TRACE_CLIENT, "client: reply received from %s port %i",
+               h ? h->h_name : inet_ntoa(sin.sin_addr),
+               ntohs(sin.sin_port));
+       })
+
+       /* --- Verify the sender --- *
+        *
+        * This is more to avoid confusion than for security: an active
+        * attacker is quite capable of forging the source address.  We rely
+        * on the checksum in the reply packet for authentication.
+        */
+
+       for (i = 0; i < n_serv; i++) {
+         if (sin.sin_addr.s_addr == serv[i].sin_addr.s_addr &&
+             sin.sin_port == serv[i].sin_port)
+           break;
+       }
+       if (i >= n_serv) {
+         T( trace(TRACE_CLIENT, "client: reply from unknown host"); )
          continue;
+       }
     
        /* --- Unpack and verify the response --- */
 
        answer = crypt_unpackReply(buff, sk, t, pid);
-       if (answer < 0)
+       if (answer < 0) {
+         T( trace(TRACE_CLIENT,
+                  "client: invalid or corrupt reply packet"); )
          continue;
+       }
        return (answer);
       }
     }
   }
 
-  die("internal error: can't get here in check_client");
+  die("internal error: can't get here in check__ask");
   return (0);
 }
 
+/* --- @check__client@ --- *
+ *
+ * Arguments:  @request *rq@ = pointer to a request block
+ *             @FILE *fp@ = file containing server configuration
+ *
+ * Returns:    Nonzero if OK, zero if forbidden
+ *
+ * Use:                Asks one or several servers whether a request is acceptable.
+ */
+
+int check__client(request *rq, FILE *fp)
+{
+  /* --- Format of the file --- *
+   *
+   * The `servers' file contains entries of the form
+   *
+   *    %%\syntax{<host> [`:' <port>]}%%
+   *
+   * separates by whitespace.  I build them all into an array of socket
+   * addresses and pass the whole lot to another function.
+   */
+
+  struct sockaddr_in *serv;            /* Array of servers */
+  size_t n_serv, max_serv;             /* Number and maximum number */
+
+  /* --- Initialise the server array --- */
+
+  T( trace(TRACE_CLIENT, "client: reading server definitions"); )
+  n_serv = 0; max_serv = 4;            /* Four seems reasonable */
+  serv = xmalloc(sizeof(*serv) * max_serv);
+
+  /* --- Start reading the file --- */
+
+  {
+    char buff[256], *p, *l;            /* A buffer and pointers for it */
+    int port;                          /* Default port for servers */
+    int state;                         /* Current parser state */
+    struct in_addr t_host;             /* Temp place for an address*/
+    int t_port;                                /* Temp place for a port */
+    int ch;                            /* The current character */
+
+    /* --- Parser states --- */
+
+    enum {
+      st_start,                                /* Waiting to begin */
+      st_host,                         /* Reading a new hostname */
+      st_colon,                                /* Expecting a colon, maybe */
+      st_preport,                      /* Waiting before reading port */
+      st_port,                         /* Reading a port number */
+      st_commit,                       /* Commit a newly read server */
+      st_done                          /* Finished reading the file */
+    };
+
+    /* --- Find a default port --- */
+
+    {
+      struct servent *s = getservbyname(quis(), "udp");
+      port = (s ? s->s_port : -1);
+    }
+
+    /* --- Initialise for scanning the file --- */
+
+    state = st_host;
+    p = buff;
+    l = buff + sizeof(buff);
+    t_port = port;
+    ch = getc(fp);
+
+    /* --- Go for it --- */
+
+    while (state != st_done) {
+      switch (state) {
+
+       /* --- Initial whitespace before hostname --- */
+
+       case st_start:
+         if (ch == EOF)
+           state = st_done;
+         else if (isspace((unsigned char)ch))
+           ch = getc(fp);
+         else
+           state = st_host;
+         break;
+
+       /* --- Read a host name --- */
+
+       case st_host:
+         if (p == l)
+           die("string too long in `" file_SERVER "'");
+         if (ch != EOF && !isspace((unsigned char)ch) && ch != ':') {
+           *p++ = ch;
+           ch = getc(fp);
+         } else {
+           struct hostent *h;
+
+           *p++ = 0;
+           if ((h = gethostbyname(buff)) == 0)
+             die("unknown host `%s' in `" file_SERVER "'", buff);
+           memcpy(&t_host, h->h_addr, sizeof(t_host));
+           state = st_colon;
+         }
+         break;
+
+       /* --- Waiting for a colon coming up --- */
+
+       case st_colon:
+         if (ch == EOF)
+           state = st_commit;
+         else if (isspace((unsigned char)ch))
+           ch = getc(fp);
+         else if (ch == ':') {
+           state = st_preport;
+           ch = getc(fp);
+         }
+         else
+           state = st_commit;
+         break;
+
+       /* --- Clearing whitespace before a port number --- */
+
+       case st_preport:
+         if (ch == EOF)
+           state = st_commit;
+         else if (isspace((unsigned char)ch))
+           ch = getc(fp);
+         else {
+           state = st_port;
+           p = buff;
+         }
+         break;
+
+       /* --- Read a port number --- */
+
+       case st_port:
+         if (p == l)
+           die("string too long in `" file_SERVER "'");
+         if (ch != EOF && !isspace((unsigned char)ch) && ch != ':') {
+           *p++ = ch;
+           ch = getc(fp);
+         } else {
+           struct servent *s;
+
+           *p++ = 0;
+           s = getservbyname(buff, "udp");
+           if (!s && isdigit((unsigned char)buff[0]))
+             t_port = htons(atoi(buff));
+           else if (!s)
+             die("unknown service `%s' in `" file_SERVER "'", buff);
+           else
+             t_port = s->s_port;
+           state = st_commit;
+         }
+         break;
+
+       /* --- A server has been read successfully --- */
+
+       case st_commit:
+         if (n_serv == max_serv) {
+           max_serv *= 2;
+           serv = xrealloc(serv, max_serv * sizeof(*serv));
+         }
+         serv[n_serv].sin_family = AF_INET;
+         serv[n_serv].sin_addr = t_host;
+         serv[n_serv].sin_port = t_port;
+         n_serv++;
+         state = st_start;
+         p = buff;
+         t_port = port;
+         break;
+
+       /* --- A safety net for a broken parser --- */
+
+       default:
+         die("internal error: can't get here in check__client");
+         break;          
+      }
+    }
+  }
+
+  fclose(fp);
+
+  /* --- Now start sending requests --- */
+
+  if (!n_serv)
+    die("no servers specified in `" file_SERVER "'");
+
+  IF_TRACING(TRACE_CLIENT, {
+    size_t i;
+
+    for (i = 0; i < n_serv; i++) {
+      trace(TRACE_CLIENT, "client: server %s port %i",
+           inet_ntoa(serv[i].sin_addr), ntohs(serv[i].sin_port));
+    }
+  })
+  return (check__ask(rq, serv, n_serv));
+}
+
 /* --- @check@ --- *
  *
  * Arguments:  @request *rq@ = pointer to request buffer
@@ -240,41 +513,20 @@ int check(request *rq)
 
   /* --- Check if we need to talk to a server --- */
 
-  if ((fp = fopen(file_SERVER, "r")) != 0) {
-    char buff[64];
-    int port;
-    int ch;
-
-    if (fscanf(fp, " %63[^: \t] ", buff) < 1)
-      die("error in `%s'", file_SERVER);
-    ch = getc(fp);
-    if (ch == ':') {
-      char b[64];
-      struct servent *se;
-
-      fscanf(fp, "%s", b);
-      if ((se = getservbyname(b, 0)) != 0)
-       port = ntohs(se->s_port);
-      else if ((port = atoi(b)) == 0)
-       die("error in `%s'", file_SERVER);
-    } else {
-      struct servent *se;
-
-      if ((se = getservbyname(quis(), "udp")) == 0)
-       die("no idea which port to use");
-      port = ntohs(se->s_port);
-    }
-    fclose(fp);
-    return (check__client(rq, buff, port));
-  }
+  if ((fp = fopen(file_SERVER, "r")) != 0)
+    return (check__client(rq, fp));
 
-  /* --- Read the configuration in and go --- */
+  /* --- Otherwise do this all the old-fashioned way --- */
 
   if ((fp = fopen(file_RULES, "r")) == 0) {
     die("couldn't read configuration file `%s': %s",
        file_RULES, strerror(errno));
   }
 
+  userdb_init();
+  userdb_local();
+  userdb_yp();
+  netg_init();
   name_init();
   rule_init();
   lexer_scan(fp);