server: Introduce privilege separation.
[tripe] / server / privsep.c
diff --git a/server/privsep.c b/server/privsep.c
new file mode 100644 (file)
index 0000000..5bc2b89
--- /dev/null
@@ -0,0 +1,215 @@
+/* -*-c-*-
+ *
+ * Privilege separation communication protocol
+ *
+ * (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 "tripe.h"
+#include "priv.h"
+
+/*----- Static variables --------------------------------------------------*/
+
+static pid_t kid = -1;
+
+/*----- Fetching a tunnel file descriptor ---------------------------------*/
+
+/* --- @ps_tunfd@ --- *
+ *
+ * Arguments:  @const tunnel_ops *tops@ = pointer to tunnel operations
+ *             @char **ifn@ = where to put the interface name
+ *
+ * Returns:    The file descriptor, or @-1@ on error.
+ *
+ * Use:                Fetches a file descriptor for a tunnel driver.
+ */
+
+int ps_tunfd(const tunnel_ops *tops, char **ifn)
+{
+  unsigned code;
+  ssize_t n;
+  dstr d = DSTR_INIT;
+  int fd;
+
+  if (pc_fd == -1) {
+    a_warn("PRIVSEP", "helper-died", A_END);
+    return (-1);
+  }
+  T( trace(T_PRIVSEP,
+          "privsep: requesting descriptor for %s tunnel",
+          tops->name); )
+  if (pc_putuint(PS_TUNRQ) || pc_putstring(tops->name)) {
+    a_warn("PRIVSEP", "helper-write-error", "?ERRNO", A_END);
+    goto lose;
+  }
+  for (;;) {
+    n = fdpass_recv(pc_fd, &fd, &code, sizeof(code));
+    if (n < 0) goto readlose;
+    if (n < sizeof(code)) {
+      a_warn("PRIVSEP", "helper-short-read", A_END);
+      goto lose;
+    }
+    switch (code) {
+      case PS_TUNFD:
+       if (fd == -1) {
+         a_warn("PRIVSEP", "no-fd-from-helper", A_END);
+         goto lose;
+       }
+       if (pc_getstring(&d)) { close(fd); goto readlose; }
+       *ifn = xstrdup(d.buf);
+       T( trace(T_PRIVSEP,
+                "privsep: received winning descriptor for %s",
+                *ifn); )
+       goto done;
+      case PS_TUNERR:
+       if (pc_geterr(&errno)) goto readlose;
+       T( trace(T_PRIVSEP, "privsep: helper lost: %s", strerror(errno)); )
+       fd = -1;
+       goto done;
+#ifndef NTRACE
+      case PS_TRACE:
+       if (pc_getuint(&code) || pc_getstring(&d)) goto readlose;
+       trace(code, "%s", d.buf);
+       DRESET(&d);
+       break;
+#endif
+      case PS_WARN:
+       if (pc_getstring(&d)) goto readlose;
+       a_warn("*%s", d.buf, A_END);
+       DRESET(&d);
+       break;
+      default:
+       a_warn("PRIVSEP", "unknown-response-code", "%u", code, A_END);
+       goto lose;
+    }
+  }
+done:
+  dstr_destroy(&d);
+  return (fd);
+
+readlose:
+  a_warn("PRIVSEP", "helper-read-error", "?ERRNO", A_END);
+lose:
+  dstr_destroy(&d);
+  close(pc_fd);
+  pc_fd = -1;
+  return (-1);
+}
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @reap@ --- *
+ *
+ * Arguments:  @int sig@ = signal number (always @SIGCHLD@; ignored)
+ *
+ * Returns:    ---
+ *
+ * Use:                Notices and reports child process death.
+ */
+
+static void reap(int sig)
+{
+  pid_t k;
+  int st;
+
+  for (;;) {
+    k = waitpid(-1, &st, WNOHANG);
+    if (k < 0) {
+      switch (errno) {
+       case EINTR:
+         break;
+       default:
+         a_warn("SERVER", "waitpid-error", "?ERRNO", A_END);
+       case ECHILD:
+         return;
+      }
+    }
+    if (!k)
+      return;
+    if (k == kid) {
+      if (WIFEXITED(st))
+       a_warn("PRIVSEP", "child-exited", "%d", WEXITSTATUS(st), A_END);
+      else if (WIFSIGNALED(st))
+       a_warn("PRIVSEP", "child-killed", "%d", WTERMSIG(st), A_END);
+      else
+       a_warn("PRIVSEP", "child-died", "%d", st, A_END);
+      kid = -1;
+    }
+  }
+}
+
+/* --- @ps_split@ --- *
+ *
+ * Arguments:  @int detachp@ = whether to detach the child from its terminal
+ *
+ * Returns:    ---
+ *
+ * Use:                Separates off the privileged tunnel-opening service from the
+ *             rest of the server.
+ */
+
+void ps_split(int detachp)
+{
+  pid_t kid;
+  int fd[2];
+  const char *helper;
+
+  if (socketpair(PF_UNIX, SOCK_STREAM, 0, fd)) {
+    die(EXIT_FAILURE,
+       "failed to create socket pair for privilege separation: %s",
+       strerror(errno));
+  }
+  helper = getenv("TRIPE_PRIVHELPER");
+  if (!helper) helper = PRIVSEP_HELPER;
+  fdflags(fd[0], 0, 0, FD_CLOEXEC, FD_CLOEXEC);
+  fdflags(fd[1], 0, 0, FD_CLOEXEC, FD_CLOEXEC);
+  signal(SIGCHLD, reap);
+  kid = fork();
+  if (kid == 0) {
+    signal(SIGCHLD, SIG_DFL);
+    if (detachp) detachtty();
+    if (dup2(fd[0], 0) < 0) goto lose;
+    close(fd[0]); close(fd[1]);
+    execl(helper, helper, (char *)0);
+  lose:
+    fprintf(stderr, "helper: failed to run helper: %s\n", strerror(errno));
+    _exit(127);
+  }
+  T( trace(T_PRIVSEP, "privsep: forked child successfully"); )
+  close(fd[0]);
+  pc_fd = fd[1];
+}
+
+/* --- @ps_quit@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Detaches from the helper process.
+ */
+
+void ps_quit(void) { if (pc_fd != -1) close(pc_fd); }
+
+/*----- That's all, folks -------------------------------------------------*/