Apparently working version, but still ugly.
authorMark Wooding <mdw@distorted.org.uk>
Sun, 14 Oct 2012 23:21:21 +0000 (00:21 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Sun, 14 Oct 2012 23:21:21 +0000 (00:21 +0100)
It needs a lot of commentary, a proper command-line interface, logging,
pidfiles, and so on.

Also, there's a bunch of inconsistency surrounding IPv4/IPv6 handling:
the core uses a vtable, while most of the rest of the code uses
switches.  This ought to be sorted out before a proper release.

Makefile.am
configure.ac
ident.c
policy.c [new file with mode: 0644]
yaid.c [new file with mode: 0644]
yaid.h [new file with mode: 0644]

index d716b07..d90a2ff 100644 (file)
@@ -39,7 +39,9 @@ sbin_PROGRAMS         += yaid
 yaid_SOURCES            =
 yaid_LDADD              = $(mLib_LIBS)
 
+yaid_SOURCES           += yaid.c
 yaid_SOURCES           += ident.c
+yaid_SOURCES           += policy.c
 
 ###--------------------------------------------------------------------------
 ### Release machinery.
index 88e5cc5..861627a 100644 (file)
@@ -28,7 +28,7 @@ dnl Initialization.
 
 mdw_AUTO_VERSION
 AC_INIT([yaid], AUTO_VERSION, [mdw@distorted.org.uk])
-AC_CONFIG_SRCDIR([ident.c])
+AC_CONFIG_SRCDIR([yaid.c])
 AC_CONFIG_AUX_DIR([config])
 AM_INIT_AUTOMAKE([foreign])
 mdw_SILENT_RULES
@@ -43,6 +43,7 @@ dnl C programming environment.
 AC_CHECK_HEADERS([stdarg.h])
 
 AC_SEARCH_LIBS([socket], [socket])
+
 PKG_CHECK_MODULES([mLib], [mLib >= 2.1.0])
 AM_CFLAGS="$AM_CFLAGS $mLib_CFLAGS"
 
diff --git a/ident.c b/ident.c
index ce4ba29..ae3cf13 100644 (file)
--- a/ident.c
+++ b/ident.c
 
 /*----- Header files ------------------------------------------------------*/
 
-#include <ctype.h>
-#include <errno.h>
-#include <stdio.h>
-#include <string.h>
-#include <string.h>
-
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <arpa/inet.h>
-
-#include <syslog.h>
-
-#include <mLib/dstr.h>
-
-/*----- Data structures ---------------------------------------------------*/
-
-union addr {
-  struct in_addr ipv4;
-  struct in6_addr ipv6;
-};
-
-struct socket {
-  union addr addr;
-  int port;
-};
-
-enum { L, R, NDIR };
-
-struct query {
-  int af;
-  struct socket s[NDIR];
-} query;
-
-#define RESPONSE(_)                                                    \
-  _(ERROR, U(error, unsigned))                                         \
-  _(UID, U(uid, uid_t))                                                        \
-  _(NAT, U(nat, struct socket))
-
-#define ERROR(_)                                                       \
-  _(INVPORT, "INVALID-PORT")                                           \
-  _(NOUSER, "NO-USER")                                                 \
-  _(HIDDEN, "HIDDEN-USER")                                             \
-  _(UNKNOWN, "UNKNOWN-ERROR")
-
-enum {
-#define DEFENUM(err, tok) E_##err,
-  ERROR(DEFENUM)
-#undef DEFENUM
-  E_LIMIT
-};
-
-enum {
-#define DEFENUM(what, branch) R_##what,
-  RESPONSE(DEFENUM)
-#undef DEFENUM
-  R_LIMIT
-};
-
-struct response {
-  unsigned what;
-  union {
-#define DEFBRANCH(WHAT, branch) branch
-#define U(memb, ty) ty memb;
-#define N
-    RESPONSE(DEFBRANCH)
-#undef U
-#undef N
-#undef DEFBRANCH
-  } u;
-};
+#include "yaid.h"
 
 /*----- Static variables --------------------------------------------------*/
 
-static const char *errtok[] = {
+const char *const errtok[] = {
 #define DEFTOK(err, tok) tok,
   ERROR(DEFTOK)
 #undef DEFTOK
 };
 
 static int parseaddr4(char **pp, union addr *a)
-  { a->ipv4.s_addr = strtoul(*pp, (char **)pp, 16); return (0); }
+  { a->ipv4.s_addr = strtoul(*pp, pp, 16); return (0); }
 
 static int addreq4(const union addr *a, const union addr *aa)
   { return a->ipv4.s_addr == aa->ipv4.s_addr; }
 
+static int parseaddr6(char **pp, union addr *a)
+{
+  int i, j;
+  unsigned long y;
+  char *p = *pp;
+  unsigned x;
+
+  for (i = 0; i < 4; i++) {
+    y = 0;
+    for (j = 0; j < 8; j++) {
+      if ('0' <= *p && *p <= '9') x = *p - '0';
+      else if ('a' <= *p && *p <= 'f') x = *p - 'a'+ 10;
+      else if ('A' <= *p && *p <= 'F') x = *p - 'A'+ 10;
+      else return (-1);
+      y = (y << 4) | x;
+      p++;
+    }
+    a->ipv6.s6_addr32[i] = y;
+  }
+  *pp = p;
+  return (0);
+}
+
+static int addreq6(const union addr *a, const union addr *b)
+  { return !memcmp(a->ipv6.s6_addr, b->ipv6.s6_addr, 16); }
+
 static const struct addrfamily {
   int af;
   const char *procfile;
@@ -119,43 +75,82 @@ static const struct addrfamily {
   int (*addreq)(const union addr *a, const union addr *aa);
 } addrfamilytab[] = {
   { AF_INET, "/proc/net/tcp", parseaddr4, addreq4 },
-  { AF_INET6, "/proc/net/tcp6", /*parseaddr6*/ },
+  { AF_INET6, "/proc/net/tcp6", parseaddr6, addreq6 },
   { -1 }
 };
 
 /*----- Main code ---------------------------------------------------------*/
 
-static void dputsock(dstr *d, int af, const struct socket *s)
+static int sockeq(const struct addrfamily *af,
+                 const struct socket *sa, const struct socket *sb)
+  { return (af->addreq(&sa->addr, &sb->addr) && sa->port == sb->port); }
+
+int get_default_gw(int af, union addr *a)
 {
-  char buf[INET6_ADDRSTRLEN];
+  int fd;
+  char buf[32768];
+  struct nlmsghdr *nlmsg;
+  struct rtgenmsg *rtgen;
+  const struct rtattr *rta;
+  const struct rtmsg *rtm;
+  ssize_t n, nn;
+  int rc = 0;
+  static unsigned long seq = 0x48b4aec4;
+
+  if ((fd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE)) < 0)
+    die(1, "failed to create netlink socket: %s", strerror(errno));
+
+  nlmsg = (struct nlmsghdr *)buf;
+  assert(NLMSG_SPACE(sizeof(*rtgen)) < sizeof(buf));
+  nlmsg->nlmsg_len = NLMSG_LENGTH(sizeof(*rtgen));
+  nlmsg->nlmsg_type = RTM_GETROUTE;
+  nlmsg->nlmsg_flags = NLM_F_REQUEST | NLM_F_ROOT;
+  nlmsg->nlmsg_seq = ++seq;
+  nlmsg->nlmsg_pid = 0;
+
+  rtgen = (struct rtgenmsg *)NLMSG_DATA(nlmsg);
+  rtgen->rtgen_family = af;
+
+  if (write(fd, nlmsg, nlmsg->nlmsg_len) < 0)
+    die(1, "failed to send RTM_GETROUTE request: %s", strerror(errno));
 
-  inet_ntop(af, &s->addr, buf, sizeof(buf));
-  if (af != AF_INET6) dstr_puts(d, buf);
-  else { dstr_putc(d, '['); dstr_puts(d, buf); dstr_putc(d, ']'); }
-  dstr_putf(d, ":%d", s->port);
-}
+  for (;;) {
+    if ((n = read(fd, buf, sizeof(buf))) < 0)
+      die(1, "failed to read RTM_GETROUTE response: %s", strerror(errno));
+    nlmsg = (struct nlmsghdr *)buf;
+    if (nlmsg->nlmsg_seq != seq) continue;
+    assert(nlmsg->nlmsg_flags & NLM_F_MULTI);
+
+    for (; NLMSG_OK(nlmsg, n); nlmsg = NLMSG_NEXT(nlmsg, n)) {
+      if (nlmsg->nlmsg_type == NLMSG_DONE) goto done;
+      if (nlmsg->nlmsg_type != RTM_NEWROUTE) continue;
+      rtm = (const struct rtmsg *)NLMSG_DATA(nlmsg);
+
+      if (rtm->rtm_family != af ||
+         rtm->rtm_dst_len > 0 ||
+         rtm->rtm_src_len > 0 ||
+         rtm->rtm_type != RTN_UNICAST ||
+         rtm->rtm_scope != RT_SCOPE_UNIVERSE ||
+         rtm->rtm_tos != 0)
+       continue;
 
-static void logmsg(const struct query *q, int prio, const char *msg, ...)
-{
-  va_list ap;
-  dstr d = DSTR_INIT;
+      for (rta = RTM_RTA(rtm), nn = RTM_PAYLOAD(nlmsg);
+          RTA_OK(rta, nn); rta = RTA_NEXT(rta, nn)) {
+       if (rta->rta_type == RTA_GATEWAY) {
+         assert(RTA_PAYLOAD(rta) <= sizeof(*a));
+         memcpy(a, RTA_DATA(rta), RTA_PAYLOAD(rta));
+         rc = 1;
+       }
+      }
+    }
+  }
 
-  va_start(ap, msg);
-  dputsock(&d, q->af, &q->s[L]);
-  dstr_puts(&d, " <-> ");
-  dputsock(&d, q->af, &q->s[R]);
-  dstr_puts(&d, ": ");
-  dstr_vputf(&d, msg, &ap);
-  va_end(ap);
-  fprintf(stderr, "yaid: %s\n", d.buf);
-  dstr_destroy(&d);
+done:
+  close(fd);
+  return (rc);
 }
 
-static int sockeq(const struct addrfamily *af,
-                 const struct socket *sa, const struct socket *sb)
-  { return (af->addreq(&sa->addr, &sb->addr) && sa->port == sb->port); }
-
-void identify(const struct query *q, struct response *r)
+void identify(struct query *q)
 {
   const struct addrfamily *af;
   FILE *fp = 0;
@@ -163,6 +158,7 @@ void identify(const struct query *q, struct response *r)
   char *p, *pp;
   struct socket s[4];
   int i;
+  int gwp = 0;
   unsigned fl;
 #define F_SADDR 1u
 #define F_SPORT 2u
@@ -180,6 +176,10 @@ void identify(const struct query *q, struct response *r)
   goto err_unk;
 found_af:;
 
+  if (get_default_gw(q->af, &s[0].addr) &&
+      af->addreq(&s[0].addr, &q->s[R].addr))
+    gwp = 1;
+
   if ((fp = fopen(af->procfile, "r")) == 0) {
     logmsg(q, LOG_ERR, "failed to open `%s' for reading: %s",
           af->procfile, strerror(errno));
@@ -242,13 +242,13 @@ found_af:;
       if (af->parseaddr(&p, &s[0].addr)) goto next_row;
       if (*p != ':') break; p++;
       s[0].port = strtoul(p, 0, 16);
-      /* FIXME: accept forwarded queries from NAT */
-      if (!sockeq(af, &q->s[i], &s[0])) goto next_row;
-      else continue;
+      if (!sockeq(af, &q->s[i], &s[0]) &&
+         (i != R || !gwp || q->s[R].port != s[0].port))
+       goto next_row;
     }
     if (uid != -1) {
-      r->what = R_UID;
-      r->u.uid = uid;
+      q->resp = R_UID;
+      q->u.uid = uid;
       goto done;
     }
   next_row:;
@@ -331,8 +331,8 @@ found_af:;
       if (!sockeq(af, &s[i^1], &s[i^2]) ||
          !sockeq(af, &s[i^1], &q->s[R]))
        continue;
-      r->what = R_NAT;
-      r->u.nat = s[i^3];
+      q->resp = R_NAT;
+      q->u.nat = s[i^3];
       goto done;
     }
 
@@ -341,53 +341,20 @@ found_af:;
             strerror(errno));
       goto err_unk;
     }
