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