chiark / gitweb /
Initial ugly non-portable core of an ident daemon.
authorMark Wooding <mdw@distorted.org.uk>
Sat, 29 Sep 2012 14:37:27 +0000 (15:37 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Sat, 29 Sep 2012 14:37:27 +0000 (15:37 +0100)
.gitignore [new file with mode: 0644]
.links [new file with mode: 0644]
.skelrc [new file with mode: 0644]
Makefile.am [new file with mode: 0644]
configure.ac [new file with mode: 0644]
ident.c [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..ab9943e
--- /dev/null
@@ -0,0 +1,6 @@
+COPYING
+Makefile.in
+aclocal.m4
+autom4te.cache
+config
+configure
diff --git a/.links b/.links
new file mode 100644 (file)
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 (file)
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 (file)
index 0000000..d716b07
--- /dev/null
@@ -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 (file)
index 0000000..88e5cc5
--- /dev/null
@@ -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 (file)
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 <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);
+}