chiark / gitweb /
Makefile.am, configure.ac, ident.c -> linux.c: System specifics.
[yaid] / linux.c
diff --git a/linux.c b/linux.c
new file mode 100644 (file)
index 0000000..91c03aa
--- /dev/null
+++ b/linux.c
@@ -0,0 +1,360 @@
+/* -*-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 "yaid.h"
+
+/*----- Static variables --------------------------------------------------*/
+
+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, 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 (*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, addreq6 },
+  { -1 }
+};
+
+/*----- Main code ---------------------------------------------------------*/
+
+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); }
+
+static int get_default_gw(int af, union addr *a)
+{
+  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));
+
+  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;
+
+      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;
+       }
+      }
+    }
+  }
+
+done:
+  close(fd);
+  return (rc);
+}
+
+void identify(struct query *q)
+{
+  const struct addrfamily *af;
+  FILE *fp = 0;
+  dstr d = DSTR_INIT;
+  char *p, *pp;
+  struct socket s[4];
+  int i;
+  int gwp = 0;
+  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 (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));
+    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);
+      if (!sockeq(af, &q->s[i], &s[0]) &&
+         (i != R || !gwp || q->s[R].port != s[0].port))
+       goto next_row;
+    }
+    if (uid != -1) {
+      q->resp = R_UID;
+      q->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;
+      q->resp = R_NAT;
+      q->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;
+    }
+    logmsg(q, LOG_ERR, "connection not found");
+  }
+
+#undef NEXTFIELD
+
+err_nouser:
+  q->resp = R_ERROR;
+  q->u.error = E_NOUSER;
+  goto done;
+err_unk:
+  q->resp = R_ERROR;
+  q->u.error = E_UNKNOWN;
+done:
+  dstr_destroy(&d);
+}
+
+/*----- That's all, folks -------------------------------------------------*/