+    logmsg(q, LOG_ERR, "connection not found");
   }
 
 #undef NEXTFIELD
 
 err_nouser:
-  r->what = R_ERROR;
-  r->u.error = E_NOUSER;
+  q->resp = R_ERROR;
+  q->u.error = E_NOUSER;
   goto done;
 err_unk:
-  r->what = R_ERROR;
-  r->u.error = E_UNKNOWN;
+  q->resp = R_ERROR;
+  q->u.error = E_UNKNOWN;
 done:
   dstr_destroy(&d);
 }
 
 /*----- That's all, folks -------------------------------------------------*/
-
-int main(int argc, char *argv[])
-{
-  struct query q;
-  struct response r;
-  char buf[INET6_ADDRSTRLEN];
-
-  q.af = AF_INET;
-  inet_pton(AF_INET, argv[1], &q.s[L].addr.ipv4);
-  q.s[L].port = atoi(argv[2]);
-  inet_pton(AF_INET, argv[3], &q.s[R].addr.ipv4);
-  q.s[R].port = atoi(argv[4]);
-
-  identify(&q, &r);
-
-  switch (r.what) {
-    case R_UID:
-      printf("uid %d\n", r.u.uid);
-      break;
-    case R_ERROR:
-      if (r.u.error < E_LIMIT) printf("error %s\n", errtok[r.u.error]);
-      else printf("error E%u\n", r.u.error);
-      break;
-    case R_NAT:
-      inet_ntop(q.af, &r.u.nat.addr, buf, sizeof(buf));
-      printf("nat -> %s:%d\n", buf, r.u.nat.port);
-      break;
-    default:
-      printf("unknown response\n");
-      break;
-  }
-
-  return (0);
-}
diff --git a/policy.c b/policy.c
new file mode 100644 (file)
index 0000000..83db0a8
--- /dev/null
+++ b/policy.c
@@ -0,0 +1,398 @@
+/* -*-c-*-
+ *
+ * Policy parsing and implementation
+ *
+ * (c) 2012 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of Yet Another Ident Daemon (YAID).
+ *
+ * YAID is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * YAID is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with YAID; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "yaid.h"
+
+/*----- Data structures ---------------------------------------------------*/
+
+/*----- Static variables --------------------------------------------------*/
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* syntax: addrpat portpat addrpar portpat policy
+ *
+ * local address/port first, then remote
+ * addrpat ::= addr [/ len]
+ * portpat ::= num | num - num | *
+ * policy ::= user policy* | token | name | deny | hide |
+ */
+
+void init_policy(struct policy *p) { p->act.act = A_LIMIT; }
+
+static void free_action(struct action *a)
+{
+  switch (a->act) {
+    case A_LIE:
+      xfree(a->u.lie);
+      break;
+  }
+  a->act = A_LIMIT;
+}
+
+void free_policy(struct policy *p)
+  { free_action(&p->act); }
+
+static void print_addrpat(int af, const struct addrpat *ap)
+{
+  char buf[ADDRLEN];
+
+  if (ap->len == 0) putchar('*');
+  else printf("%s/%u", inet_ntop(af, &ap->addr, buf, sizeof(buf)), ap->len);
+}
+
+static void print_portpat(const struct portpat *pp)
+{
+  if (pp->lo == 0 && pp->hi == 65535) putchar('*');
+  else if (pp->lo == pp->hi) printf("%u", pp->lo);
+  else printf("%u-%u", pp->lo, pp->hi);
+}
+
+static void print_sockpat(int af, const struct sockpat *sp)
+  { print_addrpat(af, &sp->addr); putchar(' '); print_portpat(&sp->port); }
+
+static const char *const acttab[] = {
+#define DEFACT(tag, name) name,
+  ACTIONS(DEFACT)
+#undef DEFACT
+  0
+};
+
+static void print_action(const struct action *act)
+{
+  assert(act->act < A_LIMIT);
+  printf("%s", acttab[act->act]);
+  switch (act->act) {
+    case A_USER: {
+      int i;
+      unsigned m;
+      for (i = 0, m = 1; i < A_LIMIT; i++, m <<= 1)
+       if (act->u.user & m) printf(" %s", acttab[i]);
+    } break;
+    case A_LIE:
+      printf(" %s", act->u.lie);
+      break;
+  }
+}
+
+void print_policy(const struct policy *p)
+{
+  print_sockpat(p->af, &p->sp[L]); putchar(' ');
+  print_sockpat(p->af, &p->sp[R]); putchar(' ');
+  print_action(&p->act); putchar('\n');
+}
+
+static int match_addrpat(int af, const struct addrpat *ap,
+                        const union addr *a)
+{
+  if (!ap->len)
+    return (1);
+  switch (af) {
+    case AF_INET: {
+      unsigned mask = htonl((MASK32 << (32 - ap->len)) & MASK32);
+      return (((ap->addr.ipv4.s_addr ^ a->ipv4.s_addr) & mask) == 0);
+    }
+    case AF_INET6:
+      abort();
+  }
+  return (0);
+}
+
+static int match_portpat(const struct portpat *pp, unsigned port)
+  { return (pp->lo <= port && port <= pp->hi); }
+
+static int match_sockpat(int af, const struct sockpat *sp,
+                        const struct socket *s)
+{
+  return (match_addrpat(af, &sp->addr, &s->addr) &&
+         match_portpat(&sp->port, s->port));
+}
+
+int match_policy(const struct policy *p, const struct query *q)
+{
+  return ((!p->af || p->af == q->af) &&
+         match_sockpat(p->af, &p->sp[L], &q->s[L]) &&
+         match_sockpat(p->af, &p->sp[R], &q->s[R]));
+}
+
+static void nextline(FILE *fp)
+{
+  for (;;) {
+    int ch = getc(fp);
+    if (ch == '\n' || ch == EOF) break;
+  }
+}
+
+static int scan(FILE *fp, char *buf, size_t sz)
+{
+  int ch;
+
+skip_ws:
+  ch = getc(fp);
+  switch (ch) {
+    case '\n':
+    newline:
+      ungetc(ch, fp);
+      return (T_EOL);
+    case EOF:
+    eof:
+      return (ferror(fp) ? T_ERROR : T_EOF);
+    case '#':
+      for (;;) {
+       ch = getc(fp);
+       if (ch == '\n') goto newline;
+       else if (ch == EOF) goto eof;
+      }
+    default:
+      if (isspace(ch)) goto skip_ws;
+      break;
+  }
+
+  for (;;) {
+    if (sz) { *buf++ = ch; sz--; }
+    ch = getc(fp);
+    switch (ch) {
+      case '\n':
+       ungetc(ch, fp);
+       goto done;
+      case EOF:
+       goto done;
+      default:
+       if (isspace(ch)) goto done;
+       break;
+    }
+  }
+
+done:
+  if (!sz)
+    return (T_ERROR);
+  else {
+    *buf++ = 0; sz--;
+    return (T_OK);
+  }
+}
+
+static int parse_actname(FILE *fp, unsigned *act)
+{
+  char buf[32];
+  int t;
+  const char *const *p;
+
+  if ((t = scan(fp, buf, sizeof(buf))) != 0) return (t);
+  for (p = acttab; *p; p++)
+    if (strcmp(buf, *p) == 0) { *act = p - acttab; return (0); }
+  return (T_ERROR);
+}
+
+static int parse_action(FILE *fp, struct action *act)
+{
+  char buf[32];
+  int t;
+  unsigned a;
+  unsigned long m;
+
+  if ((t = parse_actname(fp, &a)) != 0) return (t);
+  switch (a) {
+    case A_USER:
+      m = 0;
+      for (;;) {
+       if ((t = parse_actname(fp, &a)) != 0) break;
+       m |= (1 << a);
+      }
+      if (t != T_EOL && t != T_EOF) return (t);
+      act->act = A_USER;
+      act->u.user = m;
+      break;
+    case A_TOKEN:
+    case A_NAME:
+    case A_DENY:
+    case A_HIDE:
+      act->act = a;
+      break;
+    case A_LIE:
+      if ((t = scan(fp, buf, sizeof(buf))) != 0) return (t);
+      act->act = a;
+      act->u.lie = xstrdup(buf);
+      break;
+  }
+  t = scan(fp, buf, sizeof(buf));
+  if (t != T_EOF && t != T_EOL) {
+    free_action(act);
+    return (T_ERROR);
+  }
+  return (0);
+}
+
+static int parse_sockpat(FILE *fp, int *afp, struct sockpat *sp)
+{
+  char buf[64];
+  int t;
+  int af;
+  int alen;
+  long n;
+  char *delim;
+
+  if ((t = scan(fp, buf, sizeof(buf))) != 0) return (t);
+  if (strcmp(buf, "*") == 0)
+    sp->addr.len = 0;
+  else {
+    if (strchr(buf, ':')) {
+      af = AF_INET6;
+      alen = 128;
+    } else {
+      af = AF_INET;
+      alen = 32;
+    }
+    if (!*afp) *afp = af;
+    else if (*afp != af) return (T_ERROR);
+    delim = strchr(buf, '/');
+    if (delim) *delim++ = 0;
+    if (!inet_pton(af, buf, &sp->addr.addr)) return (T_ERROR);
+    if (!delim) n = alen;
+    else n = strtol(delim, 0, 10);
+    if (n < 0 || n > alen) return (T_ERROR);
+    sp->addr.len = n;
+  }
+
+  if ((t = scan(fp, buf, sizeof(buf))) != 0) return (T_ERROR);
+  if (strcmp(buf, "*") == 0) {
+    sp->port.lo = 0;
+    sp->port.hi = 65535;
+  } else {
+    delim = strchr(buf, '-');
+    if (delim) *delim++ = 0;
+    n = strtol(buf, 0, 0);
+    if (n < 0 || n > 65535) return (T_ERROR);
+    sp->port.lo = n;
+    if (!delim)
+      sp->port.hi = n;
+    else {
+      n = strtol(delim, 0, 0);
+      if (n < 0 || n > 65535) return (T_ERROR);
+      sp->port.hi = n;
+    }
+  }
+  return (0);
+}
+
+int parse_policy(FILE *fp, struct policy *p)
+{
+  int t;
+
+  p->af = 0;
+  free_policy(p);
+
+  if ((t = parse_sockpat(fp, &p->af, &p->sp[L])) != 0) goto fail;
+  if ((t = parse_sockpat(fp, &p->af, &p->sp[R])) != 0) goto err;
+  if ((t = parse_action(fp, &p->act)) != 0) goto err;
+  return (0);
+
+err:
+  t = T_ERROR;
+fail:
+  free_policy(p);
+  return (t);
+}
+
+int open_policy_file(struct policy_file *pf, const char *name,
+                    const char *what, const struct query *q)
+{
+  if ((pf->fp = fopen(name, "r")) == 0) {
+    logmsg(q, LOG_ERR, "failed to open %s `%s': %s",
+          what, name, strerror(errno));
+    return (-1);
+  }
+
+  pf->name = name;
+  pf->what = what;
+  pf->q = q;
+  pf->err = 0;
+  pf->lno = 0;
+  init_policy(&pf->p);
+  return (0);
+}
+
+int read_policy_file(struct policy_file *pf)
+{
+  int t;
+
+  for (;;) {
+    pf->lno++;
+    t = parse_policy(pf->fp, &pf->p);
+    switch (t) {
+      case T_OK:
+       nextline(pf->fp);
+       return (0);
+      case T_ERROR:
+       logmsg(pf->q, LOG_ERR, "%s:%d: parse error in %s",
+              pf->name, pf->lno, pf->what);
+       pf->err = 1;
+       break;
+      case T_EOF:
+       if (ferror(pf->fp)) {
+         logmsg(pf->q, LOG_ERR, "failed to read %s `%s': %s",
+                pf->what, pf->name, strerror(errno));
+       }
+       return (-1);
+      case T_EOL:
+       nextline(pf->fp);
+       break;
+      default:
+       abort();
+    }
+  }
+}
+
+void close_policy_file(struct policy_file *pf)
+{
+  fclose(pf->fp);
+  free_policy(&pf->p);
+}
+
+int load_policy_file(const char *file, policy_v *pv)
+{
+  struct policy_file pf;
+  policy_v v = DA_INIT;
+
+  if (open_policy_file(&pf, file, "policy file", 0))
+    return (-1);
+  while (!read_policy_file(&pf)) {
+    DA_PUSH(&v, pf.p);
+    init_policy(&pf.p);
+  }
+  close_policy_file(&pf);
+  if (!pf.err) {
+    DA_DESTROY(pv);
+    *pv = v;
+    return (0);
+  } else {
+    DA_DESTROY(&v);
+    return (-1);
+  }
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/yaid.c b/yaid.c
new file mode 100644 (file)
index 0000000..385ce38
--- /dev/null
+++ b/yaid.c
@@ -0,0 +1,703 @@
+/* -*-c-*-
+ *
+ * Main daemon
+ *
+ * (c) 2012 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of Yet Another Ident Daemon (YAID).
+ *
+ * YAID is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * YAID is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with YAID; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "yaid.h"
+
+/*----- Data structures ---------------------------------------------------*/
+
+struct listen {
+  int af;
+  const char *proto;
+  sel_file f;
+};
+
+#define WRBUFSZ 1024
+struct writebuf {
+  size_t o, n;
+  sel_file wr;
+  void (*func)(int, void *);
+  void *p;
+  unsigned char buf[WRBUFSZ];
+};
+
+struct proxy {
+  struct client *c;
+  int fd;
+  conn cn;
+  selbuf b;
+  struct writebuf wb;
+  char nat[ADDRLEN];
+};
+
+struct client {
+  selbuf b;
+  int fd;
+  struct query q;
+  struct listen *l;
+  struct writebuf wb;
+  struct proxy *px;
+};
+
+/*----- Static variables --------------------------------------------------*/
+
+static sel_state sel;
+
+static policy_v policy = DA_INIT;
+static fwatch polfw;
+
+static unsigned char tokenbuf[4096];
+static size_t tokenptr = sizeof(tokenbuf);
+static int randfd;
+
+/*----- Main code ---------------------------------------------------------*/
+
+static void socket_to_sockaddr(int af, const struct socket *s,
+                              struct sockaddr *sa, size_t *ssz)
+{
+  sa->sa_family = af;
+  switch (af) {
+    case AF_INET: {
+      struct sockaddr_in *sin = (struct sockaddr_in *)sa;
+      sin->sin_addr = s->addr.ipv4;
+      sin->sin_port = htons(s->port);
+      *ssz = sizeof(*sin);
+    } break;
+    case AF_INET6: {
+      struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa;
+      sin6->sin6_addr = s->addr.ipv6;
+      sin6->sin6_port = htons(s->port);
+      sin6->sin6_flowinfo = 0;
+      sin6->sin6_scope_id = 0;
+      *ssz = sizeof(*sin6);
+    } break;
+    default: abort();
+  }
+}
+
+static void sockaddr_to_addr(const struct sockaddr *sa, union addr *a)
+{
+  switch (sa->sa_family) {
+    case AF_INET: a->ipv4 = ((struct sockaddr_in *)sa)->sin_addr; break;
+    case AF_INET6: a->ipv6 = ((struct sockaddr_in6 *)sa)->sin6_addr; break;
+    default: abort();
+  }
+}
+
+static void dputsock(dstr *d, int af, const struct socket *s)
+{
+  char buf[ADDRLEN];
+
+  inet_ntop(af, &s->addr, buf, sizeof(buf));
+  if (!s->port || af != AF_INET6) dstr_puts(d, buf);
+  else { dstr_putc(d, '['); dstr_puts(d, buf); dstr_putc(d, ']'); }
+  if (s->port) dstr_putf(d, ":%d", s->port);
+}
+
+void logmsg(const struct query *q, int prio, const char *msg, ...)
+{
+  va_list ap;
+  dstr d = DSTR_INIT;
+
+  va_start(ap, msg);
+  if (q) {
+    dputsock(&d, q->af, &q->s[L]);
+    dstr_puts(&d, " <-> ");
+    dputsock(&d, q->af, &q->s[R]);
+    dstr_puts(&d, ": ");
+  }
+  dstr_vputf(&d, msg, &ap);
+  va_end(ap);
+  fprintf(stderr, "yaid: %s\n", d.buf);
+  dstr_destroy(&d);
+}
+
+static void write_out(int fd, unsigned mode, void *p)
+{
+  ssize_t n;
+  struct writebuf *wb = p;
+
+  if ((n = write(fd, wb->buf + wb->o, wb->n)) < 0) {
+    if (errno == EAGAIN || errno == EWOULDBLOCK) return;
+    wb->n = 0;
+    sel_rmfile(&wb->wr);
+    wb->func(errno, wb->p);
+  }
+  wb->o += n;
+  wb->n -= n;
+  if (!wb->n) {
+    wb->o = 0;
+    sel_rmfile(&wb->wr);
+    wb->func(0, wb->p);
+  }
+}
+
+static int queue_write(struct writebuf *wb, const void *p, size_t n)
+{
+  if (!n) return (0);
+  if (wb->n - wb->o + n > WRBUFSZ) return (-1);
+  if (wb->o) {
+    memmove(wb->buf, wb->buf + wb->o, wb->n);
+    wb->o = 0;
+  }
+  memcpy(wb->buf + wb->n, p, n);
+  if (!wb->n) {
+    sel_addfile(&wb->wr);
+    sel_force(&wb->wr);
+  }
+  wb->n += n;
+  return (0);
+}
+
+static void free_writebuf(struct writebuf *wb)
+  { if (wb->n) sel_rmfile(&wb->wr); }
+
+static void init_writebuf(struct writebuf *wb,
+                         int fd, void (*func)(int, void *), void *p)
+{
+  sel_initfile(&sel, &wb->wr, fd, SEL_WRITE, write_out, wb);
+  wb->func = func;
+  wb->p = p;
+  wb->n = wb->o = 0;
+}
+
+static void cancel_proxy(struct proxy *px)
+{
+  if (px->fd == -1)
+    conn_kill(&px->cn);
+  else {
+    close(px->fd);
+    selbuf_destroy(&px->b);
+    free_writebuf(&px->wb);
+  }
+  selbuf_enable(&px->c->b);
+  px->c->px = 0;
+  xfree(px);
+}
+
+static void disconnect_client(struct client *c)
+{
+  close(c->fd);
+  selbuf_destroy(&c->b);
+  free_writebuf(&c->wb);
+  if (c->px) cancel_proxy(c->px);
+  xfree(c);
+}
+
+static void done_client_write(int err, void *p)
+{
+  struct client *c = p;
+
+  if (!err)
+    selbuf_enable(&c->b);
+  else {
+    logmsg(&c->q, LOG_ERR, "failed to send reply: %s", strerror(err));
+    disconnect_client(c);
+  }
+}
+
+static void write_to_client(struct client *c, const char *fmt, ...)
+{
+  va_list ap;
+  char buf[WRBUFSZ];
+  ssize_t n;
+
+  va_start(ap, fmt);
+  n = vsnprintf(buf, sizeof(buf), fmt, ap);
+  if (n < 0) {
+    logmsg(&c->q, LOG_ERR, "failed to format output: %s", strerror(errno));
+    disconnect_client(c);
+    return;
+  } else if (n > sizeof(buf)) {
+    logmsg(&c->q, LOG_ERR, "output too long for client send buffer");
+    disconnect_client(c);
+    return;
+  }
+
+  selbuf_disable(&c->b);
+  if (queue_write(&c->wb, buf, n)) {
+    logmsg(&c->q, LOG_ERR, "write buffer overflow");
+    disconnect_client(c);
+  }
+}
+
+static void reply(struct client *c, const char *ty, const char *msg)
+{
+  write_to_client(c, "%u,%u:%s:%s\r\n",
+                 c->q.s[L].port, c->q.s[R].port, ty, msg);
+}
+
+static void reply_error(struct client *c, unsigned err)
+{
+  assert(err < E_LIMIT);
+  reply(c, "ERROR", errtok[err]);
+}
+
+static void skipws(const char **pp)
+  { while (isspace((unsigned char )**pp)) (*pp)++; }
+
+static int idtoken(const char **pp, char *q, size_t n)
+{
+  const char *p = *pp;
+
+  skipws(&p);
+  n--;
+  for (;;) {
+    if (*p == ':' || *p <= 32 || *p >= 127) break;
+    if (!n) return (-1);
+    *q++ = *p++;
+    n--;
+  }
+  *q++ = 0;
+  *pp = p;
+  return (0);
+}
+
+static int unum(const char **pp, unsigned *ii, unsigned min, unsigned max)
+{
+  char *q;
+  unsigned long i;
+  int e;
+
+  skipws(pp);
+  if (!isdigit((unsigned char)**pp)) return (-1);
+  e = errno; errno = 0;
+  i = strtoul(*pp, &q, 10);
+  if (errno) return (-1);
+  *pp = q;
+  errno = e;
+  if (i < min || i > max) return (-1);
+  *ii = i;
+  return (0);
+}
+
+static void proxy_line(char *line, size_t sz, void *p)
+{
+  struct proxy *px = p;
+  char buf[1024];
+  const char *q = line;
+  unsigned lp, rp;
+
+  while (sz && isspace((unsigned char)line[sz - 1])) sz--;
+  printf("received proxy line from %s: %s\n", px->nat, line);
+
+  if (unum(&q, &lp, 1, 65535)) goto syntax;
+  skipws(&q); if (*q != ',') goto syntax; q++;
+  if (unum(&q, &rp, 1, 65535)) goto syntax;
+  skipws(&q); if (*q != ':') goto syntax; q++;
+  if (lp != px->c->q.u.nat.port || rp != px->c->q.s[R].port) goto syntax;
+  if (idtoken(&q, buf, sizeof(buf))) goto syntax;
+  skipws(&q); if (*q != ':') goto syntax; q++;
+  if (strcmp(buf, "ERROR") == 0) {
+    skipws(&q);
+    logmsg(&px->c->q, LOG_ERR, "proxy error from %s: %s", px->nat, q);
+    reply(px->c, "ERROR", q);
+  } else if (strcmp(buf, "USERID") == 0) {
+    if (idtoken(&q, buf, sizeof(buf))) goto syntax;
+    skipws(&q); if (*q != ':') goto syntax; q++;
+    skipws(&q);
+    logmsg(&px->c->q, LOG_ERR, "user `%s'; proxy = %s, os = %s",
+          q, px->nat, buf);
+    write_to_client(px->c, "%u,%u:USERID:%s:%s\r\n",
+                   px->c->q.s[L].port, px->c->q.s[R].port, buf, q);
+  } else
+    goto syntax;
+  goto done;
+
+syntax:
+  logmsg(&px->c->q, LOG_ERR, "failed to parse response from %s", px->nat);
+  reply_error(px->c, E_UNKNOWN);
+done:
+  cancel_proxy(px);
+}
+
+static void done_proxy_write(int err, void *p)
+{
+  struct proxy *px = p;
+
+  if (err) {
+    logmsg(&px->c->q, LOG_ERR, "failed to proxy query to %s: %s",
+          px->nat, strerror(errno));
+    reply_error(px->c, E_UNKNOWN);
+    cancel_proxy(px);
+    return;
+  }
+  selbuf_enable(&px->b);
+}
+
+static void proxy_connected(int fd, void *p)
+{
+  struct proxy *px = p;
+  char buf[16];
+  int n;
+
+  if (fd < 0) {
+    logmsg(&px->c->q, LOG_ERR,
+          "failed to make %s proxy connection to %s: %s",
+          px->c->l->proto, px->nat, strerror(errno));
+    reply_error(px->c, E_UNKNOWN);
+    cancel_proxy(px);
+    return;
+  }
+
+  px->fd = fd;
+  selbuf_init(&px->b, &sel, fd, proxy_line, px);
+  selbuf_setsize(&px->b, 1024);
+  selbuf_disable(&px->b);
+  init_writebuf(&px->wb, fd, done_proxy_write, px);
+
+  n = sprintf(buf, "%u,%u\r\n", px->c->q.u.nat.port, px->c->q.s[R].port);
+  queue_write(&px->wb, buf, n);
+}
+
+static void proxy_query(struct client *c)
+{
+  struct socket s;
+  struct sockaddr_storage ss;
+  size_t ssz;
+  struct proxy *px;
+  int o;
+  int fd;
+
+  px = xmalloc(sizeof(*px));
+  inet_ntop(c->q.af, &c->q.u.nat.addr, px->nat, sizeof(px->nat));
+
+  if ((fd = socket(c->q.af, SOCK_STREAM, 0)) < 0) {
+    logmsg(&c->q, LOG_ERR, "failed to make %s socket for proxy: %s",
+          c->l->proto, strerror(errno));
+    goto err_0;
+  }
+
+  if ((o = fcntl(fd, F_GETFL)) < 0 ||
+      fcntl(fd, F_SETFL, o | O_NONBLOCK)) {
+    logmsg(&c->q, LOG_ERR, "failed to set %s proxy socket nonblocking: %s",
+          c->l->proto, strerror(errno));
+    goto err_1;
+  }
+
+  s = c->q.u.nat;
+  s.port = 113;
+  socket_to_sockaddr(c->q.af, &s, (struct sockaddr *)&ss, &ssz);
+  selbuf_disable(&c->b);
+  if (conn_init(&px->cn, &sel, fd, (struct sockaddr *)&ss, ssz,
+               proxy_connected, px)) {
+    logmsg(&c->q, LOG_ERR, "failed to make %s proxy connection to %s: %s",
+          c->l->proto, px->nat, strerror(errno));
+    goto err_2;
+  }
+
+  c->px = px; px->c = c;
+  px->fd = -1;
+  return;
+
+err_2:
+  selbuf_enable(&c->b);
+err_1:
+  close(px->fd);
+err_0:
+  xfree(px);
+  reply_error(c, E_UNKNOWN);
+}
+
+static const struct policy default_policy = POLICY_INIT(A_NAME);
+
+static void user_token(char *p)
+{
+  static const char tokmap[64] =
+    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.-";
+  unsigned a = 0;
+  unsigned b = 0;
+  int i;
+#define TOKENSZ 8
+
+  if (tokenptr + TOKENSZ >= sizeof(tokenbuf)) {
+    if (read(randfd, tokenbuf, sizeof(tokenbuf)) < sizeof(tokenbuf))
+      die(1, "unexpected short read or error from `/dev/urandom'");
+    tokenptr = 0;
+  }
+
+  for (i = 0; i < TOKENSZ; i++) {
+    a = (a << 8) | tokenbuf[tokenptr++]; b += 8;
+    while (b >= 6) {
+      b -= 6;
+      *p++ = tokmap[(a >> b) & 0x3f];
+    }
+  }
+  if (b)
+    *p++ = tokmap[(a << (6 - b)) & 0x3f];
+  *p++ = 0;
+}
+
+static void client_line(char *line, size_t len, void *p)
+{
+  struct client *c = p;
+  const char *q;
+  struct passwd *pw = 0;
+  const struct policy *pol;
+  dstr d = DSTR_INIT;
+  struct policy upol = POLICY_INIT(A_LIMIT);
+  struct policy_file pf;
+  char buf[16];
+  int i;
+
+  c->q.s[L].port = c->q.s[R].port = 0;
+  if (!line) {
+    disconnect_client(c);
+    return;
+  }
+
+  if (fwatch_update(&polfw, "yaid.policy")) {
+    logmsg(0, LOG_INFO, "reload master policy file `%s'", "yaid.policy");
+    load_policy_file("yaid.policy", &policy);
+  }
+
+  q = line;
+  if (unum(&q, &c->q.s[L].port, 1, 65535)) goto bad;
+  skipws(&q); if (*q != ',') goto bad; q++;
+  if (unum(&q, &c->q.s[R].port, 1, 65535)) goto bad;
+  skipws(&q); if (*q) goto bad;
+
+  identify(&c->q);
+  switch (c->q.resp) {
+    case R_UID:
+      if ((pw = getpwuid(c->q.u.uid)) == 0) {
+       logmsg(&c->q, LOG_ERR, "no passwd entry for user %d", c->q.u.uid);
+       reply_error(c, E_NOUSER);
+       return;
+      }
+      break;
+    case R_NAT:
+      proxy_query(c);
+      return;
+    case R_ERROR:
+      /* Should already be logged. */
+      reply_error(c, c->q.u.error);
+      return;
+    default:
+      abort();
+  }
+
+  for (i = 0; i < DA_LEN(&policy); i++) {
+    pol = &DA(&policy)[i];
+    if (!match_policy(pol, &c->q)) continue;
+    if (pol->act.act != A_USER)
+      goto match;
+    DRESET(&d);
+    dstr_putf(&d, "%s/.yaid.policy", pw->pw_dir);
+    if (open_policy_file(&pf, d.buf, "user policy file", &c->q))
+      continue;
+    while (!read_policy_file(&pf)) {
+      if (pf.lno > 100) {
+       logmsg(&c->q, LOG_ERR, "%s:%d: user policy file too long",
+              pf.name, pf.lno);
+       break;
+      }
+      if (!match_policy(&pf.p, &c->q)) continue;
+      if (!(pol->act.u.user & (1 << pf.p.act.act))) {
+       logmsg(&c->q, LOG_ERR,
+              "%s:%d: user action forbidden by global policy",
+              pf.name, pf.lno);
+       continue;
+      }
+      upol = pf.p; pol = &upol;
+      init_policy(&pf.p);
+      close_policy_file(&pf);
+      goto match;
+    }
+    close_policy_file(&pf);
+  }
+  pol = &default_policy;
+
+match:
+  DDESTROY(&d);
+  switch (pol->act.act) {
+    case A_NAME:
+      logmsg(&c->q, LOG_INFO, "user `%s' (%d)", pw->pw_name, c->q.u.uid);
+      reply(c, "USERID:UNIX", pw->pw_name);
+      break;
+    case A_TOKEN:
+      user_token(buf);
+      logmsg(&c->q, LOG_INFO, "user `%s' (%d); token = %s",
+            pw->pw_name, c->q.u.uid, buf);
+      reply(c, "USERID:OTHER", buf);
+      break;
+    case A_DENY:
+      logmsg(&c->q, LOG_INFO, "user `%s' (%d); denying",
+            pw->pw_name, c->q.u.uid);
+      break;
+    case A_HIDE:
+      logmsg(&c->q, LOG_INFO, "user `%s' (%d); hiding",
+            pw->pw_name, c->q.u.uid);
+      reply_error(c, E_HIDDEN);
+      break;
+    case A_LIE:
+      logmsg(&c->q, LOG_INFO, "user `%s' (%d); lie = `%s'",
+            pw->pw_name, c->q.u.uid, pol->act.u.lie);
+      reply(c, "USERID:UNIX", pol->act.u.lie);
+      break;
+    default:
+      abort();
+  }
+
+  free_policy(&upol);
+  return;
+
+bad:
+  logmsg(&c->q, LOG_ERR, "failed to parse query from client");
+  disconnect_client(c);
+}
+
+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);
+  int sk;
+
+  if ((sk = accept(fd, (struct sockaddr *)&ssr, &ssz)) < 0) {
+    if (errno != EAGAIN && errno == EWOULDBLOCK) {
+      logmsg(0, LOG_ERR, "failed to accept incoming %s connection: %s",
+            l->proto, strerror(errno));
+    }
+    return;
+  }
+
+  c = xmalloc(sizeof(*c));
+  c->l = l;
+  c->q.af = l->af;
+  sockaddr_to_addr((struct sockaddr *)&ssr, &c->q.s[R].addr);
+  ssz = sizeof(ssl);
+  if (getsockname(sk, (struct sockaddr *)&ssl, &ssz)) {
+    logmsg(0, LOG_ERR,
+          "failed to read local address for incoming %s connection: %s",
+          l->proto, strerror(errno));
+    close(sk);
+    xfree(c);
+    return;
+  }
+  sockaddr_to_addr((struct sockaddr *)&ssl, &c->q.s[L].addr);
+  c->q.s[L].port = c->q.s[R].port = 0;
+
+  /* logmsg(&c->q, LOG_INFO, "accepted %s connection", l->proto); */
+
+  selbuf_init(&c->b, &sel, sk, client_line, c);
+  selbuf_setsize(&c->b, 1024);
+  c->fd = sk;
+  c->px = 0;
+  init_writebuf(&c->wb, sk, done_client_write, c);
+}
+
+static int make_listening_socket(int af, int port, const char *proto)
+{
+  int fd;
+  int o;
+  struct sockaddr_storage ss;
+  struct listen *l;
+  size_t ssz;
+
+  if ((fd = socket(af, SOCK_STREAM, 0)) < 0) {
+    die(1, "failed to create %s listening socket: %s",
+       proto, strerror(errno));
+  }
+  o = 1; setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &o, sizeof(o));
+  ss.ss_family = af;
+  switch (af) {
+    case AF_INET: {
+      struct sockaddr_in *sin = (struct sockaddr_in *)&ss;
+      sin->sin_addr.s_addr = INADDR_ANY;
+      sin->sin_port = htons(port);
+      ssz = sizeof(*sin);
+    } break;
+    case AF_INET6: {
+      struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&ss;
+      o = 1; setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &o, sizeof(o));
+      sin6->sin6_family = AF_INET6;
+      sin6->sin6_addr = in6addr_any;
+      sin6->sin6_scope_id = 0;
+      sin6->sin6_flowinfo = 0;
+      ssz = sizeof(*sin6);
+    } break;
+    default:
+      abort();
+  }
+  if (bind(fd, (struct sockaddr *)&ss, ssz))
+    die(1, "failed to bind %s listening socket: %s", proto, strerror(errno));
+  if ((o = fcntl(fd, F_GETFL)) < 0 ||
+      fcntl(fd, F_SETFL, o | O_NONBLOCK)) {
+    die(1, "failed to set %s listening socket nonblocking: %s",
+       proto, strerror(errno));
+  }
+  if (listen(fd, 5))
+    die(1, "failed to listen for %s: %s", proto, strerror(errno));
+
+  l = xmalloc(sizeof(*l));
+  l->af = af;
+  l->proto = proto;
+  sel_initfile(&sel, &l->f, fd, SEL_READ, accept_client, l);
+  sel_addfile(&l->f);
+
+  return (fd);
+}
+
+int main(int argc, char *argv[])
+{
+  int port = 113;
+  char buf[ADDRLEN];
+  union addr a;
+
+  ego(argv[0]);
+
+  fwatch_init(&polfw, "yaid.policy");
+  if (load_policy_file("yaid.policy", &policy))
+    exit(1);
+  { int i;
+    for (i = 0; i < DA_LEN(&policy); i++)
+      print_policy(&DA(&policy)[i]);
+  }
+
+  if ((randfd = open("/dev/urandom", O_RDONLY)) < 0) {
+    die(1, "failed to open `/dev/urandom' for reading: %s",
+       strerror(errno));
+  }
+
+  if (get_default_gw(AF_INET, &a))
+    printf("ipv4 gw = %s\n", inet_ntop(AF_INET, &a, buf, sizeof(buf)));
+  if (get_default_gw(AF_INET6, &a))
+    printf("ipv6 gw = %s\n", inet_ntop(AF_INET6, &a, buf, sizeof(buf)));
+
+  sel_init(&sel);
+  make_listening_socket(AF_INET, port, "IPv4");
+  make_listening_socket(AF_INET6, port, "IPv6");
+
+  for (;;)
+    if (sel_select(&sel)) die(1, "select failed: %s", strerror(errno));
+
+  return (0);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/yaid.h b/yaid.h
new file mode 100644 (file)
index 0000000..d8215da
--- /dev/null
+++ b/yaid.h
@@ -0,0 +1,214 @@
+/* -*-c-*-
+ *
+ * Common definitions for YAID
+ *
+ * (c) 2012 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of Yet Another Ident Daemon (YAID).
+ *
+ * YAID is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * YAID is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with YAID; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef YAID_H
+#define YAID_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <pwd.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+#include <syslog.h>
+
+#include <mLib/bits.h>
+#include <mLib/conn.h>
+#include <mLib/darray.h>
+#include <mLib/dstr.h>
+#include <mLib/fwatch.h>
+#include <mLib/quis.h>
+#include <mLib/report.h>
+#include <mLib/sel.h>
+#include <mLib/selbuf.h>
+
+/*----- Data structures ---------------------------------------------------*/
+
+#define ADDRLEN 64
+
+union addr {
+  struct in_addr ipv4;
+  struct in6_addr ipv6;
+};
+
+struct socket {
+  union addr addr;
+  unsigned port;
+};
+
+enum { L, R, NDIR };
+
+#define RESPONSE(_)                                                    \
+  _(ERROR, U(error, unsigned))                                         \
+  _(UID, U(uid, uid_t))                                                        \
+  _(NAT, U(nat, struct socket))
+
+#define ERROR(_)                                                       \
+  _(INVPORT, "INVALID-PORT")                                           \
+  _(NOUSER, "NO-USER")                                                 \
+  _(HIDDEN, "HIDDEN-USER")                                             \
+  _(UNKNOWN, "UNKNOWN-ERROR")
+extern const char *const errtok[];
+
+enum {
+#define DEFENUM(err, tok) E_##err,
+  ERROR(DEFENUM)
+#undef DEFENUM
+  E_LIMIT
+};
+
+enum {
+#define DEFENUM(what, branch) R_##what,
+  RESPONSE(DEFENUM)
+#undef DEFENUM
+  R_LIMIT
+};
+
+struct query {
+  int af;
+  struct socket s[NDIR];
+  unsigned resp;
+  union {
+#define DEFBRANCH(WHAT, branch) branch
+#define U(memb, ty) ty memb;
+#define N
+    RESPONSE(DEFBRANCH)
+#undef U
+#undef N
+#undef DEFBRANCH
+  } u;
+} query;
+
+enum {
+  T_OK,
+  T_EOL,
+  T_EOF,
+  T_ERROR
+};
+
+struct addrpat {
+  unsigned len;
+  union addr addr;
+};
+
+struct portpat {
+  unsigned lo, hi;
+};
+
+struct sockpat {
+  struct addrpat addr;
+  struct portpat port;
+};
+
+#define ACTIONS(_)                                                     \
+  _(USER, "user")                                                      \
+  _(TOKEN, "token")                                                    \
+  _(NAME, "name")                                                      \
+  _(DENY, "deny")                                                      \
+  _(HIDE, "hide")                                                      \
+  _(LIE, "lie")
+
+enum {
+#define DEFENUM(tag, word) A_##tag,
+  ACTIONS(DEFENUM)
+#undef DEFENUM
+  A_LIMIT
+};
+
+struct action {
+  unsigned act;
+  union {
+    unsigned user;
+    char *lie;
+  } u;
+};
+
+struct policy {
+  int af;
+  struct sockpat sp[NDIR];
+  struct action act;
+};
+#define POLICY_INIT(a) { 0, { { { 0 } } }, { a } }
+
+struct policy_file {
+  FILE *fp;
+  const struct query *q;
+  const char *name;
+  const char *what;
+  int err;
+  int lno;
+  struct policy p;
+};
+
+DA_DECL(policy_v, struct policy);
+
+/*----- Functions provided ------------------------------------------------*/
+
+void logmsg(const struct query *q, int prio, const char *msg, ...);
+
+void identify(struct query *q);
+int get_default_gw(int af, union addr *a);
+
+void init_policy(struct policy *p);
+void free_policy(struct policy *p);
+void print_policy(const struct policy *p);
+int match_policy(const struct policy *p, const struct query *q);
+int parse_policy(FILE *fp, struct policy *p);
+int open_policy_file(struct policy_file *pf, const char *name,
+                    const char *what, const struct query *q);
+int read_policy_file(struct policy_file *pf);
+void close_policy_file(struct policy_file *pf);
+int load_policy_file(const char *file, policy_v *pv);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif