#include "stdinc.h" #include "9.h" enum { NUserHash = 1009, }; typedef struct Ubox Ubox; typedef struct User User; struct User { char* uid; char* uname; char* leader; char** group; int ngroup; User* next; /* */ User* ihash; /* lookup by .uid */ User* nhash; /* lookup by .uname */ }; #pragma varargck type "U" User* struct Ubox { User* head; User* tail; int nuser; int len; User* ihash[NUserHash]; /* lookup by .uid */ User* nhash[NUserHash]; /* lookup by .uname */ }; static struct { VtLock* lock; Ubox* box; } ubox; static char usersDefault[] = { "adm:adm:adm:sys\n" "none:none::\n" "noworld:noworld::\n" "sys:sys::glenda\n" "glenda:glenda:glenda:\n" }; static char* usersMandatory[] = { "adm", "none", "noworld", "sys", nil, }; char* uidadm = "adm"; char* unamenone = "none"; char* uidnoworld = "noworld"; static u32int userHash(char* s) { uchar *p; u32int hash; hash = 0; for(p = (uchar*)s; *p != '\0'; p++) hash = hash*7 + *p; return hash % NUserHash; } static User* _userByUid(Ubox* box, char* uid) { User *u; if(box != nil){ for(u = box->ihash[userHash(uid)]; u != nil; u = u->ihash){ if(strcmp(u->uid, uid) == 0) return u; } } vtSetError("uname: uid '%s' not found", uid); return nil; } char* unameByUid(char* uid) { User *u; char *uname; vtRLock(ubox.lock); if((u = _userByUid(ubox.box, uid)) == nil){ vtRUnlock(ubox.lock); return nil; } uname = vtStrDup(u->uname); vtRUnlock(ubox.lock); return uname; } static User* _userByUname(Ubox* box, char* uname) { User *u; if(box != nil){ for(u = box->nhash[userHash(uname)]; u != nil; u = u->nhash){ if(strcmp(u->uname, uname) == 0) return u; } } vtSetError("uname: uname '%s' not found", uname); return nil; } char* uidByUname(char* uname) { User *u; char *uid; vtRLock(ubox.lock); if((u = _userByUname(ubox.box, uname)) == nil){ vtRUnlock(ubox.lock); return nil; } uid = vtStrDup(u->uid); vtRUnlock(ubox.lock); return uid; } static int _groupMember(Ubox* box, char* group, char* member, int whenNoGroup) { int i; User *g, *m; /* * Is 'member' a member of 'group'? * Note that 'group' is a 'uid' and not a 'uname'. * A 'member' is automatically in their own group. */ if((g = _userByUid(box, group)) == nil) return whenNoGroup; if((m = _userByUname(box, member)) == nil) return 0; if(m == g) return 1; for(i = 0; i < g->ngroup; i++){ if(strcmp(g->group[i], member) == 0) return 1; } return 0; } int groupWriteMember(char* uname) { int ret; /* * If there is a ``write'' group, then only its members can write * to the file system, no matter what the permission bits say. * * To users not in the ``write'' group, the file system appears * read only. This is used to serve sources.cs.bell-labs.com * to the world. * * Note that if there is no ``write'' group, then this routine * makes it look like everyone is a member -- the opposite * of what groupMember does. * * We use this for sources.cs.bell-labs.com. * If this slows things down too much on systems that don't * use this functionality, we could cache the write group lookup. */ vtRLock(ubox.lock); ret = _groupMember(ubox.box, "write", uname, 1); vtRUnlock(ubox.lock); return ret; } static int _groupRemMember(Ubox* box, User* g, char* member) { int i; if(_userByUname(box, member) == nil) return 0; for(i = 0; i < g->ngroup; i++){ if(strcmp(g->group[i], member) == 0) break; } if(i >= g->ngroup){ if(strcmp(g->uname, member) == 0) vtSetError("uname: '%s' always in own group", member); else vtSetError("uname: '%s' not in group '%s'", member, g->uname); return 0; } vtMemFree(g->group[i]); box->len -= strlen(member); if(g->ngroup > 1) box->len--; g->ngroup--; switch(g->ngroup){ case 0: vtMemFree(g->group); g->group = nil; break; default: for(; i < g->ngroup; i++) g->group[i] = g->group[i+1]; g->group[i] = nil; /* prevent accidents */ g->group = vtMemRealloc(g->group, g->ngroup * sizeof(char*)); break; } return 1; } static int _groupAddMember(Ubox* box, User* g, char* member) { User *u; if((u = _userByUname(box, member)) == nil) return 0; if(_groupMember(box, g->uid, u->uname, 0)){ if(strcmp(g->uname, member) == 0) vtSetError("uname: '%s' always in own group", member); else vtSetError("uname: '%s' already in group '%s'", member, g->uname); return 0; } g->group = vtMemRealloc(g->group, (g->ngroup+1)*sizeof(char*)); g->group[g->ngroup] = vtStrDup(member); box->len += strlen(member); g->ngroup++; if(g->ngroup > 1) box->len++; return 1; } int groupMember(char* group, char* member) { int r; if(group == nil) return 0; vtRLock(ubox.lock); r = _groupMember(ubox.box, group, member, 0); vtRUnlock(ubox.lock); return r; } int groupLeader(char* group, char* member) { int r; User *g; /* * Is 'member' the leader of 'group'? * Note that 'group' is a 'uid' and not a 'uname'. * Uname 'none' cannot be a group leader. */ if(strcmp(member, unamenone) == 0 || group == nil) return 0; vtRLock(ubox.lock); if((g = _userByUid(ubox.box, group)) == nil){ vtRUnlock(ubox.lock); return 0; } if(g->leader != nil){ if(strcmp(g->leader, member) == 0){ vtRUnlock(ubox.lock); return 1; } r = 0; } else r = _groupMember(ubox.box, group, member, 0); vtRUnlock(ubox.lock); return r; } static void userFree(User* u) { int i; vtMemFree(u->uid); vtMemFree(u->uname); if(u->leader != nil) vtMemFree(u->leader); if(u->ngroup){ for(i = 0; i < u->ngroup; i++) vtMemFree(u->group[i]); vtMemFree(u->group); } vtMemFree(u); } static User* userAlloc(char* uid, char* uname) { User *u; u = vtMemAllocZ(sizeof(User)); u->uid = vtStrDup(uid); u->uname = vtStrDup(uname); return u; } int validUserName(char* name) { Rune *r; static Rune invalid[] = L"#:,()"; for(r = invalid; *r != '\0'; r++){ if(utfrune(name, *r)) return 0; } return 1; } static int userFmt(Fmt* fmt) { User *u; int i, r; u = va_arg(fmt->args, User*); r = fmtprint(fmt, "%s:%s:", u->uid, u->uname); if(u->leader != nil) r += fmtprint(fmt, u->leader); r += fmtprint(fmt, ":"); if(u->ngroup){ r += fmtprint(fmt, u->group[0]); for(i = 1; i < u->ngroup; i++) r += fmtprint(fmt, ",%s", u->group[i]); } return r; } static int usersFileWrite(Ubox* box) { Fs *fs; User *u; int i, r; Fsys *fsys; char *p, *q, *s; File *dir, *file; if((fsys = fsysGet("main")) == nil) return 0; fsysFsRlock(fsys); fs = fsysGetFs(fsys); /* * BUG: * the owner/group/permissions need to be thought out. */ r = 0; if((dir = fileOpen(fs, "/active")) == nil) goto tidy0; if((file = fileWalk(dir, uidadm)) == nil) file = fileCreate(dir, uidadm, ModeDir|0775, uidadm); fileDecRef(dir); if(file == nil) goto tidy; dir = file; if((file = fileWalk(dir, "users")) == nil) file = fileCreate(dir, "users", 0664, uidadm); fileDecRef(dir); if(file == nil) goto tidy; if(!fileTruncate(file, uidadm)) goto tidy; p = s = vtMemAlloc(box->len+1); q = p + box->len+1; for(u = box->head; u != nil; u = u->next){ p += snprint(p, q-p, "%s:%s:", u->uid, u->uname); if(u->leader != nil) p+= snprint(p, q-p, u->leader); p += snprint(p, q-p, ":"); if(u->ngroup){ p += snprint(p, q-p, u->group[0]); for(i = 1; i < u->ngroup; i++) p += snprint(p, q-p, ",%s", u->group[i]); } p += snprint(p, q-p, "\n"); } r = fileWrite(file, s, box->len, 0, uidadm); vtMemFree(s); tidy: if(file != nil) fileDecRef(file); tidy0: fsysFsRUnlock(fsys); fsysPut(fsys); return r; } static void uboxRemUser(Ubox* box, User *u) { User **h, *up; h = &box->ihash[userHash(u->uid)]; for(up = *h; up != nil && up != u; up = up->ihash) h = &up->ihash; assert(up == u); *h = up->ihash; box->len -= strlen(u->uid); h = &box->nhash[userHash(u->uname)]; for(up = *h; up != nil && up != u; up = up->nhash) h = &up->nhash; assert(up == u); *h = up->nhash; box->len -= strlen(u->uname); h = &box->head; for(up = *h; up != nil && strcmp(up->uid, u->uid) != 0; up = up->next) h = &up->next; assert(up == u); *h = u->next; u->next = nil; box->len -= 4; box->nuser--; } static void uboxAddUser(Ubox* box, User* u) { User **h, *up; h = &box->ihash[userHash(u->uid)]; u->ihash = *h; *h = u; box->len += strlen(u->uid); h = &box->nhash[userHash(u->uname)]; u->nhash = *h; *h = u; box->len += strlen(u->uname); h = &box->head; for(up = *h; up != nil && strcmp(up->uid, u->uid) < 0; up = up->next) h = &up->next; u->next = *h; *h = u; box->len += 4; box->nuser++; } static void uboxDump(Ubox* box) { User* u; consPrint("nuser %d len = %d\n", box->nuser, box->len); for(u = box->head; u != nil; u = u->next) consPrint("%U\n", u); } static void uboxFree(Ubox* box) { User *next, *u; for(u = box->head; u != nil; u = next){ next = u->next; userFree(u); } vtMemFree(box); } static int uboxInit(char* users, int len) { User *g, *u; Ubox *box, *obox; int blank, comment, i, nline, nuser; char *buf, *f[5], **line, *p, *q, *s; /* * Strip out whitespace and comments. * Note that comments are pointless, they disappear * when the server writes the database back out. */ blank = 1; comment = nline = 0; s = p = buf = vtMemAlloc(len+1); for(q = users; *q != '\0'; q++){ if(*q == '\r' || *q == '\t' || *q == ' ') continue; if(*q == '\n'){ if(!blank){ if(p != s){ *p++ = '\n'; nline++; s = p; } blank = 1; } comment = 0; continue; } if(*q == '#') comment = 1; blank = 0; if(!comment) *p++ = *q; } *p = '\0'; line = vtMemAllocZ((nline+2)*sizeof(char*)); if((i = gettokens(buf, line, nline+2, "\n")) != nline){ fprint(2, "nline %d (%d) botch\n", nline, i); vtMemFree(line); vtMemFree(buf); return 0; } /* * Everything is updated in a local Ubox until verified. */ box = vtMemAllocZ(sizeof(Ubox)); /* * First pass - check format, check for duplicates * and enter in hash buckets. */ nuser = 0; for(i = 0; i < nline; i++){ s = vtStrDup(line[i]); if(getfields(s, f, nelem(f), 0, ":") != 4){ fprint(2, "bad line '%s'\n", line[i]); vtMemFree(s); continue; } if(*f[0] == '\0' || *f[1] == '\0'){ fprint(2, "bad line '%s'\n", line[i]); vtMemFree(s); continue; } if(!validUserName(f[0])){ fprint(2, "invalid uid '%s'\n", f[0]); vtMemFree(s); continue; } if(_userByUid(box, f[0]) != nil){ fprint(2, "duplicate uid '%s'\n", f[0]); vtMemFree(s); continue; } if(!validUserName(f[1])){ fprint(2, "invalid uname '%s'\n", f[0]); vtMemFree(s); continue; } if(_userByUname(box, f[1]) != nil){ fprint(2, "duplicate uname '%s'\n", f[1]); vtMemFree(s); continue; } u = userAlloc(f[0], f[1]); uboxAddUser(box, u); line[nuser] = line[i]; nuser++; vtMemFree(s); } assert(box->nuser == nuser); /* * Second pass - fill in leader and group information. */ for(i = 0; i < nuser; i++){ s = vtStrDup(line[i]); getfields(s, f, nelem(f), 0, ":"); assert(g = _userByUname(box, f[1])); if(*f[2] != '\0'){ if((u = _userByUname(box, f[2])) == nil) g->leader = vtStrDup(g->uname); else g->leader = vtStrDup(u->uname); box->len += strlen(g->leader); } for(p = f[3]; p != nil; p = q){ if((q = utfrune(p, L',')) != nil) *q++ = '\0'; if(!_groupAddMember(box, g, p)){ // print/log error here } } vtMemFree(s); } vtMemFree(line); vtMemFree(buf); for(i = 0; usersMandatory[i] != nil; i++){ if((u = _userByUid(box, usersMandatory[i])) == nil){ vtSetError("user '%s' is mandatory", usersMandatory[i]); uboxFree(box); return 0; } if(strcmp(u->uid, u->uname) != 0){ vtSetError("uid/uname for user '%s' must match", usersMandatory[i]); uboxFree(box); return 0; } } vtLock(ubox.lock); obox = ubox.box; ubox.box = box; vtUnlock(ubox.lock); if(obox != nil) uboxFree(obox); return 1; } int usersFileRead(char* path) { char *p; File *file; Fsys *fsys; int len, r; uvlong size; if((fsys = fsysGet("main")) == nil) return 0; fsysFsRlock(fsys); if(path == nil) path = "/active/adm/users"; r = 0; if((file = fileOpen(fsysGetFs(fsys), path)) != nil){ if(fileGetSize(file, &size)){ len = size; p = vtMemAlloc(size+1); if(fileRead(file, p, len, 0) == len){ p[len] = '\0'; r = uboxInit(p, len); } } fileDecRef(file); } fsysFsRUnlock(fsys); fsysPut(fsys); return r; } static int cmdUname(int argc, char* argv[]) { User *u, *up; int d, dflag, i, r; char *p, *uid, *uname; char *createfmt = "fsys main create /active/usr/%s %s %s d775"; char *usage = "usage: uname [-d] uname [uid|:uid|%%newname|=leader|+member|-member]"; dflag = 0; ARGBEGIN{ default: return cliError(usage); case 'd': dflag = 1; break; }ARGEND if(argc < 1){ if(!dflag) return cliError(usage); vtRLock(ubox.lock); uboxDump(ubox.box); vtRUnlock(ubox.lock); return 1; } uname = argv[0]; argc--; argv++; if(argc == 0){ vtRLock(ubox.lock); if((u = _userByUname(ubox.box, uname)) == nil){ vtRUnlock(ubox.lock); return 0; } consPrint("\t%U\n", u); vtRUnlock(ubox.lock); return 1; } vtLock(ubox.lock); u = _userByUname(ubox.box, uname); while(argc--){ if(argv[0][0] == '%'){ if(u == nil){ vtUnlock(ubox.lock); return 0; } p = &argv[0][1]; if((up = _userByUname(ubox.box, p)) != nil){ vtSetError("uname: uname '%s' already exists", up->uname); vtUnlock(ubox.lock); return 0; } for(i = 0; usersMandatory[i] != nil; i++){ if(strcmp(usersMandatory[i], uname) != 0) continue; vtSetError("uname: uname '%s' is mandatory", uname); vtUnlock(ubox.lock); return 0; } d = strlen(p) - strlen(u->uname); for(up = ubox.box->head; up != nil; up = up->next){ if(up->leader != nil){ if(strcmp(up->leader, u->uname) == 0){ vtMemFree(up->leader); up->leader = vtStrDup(p); ubox.box->len += d; } } for(i = 0; i < up->ngroup; i++){ if(strcmp(up->group[i], u->uname) != 0) continue; vtMemFree(up->group[i]); up->group[i] = vtStrDup(p); ubox.box->len += d; break; } } uboxRemUser(ubox.box, u); vtMemFree(u->uname); u->uname = vtStrDup(p); uboxAddUser(ubox.box, u); } else if(argv[0][0] == '='){ if(u == nil){ vtUnlock(ubox.lock); return 0; } if((up = _userByUname(ubox.box, &argv[0][1])) == nil){ if(argv[0][1] != '\0'){ vtUnlock(ubox.lock); return 0; } } if(u->leader != nil){ ubox.box->len -= strlen(u->leader); vtMemFree(u->leader); u->leader = nil; } if(up != nil){ u->leader = vtStrDup(up->uname); ubox.box->len += strlen(u->leader); } } else if(argv[0][0] == '+'){ if(u == nil){ vtUnlock(ubox.lock); return 0; } if((up = _userByUname(ubox.box, &argv[0][1])) == nil){ vtUnlock(ubox.lock); return 0; } if(!_groupAddMember(ubox.box, u, up->uname)){ vtUnlock(ubox.lock); return 0; } } else if(argv[0][0] == '-'){ if(u == nil){ vtUnlock(ubox.lock); return 0; } if((up = _userByUname(ubox.box, &argv[0][1])) == nil){ vtUnlock(ubox.lock); return 0; } if(!_groupRemMember(ubox.box, u, up->uname)){ vtUnlock(ubox.lock); return 0; } } else{ if(u != nil){ vtSetError("uname: uname '%s' already exists", u->uname); vtUnlock(ubox.lock); return 0; } uid = argv[0]; if(*uid == ':') uid++; if((u = _userByUid(ubox.box, uid)) != nil){ vtSetError("uname: uid '%s' already exists", u->uid); vtUnlock(ubox.lock); return 0; } u = userAlloc(uid, uname); uboxAddUser(ubox.box, u); if(argv[0][0] != ':'){ // should have an option for the mode and gid p = smprint(createfmt, uname, uname, uname); r = cliExec(p); vtMemFree(p); if(r == 0){ vtUnlock(ubox.lock); return 0; } } } argv++; } if(usersFileWrite(ubox.box) == 0){ vtUnlock(ubox.lock); return 0; } if(dflag) uboxDump(ubox.box); vtUnlock(ubox.lock); return 1; } static int cmdUsers(int argc, char* argv[]) { Ubox *box; int dflag, r, wflag; char *file; char *usage = "usage: users [-d | -r file] [-w]"; dflag = wflag = 0; file = nil; ARGBEGIN{ default: return cliError(usage); case 'd': dflag = 1; break; case 'r': file = ARGF(); if(file == nil) return cliError(usage); break; case 'w': wflag = 1; break; }ARGEND if(argc) return cliError(usage); if(dflag && file) return cliError("cannot use -d and -r together"); if(dflag) uboxInit(usersDefault, sizeof(usersDefault)); else if(file){ if(usersFileRead(file) == 0) return 0; } vtRLock(ubox.lock); box = ubox.box; consPrint("\tnuser %d len %d\n", box->nuser, box->len); r = 1; if(wflag) r = usersFileWrite(box); vtRUnlock(ubox.lock); return r; } int usersInit(void) { fmtinstall('U', userFmt); ubox.lock = vtLockAlloc(); uboxInit(usersDefault, sizeof(usersDefault)); cliAddCmd("users", cmdUsers); cliAddCmd("uname", cmdUname); return 1; }