[net.sources] Modifications to 4.3BSD su - part 1

dce@mips.UUCP (02/04/87)

Apply the following to the 4.3BSD su.c using patch:

----------------------------

*** su.c.old	Wed Feb  4 08:06:53 1987
--- su.c	Wed Feb  4 08:08:23 1987
***************
*** 17,26 ****
--- 17,29 ----
  #include <stdio.h>
  #include <pwd.h>
  #include <grp.h>
+ #include <string.h>
  #include <syslog.h>
  #include <sys/types.h>
  #include <sys/time.h>
  #include <sys/resource.h>
+ #include <sys/stat.h>
+ #include <sys/param.h>
  
  char	userbuf[16]	= "USER=";
  char	homebuf[128]	= "HOME=";
***************
*** 28,41 ****
  char	pathbuf[128]	= "PATH=:/usr/ucb:/bin:/usr/bin";
  char	*cleanenv[] = { userbuf, homebuf, shellbuf, pathbuf, 0, 0 };
  char	*user = "root";
! char	*shell = "/bin/sh";
  int	fulllogin;
  int	fastlogin;
  
  extern char	**environ;
  struct	passwd *pwd;
  char	*crypt();
  char	*getpass();
  char	*getenv();
  char	*getlogin();
  
--- 31,77 ----
  char	pathbuf[128]	= "PATH=:/usr/ucb:/bin:/usr/bin";
  char	*cleanenv[] = { userbuf, homebuf, shellbuf, pathbuf, 0, 0 };
  char	*user = "root";
! 
! #define DEFAULT_SHELL	"/bin/sh"
! char	*shell = DEFAULT_SHELL;
  int	fulllogin;
  int	fastlogin;
  
+ /*
+  * New flags:
+  *
+  *	-e	Do not reset the environment.
+  *	-c	Execute extra arguments directly as command instead of using
+  *		shell. If none are given, use the shell.
+  *
+  * If su is called as 'ssu', it is the same as 'su -e -c root'.
+  *
+  */
+ 
+ int	Reset_env = 1;
+ int	Command = 0;
+ int	Have_user = 0;
+ 
+ /*
+  * New feature:
+  *
+  *	If the user is trying to su to root, the file /etc/su_people is read
+  *	to see if the user is in it. If so, no password is required.
+  *
+  *	This file MUST have owner and group 0 and mode 600 to be read, and no
+  *	messages are printed if it isn't. This avoids someone accidentally
+  *	leaving the file in such a state that users can change it and gain
+  *	free system access.
+  */
+ 
+ #define FREE_FILE	"/etc/su_people"
+ int	ck_free_entry();
+ 
  extern char	**environ;
  struct	passwd *pwd;
  char	*crypt();
  char	*getpass();
+ char	*get_enveq();
  char	*getenv();
  char	*getlogin();
  
