X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~mdw/git/yaid/blobdiff_plain/223e3e2be7a30ab58804b9b741c9438b123a16fd..c372d20ef78322f5cd59ddbff44fa41156d851d5:/yaid.c diff --git a/yaid.c b/yaid.c index a99604f..d0d917a 100644 --- 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. */