Debianization.
[become] / src / check.c
index 7e61433..ed561a7 100644 (file)
@@ -1,10 +1,10 @@
 /* -*-c-*-
  *
- * $Id: check.c,v 1.5 1997/09/26 09:14:58 mdw Exp $
+ * $Id: check.c,v 1.12 2003/11/29 23:39:16 mdw Exp $
  *
  * Check validity of requests
  *
- * (c) 1997 EBI
+ * (c) 1998 EBI
  */
 
 /*----- Licensing notice --------------------------------------------------*
 /*----- Revision history --------------------------------------------------*
  *
  * $Log: check.c,v $
+ * Revision 1.12  2003/11/29 23:39:16  mdw
+ * Debianization.
+ *
+ * Revision 1.11  2003/10/12 00:14:55  mdw
+ * Major overhaul.  Now uses DSA signatures rather than the bogus symmetric
+ * encrypt-and-hope thing.  Integrated with mLib and Catacomb.
+ *
+ * Revision 1.10  1999/05/04 16:17:12  mdw
+ * Change to header file name for parser.  See log for `parse.h' for
+ * details.
+ *
+ * Revision 1.9  1998/06/19  13:48:16  mdw
+ * Set close-on-exec flag for UDP socket.
+ *
+ * Revision 1.8  1998/06/18 15:10:44  mdw
+ * SECURITY HOLE: the file descriptor for the secret key was left open and
+ * inherited by the target process.  This is now fixed.  Also set
+ * close-on-exec flags on key file, close config file carefully, and close
+ * UDP socket after receiving reply from server.
+ *
+ * Revision 1.7  1998/04/23 13:22:08  mdw
+ * Support no-network configuration option, and new interface to
+ * configuration file parser.
+ *
+ * Revision 1.6  1998/01/12 16:45:47  mdw
+ * Fix copyright date.
+ *
  * Revision 1.5  1997/09/26 09:14:58  mdw
  * Merged blowfish branch into trunk.
  *
 
 #include <arpa/inet.h>
 
+#include <fcntl.h>
 #include <netdb.h>
 #include <unistd.h>
 
+/* --- mLib headers --- */
+
+#include <mLib/alloc.h>
+#include <mLib/quis.h>
+#include <mLib/report.h>
+#include <mLib/sym.h>
+#include <mLib/trace.h>
+
+/* --- Catacomb headers --- */
+
+#include <catacomb/buf.h>
+#include <catacomb/dsa.h>
+#include <catacomb/key.h>
+#include <catacomb/mp.h>
+#include <catacomb/noise.h>
+#include <catacomb/rand.h>
+#include <catacomb/sha.h>
+
 /* --- Local headers --- */
 
 #include "become.h"
-#include "blowfish.h"
 #include "config.h"
-#include "crypt.h"
 #include "lexer.h"
 #include "name.h"
 #include "netg.h"
 #include "rule.h"
-#include "parser.h"
-#include "tx.h"
+#include "parse.h"
 #include "userdb.h"
-#include "utils.h"
 
-/*----- Main code ---------------------------------------------------------*/
+/*----- Client-end network support ----------------------------------------*/
+
+#ifndef NONETWORK
 
 /* --- @check__send@ --- *
  *
- * Arguments:  @unsigned char *crq@ = pointer to encrypted request
+ * Arguments:  @char *buf@ = pointer to encrypted request
+ *             @size_t sz@ = size of request
  *             @int fd@ = socket to send from
  *             @struct sockaddr_in *serv@ = pointer to table of servers
  *             @size_t n_serv@ = number of servers
  *             reported.
  */
 
