yaid_SOURCES =
yaid_LDADD = $(mLib_LIBS)
+yaid_SOURCES += yaid.c
yaid_SOURCES += ident.c
+yaid_SOURCES += policy.c
###--------------------------------------------------------------------------
### Release machinery.
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
AC_CHECK_HEADERS([stdarg.h])
AC_SEARCH_LIBS([socket], [socket])
+
PKG_CHECK_MODULES([mLib], [mLib >= 2.1.0])
AM_CFLAGS="$AM_CFLAGS $mLib_CFLAGS"
/*----- 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;
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;
char *p, *pp;
struct socket s[4];
int i;
+ int gwp = 0;
unsigned fl;
#define F_SADDR 1u
#define F_SPORT 2u
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));
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:;
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;
}
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);
-}
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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