From 9dcdc85633401dfb5d5600b0f2c358e02692eea2 Mon Sep 17 00:00:00 2001 From: Mark Wooding Date: Sat, 29 Sep 2012 15:37:27 +0100 Subject: [PATCH 1/1] Initial ugly non-portable core of an ident daemon. --- .gitignore | 6 + .links | 2 + .skelrc | 8 ++ Makefile.am | 61 ++++++++++ configure.ac | 55 +++++++++ ident.c | 393 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 525 insertions(+) create mode 100644 .gitignore create mode 100644 .links create mode 100644 .skelrc create mode 100644 Makefile.am create mode 100644 configure.ac create mode 100644 ident.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ab9943e --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +COPYING +Makefile.in +aclocal.m4 +autom4te.cache +config +configure diff --git a/.links b/.links new file mode 100644 index 0000000..95d0804 --- /dev/null +++ b/.links @@ -0,0 +1,2 @@ +COPYING +config/auto-version diff --git a/.skelrc b/.skelrc new file mode 100644 index 0000000..7d8ef4c --- /dev/null +++ b/.skelrc @@ -0,0 +1,8 @@ +;;; -*-emacs-lisp-*- + +(setq skel-alist + (append + '((author . "Straylight/Edgeware") + (full-title . "Yet Another Ident Daemon (YAID)") + (program . "YAID")) + skel-alist)) diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..d716b07 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,61 @@ +### -*-makefile-*- +### +### Build script 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. + +EXTRA_DIST = + +CLEANFILES = +DISTCLEANFILES = +MAINTAINERCLEANFILES = + +sbin_PROGRAMS = +man_MANS = + +###-------------------------------------------------------------------------- +### The main server. + +sbin_PROGRAMS += yaid +yaid_SOURCES = +yaid_LDADD = $(mLib_LIBS) + +yaid_SOURCES += ident.c + +###-------------------------------------------------------------------------- +### Release machinery. + +dist-hook:: + echo $(VERSION) >$(distdir)/RELEASE + +EXTRA_DIST += config/auto-version + +###-------------------------------------------------------------------------- +### Debian. + +## General stuff. +##EXTRA_DIST += debian/rules +##EXTRA_DIST += debian/control +##EXTRA_DIST += debian/changelog +##EXTRA_DIST += debian/copyright + +###----- That's all, folks -------------------------------------------------- diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..88e5cc5 --- /dev/null +++ b/configure.ac @@ -0,0 +1,55 @@ +dnl -*-autoconf-*- +dnl +dnl Configuration script for YAID +dnl +dnl (c) 2012 Straylight/Edgeware +dnl + +dnl----- Licensing notice --------------------------------------------------- +dnl +dnl This file is part of Yet Another Ident Daemon (YAID). +dnl +dnl YAID is free software; you can redistribute it and/or modify +dnl it under the terms of the GNU General Public License as published by +dnl the Free Software Foundation; either version 2 of the License, or +dnl (at your option) any later version. +dnl +dnl YAID is distributed in the hope that it will be useful, +dnl but WITHOUT ANY WARRANTY; without even the implied warranty of +dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +dnl GNU General Public License for more details. +dnl +dnl You should have received a copy of the GNU General Public License +dnl along with YAID; if not, write to the Free Software Foundation, +dnl Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +dnl-------------------------------------------------------------------------- +dnl Initialization. + +mdw_AUTO_VERSION +AC_INIT([yaid], AUTO_VERSION, [mdw@distorted.org.uk]) +AC_CONFIG_SRCDIR([ident.c]) +AC_CONFIG_AUX_DIR([config]) +AM_INIT_AUTOMAKE([foreign]) +mdw_SILENT_RULES + +AC_PROG_CC +AM_PROG_CC_C_O +AX_CFLAGS_WARN_ALL + +dnl-------------------------------------------------------------------------- +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" + +dnl-------------------------------------------------------------------------- +dnl Produce output. + +AC_CONFIG_FILES([Makefile]) +AC_OUTPUT + +dnl----- That's all, folks -------------------------------------------------- diff --git a/ident.c b/ident.c new file mode 100644 index 0000000..ce4ba29 --- /dev/null +++ b/ident.c @@ -0,0 +1,393 @@ +/* -*-c-*- + * + * Discover the owner of a connection + * + * (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 +#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; +}; + +/*----- Static variables --------------------------------------------------*/ + +static const char *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); } + +static int addreq4(const union addr *a, const union addr *aa) + { return a->ipv4.s_addr == aa->ipv4.s_addr; } + +static const struct addrfamily { + int af; + const char *procfile; + int (*parseaddr)(char **pp, union addr *a); + int (*addreq)(const union addr *a, const union addr *aa); +} addrfamilytab[] = { + { AF_INET, "/proc/net/tcp", parseaddr4, addreq4 }, + { AF_INET6, "/proc/net/tcp6", /*parseaddr6*/ }, + { -1 } +}; + +/*----- Main code ---------------------------------------------------------*/ + +static void dputsock(dstr *d, int af, const struct socket *s) +{ + char buf[INET6_ADDRSTRLEN]; + + 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); +} + +static void logmsg(const struct query *q, int prio, const char *msg, ...) +{ + va_list ap; + dstr d = DSTR_INIT; + + 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); +} + +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) +{ + const struct addrfamily *af; + FILE *fp = 0; + dstr d = DSTR_INIT; + char *p, *pp; + struct socket s[4]; + int i; + unsigned fl; +#define F_SADDR 1u +#define F_SPORT 2u +#define F_DADDR 4u +#define F_DPORT 8u +#define F_ALL (F_SADDR | F_SPORT | F_DADDR | F_DPORT) +#define F_ESTAB 16u + uid_t uid; + enum { LOC, REM, ST, UID, NFIELD }; + int f, ff[NFIELD]; + + for (af = addrfamilytab; af->af >= 0; af++) + if (af->af == q->af) goto found_af; + logmsg(q, LOG_ERR, "unexpected address family `%d'", q->af); + goto err_unk; +found_af:; + + if ((fp = fopen(af->procfile, "r")) == 0) { + logmsg(q, LOG_ERR, "failed to open `%s' for reading: %s", + af->procfile, strerror(errno)); + goto err_unk; + } + +#define NEXTFIELD do { \ + for (p = pp; isspace((unsigned char)*p); p++); \ + for (pp = p; *pp && !isspace((unsigned char)*pp); pp++); \ + if (*pp) *pp++ = 0; \ +} while (0) + + if (dstr_putline(&d, fp) == EOF) { + logmsg(q, LOG_ERR, "failed to read header line from `%s': %s", + af->procfile, ferror(fp) ? strerror(errno) : "unexpected EOF"); + goto err_unk; + } + + for (i = 0; i < NFIELD; i++) ff[i] = -1; + pp = d.buf; + for (f = 0;; f++) { + NEXTFIELD; if (!*p) break; + if (strcmp(p, "local_address") == 0) + ff[LOC] = f; + else if (strcmp(p, "rem_address") == 0 || + strcmp(p, "remote_address") == 0) + ff[REM] = f; + else if (strcmp(p, "uid") == 0) + ff[UID] = f; + else if (strcmp(p, "st") == 0) + ff[ST] = f; + else if (strcmp(p, "rx_queue") == 0 || + strcmp(p, "tm->when") == 0) + f--; + } + for (i = 0; i < NFIELD; i++) { + if (ff[i] < 0) { + logmsg(q, LOG_ERR, "failed to find required fields in `%s'", + af->procfile); + goto err_unk; + } + } + + for (;;) { + DRESET(&d); + if (dstr_putline(&d, fp) == EOF) break; + pp = d.buf; + uid = -1; + for (f = 0;; f++) { + NEXTFIELD; if (!*p) break; + if (f == ff[LOC]) { i = L; goto compare; } + else if (f == ff[REM]) { i = R; goto compare; } + else if (f == ff[UID]) uid = atoi(p); + else if (f == ff[ST]) { + if (strtol(p, 0, 16) != 1) goto next_row; + } + continue; + + compare: + 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 (uid != -1) { + r->what = R_UID; + r->u.uid = uid; + goto done; + } + next_row:; + } + + if (ferror(fp)) { + logmsg(q, LOG_ERR, "failed to read connection table: %s", + strerror(errno)); + goto err_unk; + } + + if (q->af == AF_INET) { + fclose(fp); + if ((fp = fopen("/proc/net/ip_conntrack", "r")) == 0) { + if (errno == ENOENT) + goto err_nouser; + else { + logmsg(q, LOG_ERR, + "failed to open `/proc/net/ip_conntrack' for reading: %s", + strerror(errno)); + goto err_unk; + } + } + + for (;;) { + DRESET(&d); + if (dstr_putline(&d, fp) == EOF) break; + pp = d.buf; + NEXTFIELD; if (!*p) break; + if (strcmp(p, "tcp") != 0) continue; + i = 0; + fl = 0; + for (;;) { + NEXTFIELD; if (!*p) break; + if (strcmp(p, "ESTABLISHED") == 0) + fl |= F_ESTAB; + else if (strncmp(p, "src=", 4) == 0) { + inet_pton(AF_INET, p + 4, &s[i].addr); + fl |= F_SADDR; + } else if (strncmp(p, "dst=", 4) == 0) { + inet_pton(AF_INET, p + 4, &s[i + 1].addr); + fl |= F_DADDR; + } else if (strncmp(p, "sport=", 6) == 0) { + s[i].port = atoi(p + 6); + fl |= F_SPORT; + } else if (strncmp(p, "dport=", 6) == 0) { + s[i + 1].port = atoi(p + 6); + fl |= F_DPORT; + } + if ((fl & F_ALL) == F_ALL) { + fl &= ~F_ALL; + if (i < 4) i += 2; + else break; + } + } + +#ifdef notdef + { + dstr dd = DSTR_INIT; + dstr_putf(&dd, "%sestab ", (fl & F_ESTAB) ? " " : "!"); + dputsock(&dd, af->af, &s[0]); + dstr_puts(&dd, "<->"); + dputsock(&dd, af->af, &s[1]); + dstr_puts(&dd, " | "); + dputsock(&dd, af->af, &s[2]); + dstr_puts(&dd, "<->"); + dputsock(&dd, af->af, &s[3]); + printf("parsed: %s\n", dd.buf); + dstr_destroy(&dd); + } +#endif + + if (!(fl & F_ESTAB)) continue; + + for (i = 0; i < 4; i++) + if (sockeq(af, &s[i], &q->s[L])) goto found_local; + continue; + putchar('.'); + found_local: + 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]; + goto done; + } + + if (ferror(fp)) { + logmsg(q, LOG_ERR, "failed to read `/proc/net/ip_conntrack': %s", + strerror(errno)); + goto err_unk; + } + } + +#undef NEXTFIELD + +err_nouser: + r->what = R_ERROR; + r->u.error = E_NOUSER; + goto done; +err_unk: + r->what = R_ERROR; + r->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); +} -- 2.11.0