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