/*
* su for Plan 9 4th ed.
* ver.1.5
* update: 2005/01/28
* auther: Kenar (Kenji Arisawa)
* E-mail: [email protected]
*
* 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");
}
}
|