xref: /illumos-gate/usr/src/cmd/passwd/passwd.c (revision 05503cb1c50df2a1ab88ff17f03f7067311b0d2e)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  *
25  * Copyright 2023 OmniOS Community Edition (OmniOSce) Association.
26  */
27 
28 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
29 /*	  All Rights Reserved  	*/
30 
31 /*	Copyright (c) 1987, 1988 Microsoft Corporation	*/
32 /*	  All Rights Reserved	*/
33 
34 /*
35  * passwd is a program whose sole purpose is to manage
36  * the password file, map, or table. It allows system administrator
37  * to add, change and display password attributes.
38  * Non privileged user can change password or display
39  * password attributes which corresponds to their login name.
40  */
41 
42 #include <stdio.h>
43 #include <pwd.h>
44 #include <sys/types.h>
45 #include <errno.h>
46 #include <unistd.h>
47 #include <stdlib.h>
48 #include <locale.h>
49 #include <stdarg.h>
50 #include <errno.h>
51 #include <string.h>
52 #include <security/pam_appl.h>
53 #include <security/pam_modules.h>
54 #include <security/pam_impl.h>
55 #include <rpcsvc/nis.h>
56 #undef GROUP
57 #include <syslog.h>
58 #include <userdefs.h>
59 #include <passwdutil.h>
60 
61 #include <nss_dbdefs.h>
62 
63 #include <deflt.h>
64 
65 #undef	GROUP
66 #include <bsm/adt.h>
67 #include <bsm/adt_event.h>
68 
69 /*
70  * flags indicate password attributes to be modified
71  */
72 
73 #define	LFLAG 0x001		/* lock user's password  */
74 #define	DFLAG 0x002		/* delete user's  password */
75 #define	MFLAG 0x004		/* set max field -- # of days passwd is valid */
76 #define	NFLAG 0x008		/* set min field -- # of days between */
77 				/* password changes */
78 #define	SFLAG 0x010		/* display password attributes */
79 #define	FFLAG 0x020		/* expire  user's password */
80 #define	AFLAG 0x040		/* display password attributes for all users */
81 #define	SAFLAG (SFLAG|AFLAG)	/* display password attributes for all users */
82 #define	WFLAG 0x100		/* warn user to change passwd */
83 #define	OFLAG 0x200		/* domain name */
84 #define	EFLAG 0x400		/* change shell */
85 #define	GFLAG 0x800		/* change gecos information */
86 #define	HFLAG 0x1000		/* change home directory */
87 #define	XFLAG 0x2000		/* no login */
88 #define	UFLAG 0x4000		/* unlock user's password */
89 
90 #define	NONAGEFLAG	(EFLAG | GFLAG | HFLAG)
91 #define	AGEFLAG	(LFLAG | FFLAG | MFLAG | NFLAG | WFLAG | XFLAG | UFLAG)
92 #define	MUTEXFLAG	(DFLAG | LFLAG | XFLAG | UFLAG | SAFLAG)
93 
94 
95 /*
96  * exit code
97  */
98 
99 #define	SUCCESS	0	/* succeeded */
100 #define	NOPERM	1	/* No permission */
101 #define	BADOPT	2	/* Invalid combination of option */
102 #define	FMERR	3	/* File/table manipulation error */
103 #define	FATAL	4	/* Old file/table can not be recovered */
104 #define	FBUSY	5	/* Lock file/table busy */
105 #define	BADSYN	6	/* Incorrect syntax */
106 #define	BADAGE	7	/* Aging is disabled  */
107 #define	NOMEM	8	/* No memory */
108 #define	SYSERR	9	/* System error */
109 #define	EXPIRED	10	/* Account expired */
110 
111 /*
112  * define error messages
113  */
114 #define	MSG_NP		"Permission denied"
115 #define	MSG_BS		"Invalid combination of options"
116 #define	MSG_FE		"Unexpected failure. Password file/table unchanged."
117 #define	MSG_FF		"Unexpected failure. Password file/table missing."
118 #define	MSG_FB		"Password file/table busy. Try again later."
119 #define	MSG_NV  	"Invalid argument to option"
120 #define	MSG_AD		"Password aging is disabled"
121 #define	MSG_RS		"Cannot change from restricted shell %s\n"
122 #define	MSG_NM		"Out of memory."
123 #define	MSG_UNACCEPT	"%s is unacceptable as a new shell\n"
124 #define	MSG_UNAVAIL	"warning: %s is unavailable on this machine\n"
125 #define	MSG_COLON	"':' is not allowed.\n"
126 #define	MSG_MAXLEN	"Maximum number of characters allowed is %d."
127 #define	MSG_CONTROL	"Control characters are not allowed.\n"
128 #define	MSG_SHELL_UNCHANGED	"Login shell unchanged.\n"
129 #define	MSG_GECOS_UNCHANGED	"Finger information unchanged.\n"
130 #define	MSG_DIR_UNCHANGED	"Homedir information unchanged.\n"
131 #define	MSG_NAME	"\nName [%s]: "
132 #define	MSG_HOMEDIR	"\nHome Directory [%s]: "
133 #define	MSG_OLDSHELL	"Old shell: %s\n"
134 #define	MSG_NEWSHELL	"New shell: "
135 #define	MSG_AGAIN	"\nPlease try again\n"
136 #define	MSG_INPUTHDR	"Default values are printed inside of '[]'.\n" \
137 			"To accept the default, type <return>.\n" \
138 			"To have a blank entry, type the word 'none'.\n"
139 #define	MSG_UNKNOWN	"%s: User unknown: %s\n"
140 #define	MSG_ACCOUNT_EXP	"User account has expired: %s\n"
141 #define	MSG_AUTHTOK_EXP	"Your password has been expired for too long.\n" \
142 			"Please contact the system administrator.\n"
143 #define	MSG_NIS_HOMEDIR	"-h does not apply to NIS"
144 #define	MSG_CUR_PASS	"Enter existing login password: "
145 #define	MSG_CUR_PASS_UNAME	"Enter %s's existing login password: "
146 #define	MSG_SUCCESS	"%s: password information changed for %s\n"
147 #define	MSG_SORRY	"%s: Sorry, wrong passwd\n"
148 #define	MSG_INFO	"%s: Changing password for %s\n"
149 
150 
151 /*
152  * return code from ckarg() routine
153  */
154 #define	FAIL 		-1
155 
156 /*
157  *  defind password file name
158  */
159 #define	PASSWD 			"/etc/passwd"
160 
161 #define	MAX_INPUT_LEN		512
162 
163 #define	DEF_ATTEMPTS	3
164 
165 /* Number of characters in that make up an encrypted password (for now) */
166 #define	NUMCP			13
167 
168 #ifdef DEBUG
169 #define	dprintf1	printf
170 #else
171 #define	dprintf1(w, x)
172 #endif
173 
174 extern int	optind;
175 
176 static int		retval = SUCCESS;
177 static int		pam_retval = PAM_SUCCESS;
178 static uid_t		uid;
179 static char		*prognamep;
180 static long		maxdate;	/* password aging information */
181 static int		passwd_conv(int, const struct pam_message **,
182 			    struct pam_response **, void *);
183 static struct pam_conv	pam_conv = {passwd_conv, NULL};
184 static pam_handle_t	*pamh;		/* Authentication handle */
185 static char		*usrname;	/* user whose attribute we update */
186 static adt_session_data_t *ah;  /* audit session handle */
187 static adt_event_data_t *event = NULL; /* event to be generated */
188 
189 static pam_repository_t	auth_rep;
190 static pwu_repository_t	repository;
191 static pwu_repository_t	__REPFILES = { "files", NULL, 0 };
192 
193 /*
194  * Function Declarations
195  */
196 
197 extern	void		setusershell(void);
198 extern	char		*getusershell(void);
199 extern	void		endusershell(void);
200 
201 static	void		passwd_exit(int retcode) __NORETURN;
202 static	void		rusage(void);
203 static	int		ckuid(void);
204 static	int		ckarg(int argc, char **argv, attrlist **attributes);
205 
206 static	int		get_namelist(pwu_repository_t, char ***, int *);
207 static	int		get_namelist_files(char ***, int *);
208 static	int		get_namelist_local(char ***, int *);
209 static	int		get_attr(char *, pwu_repository_t *, attrlist **);
210 static	void		display_attr(char *, attrlist *);
211 static	void		free_attr(attrlist *);
212 static	void		attrlist_add(attrlist **, attrtype, char *);
213 static	void		attrlist_reorder(attrlist **);
214 static	char		*userinput(char *, pwu_repository_t *, attrtype);
215 static	char		*getresponse(char *);
216 
217 /*
218  * main():
219  *	The main routine will call ckarg() to parse the command line
220  *	arguments and call the appropriate functions to perform the
221  *	tasks specified by the arguments. It allows system
222  * 	administrator to add, change and display password attributes.
223  * 	Non privileged user can change password or display
224  * 	password attributes which corresponds to their login name.
225  */
226 
227 int
228 main(int argc, char *argv[])
229 {
230 
231 	int	flag;
232 	char	**namelist;
233 	int	num_user;
234 	int	i;
235 	attrlist *attributes = NULL;
236 	char	*input;
237 	int	tries = 1;
238 	int	updated_reps;
239 
240 
241 	if ((prognamep = strrchr(argv[0], '/')) != NULL)
242 		++prognamep;
243 	else
244 		prognamep = argv[0];
245 
246 	auth_rep.type = NULL;
247 	auth_rep.scope = NULL;
248 	repository.type = NULL;
249 	repository.scope = NULL;
250 	repository.scope_len = 0;
251 
252 
253 	/* initialization for variables, set locale and textdomain  */
254 	i = 0;
255 	flag = 0;
256 
257 	uid = getuid();		/* get the user id */
258 	(void) setlocale(LC_ALL, "");
259 
260 #if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
261 #define	TEXT_DOMAIN "SYS_TEST"	/* Use this only if it weren't */
262 #endif
263 	(void) textdomain(TEXT_DOMAIN);
264 
265 	/*
266 	 * ckarg() parses the arguments. In case of an error,
267 	 * it sets the retval and returns FAIL (-1).
268 	 */
269 
270 	flag = ckarg(argc, argv, &attributes);
271 	dprintf1("flag is %0x\n", flag);
272 	if (flag == FAIL)
273 		passwd_exit(retval);
274 
275 	argc -= optind;
276 
277 	if (argc < 1) {
278 		if ((usrname = getlogin()) == NULL) {
279 			struct passwd *pass = getpwuid(uid);
280 			if (pass != NULL)
281 				usrname = pass->pw_name;
282 			else {
283 				rusage();
284 				exit(NOPERM);
285 			}
286 		} else if (flag == 0) {
287 			/*
288 			 * If flag is zero, change passwd.
289 			 * Otherwise, it will display or
290 			 * modify password aging attributes
291 			 */
292 			(void) fprintf(stderr, gettext(MSG_INFO), prognamep,
293 			    usrname);
294 		}
295 	} else {
296 		usrname = argv[optind];
297 	}
298 
299 	if (pam_start("passwd", usrname, &pam_conv, &pamh) != PAM_SUCCESS) {
300 		passwd_exit(NOPERM);
301 	}
302 
303 	auth_rep.type = repository.type;
304 	auth_rep.scope = repository.scope;
305 	auth_rep.scope_len = repository.scope_len;
306 
307 	if (auth_rep.type != NULL) {
308 		if (pam_set_item(pamh, PAM_REPOSITORY, (void *)&auth_rep)
309 		    != PAM_SUCCESS) {
310 			passwd_exit(NOPERM);
311 		}
312 	}
313 
314 	if (flag ==  SAFLAG) {	/* display password attributes for all users */
315 		retval = get_namelist(repository, &namelist, &num_user);
316 		if (retval != SUCCESS)
317 			(void) passwd_exit(retval);
318 
319 		if (num_user == 0) {
320 			(void) fprintf(stderr, "%s: %s\n", prognamep,
321 			    gettext(MSG_FF));
322 			passwd_exit(FATAL);
323 		}
324 		i = 0;
325 		while (namelist[i] != NULL) {
326 			(void) get_attr(namelist[i], &repository,
327 			    &attributes);
328 			(void) display_attr(namelist[i], attributes);
329 			(void) free(namelist[i]);
330 			(void) free_attr(attributes);
331 			i++;
332 		}
333 		(void) free(namelist);
334 		passwd_exit(SUCCESS);
335 	} else if (flag == SFLAG) { /* display password attributes by user */
336 		if (get_attr(usrname, &repository, &attributes) ==
337 		    PWU_SUCCESS) {
338 			(void) display_attr(usrname, attributes);
339 			(void) free_attr(attributes);
340 		}
341 		passwd_exit(SUCCESS);
342 		/* NOT REACHED */
343 	}
344 
345 
346 	switch (pam_authenticate(pamh, 0)) {
347 	case PAM_SUCCESS:
348 		break;
349 	case PAM_USER_UNKNOWN:
350 		(void) fprintf(stderr, gettext(MSG_UNKNOWN), prognamep,
351 		    usrname);
352 		passwd_exit(NOPERM);
353 		break;
354 	case PAM_PERM_DENIED:
355 		passwd_exit(NOPERM);
356 		break;
357 	case PAM_AUTH_ERR:
358 		(void) fprintf(stderr, gettext(MSG_SORRY), prognamep);
359 		passwd_exit(NOPERM);
360 		break;
361 	default:
362 		/* system error */
363 		passwd_exit(FMERR);
364 		break;
365 	}
366 
367 	if (flag == 0) {			/* changing user password */
368 		int	chk_authtok = 0;	/* check password strength */
369 
370 		dprintf1("call pam_chauthtok() repository name =%s\n",
371 		    repository.type);
372 
373 		/* Set up for Audit */
374 		if (adt_start_session(&ah, NULL, ADT_USE_PROC_DATA) != 0) {
375 			perror("adt_start_session");
376 			passwd_exit(SYSERR);
377 		}
378 		if ((event = adt_alloc_event(ah, ADT_passwd)) == NULL) {
379 			perror("adt_alloc_event");
380 			passwd_exit(NOMEM);
381 		}
382 
383 		/* Don't check account expiration when invoked by root */
384 		if (ckuid() != SUCCESS) {
385 			pam_retval = pam_acct_mgmt(pamh, PAM_SILENT);
386 			switch (pam_retval) {
387 			case PAM_ACCT_EXPIRED:
388 				(void) fprintf(stderr,
389 				    gettext(MSG_ACCOUNT_EXP), usrname);
390 				passwd_exit(EXPIRED);
391 				break;
392 			case PAM_AUTHTOK_EXPIRED:
393 				(void) fprintf(stderr,
394 				    gettext(MSG_AUTHTOK_EXP));
395 				passwd_exit(NOPERM);
396 				break;
397 			case PAM_NEW_AUTHTOK_REQD:
398 				/* valid error when changing passwords */
399 				break;
400 			case PAM_SUCCESS:
401 				/* Ok to change password */
402 				break;
403 			default:
404 				passwd_exit(NOPERM);
405 			}
406 		}
407 
408 
409 		pam_retval = PAM_AUTHTOK_ERR;
410 		tries = 1;
411 		if (ckuid() == SUCCESS) {
412 			/* bypass password strength checks */
413 			chk_authtok = PAM_NO_AUTHTOK_CHECK;
414 		}
415 
416 		while (pam_retval == PAM_AUTHTOK_ERR && tries <= DEF_ATTEMPTS) {
417 			if (tries > 1)
418 				(void) printf(gettext(MSG_AGAIN));
419 			pam_retval = pam_chauthtok(pamh, chk_authtok);
420 			if (pam_retval == PAM_TRY_AGAIN) {
421 				(void) sleep(1);
422 				pam_retval = pam_chauthtok(pamh, chk_authtok);
423 			}
424 			tries++;
425 		}
426 
427 		switch (pam_retval) {
428 		case PAM_SUCCESS:
429 			retval = SUCCESS;
430 			break;
431 		case PAM_AUTHTOK_DISABLE_AGING:
432 			retval = BADAGE;
433 			break;
434 		case PAM_AUTHTOK_LOCK_BUSY:
435 			retval = FBUSY;
436 			break;
437 		case PAM_TRY_AGAIN:
438 			retval = FBUSY;
439 			break;
440 		case PAM_AUTHTOK_ERR:
441 		case PAM_AUTHTOK_RECOVERY_ERR:
442 		default:
443 			retval = NOPERM;
444 			break;
445 		}
446 
447 		(void) passwd_exit(retval);
448 		/* NOT REACHED */
449 	} else {		/* changing attributes */
450 		switch (flag) {
451 		case EFLAG:		/* changing user password attributes */
452 			input = userinput(usrname, &repository, ATTR_SHELL);
453 			if (input)
454 				attrlist_add(&attributes, ATTR_SHELL, input);
455 			else
456 				(void) printf(gettext(MSG_SHELL_UNCHANGED));
457 			break;
458 		case GFLAG:
459 			input = userinput(usrname, &repository, ATTR_GECOS);
460 			if (input)
461 				attrlist_add(&attributes, ATTR_GECOS, input);
462 			else
463 				(void) printf(gettext(MSG_GECOS_UNCHANGED));
464 			break;
465 		case HFLAG:
466 			input = userinput(usrname, &repository, ATTR_HOMEDIR);
467 			if (input)
468 				attrlist_add(&attributes, ATTR_HOMEDIR, input);
469 			else
470 				(void) printf(gettext(MSG_DIR_UNCHANGED));
471 			break;
472 		}
473 
474 		if (attributes != NULL) {
475 			retval = __set_authtoken_attr(usrname,
476 			    pamh->ps_item[PAM_AUTHTOK].pi_addr,
477 			    &repository, attributes, &updated_reps);
478 			switch (retval) {
479 			case PWU_SUCCESS:
480 				for (i = 1; i <= REP_LAST; i <<= 1) {
481 					if ((updated_reps & i) == 0)
482 						continue;
483 					(void) printf(gettext(MSG_SUCCESS),
484 					    prognamep, usrname);
485 				}
486 				retval = SUCCESS;
487 				break;
488 			case PWU_AGING_DISABLED:
489 				retval = BADAGE;
490 				break;
491 			default:
492 				retval = NOPERM;
493 				break;
494 			}
495 		} else {
496 			retval = SUCCESS; /* nothing to change won't fail */
497 		}
498 		(void) passwd_exit(retval);
499 	}
500 	/* NOTREACHED */
501 	return (0);
502 }
503 
504 /*
505  * Get a line of input from the user.
506  *
507  * If the line is empty, or the input equals 'oldval', NULL is returned.
508  * therwise, a malloced string containing the input (minus the trailing
509  * newline) is returned.
510  */
511 char *
512 getresponse(char *oldval)
513 {
514 	char    resp[MAX_INPUT_LEN];
515 	char    *retval = NULL;
516 	int	resplen;
517 
518 	(void) fgets(resp, sizeof (resp) - 1, stdin);
519 	resplen = strlen(resp) - 1;
520 	if (resp[resplen] == '\n')
521 		resp[resplen] = '\0';
522 	if (*resp != '\0' && strcmp(resp, oldval) != 0)
523 		retval = strdup(resp);
524 	return (retval);
525 }
526 
527 /*
528  * char *userinput(item)
529  *
530  * user conversation function. The old value of attribute "item" is
531  * displayed while the user is asked to provide a new value.
532  *
533  * returns a malloc()-ed string if the user actualy provided input
534  * or NULL if the user simply hit return or the input equals the old
535  * value (not changed).
536  */
537 char *
538 userinput(char *name, pwu_repository_t *rep, attrtype type)
539 {
540 	attrlist oldattr;
541 	char *oldval;			/* shorthand for oldattr.data.val_s */
542 	char *valid;			/* points to valid shells */
543 	char *response;
544 	char *cp;
545 
546 	oldattr.type = type;
547 	oldattr.next = NULL;
548 
549 	if (__get_authtoken_attr(name, rep, &oldattr) != PWU_SUCCESS)
550 		passwd_exit(FMERR);
551 
552 	oldval = oldattr.data.val_s;
553 
554 	if (type == ATTR_SHELL) {
555 		/* No current shell: set DEFSHL as default choice */
556 		if (*oldval == '\0') {
557 			free(oldval);
558 			oldval = strdup(DEFSHL);
559 		}
560 
561 		if (ckuid() != SUCCESS) {
562 			/* User must currently have a valid shell */
563 			setusershell();
564 			valid = getusershell();
565 			while (valid && strcmp(valid, oldval) != 0)
566 				valid = getusershell();
567 			endusershell();
568 
569 			if (valid == NULL) {
570 				(void) fprintf(stderr, gettext(MSG_RS), oldval);
571 				free(oldval);
572 				return (NULL);
573 			}
574 		}
575 		(void) printf(gettext(MSG_OLDSHELL), oldval);
576 		(void) printf(gettext(MSG_NEWSHELL));
577 		(void) fflush(stdout);
578 
579 		response = getresponse(oldval);
580 		free(oldval); /* We don't need the old value anymore */
581 
582 		if (response == NULL || *response == '\0')
583 			return (NULL);
584 
585 		/* Make sure new shell is listed */
586 		setusershell();
587 		valid = getusershell();
588 		while (valid) {
589 			char *cp;
590 
591 			/* Allow user to give shell without path */
592 			if (*response == '/') {
593 				cp = valid;
594 			} else {
595 				if ((cp = strrchr(valid, '/')) == NULL)
596 					cp = valid;
597 				else
598 					cp++;
599 			}
600 			if (strcmp(cp, response) == 0) {
601 				if (*response != '/') {
602 					/* take shell name including path */
603 					free(response);
604 					response = strdup(valid);
605 				}
606 				break;
607 			}
608 			valid = getusershell();
609 		}
610 		endusershell();
611 
612 		if (valid == NULL) {    /* No valid shell matches */
613 			(void) fprintf(stderr, gettext(MSG_UNACCEPT), response);
614 			return (NULL);
615 		}
616 
617 		if (access(response, X_OK) < 0)
618 			(void) fprintf(stderr, gettext(MSG_UNAVAIL), response);
619 		return (response);
620 		/* NOT REACHED */
621 	}
622 	/*
623 	 * if type == SHELL, we have returned by now. Only GECOS and
624 	 * HOMEDIR get to this point.
625 	 */
626 	(void) printf(gettext(MSG_INPUTHDR));
627 
628 	/*
629 	 * PRE: oldval points to malloced string with Old Value
630 	 * INV: oldval remains unchanged
631 	 * POST:response points to valid string or NULL.
632 	 */
633 	for (;;) {
634 		if (type == ATTR_GECOS)
635 			(void) printf(gettext(MSG_NAME), oldval);
636 		else if (type == ATTR_HOMEDIR)
637 			(void) printf(gettext(MSG_HOMEDIR), oldval);
638 
639 		response = getresponse(oldval);
640 
641 		if (response && strcmp(response, "none") == 0)
642 			*response = '\0';
643 
644 		/* No-change or empty string are OK */
645 		if (response == NULL || *response == '\0')
646 			break;
647 
648 		/* Check for illegal characters */
649 		if (strchr(response, ':')) {
650 			(void) fprintf(stderr, "%s", gettext(MSG_COLON));
651 			free(response);
652 		} else if (strlen(response) > MAX_INPUT_LEN - 1) {
653 			(void) fprintf(stderr, gettext(MSG_MAXLEN),
654 			    MAX_INPUT_LEN);
655 			free(response);
656 		} else {
657 			/* don't allow control characters */
658 			for (cp = response; *cp >= 040; cp++)
659 				;
660 			if (*cp != '\0') {
661 				(void) fprintf(stderr, gettext(MSG_CONTROL));
662 				free(response);
663 			} else
664 				break;	/* response is a valid string */
665 		}
666 		/*
667 		 * We only get here if the input was invalid.
668 		 * In that case, we again ask the user for input.
669 		 */
670 	}
671 	free(oldval);
672 	return (response);
673 }
674 /*
675  * ckarg():
676  *	This function parses and verifies the
677  * 	arguments.  It takes three parameters:
678  * 	argc => # of arguments
679  * 	argv => pointer to an argument
680  * 	attrlist => pointer to list of password attributes
681  */
682 
683 static int
684 ckarg(int argc, char **argv, attrlist **attributes)
685 {
686 	extern char	*optarg;
687 	char		*char_p;
688 	int	opt;
689 	int	flag;
690 
691 	flag = 0;
692 
693 	while ((opt = getopt(argc, argv, "r:aldefghsux:n:w:N")) != EOF) {
694 		switch (opt) {
695 
696 		case 'r': /* Repository Specified */
697 			/* repository: this option should be specified first */
698 
699 			if (repository.type != NULL) {
700 				(void) fprintf(stderr, gettext(
701 			"Repository is already defined or specified.\n"));
702 				rusage();
703 				retval = BADSYN;
704 				return (FAIL);
705 			}
706 			if (strcmp(optarg, "nis") == 0) {
707 				repository.type = optarg;
708 			} else if (strcmp(optarg, "ldap") == 0) {
709 				repository.type = optarg;
710 			} else if (strcmp(optarg, "files") == 0) {
711 				repository.type = optarg;
712 			} else {
713 				(void) fprintf(stderr,
714 				    gettext("invalid repository: %s\n"),
715 				    optarg);
716 				rusage();
717 				retval = BADSYN;
718 				return (FAIL);
719 			}
720 			break;
721 
722 		case 'd': /* Delete Auth Token */
723 			/* if no repository the default for -d is files */
724 			if (repository.type == NULL)
725 				repository = __REPFILES;
726 
727 			/*
728 			 * Delete the password - only privileged processes
729 			 * can execute this for FILES or LDAP
730 			 */
731 			if (IS_FILES(repository) == FALSE &&
732 			    IS_LDAP(repository) == FALSE) {
733 				(void) fprintf(stderr, gettext(
734 				    "-d only applies to files "
735 				    "or ldap repository\n"));
736 				rusage();	/* exit */
737 				retval = BADSYN;
738 				return (FAIL);
739 			}
740 
741 			if (ckuid() != SUCCESS) {
742 				retval = NOPERM;
743 				return (FAIL);
744 			}
745 			if (flag & (LFLAG|SAFLAG|DFLAG|XFLAG|UFLAG)) {
746 				rusage();
747 				retval = BADOPT;
748 				return (FAIL);
749 			}
750 			flag |= DFLAG;
751 			attrlist_add(attributes, ATTR_PASSWD, NULL);
752 			break;
753 
754 		case 'N': /* set account to be "no login" */
755 
756 			/* if no repository the default for -N is files */
757 			if (repository.type == NULL)
758 				repository = __REPFILES;
759 
760 			if (IS_FILES(repository) == FALSE &&
761 			    IS_LDAP(repository) == FALSE) {
762 				(void) fprintf(stderr, gettext(
763 				    "-N only applies to files or ldap "
764 				    "repository\n"));
765 				rusage();	/* exit */
766 				retval = BADOPT;
767 				return (FAIL);
768 			}
769 
770 			/*
771 			 * Only privileged processes can execute this
772 			 * for FILES or LDAP
773 			 */
774 			if ((IS_FILES(repository) || IS_LDAP(repository)) &&
775 			    ((retval = ckuid()) != SUCCESS))
776 				return (FAIL);
777 			if (flag & (MUTEXFLAG|NONAGEFLAG)) {
778 				rusage();	/* exit */
779 				retval = BADOPT;
780 				return (FAIL);
781 			}
782 			flag |= XFLAG;
783 			attrlist_add(attributes, ATTR_NOLOGIN_ACCOUNT, NULL);
784 			break;
785 
786 		case 'l': /* lock the password */
787 
788 			/* if no repository the default for -l is files */
789 			if (repository.type == NULL)
790 				repository = __REPFILES;
791 
792 			if (IS_FILES(repository) == FALSE &&
793 			    IS_LDAP(repository) == FALSE) {
794 				(void) fprintf(stderr, gettext(
795 				    "-l only applies to files or ldap "
796 				    "repository\n"));
797 				rusage();	/* exit */
798 				retval = BADOPT;
799 				return (FAIL);
800 			}
801 
802 			/*
803 			 * Only privileged processes can execute this
804 			 * for FILES or LDAP
805 			 */
806 			if ((IS_FILES(repository) || IS_LDAP(repository)) &&
807 			    ((retval = ckuid()) != SUCCESS))
808 				return (FAIL);
809 			if (flag & (MUTEXFLAG|NONAGEFLAG)) {
810 				rusage();	/* exit */
811 				retval = BADOPT;
812 				return (FAIL);
813 			}
814 			flag |= LFLAG;
815 			attrlist_add(attributes, ATTR_LOCK_ACCOUNT, NULL);
816 			break;
817 
818 		case 'u': /* unlock the password */
819 
820 			/* if no repository the default for -u is files */
821 			if (repository.type == NULL)
822 				repository = __REPFILES;
823 
824 			if (IS_FILES(repository) == FALSE &&
825 			    IS_LDAP(repository) == FALSE) {
826 				(void) fprintf(stderr, gettext(
827 				    "-u only applies to files or ldap "
828 				    "repository\n"));
829 				rusage();	/* exit */
830 				retval = BADOPT;
831 				return (FAIL);
832 			}
833 
834 			/*
835 			 * Only privileged processes can execute this
836 			 * for FILES or LDAP
837 			 */
838 			if ((IS_FILES(repository) || IS_LDAP(repository)) &&
839 			    ((retval = ckuid()) != SUCCESS))
840 				return (FAIL);
841 			if (flag & (MUTEXFLAG|NONAGEFLAG)) {
842 				rusage();	/* exit */
843 				retval = BADOPT;
844 				return (FAIL);
845 			}
846 			flag |= UFLAG;
847 			attrlist_add(attributes, ATTR_UNLOCK_ACCOUNT, NULL);
848 			attrlist_add(attributes, ATTR_RST_FAILED_LOGINS, NULL);
849 			break;
850 
851 		case 'x': /* set the max date */
852 
853 			/* if no repository the default for -x is files */
854 			if (repository.type == NULL)
855 				repository = __REPFILES;
856 
857 			if (IS_FILES(repository) == FALSE &&
858 			    IS_LDAP(repository) == FALSE) {
859 				(void) fprintf(stderr, gettext(
860 				    "-x only applies to files or ldap "
861 				    "repository\n"));
862 				rusage();	/* exit */
863 				retval = BADSYN;
864 				return (FAIL);
865 			}
866 
867 			/*
868 			 * Only privileged process can execute this
869 			 * for FILES or LDAP
870 			 */
871 			if ((IS_FILES(repository) || IS_LDAP(repository)) &&
872 			    (ckuid() != SUCCESS)) {
873 				retval = NOPERM;
874 				return (FAIL);
875 			}
876 			if (flag & (SAFLAG|MFLAG|NONAGEFLAG)) {
877 				retval = BADOPT;
878 				return (FAIL);
879 			}
880 			flag |= MFLAG;
881 			if ((int)strlen(optarg)  <= 0 ||
882 			    (maxdate = strtol(optarg, &char_p, 10)) < -1 ||
883 			    *char_p != '\0') {
884 				(void) fprintf(stderr, "%s: %s -x\n",
885 				    prognamep, gettext(MSG_NV));
886 				retval = BADSYN;
887 				return (FAIL);
888 			}
889 			attrlist_add(attributes, ATTR_MAX, optarg);
890 			break;
891 
892 		case 'n': /* set the min date */
893 
894 			/* if no repository the default for -n is files */
895 			if (repository.type == NULL)
896 				repository = __REPFILES;
897 
898 			if (IS_FILES(repository) == FALSE &&
899 			    IS_LDAP(repository) == FALSE) {
900 				(void) fprintf(stderr, gettext(
901 				    "-n only applies to files or ldap "
902 				    "repository\n"));
903 				rusage();	/* exit */
904 				retval = BADSYN;
905 				return (FAIL);
906 			}
907 
908 			/*
909 			 * Only privileged process can execute this
910 			 * for FILES or LDAP
911 			 */
912 			if ((IS_FILES(repository) || IS_LDAP(repository)) &&
913 			    ((retval = ckuid()) != SUCCESS))
914 				return (FAIL);
915 			if (flag & (SAFLAG|NFLAG|NONAGEFLAG)) {
916 				retval = BADOPT;
917 				return (FAIL);
918 			}
919 			flag |= NFLAG;
920 			if ((int)strlen(optarg)  <= 0 ||
921 			    (strtol(optarg, &char_p, 10)) < 0 ||
922 			    *char_p != '\0') {
923 				(void) fprintf(stderr, "%s: %s -n\n",
924 				    prognamep, gettext(MSG_NV));
925 				retval = BADSYN;
926 				return (FAIL);
927 			}
928 			attrlist_add(attributes, ATTR_MIN, optarg);
929 			break;
930 
931 		case 'w': /* set the warning field */
932 
933 			/* if no repository the default for -w is files */
934 			if (repository.type == NULL)
935 				repository = __REPFILES;
936 
937 			if (IS_FILES(repository) == FALSE &&
938 			    IS_LDAP(repository) == FALSE) {
939 				(void) fprintf(stderr, gettext(
940 				    "-w only applies to files or ldap "
941 				    "repository\n"));
942 				rusage();	/* exit */
943 				retval = BADSYN;
944 				return (FAIL);
945 			}
946 
947 			/*
948 			 * Only privileged process can execute this
949 			 * for FILES or LDAP
950 			 */
951 			if ((IS_FILES(repository) || IS_LDAP(repository)) &&
952 			    (ckuid() != SUCCESS)) {
953 				retval = NOPERM;
954 				return (FAIL);
955 			}
956 			if (flag & (SAFLAG|WFLAG|NONAGEFLAG)) {
957 				retval = BADOPT;
958 				return (FAIL);
959 			}
960 			flag |= WFLAG;
961 			if ((int)strlen(optarg)  <= 0 ||
962 			    (strtol(optarg, &char_p, 10)) < 0 ||
963 			    *char_p != '\0') {
964 				(void) fprintf(stderr, "%s: %s -w\n",
965 				    prognamep, gettext(MSG_NV));
966 				retval = BADSYN;
967 				return (FAIL);
968 			}
969 			attrlist_add(attributes, ATTR_WARN, optarg);
970 			break;
971 
972 		case 's': /* display password attributes */
973 
974 			/* if no repository the default for -s is files */
975 			if (repository.type == NULL)
976 				repository = __REPFILES;
977 
978 
979 			/* display password attributes */
980 			if (IS_FILES(repository) == FALSE &&
981 			    IS_LDAP(repository) == FALSE) {
982 				(void) fprintf(stderr, gettext(
983 				    "-s only applies to files or ldap "
984 				    "repository\n"));
985 				rusage();	/* exit */
986 				retval = BADSYN;
987 				return (FAIL);
988 			}
989 
990 			/*
991 			 * Only privileged process can execute this
992 			 * for FILES or LDAP
993 			 */
994 			if ((IS_FILES(repository) || IS_LDAP(repository)) &&
995 			    ((retval = ckuid()) != SUCCESS))
996 				return (FAIL);
997 			if (flag && (flag != AFLAG)) {
998 				retval = BADOPT;
999 				return (FAIL);
1000 			}
1001 			flag |= SFLAG;
1002 			break;
1003 
1004 		case 'a': /* display password attributes */
1005 
1006 			/* if no repository the default for -a is files */
1007 			if (repository.type == NULL)
1008 				repository = __REPFILES;
1009 
1010 			if (IS_FILES(repository) == FALSE &&
1011 			    IS_LDAP(repository) == FALSE) {
1012 				(void) fprintf(stderr, gettext(
1013 				    "-a only applies to files or ldap "
1014 				    "repository\n"));
1015 				rusage();	/* exit */
1016 				retval = BADSYN;
1017 				return (FAIL);
1018 			}
1019 
1020 			/*
1021 			 * Only privileged process can execute this
1022 			 * for FILES or LDAP
1023 			 */
1024 			if ((IS_FILES(repository) || IS_LDAP(repository)) &&
1025 			    ((retval = ckuid()) != SUCCESS))
1026 				return (FAIL);
1027 			if (flag && (flag != SFLAG)) {
1028 				retval = BADOPT;
1029 				return (FAIL);
1030 			}
1031 			flag |= AFLAG;
1032 			break;
1033 
1034 		case 'f': /* expire password attributes	*/
1035 
1036 			/* if no repository the default for -f is files */
1037 			if (repository.type == NULL)
1038 				repository = __REPFILES;
1039 
1040 			if (IS_FILES(repository) == FALSE &&
1041 			    IS_LDAP(repository) == FALSE) {
1042 				(void) fprintf(stderr, gettext(
1043 				    "-f only applies to files or ldap "
1044 				    "repository\n"));
1045 				rusage();	/* exit */
1046 				retval = BADSYN;
1047 				return (FAIL);
1048 			}
1049 
1050 			/*
1051 			 * Only privileged process can execute this
1052 			 * for FILES or LDAP
1053 			 */
1054 			if ((IS_FILES(repository) || IS_LDAP(repository)) &&
1055 			    ((retval = ckuid()) != SUCCESS))
1056 				return (FAIL);
1057 			if (flag & (SAFLAG|FFLAG|NONAGEFLAG)) {
1058 				retval = BADOPT;
1059 				return (FAIL);
1060 			}
1061 			flag |= FFLAG;
1062 			attrlist_add(attributes, ATTR_EXPIRE_PASSWORD, NULL);
1063 			break;
1064 
1065 		case 'e': /* change login shell */
1066 
1067 			/* if no repository the default for -e is files */
1068 			if (repository.type == NULL)
1069 				repository = __REPFILES;
1070 
1071 			if (flag & (EFLAG|SAFLAG|AGEFLAG)) {
1072 				retval = BADOPT;
1073 				return (FAIL);
1074 			}
1075 			flag |= EFLAG;
1076 			break;
1077 
1078 		case 'g': /* change gecos information */
1079 
1080 			/* if no repository the default for -g is files */
1081 			if (repository.type == NULL)
1082 				repository = __REPFILES;
1083 
1084 			/*
1085 			 * Only privileged process can execute this
1086 			 * for FILES
1087 			 */
1088 			if (IS_FILES(repository) && (ckuid() != SUCCESS)) {
1089 				retval = NOPERM;
1090 				return (FAIL);
1091 			}
1092 			if (flag & (GFLAG|SAFLAG|AGEFLAG)) {
1093 				retval = BADOPT;
1094 				return (FAIL);
1095 			}
1096 			flag |= GFLAG;
1097 			break;
1098 
1099 		case 'h': /* change home dir */
1100 
1101 			/* if no repository the default for -h is files */
1102 			if (repository.type == NULL)
1103 				repository = __REPFILES;
1104 			/*
1105 			 * Only privileged process can execute this
1106 			 * for FILES
1107 			 */
1108 			if (IS_FILES(repository) && (ckuid() != SUCCESS)) {
1109 				retval = NOPERM;
1110 				return (FAIL);
1111 			}
1112 			if (IS_NIS(repository)) {
1113 				(void) fprintf(stderr, "%s\n",
1114 				    gettext(MSG_NIS_HOMEDIR));
1115 				retval = BADSYN;
1116 				return (FAIL);
1117 			}
1118 
1119 			if (flag & (HFLAG|SAFLAG|AGEFLAG)) {
1120 				retval = BADOPT;
1121 				return (FAIL);
1122 			}
1123 			flag |= HFLAG;
1124 			break;
1125 
1126 		case '?':
1127 			rusage();
1128 			retval = BADOPT;
1129 			return (FAIL);
1130 		}
1131 	}
1132 
1133 	argc -= optind;
1134 	if (argc > 1) {
1135 		rusage();
1136 		retval = BADSYN;
1137 		return (FAIL);
1138 	}
1139 
1140 	/* Make sure (EXPIRE comes after (MAX comes after MIN)) */
1141 	attrlist_reorder(attributes);
1142 
1143 	/* If no options are specified or only the show option */
1144 	/* is specified, return because no option error checking */
1145 	/* is needed */
1146 	if (!flag || (flag == SFLAG))
1147 		return (flag);
1148 
1149 	/* AFLAG must be used with SFLAG */
1150 	if (flag == AFLAG) {
1151 		rusage();
1152 		retval = BADSYN;
1153 		return (FAIL);
1154 	}
1155 
1156 	if (flag != SAFLAG && argc < 1) {
1157 		/*
1158 		 * user name is not specified (argc<1), it can't be
1159 		 * aging info update.
1160 		 */
1161 		if (!(flag & NONAGEFLAG)) {
1162 			rusage();
1163 			retval = BADSYN;
1164 			return (FAIL);
1165 		}
1166 	}
1167 
1168 	/* user name(s) may not be specified when SAFLAG is used. */
1169 	if (flag == SAFLAG && argc >= 1) {
1170 		rusage();
1171 		retval = BADSYN;
1172 		return (FAIL);
1173 	}
1174 
1175 	/*
1176 	 * If aging is being turned off (maxdate == -1), mindate may not
1177 	 * be specified.
1178 	 */
1179 	if ((maxdate == -1) && (flag & NFLAG)) {
1180 		(void) fprintf(stderr, "%s: %s -n\n",
1181 		    prognamep, gettext(MSG_NV));
1182 		retval = BADOPT;
1183 		return (FAIL);
1184 	}
1185 
1186 	return (flag);
1187 }
1188 
1189 /*
1190  *
1191  * ckuid():
1192  *	This function returns SUCCESS if the caller is root, else
1193  *	it returns NOPERM.
1194  *
1195  */
1196 
1197 static int
1198 ckuid(void)
1199 {
1200 	if (uid != 0) {
1201 		return (retval = NOPERM);
1202 	}
1203 	return (SUCCESS);
1204 }
1205 
1206 
1207 /*
1208  * get_attr()
1209  */
1210 int
1211 get_attr(char *username, pwu_repository_t *repository, attrlist **attributes)
1212 {
1213 	int res;
1214 
1215 	attrlist_add(attributes, ATTR_PASSWD, NULL);
1216 	attrlist_add(attributes, ATTR_LSTCHG, "0");
1217 	attrlist_add(attributes, ATTR_MIN, "0");
1218 	attrlist_add(attributes, ATTR_MAX, "0");
1219 	attrlist_add(attributes, ATTR_WARN, "0");
1220 
1221 	res = __get_authtoken_attr(username, repository, *attributes);
1222 
1223 	if (res == PWU_SUCCESS) {
1224 		retval = SUCCESS;
1225 		return (PWU_SUCCESS);
1226 	}
1227 
1228 	if (res == PWU_NOT_FOUND)
1229 		(void) fprintf(stderr, gettext(MSG_UNKNOWN), prognamep,
1230 		    username);
1231 
1232 	retval = NOPERM;
1233 	passwd_exit(retval);
1234 	/*NOTREACHED*/
1235 }
1236 
1237 /*
1238  * display_attr():
1239  * This function prints out the password attributes of a usr
1240  * onto standand output.
1241  */
1242 void
1243 display_attr(char *usrname, attrlist *attributes)
1244 {
1245 	char	*status = NULL;
1246 	char	*passwd;
1247 	long	lstchg;
1248 	int	min = 0, max = 0, warn = 0;
1249 
1250 	while (attributes) {
1251 		switch (attributes->type) {
1252 		case ATTR_PASSWD:
1253 			passwd = attributes->data.val_s;
1254 			if (passwd == NULL || *passwd == '\0')
1255 				status = "NP  ";
1256 			else if (strncmp(passwd, LOCKSTRING,
1257 			    sizeof (LOCKSTRING)-1) == 0)
1258 				status = "LK  ";
1259 			else if (strncmp(passwd, NOLOGINSTRING,
1260 			    sizeof (NOLOGINSTRING)-1) == 0)
1261 				status = "NL  ";
1262 			else if ((strlen(passwd) == 13 && passwd[0] != '$') ||
1263 			    passwd[0] == '$')
1264 				status = "PS  ";
1265 			else
1266 				status = "UN  ";
1267 			break;
1268 		case ATTR_LSTCHG:
1269 			lstchg = attributes->data.val_i * DAY;
1270 			break;
1271 		case ATTR_MIN:
1272 			min = attributes->data.val_i;
1273 			break;
1274 		case ATTR_MAX:
1275 			max = attributes->data.val_i;
1276 			break;
1277 		case ATTR_WARN:
1278 			warn = attributes->data.val_i;
1279 			break;
1280 		default:
1281 			break;
1282 		}
1283 		attributes = attributes->next;
1284 	}
1285 	(void) fprintf(stdout, "%-8s  ", usrname);
1286 
1287 	if (status)
1288 		(void) fprintf(stdout, "%s  ", status);
1289 
1290 	if (max != -1) {
1291 		if (lstchg == 0) {
1292 			(void) fprintf(stdout, "00/00/00  ");
1293 		} else {
1294 			struct tm *tmp;
1295 			tmp = gmtime(&lstchg);
1296 			(void) fprintf(stdout, "%.2d/%.2d/%.2d  ",
1297 			    tmp->tm_mon + 1,
1298 			    tmp->tm_mday,
1299 			    tmp->tm_year % 100);
1300 		}
1301 		(void) fprintf(stdout, (min >= 0) ? "%4d  " : "      ", min);
1302 		(void) fprintf(stdout, "%4d  ", max);
1303 		(void) fprintf(stdout, (warn > 0) ? "%4d  " : "      ", warn);
1304 	}
1305 	(void) fprintf(stdout, "\n");
1306 }
1307 
1308 void
1309 free_attr(attrlist *attributes)
1310 {
1311 	while (attributes) {
1312 		if (attributes->type == ATTR_PASSWD)
1313 			free(attributes->data.val_s);
1314 		attributes = attributes->next;
1315 	}
1316 }
1317 
1318 /*
1319  *
1320  * get_namelist_files():
1321  *	This function gets a list of user names on the system from
1322  *	the /etc/passwd file.
1323  *
1324  */
1325 int
1326 get_namelist_files(char ***namelist_p, int *num_user)
1327 {
1328 	FILE		*pwfp;
1329 	struct passwd	*pwd;
1330 	int		max_user;
1331 	int		nuser;
1332 	char	**nl;
1333 
1334 	nuser = 0;
1335 	errno = 0;
1336 	pwd = NULL;
1337 
1338 	if ((pwfp = fopen(PASSWD, "r")) == NULL)
1339 		return (NOPERM);
1340 
1341 	/*
1342 	 * find out the actual number of entries in the PASSWD file
1343 	 */
1344 	max_user = 1;			/* need one slot for terminator NULL */
1345 	while ((pwd = fgetpwent(pwfp)) != NULL)
1346 		max_user++;
1347 
1348 	/*
1349 	 *	reset the file stream pointer
1350 	 */
1351 	rewind(pwfp);
1352 
1353 	nl = (char **)calloc(max_user, (sizeof (char *)));
1354 	if (nl == NULL) {
1355 		(void) fclose(pwfp);
1356 		return (FMERR);
1357 	}
1358 
1359 	while ((pwd = fgetpwent(pwfp)) != NULL) {
1360 		if ((nl[nuser] = strdup(pwd->pw_name)) == NULL) {
1361 			(void) fclose(pwfp);
1362 			return (FMERR);
1363 		}
1364 		nuser++;
1365 	}
1366 
1367 	nl[nuser] = NULL;
1368 	*num_user = nuser;
1369 	*namelist_p = nl;
1370 	(void) fclose(pwfp);
1371 	return (SUCCESS);
1372 }
1373 
1374 /*
1375  * get_namelist_local
1376  *
1377  */
1378 
1379 /*
1380  * Our private version of the switch frontend for getspent.  We want
1381  * to search just the ldap sp file, so we want to bypass
1382  * normal nsswitch.conf based processing.  This implementation
1383  * compatible with version 2 of the name service switch.
1384  */
1385 #define	NSS_LDAP_ONLY		"ldap"
1386 
1387 extern int str2spwd(const char *, int, void *, char *, int);
1388 
1389 static DEFINE_NSS_DB_ROOT(db_root);
1390 static DEFINE_NSS_GETENT(context);
1391 
1392 static char *local_config;
1393 static void
1394 _lc_nss_initf_shadow(nss_db_params_t *p)
1395 {
1396 	p->name	= NSS_DBNAM_SHADOW;
1397 	p->config_name    = NSS_DBNAM_PASSWD;	/* Use config for "passwd" */
1398 	p->default_config = local_config;   	/* Use ldap only */
1399 	p->flags = NSS_USE_DEFAULT_CONFIG;
1400 }
1401 
1402 static void
1403 _lc_setspent(void)
1404 {
1405 	nss_setent(&db_root, _lc_nss_initf_shadow, &context);
1406 }
1407 
1408 static void
1409 _lc_endspent(void)
1410 {
1411 	nss_endent(&db_root, _lc_nss_initf_shadow, &context);
1412 	nss_delete(&db_root);
1413 }
1414 
1415 static struct spwd *
1416 _lc_getspent_r(struct spwd *result, char *buffer, int buflen)
1417 {
1418 	nss_XbyY_args_t arg;
1419 	char		*nam;
1420 
1421 	/* In getXXent_r(), protect the unsuspecting caller from +/- entries */
1422 
1423 	do {
1424 		NSS_XbyY_INIT(&arg, result, buffer, buflen, str2spwd);
1425 			/* No key to fill in */
1426 		(void) nss_getent(&db_root, _lc_nss_initf_shadow, &context,
1427 		    &arg);
1428 	} while (arg.returnval != 0 &&
1429 	    (nam = ((struct spwd *)arg.returnval)->sp_namp) != 0 &&
1430 	    (*nam == '+' || *nam == '-'));
1431 
1432 	return (struct spwd *)NSS_XbyY_FINI(&arg);
1433 }
1434 
1435 static nss_XbyY_buf_t *buffer;
1436 
1437 static struct spwd *
1438 _lc_getspent(void)
1439 {
1440 	nss_XbyY_buf_t	*b;
1441 
1442 	b = NSS_XbyY_ALLOC(&buffer, sizeof (struct spwd), NSS_BUFLEN_SHADOW);
1443 
1444 	return (b == 0 ? 0 : _lc_getspent_r(b->result, b->buffer, b->buflen));
1445 }
1446 
1447 int
1448 get_namelist_local(char ***namelist_p, int *num_user)
1449 {
1450 	int nuser = 0;
1451 	int alloced = 100;
1452 	char **nl;
1453 	struct spwd *p;
1454 
1455 
1456 	if ((nl = calloc(alloced, sizeof (*nl))) == NULL)
1457 		return (FMERR);
1458 
1459 	(void) _lc_setspent();
1460 	while ((p = _lc_getspent()) != NULL) {
1461 		if ((nl[nuser] = strdup(p->sp_namp)) == NULL) {
1462 			_lc_endspent();
1463 			return (FMERR);
1464 		}
1465 		if (++nuser == alloced) {
1466 			alloced += 100;
1467 			nl = realloc(nl, alloced * (sizeof (*nl)));
1468 			if (nl == NULL) {
1469 				_lc_endspent();
1470 				return (FMERR);
1471 			}
1472 		}
1473 	}
1474 	(void) _lc_endspent();
1475 	nl[nuser] = NULL;
1476 
1477 	*namelist_p = nl;
1478 	*num_user = nuser;		/* including NULL */
1479 
1480 	return (SUCCESS);
1481 }
1482 
1483 int
1484 get_namelist(pwu_repository_t repository, char ***namelist, int *num_user)
1485 {
1486 	if (IS_LDAP(repository)) {
1487 		local_config = NSS_LDAP_ONLY;
1488 		return (get_namelist_local(namelist, num_user));
1489 	} else if (IS_FILES(repository))
1490 		return (get_namelist_files(namelist, num_user));
1491 
1492 	rusage();
1493 	return (BADSYN);
1494 }
1495 
1496 /*
1497  *
1498  * passwd_exit():
1499  *	This function will call exit() with appropriate exit code
1500  *	according to the input "retcode" value.
1501  *	It also calls pam_end() to clean-up buffers before exit.
1502  *
1503  */
1504 
1505 void
1506 passwd_exit(int retcode)
1507 {
1508 
1509 	if (pamh)
1510 		(void) pam_end(pamh, pam_retval);
1511 
1512 	switch (retcode) {
1513 	case SUCCESS:
1514 			break;
1515 	case NOPERM:
1516 			(void) fprintf(stderr, "%s\n", gettext(MSG_NP));
1517 			break;
1518 	case BADOPT:
1519 			(void) fprintf(stderr, "%s\n", gettext(MSG_BS));
1520 			break;
1521 	case FMERR:
1522 			(void) fprintf(stderr, "%s\n", gettext(MSG_FE));
1523 			break;
1524 	case FATAL:
1525 			(void) fprintf(stderr, "%s\n", gettext(MSG_FF));
1526 			break;
1527 	case FBUSY:
1528 			(void) fprintf(stderr, "%s\n", gettext(MSG_FB));
1529 			break;
1530 	case BADSYN:
1531 			(void) fprintf(stderr, "%s\n", gettext(MSG_NV));
1532 			break;
1533 	case BADAGE:
1534 			(void) fprintf(stderr, "%s\n", gettext(MSG_AD));
1535 			break;
1536 	case NOMEM:
1537 			(void) fprintf(stderr, "%s\n", gettext(MSG_NM));
1538 			break;
1539 	default:
1540 			(void) fprintf(stderr, "%s\n", gettext(MSG_NP));
1541 			retcode = NOPERM;
1542 			break;
1543 	}
1544 	/* write password record */
1545 	if (event != NULL) {
1546 		struct passwd *pass;
1547 
1548 		if ((pass = getpwnam(usrname)) == NULL) {
1549 			/* unlikely to ever get here, but ... */
1550 			event->adt_passwd.username = usrname;
1551 		} else if (pass->pw_uid != uid) {
1552 			/* save target user */
1553 			event->adt_passwd.uid = pass->pw_uid;
1554 			event->adt_passwd.username = pass->pw_name;
1555 		}
1556 
1557 		if (adt_put_event(event,
1558 		    retcode == SUCCESS ? ADT_SUCCESS : ADT_FAILURE,
1559 		    retcode == SUCCESS ? ADT_SUCCESS : ADT_FAIL_PAM +
1560 		    pam_retval) != 0) {
1561 			adt_free_event(event);
1562 			(void) adt_end_session(ah);
1563 			perror("adt_put_event");
1564 			exit(retcode);
1565 		}
1566 		adt_free_event(event);
1567 	}
1568 	(void) adt_end_session(ah);
1569 	exit(retcode);
1570 }
1571 
1572 /*
1573  *
1574  * passwd_conv():
1575  *	This is the conv (conversation) function called from
1576  *	a PAM authentication module to print error messages
1577  *	or garner information from the user.
1578  *
1579  */
1580 
1581 static int
1582 passwd_conv(int num_msg, const struct pam_message **msg,
1583     struct pam_response **response, void *appdata_ptr)
1584 {
1585 	const struct pam_message *m;
1586 	struct pam_response *r;
1587 	char *temp;
1588 	int k, i;
1589 
1590 	if (num_msg <= 0)
1591 		return (PAM_CONV_ERR);
1592 
1593 	*response = (struct pam_response *)calloc(num_msg,
1594 	    sizeof (struct pam_response));
1595 	if (*response == NULL)
1596 		return (PAM_BUF_ERR);
1597 
1598 	k = num_msg;
1599 	m = *msg;
1600 	r = *response;
1601 	while (k--) {
1602 
1603 		switch (m->msg_style) {
1604 
1605 		case PAM_PROMPT_ECHO_OFF:
1606 			temp = getpassphrase(m->msg);
1607 			if (temp != NULL) {
1608 				r->resp = strdup(temp);
1609 				(void) memset(temp, 0, strlen(temp));
1610 				if (r->resp == NULL) {
1611 					/* free responses */
1612 					r = *response;
1613 					for (i = 0; i < num_msg; i++, r++) {
1614 						if (r->resp)
1615 							free(r->resp);
1616 					}
1617 					free(*response);
1618 					*response = NULL;
1619 					return (PAM_BUF_ERR);
1620 				}
1621 			}
1622 			m++;
1623 			r++;
1624 			break;
1625 
1626 		case PAM_PROMPT_ECHO_ON:
1627 			if (m->msg != NULL) {
1628 				(void) fputs(m->msg, stdout);
1629 			}
1630 			r->resp = (char *)calloc(PAM_MAX_RESP_SIZE,
1631 			    sizeof (char));
1632 			if (r->resp == NULL) {
1633 				/* free responses */
1634 				r = *response;
1635 				for (i = 0; i < num_msg; i++, r++) {
1636 					if (r->resp)
1637 						free(r->resp);
1638 				}
1639 				free(*response);
1640 				*response = NULL;
1641 				return (PAM_BUF_ERR);
1642 			}
1643 			if (fgets(r->resp, PAM_MAX_RESP_SIZE-1, stdin)) {
1644 				int len = strlen(r->resp);
1645 				if (r->resp[len-1] == '\n')
1646 					r->resp[len-1] = '\0';
1647 			}
1648 			m++;
1649 			r++;
1650 			break;
1651 
1652 		case PAM_ERROR_MSG:
1653 			if (m->msg != NULL) {
1654 				(void) fputs(m->msg, stderr);
1655 				(void) fputs("\n", stderr);
1656 			}
1657 			m++;
1658 			r++;
1659 			break;
1660 		case PAM_TEXT_INFO:
1661 			if (m->msg != NULL) {
1662 				(void) fputs(m->msg, stdout);
1663 				(void) fputs("\n", stdout);
1664 			}
1665 			m++;
1666 			r++;
1667 			break;
1668 
1669 		default:
1670 			break;
1671 		}
1672 	}
1673 	return (PAM_SUCCESS);
1674 }
1675 
1676 /*
1677  * 		Utilities Functions
1678  */
1679 
1680 /*
1681  * int attrlist_add(attrlist **l, attrtype type, char *val)
1682  * add an item, with type "type" and value "val", at the tail of list l.
1683  * This functions exits the application on OutOfMem error.
1684  */
1685 void
1686 attrlist_add(attrlist **l, attrtype type, char *val)
1687 {
1688 	attrlist **w;
1689 
1690 	/* tail insert */
1691 	for (w = l; *w != NULL; w = &(*w)->next)
1692 		;
1693 
1694 	if ((*w = malloc(sizeof (**w))) == NULL)
1695 		passwd_exit(NOMEM);
1696 
1697 	(*w)->type = type;
1698 	(*w)->next = NULL;
1699 
1700 	switch (type) {
1701 	case ATTR_MIN:
1702 	case ATTR_WARN:
1703 	case ATTR_MAX:
1704 		(*w)->data.val_i = atoi(val);
1705 		break;
1706 	default:
1707 		(*w)->data.val_s = val;
1708 		break;
1709 	}
1710 }
1711 
1712 /*
1713  * attrlist_reorder(attrlist **l)
1714  * Make sure that
1715  * 	- if EXPIRE and MAX or MIN is set, EXPIRE comes after MAX/MIN
1716  *	- if both MIN and MAX are set, MAX comes before MIN.
1717  */
1718 
1719 static void
1720 attrlist_reorder(attrlist **l)
1721 {
1722 	attrlist	**w;
1723 	attrlist	*exp = NULL;	/* ATTR_EXPIRE_PASSWORD, if found */
1724 	attrlist	*max = NULL;	/* ATTR_MAX, if found */
1725 
1726 	if (*l == NULL || (*l)->next == NULL)
1727 		return;		/* order of list with <= one item is ok */
1728 
1729 	/*
1730 	 * We simply walk the list, take off the EXPIRE and MAX items if
1731 	 * they appear, and put them (first MAX, them EXPIRE) at the end
1732 	 * of the list.
1733 	 */
1734 	w = l;
1735 	while (*w != NULL) {
1736 		if ((*w)->type == ATTR_EXPIRE_PASSWORD) {
1737 			exp = *w;
1738 			*w = (*w)->next;
1739 		} else if ((*w)->type == ATTR_MAX) {
1740 			max = *w;
1741 			*w = (*w)->next;
1742 		} else
1743 			w = &(*w)->next;
1744 	}
1745 
1746 	/* 'w' points to the address of the 'next' field of the last element */
1747 
1748 	if (max) {
1749 		*w = max;
1750 		w = &max->next;
1751 	}
1752 	if (exp) {
1753 		*w = exp;
1754 		w = &exp->next;
1755 	}
1756 	*w = NULL;
1757 }
1758 
1759 void
1760 rusage(void)
1761 {
1762 
1763 #define	MSG(a) (void) fprintf(stderr, gettext((a)));
1764 
1765 	MSG("usage:\n");
1766 	MSG("\tpasswd [-r files | -r nis | -r ldap] [name]\n");
1767 	MSG("\tpasswd [-r files] [-egh] [name]\n");
1768 	MSG("\tpasswd [-r files] -sa\n");
1769 	MSG("\tpasswd [-r files] -s [name]\n");
1770 	MSG("\tpasswd [-r files] [-d|-l|-N|-u] [-f] [-n min] [-w warn] "
1771 	    "[-x max] name\n");
1772 	MSG("\tpasswd -r nis [-eg] [name]\n");
1773 	MSG("\t\t[-x max] name\n");
1774 	MSG("\tpasswd -r ldap [-egh] [name]\n");
1775 	MSG("\tpasswd -r ldap -sa\n");
1776 	MSG("\tpasswd -r ldap -s [name]\n");
1777 	MSG("\tpasswd -r ldap [-l|-N|-u] [-f] [-n min] [-w warn] "
1778 	    "[-x max] name\n");
1779 #undef MSG
1780 }
1781