|  | #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 { | 
|  | RWLock	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; | 
|  | } | 
|  | } | 
|  | werrstr("uname: uid '%s' not found", uid); | 
|  | return nil; | 
|  | } | 
|  |  | 
|  | char* | 
|  | unameByUid(char* uid) | 
|  | { | 
|  | User *u; | 
|  | char *uname; | 
|  |  | 
|  | rlock(&ubox.lock); | 
|  | if((u = _userByUid(ubox.box, uid)) == nil){ | 
|  | runlock(&ubox.lock); | 
|  | return nil; | 
|  | } | 
|  | uname = vtstrdup(u->uname); | 
|  | runlock(&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; | 
|  | } | 
|  | } | 
|  | werrstr("uname: uname '%s' not found", uname); | 
|  | return nil; | 
|  | } | 
|  |  | 
|  | char* | 
|  | uidByUname(char* uname) | 
|  | { | 
|  | User *u; | 
|  | char *uid; | 
|  |  | 
|  | rlock(&ubox.lock); | 
|  | if((u = _userByUname(ubox.box, uname)) == nil){ | 
|  | runlock(&ubox.lock); | 
|  | return nil; | 
|  | } | 
|  | uid = vtstrdup(u->uid); | 
|  | runlock(&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. | 
|  | */ | 
|  |  | 
|  | rlock(&ubox.lock); | 
|  | ret = _groupMember(ubox.box, "write", uname, 1); | 
|  | runlock(&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) | 
|  | werrstr("uname: '%s' always in own group", member); | 
|  | else | 
|  | werrstr("uname: '%s' not in group '%s'", | 
|  | member, g->uname); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | vtfree(g->group[i]); | 
|  |  | 
|  | box->len -= strlen(member); | 
|  | if(g->ngroup > 1) | 
|  | box->len--; | 
|  | g->ngroup--; | 
|  | switch(g->ngroup){ | 
|  | case 0: | 
|  | vtfree(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 = vtrealloc(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) | 
|  | werrstr("uname: '%s' always in own group", member); | 
|  | else | 
|  | werrstr("uname: '%s' already in group '%s'", | 
|  | member, g->uname); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | g->group = vtrealloc(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; | 
|  |  | 
|  | rlock(&ubox.lock); | 
|  | r = _groupMember(ubox.box, group, member, 0); | 
|  | runlock(&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; | 
|  |  | 
|  | rlock(&ubox.lock); | 
|  | if((g = _userByUid(ubox.box, group)) == nil){ | 
|  | runlock(&ubox.lock); | 
|  | return 0; | 
|  | } | 
|  | if(g->leader != nil){ | 
|  | if(strcmp(g->leader, member) == 0){ | 
|  | runlock(&ubox.lock); | 
|  | return 1; | 
|  | } | 
|  | r = 0; | 
|  | } | 
|  | else | 
|  | r = _groupMember(ubox.box, group, member, 0); | 
|  | runlock(&ubox.lock); | 
|  |  | 
|  | return r; | 
|  | } | 
|  |  | 
|  | static void | 
|  | userFree(User* u) | 
|  | { | 
|  | int i; | 
|  |  | 
|  | vtfree(u->uid); | 
|  | vtfree(u->uname); | 
|  | if(u->leader != nil) | 
|  | vtfree(u->leader); | 
|  | if(u->ngroup){ | 
|  | for(i = 0; i < u->ngroup; i++) | 
|  | vtfree(u->group[i]); | 
|  | vtfree(u->group); | 
|  | } | 
|  | vtfree(u); | 
|  | } | 
|  |  | 
|  | static User* | 
|  | userAlloc(char* uid, char* uname) | 
|  | { | 
|  | User *u; | 
|  |  | 
|  | u = vtmallocz(sizeof(User)); | 
|  | u->uid = vtstrdup(uid); | 
|  | u->uname = vtstrdup(uname); | 
|  |  | 
|  | return u; | 
|  | } | 
|  |  | 
|  | int | 
|  | validUserName(char* name) | 
|  | { | 
|  | Rune *r; | 
|  | #ifdef PLAN9PORT | 
|  | static Rune invalid[] = {'#', ':', ',', '(', ')', '\0'}; | 
|  | #else | 
|  | static Rune invalid[] = L"#:,()"; | 
|  | #endif | 
|  |  | 
|  | 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 = vtmalloc(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); | 
|  | vtfree(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); | 
|  | } | 
|  | vtfree(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 = vtmalloc(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 = vtmallocz((nline+2)*sizeof(char*)); | 
|  | if((i = gettokens(buf, line, nline+2, "\n")) != nline){ | 
|  | fprint(2, "nline %d (%d) botch\n", nline, i); | 
|  | vtfree(line); | 
|  | vtfree(buf); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Everything is updated in a local Ubox until verified. | 
|  | */ | 
|  | box = vtmallocz(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]); | 
|  | vtfree(s); | 
|  | continue; | 
|  | } | 
|  | if(*f[0] == '\0' || *f[1] == '\0'){ | 
|  | fprint(2, "bad line '%s'\n", line[i]); | 
|  | vtfree(s); | 
|  | continue; | 
|  | } | 
|  | if(!validUserName(f[0])){ | 
|  | fprint(2, "invalid uid '%s'\n", f[0]); | 
|  | vtfree(s); | 
|  | continue; | 
|  | } | 
|  | if(_userByUid(box, f[0]) != nil){ | 
|  | fprint(2, "duplicate uid '%s'\n", f[0]); | 
|  | vtfree(s); | 
|  | continue; | 
|  | } | 
|  | if(!validUserName(f[1])){ | 
|  | fprint(2, "invalid uname '%s'\n", f[0]); | 
|  | vtfree(s); | 
|  | continue; | 
|  | } | 
|  | if(_userByUname(box, f[1]) != nil){ | 
|  | fprint(2, "duplicate uname '%s'\n", f[1]); | 
|  | vtfree(s); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | u = userAlloc(f[0], f[1]); | 
|  | uboxAddUser(box, u); | 
|  | line[nuser] = line[i]; | 
|  | nuser++; | 
|  |  | 
|  | vtfree(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 | 
|  | } | 
|  | } | 
|  |  | 
|  | vtfree(s); | 
|  | } | 
|  |  | 
|  | vtfree(line); | 
|  | vtfree(buf); | 
|  |  | 
|  | for(i = 0; usersMandatory[i] != nil; i++){ | 
|  | if((u = _userByUid(box, usersMandatory[i])) == nil){ | 
|  | werrstr("user '%s' is mandatory", usersMandatory[i]); | 
|  | uboxFree(box); | 
|  | return 0; | 
|  | } | 
|  | if(strcmp(u->uid, u->uname) != 0){ | 
|  | werrstr("uid/uname for user '%s' must match", | 
|  | usersMandatory[i]); | 
|  | uboxFree(box); | 
|  | return 0; | 
|  | } | 
|  | } | 
|  |  | 
|  | wlock(&ubox.lock); | 
|  | obox = ubox.box; | 
|  | ubox.box = box; | 
|  | wunlock(&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 = vtmalloc(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); | 
|  | rlock(&ubox.lock); | 
|  | uboxDump(ubox.box); | 
|  | runlock(&ubox.lock); | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | uname = argv[0]; | 
|  | argc--; argv++; | 
|  |  | 
|  | if(argc == 0){ | 
|  | rlock(&ubox.lock); | 
|  | if((u = _userByUname(ubox.box, uname)) == nil){ | 
|  | runlock(&ubox.lock); | 
|  | return 0; | 
|  | } | 
|  | consPrint("\t%U\n", u); | 
|  | runlock(&ubox.lock); | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | wlock(&ubox.lock); | 
|  | u = _userByUname(ubox.box, uname); | 
|  | while(argc--){ | 
|  | if(argv[0][0] == '%'){ | 
|  | if(u == nil){ | 
|  | wunlock(&ubox.lock); | 
|  | return 0; | 
|  | } | 
|  | p = &argv[0][1]; | 
|  | if((up = _userByUname(ubox.box, p)) != nil){ | 
|  | werrstr("uname: uname '%s' already exists", | 
|  | up->uname); | 
|  | wunlock(&ubox.lock); | 
|  | return 0; | 
|  | } | 
|  | for(i = 0; usersMandatory[i] != nil; i++){ | 
|  | if(strcmp(usersMandatory[i], uname) != 0) | 
|  | continue; | 
|  | werrstr("uname: uname '%s' is mandatory", | 
|  | uname); | 
|  | wunlock(&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){ | 
|  | vtfree(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; | 
|  | vtfree(up->group[i]); | 
|  | up->group[i] = vtstrdup(p); | 
|  | ubox.box->len += d; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | uboxRemUser(ubox.box, u); | 
|  | vtfree(u->uname); | 
|  | u->uname = vtstrdup(p); | 
|  | uboxAddUser(ubox.box, u); | 
|  | } | 
|  | else if(argv[0][0] == '='){ | 
|  | if(u == nil){ | 
|  | wunlock(&ubox.lock); | 
|  | return 0; | 
|  | } | 
|  | if((up = _userByUname(ubox.box, &argv[0][1])) == nil){ | 
|  | if(argv[0][1] != '\0'){ | 
|  | wunlock(&ubox.lock); | 
|  | return 0; | 
|  | } | 
|  | } | 
|  | if(u->leader != nil){ | 
|  | ubox.box->len -= strlen(u->leader); | 
|  | vtfree(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){ | 
|  | wunlock(&ubox.lock); | 
|  | return 0; | 
|  | } | 
|  | if((up = _userByUname(ubox.box, &argv[0][1])) == nil){ | 
|  | wunlock(&ubox.lock); | 
|  | return 0; | 
|  | } | 
|  | if(!_groupAddMember(ubox.box, u, up->uname)){ | 
|  | wunlock(&ubox.lock); | 
|  | return 0; | 
|  | } | 
|  | } | 
|  | else if(argv[0][0] == '-'){ | 
|  | if(u == nil){ | 
|  | wunlock(&ubox.lock); | 
|  | return 0; | 
|  | } | 
|  | if((up = _userByUname(ubox.box, &argv[0][1])) == nil){ | 
|  | wunlock(&ubox.lock); | 
|  | return 0; | 
|  | } | 
|  | if(!_groupRemMember(ubox.box, u, up->uname)){ | 
|  | wunlock(&ubox.lock); | 
|  | return 0; | 
|  | } | 
|  | } | 
|  | else{ | 
|  | if(u != nil){ | 
|  | werrstr("uname: uname '%s' already exists", | 
|  | u->uname); | 
|  | wunlock(&ubox.lock); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | uid = argv[0]; | 
|  | if(*uid == ':') | 
|  | uid++; | 
|  | if((u = _userByUid(ubox.box, uid)) != nil){ | 
|  | werrstr("uname: uid '%s' already exists", | 
|  | u->uid); | 
|  | wunlock(&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); | 
|  | vtfree(p); | 
|  | if(r == 0){ | 
|  | wunlock(&ubox.lock); | 
|  | return 0; | 
|  | } | 
|  | } | 
|  | } | 
|  | argv++; | 
|  | } | 
|  |  | 
|  | if(usersFileWrite(ubox.box) == 0){ | 
|  | wunlock(&ubox.lock); | 
|  | return 0; | 
|  | } | 
|  | if(dflag) | 
|  | uboxDump(ubox.box); | 
|  | wunlock(&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; | 
|  | } | 
|  |  | 
|  | rlock(&ubox.lock); | 
|  | box = ubox.box; | 
|  | consPrint("\tnuser %d len %d\n", box->nuser, box->len); | 
|  |  | 
|  | r = 1; | 
|  | if(wflag) | 
|  | r = usersFileWrite(box); | 
|  | runlock(&ubox.lock); | 
|  | return r; | 
|  | } | 
|  |  | 
|  | int | 
|  | usersInit(void) | 
|  | { | 
|  | fmtinstall('U', userFmt); | 
|  |  | 
|  | uboxInit(usersDefault, sizeof(usersDefault)); | 
|  |  | 
|  | cliAddCmd("users", cmdUsers); | 
|  | cliAddCmd("uname", cmdUname); | 
|  |  | 
|  | return 1; | 
|  | } |