1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
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
50static struct group ggrp;
51static char *gbuffer;
52static size_t gbufsize;
53
54static int
55group_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
76static int
77group_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
94static int
95group_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
151static int
152group_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
178static int
179cap_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
247static struct group *
248cap_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
268struct group *
269cap_getgrent(cap_channel_t *chan)
270{
271
272	return (cap_getgrcommon(chan, "getgrent", NULL, 0));
273}
274
275struct group *
276cap_getgrnam(cap_channel_t *chan, const char *name)
277{
278
279	return (cap_getgrcommon(chan, "getgrnam", name, 0));
280}
281
282struct group *
283cap_getgrgid(cap_channel_t *chan, gid_t gid)
284{
285
286	return (cap_getgrcommon(chan, "getgrgid", NULL, gid));
287}
288
289int
290cap_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
298int
299cap_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
307int
308cap_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
316int
317cap_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
337int
338cap_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
357void
358cap_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
368int
369cap_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
389int
390cap_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
411int
412cap_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 */
446static bool
447grp_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
464static int
465grp_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
482static bool
483grp_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
523static int
524grp_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
552static bool
553grp_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
569static int
570grp_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
587static bool
588grp_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
629static int
630grp_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
647static int
648grp_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
668static int
669grp_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
689static int
690grp_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
703static int
704grp_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
713static int
714grp_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
723static int
724grp_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
764static int
765grp_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
791CREATE_SERVICE("system.grp", grp_limit, grp_command, 0);
792