***************
*** 44,67 ****
  	char *argv[];
  {
  	char *password;
! 	char buf[1000];
  	FILE *fp;
! 	register char *p;
  
  	openlog("su", LOG_ODELAY, LOG_AUTH);
  
! again:
! 	if (argc > 1 && strcmp(argv[1], "-f") == 0) {
! 		fastlogin++;
! 		argc--, argv++;
! 		goto again;
  	}
! 	if (argc > 1 && strcmp(argv[1], "-") == 0) {
! 		fulllogin++;
! 		argc--, argv++;
! 		goto again;
! 	}
! 	if (argc > 1 && argv[1][0] != '-') {
  		user = argv[1];
  		argc--, argv++;
  	}
--- 80,125 ----
  	char *argv[];
  {
  	char *password;
! 	char name[1000];
  	FILE *fp;
! 	int su_for_free;
! 	register char *mylogin;
  
  	openlog("su", LOG_ODELAY, LOG_AUTH);
  
! 	ckname(argv[0]);	/* Set flags based on name */
! 
! 	while (argc > 1 && argv[1][0] == '-') {
! 		switch (argv[1][1]) {
! 
! 			case 'f':
! 				fastlogin = 1;
! 				break;
! 
! 			case '\0':
! 				fulllogin = 1;
! 				break;
! 
! 			case 'e':
! 				Reset_env = 0;
! 				break;
! 
! 			case 'c':
! 				Command = 1;
! 				break;
! 			
! 			default:
! 				fprintf(stderr, "su: usage: su [-e] [-f] [-c] [-] [user] ...");
! 				exit(1);
! 				break;
! 
! 		}
! 		argv++;
! 		argc--;
  	}
! 				
! 	
! 	if (!Have_user && argc > 1 && argv[1][0] != '-') {
  		user = argv[1];
  		argc--, argv++;
  	}
***************
*** 69,89 ****
  		fprintf(stderr, "Who are you?\n");
  		exit(1);
  	}
! 	strcpy(buf, pwd->pw_name);
  	if ((pwd = getpwnam(user)) == NULL) {
  		fprintf(stderr, "Unknown login: %s\n", user);
  		exit(1);
  	}
  	/*
! 	 * Only allow those in group zero to su to root.
  	 */
! 	if (pwd->pw_uid == 0) {
  		struct	group *gr;
  		int i;
  
  		if ((gr = getgrgid(0)) != NULL) {
  			for (i = 0; gr->gr_mem[i] != NULL; i++)
! 				if (strcmp(buf, gr->gr_mem[i]) == 0)
  					goto userok;
  			fprintf(stderr, "You do not have permission to su %s\n",
  				user);
--- 127,159 ----
  		fprintf(stderr, "Who are you?\n");
  		exit(1);
  	}
! 	strcpy(name, pwd->pw_name);
  	if ((pwd = getpwnam(user)) == NULL) {
  		fprintf(stderr, "Unknown login: %s\n", user);
  		exit(1);
  	}
  	/*
! 	 * Only allow those in group zero or in the su_people file to su to
! 	 * root, unless the effective groupid is 0 (i.e., if the command
! 	 * is setgid 0).
  	 */
! 
! 	if ((mylogin = getlogin()) == NULL || *mylogin == '\0') {
! 		mylogin = name;
! 	}
! 	if (pwd->pw_uid == 0 && ck_free_entry(mylogin, name)) {
! 		su_for_free = 1;
! 	} else {
! 		su_for_free == 0;
! 	}
! 
! 	if (!su_for_free && pwd->pw_uid == 0 && getegid() != 0) {
  		struct	group *gr;
  		int i;
  
  		if ((gr = getgrgid(0)) != NULL) {
  			for (i = 0; gr->gr_mem[i] != NULL; i++)
! 				if (strcmp(name, gr->gr_mem[i]) == 0)
  					goto userok;
  			fprintf(stderr, "You do not have permission to su %s\n",
  				user);
***************
*** 93,114 ****
  		setpriority(PRIO_PROCESS, 0, -2);
  	}
  
! #define Getlogin()  (((p = getlogin()) && *p) ? p : buf)
! 	if (pwd->pw_passwd[0] == '\0' || getuid() == 0)
! 		goto ok;
! 	password = getpass("Password:");
! 	if (strcmp(pwd->pw_passwd, crypt(password, pwd->pw_passwd)) != 0) {
! 		fprintf(stderr, "Sorry\n");
! 		if (pwd->pw_uid == 0) {
! 			syslog(LOG_CRIT, "BAD SU %s on %s",
! 					Getlogin(), ttyname(2));
  		}
- 		exit(2);
  	}
- ok:
  	endpwent();
  	if (pwd->pw_uid == 0) {
! 		syslog(LOG_NOTICE, "%s on %s", Getlogin(), ttyname(2));
  		closelog();
  	}
  	if (setgid(pwd->pw_gid) < 0) {
--- 163,182 ----
  		setpriority(PRIO_PROCESS, 0, -2);
  	}
  
! 	if (pwd->pw_passwd[0] != '\0' && getuid() != 0 && !su_for_free) {
! 		password = getpass("Password:");
! 		if (strcmp(pwd->pw_passwd, crypt(password, pwd->pw_passwd)) != 0) {
! 			fprintf(stderr, "Sorry\n");
! 			if (pwd->pw_uid == 0) {
! 				syslog(LOG_CRIT, "BAD SU %s on %s",
! 						mylogin, ttyname(2));
! 			}
! 			exit(2);
  		}
  	}
  	endpwent();
  	if (pwd->pw_uid == 0) {
! 		syslog(LOG_NOTICE, "%s on %s", mylogin, ttyname(2));
  		closelog();
  	}
  	if (setgid(pwd->pw_gid) < 0) {
***************
*** 123,139 ****
  		perror("su: setuid");
  		exit(5);
  	}
! 	if (pwd->pw_shell && *pwd->pw_shell)
! 		shell = pwd->pw_shell;
  	if (fulllogin) {
! 		cleanenv[4] = getenv("TERM");
  		environ = cleanenv;
  	}
! 	if (strcmp(user, "root"))
! 		setenv("USER", pwd->pw_name, userbuf);
! 	setenv("SHELL", shell, shellbuf);
! 	setenv("HOME", pwd->pw_dir, homebuf);
  	setpriority(PRIO_PROCESS, 0, 0);
  	if (fastlogin) {
  		*argv-- = "-f";
  		*argv = "su";
--- 191,222 ----
  		perror("su: setuid");
  		exit(5);
  	}
! 	if (Reset_env) {
! 		if (pwd->pw_shell && *pwd->pw_shell) {
! 			shell = pwd->pw_shell;
! 		}
! 	} else {
! 		shell = getenv("SHELL");
! 		if (shell == NULL) {
! 			shell = DEFAULT_SHELL;
! 		}
! 	}
  	if (fulllogin) {
! 		cleanenv[4] = get_enveq("TERM");
  		environ = cleanenv;
  	}
! 	if (Reset_env) {
! 		if (strcmp(user, "root"))
! 			setenv("USER", pwd->pw_name, userbuf);
! 		setenv("SHELL", shell, shellbuf);
! 		setenv("HOME", pwd->pw_dir, homebuf);
! 	}
  	setpriority(PRIO_PROCESS, 0, 0);
+ 	if (Command && argc > 1) {
+ 		execvp(argv[1], argv + 1);
+ 		fprintf(stderr, "Could not execute %s\n", argv[1]);
+ 		exit(7);
+ 	}
  	if (fastlogin) {
  		*argv-- = "-f";
  		*argv = "su";
***************
*** 170,177 ****
  	}
  }
  
  char *
! getenv(ename)
  	char *ename;
  {
  	register char *cp, *dp;
--- 253,269 ----
  	}
  }
  
+ /*
+  * The subroutine get_enveq() was called getenv(), but this isn't a
+  * good idea, since it may cause profiling to break. Also, we need the
+  * normal getenv behavior, too.
+  *
+  * This routine returns the full "NAME=value" string instead of just the
+  * value portion.
+  */
+ 
  char *
! get_enveq(ename)
  	char *ename;
  {
  	register char *cp, *dp;
***************
*** 184,187 ****
--- 276,541 ----
  			return (*--ep);
  	}
  	return ((char *)0);
+ }
+ 
+ /*
+  * The subroutine ckname() sets options based on the name of the command.
+  */
+ 
+ ckname(name)
+ char *name;
+ {
+ 	char *base;
+ 
+ 	base = rindex(name, '/');
+ 	if (base) {
+ 		base++;
+ 	} else {
+ 		base = name;
+ 	}
+ 
+ 	if (strcmp(base, "ssu") == 0) {
+ 		Have_user = 1;
+ 		Reset_env = 0;
+ 		Command = 1;
+ 	}
+ }
+ 
+ /*
+  * The subroutine ck_free_entry() checks to see if the user need not give
+  * a password. This is only true if the file /etc/su_people is mode 0600,
+  * has owner and group 0, and contains one of the given names. We look up
+  * both the current username and the login name, since the user could
+  * already be su'ed.
+  *
+  * The return value is 1 if the above are true, and 0 if not.
+  */
+ 
+ static int ck_apply();
+ #define A_NOT		0
+ #define A_ALLOW		1
+ #define A_DENY		2
+ 
+ ck_free_entry(name1, name2)
+ char *name1;
+ char *name2;
+ {
+ 
+ 	struct stat statb;
+ 	FILE *fp;
+ 	char buf[1024];		/* Buffer for holding data	*/
+ 
+ 	if (name1 == NULL || name1[0] == '\0' ||
+ 	    name2 == NULL || name2[0] == '\0') {	/* Unsafe */
+ 		return 0;
+ 	}
+ 
+ 	if (stat(FREE_FILE, &statb) < 0) {
+ 		return 0;
+ 	}
+ 
+ 	if ((statb.st_mode & ~S_IFMT) != 0600 || statb.st_uid != 0 ||
+ 	    statb.st_gid != 0) {
+ 		return 0;
+ 	}
+ 
+ 	if ((fp = fopen(FREE_FILE, "r")) == NULL) {
+ 		return 0;
+ 	}
+ 
+ 	while (fgets(buf, sizeof(buf), fp) != NULL) {
+ 		if (buf[strlen(buf) - 1] != '\n') {	/* Line too long */
+ 			(void) fclose(fp);
+ 			return 0;
+ 		}
+ 		if (buf[0] == '#') {			/* Comment */
+ 			continue;
+ 		}
+ 
+ 		switch(ck_apply(name1, buf)) {
+ 			case A_ALLOW:
+ 				(void) fclose(fp);
+ 				return 1;
+ 
+ 			case A_DENY:
+ 				(void) fclose(fp);
+ 				return 0;
+ 		}
+ 
+ 		if (strcmp(name1, name2) == 0) {
+ 			continue;
+ 		}
+ 		switch(ck_apply(name2, buf)) {
+ 			case A_ALLOW:
+ 				(void) fclose(fp);
+ 				return 1;
+ 
+ 			case A_DENY:
+ 				(void) fclose(fp);
+ 				return 0;
+ 		}
+ 	}
+ 
+ 	(void) fclose(fp);
+ 	return 0;
+ }
+ 
+ /*
+  * The subroutine ck_apply() checks to see if the buffer applies to the
+  * given name. If not, A_NOT is returned. Otherwise, the buffer is checked
+  * to see how it applies, based on the buffer type.
+  *
+  * The buffer may be one of the following types:
+  *
+  *	{name}
+  *		Return A_ALLOW if either of the names is {name}
+  *	{name} {hostname list}
+  *		Return A_ALLOW if either of the names is {name} and the
+  *		current hostname is in {hostname list}
+  *	{name} !{hostname list}
+  *		Return A_DENY if either of the names is {name} and the
+  *		current hostname is in {hostname list}
+  *
+  * The hostname list is a list of names separated by commas or whitespace.
+  */
+ 
+ static int ck_host();
+ 
+ static int
+ ck_apply(name, buf)
+ char *name;
+ char *buf;
+ {
+ 
+ 	int len;	/* Length of name */
+ 
+ 	len = strlen(name);
+ 	if (strncmp(name, buf, len) == 0) {
+ 		switch(buf[len]) {
+ 			case '\n':
+ 				return A_ALLOW;
+ 				break;
+ 
+ 			case '\t':
+ 			case ' ':
+ 				if (ck_empty(&buf[len + 1])) {
+ 					return A_ALLOW;
+ 				}
+ 				return ck_host(&buf[len + 1]);
+ 				break;
+ 		}
+ 
+ 		/*
+ 		 * Name doesn't match. May not be a syntax error. Example:
+ 		 * 	name = foo
+ 		 *	buffer = foobar
+ 		 */
+ 	}
+ 
+ 	return A_NOT;
+ }
+ 
+ /*
+  * The subroutine ck_empty() returns 1 if the given string contains only
+  * spaces and tabs followed by a newline, and 0 otherwise.
+  */
+ 
+ static
+ ck_empty(str)
+ char *str;
+ {
+ 
+ 	while (*str != '\n') {
+ 		if (*str != ' ' && *str != '\t') {
+ 			return 0;
+ 		}
+ 		str++;
+ 	}
+ 
+ 	return 1;
+ }
+ 
+ /*
+  * The subroutine ck_host() takes a list of hostnames and checks to see
+  * if the current hostname is in the list.
+  *
+  * If the current hostname is in the list, A_ALLOW is returned. If not,
+  * A_DENY is returned. If the list begins with '!', these two values are
+  * reversed.
+  *
+  * If there are any syntax errors, A_DENY is returned for the sake of safety.
+  */
+ 
+ static int
+ ck_host(list)
+ char *list;
+ {
+ 
+ 	int not = 0;				/* 1 if list begins with ! */
+ 	static char host[MAXHOSTNAMELEN + 1];	/* Current hostname */
+ 	static int hlen = 0;			/* Length of hostname */
+ 
+ 	if (hlen == 0) {
+ 		gethostname(host, MAXHOSTNAMELEN);
+ 		hlen = strlen(host);
+ 	}
+ 
+ 	/*
+ 	 * Find beginning of list.
+ 	 */
+ 
+ 	while (*list == ' ' || *list == '\t') {
+ 		list++;
+ 	}
+ 
+ 	if (*list == '!') {
+ 		not = 1;
+ 		list++;
+ 		while (*list == ' ' || *list == '\t') {
+ 			list++;
+ 		}
+ 		if (*list == '\n') {
+ 			return A_DENY;		/* Syntax */
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * Look at each list element
+ 	 */
+ 
+ 	while (*list != '\n') {
+ 		if (strncmp(list, host, hlen) == 0) {
+ 			switch (list[hlen]) {
+ 				case ' ':
+ 				case ',':
+ 				case '\t':
+ 				case '\n':
+ 					if (not) {
+ 						return A_DENY;
+ 					} else {
+ 						return A_ALLOW;
+ 					}
+ 					break;
+ 			}
+ 		}
+ 
+ 		/*
+ 		 * Go to next list item.
+ 		 */
+ 
+ 		list++;
+ 		while (*list != ' ' && *list != '\t' && *list != ',' &&
+ 		       *list != '\n') {
+ 			list++;
+ 		}
+ 		while (*list == ' ' || *list == '\t' || *list == ',') {
+ 			list++;
+ 		}
+ 	}
+ 
+ 	if (not) {
+ 		return A_ALLOW;
+ 	} else {
+ 		return A_DENY;
+ 	}
  }