From 6f4d00ee45693290fae042b27536b54f77b96acd Mon Sep 17 00:00:00 2001 From: David du Colombier <0intro@gmail.com> Date: Mon, 23 Sep 2013 23:00:39 +0200 Subject: fossil: import from plan 9 R=rsc https://codereview.appspot.com/7988047 --- src/cmd/fossil/9user.c | 948 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 948 insertions(+) create mode 100644 src/cmd/fossil/9user.c (limited to 'src/cmd/fossil/9user.c') diff --git a/src/cmd/fossil/9user.c b/src/cmd/fossil/9user.c new file mode 100644 index 00000000..98203d0b --- /dev/null +++ b/src/cmd/fossil/9user.c @@ -0,0 +1,948 @@ +#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; +} -- cgit v1.2.3