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