Plan 9 from Bell Labs’s /usr/web/sources/contrib/arisawa/su/su.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


/*
*	su for Plan 9 4th ed.
*	ver.1.5
*	update: 2005/01/28
*	auther: Kenar (Kenji Arisawa)
*	E-mail: arisawa@aichi-u.ac.jp
*
*	three persons in the comments
*	Let's assume bob executed something like this:
*		su alice
*	bootes: system owner
*/
#include <u.h>
#include <libc.h>
#include <ctype.h>
#include <mp.h>
#include <libsec.h>
#include <auth.h>
static void	becomenone(void);
static int	waitfor(int pid, char *msg);
int mkcap(char *buf, int n, char *user, char *newuser);
int anewns(char *user, char *file);


#define ERRLEN 256

char *usage="usage: su [-fnuwD] [-p password]  [user [cmd arg ...]]";
char *usage_alt1="don't give passwd in args, use: su -p. %s";
char *usage_alt2="don't give passwd in args, use: su -np. %s";
int fflag = 0;
int nflag = 1;
int debug = 0;
int uflag = 1;
int nowait = 0;

int maxtry = 3;

char *hostowner=nil;
char *user=nil;
char *newuser=nil;
int factlfd = -1; // factlfd = open("/mnt/factotum/ctl", ORDWR)

char*
strtrim(char *s)
{
	char *t;
	while(isspace(*s))
		s++;
	t = strchr(s, 0);
	t--;
	while(isspace(*t ))
		t--;
	t++;
	*t = 0;
	return s;
}


char *
input(char *prompt)
{
	int n;
	char buf[4096], *s;
	write(2,prompt,strlen(prompt));
	n = read(0, buf, sizeof buf);
	buf[n-1] = 0; // cut off last '\n'
	s = strtrim(buf);
	if(strlen(s) == 0)
		return nil;
	return strdup(s);
}


/*	NOTE:
*	codes /sys/src/libc/9sys/getenv.c look nice,
* 	however does not work for device. we use this one.	*/
static char*
getstr(char *name)
{
	int fd;
	int n;
	char *r;
	char buf[4096];
	fd = open(name,OREAD);
	if(fd < 0){
		buf[0] = 0;
		werrstr("open %s: %r", name);
		return nil;
	}
	n = read(fd, buf, sizeof buf);
	close(fd);
	if(n < 0){
		werrstr("read: %r");
		return nil;
	}
	buf[n] = 0;
	if(n  > 0 && buf[n-1] == '\n')
		fprint(2,"# new line in %s\n", name);
	r = strdup(buf);
	return r;
}

static int
waitfor(int pid, char *msg)
{
	Waitmsg *w;
	while((w = wait()) != nil){
		if(w->pid == pid){
			strncpy(msg, w->msg, ERRMAX);
			free(w);
			return 0;
		}
		free(w);
	}
	return -1;
}


char *
owner(char *file)
{	Dir *d;
	static char buf[256];

	d = dirstat(file);
	if(d){
		strncpy(buf,d->uid, sizeof buf);
		free(d);
		return buf;
	} else
		return nil;
}

/* debug routine */
void
print_owner(char *file)
{	char *s;
	s = owner(file);
	if(s)
		fprint(2,"%s uid=%s\n", file, s);
	else
		fprint(2,"%r\n");
}

int
mountf(char *serv, char *tar, int flag)
{
	int fd;
	fd = open(serv, ORDWR);
	if(fd < 0){
		werrstr("open: %r");
		return -1;
	}
	if(mount(fd, -1, tar, flag, "") < 0){
		werrstr("mount: %r");
		return -1;
	}
	return 0;
}

int
execute(char *path, char *cmd)
{
	int pid;
	int status;
	int n;
	char *args[32];
	char msg[ERRLEN];

	n = tokenize(cmd, args, 32);
	if(n == 0)
		return 0;
	args[n] = nil;

	switch(pid = fork()) {/* assign = */
	case -1:
		sysfatal("fork: %r");
	case 0:
		close(0);
		exec(path, args);
		sysfatal("exec: %r");
	default:
		break;
	}
	/* ensure "/bin/auth/factotum" finished */
	status = waitfor(pid, msg);
	if(status < 0){
		werrstr("waitfor: %r");
		return -1;
	}
	return 0;
}

