xref: /plan9/sys/src/cmd/fossil/9user.c (revision e12a987081f10894b49298514b3a97b41db862b0)
1 #include "stdinc.h"
2 
3 #include "9.h"
4 
5 enum {
6 	NUserHash	= 1009,
7 };
8 
9 typedef struct Ubox Ubox;
10 typedef struct User User;
11 
12 struct User {
13 	char*	uid;
14 	char*	uname;
15 	char*	leader;
16 	char**	group;
17 	int	ngroup;
18 
19 	User*	next;			/* */
20 	User*	ihash;			/* lookup by .uid */
21 	User*	nhash;			/* lookup by .uname */
22 };
23 
24 #pragma varargck type "U"   User*
25 
26 struct Ubox {
27 	User*	head;
28 	User*	tail;
29 	int	nuser;
30 	int	len;
31 
32 	User*	ihash[NUserHash];	/* lookup by .uid */
33 	User*	nhash[NUserHash];	/* lookup by .uname */
34 };
35 
36 static struct {
37 	VtLock*	lock;
38 
39 	Ubox*	box;
40 } ubox;
41 
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 };
49 
50 static char* usersMandatory[] = {
51 	"adm",
52 	"none",
53 	"noworld",
54 	"sys",
55 	nil,
56 };
57 
58 char* uidadm = "adm";
59 char* unamenone = "none";
60 char* uidnoworld = "noworld";
61 
62 static u32int
userHash(char * s)63 userHash(char* s)
64 {
65 	uchar *p;
66 	u32int hash;
67 
68 	hash = 0;
69 	for(p = (uchar*)s; *p != '\0'; p++)
70 		hash = hash*7 + *p;
71 
72 	return hash % NUserHash;
73 }
74 
75 static User*
_userByUid(Ubox * box,char * uid)76 _userByUid(Ubox* box, char* uid)
77 {
78 	User *u;
79 
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 	vtSetError("uname: uid '%s' not found", uid);
87 	return nil;
88 }
89 
90 char*
unameByUid(char * uid)91 unameByUid(char* uid)
92 {
93 	User *u;
94 	char *uname;
95 
96 	vtRLock(ubox.lock);
97 	if((u = _userByUid(ubox.box, uid)) == nil){
98 		vtRUnlock(ubox.lock);
99 		return nil;
100 	}
101 	uname = vtStrDup(u->uname);
102 	vtRUnlock(ubox.lock);
103 
104 	return uname;
105 }
106 
107 static User*
_userByUname(Ubox * box,char * uname)108 _userByUname(Ubox* box, char* uname)
109 {
110 	User *u;
111 
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;
116 		}
117 	}
118 	vtSetError("uname: uname '%s' not found", uname);
119 	return nil;
120 }
121 
122 char*
uidByUname(char * uname)123 uidByUname(char* uname)
124 {
125 	User *u;
126 	char *uid;
127 
128 	vtRLock(ubox.lock);
129 	if((u = _userByUname(ubox.box, uname)) == nil){
130 		vtRUnlock(ubox.lock);
131 		return nil;
132 	}
133 	uid = vtStrDup(u->uid);
134 	vtRUnlock(ubox.lock);
135 
136 	return uid;
137 }
138 
139 static int
_groupMember(Ubox * box,char * group,char * member,int whenNoGroup)140 _groupMember(Ubox* box, char* group, char* member, int whenNoGroup)
141 {
142 	int i;
143 	User *g, *m;
144 
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;
159 	}
160 	return 0;
161 }
162 
163 int
groupWriteMember(char * uname)164 groupWriteMember(char* uname)
165 {
166 	int ret;
167 
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.
171 	 *
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.
175 	 *
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.
179 	 *
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 	 */
184 
185 	vtRLock(ubox.lock);
186 	ret = _groupMember(ubox.box, "write", uname, 1);
187 	vtRUnlock(ubox.lock);
188 	return ret;
189 }
190 
191 static int
_groupRemMember(Ubox * box,User * g,char * member)192 _groupRemMember(Ubox* box, User* g, char* member)
193 {
194 	int i;
195 
196 	if(_userByUname(box, member) == nil)
197 		return 0;
198 
199 	for(i = 0; i < g->ngroup; i++){
200 		if(strcmp(g->group[i], member) == 0)
201 			break;
202 	}
203 	if(i >= g->ngroup){
204 		if(strcmp(g->uname, member) == 0)
205 			vtSetError("uname: '%s' always in own group", member);
206 		else
207 			vtSetError("uname: '%s' not in group '%s'",
208 				member, g->uname);
209 		return 0;
210 	}
211 
212 	vtMemFree(g->group[i]);
213 
214 	box->len -= strlen(member);
215 	if(g->ngroup > 1)
216 		box->len--;
217 	g->ngroup--;
218 	switch(g->ngroup){
219 	case 0:
220 		vtMemFree(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 = vtMemRealloc(g->group, g->ngroup * sizeof(char*));
228 		break;
229 	}
230 
231 	return 1;
232 }
233 
234 static int
_groupAddMember(Ubox * box,User * g,char * member)235 _groupAddMember(Ubox* box, User* g, char* member)
236 {
237 	User *u;
238 
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 			vtSetError("uname: '%s' always in own group", member);
244 		else
245 			vtSetError("uname: '%s' already in group '%s'",
246 				member, g->uname);
247 		return 0;
248 	}
249 
250 	g->group = vtMemRealloc(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++;
256 
257 	return 1;
258 }
259 
260 int
groupMember(char * group,char * member)261 groupMember(char* group, char* member)
262 {
263 	int r;
264 
265 	if(group == nil)
266 		return 0;
267 
268 	vtRLock(ubox.lock);
269 	r = _groupMember(ubox.box, group, member, 0);
270 	vtRUnlock(ubox.lock);
271 
272 	return r;
273 }
274 
275 int
groupLeader(char * group,char * member)276 groupLeader(char* group, char* member)
277 {
278 	int r;
279 	User *g;
280 
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;
288 
289 	vtRLock(ubox.lock);
290 	if((g = _userByUid(ubox.box, group)) == nil){
291 		vtRUnlock(ubox.lock);
292 		return 0;
293 	}
294 	if(g->leader != nil){
295 		if(strcmp(g->leader, member) == 0){
296 			vtRUnlock(ubox.lock);
297 			return 1;
298 		}
299 		r = 0;
300 	}
301 	else
302 		r = _groupMember(ubox.box, group, member, 0);
303 	vtRUnlock(ubox.lock);
304 
305 	return r;
306 }
307 
308 static void
userFree(User * u)309 userFree(User* u)
310 {
311 	int i;
312 
313 	vtMemFree(u->uid);
314 	vtMemFree(u->uname);
315 	if(u->leader != nil)
316 		vtMemFree(u->leader);
317 	if(u->ngroup){
318 		for(i = 0; i < u->ngroup; i++)
319 			vtMemFree(u->group[i]);
320 		vtMemFree(u->group);
321 	}
322 	vtMemFree(u);
323 }
324 
325 static User*
userAlloc(char * uid,char * uname)326 userAlloc(char* uid, char* uname)
327 {
328 	User *u;
329 
330 	u = vtMemAllocZ(sizeof(User));
331 	u->uid = vtStrDup(uid);
332 	u->uname = vtStrDup(uname);
333 
334 	return u;
335 }
336 
337 int
validUserName(char * name)338 validUserName(char* name)
339 {
340 	Rune *r;
341 	static Rune invalid[] = L"#:,()";
342 
343 	for(r = invalid; *r != '\0'; r++){
344 		if(utfrune(name, *r))
345 			return 0;
346 	}
347 	return 1;
348 }
349 
350 static int
userFmt(Fmt * fmt)351 userFmt(Fmt* fmt)
352 {
353 	User *u;
354 	int i, r;
355 
356 	u = va_arg(fmt->args, User*);
357 
358 	r = fmtprint(fmt, "%s:%s:", u->uid, u->uname);
359 	if(u->leader != nil)
360 		r += fmtprint(fmt, u->leader);
361 	r += fmtprint(fmt, ":");
362 	if(u->ngroup){
363 		r += fmtprint(fmt, u->group[0]);
364 		for(i = 1; i < u->ngroup; i++)
365 			r += fmtprint(fmt, ",%s", u->group[i]);
366 	}
367 
368 	return r;
369 }
370 
371 static int
usersFileWrite(Ubox * box)372 usersFileWrite(Ubox* box)
373 {
374 	Fs *fs;
375 	User *u;
376 	int i, r;
377 	Fsys *fsys;
378 	char *p, *q, *s;
379 	File *dir, *file;
380 
381 	if((fsys = fsysGet("main")) == nil)
382 		return 0;
383 	fsysFsRlock(fsys);
384 	fs = fsysGetFs(fsys);
385 
386 	/*
387 	 * BUG:
388 	 * 	the owner/group/permissions need to be thought out.
389 	 */
390 	r = 0;
391 	if((dir = fileOpen(fs, "/active")) == nil)
392 		goto tidy0;
393 	if((file = fileWalk(dir, uidadm)) == nil)
394 		file = fileCreate(dir, uidadm, ModeDir|0775, uidadm);
395 	fileDecRef(dir);
396 	if(file == nil)
397 		goto tidy;
398 	dir = file;
399 	if((file = fileWalk(dir, "users")) == nil)
400 		file = fileCreate(dir, "users", 0664, uidadm);
401 	fileDecRef(dir);
402 	if(file == nil)
403 		goto tidy;
404 	if(!fileTruncate(file, uidadm))
405 		goto tidy;
406 
407 	p = s = vtMemAlloc(box->len+1);
408 	q = p + box->len+1;
409 	for(u = box->head; u != nil; u = u->next){
410 		p += snprint(p, q-p, "%s:%s:", u->uid, u->uname);
411 		if(u->leader != nil)
412 			p+= snprint(p, q-p, u->leader);
413 		p += snprint(p, q-p, ":");
414 		if(u->ngroup){
415 			p += snprint(p, q-p, u->group[0]);
416 			for(i = 1; i < u->ngroup; i++)
417 				p += snprint(p, q-p, ",%s", u->group[i]);
418 		}
419 		p += snprint(p, q-p, "\n");
420 	}
421 	r = fileWrite(file, s, box->len, 0, uidadm);
422 	vtMemFree(s);
423 
424 tidy:
425 	if(file != nil)
426 		fileDecRef(file);
427 tidy0:
428 	fsysFsRUnlock(fsys);
429 	fsysPut(fsys);
430 
431 	return r;
432 }
433 
434 static void
uboxRemUser(Ubox * box,User * u)435 uboxRemUser(Ubox* box, User *u)
436 {
437 	User **h, *up;
438 
439 	h = &box->ihash[userHash(u->uid)];
440 	for(up = *h; up != nil && up != u; up = up->ihash)
441 		h = &up->ihash;
442 	assert(up == u);
443 	*h = up->ihash;
444 	box->len -= strlen(u->uid);
445 
446 	h = &box->nhash[userHash(u->uname)];
447 	for(up = *h; up != nil && up != u; up = up->nhash)
448 		h = &up->nhash;
449 	assert(up == u);
450 	*h = up->nhash;
451 	box->len -= strlen(u->uname);
452 
453 	h = &box->head;
454 	for(up = *h; up != nil && strcmp(up->uid, u->uid) != 0; up = up->next)
455 		h = &up->next;
456 	assert(up == u);
457 	*h = u->next;
458 	u->next = nil;
459 
460 	box->len -= 4;
461 	box->nuser--;
462 }
463 
464 static void
uboxAddUser(Ubox * box,User * u)465 uboxAddUser(Ubox* box, User* u)
466 {
467 	User **h, *up;
468 
469 	h = &box->ihash[userHash(u->uid)];
470 	u->ihash = *h;
471 	*h = u;
472 	box->len += strlen(u->uid);
473 
474 	h = &box->nhash[userHash(u->uname)];
475 	u->nhash = *h;
476 	*h = u;
477 	box->len += strlen(u->uname);
478 
479 	h = &box->head;
480 	for(up = *h; up != nil && strcmp(up->uid, u->uid) < 0; up = up->next)
481 		h = &up->next;
482 	u->next = *h;
483 	*h = u;
484 
485 	box->len += 4;
486 	box->nuser++;
487 }
488 
489 static void
uboxDump(Ubox * box)490 uboxDump(Ubox* box)
491 {
492 	User* u;
493 
494 	consPrint("nuser %d len = %d\n", box->nuser, box->len);
495 
496 	for(u = box->head; u != nil; u = u->next)
497 		consPrint("%U\n", u);
498 }
499 
500 static void
uboxFree(Ubox * box)501 uboxFree(Ubox* box)
502 {
503 	User *next, *u;
504 
505 	for(u = box->head; u != nil; u = next){
506 		next = u->next;
507 		userFree(u);
508 	}
509 	vtMemFree(box);
510 }
511 
512 static int
uboxInit(char * users,int len)513 uboxInit(char* users, int len)
514 {
515 	User *g, *u;
516 	Ubox *box, *obox;
517 	int blank, comment, i, nline, nuser;
518 	char *buf, *f[5], **line, *p, *q, *s;
519 
520 	/*
521 	 * Strip out whitespace and comments.
522 	 * Note that comments are pointless, they disappear
523 	 * when the server writes the database back out.
524 	 */
525 	blank = 1;
526 	comment = nline = 0;
527 
528 	s = p = buf = vtMemAlloc(len+1);
529 	for(q = users; *q != '\0'; q++){
530 		if(*q == '\r' || *q == '\t' || *q == ' ')
531 			continue;
532 		if(*q == '\n'){
533 			if(!blank){
534 				if(p != s){
535 					*p++ = '\n';
536 					nline++;
537 					s = p;
538 				}
539 				blank = 1;
540 			}
541 			comment = 0;
542 			continue;
543 		}
544 		if(*q == '#')
545 			comment = 1;
546 		blank = 0;
547 		if(!comment)
548 			*p++ = *q;
549 	}
550 	*p = '\0';
551 
552 	line = vtMemAllocZ((nline+2)*sizeof(char*));
553 	if((i = gettokens(buf, line, nline+2, "\n")) != nline){
554 		fprint(2, "nline %d (%d) botch\n", nline, i);
555 		vtMemFree(line);
556 		vtMemFree(buf);
557 		return 0;
558 	}
559 
560 	/*
561 	 * Everything is updated in a local Ubox until verified.
562 	 */
563 	box = vtMemAllocZ(sizeof(Ubox));
564 
565 	/*
566 	 * First pass - check format, check for duplicates
567 	 * and enter in hash buckets.
568 	 */
569 	nuser = 0;
570 	for(i = 0; i < nline; i++){
571 		s = vtStrDup(line[i]);
572 		if(getfields(s, f, nelem(f), 0, ":") != 4){
573 			fprint(2, "bad line '%s'\n", line[i]);
574 			vtMemFree(s);
575 			continue;
576 		}
577 		if(*f[0] == '\0' || *f[1] == '\0'){
578 			fprint(2, "bad line '%s'\n", line[i]);
579 			vtMemFree(s);
580 			continue;
581 		}
582 		if(!validUserName(f[0])){
583 			fprint(2, "invalid uid '%s'\n", f[0]);
584 			vtMemFree(s);
585 			continue;
586 		}
587 		if(_userByUid(box, f[0]) != nil){
588 			fprint(2, "duplicate uid '%s'\n", f[0]);
589 			vtMemFree(s);
590 			continue;
591 		}
592 		if(!validUserName(f[1])){
593 			fprint(2, "invalid uname '%s'\n", f[0]);
594 			vtMemFree(s);
595 			continue;
596 		}
597 		if(_userByUname(box, f[1]) != nil){
598 			fprint(2, "duplicate uname '%s'\n", f[1]);
599 			vtMemFree(s);
600 			continue;
601 		}
602 
603 		u = userAlloc(f[0], f[1]);
604 		uboxAddUser(box, u);
605 		line[nuser] = line[i];
606 		nuser++;
607 
608 		vtMemFree(s);
609 	}
610 	assert(box->nuser == nuser);
611 
612 	/*
613 	 * Second pass - fill in leader and group information.
614 	 */
615 	for(i = 0; i < nuser; i++){
616 		s = vtStrDup(line[i]);
617 		getfields(s, f, nelem(f), 0, ":");
618 
619 		assert(g = _userByUname(box, f[1]));
620 		if(*f[2] != '\0'){
621 			if((u = _userByUname(box, f[2])) == nil)
622 				g->leader = vtStrDup(g->uname);
623 			else
624 				g->leader = vtStrDup(u->uname);
625 			box->len += strlen(g->leader);
626 		}
627 		for(p = f[3]; p != nil; p = q){
628 			if((q = utfrune(p, L',')) != nil)
629 				*q++ = '\0';
630 			if(!_groupAddMember(box, g, p)){
631 				// print/log error here
632 			}
633 		}
634 
635 		vtMemFree(s);
636 	}
637 
638 	vtMemFree(line);
639 	vtMemFree(buf);
640 
641 	for(i = 0; usersMandatory[i] != nil; i++){
642 		if((u = _userByUid(box, usersMandatory[i])) == nil){
643 			vtSetError("user '%s' is mandatory", usersMandatory[i]);
644 			uboxFree(box);
645 			return 0;
646 		}
647 		if(strcmp(u->uid, u->uname) != 0){
648 			vtSetError("uid/uname for user '%s' must match",
649 				usersMandatory[i]);
650 			uboxFree(box);
651 			return 0;
652 		}
653 	}
654 
655 	vtLock(ubox.lock);
656 	obox = ubox.box;
657 	ubox.box = box;
658 	vtUnlock(ubox.lock);
659 
660 	if(obox != nil)
661 		uboxFree(obox);
662 
663 	return 1;
664 }
665 
666 int
usersFileRead(char * path)667 usersFileRead(char* path)
668 {
669 	char *p;
670 	File *file;
671 	Fsys *fsys;
672 	int len, r;
673 	uvlong size;
674 
675 	if((fsys = fsysGet("main")) == nil)
676 		return 0;
677 	fsysFsRlock(fsys);
678 
679 	if(path == nil)
680 		path = "/active/adm/users";
681 
682 	r = 0;
683 	if((file = fileOpen(fsysGetFs(fsys), path)) != nil){
684 		if(fileGetSize(file, &size)){
685 			len = size;
686 			p = vtMemAlloc(size+1);
687 			if(fileRead(file, p, len, 0) == len){
688 				p[len] = '\0';
689 				r = uboxInit(p, len);
690 			}
691 		}
692 		fileDecRef(file);
693 	}
694 
695 	fsysFsRUnlock(fsys);
696 	fsysPut(fsys);
697 
698 	return r;
699 }
700 
701 static int
cmdUname(int argc,char * argv[])702 cmdUname(int argc, char* argv[])
703 {
704 	User *u, *up;
705 	int d, dflag, i, r;
706 	char *p, *uid, *uname;
707 	char *createfmt = "fsys main create /active/usr/%s %s %s d775";
708 	char *usage = "usage: uname [-d] uname [uid|:uid|%%newname|=leader|+member|-member]";
709 
710 	dflag = 0;
711 
712 	ARGBEGIN{
713 	default:
714 		return cliError(usage);
715 	case 'd':
716 		dflag = 1;
717 		break;
718 	}ARGEND
719 
720 	if(argc < 1){
721 		if(!dflag)
722 			return cliError(usage);
723 		vtRLock(ubox.lock);
724 		uboxDump(ubox.box);
725 		vtRUnlock(ubox.lock);
726 		return 1;
727 	}
728 
729 	uname = argv[0];
730 	argc--; argv++;
731 
732 	if(argc == 0){
733 		vtRLock(ubox.lock);
734 		if((u = _userByUname(ubox.box, uname)) == nil){
735 			vtRUnlock(ubox.lock);
736 			return 0;
737 		}
738 		consPrint("\t%U\n", u);
739 		vtRUnlock(ubox.lock);
740 		return 1;
741 	}
742 
743 	vtLock(ubox.lock);
744 	u = _userByUname(ubox.box, uname);
745 	while(argc--){
746 		if(argv[0][0] == '%'){
747 			if(u == nil){
748 				vtUnlock(ubox.lock);
749 				return 0;
750 			}
751 			p = &argv[0][1];
752 			if((up = _userByUname(ubox.box, p)) != nil){
753 				vtSetError("uname: uname '%s' already exists",
754 					up->uname);
755 				vtUnlock(ubox.lock);
756 				return 0;
757 			}
758 			for(i = 0; usersMandatory[i] != nil; i++){
759 				if(strcmp(usersMandatory[i], uname) != 0)
760 					continue;
761 				vtSetError("uname: uname '%s' is mandatory",
762 					uname);
763 				vtUnlock(ubox.lock);
764 				return 0;
765 			}
766 
767 			d = strlen(p) - strlen(u->uname);
768 			for(up = ubox.box->head; up != nil; up = up->next){
769 				if(up->leader != nil){
770 					if(strcmp(up->leader, u->uname) == 0){
771 						vtMemFree(up->leader);
772 						up->leader = vtStrDup(p);
773 						ubox.box->len += d;
774 					}
775 				}
776 				for(i = 0; i < up->ngroup; i++){
777 					if(strcmp(up->group[i], u->uname) != 0)
778 						continue;
779 					vtMemFree(up->group[i]);
780 					up->group[i] = vtStrDup(p);
781 					ubox.box->len += d;
782 					break;
783 				}
784 			}
785 
786 			uboxRemUser(ubox.box, u);
787 			vtMemFree(u->uname);
788 			u->uname = vtStrDup(p);
789 			uboxAddUser(ubox.box, u);
790 		}
791 		else if(argv[0][0] == '='){
792 			if(u == nil){
793 				vtUnlock(ubox.lock);
794 				return 0;
795 			}
796 			if((up = _userByUname(ubox.box, &argv[0][1])) == nil){
797 				if(argv[0][1] != '\0'){
798 					vtUnlock(ubox.lock);
799 					return 0;
800 				}
801 			}
802 			if(u->leader != nil){
803 				ubox.box->len -= strlen(u->leader);
804 				vtMemFree(u->leader);
805 				u->leader = nil;
806 			}
807 			if(up != nil){
808 				u->leader = vtStrDup(up->uname);
809 				ubox.box->len += strlen(u->leader);
810 			}
811 		}
812 		else if(argv[0][0] == '+'){
813 			if(u == nil){
814 				vtUnlock(ubox.lock);
815 				return 0;
816 			}
817 			if((up = _userByUname(ubox.box, &argv[0][1])) == nil){
818 				vtUnlock(ubox.lock);
819 				return 0;
820 			}
821 			if(!_groupAddMember(ubox.box, u, up->uname)){
822 				vtUnlock(ubox.lock);
823 				return 0;
824 			}
825 		}
826 		else if(argv[0][0] == '-'){
827 			if(u == nil){
828 				vtUnlock(ubox.lock);
829 				return 0;
830 			}
831 			if((up = _userByUname(ubox.box, &argv[0][1])) == nil){
832 				vtUnlock(ubox.lock);
833 				return 0;
834 			}
835 			if(!_groupRemMember(ubox.box, u, up->uname)){
836 				vtUnlock(ubox.lock);
837 				return 0;
838 			}
839 		}
840 		else{
841 			if(u != nil){
842 				vtSetError("uname: uname '%s' already exists",
843 					u->uname);
844 				vtUnlock(ubox.lock);
845 				return 0;
846 			}
847 
848 			uid = argv[0];
849 			if(*uid == ':')
850 				uid++;
851 			if((u = _userByUid(ubox.box, uid)) != nil){
852 				vtSetError("uname: uid '%s' already exists",
853 					u->uid);
854 				vtUnlock(ubox.lock);
855 				return 0;
856 			}
857 
858 			u = userAlloc(uid, uname);
859 			uboxAddUser(ubox.box, u);
860 			if(argv[0][0] != ':'){
861 				// should have an option for the mode and gid
862 				p = smprint(createfmt, uname, uname, uname);
863 				r = cliExec(p);
864 				vtMemFree(p);
865 				if(r == 0){
866 					vtUnlock(ubox.lock);
867 					return 0;
868 				}
869 			}
870 		}
871 		argv++;
872 	}
873 
874 	if(usersFileWrite(ubox.box) == 0){
875 		vtUnlock(ubox.lock);
876 		return 0;
877 	}
878 	if(dflag)
879 		uboxDump(ubox.box);
880 	vtUnlock(ubox.lock);
881 
882 	return 1;
883 }
884 
885 static int
cmdUsers(int argc,char * argv[])886 cmdUsers(int argc, char* argv[])
887 {
888 	Ubox *box;
889 	int dflag, r, wflag;
890 	char *file;
891 	char *usage = "usage: users [-d | -r file] [-w]";
892 
893 	dflag = wflag = 0;
894 	file = nil;
895 
896 	ARGBEGIN{
897 	default:
898 		return cliError(usage);
899 	case 'd':
900 		dflag = 1;
901 		break;
902 	case 'r':
903 		file = ARGF();
904 		if(file == nil)
905 			return cliError(usage);
906 		break;
907 	case 'w':
908 		wflag = 1;
909 		break;
910 	}ARGEND
911 
912 	if(argc)
913 		return cliError(usage);
914 
915 	if(dflag && file)
916 		return cliError("cannot use -d and -r together");
917 
918 	if(dflag)
919 		uboxInit(usersDefault, sizeof(usersDefault));
920 	else if(file){
921 		if(usersFileRead(file) == 0)
922 			return 0;
923 	}
924 
925 	vtRLock(ubox.lock);
926 	box = ubox.box;
927 	consPrint("\tnuser %d len %d\n", box->nuser, box->len);
928 
929 	r = 1;
930 	if(wflag)
931 		r = usersFileWrite(box);
932 	vtRUnlock(ubox.lock);
933 	return r;
934 }
935 
936 int
usersInit(void)937 usersInit(void)
938 {
939 	fmtinstall('U', userFmt);
940 
941 	ubox.lock = vtLockAlloc();
942 	uboxInit(usersDefault, sizeof(usersDefault));
943 
944 	cliAddCmd("users", cmdUsers);
945 	cliAddCmd("uname", cmdUname);
946 
947 	return 1;
948 }
949