chiark / gitweb /
Merge branch 'privsep'
authorMark Wooding <mdw@distorted.org.uk>
Tue, 30 Dec 2008 14:51:07 +0000 (14:51 +0000)
committerMark Wooding <mdw@distorted.org.uk>
Tue, 30 Dec 2008 14:51:07 +0000 (14:51 +0000)
* privsep:
  server: Introduce privilege separation.
  server: Zap spurious space output by a_vformat.
  server: Make a_vformat public.
  server: Set admin socket permissions to match user.
  client: Capture server stderr and send it to the logfile.
  client: Better logging infrastructure.
  client: Clean up variable declarations.
  client: New options for setting user and group identities.
  client: Function for inserting arguments.
  peer, tunnels: New file-descriptor opening interface.

Conflicts:

server/Makefile.am

1  2 
Makefile.am
client/tripectl.c
configure.ac
server/Makefile.am
server/admin.c

diff --combined Makefile.am
index 8926279034a1a5ec9cb35d7c13bd36fc6db15faa,192dcc5eebc053bc3926fbea90eb3afc107204a0..ce25e3704a9b3158aec6c51c6c44ba647b710549
@@@ -34,16 -34,12 +34,17 @@@ SUBDIRS                     
  SUBDIRS                       += common
  SUBDIRS                       += uslip
  SUBDIRS                       += client
+ SUBDIRS                       += priv
  SUBDIRS                       += server
  SUBDIRS                       += proxy
  SUBDIRS                       += pkstream
  SUBDIRS                       += init
  
 +## Path MTU discovery.
 +if PATHMTU
 +SUBDIRS                       += pathmtu
 +endif
 +
  ## Wireshark.
  if HAVE_WIRESHARK
  SUBDIRS                       += wireshark
@@@ -94,7 -90,6 +95,7 @@@ CLEANFILES            += defs.ma
  
  ## Additional build tools.
  EXTRA_DIST            += config/confsubst
 +EXTRA_DIST            += config/auto-version
  
  ###--------------------------------------------------------------------------
  ### Debian.
@@@ -109,10 -104,6 +110,10 @@@ EXTRA_DIST               += debian/copyrigh
  EXTRA_DIST            += debian/pkstream.copyright
  EXTRA_DIST            += debian/pkstream.install
  
 +## pathmtu
 +EXTRA_DIST            += debian/pathmtu.copyright
 +EXTRA_DIST            += debian/pathmtu.install
 +
  ## server and client
  EXTRA_DIST            += debian/tripe.README
  EXTRA_DIST            += debian/tripe.dirs
@@@ -132,4 -123,5 +133,4 @@@ EXTRA_DIST         += debian/tripemon.instal
  ## wireshark
  EXTRA_DIST            += debian/tripe-wireshark.install
  
 -
  ###----- That's all, folks --------------------------------------------------
diff --combined client/tripectl.c
index 661e51990c6fa981cc64851abaf63aeb7b25d6b2,0daaacbad1b7ae401f05b3e10d970387a59234cc..2df7935814c6f7211397301cc0cc00a37c16617e
@@@ -74,6 -74,7 +74,7 @@@
  
  /*----- Static variables --------------------------------------------------*/
  
+ static sel_state sel;
  static const char *pidfile = 0;
  static const char *logname = 0;
  static FILE *logfp = 0;
@@@ -119,6 -120,30 +120,30 @@@ static void checkbg(char **p
      die(EXIT_FAILURE, "unexpected background tag `%s'", q);
  }
  
+ static void dolog(int prio, const char *msg, ...)
+ {
+   va_list ap;
+   dstr d = DSTR_INIT;
+   const char *cat;
+   va_start(ap, msg);
+   dstr_vputf(&d, msg, &ap);
+   va_end(ap);
+   if (f & f_syslog) syslog(prio, "%s", d.buf);
+   if (logfp) {
+     switch (prio) {
+       case LOG_WARNING: cat = "warning"; break;
+       case LOG_DEBUG: cat = "debug"; break;
+       case LOG_ERR: cat = "error"; break;
+       default: cat = "message"; break;
+     }
+     writelog(cat, d.buf);
+   }
+   if (prio == LOG_WARNING && (f & f_warn))
+     fprintf(stderr, "Warning: %s\n", d.buf);
+   dstr_destroy(&d);
+ }
  static void checkfg(void)
    { if (bgtag) die(EXIT_FAILURE, "unexpected foreground response"); }
  
