Blob


1 #include "stdinc.h"
3 #include "9.h"
5 enum {
6 NUserHash = 1009,
7 };
9 typedef struct Ubox Ubox;
10 typedef struct User User;
12 struct User {
13 char* uid;
14 char* uname;
15 char* leader;
16 char** group;
17 int ngroup;
19 User* next; /* */
20 User* ihash; /* lookup by .uid */
21 User* nhash; /* lookup by .uname */
22 };
24 #pragma varargck type "U" User*
26 struct Ubox {
27 User* head;
28 User* tail;
29 int nuser;
30 int len;
32 User* ihash[NUserHash]; /* lookup by .uid */
33 User* nhash[NUserHash]; /* lookup by .uname */
34 };
36 static struct {
37 RWLock lock;
39 Ubox* box;
40 } ubox;
42 static char usersDefault[] = {
43 "adm:adm:adm:sys\n"
44 "none:none::\n"
45 "noworld:noworld::\n"
46 "sys:sys::glenda\n"
47 "glenda:glenda:glenda:\n"
48 };
50 static char* usersMandatory[] = {
51 "adm",
52 "none",
53 "noworld",
54 "sys",
55 nil,
56 };
58 char* uidadm = "adm";
59 char* unamenone = "none";
60 char* uidnoworld = "noworld";
62 static u32int
63 userHash(char* s)
64 {
65 uchar *p;
66 u32int hash;
68 hash = 0;
69 for(p = (uchar*)s; *p != '\0'; p++)
70 hash = hash*7 + *p;
72 return hash % NUserHash;
73 }
75 static User*
76 _userByUid(Ubox* box, char* uid)
77 {
78 User *u;
80 if(box != nil){
81 for(u = box->ihash[userHash(uid)]; u != nil; u = u->ihash){
82 if(strcmp(u->uid, uid) == 0)
83 return u;
84 }
85 }
86 werrstr("uname: uid '%s' not found", uid);
87 return nil;
88 }
90 char*
91 unameByUid(char* uid)
92 {
93 User *u;
94 char *uname;
96 rlock(&ubox.lock);
97 if((u = _userByUid(ubox.box, uid)) == nil){
98 runlock(&ubox.lock);
99 return nil;
101 uname = vtstrdup(u->uname);
102 runlock(&ubox.lock);
104 return uname;
107 static User*
108 _userByUname(Ubox* box, char* uname)
110 User *u;
112 if(box != nil){
113 for(u = box->nhash[userHash(uname)]; u != nil; u = u->nhash){
114 if(strcmp(u->uname, uname) == 0)
115 return u;
118 werrstr("uname: uname '%s' not found", uname);
119 return nil;
122 char*
123 uidByUname(char* uname)
125 User *u;
126 char *uid;
128 rlock(&ubox.lock);
129 if((u = _userByUname(ubox.box, uname)) == nil){
130 runlock(&ubox.lock);
131 return nil;
133 uid = vtstrdup(u->uid);
134 runlock(&ubox.lock);
136 return uid;
139 static int
140 _groupMember(Ubox* box, char* group, char* member, int whenNoGroup)
142 int i;
143 User *g, *m;
145 /*
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.
149 */
150 if((g = _userByUid(box, group)) == nil)
151 return whenNoGroup;
152 if((m = _userByUname(box, member)) == nil)
153 return 0;
154 if(m == g)
155 return 1;
156 for(i = 0; i < g->ngroup; i++){
157 if(strcmp(g->group[i], member) == 0)
158 return 1;
160 return 0;
163 int
164 groupWriteMember(char* uname)
166 int ret;
168 /*
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
174 * to the world.
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.
183 */
185 rlock(&ubox.lock);
186 ret = _groupMember(ubox.box, "write", uname, 1);
187 runlock(&ubox.lock);
188 return ret;
191 static int
192 _groupRemMember(Ubox* box, User* g, char* member)
194 int i;
196 if(_userByUname(box, member) == nil)
197 return 0;
199 for(i = 0; i < g->ngroup; i++){
200 if(strcmp(g->group[i], member) == 0)
201 break;
203 if(i >= g->ngroup){
204 if(strcmp(g->uname, member) == 0)
205 werrstr("uname: '%s' always in own group", member);
206 else
207 werrstr("uname: '%s' not in group '%s'",
208 member, g->uname);
209 return 0;
212 vtfree(g->group[i]);
214 box->len -= strlen(member);
215 if(g->ngroup > 1)
216 box->len--;
217 g->ngroup--;
218 switch(g->ngroup){
219 case 0:
220 vtfree(g->group);
221 g->group = nil;
222 break;
223 default:
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*));
228 break;
231 return 1;
234 static int
235 _groupAddMember(Ubox* box, User* g, char* member)
237 User *u;
239 if((u = _userByUname(box, member)) == nil)
240 return 0;
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);
244 else
245 werrstr("uname: '%s' already in group '%s'",
246 member, g->uname);
247 return 0;
250 g->group = vtrealloc(g->group, (g->ngroup+1)*sizeof(char*));
251 g->group[g->ngroup] = vtstrdup(member);
252 box->len += strlen(member);
253 g->ngroup++;
254 if(g->ngroup > 1)
255 box->len++;
257 return 1;
260 int
261 groupMember(char* group, char* member)
263 int r;
265 if(group == nil)
266 return 0;
268 rlock(&ubox.lock);
269 r = _groupMember(ubox.box, group, member, 0);
270 runlock(&ubox.lock);
272 return r;
275 int
276 groupLeader(char* group, char* member)
278 int r;
279 User *g;
281 /*
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.
285 */
286 if(strcmp(member, unamenone) == 0 || group == nil)
287 return 0;
289 rlock(&ubox.lock);
290 if((g = _userByUid(ubox.box, group)) == nil){
291 runlock(&ubox.lock);
292 return 0;
294 if(g->leader != nil){
295 if(strcmp(g->leader, member) == 0){
296 runlock(&ubox.lock);
297 return 1;
299 r = 0;
301 else
302 r = _groupMember(ubox.box, group, member, 0);
303 runlock(&ubox.lock);
305 return r;
308 static void
309 userFree(User* u)
311 int i;
313 vtfree(u->uid);
314 vtfree(u->uname);
315 if(u->leader != nil)
316 vtfree(u->leader);
317 if(u->ngroup){
318 for(i = 0; i < u->ngroup; i++)
319 vtfree(u->group[i]);
320 vtfree(u->group);
322 vtfree(u);
325 static User*
326 userAlloc(char* uid, char* uname)
328 User *u;
330 u = vtmallocz(sizeof(User));
331 u->uid = vtstrdup(uid);
332 u->uname = vtstrdup(uname);
334 return u;
337 int
338 validUserName(char* name)
340 Rune *r;
341 #ifdef PLAN9PORT
342 static Rune invalid[] = {'#', ':', ',', '(', ')', '\0'};
343 #else
344 static Rune invalid[] = L"#:,()";
345 #endif
347 for(r = invalid; *r != '\0'; r++){
348 if(utfrune(name, *r))
349 return 0;
351 return 1;
354 static int
355 userFmt(Fmt* fmt)
357 User *u;
358 int i, r;
360 u = va_arg(fmt->args, User*);
362 r = fmtprint(fmt, "%s:%s:", u->uid, u->uname);
363 if(u->leader != nil)
364 r += fmtprint(fmt, u->leader);
365 r += fmtprint(fmt, ":");
366 if(u->ngroup){
367 r += fmtprint(fmt, u->group[0]);
368 for(i = 1; i < u->ngroup; i++)
369 r += fmtprint(fmt, ",%s", u->group[i]);
372 return r;
375 static int
376 usersFileWrite(Ubox* box)
378 Fs *fs;
379 User *u;
380 int i, r;
381 Fsys *fsys;
382 char *p, *q, *s;
383 File *dir, *file;
385 if((fsys = fsysGet("main")) == nil)
386 return 0;
387 fsysFsRlock(fsys);
388 fs = fsysGetFs(fsys);
390 /*
391 * BUG:
392 * the owner/group/permissions need to be thought out.
393 */
394 r = 0;
395 if((dir = fileOpen(fs, "/active")) == nil)
396 goto tidy0;
397 if((file = fileWalk(dir, uidadm)) == nil)
398 file = fileCreate(dir, uidadm, ModeDir|0775, uidadm);
399 fileDecRef(dir);
400 if(file == nil)
401 goto tidy;
402 dir = file;
403 if((file = fileWalk(dir, "users")) == nil)
404 file = fileCreate(dir, "users", 0664, uidadm);
405 fileDecRef(dir);
406 if(file == nil)
407 goto tidy;
408 if(!fileTruncate(file, uidadm))
409 goto tidy;
411 p = s = vtmalloc(box->len+1);
412 q = p + 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);
415 if(u->leader != nil)
416 p+= snprint(p, q-p, u->leader);
417 p += snprint(p, q-p, ":");
418 if(u->ngroup){
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);
426 vtfree(s);
428 tidy:
429 if(file != nil)
430 fileDecRef(file);
431 tidy0:
432 fsysFsRUnlock(fsys);
433 fsysPut(fsys);
435 return r;
438 static void
439 uboxRemUser(Ubox* box, User *u)
441 User **h, *up;
443 h = &box->ihash[userHash(u->uid)];
444 for(up = *h; up != nil && up != u; up = up->ihash)
445 h = &up->ihash;
446 assert(up == u);
447 *h = 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)
452 h = &up->nhash;
453 assert(up == u);
454 *h = up->nhash;
455 box->len -= strlen(u->uname);
457 h = &box->head;
458 for(up = *h; up != nil && strcmp(up->uid, u->uid) != 0; up = up->next)
459 h = &up->next;
460 assert(up == u);
461 *h = u->next;
462 u->next = nil;
464 box->len -= 4;
465 box->nuser--;
468 static void
469 uboxAddUser(Ubox* box, User* u)
471 User **h, *up;
473 h = &box->ihash[userHash(u->uid)];
474 u->ihash = *h;
475 *h = u;
476 box->len += strlen(u->uid);
478 h = &box->nhash[userHash(u->uname)];
479 u->nhash = *h;
480 *h = u;
481 box->len += strlen(u->uname);
483 h = &box->head;
484 for(up = *h; up != nil && strcmp(up->uid, u->uid) < 0; up = up->next)
485 h = &up->next;
486 u->next = *h;
487 *h = u;
489 box->len += 4;
490 box->nuser++;
493 static void
494 uboxDump(Ubox* box)
496 User* u;
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);
504 static void
505 uboxFree(Ubox* box)
507 User *next, *u;
509 for(u = box->head; u != nil; u = next){
510 next = u->next;
511 userFree(u);
513 vtfree(box);
516 static int
517 uboxInit(char* users, int len)
519 User *g, *u;
520 Ubox *box, *obox;
521 int blank, comment, i, nline, nuser;
522 char *buf, *f[5], **line, *p, *q, *s;
524 /*
525 * Strip out whitespace and comments.
526 * Note that comments are pointless, they disappear
527 * when the server writes the database back out.
528 */
529 blank = 1;
530 comment = nline = 0;
532 s = p = buf = vtmalloc(len+1);
533 for(q = users; *q != '\0'; q++){
534 if(*q == '\r' || *q == '\t' || *q == ' ')
535 continue;
536 if(*q == '\n'){
537 if(!blank){
538 if(p != s){
539 *p++ = '\n';
540 nline++;
541 s = p;
543 blank = 1;
545 comment = 0;
546 continue;
548 if(*q == '#')
549 comment = 1;
550 blank = 0;
551 if(!comment)
552 *p++ = *q;
554 *p = '\0';
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);
559 vtfree(line);
560 vtfree(buf);
561 return 0;
564 /*
565 * Everything is updated in a local Ubox until verified.
566 */
567 box = vtmallocz(sizeof(Ubox));
569 /*
570 * First pass - check format, check for duplicates
571 * and enter in hash buckets.
572 */
573 nuser = 0;
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]);
578 vtfree(s);
579 continue;
581 if(*f[0] == '\0' || *f[1] == '\0'){
582 fprint(2, "bad line '%s'\n", line[i]);
583 vtfree(s);
584 continue;
586 if(!validUserName(f[0])){
587 fprint(2, "invalid uid '%s'\n", f[0]);
588 vtfree(s);
589 continue;
591 if(_userByUid(box, f[0]) != nil){
592 fprint(2, "duplicate uid '%s'\n", f[0]);
593 vtfree(s);
594 continue;
596 if(!validUserName(f[1])){
597 fprint(2, "invalid uname '%s'\n", f[0]);
598 vtfree(s);
599 continue;
601 if(_userByUname(box, f[1]) != nil){
602 fprint(2, "duplicate uname '%s'\n", f[1]);
603 vtfree(s);
604 continue;
607 u = userAlloc(f[0], f[1]);
608 uboxAddUser(box, u);
609 line[nuser] = line[i];
610 nuser++;
612 vtfree(s);
614 assert(box->nuser == nuser);
616 /*
617 * Second pass - fill in leader and group information.
618 */
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]));
624 if(*f[2] != '\0'){
625 if((u = _userByUname(box, f[2])) == nil)
626 g->leader = vtstrdup(g->uname);
627 else
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)
633 *q++ = '\0';
634 if(!_groupAddMember(box, g, p)){
635 // print/log error here
639 vtfree(s);
642 vtfree(line);
643 vtfree(buf);
645 for(i = 0; usersMandatory[i] != nil; i++){
646 if((u = _userByUid(box, usersMandatory[i])) == nil){
647 werrstr("user '%s' is mandatory", usersMandatory[i]);
648 uboxFree(box);
649 return 0;
651 if(strcmp(u->uid, u->uname) != 0){
652 werrstr("uid/uname for user '%s' must match",
653 usersMandatory[i]);
654 uboxFree(box);
655 return 0;
659 wlock(&ubox.lock);
660 obox = ubox.box;
661 ubox.box = box;
662 wunlock(&ubox.lock);
664 if(obox != nil)
665 uboxFree(obox);
667 return 1;
670 int
671 usersFileRead(char* path)
673 char *p;
674 File *file;
675 Fsys *fsys;
676 int len, r;
677 uvlong size;
679 if((fsys = fsysGet("main")) == nil)
680 return 0;
681 fsysFsRlock(fsys);
683 if(path == nil)
684 path = "/active/adm/users";
686 r = 0;
687 if((file = fileOpen(fsysGetFs(fsys), path)) != nil){
688 if(fileGetSize(file, &size)){
689 len = size;
690 p = vtmalloc(size+1);
691 if(fileRead(file, p, len, 0) == len){
692 p[len] = '\0';
693 r = uboxInit(p, len);
696 fileDecRef(file);
699 fsysFsRUnlock(fsys);
700 fsysPut(fsys);
702 return r;
705 static int
706 cmdUname(int argc, char* argv[])
708 User *u, *up;
709 int d, dflag, i, r;
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]";
714 dflag = 0;
716 ARGBEGIN{
717 default:
718 return cliError(usage);
719 case 'd':
720 dflag = 1;
721 break;
722 }ARGEND
724 if(argc < 1){
725 if(!dflag)
726 return cliError(usage);
727 rlock(&ubox.lock);
728 uboxDump(ubox.box);
729 runlock(&ubox.lock);
730 return 1;
733 uname = argv[0];
734 argc--; argv++;
736 if(argc == 0){
737 rlock(&ubox.lock);
738 if((u = _userByUname(ubox.box, uname)) == nil){
739 runlock(&ubox.lock);
740 return 0;
742 consPrint("\t%U\n", u);
743 runlock(&ubox.lock);
744 return 1;
747 wlock(&ubox.lock);
748 u = _userByUname(ubox.box, uname);
749 while(argc--){
750 if(argv[0][0] == '%'){
751 if(u == nil){
752 wunlock(&ubox.lock);
753 return 0;
755 p = &argv[0][1];
756 if((up = _userByUname(ubox.box, p)) != nil){
757 werrstr("uname: uname '%s' already exists",
758 up->uname);
759 wunlock(&ubox.lock);
760 return 0;
762 for(i = 0; usersMandatory[i] != nil; i++){
763 if(strcmp(usersMandatory[i], uname) != 0)
764 continue;
765 werrstr("uname: uname '%s' is mandatory",
766 uname);
767 wunlock(&ubox.lock);
768 return 0;
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){
775 vtfree(up->leader);
776 up->leader = vtstrdup(p);
777 ubox.box->len += d;
780 for(i = 0; i < up->ngroup; i++){
781 if(strcmp(up->group[i], u->uname) != 0)
782 continue;
783 vtfree(up->group[i]);
784 up->group[i] = vtstrdup(p);
785 ubox.box->len += d;
786 break;
790 uboxRemUser(ubox.box, u);
791 vtfree(u->uname);
792 u->uname = vtstrdup(p);
793 uboxAddUser(ubox.box, u);
795 else if(argv[0][0] == '='){
796 if(u == nil){
797 wunlock(&ubox.lock);
798 return 0;
800 if((up = _userByUname(ubox.box, &argv[0][1])) == nil){
801 if(argv[0][1] != '\0'){
802 wunlock(&ubox.lock);
803 return 0;
806 if(u->leader != nil){
807 ubox.box->len -= strlen(u->leader);
808 vtfree(u->leader);
809 u->leader = nil;
811 if(up != nil){
812 u->leader = vtstrdup(up->uname);
813 ubox.box->len += strlen(u->leader);
816 else if(argv[0][0] == '+'){
817 if(u == nil){
818 wunlock(&ubox.lock);
819 return 0;
821 if((up = _userByUname(ubox.box, &argv[0][1])) == nil){
822 wunlock(&ubox.lock);
823 return 0;
825 if(!_groupAddMember(ubox.box, u, up->uname)){
826 wunlock(&ubox.lock);
827 return 0;
830 else if(argv[0][0] == '-'){
831 if(u == nil){
832 wunlock(&ubox.lock);
833 return 0;
835 if((up = _userByUname(ubox.box, &argv[0][1])) == nil){
836 wunlock(&ubox.lock);
837 return 0;
839 if(!_groupRemMember(ubox.box, u, up->uname)){
840 wunlock(&ubox.lock);
841 return 0;
844 else{
845 if(u != nil){
846 werrstr("uname: uname '%s' already exists",
847 u->uname);
848 wunlock(&ubox.lock);
849 return 0;
852 uid = argv[0];
853 if(*uid == ':')
854 uid++;
855 if((u = _userByUid(ubox.box, uid)) != nil){
856 werrstr("uname: uid '%s' already exists",
857 u->uid);
858 wunlock(&ubox.lock);
859 return 0;
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);
867 r = cliExec(p);
868 vtfree(p);
869 if(r == 0){
870 wunlock(&ubox.lock);
871 return 0;
875 argv++;
878 if(usersFileWrite(ubox.box) == 0){
879 wunlock(&ubox.lock);
880 return 0;
882 if(dflag)
883 uboxDump(ubox.box);
884 wunlock(&ubox.lock);
886 return 1;
889 static int
890 cmdUsers(int argc, char* argv[])
892 Ubox *box;
893 int dflag, r, wflag;
894 char *file;
895 char *usage = "usage: users [-d | -r file] [-w]";
897 dflag = wflag = 0;
898 file = nil;
900 ARGBEGIN{
901 default:
902 return cliError(usage);
903 case 'd':
904 dflag = 1;
905 break;
906 case 'r':
907 file = ARGF();
908 if(file == nil)
909 return cliError(usage);
910 break;
911 case 'w':
912 wflag = 1;
913 break;
914 }ARGEND
916 if(argc)
917 return cliError(usage);
919 if(dflag && file)
920 return cliError("cannot use -d and -r together");
922 if(dflag)
923 uboxInit(usersDefault, sizeof(usersDefault));
924 else if(file){
925 if(usersFileRead(file) == 0)
926 return 0;
929 rlock(&ubox.lock);
930 box = ubox.box;
931 consPrint("\tnuser %d len %d\n", box->nuser, box->len);
933 r = 1;
934 if(wflag)
935 r = usersFileWrite(box);
936 runlock(&ubox.lock);
937 return r;
940 int
941 usersInit(void)
943 fmtinstall('U', userFmt);
945 uboxInit(usersDefault, sizeof(usersDefault));
947 cliAddCmd("users", cmdUsers);
948 cliAddCmd("uname", cmdUname);
950 return 1;