su

Message ID 20051230180729.GA23383@slipstream
State New
Headers

Commit Message

Thomas Schmidt Dec. 30, 2005, 6:07 p.m. UTC
  * 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
  

Patch

diff -Nurd vdr-1.3.37.orig/Makefile vdr-1.3.37/Makefile
--- vdr-1.3.37.orig/Makefile	2005-09-02 16:23:38.000000000 +0200
+++ vdr-1.3.37/Makefile	2005-12-30 18:54:21.106357750 +0100
@@ -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
diff -Nurd vdr-1.3.37.orig/vdr.c vdr-1.3.37/vdr.c
--- vdr-1.3.37.orig/vdr.c	2005-11-27 16:56:18.000000000 +0100
+++ vdr-1.3.37/vdr.c	2005-12-30 18:54:21.270368000 +0100
@@ -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)