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/types.h>
34 #include <sys/nv.h>
35 
36 #include <assert.h>
37 #include <errno.h>
38 #include <pwd.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
42 
43 #include <libcasper.h>
44 #include <libcasper_service.h>
45 
46 #include "cap_pwd.h"
47 
48 static struct passwd gpwd;
49 static char *gbuffer;
50 static size_t gbufsize;
51 
52 static int
53 passwd_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 passwd_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 passwd_unpack(const nvlist_t *nvl, struct passwd *pwd, char *buffer,
94     size_t bufsize)
95 {
96 	int error;
97 
98 	if (!nvlist_exists_string(nvl, "pw_name"))
99 		return (EINVAL);
100 
101 	memset(pwd, 0, sizeof(*pwd));
102 
103 	error = passwd_unpack_string(nvl, "pw_name", &pwd->pw_name, &buffer,
104 	    &bufsize);
105 	if (error != 0)
106 		return (error);
107 	pwd->pw_uid = (uid_t)nvlist_get_number(nvl, "pw_uid");
108 	pwd->pw_gid = (gid_t)nvlist_get_number(nvl, "pw_gid");
109 	pwd->pw_change = (time_t)nvlist_get_number(nvl, "pw_change");
110 	error = passwd_unpack_string(nvl, "pw_passwd", &pwd->pw_passwd, &buffer,
111 	    &bufsize);
112 	if (error != 0)
113 		return (error);
114 	error = passwd_unpack_string(nvl, "pw_class", &pwd->pw_class, &buffer,
115 	    &bufsize);
116 	if (error != 0)
117 		return (error);
118 	error = passwd_unpack_string(nvl, "pw_gecos", &pwd->pw_gecos, &buffer,
119 	    &bufsize);
120 	if (error != 0)
121 		return (error);
122 	error = passwd_unpack_string(nvl, "pw_dir", &pwd->pw_dir, &buffer,
123 	    &bufsize);
124 	if (error != 0)
125 		return (error);
126 	error = passwd_unpack_string(nvl, "pw_shell", &pwd->pw_shell, &buffer,
127 	    &bufsize);
128 	if (error != 0)
129 		return (error);
130 	pwd->pw_expire = (time_t)nvlist_get_number(nvl, "pw_expire");
131 	pwd->pw_fields = (int)nvlist_get_number(nvl, "pw_fields");
132 
133 	return (0);
134 }
135 
136 static int
137 cap_getpwcommon_r(cap_channel_t *chan, const char *cmd, const char *login,
138     uid_t uid, struct passwd *pwd, char *buffer, size_t bufsize,
139     struct passwd **result)
140 {
141 	nvlist_t *nvl;
142 	bool getpw_r;
143 	int error;
144 
145 	nvl = nvlist_create(0);
146 	nvlist_add_string(nvl, "cmd", cmd);
147 	if (strcmp(cmd, "getpwent") == 0 || strcmp(cmd, "getpwent_r") == 0) {
148 		/* Add nothing. */
149 	} else if (strcmp(cmd, "getpwnam") == 0 ||
150 	    strcmp(cmd, "getpwnam_r") == 0) {
151 		nvlist_add_string(nvl, "name", login);
152 	} else if (strcmp(cmd, "getpwuid") == 0 ||
153 	    strcmp(cmd, "getpwuid_r") == 0) {
154 		nvlist_add_number(nvl, "uid", (uint64_t)uid);
155 	} else {
156 		abort();
157 	}
158 	nvl = cap_xfer_nvlist(chan, nvl, 0);
159 	if (nvl == NULL) {
160 		assert(errno != 0);
161 		*result = NULL;
162 		return (errno);
163 	}
164 	error = (int)nvlist_get_number(nvl, "error");
165 	if (error != 0) {
166 		nvlist_destroy(nvl);
167 		*result = NULL;
168 		return (error);
169 	}
170 
171 	if (!nvlist_exists_string(nvl, "pw_name")) {
172 		/* Not found. */
173 		nvlist_destroy(nvl);
174 		*result = NULL;
175 		return (0);
176 	}
177 
178 	getpw_r = (strcmp(cmd, "getpwent_r") == 0 ||
179 	    strcmp(cmd, "getpwnam_r") == 0 || strcmp(cmd, "getpwuid_r") == 0);
180 
181 	for (;;) {
182 		error = passwd_unpack(nvl, pwd, buffer, bufsize);
183 		if (getpw_r || error != ERANGE)
184 			break;
185 		assert(buffer == gbuffer);
186 		assert(bufsize == gbufsize);
187 		error = passwd_resize();
188 		if (error != 0)
189 			break;
190 		/* Update pointers after resize. */
191 		buffer = gbuffer;
192 		bufsize = gbufsize;
193 	}
194 
195 	nvlist_destroy(nvl);
196 
197 	if (error == 0)
198 		*result = pwd;
199 	else
200 		*result = NULL;
201 
202 	return (error);
203 }
204 
205 static struct passwd *
206 cap_getpwcommon(cap_channel_t *chan, const char *cmd, const char *login,
207     uid_t uid)
208 {
209 	struct passwd *result;
210 	int error, serrno;
211 
212 	serrno = errno;
213 
214 	error = cap_getpwcommon_r(chan, cmd, login, uid, &gpwd, gbuffer,
215 	    gbufsize, &result);
216 	if (error != 0) {
217 		errno = error;
218 		return (NULL);
219 	}
220 
221 	errno = serrno;
222 
223 	return (result);
224 }
225 
226 struct passwd *
227 cap_getpwent(cap_channel_t *chan)
228 {
229 
230 	return (cap_getpwcommon(chan, "getpwent", NULL, 0));
231 }
232 
233 struct passwd *
234 cap_getpwnam(cap_channel_t *chan, const char *login)
235 {
236 
237 	return (cap_getpwcommon(chan, "getpwnam", login, 0));
238 }
239 
240 struct passwd *
241 cap_getpwuid(cap_channel_t *chan, uid_t uid)
242 {
243 
244 	return (cap_getpwcommon(chan, "getpwuid", NULL, uid));
245 }
246 
247 int
248 cap_getpwent_r(cap_channel_t *chan, struct passwd *pwd, char *buffer,
249     size_t bufsize, struct passwd **result)
250 {
251 
252 	return (cap_getpwcommon_r(chan, "getpwent_r", NULL, 0, pwd, buffer,
253 	    bufsize, result));
254 }
255 
256 int
257 cap_getpwnam_r(cap_channel_t *chan, const char *name, struct passwd *pwd,
258     char *buffer, size_t bufsize, struct passwd **result)
259 {
260 
261 	return (cap_getpwcommon_r(chan, "getpwnam_r", name, 0, pwd, buffer,
262 	    bufsize, result));
263 }
264 
265 int
266 cap_getpwuid_r(cap_channel_t *chan, uid_t uid, struct passwd *pwd, char *buffer,
267     size_t bufsize, struct passwd **result)
268 {
269 
270 	return (cap_getpwcommon_r(chan, "getpwuid_r", NULL, uid, pwd, buffer,
271 	    bufsize, result));
272 }
273 
274 int
275 cap_setpassent(cap_channel_t *chan, int stayopen)
276 {
277 	nvlist_t *nvl;
278 
279 	nvl = nvlist_create(0);
280 	nvlist_add_string(nvl, "cmd", "setpassent");
281 	nvlist_add_bool(nvl, "stayopen", stayopen != 0);
282 	nvl = cap_xfer_nvlist(chan, nvl, 0);
283 	if (nvl == NULL)
284 		return (0);
285 	if (nvlist_get_number(nvl, "error") != 0) {
286 		errno = nvlist_get_number(nvl, "error");
287 		nvlist_destroy(nvl);
288 		return (0);
289 	}
290 	nvlist_destroy(nvl);
291 
292 	return (1);
293 }
294 
295 static void
296 cap_set_end_pwent(cap_channel_t *chan, const char *cmd)
297 {
298 	nvlist_t *nvl;
299 
300 	nvl = nvlist_create(0);
301 	nvlist_add_string(nvl, "cmd", cmd);
302 	/* Ignore any errors, we have no way to report them. */
303 	nvlist_destroy(cap_xfer_nvlist(chan, nvl, 0));
304 }
305 
306 void
307 cap_setpwent(cap_channel_t *chan)
308 {
309 
310 	cap_set_end_pwent(chan, "setpwent");
311 }
312 
313 void
314 cap_endpwent(cap_channel_t *chan)
315 {
316 
317 	cap_set_end_pwent(chan, "endpwent");
318 }
319 
320 int
321 cap_pwd_limit_cmds(cap_channel_t *chan, const char * const *cmds, size_t ncmds)
322 {
323 	nvlist_t *limits, *nvl;
324 	unsigned int i;
325 
326 	if (cap_limit_get(chan, &limits) < 0)
327 		return (-1);
328 	if (limits == NULL) {
329 		limits = nvlist_create(0);
330 	} else {
331 		if (nvlist_exists_nvlist(limits, "cmds"))
332 			nvlist_free_nvlist(limits, "cmds");
333 	}
334 	nvl = nvlist_create(0);
335 	for (i = 0; i < ncmds; i++)
336 		nvlist_add_null(nvl, cmds[i]);
337 	nvlist_move_nvlist(limits, "cmds", nvl);
338 	return (cap_limit_set(chan, limits));
339 }
340 
341 int
342 cap_pwd_limit_fields(cap_channel_t *chan, const char * const *fields,
343     size_t nfields)
344 {
345 	nvlist_t *limits, *nvl;
346 	unsigned int i;
347 
348 	if (cap_limit_get(chan, &limits) < 0)
349 		return (-1);
350 	if (limits == NULL) {
351 		limits = nvlist_create(0);
352 	} else {
353 		if (nvlist_exists_nvlist(limits, "fields"))
354 			nvlist_free_nvlist(limits, "fields");
355 	}
356 	nvl = nvlist_create(0);
357 	for (i = 0; i < nfields; i++)
358 		nvlist_add_null(nvl, fields[i]);
359 	nvlist_move_nvlist(limits, "fields", nvl);
360 	return (cap_limit_set(chan, limits));
361 }
362 
363 int
364 cap_pwd_limit_users(cap_channel_t *chan, const char * const *names,
365     size_t nnames, uid_t *uids, size_t nuids)
366 {
367 	nvlist_t *limits, *users;
368 	char nvlname[64];
369 	unsigned int i;
370 	int n;
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, "users"))
378 			nvlist_free_nvlist(limits, "users");
379 	}
380 	users = nvlist_create(0);
381 	for (i = 0; i < nuids; i++) {
382 		n = snprintf(nvlname, sizeof(nvlname), "uid%u", i);
383 		assert(n > 0 && n < (int)sizeof(nvlname));
384 		nvlist_add_number(users, nvlname, (uint64_t)uids[i]);
385 	}
386 	for (i = 0; i < nnames; i++) {
387 		n = snprintf(nvlname, sizeof(nvlname), "name%u", i);
388 		assert(n > 0 && n < (int)sizeof(nvlname));
389 		nvlist_add_string(users, nvlname, names[i]);
390 	}
391 	nvlist_move_nvlist(limits, "users", users);
392 	return (cap_limit_set(chan, limits));
393 }
394 
395 
396 /*
397  * Service functions.
398  */
399 static bool
400 pwd_allowed_cmd(const nvlist_t *limits, const char *cmd)
401 {
402 
403 	if (limits == NULL)
404 		return (true);
405 
406 	/*
407 	 * If no limit was set on allowed commands, then all commands
408 	 * are allowed.
409 	 */
410 	if (!nvlist_exists_nvlist(limits, "cmds"))
411 		return (true);
412 
413 	limits = nvlist_get_nvlist(limits, "cmds");
414 	return (nvlist_exists_null(limits, cmd));
415 }
416 
417 static int
418 pwd_allowed_cmds(const nvlist_t *oldlimits, const nvlist_t *newlimits)
419 {
420 	const char *name;
421 	void *cookie;
422 	int type;
423 
424 	cookie = NULL;
425 	while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) {
426 		if (type != NV_TYPE_NULL)
427 			return (EINVAL);
428 		if (!pwd_allowed_cmd(oldlimits, name))
429 			return (ENOTCAPABLE);
430 	}
431 
432 	return (0);
433 }
434 
435 static bool
436 pwd_allowed_user(const nvlist_t *limits, const char *uname, uid_t uid)
437 {
438 	const char *name;
439 	void *cookie;
440 	int type;
441 
442 	if (limits == NULL)
443 		return (true);
444 
445 	/*
446 	 * If no limit was set on allowed users, then all users are allowed.
447 	 */
448 	if (!nvlist_exists_nvlist(limits, "users"))
449 		return (true);
450 
451 	limits = nvlist_get_nvlist(limits, "users");
452 	cookie = NULL;
453 	while ((name = nvlist_next(limits, &type, &cookie)) != NULL) {
454 		switch (type) {
455 		case NV_TYPE_NUMBER:
456 			if (uid != (uid_t)-1 &&
457 			    nvlist_get_number(limits, name) == (uint64_t)uid) {
458 				return (true);
459 			}
460 			break;
461 		case NV_TYPE_STRING:
462 			if (uname != NULL &&
463 			    strcmp(nvlist_get_string(limits, name),
464 			    uname) == 0) {
465 				return (true);
466 			}
467 			break;
468 		default:
469 			abort();
470 		}
471 	}
472 
473 	return (false);
474 }
475 
476 static int
477 pwd_allowed_users(const nvlist_t *oldlimits, const nvlist_t *newlimits)
478 {
479 	const char *name, *uname;
480 	void *cookie;
481 	uid_t uid;
482 	int type;
483 
484 	cookie = NULL;
485 	while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) {
486 		switch (type) {
487 		case NV_TYPE_NUMBER:
488 			uid = (uid_t)nvlist_get_number(newlimits, name);
489 			uname = NULL;
490 			break;
491 		case NV_TYPE_STRING:
492 			uid = (uid_t)-1;
493 			uname = nvlist_get_string(newlimits, name);
494 			break;
495 		default:
496 			return (EINVAL);
497 		}
498 		if (!pwd_allowed_user(oldlimits, uname, uid))
499 			return (ENOTCAPABLE);
500 	}
501 
502 	return (0);
503 }
504 
505 static bool
506 pwd_allowed_field(const nvlist_t *limits, const char *field)
507 {
508 
509 	if (limits == NULL)
510 		return (true);
511 
512 	/*
513 	 * If no limit was set on allowed fields, then all fields are allowed.
514 	 */
515 	if (!nvlist_exists_nvlist(limits, "fields"))
516 		return (true);
517 
518 	limits = nvlist_get_nvlist(limits, "fields");
519 	return (nvlist_exists_null(limits, field));
520 }
521 
522 static int
523 pwd_allowed_fields(const nvlist_t *oldlimits, const nvlist_t *newlimits)
524 {
525 	const char *name;
526 	void *cookie;
527 	int type;
528 
529 	cookie = NULL;
530 	while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) {
531 		if (type != NV_TYPE_NULL)
532 			return (EINVAL);
533 		if (!pwd_allowed_field(oldlimits, name))
534 			return (ENOTCAPABLE);
535 	}
536 
537 	return (0);
538 }
539 
540 static bool
541 pwd_pack(const nvlist_t *limits, const struct passwd *pwd, nvlist_t *nvl)
542 {
543 	int fields;
544 
545 	if (pwd == NULL)
546 		return (true);
547 
548 	/*
549 	 * If either name or UID is allowed, we allow it.
550 	 */
551 	if (!pwd_allowed_user(limits, pwd->pw_name, pwd->pw_uid))
552 		return (false);
553 
554 	fields = pwd->pw_fields;
555 
556 	if (pwd_allowed_field(limits, "pw_name")) {
557 		nvlist_add_string(nvl, "pw_name", pwd->pw_name);
558 	} else {
559 		nvlist_add_string(nvl, "pw_name", "");
560 		fields &= ~_PWF_NAME;
561 	}
562 	if (pwd_allowed_field(limits, "pw_uid")) {
563 		nvlist_add_number(nvl, "pw_uid", (uint64_t)pwd->pw_uid);
564 	} else {
565 		nvlist_add_number(nvl, "pw_uid", (uint64_t)-1);
566 		fields &= ~_PWF_UID;
567 	}
568 	if (pwd_allowed_field(limits, "pw_gid")) {
569 		nvlist_add_number(nvl, "pw_gid", (uint64_t)pwd->pw_gid);
570 	} else {
571 		nvlist_add_number(nvl, "pw_gid", (uint64_t)-1);
572 		fields &= ~_PWF_GID;
573 	}
574 	if (pwd_allowed_field(limits, "pw_change")) {
575 		nvlist_add_number(nvl, "pw_change", (uint64_t)pwd->pw_change);
576 	} else {
577 		nvlist_add_number(nvl, "pw_change", (uint64_t)0);
578 		fields &= ~_PWF_CHANGE;
579 	}
580 	if (pwd_allowed_field(limits, "pw_passwd")) {
581 		nvlist_add_string(nvl, "pw_passwd", pwd->pw_passwd);
582 	} else {
583 		nvlist_add_string(nvl, "pw_passwd", "");
584 		fields &= ~_PWF_PASSWD;
585 	}
586 	if (pwd_allowed_field(limits, "pw_class")) {
587 		nvlist_add_string(nvl, "pw_class", pwd->pw_class);
588 	} else {
589 		nvlist_add_string(nvl, "pw_class", "");
590 		fields &= ~_PWF_CLASS;
591 	}
592 	if (pwd_allowed_field(limits, "pw_gecos")) {
593 		nvlist_add_string(nvl, "pw_gecos", pwd->pw_gecos);
594 	} else {
595 		nvlist_add_string(nvl, "pw_gecos", "");
596 		fields &= ~_PWF_GECOS;
597 	}
598 	if (pwd_allowed_field(limits, "pw_dir")) {
599 		nvlist_add_string(nvl, "pw_dir", pwd->pw_dir);
600 	} else {
601 		nvlist_add_string(nvl, "pw_dir", "");
602 		fields &= ~_PWF_DIR;
603 	}
604 	if (pwd_allowed_field(limits, "pw_shell")) {
605 		nvlist_add_string(nvl, "pw_shell", pwd->pw_shell);
606 	} else {
607 		nvlist_add_string(nvl, "pw_shell", "");
608 		fields &= ~_PWF_SHELL;
609 	}
610 	if (pwd_allowed_field(limits, "pw_expire")) {
611 		nvlist_add_number(nvl, "pw_expire", (uint64_t)pwd->pw_expire);
612 	} else {
613 		nvlist_add_number(nvl, "pw_expire", (uint64_t)0);
614 		fields &= ~_PWF_EXPIRE;
615 	}
616 	nvlist_add_number(nvl, "pw_fields", (uint64_t)fields);
617 
618 	return (true);
619 }
620 
621 static int
622 pwd_getpwent(const nvlist_t *limits, const nvlist_t *nvlin __unused,
623     nvlist_t *nvlout)
624 {
625 	struct passwd *pwd;
626 
627 	for (;;) {
628 		errno = 0;
629 		pwd = getpwent();
630 		if (errno != 0)
631 			return (errno);
632 		if (pwd_pack(limits, pwd, nvlout))
633 			return (0);
634 	}
635 
636 	/* NOTREACHED */
637 }
638 
639 static int
640 pwd_getpwnam(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout)
641 {
642 	struct passwd *pwd;
643 	const char *name;
644 
645 	if (!nvlist_exists_string(nvlin, "name"))
646 		return (EINVAL);
647 	name = nvlist_get_string(nvlin, "name");
648 	assert(name != NULL);
649 
650 	errno = 0;
651 	pwd = getpwnam(name);
652 	if (errno != 0)
653 		return (errno);
654 
655 	(void)pwd_pack(limits, pwd, nvlout);
656 
657 	return (0);
658 }
659 
660 static int
661 pwd_getpwuid(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout)
662 {
663 	struct passwd *pwd;
664 	uid_t uid;
665 
666 	if (!nvlist_exists_number(nvlin, "uid"))
667 		return (EINVAL);
668 
669 	uid = (uid_t)nvlist_get_number(nvlin, "uid");
670 
671 	errno = 0;
672 	pwd = getpwuid(uid);
673 	if (errno != 0)
674 		return (errno);
675 
676 	(void)pwd_pack(limits, pwd, nvlout);
677 
678 	return (0);
679 }
680 
681 static int
682 pwd_setpassent(const nvlist_t *limits __unused, const nvlist_t *nvlin,
683     nvlist_t *nvlout __unused)
684 {
685 	int stayopen;
686 
687 	if (!nvlist_exists_bool(nvlin, "stayopen"))
688 		return (EINVAL);
689 
690 	stayopen = nvlist_get_bool(nvlin, "stayopen") ? 1 : 0;
691 
692 	return (setpassent(stayopen) == 0 ? EFAULT : 0);
693 }
694 
695 static int
696 pwd_setpwent(const nvlist_t *limits __unused, const nvlist_t *nvlin __unused,
697     nvlist_t *nvlout __unused)
698 {
699 
700 	setpwent();
701 
702 	return (0);
703 }
704 
705 static int
706 pwd_endpwent(const nvlist_t *limits __unused, const nvlist_t *nvlin __unused,
707     nvlist_t *nvlout __unused)
708 {
709 
710 	endpwent();
711 
712 	return (0);
713 }
714 
715 static int
716 pwd_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits)
717 {
718 	const nvlist_t *limits;
719 	const char *name;
720 	void *cookie;
721 	int error, type;
722 
723 	if (oldlimits != NULL && nvlist_exists_nvlist(oldlimits, "cmds") &&
724 	    !nvlist_exists_nvlist(newlimits, "cmds")) {
725 		return (ENOTCAPABLE);
726 	}
727 	if (oldlimits != NULL && nvlist_exists_nvlist(oldlimits, "fields") &&
728 	    !nvlist_exists_nvlist(newlimits, "fields")) {
729 		return (ENOTCAPABLE);
730 	}
731 	if (oldlimits != NULL && nvlist_exists_nvlist(oldlimits, "users") &&
732 	    !nvlist_exists_nvlist(newlimits, "users")) {
733 		return (ENOTCAPABLE);
734 	}
735 
736 	cookie = NULL;
737 	while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) {
738 		if (type != NV_TYPE_NVLIST)
739 			return (EINVAL);
740 		limits = nvlist_get_nvlist(newlimits, name);
741 		if (strcmp(name, "cmds") == 0)
742 			error = pwd_allowed_cmds(oldlimits, limits);
743 		else if (strcmp(name, "fields") == 0)
744 			error = pwd_allowed_fields(oldlimits, limits);
745 		else if (strcmp(name, "users") == 0)
746 			error = pwd_allowed_users(oldlimits, limits);
747 		else
748 			error = EINVAL;
749 		if (error != 0)
750 			return (error);
751 	}
752 
753 	return (0);
754 }
755 
756 static int
757 pwd_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin,
758     nvlist_t *nvlout)
759 {
760 	int error;
761 
762 	if (!pwd_allowed_cmd(limits, cmd))
763 		return (ENOTCAPABLE);
764 
765 	if (strcmp(cmd, "getpwent") == 0 || strcmp(cmd, "getpwent_r") == 0)
766 		error = pwd_getpwent(limits, nvlin, nvlout);
767 	else if (strcmp(cmd, "getpwnam") == 0 || strcmp(cmd, "getpwnam_r") == 0)
768 		error = pwd_getpwnam(limits, nvlin, nvlout);
769 	else if (strcmp(cmd, "getpwuid") == 0 || strcmp(cmd, "getpwuid_r") == 0)
770 		error = pwd_getpwuid(limits, nvlin, nvlout);
771 	else if (strcmp(cmd, "setpassent") == 0)
772 		error = pwd_setpassent(limits, nvlin, nvlout);
773 	else if (strcmp(cmd, "setpwent") == 0)
774 		error = pwd_setpwent(limits, nvlin, nvlout);
775 	else if (strcmp(cmd, "endpwent") == 0)
776 		error = pwd_endpwent(limits, nvlin, nvlout);
777 	else
778 		error = EINVAL;
779 
780 	return (error);
781 }
782 
783 CREATE_SERVICE("system.pwd", pwd_limit, pwd_command);
784