Implement privilege dropping
authorMichael Tokarev <mjt@corpit.ru>
Mon, 18 May 2009 12:25:41 +0000 (16:25 +0400)
committerGuus Sliepen <guus@sliepen.eu.org>
Mon, 18 May 2009 12:34:24 +0000 (14:34 +0200)
Add two options, -R/--chroot and -U/--user=user, to chroot to the
config directory (where tinc.conf is located) and to perform
setuid to the user specified, after all the initialization is done.

What's left is handling of pid file since we can't remove it anymore.

doc/tinc.texi
doc/tincd.8.in
src/tincd.c

index ac52e7b4de2b560af4b374852bc7d0ed18346a10..02265dc5a074462e51db512a506189362161fca3 100644 (file)
@@ -1511,6 +1511,23 @@ Write PID to @var{file} instead of @file{@value{localstatedir}/run/tinc.@var{net
 Disables encryption and authentication.
 Only useful for debugging.
 
+@item -R, --chroot
+Change process root directory to the directory where the config file is
+located (@file{@value{sysconfdir}/tinc/@var{netname}/} as determined by
+-n/--net option or as given by -c/--config option), for added security.
+The chroot is performed after all the initialization is done, after
+writing pid files and opening network sockets.
+
+Note that this option alone does not do any good without -U/--user, below.
+
+Note also that tinc can't run scripts anymore (such as tinc-down or host-up),
+unless it's setup to be runnable inside chroot environment.
+
+@item -U, --user=@var{user}
+Switch to the given @var{user} after initialization, at the same time as
+chroot is performed (see --chroot above).  With this option tinc drops
+privileges, for added security.
+
 @item --help
 Display a short reminder of these runtime options and terminate.
 
index 97654f33223025df36f8b2683d2c5e5eb6ba24b7..7e168fd0c1e41d34b0ed4a7ff8185267c480ead3 100644 (file)
@@ -8,7 +8,7 @@
 .Nd tinc VPN daemon
 .Sh SYNOPSIS
 .Nm
-.Op Fl cdDkKnL
+.Op Fl cdDkKnLRU
 .Op Fl -config Ns = Ns Ar DIR
 .Op Fl -no-detach
 .Op Fl -debug Ns Op = Ns Ar LEVEL
@@ -19,6 +19,8 @@
 .Op Fl -logfile Ns Op = Ns Ar FILE
 .Op Fl -pidfile Ns = Ns Ar FILE
 .Op Fl -bypass-security
+.Op Fl -chroot
+.Op Fl -user Ns = Ns Ar USER
 .Op Fl -help
 .Op Fl -version
 .Sh DESCRIPTION
@@ -87,6 +89,14 @@ Under Windows this option will be ignored.
 .It Fl -bypass-security
 Disables encryption and authentication of the meta protocol.
 Only useful for debugging.
+.It Fl -chroot
+With this option tinc chroots into the directory where network
+config is located (@sysconfdir@/tinc/NETNAME if -n option is used,
+or to the directory specified with -c option) after initialization.
+.It Fl -user Ns = Ns Ar USER
+setuid to the specified
+.Ar USER
+after initialization.
 .It Fl -help
 Display short list of options.
 .It Fl -version
index 6fa878532e80a9e3ec2b814f02e78e501ddcdf5b..a8a0146dfc32a87813ab3d00f04a65c8e9b61ff5 100644 (file)
 
 #include LZO1X_H
 
+#ifndef HAVE_MINGW
+#include <pwd.h>
+#include <grp.h>
+#include <time.h>
+#endif
+
 #include <getopt.h>
 #include "pidfile.h"
 
@@ -73,6 +79,12 @@ bool bypass_security = false;
 /* If nonzero, disable swapping for this process. */
 bool do_mlock = false;
 
+/* If nonzero, chroot to netdir after startup. */
+static bool do_chroot = false;
+
+/* If !NULL, do setuid to given user after startup */
+static const char *switchuser = NULL;
+
 /* If nonzero, write log entries to a separate file. */
 bool use_logfile = false;
 
@@ -94,6 +106,8 @@ static struct option const long_options[] = {
        {"debug", optional_argument, NULL, 'd'},
        {"bypass-security", no_argument, NULL, 3},
        {"mlock", no_argument, NULL, 'L'},
+       {"chroot", no_argument, NULL, 'R'},
+       {"user", required_argument, NULL, 'U'},
        {"logfile", optional_argument, NULL, 4},
        {"pidfile", required_argument, NULL, 5},
        {NULL, 0, NULL, 0}
@@ -119,6 +133,8 @@ static void usage(bool status)
                                "  -L, --mlock                Lock tinc into main memory.\n"
                                "      --logfile[=FILENAME]   Write log entries to a logfile.\n"
                                "      --pidfile=FILENAME     Write PID to FILENAME.\n"
+                               "  -R, --chroot               chroot to NET dir at startup.\n"
+                               "  -U, --user=USER            setuid to given USER at startup.\n"
                                "      --help                 Display this help and exit.\n"
                                "      --version              Output version information and exit.\n\n"));
                printf(_("Report bugs to tinc@tinc-vpn.org.\n"));
@@ -130,7 +146,7 @@ static bool parse_options(int argc, char **argv)
        int r;
        int option_index = 0;
 
-       while((r = getopt_long(argc, argv, "c:DLd::k::n:K::", long_options, &option_index)) != EOF) {
+       while((r = getopt_long(argc, argv, "c:DLd::k::n:K::RU:", long_options, &option_index)) != EOF) {
                switch (r) {
                        case 0:                         /* long option */
                                break;
@@ -210,6 +226,14 @@ static bool parse_options(int argc, char **argv)
                                        generate_keys = 1024;
                                break;
 
+                       case 'R':                               /* chroot to NETNAME dir */
+                               do_chroot = true;
+                               break;
+
+                       case 'U':                               /* setuid to USER */
+                               switchuser = optarg;
+                               break;
+
                        case 1:                                 /* show help */
                                show_help = true;
                                break;
@@ -407,6 +431,51 @@ static void free_names() {
        if (confbase) free(confbase);
 }
 
+static bool drop_privs() {
+#ifdef HAVE_MINGW
+       if (switchuser) {
+               logger(LOG_ERR, _("%s not supported on this platform"), "-U");
+               return false;
+       }
+       if (do_chroot) {
+               logger(LOG_ERR, _("%s not supported on this platform"), "-R");
+               return false;
+       }
+#else
+       uid_t uid = 0;
+       if (switchuser) {
+               struct passwd *pw = getpwnam(switchuser);
+               if (!pw) {
+                       logger(LOG_ERR, _("unknown user `%s'"), switchuser);
+                       return false;
+               }
+               uid = pw->pw_uid;
+               if (initgroups(switchuser, pw->pw_gid) != 0 ||
+                   setgid(pw->pw_gid) != 0) {
+                       logger(LOG_ERR, _("%s failed"), "initgroups()");
+                       return false;
+               }
+               endgrent();
+               endpwent();
+       }
+       if (do_chroot) {
+               tzset();        /* for proper timestamps in logs */
+               if (chroot(confbase) != 0 || chdir(".") != 0) {
+                       logger(LOG_ERR, _("%s failed"), "chroot()");
+                       return false;
+               }
+               free(confbase);
+               confbase = xstrdup("");
+       }
+       if (switchuser)
+               if (setuid(uid) != 0) {
+                       logger(LOG_ERR, _("%s failed"), "setuid()");
+                       return false;
+               }
+#endif
+       return true;
+}
+
 int main(int argc, char **argv)
 {
        program_name = argv[0];
@@ -506,6 +575,10 @@ int main2(int argc, char **argv)
        if(!setup_network())
                goto end;
 
+       /* drop privileges */
+       if (!drop_privs())
+               goto end;
+
        /* Initiate all outgoing connections. */
 
        try_outgoing_connections();
@@ -536,6 +609,6 @@ end:
 
        exit_configuration(&config_tree);
        free_names();
-       
+
        return status;
 }