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