1/*-
2 * Copyright (c) 2013 The FreeBSD Foundation
3 * All rights reserved.
4 *
5 * This software was developed by Pawel Jakub Dawidek under sponsorship from
6 * the FreeBSD Foundation.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 */
29
30#include <sys/cdefs.h>
31__FBSDID("$FreeBSD$");
32
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
48static struct group ggrp;
49static char *gbuffer;
50static size_t gbufsize;
51
52static int
53group_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
74static int
75group_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
92static int
93group_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
149static int
150group_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	memset(grp, 0, 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
176static int
177cap_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, 0);
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
245static struct group *
246cap_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
266struct group *
267cap_getgrent(cap_channel_t *chan)
268{
269
270	return (cap_getgrcommon(chan, "getgrent", NULL, 0));
271}
272
273struct group *
274cap_getgrnam(cap_channel_t *chan, const char *name)
275{
276
277	return (cap_getgrcommon(chan, "getgrnam", name, 0));
278}
279
280struct group *
281cap_getgrgid(cap_channel_t *chan, gid_t gid)
282{
283
284	return (cap_getgrcommon(chan, "getgrgid", NULL, gid));
285}
286
287int
288cap_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
296int
297cap_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
305int
306cap_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
314int
315cap_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, 0);
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
335int
336cap_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, 0);
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
355void
356cap_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, 0));
364}
365
366int
367cap_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
387int
388cap_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
409int
410cap_grp_limit_groups(cap_channel_t *chan, const char * const *names,
411    size_t nnames, 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 */
444static bool
445grp_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
462static int
463grp_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
480static bool
481grp_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
521static int
522grp_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
550static bool
551grp_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
567static int
568grp_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
585static bool
586grp_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
627static int
628grp_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
645static int
646grp_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
666static int
667grp_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
687static int
688grp_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
701static int
702grp_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
711static int
712grp_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
721static int
722grp_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
762static int
763grp_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
789CREATE_SERVICE("system.grp", grp_limit, grp_command, 0);
790