--- /dev/null
+/* -*-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 <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;
+};
+
+/*----- 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);
+}