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