patch to simulate login for sudo

David J. MacKenzie djm at web.us.uu.net
Tue Sep 12 20:53:37 EDT 2000


Here's a patch to sudo 1.6.3p5 to add a feature we need.
UUNET Web Hosting's customer support staff needs to be able to login
as customers to run commands in the same environment the customer
does, to help track down and reproduce problems.  Our customer support
staff isn't senior enough to have root privileges.
We've been having them ask the customers for their password in order
to do this, but using sudo, we should be able to allow our staff to
use their own password to su to a customer account.

But sudo doesn't support simulating a full login, like "su - username"
(or, on modern Unixes, "su -l username").  We need that feature so the
environment, dot files, etc. will be the same as what the customer
gets when they login.  Here's a patch to add it.  It also cleans up
some related junk here and there in the existing code.

A few notes:

* I didn't remove the SHELL environment override, just had to move the code.
* exit() takes an unsigned char, so -1 isn't a sensible argument.

Index: sudo.pod
===================================================================
--- sudo.pod	2000/09/12 19:50:57	1.1.1.1
+++ sudo.pod	2000/09/13 00:37:11
@@ -41,7 +41,7 @@
 
 =head1 SYNOPSIS
 
-B<sudo> B<-V> | B<-h> | B<-l> | B<-L> | B<-v> | B<-k> | B<-K> | B<-s> |
+B<sudo> B<-V> | B<-h> | B<-i> | B<-l> | B<-L> | B<-v> | B<-k> | B<-K> | B<-s> |
 [ B<-H> ] [B<-S> ] [ B<-b> ] | [ B<-p> prompt ] [ B<-c> class|- ]
 [ B<-u> username/#uid ] I<command>
 
@@ -161,6 +161,17 @@
 The C<-s> (I<shell>) option runs the shell specified by the I<SHELL>
 environment variable if it is set or the shell as specified
 in passwd(@mansectform@).
+
+=item -i
+
+The C<-s> (I<simulate initial login>) option runs the shell
+specified in the passwd(@mansectform@) entry of the user that
+the command is being run as.  The command name argument given to
+the shell begins with a C<-> to tell the shell to run as a login shell.
+B<sudo> attempts to change to that user's home directory before
+running the shell.  It also initializes the environment, leaving
+I<TERM> unchanged, setting I<HOME>, I<SHELL>, I<USER>,
+I<LOGNAME>, and I<PATH>, and unsetting all other environment variables.
 
 =item -H
 
Index: sudo.man.in
===================================================================
--- sudo.man.in	2000/09/12 19:50:57	1.1.1.1
+++ sudo.man.in	2000/09/13 00:37:11
@@ -193,7 +190,7 @@
 .SH "NAME"
 sudo \- execute a command as another user
 .SH "SYNOPSIS"
-\fBsudo\fR \fB\-V\fR | \fB\-h\fR | \fB\-l\fR | \fB\-L\fR | \fB\-v\fR | \fB\-k\fR | \fB\-K\fR | \fB\-s\fR |
+\fBsudo\fR \fB\-V\fR | \fB\-h\fR | \fB\-i\fR | \fB\-l\fR | \fB\-L\fR | \fB\-v\fR | \fB\-k\fR | \fB\-K\fR | \fB\-s\fR |
 [ \fB\-H\fR ] [\fB\-S\fR ] [ \fB\-b\fR ] | [ \fB\-p\fR prompt ] [ \fB\-c\fR class|\- ]
 [ \fB\-u\fR username/#uid ] \fIcommand\fR
 .SH "DESCRIPTION"
@@ -283,6 +280,15 @@
 The \f(CW-s\fR (\fIshell\fR) option runs the shell specified by the \fI\s-1SHELL\s0\fR
 environment variable if it is set or the shell as specified
 in \fIpasswd\fR\|(@mansectform@).
+.Ip "-i" 4
+The \f(CW-s\fR (\fIsimulate initial login\fR) option runs the shell
+specified in the \fIpasswd\fR\|(@mansectform@) entry of the user that
+the command is being run as.  The command name argument given to
+the shell begins with a \f(CW-\fR to tell the shell to run as a login shell.
+\fBsudo\fR attempts to change to that user's home directory before
+running the shell.  It also initializes the environment, leaving
+\fI\s-1TERM\s0\fR unchanged, setting \fI\s-1HOME\s0\fR, \fI\s-1SHELL\s0\fR, \fI\s-1USER\s0\fR,
+\fI\s-1LOGNAME\s0\fR, and \fI\s-1PATH\s0\fR, and unsetting all other environment variables.
 .Ip "-H" 4
 The \f(CW-H\fR (\fI\s-1HOME\s0\fR) option sets the \fI\s-1HOME\s0\fR environment variable
 to the homedir of the target user (root by default) as specified
@@ -472,6 +478,8 @@
 .IX Item "-u"
 
 .IX Item "-s"
+
+.IX Item "-i"
 
 .IX Item "-H"
 
Index: getspwuid.c
===================================================================
--- getspwuid.c	2000/09/12 19:50:56	1.1.1.1
+++ getspwuid.c	2000/09/13 00:37:11
@@ -107,14 +107,11 @@
 {
     char *pw_shell;
 
-    if ((pw_shell = getenv("SHELL")) == NULL)
-	pw_shell = pw->pw_shell;
+    pw_shell = pw->pw_shell;
 
-#ifdef _PATH_BSHELL
     /* empty string "" means bourne shell */
     if (*pw_shell == '\0')
 	pw_shell = _PATH_BSHELL;
-#endif /* _PATH_BSHELL */
 
     return(pw_shell);
 }
@@ -207,8 +204,6 @@
     (void) memcpy(local_pw, pw, sizeof(struct passwd));
     local_pw->pw_name = estrdup(pw->pw_name);
     local_pw->pw_dir = estrdup(pw->pw_dir);
-
-    /* pw_shell is a special case since we overide with $SHELL */
     local_pw->pw_shell = estrdup(sudo_getshell(pw));
 
     /* pw_passwd gets a shadow password if applicable */
Index: pathnames.h.in
===================================================================
--- pathnames.h.in	2000/09/12 19:50:57	1.1.1.1
+++ pathnames.h.in	2000/09/13 00:37:11
@@ -42,6 +42,13 @@
 #include <paths.h>
 #endif /* HAVE_PATHS_H */
 
+/*
+ * The default PATH when logging in.
+*/
+#ifndef _PATH_DEFPATH
+#define	_PATH_DEFPATH	"/usr/bin:/bin"
+#endif
+
 #ifndef _PATH_DEV
 #define _PATH_DEV		"/dev/"
 #endif /* _PATH_DEV */
Index: sudo.c
===================================================================
--- sudo.c	2000/09/12 19:50:57	1.1.1.1
+++ sudo.c	2000/09/13 00:37:11
@@ -113,6 +113,7 @@
 static int init_vars			__P((int));
 static int set_loginclass		__P((struct passwd *));
 static void add_env			__P((int));
+static void reset_env			__P((void));
 static void clean_env			__P((char **, struct env_table *));
 static void initial_setup		__P((void));
 static struct passwd *get_authpw	__P((void));
@@ -231,7 +232,9 @@
     init_defaults();
 
     if (sudo_mode & MODE_SHELL)
-	user_cmnd = "shell";
+	user_cmnd = "shell";	/* Replaced later by find_path(). */
+    else if (sudo_mode & MODE_INITIAL_LOGIN)
+	user_cmnd = "login";	/* Replaced later by find_path(). */
     else
 	switch (sudo_mode) {
 	    case MODE_VERSION:
@@ -275,7 +278,11 @@
 
     check_sudoers();	/* check mode/owner on _PATH_SUDOERS */
 
-    add_env(!(sudo_mode & MODE_SHELL));	/* add in SUDO_* envariables */
+    if (sudo_mode & MODE_INITIAL_LOGIN) {
+      reset_env();
+    } else {
+      add_env(!(sudo_mode & MODE_SHELL));	/* add in SUDO_* envariables */
+    }
 
     /* Validate the user but don't search for pseudo-commands. */
     validated = sudoers_lookup(sudo_mode);
@@ -363,11 +370,7 @@
 
 	/* Replace the PATH envariable with a secure one. */
 	if (def_str(I_SECURE_PATH) && !user_is_exempt())
-	    if (sudo_setenv("PATH", def_str(I_SECURE_PATH))) {
-		(void) fprintf(stderr, "%s: cannot allocate memory!\n",
-		    Argv[0]);
-		exit(1);
-	    }
+	    esetenv("PATH", def_str(I_SECURE_PATH));
 
 	/* Restore coredumpsize resource limit. */
 #if defined(RLIMIT_CORE) && !defined(SUDO_DEVEL)
@@ -379,8 +382,15 @@
 
 	/* Set $HOME for `sudo -H'.  Only valid at PERM_RUNAS. */
 	if ((sudo_mode & MODE_RESET_HOME) && runas_homedir)
-	    (void) sudo_setenv("HOME", runas_homedir);
+	    esetenv("HOME", runas_homedir);
 
+	if (sudo_mode & MODE_INITIAL_LOGIN) {
+	  if (chdir(runas_user_dir)) {
+	    (void) fprintf(stderr, "%s: warning: unable to change to directory %s: %s\n",
+			   Argv[0], runas_user_dir, strerror(errno));
+	  }
+	}
+
 #ifndef PROFILING
 	if ((sudo_mode & MODE_BACKGROUND) && fork() > 0)
 	    exit(0);
@@ -394,7 +404,7 @@
 	 */
 	(void) fprintf(stderr, "%s: unable to exec %s: %s\n",
 	    Argv[0], safe_cmnd, strerror(errno));
-	exit(-1);
+	exit(1);
     } else if ((validated & FLAG_NO_USER) || (validated & FLAG_NO_HOST)) {
 	log_auth(validated, 1);
 	exit(1);
@@ -501,9 +511,24 @@
 	log_error(0, "uid %ld does not exist in the passwd file!",
 	    (long) pw.pw_uid);
     }
+    if (getenv("SHELL")) {
+      free(user_shell);
+      user_shell = estrdup(getenv("SHELL"));
+    }
 
     /* It is now safe to use log_error() and set_perms() */
 
+    if (**user_runas == '#')
+      sudo_user.runas_pw = sudo_getpwuid(atoi(*user_runas + 1));
+    else
+      sudo_user.runas_pw = sudo_getpwnam(*user_runas);
+    if (sudo_user.runas_pw == NULL) {
+      (void) fprintf(stderr,
+		     "%s: no passwd entry for %s!\n",
+		     Argv[0], *user_runas);
+      exit(1);
+    }
+
     /*
      * Get current working directory.  Try as user, fall back to root.
      */
@@ -525,23 +550,35 @@
     load_interfaces();
 
     /*
-     * If we were given the '-s' option (run shell) we need to redo
-     * NewArgv and NewArgc.
+     * If we were given the '-s' option (run shell)
+     * or the '-i' option (simulate initial login),
+     * we need to redo NewArgv and NewArgc.
      */
     if ((sudo_mode & MODE_SHELL)) {
 	char **dst, **src = NewArgv;
 
 	NewArgv = (char **) emalloc (sizeof(char *) * (++NewArgc + 1));
-	if (user_shell && *user_shell) {
-	    NewArgv[0] = user_shell;
-	} else {
-	    (void) fprintf(stderr, "%s: Unable to determine shell.", Argv[0]);
-	    exit(1);
-	}
+	NewArgv[0] = user_shell;
 
 	/* copy the args from Argv */
 	for (dst = NewArgv + 1; (*dst = *src) != NULL; ++src, ++dst)
 	    ;
+    } else if (sudo_mode & MODE_INITIAL_LOGIN) {
+	char **dst, **src = NewArgv, *base;
+
+	NewArgv = (char **) emalloc (sizeof(char *) * (++NewArgc + 1));
+
+	base = strrchr(runas_user_shell, '/');
+	if (base)
+	  ++base;
+	else
+	  base = runas_user_shell;
+	easprintf(&NewArgv[0], "-%s", base);
+
+	/* copy the args from Argv */
+	for (dst = NewArgv + 1; (*dst = *src) != NULL; ++src, ++dst)
+	    ;
+	return(find_path(runas_user_shell, &user_cmnd));
     }
 
     /* Resolve the path and return. */
@@ -557,7 +594,7 @@
 static int
 parse_args()
 {
-    int rval = MODE_RUN;		/* what mode is suod to be run in? */
+    int rval = MODE_RUN;		/* what mode is sudo to be run in? */
     int excl = 0;			/* exclusive arg, no others allowed */
 
     NewArgv = Argv + 1;
@@ -663,6 +700,12 @@
 		    usage_excl(1);
 		excl = 's';
 		break;
+	    case 'i':
+		rval |= MODE_INITIAL_LOGIN;
+		if (excl && excl != 'i')
+		    usage_excl(1);
+		excl = 'i';
+		break;
 	    case 'H':
 		rval |= MODE_RESET_HOME;
 		break;
@@ -733,11 +776,8 @@
 	}
     } else {
 	buf = user_cmnd;
-    }
-    if (sudo_setenv("SUDO_COMMAND", buf)) {
-	(void) fprintf(stderr, "%s: cannot allocate memory!\n", Argv[0]);
-	exit(1);
     }
+    esetenv("SUDO_COMMAND", buf);
     if (NewArgc > 1)
 	free(buf);
 
@@ -750,31 +790,41 @@
     }
 
     /* Add the SUDO_USER environment variable. */
-    if (sudo_setenv("SUDO_USER", user_name)) {
-	(void) fprintf(stderr, "%s: cannot allocate memory!\n", Argv[0]);
-	exit(1);
-    }
+    esetenv("SUDO_USER", user_name);
 
     /* Add the SUDO_UID environment variable. */
     (void) sprintf(idstr, "%ld", (long) user_uid);
-    if (sudo_setenv("SUDO_UID", idstr)) {
-	(void) fprintf(stderr, "%s: cannot allocate memory!\n", Argv[0]);
-	exit(1);
-    }
+    esetenv("SUDO_UID", idstr);
 
     /* Add the SUDO_GID environment variable. */
     (void) sprintf(idstr, "%ld", (long) user_gid);
-    if (sudo_setenv("SUDO_GID", idstr)) {
-	(void) fprintf(stderr, "%s: cannot allocate memory!\n", Argv[0]);
-	exit(1);
-    }
+    esetenv("SUDO_GID", idstr);
 
     /* Set PS1 if SUDO_PS1 is set. */
     if ((buf = getenv("SUDO_PS1")))
-	if (sudo_setenv("PS1", buf)) {
-	    (void) fprintf(stderr, "%s: cannot allocate memory!\n", Argv[0]);
-	    exit(1);
-	}
+	esetenv("PS1", buf);
+}
+
+/*
+ * Leave TERM unchanged.  Set HOME, SHELL, USER, LOGNAME, PATH.
+ * Unset all other environment variables.
+ */
+static void
+reset_env()
+{
+  extern char **environ;
+
+  char *term = getenv("TERM");
+  environ = (char **) emalloc(2 * sizeof (char *));
+  environ[0] = NULL;
+
+  if (term)
+    esetenv("TERM", term);
+  esetenv("HOME", runas_user_dir);
+  esetenv("SHELL", runas_user_shell);
+  esetenv("USER", runas_user_name);
+  esetenv("LOGNAME", runas_user_name);
+  esetenv("PATH", _PATH_DEFPATH);
 }
 
 /*
@@ -895,8 +945,6 @@
     int perm;
     int sudo_mode;
 {
-    struct passwd *pw;
-
     /*
      * First, set real & effective uids to root.
      * If perm is PERM_ROOT then we don't need to do anything else.
@@ -927,35 +975,10 @@
 
 	case PERM_RUNAS:
 				/* XXX - add group/gid support */
-				if (**user_runas == '#') {
-				    if (setuid(atoi(*user_runas + 1))) {
-					(void) fprintf(stderr,
-					    "%s: cannot set uid to %s: %s\n",
-					    Argv[0], *user_runas, strerror(errno));
-					exit(1);
-				    }
-				} else {
-				    if (!(pw = getpwnam(*user_runas))) {
-					(void) fprintf(stderr,
-					    "%s: no passwd entry for %s!\n",
-					    Argv[0], *user_runas);
-					exit(1);
-				    }
-
 				    /* Set $USER and $LOGNAME to target user */
 				    if (def_flag(I_LOGNAME)) {
-					if (sudo_setenv("USER", pw->pw_name)) {
-					    (void) fprintf(stderr,
-						"%s: cannot allocate memory!\n",
-						Argv[0]);
-					    exit(1);
-					}
-					if (sudo_setenv("LOGNAME", pw->pw_name)) {
-					    (void) fprintf(stderr,
-						"%s: cannot allocate memory!\n",
-						Argv[0]);
-					    exit(1);
-					}
+					esetenv("USER", runas_user_name);
+					esetenv("LOGNAME", runas_user_name);
 				    }
 
 				    if (def_flag(I_LOGINCLASS)) {
@@ -963,14 +986,14 @@
 					 * setusercontext() will set uid/gid/etc
 					 * for us so no need to do it below.
 					 */
-					if (set_loginclass(pw) > 0)
+					if (set_loginclass(sudo_user.runas_pw) > 0)
 					    break;
 				    }
 
-				    if (setgid(pw->pw_gid)) {
+				    if (setgid(runas_user_gid)) {
 					(void) fprintf(stderr,
 					    "%s: cannot set gid to %ld: %s\n",
-					    Argv[0], (long) pw->pw_gid,
+					    Argv[0], (long) runas_user_gid,
 					    strerror(errno));
 					exit(1);
 				    }
@@ -980,7 +1003,7 @@
 				     * going to run as a non-root user.
 				     */
 				    if (strcmp(*user_runas, "root") != 0 &&
-					initgroups(*user_runas, pw->pw_gid)
+					initgroups(*user_runas, runas_user_gid)
 					== -1) {
 					(void) fprintf(stderr,
 					    "%s: cannot set group vector: %s\n",
@@ -988,16 +1011,15 @@
 					exit(1);
 				    }
 #endif /* HAVE_INITGROUPS */
-				    if (setuid(pw->pw_uid)) {
+				    if (setuid(runas_user_uid)) {
 					(void) fprintf(stderr,
 					    "%s: cannot set uid to %ld: %s\n",
-					    Argv[0], (long) pw->pw_uid,
+					    Argv[0], (long) runas_user_uid,
 					    strerror(errno));
 					exit(1);
 				    }
 				    if (sudo_mode & MODE_RESET_HOME)
-					runas_homedir = pw->pw_dir;
-				}
+					runas_homedir = runas_user_dir;
 				break;
 
 	case PERM_SUDOERS:
@@ -1203,7 +1225,7 @@
     int exit_val;
 {
     (void) fprintf(stderr,
-	"Only one of the -h, -k, -K, -l, -s, -v or -V options may be used\n");
+	"Only one of the -h, -i, -k, -K, -l, -s, -v or -V options may be used\n");
     usage(exit_val);
 }
 
@@ -1215,7 +1237,7 @@
     int exit_val;
 {
     (void) fprintf(stderr,
-	"usage: %s -V | -h | -L | -l | -v | -k | -K | [-H] [-S] [-b]\n%*s",
+	"usage: %s -V | -h | -i | -L | -l | -v | -k | -K | [-H] [-S] [-b]\n%*s",
 	Argv[0], (int) strlen(Argv[0]) + 8, " ");
 #ifdef HAVE_LOGINCAP
     (void) fprintf(stderr, "[-p prompt] [-u username/#uid] [-c class] -s | <command>\n");
Index: sudo.h
===================================================================
--- sudo.h	2000/09/12 19:50:57	1.1.1.1
+++ sudo.h	2000/09/13 00:37:11
@@ -47,6 +47,7 @@
  */
 struct sudo_user {
     struct passwd *pw;
+    struct passwd *runas_pw;
     char *tty;
     char  cwd[MAXPATHLEN];
     char *host;
@@ -102,6 +103,7 @@
 #define MODE_SHELL               01000
 #define MODE_IMPLIED_SHELL       02000
 #define MODE_RESET_HOME          04000
+#define MODE_INITIAL_LOGIN      010000
 
 /*
  * Used with set_perms()
@@ -121,6 +123,11 @@
 #define user_gid		(sudo_user.pw->pw_gid)
 #define user_shell		(sudo_user.pw->pw_shell)
 #define user_dir		(sudo_user.pw->pw_dir)
+#define runas_user_name		(sudo_user.runas_pw->pw_name)
+#define runas_user_uid		(sudo_user.runas_pw->pw_uid)
+#define runas_user_gid		(sudo_user.runas_pw->pw_gid)
+#define runas_user_shell	(sudo_user.runas_pw->pw_shell)
+#define runas_user_dir		(sudo_user.runas_pw->pw_dir)
 #define user_tty		(sudo_user.tty)
 #define user_cwd		(sudo_user.cwd)
 #define user_runas		(sudo_user.runas)
@@ -208,6 +215,7 @@
 VOID *emalloc		__P((size_t));
 VOID *erealloc		__P((VOID *, size_t));
 char *estrdup		__P((const char *));
+void esetenv		__P((char *, char *));
 int easprintf		__P((char **, const char *, ...));
 int evasprintf		__P((char **, const char *, va_list));
 void dump_defaults	__P((void));
Index: sudo_setenv.c
===================================================================
--- sudo_setenv.c	2000/09/12 19:50:57	1.1.1.1
+++ sudo_setenv.c	2000/09/13 00:37:11
@@ -93,3 +93,14 @@
     return(putenv(envstring));
 #endif /* HAVE_SETENV */
 }
+
+void
+esetenv(var, val)
+    char *var;
+    char *val;
+{
+    if (sudo_setenv(var, val)) {
+	(void) fprintf(stderr, "%s: cannot allocate memory!\n", Argv[0]);
+	exit(1);
+    }
+}



More information about the sudo-workers mailing list