1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2013 The FreeBSD Foundation
5  * All rights reserved.
6  *
7  * This software was developed by Pawel Jakub Dawidek under sponsorship from
8  * the FreeBSD Foundation.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 
32 #include <sys/cdefs.h>
33 __FBSDID("$FreeBSD$");
34 
35 #include <sys/dnv.h>
36 #include <sys/nv.h>
37 #include <sys/param.h>
38 
39 #include <assert.h>
40 #include <errno.h>
41 #include <grp.h>
42 #include <stdlib.h>
43 #include <string.h>
44 
45 #include <libcasper.h>
46 #include <libcasper_service.h>
47 
48 #include "cap_grp.h"
49 
50 static struct group ggrp;
51 static char *gbuffer;
52 static size_t gbufsize;
53 
54 static int
55 group_resize(void)
56 {
57 	char *buf;
58 
59 	if (gbufsize == 0)
60 		gbufsize = 1024;
61 	else
62 		gbufsize *= 2;
63 
64 	buf = gbuffer;
65 	gbuffer = realloc(buf, gbufsize);
66 	if (gbuffer == NULL) {
67 		free(buf);
68 		gbufsize = 0;
69 		return (ENOMEM);
70 	}
71 	memset(gbuffer, 0, gbufsize);
72 
73 	return (0);
74 }
75 
76 static int
77 group_unpack_string(const nvlist_t *nvl, const char *fieldname, char **fieldp,
78     char **bufferp, size_t *bufsizep)
79 {
80 	const char *str;
81 	size_t len;
82 
83 	str = nvlist_get_string(nvl, fieldname);
84 	len = strlcpy(*bufferp, str, *bufsizep);
85 	if (len >= *bufsizep)
86 		return (ERANGE);
87 	*fieldp = *bufferp;
88 	*bufferp += len + 1;
89 	*bufsizep -= len + 1;
90 
91 	return (0);
92 }
93 
94 static int
95 group_unpack_members(const nvlist_t *nvl, char ***fieldp, char **bufferp,
96     size_t *bufsizep)
97 {
98 	const char *mem;
99 	char **outstrs, *str, nvlname[64];
100 	size_t nmem, datasize, strsize;
101 	unsigned int ii;
102 	int n;
103 
104 	if (!nvlist_exists_number(nvl, "gr_nmem")) {
105 		datasize = _ALIGNBYTES + sizeof(char *);
106 		if (datasize >= *bufsizep)
107 			return (ERANGE);
108 		outstrs = (char **)_ALIGN(*bufferp);
109 		outstrs[0] = NULL;
110 		*fieldp = outstrs;
111 		*bufferp += datasize;
112 		*bufsizep -= datasize;
113 		return (0);
114 	}
115 
116 	nmem = (size_t)nvlist_get_number(nvl, "gr_nmem");
117 	datasize = _ALIGNBYTES + sizeof(char *) * (nmem + 1);
118 	for (ii = 0; ii < nmem; ii++) {
119 		n = snprintf(nvlname, sizeof(nvlname), "gr_mem[%u]", ii);
120 		assert(n > 0 && n < (int)sizeof(nvlname));
121 		mem = dnvlist_get_string(nvl, nvlname, NULL);
122 		if (mem == NULL)
123 			return (EINVAL);
124 		datasize += strlen(mem) + 1;
125 	}
126 
127 	if (datasize >= *bufsizep)
128 		return (ERANGE);
129 
130 	outstrs = (char **)_ALIGN(*bufferp);
131 	str = (char *)outstrs + sizeof(char *) * (nmem + 1);
132 	for (ii = 0; ii < nmem; ii++) {
133 		n = snprintf(nvlname, sizeof(nvlname), "gr_mem[%u]", ii);
134 		assert(n > 0 && n < (int)sizeof(nvlname));
135 		mem = nvlist_get_string(nvl, nvlname);
136 		strsize = strlen(mem) + 1;
137 		memcpy(str, mem, strsize);
138 		outstrs[ii] = str;
139 		str += strsize;
140 	}
141 	assert(ii == nmem);
142 	outstrs[ii] = NULL;
143 
144 	*fieldp = outstrs;
145 	*bufferp += datasize;
146 	*bufsizep -= datasize;
147 
148 	return (0);
149 }
150 
151 static int
152 group_unpack(const nvlist_t *nvl, struct group *grp, char *buffer,
153     size_t bufsize)
154 {
155 	int error;
156 
157 	if (!nvlist_exists_string(nvl, "gr_name"))
158 		return (EINVAL);
159 
160 	explicit_bzero(grp, sizeof(*grp));
161 
162 	error = group_unpack_string(nvl, "gr_name", &grp->gr_name, &buffer,
163 	    &bufsize);
164 	if (error != 0)
165 		return (error);
166 	error = group_unpack_string(nvl, "gr_passwd", &grp->gr_passwd, &buffer,
167 	    &bufsize);
168 	if (error != 0)
169 		return (error);
170 	grp->gr_gid = (gid_t)nvlist_get_number(nvl, "gr_gid");
171 	error = group_unpack_members(nvl, &grp->gr_mem, &buffer, &bufsize);
172 	if (error != 0)
173 		return (error);
174 
175 	return (0);
176 }
177 
178 static int
179 cap_getgrcommon_r(cap_channel_t *chan, const char *cmd, const char *name,
180     gid_t gid, struct group *grp, char *buffer, size_t bufsize,
181     struct group **result)
182 {
183 	nvlist_t *nvl;
184 	bool getgr_r;
185 	int error;
186 
187 	nvl = nvlist_create(0);
188 	nvlist_add_string(nvl, "cmd", cmd);
189 	if (strcmp(cmd, "getgrent") == 0 || strcmp(cmd, "getgrent_r") == 0) {
190 		/* Add nothing. */
191 	} else if (strcmp(cmd, "getgrnam") == 0 ||
192 	    strcmp(cmd, "getgrnam_r") == 0) {
193 		nvlist_add_string(nvl, "name", name);
194 	} else if (strcmp(cmd, "getgrgid") == 0 ||
195 	    strcmp(cmd, "getgrgid_r") == 0) {
196 		nvlist_add_number(nvl, "gid", (uint64_t)gid);
197 	} else {
198 		abort();
199 	}
200 	nvl = cap_xfer_nvlist(chan, nvl);
201 	if (nvl == NULL) {
202 		assert(errno != 0);
203 		*result = NULL;
204 		return (errno);
205 	}
206 	error = (int)nvlist_get_number(nvl, "error");
207 	if (error != 0) {
208 		nvlist_destroy(nvl);
209 		*result = NULL;
210 		return (error);
211 	}
212 
213 	if (!nvlist_exists_string(nvl, "gr_name")) {
214 		/* Not found. */
215 		nvlist_destroy(nvl);
216 		*result = NULL;
217 		return (0);
218 	}
219 
220 	getgr_r = (strcmp(cmd, "getgrent_r") == 0 ||
221 	    strcmp(cmd, "getgrnam_r") == 0 || strcmp(cmd, "getgrgid_r") == 0);
222 
223 	for (;;) {
224 		error = group_unpack(nvl, grp, buffer, bufsize);
225 		if (getgr_r || error != ERANGE)
226 			break;
227 		assert(buffer == gbuffer);
228 		assert(bufsize == gbufsize);
229 		error = group_resize();
230 		if (error != 0)
231 			break;
232 		/* Update pointers after resize. */
233 		buffer = gbuffer;
234 		bufsize = gbufsize;
235 	}
236 
237 	nvlist_destroy(nvl);
238 
239 	if (error == 0)
240 		*result = grp;
241 	else
242 		*result = NULL;
243 
244 	return (error);
245 }
246 
247 static struct group *
248 cap_getgrcommon(cap_channel_t *chan, const char *cmd, const char *name,
249     gid_t gid)
250 {
251 	struct group *result;
252 	int error, serrno;
253 
254 	serrno = errno;
255 
256 	error = cap_getgrcommon_r(chan, cmd, name, gid, &ggrp, gbuffer,
257 	    gbufsize, &result);
258 	if (error != 0) {
259 		errno = error;
260 		return (NULL);
261 	}
262 
263 	errno = serrno;
264 
265 	return (result);
266 }
267 
268 struct group *
269 cap_getgrent(cap_channel_t *chan)
270 {
271 
272 	return (cap_getgrcommon(chan, "getgrent", NULL, 0));
273 }
274 
275 struct group *
276 cap_getgrnam(cap_channel_t *chan, const char *name)
277 {
278 
279 	return (cap_getgrcommon(chan, "getgrnam", name, 0));
280 }
281 
282 struct group *
283 cap_getgrgid(cap_channel_t *chan, gid_t gid)
284 {
285 
286 	return (cap_getgrcommon(chan, "getgrgid", NULL, gid));
287 }
288 
289 int
290 cap_getgrent_r(cap_channel_t *chan, struct group *grp, char *buffer,
291     size_t bufsize, struct group **result)
292 {
293 
294 	return (cap_getgrcommon_r(chan, "getgrent_r", NULL, 0, grp, buffer,
295 	    bufsize, result));
296 }
297 
298 int
299 cap_getgrnam_r(cap_channel_t *chan, const char *name, struct group *grp,
300     char *buffer, size_t bufsize, struct group **result)
301 {
302 
303 	return (cap_getgrcommon_r(chan, "getgrnam_r", name, 0, grp, buffer,
304 	    bufsize, result));
305 }
306 
307 int
308 cap_getgrgid_r(cap_channel_t *chan, gid_t gid, struct group *grp, char *buffer,
309     size_t bufsize, struct group **result)
310 {
311 
312 	return (cap_getgrcommon_r(chan, "getgrgid_r", NULL, gid, grp, buffer,
313 	    bufsize, result));
314 }
315 
316 int
317 cap_setgroupent(cap_channel_t *chan, int stayopen)
318 {
319 	nvlist_t *nvl;
320 
321 	nvl = nvlist_create(0);
322 	nvlist_add_string(nvl, "cmd", "setgroupent");
323 	nvlist_add_bool(nvl, "stayopen", stayopen != 0);
324 	nvl = cap_xfer_nvlist(chan, nvl);
325 	if (nvl == NULL)
326 		return (0);
327 	if (nvlist_get_number(nvl, "error") != 0) {
328 		errno = nvlist_get_number(nvl, "error");
329 		nvlist_destroy(nvl);
330 		return (0);
331 	}
332 	nvlist_destroy(nvl);
333 
334 	return (1);
335 }
336 
337 int
338 cap_setgrent(cap_channel_t *chan)
339 {
340 	nvlist_t *nvl;
341 
342 	nvl = nvlist_create(0);
343 	nvlist_add_string(nvl, "cmd", "setgrent");
344 	nvl = cap_xfer_nvlist(chan, nvl);
345 	if (nvl == NULL)
346 		return (0);
347 	if (nvlist_get_number(nvl, "error") != 0) {
348 		errno = nvlist_get_number(nvl, "error");
349 		nvlist_destroy(nvl);
350 		return (0);
351 	}
352 	nvlist_destroy(nvl);
353 
354 	return (1);
355 }
356 
357 void
358 cap_endgrent(cap_channel_t *chan)
359 {
360 	nvlist_t *nvl;
361 
362 	nvl = nvlist_create(0);
363 	nvlist_add_string(nvl, "cmd", "endgrent");
364 	/* Ignore any errors, we have no way to report them. */
365 	nvlist_destroy(cap_xfer_nvlist(chan, nvl));
366 }
367 
368 int
369 cap_grp_limit_cmds(cap_channel_t *chan, const char * const *cmds, size_t ncmds)
370 {
371 	nvlist_t *limits, *nvl;
372 	unsigned int i;
373 
374 	if (cap_limit_get(chan, &limits) < 0)
375 		return (-1);
376 	if (limits == NULL) {
377 		limits = nvlist_create(0);
378 	} else {
379 		if (nvlist_exists_nvlist(limits, "cmds"))
380 			nvlist_free_nvlist(limits, "cmds");
381 	}
382 	nvl = nvlist_create(0);
383 	for (i = 0; i < ncmds; i++)
384 		nvlist_add_null(nvl, cmds[i]);
385 	nvlist_move_nvlist(limits, "cmds", nvl);
386 	return (cap_limit_set(chan, limits));
387 }
388 
389 int
390 cap_grp_limit_fields(cap_channel_t *chan, const char * const *fields,
391     size_t nfields)
392 {
393 	nvlist_t *limits, *nvl;
394 	unsigned int i;
395 
396 	if (cap_limit_get(chan, &limits) < 0)
397 		return (-1);
398 	if (limits == NULL) {
399 		limits = nvlist_create(0);
400 	} else {
401 		if (nvlist_exists_nvlist(limits, "fields"))
402 			nvlist_free_nvlist(limits, "fields");
403 	}
404 	nvl = nvlist_create(0);
405 	for (i = 0; i < nfields; i++)
406 		nvlist_add_null(nvl, fields[i]);
407 	nvlist_move_nvlist(limits, "fields", nvl);
408 	return (cap_limit_set(chan, limits));
409 }
410 
411 int
412 cap_grp_limit_groups(cap_channel_t *chan, const char * const *names,
413     size_t nnames, const gid_t *gids, size_t ngids)
414 {
415 	nvlist_t *limits, *groups;
416 	unsigned int i;
417 	char nvlname[64];
418 	int n;
419 
420 	if (cap_limit_get(chan, &limits) < 0)
421 		return (-1);
422 	if (limits == NULL) {
423 		limits = nvlist_create(0);
424 	} else {
425 		if (nvlist_exists_nvlist(limits, "groups"))
426 			nvlist_free_nvlist(limits, "groups");
427 	}
428 	groups = nvlist_create(0);
429 	for (i = 0; i < ngids; i++) {
430 		n = snprintf(nvlname, sizeof(nvlname), "gid%u", i);
431 		assert(n > 0 && n < (int)sizeof(nvlname));
432 		nvlist_add_number(groups, nvlname, (uint64_t)gids[i]);
433 	}
434 	for (i = 0; i < nnames; i++) {
435 		n = snprintf(nvlname, sizeof(nvlname), "gid%u", i);
436 		assert(n > 0 && n < (int)sizeof(nvlname));
437 		nvlist_add_string(groups, nvlname, names[i]);
438 	}
439 	nvlist_move_nvlist(limits, "groups", groups);
440 	return (cap_limit_set(chan, limits));
441 }
442 
443 /*
444  * Service functions.
445  */
446 static bool
447 grp_allowed_cmd(const nvlist_t *limits, const char *cmd)
448 {
449 
450 	if (limits == NULL)
451 		return (true);
452 
453 	/*
454 	 * If no limit was set on allowed commands, then all commands
455 	 * are allowed.
456 	 */
457 	if (!nvlist_exists_nvlist(limits, "cmds"))
458 		return (true);
459 
460 	limits = nvlist_get_nvlist(limits, "cmds");
461 	return (nvlist_exists_null(limits, cmd));
462 }
463 
464 static int
465 grp_allowed_cmds(const nvlist_t *oldlimits, const nvlist_t *newlimits)
466 {
467 	const char *name;
468 	void *cookie;
469 	int type;
470 
471 	cookie = NULL;
472 	while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) {
473 		if (type != NV_TYPE_NULL)
474 			return (EINVAL);
475 		if (!grp_allowed_cmd(oldlimits, name))
476 			return (ENOTCAPABLE);
477 	}
478 
479 	return (0);
480 }
481 
482 static bool
483 grp_allowed_group(const nvlist_t *limits, const char *gname, gid_t gid)
484 {
485 	const char *name;
486 	void *cookie;
487 	int type;
488 
489 	if (limits == NULL)
490 		return (true);
491 
492 	/*
493 	 * If no limit was set on allowed groups, then all groups are allowed.
494 	 */
495 	if (!nvlist_exists_nvlist(limits, "groups"))
496 		return (true);
497 
498 	limits = nvlist_get_nvlist(limits, "groups");
499 	cookie = NULL;
500 	while ((name = nvlist_next(limits, &type, &cookie)) != NULL) {
501 		switch (type) {
502 		case NV_TYPE_NUMBER:
503 			if (gid != (gid_t)-1 &&
504 			    nvlist_get_number(limits, name) == (uint64_t)gid) {
505 				return (true);
506 			}
507 			break;
508 		case NV_TYPE_STRING:
509 			if (gname != NULL &&
510 			    strcmp(nvlist_get_string(limits, name),
511 			    gname) == 0) {
512 				return (true);
513 			}
514 			break;
515 		default:
516 			abort();
517 		}
518 	}
519 
520 	return (false);
521 }
522 
523 static int
524 grp_allowed_groups(const nvlist_t *oldlimits, const nvlist_t *newlimits)
525 {
526 	const char *name, *gname;
527 	void *cookie;
528 	gid_t gid;
529 	int type;
530 
531 	cookie = NULL;
532 	while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) {
533 		switch (type) {
534 		case NV_TYPE_NUMBER:
535 			gid = (gid_t)nvlist_get_number(newlimits, name);
536 			gname = NULL;
537 			break;
538 		case NV_TYPE_STRING:
539 			gid = (gid_t)-1;
540 			gname = nvlist_get_string(newlimits, name);
541 			break;
542 		default:
543 			return (EINVAL);
544 		}
545 		if (!grp_allowed_group(oldlimits, gname, gid))
546 			return (ENOTCAPABLE);
547 	}
548 
549 	return (0);
550 }
551 
552 static bool
553 grp_allowed_field(const nvlist_t *limits, const char *field)
554 {
555 
556 	if (limits == NULL)
557 		return (true);
558 
559 	/*
560 	 * If no limit was set on allowed fields, then all fields are allowed.
561 	 */
562 	if (!nvlist_exists_nvlist(limits, "fields"))
563 		return (true);
564 
565 	limits = nvlist_get_nvlist(limits, "fields");
566 	return (nvlist_exists_null(limits, field));
567 }
568 
569 static int
570 grp_allowed_fields(const nvlist_t *oldlimits, const nvlist_t *newlimits)
571 {
572 	const char *name;
573 	void *cookie;
574 	int type;
575 
576 	cookie = NULL;
577 	while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) {
578 		if (type != NV_TYPE_NULL)
579 			return (EINVAL);
580 		if (!grp_allowed_field(oldlimits, name))
581 			return (ENOTCAPABLE);
582 	}
583 
584 	return (0);
585 }
586 
587 static bool
588 grp_pack(const nvlist_t *limits, const struct group *grp, nvlist_t *nvl)
589 {
590 	char nvlname[64];
591 	int n;
592 
593 	if (grp == NULL)
594 		return (true);
595 
596 	/*
597 	 * If either name or GID is allowed, we allow it.
598 	 */
599 	if (!grp_allowed_group(limits, grp->gr_name, grp->gr_gid))
600 		return (false);
601 
602 	if (grp_allowed_field(limits, "gr_name"))
603 		nvlist_add_string(nvl, "gr_name", grp->gr_name);
604 	else
605 		nvlist_add_string(nvl, "gr_name", "");
606 	if (grp_allowed_field(limits, "gr_passwd"))
607 		nvlist_add_string(nvl, "gr_passwd", grp->gr_passwd);
608 	else
609 		nvlist_add_string(nvl, "gr_passwd", "");
610 	if (grp_allowed_field(limits, "gr_gid"))
611 		nvlist_add_number(nvl, "gr_gid", (uint64_t)grp->gr_gid);
612 	else
613 		nvlist_add_number(nvl, "gr_gid", (uint64_t)-1);
614 	if (grp_allowed_field(limits, "gr_mem") && grp->gr_mem[0] != NULL) {
615 		unsigned int ngroups;
616 
617 		for (ngroups = 0; grp->gr_mem[ngroups] != NULL; ngroups++) {
618 			n = snprintf(nvlname, sizeof(nvlname), "gr_mem[%u]",
619 			    ngroups);
620 			assert(n > 0 && n < (ssize_t)sizeof(nvlname));
621 			nvlist_add_string(nvl, nvlname, grp->gr_mem[ngroups]);
622 		}
623 		nvlist_add_number(nvl, "gr_nmem", (uint64_t)ngroups);
624 	}
625 
626 	return (true);
627 }
628 
629 static int
630 grp_getgrent(const nvlist_t *limits, const nvlist_t *nvlin __unused,
631     nvlist_t *nvlout)
632 {
633 	struct group *grp;
634 
635 	for (;;) {
636 		errno = 0;
637 		grp = getgrent();
638 		if (errno != 0)
639 			return (errno);
640 		if (grp_pack(limits, grp, nvlout))
641 			return (0);
642 	}
643 
644 	/* NOTREACHED */
645 }
646 
647 static int
648 grp_getgrnam(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout)
649 {
650 	struct group *grp;
651 	const char *name;
652 
653 	if (!nvlist_exists_string(nvlin, "name"))
654 		return (EINVAL);
655 	name = nvlist_get_string(nvlin, "name");
656 	assert(name != NULL);
657 
658 	errno = 0;
659 	grp = getgrnam(name);
660 	if (errno != 0)
661 		return (errno);
662 
663 	(void)grp_pack(limits, grp, nvlout);
664 
665 	return (0);
666 }
667 
668 static int
669 grp_getgrgid(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout)
670 {
671 	struct group *grp;
672 	gid_t gid;
673 
674 	if (!nvlist_exists_number(nvlin, "gid"))
675 		return (EINVAL);
676 
677 	gid = (gid_t)nvlist_get_number(nvlin, "gid");
678 
679 	errno = 0;
680 	grp = getgrgid(gid);
681 	if (errno != 0)
682 		return (errno);
683 
684 	(void)grp_pack(limits, grp, nvlout);
685 
686 	return (0);
687 }
688 
689 static int
690 grp_setgroupent(const nvlist_t *limits __unused, const nvlist_t *nvlin,
691     nvlist_t *nvlout __unused)
692 {
693 	int stayopen;
694 
695 	if (!nvlist_exists_bool(nvlin, "stayopen"))
696 		return (EINVAL);
697 
698 	stayopen = nvlist_get_bool(nvlin, "stayopen") ? 1 : 0;
699 
700 	return (setgroupent(stayopen) == 0 ? EFAULT : 0);
701 }
702 
703 static int
704 grp_setgrent(const nvlist_t *limits __unused, const nvlist_t *nvlin __unused,
705     nvlist_t *nvlout __unused)
706 {
707 
708 	setgrent();
709 
710 	return (0);
711 }
712 
713 static int
714 grp_endgrent(const nvlist_t *limits __unused, const nvlist_t *nvlin __unused,
715     nvlist_t *nvlout __unused)
716 {
717 
718 	endgrent();
719 
720 	return (0);
721 }
722 
723 static int
724 grp_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits)
725 {
726 	const nvlist_t *limits;
727 	const char *name;
728 	void *cookie;
729 	int error, type;
730 
731 	if (oldlimits != NULL && nvlist_exists_nvlist(oldlimits, "cmds") &&
732 	    !nvlist_exists_nvlist(newlimits, "cmds")) {
733 		return (ENOTCAPABLE);
734 	}
735 	if (oldlimits != NULL && nvlist_exists_nvlist(oldlimits, "fields") &&
736 	    !nvlist_exists_nvlist(newlimits, "fields")) {
737 		return (ENOTCAPABLE);
738 	}
739 	if (oldlimits != NULL && nvlist_exists_nvlist(oldlimits, "groups") &&
740 	    !nvlist_exists_nvlist(newlimits, "groups")) {
741 		return (ENOTCAPABLE);
742 	}
743 
744 	cookie = NULL;
745 	while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) {
746 		if (type != NV_TYPE_NVLIST)
747 			return (EINVAL);
748 		limits = nvlist_get_nvlist(newlimits, name);
749 		if (strcmp(name, "cmds") == 0)
750 			error = grp_allowed_cmds(oldlimits, limits);
751 		else if (strcmp(name, "fields") == 0)
752 			error = grp_allowed_fields(oldlimits, limits);
753 		else if (strcmp(name, "groups") == 0)
754 			error = grp_allowed_groups(oldlimits, limits);
755 		else
756 			error = EINVAL;
757 		if (error != 0)
758 			return (error);
759 	}
760 
761 	return (0);
762 }
763 
764 static int
765 grp_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin,
766     nvlist_t *nvlout)
767 {
768 	int error;
769 
770 	if (!grp_allowed_cmd(limits, cmd))
771 		return (ENOTCAPABLE);
772 
773 	if (strcmp(cmd, "getgrent") == 0 || strcmp(cmd, "getgrent_r") == 0)
774 		error = grp_getgrent(limits, nvlin, nvlout);
775 	else if (strcmp(cmd, "getgrnam") == 0 || strcmp(cmd, "getgrnam_r") == 0)
776 		error = grp_getgrnam(limits, nvlin, nvlout);
777 	else if (strcmp(cmd, "getgrgid") == 0 || strcmp(cmd, "getgrgid_r") == 0)
778 		error = grp_getgrgid(limits, nvlin, nvlout);
779 	else if (strcmp(cmd, "setgroupent") == 0)
780 		error = grp_setgroupent(limits, nvlin, nvlout);
781 	else if (strcmp(cmd, "setgrent") == 0)
782 		error = grp_setgrent(limits, nvlin, nvlout);
783 	else if (strcmp(cmd, "endgrent") == 0)
784 		error = grp_endgrent(limits, nvlin, nvlout);
785 	else
786 		error = EINVAL;
787 
788 	return (error);
789 }
790 
791 CREATE_SERVICE("system.grp", grp_limit, grp_command, 0);
792