/* * su for Plan 9 4th ed. * ver.1.5a * update: 2007/05/11 * 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 #include #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); 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= 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|OCEXEC); 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"); } }