struct client {
int fd; /* The connection to the client */
selbuf b; /* Accumulate lines of input */
+ union addr raddr; /* Remote address */
struct query q; /* The clients query and our reply */
+ struct sel_timer t; /* Timeout for idle or doomed conn */
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. */
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 --------------------------------------------------*/
static sel_state sel; /* I/O multiplexer state */
+static const char *pidfile = 0; /* Where to write daemon's pid */
+
+static const char *policyfile = POLICYFILE; /* Filename for global policy */
static const struct policy default_policy = POLICY_INIT(A_NAME);
static policy_v policy = DA_INIT; /* Vector of global policy rules */
static fwatch polfw; /* Watch policy file for changes */
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 */
+#define F_RUNNING 2u /* Running properly now */
+
/*----- Ident protocol parsing --------------------------------------------*/
/* Advance *PP over whitespace characters. */
{
va_list ap;
dstr d = DSTR_INIT;
+ time_t t;
+ struct tm *tm;
+ char buf[64];
va_start(ap, msg);
if (q) {
}
dstr_vputf(&d, msg, &ap);
va_end(ap);
- fprintf(stderr, "yaid: %s\n", d.buf);
+
+ if (!(flags & F_RUNNING))
+ moan("%s", d.buf);
+ else if (flags & F_SYSLOG)
+ syslog(prio, "%s", d.buf);
+ else {
+ t = time(0);
+ tm = localtime(&t);
+ strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S %z", tm);
+ fprintf(stderr, "%s %s: %s\n", buf, QUIS, d.buf);
+ }
+
dstr_destroy(&d);
}
/* 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];
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
/* 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. */
+static void timeout_client(struct timeval *tv, void *p)
+{
+ struct client *c = p;
+ logmsg(&c->q, LOG_NOTICE, "timing out idle or stuck client");
+ sel_addtimer(&sel, &c->t, tv, timeout_client, 0);
+ disconnect_client(c);
+}
+
+/* Reset the client idle timer, as a result of activity. Set EXISTP if
+ * there is an existing timer which needs to be removed.
+ */
+static void reset_client_timer(struct client *c, int existp)
+{
+ struct timeval tv;
+
+ gettimeofday(&tv, 0);
+ tv.tv_sec += 30;
+ if (existp) sel_rmtimer(&c->t);
+ sel_addtimer(&sel, &c->t, &tv, timeout_client, c);
}
/* Write a pseudorandom token into the buffer at P, which must have space for
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);
return;
}
+ /* Client activity, so update the timer. */
+ reset_client_timer(c, 1);
+
/* See if the policy file has changed since we last looked. If so, try to
* read the new version.
*/
- if (fwatch_update(&polfw, "yaid.policy")) {
- logmsg(0, LOG_INFO, "reload master policy file `%s'", "yaid.policy");
- load_policy_file("yaid.policy", &policy);
+ if (fwatch_update(&polfw, policyfile)) {
+ logmsg(0, LOG_INFO, "reload master policy file `%s'", policyfile);
+ load_policy_file(policyfile, &policy);
}
/* Read the local and remote port numbers into the query structure. */
*/
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",
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;
c->q.ao = l->ao;
/* Collect the local and remote addresses. */
- l->ao->sockaddr_to_addr(&ssr, &c->q.s[R].addr);
+ l->ao->sockaddr_to_addr(&ssr, &c->raddr);
ssz = sizeof(ssl);
if (getsockname(sk, (struct sockaddr *)&ssl, &ssz)) {
logmsg(0, LOG_ERR,
/* Set stuff up for reading the query and sending responses. */
selbuf_init(&c->b, &sel, sk, client_line, c);
selbuf_setsize(&c->b, 1024);
+ reset_client_timer(c, 0);
c->fd = sk;
c->px = 0;
init_writebuf(&c->wb, sk, done_client_write, c);
return (0);
}
+/* Quit because of a fatal signal. */
+static void NORETURN quit(int sig, void *p)
+{
+ const char *signame = p;
+
+ logmsg(0, LOG_NOTICE, "shutting down on %s", signame);
+ if (pidfile) unlink(pidfile);
+ exit(0);
+}
+
+/* Answer whether the string pointed to by P consists entirely of digits. */
+static int numericp(const char *p)
+{
+ while (*p)
+ if (!isdigit((unsigned char)*p++)) return (0);
+ return (1);
+}
+
+static void usage(FILE *fp)
+{
+ pquis(fp, "Usage: $ [-Dl] [-G GROUP] [-U USER] [-P FILE] "
+ "[-c FILE] [-p PORT]\n");
+}
+
+static void version(FILE *fp)
+ { pquis(fp, "$, version " VERSION "\n"); }
+
+static void help(FILE *fp)
+{
+ version(fp); fputc('\n', fp);
+ usage(fp);
+ fputs("\n\
+Yet Another Ident Daemon. Really, the world doesn't need such a thing.\n\
+It's just a shame none of the others do the right things.\n\
+\n\
+Options:\n\
+\n\
+ -h, --help Show this help message.\n\
+ -v, --version Show the version number.\n\
+ -u, --usage Show a very short usage summary.\n\
+\n\
+ -D, --daemon Become a daemon, running in the background.\n\
+ -G, --group=GROUP Set group after initialization.\n\
+ -P, --pidfile=FILE Write process id to FILE.\n\
+ -U, --user=USER Set user after initialization.\n\
+ -c, --config=FILE Read global policy from FILE.\n\
+ -l, --syslog Write log messages using syslog(3).\n\
+ -p, --port=PORT Listen for connections on this port.\n",
+ fp);
+}
+
int main(int argc, char *argv[])
{
int port = 113;
+ uid_t u = -1;
+ gid_t g = -1;
+ struct passwd *pw = 0;
+ struct group *gr;
+ struct servent *s;
+ sig sigint, sigterm;
+ FILE *fp = 0;
+ int i;
+ unsigned f = 0;
+#define f_bogus 1u
+#define f_daemon 2u
const struct addrops *ao;
int any = 0;
ego(argv[0]);
- fwatch_init(&polfw, "yaid.policy");
+ /* Parse command-line options. */
+ for (;;) {
+ const struct option opts[] = {
+ { "help", 0, 0, 'h' },
+ { "version", 0, 0, 'v' },
+ { "usage", 0, 0, 'u' },
+ { "daemon", 0, 0, 'D' },
+ { "group", OPTF_ARGREQ, 0, 'G' },
+ { "pidfile", OPTF_ARGREQ, 0, 'P' },
+ { "user", OPTF_ARGREQ, 0, 'U' },
+ { "config", OPTF_ARGREQ, 0, 'c' },
+ { "syslog", 0, 0, 'l' },
+ { "port", OPTF_ARGREQ, 0, 'p' },
+ { 0, 0, 0, 0 }
+ };
+
+ if ((i = mdwopt(argc, argv, "hvuDG:P:U:c:lp:", opts, 0, 0, 0)) < 0)
+ break;
+ switch (i) {
+ case 'h': help(stdout); exit(0);
+ case 'v': version(stdout); exit(0);
+ case 'u': usage(stdout); exit(0);
+ case 'D': f |= f_daemon; break;
+ case 'P': pidfile = optarg; break;
+ case 'c': policyfile = optarg; break;
+ case 'l': flags |= F_SYSLOG; break;
+ case 'G':
+ if (numericp(optarg))
+ g = atoi(optarg);
+ else if ((gr = getgrnam(optarg)) == 0)
+ die(1, "unknown group `%s'", optarg);
+ else
+ g = gr->gr_gid;
+ break;
+ case 'U':
+ if (numericp(optarg))
+ u = atoi(optarg);
+ else if ((pw = getpwnam(optarg)) == 0)
+ die(1, "unknown user `%s'", optarg);
+ else
+ u = pw->pw_uid;
+ break;
+ case 'p':
+ if (numericp(optarg))
+ port = atoi(optarg);
+ else if ((s = getservbyname(optarg, "tcp")) == 0)
+ die(1, "unknown service name `%s'", optarg);
+ else
+ port = ntohs(s->s_port);
+ break;
+ default: f |= f_bogus; break;
+ }
+ }
+ if (optind < argc) f |= f_bogus;
+ if (f & f_bogus) { usage(stderr); exit(1); }
+
+ /* If a user has been requested, but no group, then find the user's primary
+ * group. If the user was given by name, then we already have a password
+ * entry and should use that, in case two differently-named users have the
+ * same uid but distinct gids.
+ */
+ if (u != -1 && g == -1) {
+ if (!pw && (pw = getpwuid(u)) == 0) {
+ die(1, "failed to find password entry for user %d: "
+ "request group explicitly", u);
+ }
+ g = pw->pw_gid;
+ }
+
+ /* Initialize system-specific machinery. */
init_sys();
- if (load_policy_file("yaid.policy", &policy))
+
+ /* Load the global policy rules. */
+ fwatch_init(&polfw, policyfile);
+ if (load_policy_file(policyfile, &policy))
exit(1);
- { int i;
- for (i = 0; i < DA_LEN(&policy); i++)
- print_policy(&DA(&policy)[i]);
- }
+ /* 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);
+
+ /* Watch for some interesting signals. */
+ sig_init(&sel);
+ sig_add(&sigint, SIGINT, quit, "SIGINT");
+ sig_add(&sigterm, SIGTERM, quit, "SIGTERM");
+
+ /* Listen for incoming connections. */
for (ao = addroptab; ao->name; ao++)
if (!make_listening_socket(ao, port)) any = 1;
- if (!any)
- die(1, "no IP protocols supported");
+ if (!any) die(1, "no IP protocols supported");
- for (;;)
- if (sel_select(&sel)) die(1, "select failed: %s", strerror(errno));
+ /* Open the pidfile now, in case it's somewhere we can't write. */
+ if (pidfile && (fp = fopen(pidfile, "w")) == 0) {
+ die(1, "failed to open pidfile `%s' for writing: %s",
+ pidfile, strerror(errno));
+ }
+
+ /* If we're meant to use syslog, then open the log. */
+ if (flags & F_SYSLOG)
+ openlog(QUIS, 0, LOG_DAEMON);
+
+ /* Drop privileges. */
+ if ((g != -1 && (setegid(g) || setgid(g) ||
+ (getuid() == 0 && setgroups(1, &g)))) ||
+ (u != -1 && setuid(u)))
+ die(1, "failed to drop privileges: %s", strerror(errno));
+
+ /* Become a background process, if requested. */
+ if ((f & f_daemon) && daemonize())
+ die(1, "failed to become daemon: %s", strerror(errno));
+
+ /* Write the process id to the pidfile. */
+ if (fp) {
+ fprintf(fp, "%d\n", getpid());
+ fclose(fp);
+ }
+
+ /* And now we're going. */
+ flags |= F_RUNNING;
+
+ /* Read events and process them. */
+ 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. */
return (0);
}