/* * su for Plan 9 4th ed. * ver.1.2 (experimental version) * update: 2004/11/04 * * usage: su [-in] [-p password] [user] * options: * -i: none interractive mode * -n: you stay in your namespace * -p passwd: password for real user * password is visible in screen and might be stolen by hostowner, * sorry for my laxness ... * * option -i is intended for usage: * echo 'ls $home' | su -i alice * su -i alice < #include #include #include #include static void becomenone(void); static int waitfor(int pid, char *msg); int mkcap(char *buf, int n, char *user, char *newuser); #define ERRLEN 256 char *usage="usage: su [-in] [-p password] [user]"; int iflag = 0; int nflag = 0; 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; } void setfactotum(char *fact) { int fd; int pid, status; int n; char msg[ERRLEN]; char *factctl; switch(pid = fork()) {/* assign = */ case -1: sysfatal("fork"); case 0: execl("/bin/auth/factotum", "factotum", "-n", nil); sysfatal("execl: factotum: %r"); default: break; } /* ensure "/bin/auth/factotum" finished */ status = waitfor(pid, msg); if(status < 0){ sysfatal("error: setfactotum: waitfor: %r"); } factctl = "/mnt/factotum/ctl"; fd = open(factctl, OWRITE); if(fd < 0){ sysfatal("unable to open %s: %r", factctl); } seek(fd, 0, 2); n = write(fd, fact, strlen(fact)); if(n != strlen(fact)){ sysfatal("unable to write %s: %r", factctl); } close(fd); } 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 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 *user, char *cap) { int rv, fd; if(user == nil){ werrstr("no user"); return -1; } 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 * ar is my cpu server */ fd = open("#¤/capuse", OWRITE); if(fd < 0){ werrstr("opening #¤/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; } void main(int argc, char *argv[]) { int pid, status; char *user, *newuser, *option, *cap; char *passwd = nil; char *fact = nil; char *adom; AuthInfo *av; char buf[4096]; char msg[ERRLEN]; char pathname[512]; USED(argc); USED(argv); ARGBEGIN{ case 'i': iflag = 1; break; case 'n': nflag = 1; break; case 'p': passwd = ARGF(); if(! passwd) sysfatal(usage); break; default: sysfatal(usage); }ARGEND newuser = *argv; // a user you want to be. if not given "none" is asumed. user = getuser(); // this user must be host owner switch(pid = rfork(RFNOTEG|RFPROC|RFFDG|RFNAMEG)) {/* assign = */ case -1: sysfatal("fork"); case 0: /* child process */ break; default: close(0); close(1); close(2); status = waitfor(pid, msg); if(status < 0) sysfatal("waitfor: %r"); exits(nil); } if(getwd(pathname, sizeof(pathname)) == 0) { fprint(2, "pwd: %r\n"); exits("getwd"); } adom = getenv("adom"); if(!adom && passwd) sysfatal("no adom env"); if(passwd){ snprint(buf, sizeof buf, "key proto=p9sk1 dom=%s user=%s !password=%s", adom, newuser, passwd); fact = strdup(buf); } if(newuser){ if(passwd){ if((av = auth_userpasswd(newuser, passwd)) == nil) sysfatal("passwd: %r"); cap = av->cap; } else { if(mkcap(buf, sizeof buf, user, newuser) < 0) sysfatal("mkcap;%r"); cap = buf; } /* chuid writes cap to /dev/cap * * look /sys/src/libauth/auth_chuid.c * * you must write cap within 1 min * * after you write to caphash */ if(chuid(newuser, cap) < 0) sysfatal("chuid:%r"); if(!nflag){ /* setup new factotum for the user */ if(fact) setfactotum(fact); else{ int fd; fd = open("/srv/factotum", ORDWR); if(fd < 0) sysfatal("%r"); mount(fd, -1, "/mnt", MREPL, ""); } /* * set up new namespace for the user * newns() uses /mnt/factotum/rpc ineternally * look /sys/src/libauth/newns.c for details */ status = newns(newuser, nil); if(status < 0) sysfatal("newns: %r"); } } else becomenone(); chdir(pathname); putenv("prompt", "su# "); option = iflag?nil:"-i"; execl("/bin/rc", "rc", option, nil); sysfatal("execl: %r"); }