* Klaus Schmidinger schrieb am 30.12.05, um 18:19 Uhr:
> >I've updated the switch user patch. The user to switch to is a build
> >time define now to prevent vdr vom accidently running under the
> >wrong uid. Patches for 1.2.6 and 1.3.17 attached.
>
> I'm just looking through this for the next VDR version.
Thats really nice to hear.
> >Index: vdr-1.3.17/Makefile
> >===================================================================
> >--- vdr-1.3.17.orig/Makefile
> >+++ vdr-1.3.17/Makefile
> >@@ -73,7 +73,18 @@ DEFINES += -DPLUGINDIR=\"$(PLUGINLIBDIR)
> >
> > ifdef VFAT
> > # for people who want their video directory on a VFAT partition
> >-DEFINES += -DVFAT
> >+DEFINES += -DVFALDT
>
> What's the meaning of this?
> I can't see any place where VFALDT is actually used.
For me this looks like a typo by accident. ;-)
> >+ifdef WITH_CAPABILITIES
> >+DEFINES += -DWITH_CAPABILITIES
> >+LIBS += -lcap
> >+endif
>
> I assume this means there are systems that don't provide this.
> Is there a runtime method to determine the presence of this?
Well, you need libcap to be able to use this patch.
> >+#ifdef VDR_USER
> >+# ifndef VDR_GROUP
> >+# define VDR_GROUP NULL
> >+# endif
> >+
> >+ if(set_keepcaps() != 0)
> >+ return 2;
> >+
> >+ if (su(VDR_USER, VDR_GROUP) != 0)
> >+ return 2;
> >+
> >+ if(set_nokeepcaps() != 0)
> >+ return 2;
> >+
> >+ set_cap_sys_time();
> >+#endif
>
> Am I missing something here, or is the su() call always done,
> no matter under which user ID VDR has been started?
> Shouldn't this only be done if it was started as 'root'?
You are completely right, these calls should only be done when root is
calling vdr.
I attached the patch which debian and ctvdr use since allmoast one
year without bigger drawbacks. (it is based on Ludwig Nussel's patch,
but sligtly modified)
Regards,
Thomas
@@ -27,7 +27,7 @@
LSIDIR = ./libsi
MANDIR = /usr/local/man
BINDIR = /usr/local/bin
-LIBS = -ljpeg -lpthread -ldl
+LIBS = -ljpeg -lpthread -ldl -lcap
INCLUDES =
PLUGINDIR= ./PLUGINS
@@ -31,6 +31,10 @@
#include <stdlib.h>
#include <termios.h>
#include <unistd.h>
+#include <pwd.h>
+#include <grp.h>
+#include <sys/capability.h>
+#include <sys/prctl.h>
#include "audio.h"
#include "channels.h"
#include "config.h"
@@ -89,6 +93,96 @@
exit(1);
}
+// switch user and group uid
+// taken from startproc by Werner Fink
+static int su(const char* username, const char* groupname)
+{
+ gid_t ngid = 0;
+ struct group* grp = NULL;
+ struct passwd *user = NULL;
+
+ if(!username) return 0;
+
+ user = getpwnam(username);
+ endpwent();
+ if(!user)
+ {
+ fprintf(stderr,"invalid user %s: %s\n",username,strerror(errno));
+ return 1;
+ }
+ if(groupname)
+ {
+ grp = getgrnam(groupname);
+ endgrent();
+ if(!grp)
+ {
+ fprintf(stderr,"invalid group %s: %s\n",groupname,strerror(errno));
+ return 1;
+ }
+ }
+
+ ngid = user->pw_gid;
+ if (grp)
+ ngid = grp->gr_gid;
+
+ if (setgid(ngid) < 0)
+ {
+ fprintf(stderr,"cannot set group id %u: %s\n", (unsigned int)ngid, strerror(errno));
+ return 1;
+ }
+ if (!getuid())
+ {
+ if (initgroups(user->pw_name, ngid) < 0)
+ {
+ fprintf(stderr,"cannot set supplemental group ids for user %s: %s\n",
+ user->pw_name, strerror(errno));
+ return 1;
+ }
+ }
+ if (setuid(user->pw_uid) < 0)
+ {
+ fprintf(stderr,"cannot set user id %u: %s\n",
+ (unsigned int)user->pw_uid, strerror(errno));
+ return 1;
+ }
+ return 0;
+}
+
+// drop all capabilities except cap_sys_time
+static int set_cap_sys_time(void)
+{
+ cap_t caps;
+
+ caps = cap_from_text("= cap_sys_time=ep");
+ if(!caps)
+ {
+ perror("cap_from_text");
+ return -1;
+ }
+
+ if( cap_set_proc(caps) == -1 )
+ {
+ perror("cap_set_proc");
+ cap_free(caps);
+ return -1;
+ }
+
+ cap_free(caps);
+
+ return 0;
+}
+
+// keep capabilities during setuid()
+static inline int set_keepcaps(void)
+{
+ return prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0);
+}
+
+static inline int set_nokeepcaps(void)
+{
+ return prctl(PR_SET_KEEPCAPS, 0, 0, 0, 0);
+}
+
int main(int argc, char *argv[])
{
// Save terminal settings:
@@ -136,6 +230,8 @@
#endif
cPluginManager PluginManager(DEFAULTPLUGINDIR);
+ const char* username = NULL;
+ const char* groupname = NULL;
int ExitCode = 0;
static struct option long_options[] = {
@@ -160,11 +256,13 @@
{ "vfat", no_argument, NULL, 'v' | 0x100 },
{ "video", required_argument, NULL, 'v' },
{ "watchdog", required_argument, NULL, 'w' },
+ { "user", required_argument, NULL, 'u' },
+ { "group", required_argument, NULL, 'g' },
{ NULL }
};
int c;
- while ((c = getopt_long(argc, argv, "a:c:dD:E:hl:L:mp:P:r:s:t:v:Vw:", long_options, NULL)) != -1) {
+ while ((c = getopt_long(argc, argv, "a:c:dD:E:hl:L:mp:P:r:s:t:v:Vw:u:g:", long_options, NULL)) != -1) {
switch (c) {
case 'a': AudioCommand = optarg;
break;
@@ -266,6 +364,10 @@
fprintf(stderr, "vdr: invalid watchdog timeout: %s\n", optarg);
return 2;
break;
+ case 'u': username = optarg;
+ break;
+ case 'g': groupname = optarg;
+ break;
default: return 2;
}
}
@@ -315,6 +417,8 @@
" avoid problems with VFAT file systems\n"
" -w SEC, --watchdog=SEC activate the watchdog timer with a timeout of SEC\n"
" seconds (default: %d); '0' disables the watchdog\n"
+ " -u USER, --user=USER run as user USER instead of root\n"
+ " -g GROUP, --group=GROUP use group GROUP instead of primary group of user\n"
"\n",
DEFAULTEPGDATAFILENAME,
DEFAULTPLUGINDIR,
@@ -355,6 +459,21 @@
return 2;
}
+ // Only try to change capabilities/user when vdr is called by
+ // root
+ if (!getuid () || !getgid () || !geteuid () || !getegid ()) {
+ if(username && set_keepcaps() != 0)
+ return 2;
+
+ if (su(username, groupname) != 0)
+ return 2;
+
+ if(username && set_nokeepcaps() != 0)
+ return 2;
+
+ set_cap_sys_time();
+ }
+
// Log file:
if (SysLogLevel > 0)