From 89df44971574415bd51f4eb01f8e085fb6266dc1 Mon Sep 17 00:00:00 2001 From: Klaus Schmidinger Date: Sat, 31 Dec 2005 13:30:11 +0100 Subject: [PATCH] When started as user 'root' VDR now switches to a lesser privileged user id, keeping the capability to set the system time --- CONTRIBUTORS | 1 + HISTORY | 9 +++++- INSTALL | 13 ++++++++ Makefile | 4 +-- runvdr | 8 ++--- vdr.1 | 9 +++++- vdr.c | 91 +++++++++++++++++++++++++++++++++++++++++++++++++--- 7 files changed, 122 insertions(+), 13 deletions(-) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 2e977d5e..b9b2e321 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -824,6 +824,7 @@ Ludwig Nussel cThread::Start() for removing the LOCK_THREAD from the LIRC thread for making the Makefile patch friendlier + for a patch that was used for implementing setting the user id Thomas Koch for his support in keeping the Premiere World channels up to date in 'channels.conf' diff --git a/HISTORY b/HISTORY index 1ee135d3..82ce33e0 100644 --- a/HISTORY +++ b/HISTORY @@ -3963,7 +3963,7 @@ Video Disk Recorder Revision History commands may now be executed at any time, and the message will be displayed (no more "pending message"). -2005-12-30: Version 1.3.38 +2005-12-31: Version 1.3.38 - Fixed handling second audio and Dolby Digital PIDs for encrypted channels (was broken in version 1.3.37). @@ -4040,3 +4040,10 @@ Video Disk Recorder Revision History - Updated the Greek OSD texts (thanks to Dimitrios Dimitrakos). - Changed all "illegal" to "invalid" in error messages (there's nothing "illegal" in VDR ;-). +- When started as user 'root' VDR now switches to a lesser privileged user id, + keeping the capability to set the system time (based on a patch from Ludwig + Nussel). By default the user id 'vdr' is used, which can be changed through + the new command line option '-u'. Note that for security reasons VDR will no + longer run as user 'root' (unless you explicitly start it with '-u root', + but this is not recommended!). The 'runvdr' script has been changed to + use the '-u' option. diff --git a/INSTALL b/INSTALL index 540b45f2..8290c259 100644 --- a/INSTALL +++ b/INSTALL @@ -132,6 +132,19 @@ call to the VDR program, be sure to NOT use the '-d' option! Otherwise VDR will go into 'deamon' mode and the initial program call will return immediately! +Setting the system time: +------------------------ + +If you want VDR to set the system time according to the data received +from the transponder, you need to start VDR as user 'root'. VDR will +then only keep the capability to set the system time, and set its +user id to a lesser privileged one ('vdr' by default, can be set +to a different value with the '-u' option). +You also need to enable the "EPG/Set system time" option in VDR's +Setup menu, and select a transponder from which you want to receive +the time in "Use time from transponder". Make sure you select a transponder +that has a reliable clock - some transponders are quite off. + Automatic shutdown: ------------------- diff --git a/Makefile b/Makefile index b268a788..5589ce18 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ # See the main source file 'vdr.c' for copyright information and # how to reach the author. # -# $Id: Makefile 1.79 2005/09/02 14:23:38 kls Exp $ +# $Id: Makefile 1.80 2005/12/31 10:14:33 kls Exp $ .DELETE_ON_ERROR: @@ -27,7 +27,7 @@ endif LSIDIR = ./libsi MANDIR = /usr/local/man BINDIR = /usr/local/bin -LIBS = -ljpeg -lpthread -ldl +LIBS = -ljpeg -lpthread -ldl -lcap INCLUDES = PLUGINDIR= ./PLUGINS diff --git a/runvdr b/runvdr index 76fd5f08..7fd4d819 100755 --- a/runvdr +++ b/runvdr @@ -7,7 +7,7 @@ # # Set the environment variable VDRUSR to the user id you # want VDR to run with. If VDRUSR is not set, VDR will run -# as 'root', which is not necessarily advisable. +# as user 'vdr'. # # Since this script loads the DVB driver, it must be started # as user 'root'. @@ -18,11 +18,11 @@ # See the main source file 'vdr.c' for copyright information and # how to reach the author. # -# $Id: runvdr 1.14 2004/11/21 11:30:00 kls Exp $ +# $Id: runvdr 1.15 2005/12/31 13:30:11 kls Exp $ DVBDIR="../DVB/driver" VDRPRG="./vdr" -VDRCMD="$VDRPRG -w 60 $*" +VDRCMD="$VDRPRG -u $VDRUSR -w 60 $*" LSMOD="`/sbin/lsmod | grep -w '^dvb' | wc -l`" KILL="/usr/bin/killall -q -TERM" @@ -33,7 +33,7 @@ if [ $LSMOD -eq 0 ] ; then fi while (true) do - su $VDRUSR -c "$VDRCMD" + $VDRCMD if test $? -eq 0 -o $? -eq 2; then exit; fi date echo "restarting VDR" diff --git a/vdr.1 b/vdr.1 index 54deb51d..43c15dc9 100644 --- a/vdr.1 +++ b/vdr.1 @@ -8,7 +8,7 @@ .\" License as specified in the file COPYING that comes with the .\" vdr distribution. .\" -.\" $Id: vdr.1 1.16 2005/12/30 15:09:01 kls Exp $ +.\" $Id: vdr.1 1.17 2005/12/31 12:55:16 kls Exp $ .\" .TH vdr 1 "19 Dec 2004" "1.3.18" "Video Disk Recorder" .SH NAME @@ -128,6 +128,13 @@ Call \fIcmd\fR to shutdown the computer. .BI \-t\ tty ,\ \-\-terminal= tty Set the controlling terminal. .TP +.BI \-u\ user ,\ \-\-user= user +Run as user \fIuser\fR in case vdr was started as user 'root'. +Starting vdr as 'root' is necessary if the system time shall +be set from the transponder data, but for security reasons +during normal operation vdr switches to a lesser privileged +user id. By default the user 'vdr' is used. +.TP .BI \-v\ dir ,\ \-\-video= dir Use \fIdir\fR as video directory. The default is \fI/video\fR. diff --git a/vdr.c b/vdr.c index 0d22f257..cb4ca566 100644 --- a/vdr.c +++ b/vdr.c @@ -22,13 +22,17 @@ * * The project's page is at http://www.cadsoft.de/vdr * - * $Id: vdr.c 1.223 2005/12/30 15:07:47 kls Exp $ + * $Id: vdr.c 1.224 2005/12/31 13:30:11 kls Exp $ */ #include +#include #include +#include #include #include +#include +#include #include #include #include "audio.h" @@ -72,6 +76,57 @@ static int Interrupted = 0; +static bool SetUser(const char *UserName) +{ + if (UserName) { + struct passwd *user = getpwnam(UserName); + if (!user) { + fprintf(stderr, "vdr: unknown user: '%s'\n", UserName); + return false; + } + if (setgid(user->pw_gid) < 0) { + fprintf(stderr, "vdr: cannot set group id %u: %s\n", (unsigned int)user->pw_gid, strerror(errno)); + return false; + } + if (initgroups(user->pw_name, user->pw_gid) < 0) { + fprintf(stderr, "vdr: cannot set supplemental group ids for user %s: %s\n", user->pw_name, strerror(errno)); + return false; + } + if (setuid(user->pw_uid) < 0) { + fprintf(stderr, "vdr: cannot set user id %u: %s\n", (unsigned int)user->pw_uid, strerror(errno)); + return false; + } + } + return true; +} + +static bool SetCapSysTime(void) +{ + // drop all capabilities except cap_sys_time + cap_t caps = cap_from_text("= cap_sys_time=ep"); + if (!caps) { + fprintf(stderr, "vdr: cap_from_text failed: %s\n", strerror(errno)); + return false; + } + if (cap_set_proc(caps) == -1) { + fprintf(stderr, "vdr: cap_set_proc failed: %s\n", strerror(errno)); + cap_free(caps); + return false; + } + cap_free(caps); + return true; +} + +static bool SetKeepCaps(bool On) +{ + // set keeping capabilities during setuid() on/off + if (prctl(PR_SET_KEEPCAPS, On ? 1 : 0, 0, 0, 0) != 0) { + fprintf(stderr, "vdr: prctl failed\n"); + return false; + } + return true; +} + static void SignalHandler(int signum) { if (signum != SIGPIPE) { @@ -102,11 +157,14 @@ int main(int argc, char *argv[]) // Command line options: +#define DEFAULTVDRUSER "vdr" #define DEFAULTSVDRPPORT 2001 #define DEFAULTWATCHDOG 0 // seconds #define DEFAULTPLUGINDIR PLUGINDIR #define DEFAULTEPGDATAFILENAME "epg.data" + bool StartedAsRoot = false; + const char *VdrUser = DEFAULTVDRUSER; int SVDRPport = DEFAULTSVDRPPORT; const char *AudioCommand = NULL; const char *ConfigDirectory = NULL; @@ -157,6 +215,7 @@ int main(int argc, char *argv[]) { "record", required_argument, NULL, 'r' }, { "shutdown", required_argument, NULL, 's' }, { "terminal", required_argument, NULL, 't' }, + { "user", required_argument, NULL, 'u' }, { "version", no_argument, NULL, 'V' }, { "vfat", no_argument, NULL, 'v' | 0x100 }, { "video", required_argument, NULL, 'v' }, @@ -165,7 +224,7 @@ int main(int argc, char *argv[]) }; int c; - while ((c = getopt_long(argc, argv, "a:c:dD:E:g:hl:L:mp:P:r:s:t:v:Vw:", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "a:c:dD:E:g:hl:L:mp:P:r:s:t:u:v:Vw:", long_options, NULL)) != -1) { switch (c) { case 'a': AudioCommand = optarg; break; @@ -251,6 +310,9 @@ int main(int argc, char *argv[]) return 2; } break; + case 'u': if (*optarg) + VdrUser = optarg; + break; case 'V': DisplayVersion = true; break; case 'v' | 0x100: @@ -273,6 +335,20 @@ int main(int argc, char *argv[]) } } + // Set user id in case we were started as root: + + if (getuid() == 0) { + StartedAsRoot = true; + if (!SetKeepCaps(true)) + return 2; + if (!SetUser(VdrUser)) + return 2; + if (!SetKeepCaps(false)) + return 2; + if (!SetCapSysTime()) + return 2; + } + // Help and version info: if (DisplayHelp || DisplayVersion) { @@ -288,12 +364,12 @@ int main(int argc, char *argv[]) " -D NUM, --device=NUM use only the given DVB device (NUM = 0, 1, 2...)\n" " there may be several -D options (default: all DVB\n" " devices will be used)\n" - " -E FILE --epgfile=FILE write the EPG data into the given FILE (default is\n" + " -E FILE, --epgfile=FILE write the EPG data into the given FILE (default is\n" " '%s' in the video directory)\n" " '-E-' disables this\n" " if FILE is a directory, the default EPG file will be\n" " created in that directory\n" - " -g DIR --grab=DIR write images from the SVDRP command GRAB into the\n" + " -g DIR, --grab=DIR write images from the SVDRP command GRAB into the\n" " given DIR; DIR must be the full path name of an\n" " existing directory, without any \"..\", double '/'\n" " or symlinks (default: none, same as -g-)\n" @@ -316,6 +392,8 @@ int main(int argc, char *argv[]) " -r CMD, --record=CMD call CMD before and after a recording\n" " -s CMD, --shutdown=CMD call CMD to shutdown the computer\n" " -t TTY, --terminal=TTY controlling tty\n" + " -u USER, --user=USER run as user USER (default: %s); only applicable\n" + " if started as root\n" " -v DIR, --video=DIR use DIR as video directory (default: %s)\n" " -V, --version print version information and exit\n" " --vfat encode special characters in recording names to\n" @@ -328,6 +406,7 @@ int main(int argc, char *argv[]) LIRC_DEVICE, DEFAULTSVDRPPORT, RCU_DEVICE, + DEFAULTVDRUSER, VideoDirectory, DEFAULTWATCHDOG ); @@ -378,7 +457,7 @@ int main(int argc, char *argv[]) if (DaemonMode) { if (daemon(1, 0) == -1) { - fprintf(stderr, "%m\n"); + fprintf(stderr, "vdr: %m\n"); esyslog("ERROR: %m"); return 2; } @@ -392,6 +471,8 @@ int main(int argc, char *argv[]) } isyslog("VDR version %s started", VDRVERSION); + if (StartedAsRoot) + isyslog("switched to user '%s'", VdrUser); // Main program loop variables - need to be here to have them initialized before any EXIT():