server: Introduce privilege separation.
[tripe] / priv / helper.c
diff --git a/priv/helper.c b/priv/helper.c
new file mode 100644 (file)
index 0000000..c1401f1
--- /dev/null
@@ -0,0 +1,307 @@
+/* -*-c-*-
+ *
+ * Privilege-separated helper
+ *
+ * (c) 2008 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of Trivial IP Encryption (TrIPE).
+ *
+ * TrIPE 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.
+ *
+ * TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "priv.h"
+
+/*----- Helper-side utilities ---------------------------------------------*/
+
+/* --- @lose@ --- *
+ *
+ * Arguments:  @const char *excuse@ = what went wrong
+ *
+ * Returns:    Doesn't.
+ *
+ * Use:                Reports a fatal error and quits.
+ */
+
+static void lose(const char *excuse)
+{
+  moan("helper process bailing out: %s; error: %s",
+       excuse,
+       errno == -1 ? "Unexpected EOF" : strerror(errno));
+  _exit(127);
+}
+
+/*----- Diagnostic functions ----------------------------------------------*/
+
+/* --- @trace@ --- *
+ *
+ * Arguments:  @unsigned mask@ = trace mask to check
+ *             @const char *fmt@ = message format
+ *             @...@ = values for placeholders
+ *
+ * Returns:    ---
+ *
+ * Use:                Writes a trace message.
+ */
+
+#ifndef NTRACE
+
+static void itrace(unsigned mask, const char *fmt, ...)
+{
+  va_list ap;
+  dstr d = DSTR_INIT;
+
+  va_start(ap, fmt);
+  dstr_vputf(&d, fmt, &ap);
+  if (pc_putuint(PS_TRACE) ||
+      pc_putuint(mask) ||
+      pc_putsz(d.len) ||
+      pc_put(d.buf, d.len))
+    lose("write (trace)");
+  va_end(ap);
+  dstr_destroy(&d);
+}
+
+#endif
+
+/* --- @warn@ --- *
+ *
+ * Arguments:  @const char *fmt@ = message format
+ *             @...@ = values for placeholders
+ *
+ * Returns:    ---
+ *
+ * Use:                Writes a warning message.
+ */
+
+#define A_END ((char *)0)
+
+static void warn(const char *fmt, ...)
+{
+  va_list ap;
+  dstr d = DSTR_INIT, dd = DSTR_INIT;
+
+  va_start(ap, fmt);
+  while (fmt) {
+    if (*fmt == '?') {
+      if (strcmp(fmt, "?ERRNO") == 0) {
+       dstr_putf(&d, " E%d", errno);
+       u_quotify(&d, strerror(errno));
+      } else
+       abort();
+    } else {
+      DRESET(&dd);
+      dstr_vputf(&dd, fmt, &ap);
+      u_quotify(&d, dd.buf);
+    }
+    fmt = va_arg(ap, const char *);
+  }
+  va_end(ap);
+
+  if (pc_putuint(PS_WARN) ||
+      pc_putsz(d.len) ||
+      pc_put(d.buf, d.len))
+    lose("write (warn)");
+
+  dstr_destroy(&d);
+  dstr_destroy(&dd);
+}
+
+/*----- Tunnel drivers ----------------------------------------------------*/
+
+/* --- @topen_DRIVER@ --- *
+ *
+ * Arguments:  @char **ifn@ = where to put the interface name
+ *
+ * Returns:    A file descriptor, or @-1@ on failure.
+ *
+ * Use:                Opens a tunnel device.
+ */
+
+#ifdef TUN_LINUX
+
+#include <sys/ioctl.h>
+#include <linux/if.h>
+#include <linux/if_tun.h>
+
+static int topen_linux(char **ifn)
+{
+  int fd;
+  struct ifreq iff;
+
+  if ((fd = open("/dev/net/tun", O_RDWR)) < 0) {
+    warn("TUN", "-", "linux",
+        "open-error", "/dev/net/tun", "?ERRNO",
+        A_END);
+    return (-1);
+  }
+  memset(&iff, 0, sizeof(iff));
+  iff.ifr_name[0] = 0;
+  iff.ifr_flags = IFF_TUN | IFF_NO_PI;
+  if (ioctl(fd, TUNSETIFF, &iff) < 0) {
+    warn("TUN", "-", "linux", "config-error", "?ERRNO", A_END);
+    close(fd);
+    return (-1);
+  }
+  iff.ifr_name[IFNAMSIZ - 1] = 0;
+  *ifn = xstrdup(iff.ifr_name);
+  return (fd);
+}
+
+#endif
+
+#ifdef TUN_BSD
+
+static int topen_bsd(char **ifn)
+{
+  int fd;
+  unsigned n;
+  char buf[16];
+
+  n = 0;
+  for (;;) {
+    sprintf(buf, "/dev/tun%u", n);
+    if ((fd = open(buf, O_RDWR)) >= 0)
+      break;
+    switch (errno) {
+      case EBUSY:
+       T( itrace(T_PRIVSEP, "tunnel device %u busy: skipping", n); )
+       break;
+      case ENOENT:
+       warn("TUN", "-", "bsd", "no-tunnel-devices", A_END);
+       return (-1);
+      default:
+       warn("TUN", "-", "open-error", "%s", buf, "?ERRNO", A_END);
+       break;
+    }
+    n++;
+  }
+  return (fd);
+}
+
+#endif
+
+#ifdef TUN_UNET
+
+#include <sys/ioctl.h>
+#include <linux/if.h>
+#include <unet.h>
+
+static int topen_unet(char **ifn)
+{
+  int fd;
+  int f;
+  struct unet_info uni;
+
+  if ((fd = open("/dev/unet", O_RDWR)) < 0) {
+    warn("TUN", "-", "unet", "open-error", "/dev/unet", "?ERRNO", A_END);
+    goto fail_0;
+  }
+  if ((f = ioctl(fd, UNIOCGIFFLAGS)) < 0 ||
+      ioctl(fd, UNIOCSIFFLAGS, f | IFF_POINTOPOINT)) {
+    warn("TUN", "-", "unet", "config-error", "?ERRNO", A_END);
+    goto fail_1;
+  }
+  if (ioctl(fd, UNIOCGINFO, &uni)) {
+    warn("TUN", "-", "unet", "getinfo-error", "?ERRNO", A_END);
+    goto fail_1;
+  }
+  *ifn = xstrdup(uni.uni_ifname);
+  return (fd);
+
+fail_1:
+  close(fd);
+fail_0:
+  return (-1);
+}
+
+#endif
+
+static const struct tunnel {
+  const char *name;
+  int (*open)(char **);
+} tunnels[] = {
+#ifdef TUN_LINUX
+  { "linux", topen_linux },
+#endif
+#ifdef TUN_BSD
+  { "bsd", topen_bsd },
+#endif
+#ifdef TUN_UNET
+  { "unet", topen_unet },
+#endif
+  { 0, 0 }
+};
+
+/*----- Helper process core -----------------------------------------------*/
+
+int main(int argc, char *argv[])
+{
+  struct sockaddr_un sun;
+  socklen_t slen = sizeof(sun);
+  unsigned rq;
+  dstr d = DSTR_INIT;
+  const struct tunnel *t;
+  char *ifn = 0;
+  int fd;
+  ssize_t n;
+
+  ego(argv[0]);
+  if (argc != 1 ||
+      getpeername(0, (struct sockaddr *)&sun, &slen) ||
+      sun.sun_family != AF_UNIX)
+    die(EXIT_FAILURE, "please do not run this program again.");
+
+  for (;;) {
+    if (pc_getuint(&rq)) {
+      if (errno == -1) break;
+      else lose("read (main)");
+    }
+    switch (rq) {
+      case PS_TUNRQ:
+       DRESET(&d);
+       if (pc_getstring(&d)) lose("read (tunnel)");
+       for (t = tunnels;; t++) {
+         if (!t->name) lose("unknown tunnel");
+         if (strcmp(d.buf, t->name) == 0) break;
+       }
+       T( itrace(T_PRIVSEP,
+                   "privsep: received request for %s tunnel",
+                   t->name); )
+       if ((fd = t->open(&ifn)) < 0)
+         goto err;
+       rq = PS_TUNFD;
+       n = fdpass_send(pc_fd, fd, &rq, sizeof(rq)); close(fd);
+       if (n < 0) { xfree(ifn); goto err; }
+       else if (n < sizeof(rq)) lose("partial write (fd-pass)");
+       if (pc_putstring(ifn)) lose("write (ifname)");
+       xfree(ifn);
+       break;
+      err:
+       if (pc_putuint(PS_TUNERR) || pc_puterr(errno)) lose("write (error)");
+       break;
+      default:
+       lose("bad request");
+       break;
+    }
+  }
+  _exit(0);
+}
+
+/*----- That's all, folks -------------------------------------------------*/