/*	putfactotum will put alice's key into bob's /mnt/factotum/ctl
*	the key will be used in newns()
*	factlfd = open("/mnt/factotum/ctl", ORDWR)	*/
int
putfactotum(char *key)
{
	int n,m;

	if(debug){
		fprint(2,"pushing key ...\n");
		fprint(2,"%s\n", key);
	}

	seek(factlfd, 0, 2);
	m = strlen(key);
	n = write(factlfd, key, m);
	if(n != m){
		werrstr("write: %r");
		return -1;
	}
	return 0;
}

static void
becomenone(void)
{
	int fd;

	fd = open("#c/user", OWRITE);
	if(fd < 0 || write(fd, "none", strlen("none")) < 0)
		sysfatal("can't become none");
	close(fd);
	if(newns("none", nil) < 0)
		sysfatal("can't build normal namespace");
}

int
getkey(char *params)
{
	char buf[256], pw[256];
	int  n,  i;
	char *p, *args[16];

	if(debug){
		fprint(2,"getkey ...\n");
		fprint(2,"params: %s\n", params);
	}

	if(maxtry == 0)
		sysfatal("password trials exceeded limit");
	maxtry--;

	p = input("password: ");
	if(p == nil)
		return -1;
	snprint(pw, sizeof pw, "!password=%s", p);
	free(p);

	/*	typical params is:
	* 	!password? dom=aichi-u.ac.jp proto=pass user=alice user?
	*	proto=p9sk1 dom=aichi-u.ac.jp user=alice user? !password?
	*	we get rid of "user?"	*/
	p = strdup(params);
	n = tokenize(p, args, 16);

	/*	now we build a factotum key. the typical key is:
	*	key proto=p9sk1 dom=aichi-u.ac.jp user=alice  !password=blackcat
	*/
	strcpy(buf, "key");
	for(i = 0; i<n; i++){
		if(strcmp(args[i], "user?") == 0)
			continue;
		if(strcmp(args[i], "!password?") == 0)
			args[i] = pw;
		strncat(buf, " ", sizeof buf);
		strncat(buf, args[i], sizeof buf);
	}
	free(p);

	if(putfactotum(buf) < 0)
		fprint(2, "putfactotum: %r\n");

	if(debug) fprint(2,"getkey done\n");
	return 0;
}



int
mkcap(char *buf, int n, char *user, char *newuser)
{
	int i, fd;
	char *key;
	int keysize = 10;
	uchar hash[SHA1dlen];

	/* check n >= strlen(usr) + 1 + strlen(newuser) + 1 + 2* keysize +1 */
	if(n < strlen(user) + 1 + strlen(newuser) + 1 + 2* keysize +1){
		werrstr("buf size too small");
		return -1;
	}
	
	/* making a key; key must be a null terminated string */
	srand(truerand());
	for(i = 0; i < keysize; i++)
		sprint(buf+2*i, "%2.2ux", rand());
	key = strdup(buf);

	snprint(buf, n, "%s@%s", user, newuser);
	hmac_sha1((uchar*)buf, strlen(buf), (uchar*)key, strlen(key), hash, nil);

	/*	only hostowner can write to /dev/caphash
	*	it is safe to use #¤/caphash */
	fd = open("#¤/caphash", OWRITE);
	if(fd < 0)
		return -1;
	i = write(fd, hash, sizeof hash);
	close(fd);
	if(i < 0)
		return -1;

	n = snprint(buf, n, "%s@%s@%s", user, newuser, key);
	/*	snprint returns strlen(buf)	*/
	return n;
}


/*	stolen from  /sys/src/libauth/auth_chuid.c 	*/
int
chuid(char *cap)
{
	int rv, fd;

	if(cap == nil){
		werrstr("no cap");
		return -1;
	}

	/* change uid */
	/* we should use #¤/capuse, look:
	*   ar% ls -l /dev
         *   ---w------- ¤      bootes  bootes   /dev/caphash
         *   ---w------- M    arisawa arisawa   /dev/caphash
         *   --rw------- M     arisawa arisawa   /dev/capuse
         *   --rw------- M     arisawa arisawa    /dev/capuse
	*   where ar is my cpu server */
	fd = open("#¤/capuse", OWRITE);
	if(fd < 0){
		werrstr("open #¤/capuse: %r");
		return -1;
	}
	rv = write(fd, cap, strlen(cap));
	close(fd);
	if(rv < 0){
		werrstr("writing %s to #¤/capuse: %r", cap);
		return -1;
	}

	return 0;
}

