9 typedef struct Ubox Ubox;
10 typedef struct User User;
20 User* ihash; /* lookup by .uid */
21 User* nhash; /* lookup by .uname */
24 #pragma varargck type "U" User*
32 User* ihash[NUserHash]; /* lookup by .uid */
33 User* nhash[NUserHash]; /* lookup by .uname */
42 static char usersDefault[] = {
47 "glenda:glenda:glenda:\n"
50 static char* usersMandatory[] = {
59 char* unamenone = "none";
60 char* uidnoworld = "noworld";
69 for(p = (uchar*)s; *p != '\0'; p++)
72 return hash % NUserHash;
76 _userByUid(Ubox* box, char* uid)
81 for(u = box->ihash[userHash(uid)]; u != nil; u = u->ihash){
82 if(strcmp(u->uid, uid) == 0)
86 werrstr("uname: uid '%s' not found", uid);
97 if((u = _userByUid(ubox.box, uid)) == nil){
101 uname = vtstrdup(u->uname);
108 _userByUname(Ubox* box, char* uname)
113 for(u = box->nhash[userHash(uname)]; u != nil; u = u->nhash){
114 if(strcmp(u->uname, uname) == 0)
118 werrstr("uname: uname '%s' not found", uname);
123 uidByUname(char* uname)
129 if((u = _userByUname(ubox.box, uname)) == nil){
133 uid = vtstrdup(u->uid);
140 _groupMember(Ubox* box, char* group, char* member, int whenNoGroup)
146 * Is 'member' a member of 'group'?
147 * Note that 'group' is a 'uid' and not a 'uname'.
148 * A 'member' is automatically in their own group.
150 if((g = _userByUid(box, group)) == nil)
152 if((m = _userByUname(box, member)) == nil)
156 for(i = 0; i < g->ngroup; i++){
157 if(strcmp(g->group[i], member) == 0)
164 groupWriteMember(char* uname)
169 * If there is a ``write'' group, then only its members can write
170 * to the file system, no matter what the permission bits say.
172 * To users not in the ``write'' group, the file system appears
173 * read only. This is used to serve sources.cs.bell-labs.com
176 * Note that if there is no ``write'' group, then this routine
177 * makes it look like everyone is a member -- the opposite
178 * of what groupMember does.
180 * We use this for sources.cs.bell-labs.com.
181 * If this slows things down too much on systems that don't
182 * use this functionality, we could cache the write group lookup.
186 ret = _groupMember(ubox.box, "write", uname, 1);
192 _groupRemMember(Ubox* box, User* g, char* member)
196 if(_userByUname(box, member) == nil)
199 for(i = 0; i < g->ngroup; i++){
200 if(strcmp(g->group[i], member) == 0)
204 if(strcmp(g->uname, member) == 0)
205 werrstr("uname: '%s' always in own group", member);
207 werrstr("uname: '%s' not in group '%s'",
214 box->len -= strlen(member);
224 for(; i < g->ngroup; i++)
225 g->group[i] = g->group[i+1];
226 g->group[i] = nil; /* prevent accidents */
227 g->group = vtrealloc(g->group, g->ngroup * sizeof(char*));
235 _groupAddMember(Ubox* box, User* g, char* member)
239 if((u = _userByUname(box, member)) == nil)
241 if(_groupMember(box, g->uid, u->uname, 0)){
242 if(strcmp(g->uname, member) == 0)
243 werrstr("uname: '%s' always in own group", member);
245 werrstr("uname: '%s' already in group '%s'",
250 g->group = vtrealloc(g->group, (g->ngroup+1)*sizeof(char*));
251 g->group[g->ngroup] = vtstrdup(member);
252 box->len += strlen(member);
261 groupMember(char* group, char* member)
269 r = _groupMember(ubox.box, group, member, 0);
276 groupLeader(char* group, char* member)
282 * Is 'member' the leader of 'group'?
283 * Note that 'group' is a 'uid' and not a 'uname'.
284 * Uname 'none' cannot be a group leader.
286 if(strcmp(member, unamenone) == 0 || group == nil)
290 if((g = _userByUid(ubox.box, group)) == nil){
294 if(g->leader != nil){
295 if(strcmp(g->leader, member) == 0){
302 r = _groupMember(ubox.box, group, member, 0);
318 for(i = 0; i < u->ngroup; i++)
326 userAlloc(char* uid, char* uname)
330 u = vtmallocz(sizeof(User));
331 u->uid = vtstrdup(uid);
332 u->uname = vtstrdup(uname);
338 validUserName(char* name)
342 static Rune invalid[] = {'#', ':', ',', '(', ')', '\0'};
344 static Rune invalid[] = L"#:,()";
347 for(r = invalid; *r != '\0'; r++){
348 if(utfrune(name, *r))
360 u = va_arg(fmt->args, User*);
362 r = fmtprint(fmt, "%s:%s:", u->uid, u->uname);
364 r += fmtprint(fmt, u->leader);
365 r += fmtprint(fmt, ":");
367 r += fmtprint(fmt, u->group[0]);
368 for(i = 1; i < u->ngroup; i++)
369 r += fmtprint(fmt, ",%s", u->group[i]);
376 usersFileWrite(Ubox* box)
385 if((fsys = fsysGet("main")) == nil)
388 fs = fsysGetFs(fsys);
392 * the owner/group/permissions need to be thought out.
395 if((dir = fileOpen(fs, "/active")) == nil)
397 if((file = fileWalk(dir, uidadm)) == nil)
398 file = fileCreate(dir, uidadm, ModeDir|0775, uidadm);
403 if((file = fileWalk(dir, "users")) == nil)
404 file = fileCreate(dir, "users", 0664, uidadm);
408 if(!fileTruncate(file, uidadm))
411 p = s = vtmalloc(box->len+1);
413 for(u = box->head; u != nil; u = u->next){
414 p += snprint(p, q-p, "%s:%s:", u->uid, u->uname);
416 p+= snprint(p, q-p, u->leader);
417 p += snprint(p, q-p, ":");
419 p += snprint(p, q-p, u->group[0]);
420 for(i = 1; i < u->ngroup; i++)
421 p += snprint(p, q-p, ",%s", u->group[i]);
423 p += snprint(p, q-p, "\n");
425 r = fileWrite(file, s, box->len, 0, uidadm);
439 uboxRemUser(Ubox* box, User *u)
443 h = &box->ihash[userHash(u->uid)];
444 for(up = *h; up != nil && up != u; up = up->ihash)
448 box->len -= strlen(u->uid);
450 h = &box->nhash[userHash(u->uname)];
451 for(up = *h; up != nil && up != u; up = up->nhash)
455 box->len -= strlen(u->uname);
458 for(up = *h; up != nil && strcmp(up->uid, u->uid) != 0; up = up->next)
469 uboxAddUser(Ubox* box, User* u)
473 h = &box->ihash[userHash(u->uid)];
476 box->len += strlen(u->uid);
478 h = &box->nhash[userHash(u->uname)];
481 box->len += strlen(u->uname);
484 for(up = *h; up != nil && strcmp(up->uid, u->uid) < 0; up = up->next)
498 consPrint("nuser %d len = %d\n", box->nuser, box->len);
500 for(u = box->head; u != nil; u = u->next)
501 consPrint("%U\n", u);
509 for(u = box->head; u != nil; u = next){
517 uboxInit(char* users, int len)
521 int blank, comment, i, nline, nuser;
522 char *buf, *f[5], **line, *p, *q, *s;
525 * Strip out whitespace and comments.
526 * Note that comments are pointless, they disappear
527 * when the server writes the database back out.
532 s = p = buf = vtmalloc(len+1);
533 for(q = users; *q != '\0'; q++){
534 if(*q == '\r' || *q == '\t' || *q == ' ')
556 line = vtmallocz((nline+2)*sizeof(char*));
557 if((i = gettokens(buf, line, nline+2, "\n")) != nline){
558 fprint(2, "nline %d (%d) botch\n", nline, i);
565 * Everything is updated in a local Ubox until verified.
567 box = vtmallocz(sizeof(Ubox));
570 * First pass - check format, check for duplicates
571 * and enter in hash buckets.
574 for(i = 0; i < nline; i++){
575 s = vtstrdup(line[i]);
576 if(getfields(s, f, nelem(f), 0, ":") != 4){
577 fprint(2, "bad line '%s'\n", line[i]);
581 if(*f[0] == '\0' || *f[1] == '\0'){
582 fprint(2, "bad line '%s'\n", line[i]);
586 if(!validUserName(f[0])){
587 fprint(2, "invalid uid '%s'\n", f[0]);
591 if(_userByUid(box, f[0]) != nil){
592 fprint(2, "duplicate uid '%s'\n", f[0]);
596 if(!validUserName(f[1])){
597 fprint(2, "invalid uname '%s'\n", f[0]);
601 if(_userByUname(box, f[1]) != nil){
602 fprint(2, "duplicate uname '%s'\n", f[1]);
607 u = userAlloc(f[0], f[1]);
609 line[nuser] = line[i];
614 assert(box->nuser == nuser);
617 * Second pass - fill in leader and group information.
619 for(i = 0; i < nuser; i++){
620 s = vtstrdup(line[i]);
621 getfields(s, f, nelem(f), 0, ":");
623 assert(g = _userByUname(box, f[1]));
625 if((u = _userByUname(box, f[2])) == nil)
626 g->leader = vtstrdup(g->uname);
628 g->leader = vtstrdup(u->uname);
629 box->len += strlen(g->leader);
631 for(p = f[3]; p != nil; p = q){
632 if((q = utfrune(p, L',')) != nil)
634 if(!_groupAddMember(box, g, p)){
635 // print/log error here
645 for(i = 0; usersMandatory[i] != nil; i++){
646 if((u = _userByUid(box, usersMandatory[i])) == nil){
647 werrstr("user '%s' is mandatory", usersMandatory[i]);
651 if(strcmp(u->uid, u->uname) != 0){
652 werrstr("uid/uname for user '%s' must match",
671 usersFileRead(char* path)
679 if((fsys = fsysGet("main")) == nil)
684 path = "/active/adm/users";
687 if((file = fileOpen(fsysGetFs(fsys), path)) != nil){
688 if(fileGetSize(file, &size)){
690 p = vtmalloc(size+1);
691 if(fileRead(file, p, len, 0) == len){
693 r = uboxInit(p, len);
706 cmdUname(int argc, char* argv[])
710 char *p, *uid, *uname;
711 char *createfmt = "fsys main create /active/usr/%s %s %s d775";
712 char *usage = "usage: uname [-d] uname [uid|:uid|%%newname|=leader|+member|-member]";
718 return cliError(usage);
726 return cliError(usage);
738 if((u = _userByUname(ubox.box, uname)) == nil){
742 consPrint("\t%U\n", u);
748 u = _userByUname(ubox.box, uname);
750 if(argv[0][0] == '%'){
756 if((up = _userByUname(ubox.box, p)) != nil){
757 werrstr("uname: uname '%s' already exists",
762 for(i = 0; usersMandatory[i] != nil; i++){
763 if(strcmp(usersMandatory[i], uname) != 0)
765 werrstr("uname: uname '%s' is mandatory",
771 d = strlen(p) - strlen(u->uname);
772 for(up = ubox.box->head; up != nil; up = up->next){
773 if(up->leader != nil){
774 if(strcmp(up->leader, u->uname) == 0){
776 up->leader = vtstrdup(p);
780 for(i = 0; i < up->ngroup; i++){
781 if(strcmp(up->group[i], u->uname) != 0)
783 vtfree(up->group[i]);
784 up->group[i] = vtstrdup(p);
790 uboxRemUser(ubox.box, u);
792 u->uname = vtstrdup(p);
793 uboxAddUser(ubox.box, u);
795 else if(argv[0][0] == '='){
800 if((up = _userByUname(ubox.box, &argv[0][1])) == nil){
801 if(argv[0][1] != '\0'){
806 if(u->leader != nil){
807 ubox.box->len -= strlen(u->leader);
812 u->leader = vtstrdup(up->uname);
813 ubox.box->len += strlen(u->leader);
816 else if(argv[0][0] == '+'){
821 if((up = _userByUname(ubox.box, &argv[0][1])) == nil){
825 if(!_groupAddMember(ubox.box, u, up->uname)){
830 else if(argv[0][0] == '-'){
835 if((up = _userByUname(ubox.box, &argv[0][1])) == nil){
839 if(!_groupRemMember(ubox.box, u, up->uname)){
846 werrstr("uname: uname '%s' already exists",
855 if((u = _userByUid(ubox.box, uid)) != nil){
856 werrstr("uname: uid '%s' already exists",
862 u = userAlloc(uid, uname);
863 uboxAddUser(ubox.box, u);
864 if(argv[0][0] != ':'){
865 // should have an option for the mode and gid
866 p = smprint(createfmt, uname, uname, uname);
878 if(usersFileWrite(ubox.box) == 0){
890 cmdUsers(int argc, char* argv[])
895 char *usage = "usage: users [-d | -r file] [-w]";
902 return cliError(usage);
909 return cliError(usage);
917 return cliError(usage);
920 return cliError("cannot use -d and -r together");
923 uboxInit(usersDefault, sizeof(usersDefault));
925 if(usersFileRead(file) == 0)
931 consPrint("\tnuser %d len %d\n", box->nuser, box->len);
935 r = usersFileWrite(box);
943 fmtinstall('U', userFmt);
945 uboxInit(usersDefault, sizeof(usersDefault));
947 cliAddCmd("users", cmdUsers);
948 cliAddCmd("uname", cmdUname);