@@@ -133,28 -158,13 +158,13 @@@ static void cline(char *p, size_t len, 
    q = str_getword(&p);
    if (!q)
      return;
-   if (strcmp(q, "WARN") == 0) {
-     if (f & f_syslog)
-       syslog(LOG_WARNING, "%s", p);
-     if (logfp)
-       writelog("warning", p);
-     if (f & f_warn)
-       fprintf(stderr, "Warning: %s\n", p);
-   } else if (strcmp(q, "TRACE") == 0) {
-     if (f & f_syslog)
-       syslog(LOG_DEBUG, "%s", p);
-     if (logfp)
-       writelog("debug", p);
-   } else if (!(f & f_command)) {
-     if (f & f_syslog)
-       syslog(LOG_ERR, "unexpected output `%s %s'", q, p);
-     if (logfp) {
-       dstr d = DSTR_INIT;
-       dstr_putf(&d, "unexpected output `%s %s'", q, p);
-       writelog("error", d.buf);
-       dstr_destroy(&d);
-     }
-   } else if (strcmp(q, "FAIL") == 0) {
+   if (strcmp(q, "WARN") == 0)
+     dolog(LOG_WARNING, p);
+   else if (strcmp(q, "TRACE") == 0)
+     dolog(LOG_DEBUG, p);
+   else if (!(f & f_command))
+     dolog(LOG_ERR, "unexpected output `%s %s'", q, p);
+   else if (strcmp(q, "FAIL") == 0) {
      checkfg();
      die(EXIT_FAILURE, "%s", p);
    } else if (strcmp(q, "INFO") == 0) {
@@@ -207,6 -217,16 +217,16 @@@ static void uline(char *p, size_t len, 
    }
  }
  
+ static void eline(char *p, size_t len, void *b)
+ {
+   if (p)
+     dolog(LOG_WARNING, "(stderr): %s", p);
+   else {
+     selbuf_destroy(b);
+     close(fd);
+   }
+ }
  static void setup(const char *cmd)
  {
    dstr d = DSTR_INIT;
@@@ -263,7 -283,7 +283,7 @@@ static void logfile(const char *name
      else if (logname)
        die(EXIT_FAILURE, d.buf);
      if (f & f_syslog)
 -      syslog(LOG_ERR, d.buf);
 +      syslog(LOG_ERR, "%s", d.buf);
      dstr_destroy(&d);
    }
  }
@@@ -275,6 -295,19 +295,19 @@@ static void cleanup(void) { if (pidfile
  static void sigdie(int sig)
    { cleanup(); signal(sig, SIG_DFL); raise(sig); }
  
+ static void putarg(string_v *av, const char *fmt, ...)
+ {
+   va_list ap;
+   dstr d = DSTR_INIT;
+   va_start(ap, fmt);
+   dstr_vputf(&d, fmt, &ap);
+   dstr_putz(&d);
+   va_end(ap);
+   DA_UNSHIFT(av, xstrdup(d.buf));
+   dstr_destroy(&d);
+ }
  static void version(FILE *fp)
    { pquis(fp, "$, TrIPE version " VERSION "\n"); }
  
@@@ -305,6 -338,8 +338,8 @@@ Options in full:\n
  \n\
  -D, --daemon          Become a background task after connecting.\n\
  -d, --directory=DIR   Select current directory [default " CONFIGDIR "].\n\
+ -U, --setuid=USER     Set uid to USER after initialization.\n\
+ -G, --setgid=GROUP    Set gid to GROUP after initialization.\n\
  -a, --admin-socket=FILE       Select socket to connect to\n\
                          [default " SOCKETDIR "/tripesock].\n\
  -P, --pidfile=FILE    Write process-id to FILE.\n\
@@@ -327,6 -362,18 +362,18 @@@ int main(int argc, char *argv[]
    string_v spawnopts = DA_INIT;
    char *p;
    FILE *pidfp = 0;
+   int i;
+   size_t sz;
+   uid_t u = -1;
+   gid_t g = -1;
+   int pfd[2], efd[2];
+   pid_t kid;
+   struct sigaction sa;
+   sigset_t newmask, oldmask;
+   struct sockaddr_un sun;
+   selbuf bu, bs, be;
+   dstr d = DSTR_INIT;
+   sig hup;
  
    ego(argv[0]);
  
        { "version",    0,              0,      'v' },
        { "usage",      0,              0,      'u' },
        { "daemon",     0,              0,      'D' },
+       { "uid",                OPTF_ARGREQ,    0,      'U' },
+       { "setuid",     OPTF_ARGREQ,    0,      'U' },
+       { "gid",                OPTF_ARGREQ,    0,      'G' },
+       { "setgid",     OPTF_ARGREQ,    0,      'G' },
        { "directory",  OPTF_ARGREQ,    0,      'd' },
        { "admin-socket",       OPTF_ARGREQ,    0,      'a' },
        { "spawn",      0,              0,      's' },
        { 0,            0,              0,      0 }
      };
  
-     int i = mdwopt(argc, argv, "+hvuDd:a:sp:S:lwf:nP:", opts, 0, 0, 0);
+     i = mdwopt(argc, argv, "+hvuDU:G:d:a:sp:S:lwf:nP:", opts, 0, 0, 0);
      if (i < 0)
        break;
      switch (i) {
        case 'D':
        f |= f_daemon | f_noinput;
        break;
+       case 'U':
+       u = u_getuser(optarg, &g);
+       break;
+       case 'G':
+       g = u_getgroup(optarg);
+       break;
        case 'd':
        dir = optarg;
        break;
      die(EXIT_FAILURE, "couldn't open `%s' for writing: %s",
        pidfile, strerror(errno));
    }
+   sel_init(&sel);
+   sig_init(&sel);
    signal(SIGINT, sigdie);
    signal(SIGQUIT, sigdie);
    signal(SIGTERM, sigdie);
    /* --- Connect to the server --- */
  
    if (f & f_spawn) {
-     int pfd[2];
-     pid_t kid;
-     struct sigaction sa;
-     sigset_t newmask, oldmask;
      sa.sa_handler = reap;
      sigemptyset(&sa.sa_mask);
      sa.sa_flags = SA_NOCLDSTOP;
  #endif
      sigaction(SIGCHLD, &sa, 0);
  
-     DA_UNSHIFT(&spawnopts, (char *)sock);
-     DA_UNSHIFT(&spawnopts, "-a");
-     DA_UNSHIFT(&spawnopts, "-d.");
-     DA_UNSHIFT(&spawnopts, "-F");
-     DA_UNSHIFT(&spawnopts, (char *)spawnpath);
      DA_PUSH(&spawnopts, 0);
-     if (socketpair(PF_UNIX, SOCK_STREAM, 0, pfd))
+     if (g != (gid_t)-1) putarg(&spawnopts, "-G%lu", (unsigned long)g);
+     if (u != (uid_t)-1) putarg(&spawnopts, "-U%lu", (unsigned long)u);
+     putarg(&spawnopts, "-a%s", sock);
+     putarg(&spawnopts, "-d.");
+     putarg(&spawnopts, "-F");
+     putarg(&spawnopts, "%s", spawnpath);
+     if (socketpair(PF_UNIX, SOCK_STREAM, 0, pfd) || pipe(efd))
        die(EXIT_FAILURE, "error from socketpair: %s", strerror(errno));
      sigemptyset(&newmask);
      sigaddset(&newmask, SIGCHLD);
      if (!kid) {
        dup2(pfd[1], STDIN_FILENO);
        dup2(pfd[1], STDOUT_FILENO);
-       close(pfd[0]);
-       close(pfd[1]);
+       dup2(efd[1], STDERR_FILENO);
+       close(pfd[0]); close(pfd[1]);
+       close(efd[0]); close(efd[1]);
        if (logfp) fclose(logfp);
        if (pidfp) fclose(pidfp);
        closelog();
      }
      sigprocmask(SIG_SETMASK, &oldmask, 0);
      fd = pfd[0];
-     close(pfd[1]);
+     close(pfd[1]); close(efd[1]);
+     selbuf_init(&be, &sel, efd[0], eline, &be);
    } else {
-     struct sockaddr_un sun;
-     size_t sz = strlen(sock) + 1;
+     sz = strlen(sock) + 1;
      if (sz > sizeof(sun.sun_path))
        die(EXIT_FAILURE, "socket name `%s' too long", sock);
      memset(&sun, 0, sizeof(sun));
      }
    }
  
+   u_setugid(u, g);
    if (f & f_daemon) {
      if (daemonize())
        die(EXIT_FAILURE, "error becoming daemon: %s", strerror(errno));
    if (optind == argc)
      setup("WATCH -A+tw");
    if (!(f & f_noinput) && optind == argc) {
-     sel_state sel;
-     selbuf bu, bs;
-     sel_init(&sel);
      selbuf_init(&bu, &sel, STDIN_FILENO, uline, &bu);
      selbuf_init(&bs, &sel, fd, sline, &bs);
      for (;;) {
    /* --- If there's a command, submit it --- */
  
    if (optind < argc) {
-     dstr d = DSTR_INIT;
      setup((f & f_warn) ? "WATCH -A+w" : "WATCH -A");
      while (optind < argc)
        u_quotify(&d, argv[optind++]);
  
    /* --- Pull everything else out of the box --- */
  
-   {
-     sel_state sel;
-     selbuf b;
-     sig hup;
-     sel_init(&sel);
-     selbuf_init(&b, &sel, fd, cline, 0);
+   selbuf_init(&bs, &sel, fd, cline, 0);
  
-     if (f & f_syslog)
-       openlog(QUIS, 0, LOG_DAEMON);
-     if (logfp) {
-       sig_init(&sel);
-       sig_add(&hup, SIGHUP, sighup, 0);
-     }
-     for (;;) {
-       if (sel_select(&sel) && errno != EINTR && errno != EAGAIN)
-       die(EXIT_FAILURE, "select failed: %s", strerror(errno));
-     }
+   if (f & f_syslog)
+     openlog(QUIS, 0, LOG_DAEMON);
+   if (logfp)
+     sig_add(&hup, SIGHUP, sighup, 0);
+   for (;;) {
+     if (sel_select(&sel) && errno != EINTR && errno != EAGAIN)
+       die(EXIT_FAILURE, "select failed: %s", strerror(errno));
    }
  
    return (0);
diff --combined configure.ac
index 674116e37da66c091beb09518223100b608c387f,5b5eaa2cb94fbe791f9bf785c4ce89a7e5212764..61ed4a8fd1c1c25119d33bcd6989b15b9cc64d09
@@@ -61,6 -61,7 +61,6 @@@ PKG_CHECK_MODULES([mLib], [mLib >= 2.0.
  PKG_CHECK_MODULES([catacomb], [catacomb >= 2.1.1])
  
  CFLAGS="$CFLAGS $mLib_CFLAGS $catacomb_CFLAGS"
 -LIBS="$LIBS $mLib_LIBS"
  
  dnl--------------------------------------------------------------------------
  dnl Directories to install things into.
@@@ -95,6 -96,15 +95,15 @@@ TRIPE_DEFINE_PATH
  TRIPE_DEFINE_PATH(
    [logfile], [FILE], [logging output [[./tripe.log]]], [tripe.log])
  
+ dnl--------------------------------------------------------------------------
+ dnl Privilege-separation helper.
+ mdw_DEFINE_PATHS([
+   AC_DEFINE_UNQUOTED([PRIVSEP_HELPER],
+     ["mdw_PATH([$libexecdir])/mdw_PROG([tripe-privhelper])"],
+     [Pathname of privilege-separation helper.])
+ ])
  dnl--------------------------------------------------------------------------
  dnl Other options.
  
@@@ -106,19 -116,6 +115,19 @@@ AC_ARG_WITH([tracing]
              AC_DEFINE([NTRACE], [1], [Disable all tracing.])],
            [:])
  
 +dnl--------------------------------------------------------------------------
 +dnl Path MTU discovery.
 +
 +case $host_os in
 +  linux*)
 +    pmtu=yes
 +    ;;
 +  *)
 +    pmtu=no
 +    ;;
 +esac
 +AM_CONDITIONAL([PATHMTU], [test $pmtu = yes])
 +
  dnl--------------------------------------------------------------------------
  dnl Tunnel devices.
  
@@@ -239,7 -236,7 +248,7 @@@ esa
  
  dnl If we're still interested, find Glib.
  case "$haveshark" in
 -  yes) AM_PATH_GLIB([1.2.0], [], haveshark=false, [gmodule]) ;;
 +  yes) AM_PATH_GLIB_2_0([2.4.0], [], [haveshark=false], [gmodule]) ;;
  esac
  
  dnl Find the include directory.  This would be much easier if they just
@@@ -303,8 -300,8 +312,9 @@@ AC_CONFIG_FILES
    [Makefile]
    [common/Makefile]
    [uslip/Makefile]
 +  [pathmtu/Makefile]
    [client/Makefile]
+   [priv/Makefile]
    [server/Makefile]
    [proxy/Makefile]
    [pkstream/Makefile]
diff --combined server/Makefile.am
index 3c050a109a566ac1b22c06f332836200f0287bb4,f4fb936fabb8e3a584c1d018ecd28099d146fba3..e54b79789bdb3eb7c614c6fdf3759817c7db926e
@@@ -28,14 -28,13 +28,14 @@@ include $(top_srcdir)/vars.a
  sbin_PROGRAMS          =
  man_MANS               =
  
- LDADD                  = $(libtripe) $(catacomb_LIBS)
++LDADD                  = $(libtripe) $(libpriv) $(catacomb_LIBS)
 +
  ###--------------------------------------------------------------------------
  ### The main server.
  
  sbin_PROGRAMS         += tripe
  
  tripe_SOURCES          =
 -tripe_LDADD            = $(libpriv) $(libtripe) $(catacomb_LIBS)
  
  ## Main header file.
  tripe_SOURCES         += tripe.h
@@@ -48,6 -47,7 +48,7 @@@ tripe_SOURCES         += keyset.
  tripe_SOURCES         += keyexch.c
  tripe_SOURCES         += chal.c
  tripe_SOURCES         += peer.c
+ tripe_SOURCES         += privsep.c
  tripe_SOURCES         += admin.c
  tripe_SOURCES         += tripe.c
  
diff --combined server/admin.c
index 045727f71a9df12378786e7a2be311cbf33d154d,ccd49b7373789da37750b086c9bde70e85db6150..a62bbe2243821a077725f71c7f3d1f88eb8e7b52
@@@ -40,6 -40,7 +40,7 @@@ const trace_opt tr_opts[] = 
    { 'x',      T_KEYEXCH,      "key exchange" },
    { 'm',      T_KEYMGMT,      "key management" },
    { 'l',      T_CHAL,         "challenge management" },
+   { 'v',      T_PRIVSEP,      "privilege separation" },
    { 'p',      T_PACKET,       "packet contents" },
    { 'c',      T_CRYPTO,       "crypto details" },
    { 'A',      T_ALL,          "all of the above" },
@@@ -238,16 -239,34 +239,34 @@@ static void a_flush(int fd, unsigned mo
   *
   * Returns:   ---
   *
-  * Use:               Main message token formatting driver.
+  * Use:               Main message token formatting driver.  The arguments are
+  *            interleaved formatting tokens and their parameters, finally
+  *            terminated by an entry @A_END@.
+  *
+  *            Tokens recognized:
+  *
+  *              * "*..." ... -- pretokenized @dstr_putf@-like string
+  *
+  *              * "?ADDR" SOCKADDR -- a socket address, to be converted
+  *
+  *              * "?B64" BUFFER SIZE -- binary data to be base64-encoded
+  *
+  *              * "?TOKENS" VECTOR -- null-terminated vector of tokens
+  *
+  *              * "?PEER" PEER -- peer's name
+  *
+  *              * "?ERRNO" ERRNO -- system error code
+  *
+  *              * "[!]..." ... -- @dstr_putf@-like string as single token
   */
  
static void a_vformat(dstr *d, const char *fmt, va_list ap)
+ void a_vformat(dstr *d, const char *fmt, va_list ap)
  {
    dstr dd = DSTR_INIT;
  
    while (fmt) {
      if (*fmt == '*') {
-       dstr_putc(d, ' ');
+       if (d->len) dstr_putc(d, ' ');
        dstr_vputf(d, fmt + 1, &ap);
      } else if (*fmt == '?') {
        if (strcmp(fmt, "?ADDR") == 0) {
@@@ -503,6 -522,7 +522,7 @@@ void a_quit(void
    close(sock.fd);
    unlink(sockname);
    FOREACH_PEER(p, { p_destroy(p); });
+   ps_quit();
    exit(0);
  }
  
@@@ -1646,34 -1666,6 +1666,34 @@@ static void acmd_bgcancel(admin *a, uns
    }
  }
  
 +static void acmd_algs(admin *a, unsigned ac, char *av[])
 +{
 +  a_info(a,
 +       "kx-group=%s", gg->ops->name,
 +       "kx-group-order-bits=%lu", (unsigned long)mp_bits(gg->r),
 +       "kx-group-elt-bits=%lu", (unsigned long)gg->nbits,
 +       A_END);
 +  a_info(a,
 +       "hash=%s", algs.h->name,
 +       "mgf=%s", algs.mgf->name,
 +       "hash-sz=%lu", (unsigned long)algs.h->hashsz,
 +       A_END);
 +  a_info(a,
 +       "cipher=%s", algs.c->name,
 +       "cipher-keysz=%lu", (unsigned long)algs.cksz,
 +       "cipher-blksz=%lu", (unsigned long)algs.c->blksz,
 +       A_END);
 +  a_info(a,
 +       "cipher-data-limit=%lu", (unsigned long)algs.expsz,
 +       A_END);
 +  a_info(a,
 +       "mac=%s", algs.m->name,
 +       "mac-keysz=%lu", (unsigned long)algs.mksz,
 +       "mac-tagsz=%lu", (unsigned long)algs.tagsz,
 +       A_END);
 +  a_ok(a);
 +}
 +
  static void acmd_list(admin *a, unsigned ac, char *av[])
  {
    FOREACH_PEER(p, {  a_info(a, "%s", p_name(p), A_END); });
@@@ -1874,7 -1866,6 +1894,7 @@@ static void acmd_help(admin */*a*/, uns
  static const acmd acmdtab[] = {
    { "add",    "[OPTIONS] PEER ADDR ...", 2,   0xffff, acmd_add },
    { "addr",   "PEER",                 1,      1,      acmd_addr },
 +  { "algs",   0,                      0,      0,      acmd_algs },
    { "bgcancel",       "TAG",                  1,      1,      acmd_bgcancel },
    { "checkchal", "CHAL",              1,      1,      acmd_checkchal },
    { "daemon", 0,                      0,      0,      acmd_daemon },
@@@ -2182,13 -2173,15 +2202,15 @@@ void a_daemon(void) { flags |= F_DAEMON
  /* --- @a_init@ --- *
   *
   * Arguments: @const char *name@ = socket name to create
+  *            @uid_t u@ = user to own the socket
+  *            @gid_t g@ = group to own the socket
   *
   * Returns:   ---
   *
   * Use:               Creates the admin listening socket.
   */
  
- void a_init(const char *name)
+ void a_init(const char *name, uid_t u, gid_t g)
  {
    int fd;
    int n = 5;
@@@ -2244,6 -2237,11 +2266,11 @@@ again
      goto again;
    }
    chmod(sun.sun_path, 0600);
+   if (chown(sun.sun_path, u, g)) {
+     T( trace(T_ADMIN,
+            "admin: failed to give away socket: %s",
+            strerror(errno)); )
+   }
    fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
    if (listen(fd, 5))
      die(EXIT_FAILURE, "couldn't listen on socket: %s", strerror(errno));