When started as user 'root' VDR now switches to a lesser privileged user id, keeping the capability to set the system time

This commit is contained in:
Klaus Schmidinger 2005-12-31 13:30:11 +01:00
parent c65133979f
commit 89df449715
7 changed files with 122 additions and 13 deletions

View File

@ -824,6 +824,7 @@ Ludwig Nussel <ludwig.nussel@web.de>
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 <tom@harhar.net>
for his support in keeping the Premiere World channels up to date in 'channels.conf'

View File

@ -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.

13
INSTALL
View File

@ -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:
-------------------

View File

@ -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

8
runvdr
View File

@ -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"

9
vdr.1
View File

@ -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.

91
vdr.c
View File

@ -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 <getopt.h>
#include <grp.h>
#include <locale.h>
#include <pwd.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/capability.h>
#include <sys/prctl.h>
#include <termios.h>
#include <unistd.h>
#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():