char *
userpasswd(char *user, char *passwd)
{
	/*	we must beg hostowner for cap
	*	auth_userpasswd assumes /mnt/factotum/rpc
	*	is owned by hostowner
	*	note that /srv/factotum is hostowner's service
	*/

	int fd = -1;
	AuthInfo *av;
	char *cap;

	if(debug)
		fprint(2,"userpasswd ...\n");
	if(strcmp(owner("/mnt/factotum"), hostowner) != 0){
		if(bind("/mnt", "/n/temp", MREPL) < 0){
			werrstr("bind: %r\n");
			return nil;
		}
		fd = open("/srv/factotum", ORDWR);
		if(fd < 0){
			werrstr("open: %r");
			return nil;
		}
		if(mount(fd,  -1, "/mnt", MBEFORE, "") < 0){
			werrstr("mount: %r");
			return nil;
		}
 		/* 	mount(2)
		*       The file descriptor fd is automatically closed
		*	by a successful mount call.		*/
	}
	av = auth_userpasswd(user, passwd);
	if(!av){
		werrstr("auth_userpasswd: %r");
		return nil;
	}
	if(fd != -1){
		unmount("/srv/factotum", "/mnt/factotum");
		bind("/n/temp", "/mnt", MREPL);
		unmount(nil, "/n/temp");
	}
	cap = strdup(av->cap);
	if(debug)
		fprint(2,"userpasswd done\n");
	return cap;
}

