From 9da480be78586152866e0421a61994aab9adeaf3 Mon Sep 17 00:00:00 2001 From: Mark Wooding Date: Mon, 15 Oct 2012 00:21:21 +0100 Subject: [PATCH] Apparently working version, but still ugly. 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 | 2 + configure.ac | 3 +- ident.c | 255 ++++++++++------------ policy.c | 398 +++++++++++++++++++++++++++++++++ yaid.c | 703 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yaid.h | 214 ++++++++++++++++++ 6 files changed, 1430 insertions(+), 145 deletions(-) create mode 100644 policy.c create mode 100644 yaid.c create mode 100644 yaid.h diff --git a/Makefile.am b/Makefile.am index d716b07..d90a2ff 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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. diff --git a/configure.ac b/configure.ac index 88e5cc5..861627a 100644 --- a/configure.ac +++ b/configure.ac @@ -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 --- a/ident.c +++ b/ident.c @@ -26,92 +26,48 @@ /*----- Header files ------------------------------------------------------*/ -#include -#include -#include -#include -#include - -#include -#include - -#include - -#include - -#include - -/*----- 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 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 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 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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*----- 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 -- 2.11.0