chiark / gitweb /
yaid.c: Use `socklen_t' as appropriate.
[yaid] / yaid.c
diff --git a/yaid.c b/yaid.c
index a99604f4a139b14fde94079f66b0d5dd2ff0d77a..d0d917aa2e4b8eab6bfc1f29c36c6bfee554cde0 100644 (file)
--- a/yaid.c
+++ b/yaid.c
@@ -61,6 +61,7 @@ struct client {
   struct listen *l;                    /* Back to the listener (and ops) */
   struct writebuf wb;                  /* Write buffer for our reply */
   struct proxy *px;                    /* Proxy if conn goes via NAT */
+  struct client *next;                 /* Next in a chain of clients */
 };
 
 /* A proxy connection. */
@@ -71,6 +72,7 @@ struct proxy {
   selbuf b;                            /* Accumulate the response line */
   struct writebuf wb;                  /* Write buffer for query */
   char nat[ADDRLEN];                   /* Server address, as text */
+  struct proxy *next;                  /* Next in a chain of proxies */
 };
 
 /*----- Static variables --------------------------------------------------*/
@@ -86,7 +88,9 @@ static fwatch polfw;                  /* Watch policy file for changes */
 
 static unsigned char tokenbuf[4096];   /* Random-ish data for tokens */
 static size_t tokenptr = sizeof(tokenbuf); /* Current read position */
-static int randfd;                     /* File descriptor for random data */
+
+static struct client *dead_clients = 0;        /* List of defunct clients */
+static struct proxy *dead_proxies = 0; /* List of defunct proxies */
 
 static unsigned flags = 0;             /* Various interesting flags */
 #define F_SYSLOG 1u                    /*   Use syslog for logging */
@@ -218,26 +222,21 @@ static void init_writebuf(struct writebuf *wb,
 
 /*----- General utilities -------------------------------------------------*/
 
-/* Format and log MSG somewhere sensible, at the syslog(3) priority PRIO.
- * Prefix it with a description of the query Q, if non-null.
- */
-void logmsg(const struct query *q, int prio, const char *msg, ...)
+static void vlogmsg(const struct query *q, int prio,
+                   const char *msg, va_list *ap)
 {
-  va_list ap;
   dstr d = DSTR_INIT;
   time_t t;
   struct tm *tm;
   char buf[64];
 
-  va_start(ap, msg);
   if (q) {
     dputsock(&d, q->ao, &q->s[L]);
     dstr_puts(&d, " <-> ");
     dputsock(&d, q->ao, &q->s[R]);
     dstr_puts(&d, ": ");
   }
-  dstr_vputf(&d, msg, &ap);
-  va_end(ap);
+  dstr_vputf(&d, msg, ap);
 
   if (!(flags & F_RUNNING))
     moan("%s", d.buf);
@@ -253,6 +252,29 @@ void logmsg(const struct query *q, int prio, const char *msg, ...)
   dstr_destroy(&d);
 }
 
+/* Format and log MSG somewhere sensible, at the syslog(3) priority PRIO.
+ * Prefix it with a description of the query Q, if non-null.
+ */
+void logmsg(const struct query *q, int prio, const char *msg, ...)
+{
+  va_list ap;
+
+  va_start(ap, msg);
+  vlogmsg(q, prio, msg, &ap);
+  va_end(ap);
+}
+
+/* Format and report MSG as a fatal error, and exit. */
+void fatal(const char *msg, ...)
+{
+  va_list ap;
+
+  va_start(ap, msg);
+  vlogmsg(0, LOG_CRIT, msg, &ap);
+  va_end(ap);
+  exit(1);
+}
+
 /* Fix up a socket FD so that it won't bite us.  Returns zero on success, or
  * nonzero on error.
  */
@@ -298,7 +320,8 @@ static void done_client_write(int err, void *p)
 /* Format the message FMT and queue it to be sent to the client.  Client
  * input will be disabled until the write completes.
  */
-static void write_to_client(struct client *c, const char *fmt, ...)
+static void PRINTF_LIKE(2, 3)
+  write_to_client(struct client *c, const char *fmt, ...)
 {
   va_list ap;
   char buf[WRBUFSZ];
@@ -361,12 +384,28 @@ static void cancel_proxy(struct proxy *px)
     conn_kill(&px->cn);
   else {
     close(px->fd);
-    selbuf_destroy(&px->b);
-    free_writebuf(&px->wb);
+    selbuf_disable(&px->b);
   }
-  selbuf_enable(&px->c->b);
   px->c->px = 0;
-  xfree(px);
+  selbuf_enable(&px->c->b);
+  px->next = dead_proxies;
+  dead_proxies = px;
+}
+
+/* Delayed destruction of unsafe parts of proxies. */
+static void reap_dead_proxies(void)
+{
+  struct proxy *px, *pp;
+
+  for (px = dead_proxies; px; px = pp) {
+    pp = px->next;
+    if (px->fd != -1) {
+      selbuf_destroy(&px->b);
+      free_writebuf(&px->wb);
+    }
+    xfree(px);
+  }
+  dead_proxies = 0;
 }
 
 /* Notification that a line (presumably a reply) has been received from the
@@ -542,12 +581,27 @@ err_0:
 /* Disconnect a client, freeing up any associated resources. */
 static void disconnect_client(struct client *c)
 {
+  selbuf_disable(&c->b);
   close(c->fd);
-  selbuf_destroy(&c->b);
   sel_rmtimer(&c->t);
   free_writebuf(&c->wb);
   if (c->px) cancel_proxy(c->px);
-  xfree(c);
+  c->next = dead_clients;
+  dead_clients = c;
+}
+
+/* Throw away dead clients now that we've reached a safe point in the
+ * program.
+ */
+static void reap_dead_clients(void)
+{
+  struct client *c, *cc;
+  for (c = dead_clients; c; c = cc) {
+    cc = c->next;
+    selbuf_destroy(&c->b);
+    xfree(c);
+  }
+  dead_clients = 0;
 }
 
 /* Time out a client because it's been idle for too long. */
@@ -589,8 +643,7 @@ static void user_token(char *p)
    * from the kernel.
    */
   if (tokenptr + TOKENRANDSZ >= sizeof(tokenbuf)) {
-    if (read(randfd, tokenbuf, sizeof(tokenbuf)) < sizeof(tokenbuf))
-      die(1, "unexpected short read or error from `/dev/urandom'");
+    fill_random(tokenbuf, sizeof(tokenbuf));
     tokenptr = 0;
   }
 