void main(int argc, char *argv[])
{	int pid, status;
	char *passwd = nil;
	char *hostdomain = nil;
	char buf[4096];
	char msg[ERRLEN];
	char pathname[512];

	ARGBEGIN{
	case 'f':
		fflag = 1;
		break;
	case 'n':
		nflag = 0;
		break;
	case 'p':
		passwd = ARGF();
		if(! passwd)
			sysfatal(usage);
		break;
	case 'u':
		uflag = 0;
		break;
	case 'D':
		debug = 1;
		break;
	case 'w': // nowait in rfork
		nowait = RFNOWAIT;
		break;
	default:
		sysfatal(usage);
	}ARGEND

	/*	three persons of our program	*/
	hostowner = getstr("/dev/hostowner");
	user = getuser(); // bob (a person who executed su)
	argc--;
	newuser = *argv++; // alice (a user bob want to be). if not given "none" is assumed.
	if(newuser && strcmp(newuser,"none") == 0)
		newuser = nil;
	if(debug)
		fprint(2,"hostowner=%s user=%s newuser=%s\n", 
			hostowner, user, newuser);

	/*	command arguments are visible by the hostowner
	*	therefore we should exit if bob is not hostowner */
	if(!nowait && passwd && strcmp(passwd, ".") != 0
		&& strcmp(user, hostowner) != 0){
		newuser = newuser?newuser:"user";
		if(nflag)
			sysfatal(usage_alt1, newuser);
		else
			sysfatal(usage_alt2, newuser);
	}

	if(passwd && strcmp(passwd, ".") == 0){
		passwd = input("password: ");
		if(passwd == nil)
			exits(nil);
	}

	if(passwd || (newuser && strcmp(user, hostowner) != 0)){
		hostdomain = getstr("/dev/hostdomain");
		if(hostdomain == nil || *hostdomain == 0)
			hostdomain = input("hostdomain: ");
	}

	if(strcmp(user, "bootes") == 0)
		fflag = 1;

	switch(pid = rfork(RFNOTEG|RFPROC|RFFDG|RFNAMEG|nowait)) {/* assign = */
	case -1:
		sysfatal("fork");
	case 0: /* child process */
		break;
	default:
		if(nowait)
			exits(nil);
		close(0);
		status = waitfor(pid, msg);
		if(status < 0)
			sysfatal("waitfor: %r");
		exits(nil);
	}

	/*	/mnt/factotum must be owned by a person
	*	who executed su	*/
	if(strcmp(user, owner("/mnt/factotum")) != 0)
		execute("/boot/factotum", "factotum -n");
	factlfd = open("/mnt/factotum/ctl", ORDWR);

	if(getwd(pathname, sizeof(pathname)) == 0)
		strcpy(pathname,"/");

	if(newuser){
		char *cap=nil;
		char *factkey = nil;
		if(uflag){
			if(strcmp(user, hostowner) == 0){
				if(mkcap(buf, sizeof buf, user, newuser) < 0)
					sysfatal("mkcap: %r");
				cap = strdup(buf);
			} else if(passwd){
				if(debug) print_owner("/mnt/factotum");
				if((cap = userpasswd(newuser, passwd)) == nil)
					sysfatal("userpasswd: %r");
				snprint(buf, sizeof buf,
					"key dom=%s proto=pass  user=%s !password=%s",
					hostdomain, newuser, passwd);
				putfactotum(buf);
			} else { /* try this one */
				UserPasswd *up;
				if(strcmp(owner("/mnt/factotum"), user) != 0)	// something wrong
					print_owner("/mnt/factotum");
				if(debug) fprint(2,"auth_getuserpasswd ...\n");
				up = auth_getuserpasswd(getkey,
					"dom=%s proto=pass  user=%s", hostdomain, newuser);
				if(!up)
					sysfatal("auth_getuserpasswd: %r\n");
				if(debug && up)
					fprint(2,"user=%s password=%s\n",up->user, up->passwd);
				if(up){
					passwd = up->passwd;
					cap = userpasswd(newuser, passwd);
					if(cap == nil)
						sysfatal("userpasswd: %r\n");
				}
			}
			if(debug) fprint(2,"cap=%s\n", cap);

	
			/*	chuid writes cap to /dev/capuse	*
			*		look /sys/src/libauth/auth_chuid.c		*
			* 	you must write cap within 1 min			*
			*	after you write to caphash 				*/
			if(debug) fprint(2,"before chuid: user=%s\n", getstr("#c/user"));
			if(chuid(cap) < 0)
				fprint(2,"chuid: %r\n");

			/* our main result of this stage */
			if(debug) fprint(2,"chuid done: user=%s\n", getstr("#c/user"));
		}

		/*	setup new factotum key for alice	*/
		if(passwd){
			snprint(buf, sizeof buf, 
				"key proto=p9sk1 dom=%s user=%s !password=%s", 
				hostdomain, newuser, passwd);
			factkey = strdup(buf);
		}
		if(debug) fprint(2,"factkey=%s\n",factkey);

		if(debug) print_owner("/mnt/factotum");
		/*	owner of /mnt/factotum should be user(=bob)
		*	if not, something wrong	*/
		if(strcmp(user, owner("/mnt/factotum")) != 0)
				sysfatal("something wrong");

		if(fflag && factkey == nil){
			/*	it seems this is essential for bootes in cpu server
			*	to construct any user's namespace without password.
			*	even if /mnt/factotum is already owned by bootes.
			*	I don't know the reason.
			*	fflag is provided for the trusted hostowner of a terminal */
			mountf("/srv/factotum", "/mnt", MBEFORE);
		}

		/*	put the factotum key for alice into /mnt/factotum/ctl */
		if(factkey && putfactotum(factkey) < 0)
			sysfatal("putfactotum: %r");

		/* name space configuration */
		if(nflag){
			/* 	set up new namespace for alice
			*	newns() uses /mnt/factotum/rpc ineternally
			*	/mnt/factotum/rpc must be owned by bob
			*	look /sys/src/libauth/newns.c for details		*/

			if(debug) fprint(2,"newns for %s...\n", newuser);
			/*	owner of /mnt/factotum should be user	*/
			if(strcmp(user, owner("/mnt/factotum")) != 0)
				fprint(2,"#warning /mnt/factotum %s\n", owner("/mnt/factotum"));
			if(anewns(newuser, nil) < 0)
				sysfatal("newns: %r");
			if(debug) print_owner("/mnt/factotum");
			if(debug) fprint(2,"newns done\n");
		}
		else {
			putenv("user", newuser);
			snprint(buf, sizeof buf, "/usr/%s", newuser);
			putenv("home", buf);
		} 
	} else
		becomenone();

	chdir(pathname);
	if(argc > 0){
		char *path, *p;
		path = argv[0];
		/*	consider the path. let foo[0] != '/'
		*	./foo/bar
		*	.foo/bar
		*	aux/foo/bar
		*	foo/bar
		*/
		p = strrchr(path,'/');
		if(p)
			argv[0] = ++p;
		if(path[0] != '.' || path[1] != '/'){
			snprint(buf, sizeof buf,"/bin/%s", path);
			path = buf;
		}
		exec(path, argv);
		sysfatal("exec: %r");
	}
	else{
		putenv("prompt", "su# ");
		execl("/bin/rc", "rc", "-i", nil);
		sysfatal("execl: %r");
	}
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.