-static void check__send(unsigned char *crq, int fd,
+static void check__send(char *buf, size_t sz, int fd,
                        struct sockaddr_in *serv, size_t n_serv)
 {
   size_t i;
@@ -112,7 +157,7 @@ static void check__send(unsigned char *crq, int fd,
   int err = 0;
 
   for (i = 0; i < n_serv; i++) {
-    if (sendto(fd, (char *)crq, crq_size, 0,
+    if (sendto(fd, buf, sz, 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)); )
@@ -122,7 +167,7 @@ static void check__send(unsigned char *crq, int fd,
   }
 
   if (!ok)
-    die("couldn't send request to server: %s", strerror(err));
+    die(1, "couldn't send request to server: %s", strerror(err));
 }
 
 /* --- @check__ask@ --- *
@@ -139,173 +184,188 @@ static void check__send(unsigned char *crq, int fd,
 
 static int check__ask(request *rq, struct sockaddr_in *serv, size_t n_serv)
 {
+  static int tbl[] = { 0, 5, 10, 20, -1 };
+
+  char buff[2048], rbuff[2048];
+  size_t rqlen;
+  octet hmsg[SHA_HASHSZ];
+  octet h[SHA_HASHSZ];
+  key_packstruct kps[DSA_PUBFETCHSZ];
+  key_packdef *kp;
+  dsa_pub kpub;
+  buf b;
+  sha_ctx hc;
   int fd;
-  unsigned char crq[crq_size];
-  unsigned char sk[BLOWFISH_KEYSIZE];
-  time_t t;
-  pid_t pid;
-
-  /* --- First, build the encrypted request packet --- */
-
-  {
-    unsigned char k[BLOWFISH_KEYSIZE];
-    FILE *fp;
+  struct sockaddr_in sin;
+  socklen_t slen;
+  ssize_t sz;
+  int ans;
+  fd_set fds;
+  struct timeval start, now, tv;
+  mp *m, *r, *s;
+  key_file f;
+  key *k;
+  key_iter ki;
+  int ind;
+  size_t i;
 
-    /* --- Read in the encryption key --- */
+  /* --- Open the public keyring --- */
 
-    if ((fp = fopen(file_KEY, "r")) == 0) {
-      die("couldn't open key file `%s': %s", file_KEY,
-         strerror(errno));
-    }
-    tx_getBits(k, 128, fp);
+  if ((key_open(&f, file_PUBKEY, KOPEN_READ, key_moan, 0)) != 0)
+    die(1, "couldn't open public keyring");
+  kp = key_fetchinit(dsa_pubfetch, kps, &kpub);
 
-    /* --- Now build a request packet --- */
+  /* --- Build the request packet --- */
 
-    t = time(0);
-    pid = getpid();
-    crypt_packRequest(rq, crq, t, pid, k, sk);
-    burn(k);
-    T( trace(TRACE_CLIENT, "client: encrypted request packet"); )
-  }
+  rand_noisesrc(RAND_GLOBAL, &noise_source);
+  rand_seed(RAND_GLOBAL, 160);
+  buf_init(&b, buff, sizeof(buff));
+  rand_get(RAND_GLOBAL, buf_get(&b, SHA_HASHSZ), SHA_HASHSZ);
+  buf_putu32(&b, rq->from);
+  buf_putu32(&b, rq->to);
+  buf_putu16(&b, strlen(rq->cmd));
+  buf_put(&b, rq->cmd, strlen(rq->cmd));
+  rqlen = BLEN(&b);
+  sha_init(&hc);
+  sha_hash(&hc, buff, rqlen);
+  sha_done(&hc, hmsg);
 
   /* --- Create my socket --- */
 
-  {
-    struct sockaddr_in sin;
+  if ((fd = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
+    die(1, "couldn't create socket: %s", strerror(errno));
+  if (fcntl(fd, F_SETFD, 1) < 0)
+    die(1, "couldn't set close-on-exec flag for socket: %s", strerror(errno));
 
-    if ((fd = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
-      die("couldn't create socket: %s", strerror(errno));
+  /* --- Bind myself to some address --- */
 
-    /* --- Bind myself to some address --- */
+  sin.sin_family = AF_INET;
+  sin.sin_port = 0;
+  sin.sin_addr.s_addr = htonl(INADDR_ANY);
 
-    sin.sin_family = AF_INET;
-    sin.sin_port = 0;
-    sin.sin_addr.s_addr = htonl(INADDR_ANY);
+  if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0)
+    die(1, "couldn't bind socket to address: %s", strerror(errno));
 
-    if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0)
-      die("couldn't bind socket to address: %s", strerror(errno));
-  }
+  /* --- Find out when we are --- */
 
-  /* --- Now wait for a reply --- */
+  gettimeofday(&start, 0);
+  ind = 0;
 
-  {
-    fd_set fds;
-    struct timeval start, now, tv;
-    int ind;
-    size_t i;
+  /* --- Now loop until everything's done --- */
+
+  for (;;) {
+    gettimeofday(&now, 0);
 
-    /* --- State table for waiting for replies --- *
+    /* --- If the current timer has expired, find one that hasn't --- *
      *
-     * 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.
+     * Also resend the request after I've found a timer which is still
+     * extant.  If there aren't any, report an error.
      */
 
-    static int tbl[] = { 0, 5, 10, 20, -1 };
+    if (now.tv_sec >= start.tv_sec + tbl[ind] &&
+       now.tv_usec >= start.tv_usec) {
+      do {
+       ind++;
+       if (tbl[ind] < 0)
+         die(1, "no reply from servers");
+      } while (now.tv_sec >= start.tv_sec + tbl[ind] &&
+              now.tv_usec >= start.tv_usec);
+      check__send(buff, rqlen, fd, serv, n_serv);
+      T( trace(TRACE_CLIENT, "client: send request to servers"); )
+    }
 
-    /* --- Find out when we are --- */
+    /* --- Now wait for a packet to arrive --- */
 
-    gettimeofday(&start, 0);
-    ind = 0;
+    if (now.tv_usec > start.tv_usec) {
+      now.tv_usec -= 1000000;
+      now.tv_sec += 1;
+    }
+    tv.tv_sec = start.tv_sec + tbl[ind] - now.tv_sec;
+    tv.tv_usec = start.tv_usec - now.tv_usec;
 
-    /* --- Now loop until everything's done --- */
+    /* --- Sort out file descriptors to watch --- */
 
-    for (;;) {
-      gettimeofday(&now, 0);
+    FD_ZERO(&fds);
+    FD_SET(fd, &fds);
 
-      /* --- 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.
-       */
+    /* --- Wait for them --- */
 
-      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"); )
-      }
+    i = select(FD_SETSIZE, &fds, 0, 0, &tv);
+    if (i == 0 || (i < 0 && errno == EINTR))
+      continue;
+    if (i < 0)
+      die(1, "error waiting for reply: %s", strerror(errno));
 
-      /* --- Now wait for a packet to arrive --- */
+    /* --- Read the reply data --- */
 
-      if (now.tv_usec > start.tv_usec) {
-       now.tv_usec -= 1000000;
-       now.tv_sec += 1;
-      }
-      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 --- */
-
-      FD_ZERO(&fds);
-      FD_SET(fd, &fds);
-
-      /* --- Wait for them --- */
-
-      i = select(FD_SETSIZE, &fds, 0, 0, &tv);
-      if (i == 0 || (i < 0 && errno == EINTR))
-       continue;
-      if (i < 0)
-       die("error waiting for reply: %s", strerror(errno));
-
-      /* --- A reply should be waiting now --- */
-
-      {
-       struct sockaddr_in sin;
-       int slen = sizeof(sin);
-       unsigned char buff[256];
-       int answer;
-
-       /* --- Read the reply data --- */
-
-       if (recvfrom(fd, (char *)buff, sizeof(buff), 0,
-                    (struct sockaddr *)&sin, &slen) < 0)
-         die("error reading server's reply: %s", strerror(errno));
-
-       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) {
-         T( trace(TRACE_CLIENT,
-                  "client: invalid or corrupt reply packet"); )
-         continue;
-       }
-       return (answer);
+    slen = sizeof(sin);
+    if ((sz = recvfrom(fd, (char *)rbuff, sizeof(rbuff), 0,
+                      (struct sockaddr *)&sin, &slen)) < 0)
+      die(1, "error reading server's reply: %s", strerror(errno));
+
+    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 signature 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 --- */
+
+    buf_init(&b, rbuff, sz);
+    if (buf_ensure(&b, sizeof(hmsg))) goto bad;
+    if (memcmp(BCUR(&b), hmsg, sizeof(hmsg)) != 0) goto bad;
+    BSTEP(&b, sizeof(hmsg));
+    if ((ans = buf_getbyte(&b)) < 0) goto bad;
+
+    sha_init(&hc);
+    sha_hash(&hc, BBASE(&b), BLEN(&b));
+    sha_done(&hc, h);
+    if ((r = buf_getmp(&b)) == 0 || (s = buf_getmp(&b)) == 0) goto bad;
+    m = mp_loadb(MP_NEW, h, sizeof(h));
+
+    key_mkiter(&ki, &f);
+    while ((k = key_next(&ki)) != 0) {
+      if (key_expired(k)) continue;
+      if (strcmp(k->type, "become-dsa") != 0) continue;
+      if (key_fetch(kp, k)) continue;
+      i = dsa_vrfy(&kpub.dp, kpub.y, m, r, s);
+      dsa_pubfree(&kpub);
+      if (i) {
+       key_fetchdone(kp);
+       key_close(&f);
+       close(fd);
+       mp_drop(m);
+       mp_drop(r);
+       mp_drop(s);
+       return (ans);
       }
     }
+    
+  bad:
+    T( trace(TRACE_CLIENT,
+            "client: invalid or corrupt reply packet"); )
   }
 
-  die("internal error: can't get here in check__ask");
+  die(1, "internal error: can't get here in check__ask");
   return (0);
 }
 
@@ -366,7 +426,7 @@ int check__client(request *rq, FILE *fp)
 
     {
       struct servent *s = getservbyname(quis(), "udp");
-      port = (s ? s->s_port : -1);
+      port = (s ? s->s_port : htons(SERVER_PORT));
     }
 
     /* --- Initialise for scanning the file --- */
@@ -397,7 +457,7 @@ int check__client(request *rq, FILE *fp)
 
        case st_host:
          if (p == l)
-           die("string too long in `" file_SERVER "'");
+           die(1, "string too long in `" file_SERVER "'");
          if (ch != EOF && !isspace((unsigned char)ch) && ch != ':') {
            *p++ = ch;
            ch = getc(fp);
@@ -406,7 +466,7 @@ int check__client(request *rq, FILE *fp)
 
            *p++ = 0;
            if ((h = gethostbyname(buff)) == 0)
-             die("unknown host `%s' in `" file_SERVER "'", buff);
+             die(1, "unknown host `%s' in `" file_SERVER "'", buff);
            memcpy(&t_host, h->h_addr, sizeof(t_host));
            state = st_colon;
          }
@@ -444,7 +504,7 @@ int check__client(request *rq, FILE *fp)
 
        case st_port:
          if (p == l)
-           die("string too long in `" file_SERVER "'");
+           die(1, "string too long in `" file_SERVER "'");
          if (ch != EOF && !isspace((unsigned char)ch) && ch != ':') {
            *p++ = ch;
            ch = getc(fp);
@@ -456,7 +516,7 @@ int check__client(request *rq, FILE *fp)
            if (!s && isdigit((unsigned char)buff[0]))
              t_port = htons(atoi(buff));
            else if (!s)
-             die("unknown service `%s' in `" file_SERVER "'", buff);
+             die(1, "unknown service `%s' in `" file_SERVER "'", buff);
            else
              t_port = s->s_port;
            state = st_commit;
@@ -468,7 +528,8 @@ int check__client(request *rq, FILE *fp)
        case st_commit:
          if (n_serv == max_serv) {
            max_serv *= 2;
-           serv = xrealloc(serv, max_serv * sizeof(*serv));
+           serv = xrealloc(serv, n_serv * sizeof(*serv),
+                           max_serv * sizeof(*serv));
          }
          serv[n_serv].sin_family = AF_INET;
          serv[n_serv].sin_addr = t_host;
@@ -482,7 +543,7 @@ int check__client(request *rq, FILE *fp)
        /* --- A safety net for a broken parser --- */
 
        default:
-         die("internal error: can't get here in check__client");
+         die(1, "internal error: can't get here in check__client");
          break;          
       }
     }
@@ -493,7 +554,7 @@ int check__client(request *rq, FILE *fp)
   /* --- Now start sending requests --- */
 
   if (!n_serv)
-    die("no servers specified in `" file_SERVER "'");
+    die(1, "no servers specified in `" file_SERVER "'");
 
   IF_TRACING(TRACE_CLIENT, {
     size_t i;
@@ -506,6 +567,10 @@ int check__client(request *rq, FILE *fp)
   return (check__ask(rq, serv, n_serv));
 }
 
+#endif
+
+/*----- Main checking function --------------------------------------------*/
+
 /* --- @check@ --- *
  *
  * Arguments:  @request *rq@ = pointer to request buffer
@@ -521,13 +586,15 @@ int check(request *rq)
 
   /* --- Check if we need to talk to a server --- */
 
+#ifndef NONETWORK
   if ((fp = fopen(file_SERVER, "r")) != 0)
     return (check__client(rq, fp));
+#endif
 
   /* --- Otherwise do this all the old-fashioned way --- */
 
   if ((fp = fopen(file_RULES, "r")) == 0) {
-    die("couldn't read configuration file `%s': %s",
+    die(1, "couldn't read configuration file `%s': %s",
        file_RULES, strerror(errno));
   }
 
@@ -538,7 +605,8 @@ int check(request *rq)
   name_init();
   rule_init();
   lexer_scan(fp);
-  yyparse();
+  parse();
+  fclose(fp);
 
   return (rule_check(rq));
 }