@@ -629,9 +682,10 @@ static void client_line(char *line, size_t len, void *p)
   struct policy upol = POLICY_INIT(A_LIMIT);
   struct policy_file pf;
   char buf[16];
-  int i;
+  int i, t;
 
   /* If the connection has closed, then tidy stuff away. */
+  c->q.s[R].addr = c->raddr;
   c->q.s[L].port = c->q.s[R].port = 0;
   if (!line) {
     disconnect_client(c);
@@ -657,7 +711,6 @@ static void client_line(char *line, size_t len, void *p)
   skipws(&q); if (*q) goto bad;
 
   /* Identify the connection.  Act on the result. */
-  c->q.s[R].addr = c->raddr;
   identify(&c->q);
   switch (c->q.resp) {
 
@@ -705,13 +758,13 @@ static void client_line(char *line, size_t len, void *p)
      */
     DRESET(&d);
     dstr_putf(&d, "%s/.yaid.policy", pw->pw_dir);
-    if (open_policy_file(&pf, d.buf, "user policy file", &c->q))
+    if (open_policy_file(&pf, d.buf, "user policy file", &c->q, OPF_NOENTOK))
       continue;
-    while (!read_policy_file(&pf)) {
+    while ((t = read_policy_file(&pf)) < T_ERROR) {
 
-      /* Give up after 100 lines.  If the user's policy is that complicated,
-       * something's gone very wrong.  Or there's too much commentary or
-       * something.
+      /* Give up after 100 lines or if there's an error.  If the user's
+       * policy is that complicated, something's gone very wrong.  Or there's
+       * too much commentary or something.
        */
       if (pf.lno > 100) {
        logmsg(&c->q, LOG_ERR, "%s:%d: user policy file too long",
@@ -719,6 +772,9 @@ static void client_line(char *line, size_t len, void *p)
        break;
       }
 
+      /* If this was a blank line, just go around again. */
+      if (t != T_OK) continue;
+
       /* If this isn't a match, go around for the next rule. */
       if (!match_policy(&pf.p, &c->q)) continue;
 
@@ -805,7 +861,7 @@ static void accept_client(int fd, unsigned mode, void *p)
   struct listen *l = p;
   struct client *c;
   struct sockaddr_storage ssr, ssl;
-  size_t ssz = sizeof(ssr);
+  socklen_t ssz = sizeof(ssr);
   int sk;
 
   /* Accept the new connection. */
@@ -906,7 +962,7 @@ static int make_listening_socket(const struct addrops *ao, int port)
 }
 
 /* Quit because of a fatal signal. */
-static void quit(int sig, void *p)
+static void NORETURN quit(int sig, void *p)
 {
   const char *signame = p;
 
@@ -1052,12 +1108,6 @@ int main(int argc, char *argv[])
   if (load_policy_file(policyfile, &policy))
     exit(1);
 
-  /* Open the random data source. */
-  if ((randfd = open("/dev/urandom", O_RDONLY)) < 0) {
-    die(1, "failed to open `/dev/urandom' for reading: %s",
-       strerror(errno));
-  }
-
   /* Set up the I/O event system. */
   sel_init(&sel);
 
@@ -1104,6 +1154,8 @@ int main(int argc, char *argv[])
   for (;;) {
     if (sel_select(&sel) && errno != EINTR)
       die(1, "select failed: %s", strerror(errno));
+    reap_dead_proxies();
+    reap_dead_clients();
   }
 
   /* This just keeps the compiler happy. */