diff --git a/doc/tinc.texi b/doc/tinc.texi index ac52e7b4..02265dc5 100644 --- a/doc/tinc.texi +++ b/doc/tinc.texi @@ -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. diff --git a/doc/tincd.8.in b/doc/tincd.8.in index 97654f33..7e168fd0 100644 --- a/doc/tincd.8.in +++ b/doc/tincd.8.in @@ -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 diff --git a/src/tincd.c b/src/tincd.c index 6fa87853..a8a0146d 100644 --- a/src/tincd.c +++ b/src/tincd.c @@ -39,6 +39,12 @@ #include LZO1X_H +#ifndef HAVE_MINGW +#include +#include +#include +#endif + #include #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; }