From: jason@jarthur.claremont.edu (Jason Merrill)
Subject: Patch to allow restricting root su's
Organization: Harvey Mudd College, Claremont, CA 91711
Distribution: gnu
Date: Mon, 12 Oct 1992 07:19:59 GMT

Since I wanted to be able to restrict root access with the GNU su, I
decided to patch it myself.  Here is the result.

An additional benefit is that if getlogin() does not return the name of the
user doing the su'ing, su will use getpwuid(getuid())->pw_name instead.

Since I wanted to be able to check the name of the user before asking for a
password, I moved the code to determine old_name into main() and made
old_name a global variable.

The restriction code is #ifdef'd for your convenience.

----cut here----
--- /gnu/tmp/shellutils-1.7/src/su.c	Sat Aug 22 22:40:20 1992
+++ shellutils-1.7/src/su.c	Mon Oct 12 00:18:05 1992
@@ -65,6 +65,7 @@
    -DSYSLOG_FAILURE	Log failed su's (by default, to root) with syslog.
 
    -DSYSLOG_NON_ROOT	Log all su's, not just those to root (UID 0).
+   -DRESTRICT_ROOT	Only allow root su's from users in group 0.
    Never logs attempted su's to nonexistent accounts.
 
    Written by David MacKenzie <djm@gnu.ai.mit.edu>.  */
@@ -75,6 +76,10 @@
 #include <pwd.h>
 #include "system.h"
 
+#ifdef RESTRICT_ROOT
+#include <grp.h>
+#endif
+
 #ifdef HAVE_SYSLOG_H
 #include <syslog.h>
 void log_su ();
@@ -156,6 +161,12 @@
 /* If nonzero, change some environment vars to indicate the user su'd to.  */
 int change_environment;
 
+/* The name of the user doing the su'ing.  */
+#if defined (SYSLOG_SUCCESS) || defined (SYSLOG_FAILURE) \
+  || defined (RESTRICT_ROOT)
+char *old_user;
+#endif
+
 struct option longopts[] =
 {
   {"command", 1, 0, 'c'},
@@ -220,10 +231,54 @@
   if (optind < argc)
     additional_args = argv + optind;
 
+#if defined (SYSLOG_SUCCESS) || defined (SYSLOG_FAILURE) \
+  || defined (RESTRICT_ROOT)
+
+#define STRDUP(s) strcpy (xmalloc (strlen (s) + 1), (s))
+
+  /* The utmp entry (via getlogin) is probably the best way to identify
+     the user, especially if someone su's from a su-shell.
+     However, they may be on a pty, in which case getlogin() can't find
+     them, so use the current uid. */
+  old_user = getlogin ();
+  if (old_user == 0)
+    {
+      pw = getpwuid (getuid ());
+      if (pw == 0) 
+	old_user = "";
+      else
+	old_user = STRDUP (pw->pw_name);
+    }
+#endif
+
   pw = getpwnam (new_user);
   if (pw == 0)
     error (1, 0, "user %s does not exist", new_user);
   endpwent ();
+
+#ifdef RESTRICT_ROOT
+  if (pw->pw_uid == 0)
+    {
+      struct group* gr;
+      char** p;
+
+      gr = getgrgid (0);
+      if (gr != 0)
+	{
+	  for (p = gr->gr_mem; *p != 0; p++)
+	    if (strcmp (old_user, *p) == 0)
+	      goto found;
+
+#ifdef SYSLOG_FAILURE
+	  log_su (pw, 0);
+#endif	  
+	  error (1, 0, "You do not have permission to su to %s", new_user);
+	  
+	found:
+	}
+    }    
+#endif
+  
   if (!correct_password (pw))
     {
 #ifdef SYSLOG_FAILURE
@@ -404,7 +459,7 @@
      struct passwd *pw;
      int successful;
 {
-  char *new_user, *old_user, *tty;
+  char *new_user, *tty;
 
 #ifndef SYSLOG_NON_ROOT
   if (pw->pw_uid)
@@ -411,11 +466,6 @@
     return;
 #endif
   new_user = pw->pw_name;
-  /* The utmp entry (via getlogin) is probably the best way to identify
-     the user, especially if someone su's from a su-shell.  */
-  old_user = getlogin ();
-  if (old_user == 0)
-    old_user = "";
   tty = ttyname (2);
   if (tty == 0)
     tty = "";

Newsgroups: gnu.utils.bug