xref: /illumos-gate/usr/src/lib/pam_modules/dhkeys/dhkeys.c (revision 4de2612967d06c4fdbf524a62556a1e8118a006f)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 #include <stdlib.h>
30 #include <syslog.h>
31 #include <errno.h>
32 #include <string.h>
33 #include <rpc/rpc.h>
34 #include <unistd.h>
35 #include <assert.h>
36 #include <stdarg.h>
37 #include <sys/types.h>
38 #include <sys/wait.h>
39 #include <limits.h>
40 
41 #include <rpcsvc/nis.h>
42 #include <rpcsvc/nispasswd.h>
43 #include <rpcsvc/yppasswd.h>
44 #include <rpcsvc/ypclnt.h>
45 #include <rpc/key_prot.h>
46 #include <rpc/rpc.h>
47 #include <nfs/nfs.h>
48 #include <nfs/nfssys.h>
49 #include <nss_dbdefs.h>
50 #include <nsswitch.h>
51 #include <rpcsvc/nis_dhext.h>
52 
53 #include <security/pam_appl.h>
54 #include <security/pam_modules.h>
55 #include <security/pam_impl.h>
56 
57 #include <libintl.h>
58 
59 #include <sys/mman.h>
60 
61 #include <passwdutil.h>
62 
63 #include "key_call_uid.h"
64 
65 /* to keep track of codepath */
66 #define	CODEPATH_PAM_SM_AUTHENTICATE	0
67 #define	CODEPATH_PAM_SM_SETCRED		1
68 
69 #define	SUNW_OLDRPCPASS	"SUNW-OLD-RPC-PASSWORD"
70 
71 extern	int	_nfssys(int, void *);
72 
73 /*
74  * int msg(pamh, ...)
75  *
76  * display message to the user
77  */
78 /*PRINTFLIKE2*/
79 int
80 msg(pam_handle_t *pamh, char *fmt, ...)
81 {
82 	va_list	ap;
83 	char	messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
84 
85 	va_start(ap, fmt);
86 	(void) vsnprintf(messages[0], sizeof (messages[0]), fmt, ap);
87 	va_end(ap);
88 
89 	return (__pam_display_msg(pamh, PAM_ERROR_MSG, 1, messages, NULL));
90 }
91 
92 
93 /*
94  * Get the secret key for the given netname, key length, and algorithm
95  * type and send it to keyserv if the given pw decrypts it.  Update the
96  * following counter args as necessary: get_seckey_cnt, good_pw_cnt, and
97  * set_seckey_cnt.
98  *
99  * Returns 0 on malloc failure, else 1.
100  */
101 static int
102 get_and_set_seckey(
103 	pam_handle_t	*pamh,			/* in */
104 	const char	*netname,		/* in */
105 	keylen_t	keylen,			/* in */
106 	algtype_t	algtype,		/* in */
107 	const char	*pw,			/* in */
108 	uid_t		uid,			/* in */
109 	gid_t		gid,			/* in */
110 	int		*get_seckey_cnt,	/* out */
111 	int		*good_pw_cnt,		/* out */
112 	int		*set_seckey_cnt,	/* out */
113 	int		flags,			/* in */
114 	int		debug)			/* in */
115 {
116 	char	*skey;
117 	int	skeylen;
118 	char	messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
119 
120 	skeylen = BITS2NIBBLES(keylen) + 1;
121 
122 	if ((skey = malloc(skeylen)) == NULL) {
123 		return (0);
124 	}
125 
126 	if (getsecretkey_g(netname, keylen, algtype, skey, skeylen, pw)) {
127 		(*get_seckey_cnt)++;
128 
129 		if (skey[0]) {
130 			/* password does decrypt secret key */
131 			(*good_pw_cnt)++;
132 			if (key_setnet_g_uid(netname, skey, keylen, NULL, 0,
133 						algtype, uid, gid) >= 0) {
134 				(*set_seckey_cnt)++;
135 			} else {
136 				if (debug)
137 					syslog(LOG_DEBUG, "pam_dhkeys: "
138 						"get_and_set_seckey: could not "
139 						"set secret key for keytype "
140 						"%d-%d", keylen, algtype);
141 			}
142 		} else {
143 			if (pamh && !(flags & PAM_SILENT)) {
144 				(void) snprintf(messages[0],
145 				    sizeof (messages[0]),
146 				    dgettext(TEXT_DOMAIN,
147 					"Password does not "
148 					"decrypt secret key (type = %d-%d) "
149 					"for '%s'."), keylen, algtype, netname);
150 				(void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1,
151 							messages, NULL);
152 			}
153 		}
154 	} else {
155 		if (debug)
156 			syslog(LOG_DEBUG, "pam_dhkeys: get_and_set_seckey: "
157 				"could not get secret key for keytype %d-%d",
158 				keylen, algtype);
159 	}
160 
161 	free(skey);
162 
163 	return (1);
164 }
165 
166 /*
167  * int establish_key(pamh, flags, debug, netname)
168  *
169  * This routine established the Secure RPC Credentials for the
170  * user specified in PAM_USER, using the password in PAM_AUTHTOK.
171  *
172  * Because this routine is used for both pam_authenticate *and*
173  * pam_setcred, we have to be somewhat careful:
174  *
175  *      - if called from pam_sm_authenticate:
176  *		1. if we don't need creds (no NIS+ or not tight), we don't
177  *		   set them (they will be set by pam_sm_setcred()) and return
178  *		   PAM_IGNORE.
179  *              2. if we do need to set them (passwd == "*NP*"), we try to
180  *		   do so. Not having credentials in this case results in
181  *		   PAM_AUTH_ERR.
182  *
183  *	- if called from pam_sm_setcred:
184  *		If we are root (uid == 0), we do nothing and return PAM_IGNORE.
185  *		Otherwise, we try to establish the credentials.
186  *		Not having credentials in this case results in PAM_IGNORE.
187  *
188  *	For both modi, we return PAM_IGNORE if the creds are established.
189  *	If we fail, we return
190  *	   - PAM_AUTH_ERR if the password didn't decrypt the cred
191  *	   - PAM_SYSTEM_ERR if the cred's could not be stored.
192  *
193  * This routine returns the user's netname in "netname".
194  *
195  * All tools--but the PAM stack--currently use getpass() to obtain
196  * the user's secure RPC password. We must make sure we don't use more than
197  * the first des_block (eight) characters of whatever is handed down to us.
198  * Therefore, we use a local variable "short_pass" to hold those 8 char's.
199  */
200 int
201 establish_key(pam_handle_t *pamh, int flags, int codepath, int debug,
202 	char *netname)
203 {
204 	char	*user;
205 	char	*passwd;
206 	char	short_pass[sizeof (des_block)+1], *short_passp;
207 	int	result;
208 	uid_t	uid;
209 	gid_t	gid;
210 	int	err;
211 
212 	struct passwd pw;	/* Needed to obtain uid */
213 	char	*scratch;
214 	int	scratchlen;
215 
216 	int	need_cred;	/* is not having credentials set a failure? */
217 	char *repository_name = NULL;	/* which repository are we using */
218 	char *repository_pass = NULL;	/* user's password from that rep */
219 	pwu_repository_t *pwu_rep;
220 	struct pam_repository *auth_rep;
221 	attrlist attr_pw[2];
222 
223 	mechanism_t	**mechs;
224 	mechanism_t	**mpp;
225 	int	get_seckey_cnt = 0;
226 	int	set_seckey_cnt = 0;
227 	int	good_pw_cnt = 0;
228 	int	valid_mech_cnt = 0;
229 
230 	(void) pam_get_item(pamh, PAM_USER, (void **)&user);
231 
232 	if (user == NULL || *user == '\0') {
233 		if (debug)
234 			syslog(LOG_DEBUG, "pam_dhkeys: user NULL or empty");
235 		return (PAM_USER_UNKNOWN);
236 	}
237 
238 	(void) pam_get_item(pamh, PAM_AUTHTOK, (void **)&passwd);
239 
240 	scratchlen = sysconf(_SC_GETPW_R_SIZE_MAX);
241 	if ((scratch = malloc(scratchlen)) == NULL)
242 		return (PAM_BUF_ERR);
243 
244 	if (getpwnam_r(user, &pw, scratch, scratchlen) == NULL) {
245 		result = PAM_USER_UNKNOWN;
246 		goto out;
247 	}
248 
249 	uid = pw.pw_uid;
250 	gid = pw.pw_gid;
251 
252 	/*
253 	 * We don't set credentials when root logs in.
254 	 * We do, however, need to set the credentials if the NIS+ permissions
255 	 * require so. Thus, we only bail out if we're root and we're
256 	 * called from pam_setcred.
257 	 */
258 	if (uid == 0 && codepath == CODEPATH_PAM_SM_SETCRED) {
259 		result = PAM_IGNORE;
260 		goto out;
261 	}
262 
263 	/*
264 	 * Check to see if we REALLY need to set the credentials, i.e.
265 	 * whether not being able to do so is an error or whether we
266 	 * can ignore it.
267 	 * We need to get the password from the repository that we're
268 	 * currently authenticating against. IFF this password equals
269 	 * "*NP" *AND* we are authenticating against NIS+, we actually
270 	 * do need to set the credentials. In all other cases, we
271 	 * can forget about them.
272 	 */
273 	(void) pam_get_item(pamh, PAM_REPOSITORY, (void **)&auth_rep);
274 	if (auth_rep != NULL) {
275 		if ((pwu_rep = calloc(1, sizeof (*pwu_rep))) == NULL)
276 			return (PAM_BUF_ERR);
277 		pwu_rep->type = auth_rep->type;
278 		pwu_rep->scope = auth_rep->scope;
279 		pwu_rep->scope_len = auth_rep->scope_len;
280 	} else
281 		pwu_rep = PWU_DEFAULT_REP;
282 
283 	attr_pw[0].type = ATTR_PASSWD; attr_pw[0].next = &attr_pw[1];
284 	attr_pw[1].type = ATTR_REP_NAME; attr_pw[1].next = NULL;
285 	result = __get_authtoken_attr(user, pwu_rep, attr_pw);
286 
287 	if (pwu_rep != PWU_DEFAULT_REP)
288 		free(pwu_rep);
289 
290 	if (result == PWU_NOT_FOUND) {
291 		if (debug)
292 			syslog(LOG_DEBUG, "pam_dhkeys: user %s not found",
293 				user);
294 		result = PAM_USER_UNKNOWN;
295 		goto out;
296 	} else if (result != PWU_SUCCESS) {
297 		result = PAM_PERM_DENIED;
298 		goto out;
299 	}
300 
301 	repository_name = attr_pw[1].data.val_s;
302 	repository_pass = attr_pw[0].data.val_s;
303 
304 	need_cred = (strcmp(repository_name, "nisplus") == 0 &&
305 		strcmp(repository_pass, "*NP*") == 0);
306 
307 	if (codepath == CODEPATH_PAM_SM_AUTHENTICATE && need_cred == 0) {
308 		/*
309 		 * No need to set credentials right now.
310 		 * Will do so later through pam_sm_setcred()
311 		 */
312 		result = PAM_IGNORE;
313 		goto out;
314 	}
315 
316 	if (uid == 0)		/* "root", need to create a host-netname */
317 		err = host2netname(netname, NULL, NULL);
318 	else
319 		err = user2netname(netname, uid, NULL);
320 
321 	if (err != 1) {
322 		if (debug)
323 			syslog(LOG_DEBUG, "pam_dhkeys: user2netname failed");
324 		if (need_cred) {
325 			syslog(LOG_ALERT, "pam_dhkeys: user %s needs "
326 			    "Secure RPC Credentials to login.", user);
327 			result = PAM_SERVICE_ERR;
328 		} else
329 			result = PAM_SYSTEM_ERR;
330 		goto out;
331 	}
332 
333 	/* passwd can be NULL (no passwd or su as root) */
334 	if (passwd) {
335 		(void) strlcpy(short_pass, passwd, sizeof (short_pass));
336 		short_passp = short_pass;
337 	} else
338 		short_passp = NULL;
339 
340 	if (mechs = __nis_get_mechanisms(FALSE)) {
341 
342 		for (mpp = mechs; *mpp; mpp++) {
343 			mechanism_t *mp = *mpp;
344 
345 			if (AUTH_DES_COMPAT_CHK(mp))
346 				break;	/* fall through to AUTH_DES below */
347 
348 			if (!VALID_MECH_ENTRY(mp))
349 				continue;
350 
351 			if (debug)
352 				syslog(LOG_DEBUG, "pam_dhkeys: trying "
353 					"key type = %d-%d", mp->keylen,
354 					mp->algtype);
355 			valid_mech_cnt++;
356 			if (!get_and_set_seckey(pamh, netname, mp->keylen,
357 						mp->algtype, short_passp,
358 						uid, gid,
359 						&get_seckey_cnt, &good_pw_cnt,
360 						&set_seckey_cnt, flags,
361 						debug)) {
362 				result = PAM_BUF_ERR;
363 				goto out;
364 			}
365 		}
366 		__nis_release_mechanisms(mechs);
367 		/* fall through to AUTH_DES below */
368 	} else {
369 		/*
370 		 * No usable mechs found in NIS+ security cf thus
371 		 * fallback to AUTH_DES compat.
372 		 */
373 		if (debug)
374 			syslog(LOG_DEBUG, "pam_dhkeys: no valid mechs "
375 				"found. Trying AUTH_DES.");
376 	}
377 
378 	/*
379 	 * We always perform AUTH_DES for the benefit of non-NIS+
380 	 * services (e.g. NFS) that may depend on the classic des
381 	 * 192bit key being set.
382 	 */
383 	if (!get_and_set_seckey(pamh, netname, AUTH_DES_KEYLEN,
384 	    AUTH_DES_ALGTYPE, short_passp, uid, gid, &get_seckey_cnt,
385 	    &good_pw_cnt, &set_seckey_cnt, flags, debug)) {
386 		result = PAM_BUF_ERR;
387 		goto out;
388 	}
389 
390 	if (debug) {
391 		syslog(LOG_DEBUG, "pam_dhkeys: mech key totals:\n");
392 		syslog(LOG_DEBUG, "pam_dhkeys: %d valid mechanism(s)",
393 			valid_mech_cnt);
394 		syslog(LOG_DEBUG, "pam_dhkeys: %d secret key(s) retrieved",
395 			get_seckey_cnt);
396 		syslog(LOG_DEBUG, "pam_dhkeys: %d passwd decrypt successes",
397 			good_pw_cnt);
398 		syslog(LOG_DEBUG, "pam_dhkeys: %d secret key(s) set",
399 			set_seckey_cnt);
400 	}
401 
402 	if (get_seckey_cnt == 0) {		/* No credentials */
403 		result = need_cred ? PAM_AUTH_ERR : PAM_IGNORE;
404 		goto out;
405 	}
406 
407 	if (good_pw_cnt == 0) {			/* wrong password */
408 		result = PAM_AUTH_ERR;
409 		goto out;
410 	}
411 
412 	if (set_seckey_cnt == 0) {
413 		result = PAM_SYSTEM_ERR;
414 		goto out;
415 	}
416 
417 	result = PAM_IGNORE;
418 out:
419 	if (repository_name)
420 		free(repository_name);
421 	if (repository_pass)
422 		free(repository_pass);
423 
424 	free(scratch);
425 
426 	(void) memset(short_pass, '\0', sizeof (short_pass));
427 
428 	return (result);
429 }
430 
431 int
432 pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv)
433 {
434 	int	i;
435 	int	debug = 0;
436 	int	result;
437 	char	netname[MAXNETNAMELEN + 1];
438 
439 	for (i = 0; i < argc; i++) {
440 		if (strcmp(argv[i], "debug") == 0)
441 			debug = 1;
442 		else if (strcmp(argv[i], "nowarn") == 0)
443 			flags |= PAM_SILENT;
444 	}
445 
446 	result = establish_key(pamh, flags, CODEPATH_PAM_SM_AUTHENTICATE, debug,
447 				netname);
448 
449 	return (result);
450 }
451 
452 int
453 remove_key(pam_handle_t *pamh, int flags, int debug)
454 {
455 	int result;
456 	char *uname;
457 	attrlist attr_pw[2];
458 	struct pam_repository *auth_rep = NULL;
459 	pwu_repository_t *pwu_rep;
460 	uid_t uid;
461 	gid_t gid;
462 	struct nfs_revauth_args nra;
463 
464 	(void) pam_get_item(pamh, PAM_USER, (void **)&uname);
465 	if (uname == NULL || *uname == NULL) {
466 		if (debug)
467 			syslog(LOG_DEBUG,
468 			    "pam_dhkeys: user NULL or empty in remove_key()");
469 		return (PAM_USER_UNKNOWN);
470 	}
471 
472 	if (strcmp(uname, "root") == 0) {
473 		if ((flags & PAM_SILENT) == 0) {
474 			char msg[3][PAM_MAX_MSG_SIZE];
475 			(void) snprintf(msg[0], sizeof (msg[0]),
476 			    dgettext(TEXT_DOMAIN,
477 				"removing root credentials would"
478 				" break the rpc services that"));
479 			(void) snprintf(msg[1], sizeof (msg[1]),
480 			    dgettext(TEXT_DOMAIN,
481 				"use secure rpc on this host!"));
482 			(void) snprintf(msg[2], sizeof (msg[2]),
483 			    dgettext(TEXT_DOMAIN,
484 				"root may use keylogout -f to do"
485 				" this (at your own risk)!"));
486 			(void) __pam_display_msg(pamh, PAM_ERROR_MSG, 3,
487 			    msg, NULL);
488 		}
489 		return (PAM_PERM_DENIED);
490 	}
491 
492 	(void) pam_get_item(pamh, PAM_REPOSITORY, (void **)&auth_rep);
493 	if (auth_rep != NULL) {
494 		if ((pwu_rep = calloc(1, sizeof (*pwu_rep))) == NULL)
495 			return (PAM_BUF_ERR);
496 		pwu_rep->type = auth_rep->type;
497 		pwu_rep->scope = auth_rep->scope;
498 		pwu_rep->scope_len = auth_rep->scope_len;
499 	} else
500 		pwu_rep = PWU_DEFAULT_REP;
501 
502 	/* Retrieve user's uid/gid from the password repository */
503 	attr_pw[0].type = ATTR_UID; attr_pw[0].next = &attr_pw[1];
504 	attr_pw[1].type = ATTR_GID; attr_pw[1].next = NULL;
505 
506 	result = __get_authtoken_attr(uname, pwu_rep, attr_pw);
507 
508 	if (pwu_rep != PWU_DEFAULT_REP)
509 		free(pwu_rep);
510 
511 	if (result == PWU_NOT_FOUND)
512 		return (PAM_USER_UNKNOWN);
513 	if (result == PWU_DENIED)
514 		return (PAM_PERM_DENIED);
515 	if (result != PWU_SUCCESS)
516 		return (PAM_SYSTEM_ERR);
517 
518 	uid = (uid_t)attr_pw[0].data.val_i;
519 	gid = (gid_t)attr_pw[1].data.val_i;
520 
521 	(void) key_removesecret_g_uid(uid, gid);
522 
523 	/* Revoke NFS DES credentials */
524 	nra.authtype = AUTH_DES;
525 	nra.uid = uid;
526 
527 	if (_nfssys(NFS_REVAUTH, &nra) < 0) {
528 		if ((flags & PAM_SILENT) == 0) {
529 			(void) msg(pamh, dgettext(TEXT_DOMAIN,
530 				"Warning: NFS credentials not destroyed"));
531 		}
532 		return (PAM_AUTH_ERR);
533 	}
534 
535 	return (PAM_IGNORE);
536 }
537 
538 int
539 pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv)
540 {
541 	int	i;
542 	int	debug = 0;
543 	int	result;
544 	char	netname[MAXNETNAMELEN + 1];
545 
546 	for (i = 0; i < argc; i++) {
547 		if (strcmp(argv[i], "debug") == 0)
548 			debug = 1;
549 		else if (strcmp(argv[i], "nowarn") == 0)
550 			flags |= PAM_SILENT;
551 	}
552 
553 	/* Check for invalid flags */
554 	if (flags && (flags & PAM_ESTABLISH_CRED) == 0 &&
555 			(flags & PAM_REINITIALIZE_CRED) == 0 &&
556 			(flags & PAM_REFRESH_CRED) == 0 &&
557 			(flags & PAM_DELETE_CRED) == 0 &&
558 			(flags & PAM_SILENT) == 0) {
559 		syslog(LOG_ERR, "pam_dhkeys: pam_setcred: illegal flags %d",
560 				flags);
561 		return (PAM_SYSTEM_ERR);
562 	}
563 
564 
565 	if ((flags & PAM_REINITIALIZE_CRED) || (flags & PAM_REFRESH_CRED)) {
566 		/* doesn't apply to UNIX */
567 		if (debug)
568 			syslog(LOG_DEBUG, "pam_dhkeys: cred reinit/refresh "
569 			    "ignored\n");
570 		return (PAM_IGNORE);
571 	}
572 
573 	if (flags & PAM_DELETE_CRED) {
574 		if (debug)
575 			syslog(LOG_DEBUG, "pam_dhkeys: removing creds\n");
576 		result = remove_key(pamh, flags, debug);
577 	} else {
578 		result = establish_key(pamh, flags, CODEPATH_PAM_SM_SETCRED,
579 					debug, netname);
580 		/* Some diagnostics */
581 		if ((flags & PAM_SILENT) == 0) {
582 			if (result == PAM_AUTH_ERR)
583 				(void) msg(pamh, dgettext(TEXT_DOMAIN,
584 				    "Password does not decrypt any secret "
585 				    "keys for %s."), netname);
586 			else if (result == PAM_SYSTEM_ERR && netname[0])
587 				(void) msg(pamh, dgettext(TEXT_DOMAIN,
588 				    "Could not set secret key(s) for %s. "
589 				    "The key server may be down."), netname);
590 		}
591 
592 		/* Not having credentials set is not an error... */
593 		result = PAM_IGNORE;
594 	}
595 
596 	return (result);
597 }
598 
599 /*ARGSUSED*/
600 void
601 rpc_cleanup(pam_handle_t *pamh, void *data, int pam_status)
602 {
603 	if (data) {
604 		(void) memset(data, 0, strlen(data));
605 		free(data);
606 	}
607 }
608 
609 int
610 pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv)
611 {
612 	int i;
613 	int debug = 0;
614 	int res;
615 	pam_repository_t *pam_rep;
616 	pwu_repository_t *pwu_rep;
617 	char *oldpw;
618 	char *user;
619 	int tries;
620 	int oldpw_ok;
621 	char *oldrpcpw;
622 	char *oldrpcpass;
623 	char *data;
624 	/* password truncated at 8 chars, see comment at establish_key() */
625 	char short_pass[sizeof (des_block)+1], *short_passp;
626 
627 	for (i = 0; i < argc; i++)
628 		if (strcmp(argv[i], "debug") == 0)
629 			debug = 1;
630 
631 	if (debug)
632 		syslog(LOG_DEBUG, "pam_dhkeys: entered pam_sm_chauthtok()");
633 
634 	if ((flags & PAM_PRELIM_CHECK) == 0)
635 		return (PAM_IGNORE);
636 
637 	/*
638 	 * See if the old secure-rpc password has already been set
639 	 */
640 	res = pam_get_data(pamh, SUNW_OLDRPCPASS, (const void **)&oldrpcpass);
641 	if (res == PAM_SUCCESS) {
642 		if (debug)
643 			syslog(LOG_DEBUG,
644 			    "pam_dhkeys: OLDRPCPASS already set");
645 		return (PAM_IGNORE);
646 	}
647 
648 	(void) pam_get_item(pamh, PAM_REPOSITORY, (void **)&pam_rep);
649 
650 	(void) pam_get_item(pamh, PAM_USER, (void **)&user);
651 
652 	(void) pam_get_item(pamh, PAM_AUTHTOK, (void **)&oldpw);
653 
654 	if (user == NULL || *user == '\0') {
655 		if (debug)
656 			syslog(LOG_DEBUG, "pam_dhkeys: user NULL or empty");
657 		return (PAM_USER_UNKNOWN);
658 	}
659 
660 	/* oldpw can be NULL (eg. root changing someone's passwd) */
661 	if (oldpw) {
662 		(void) strlcpy(short_pass, oldpw, sizeof (short_pass));
663 		short_passp = short_pass;
664 	} else
665 		short_passp = NULL;
666 
667 	/*
668 	 * For NIS+ we need to check whether the old password equals
669 	 * the RPC password. If it doesn't, we won't be able to update
670 	 * the secure RPC credentials later on in the process.
671 	 */
672 
673 	if (pam_rep == NULL)
674 		pwu_rep = PWU_DEFAULT_REP;
675 	else {
676 		if ((pwu_rep = calloc(1, sizeof (*pwu_rep))) == NULL)
677 			return (PAM_BUF_ERR);
678 		pwu_rep->type = pam_rep->type;
679 		pwu_rep->scope = pam_rep->scope;
680 		pwu_rep->scope_len = pam_rep->scope_len;
681 	}
682 
683 	switch (__verify_rpc_passwd(user, short_passp, pwu_rep)) {
684 	case PWU_SUCCESS:
685 		/* oldpw matches RPC password, or no RPC password needed */
686 
687 		if (pwu_rep != PWU_DEFAULT_REP)
688 			free(pwu_rep);
689 
690 		if (short_passp) {
691 			if ((data = strdup(short_pass)) == NULL) {
692 				(void) memset(short_pass, '\0',
693 				    sizeof (short_pass));
694 				return (PAM_BUF_ERR);
695 			}
696 		} else
697 			data = NULL;
698 
699 		(void) pam_set_data(pamh, SUNW_OLDRPCPASS, data, rpc_cleanup);
700 		return (PAM_IGNORE);
701 
702 	case PWU_NOT_FOUND:
703 		if (pwu_rep != PWU_DEFAULT_REP)
704 			free(pwu_rep);
705 		(void) memset(short_pass, '\0', sizeof (short_pass));
706 		return (PAM_USER_UNKNOWN);
707 	case PWU_CRED_ERROR:
708 		/* The old password does not decrypt any credentials */
709 		break;
710 	default:
711 		if (pwu_rep != PWU_DEFAULT_REP)
712 			free(pwu_rep);
713 		(void) memset(short_pass, '\0', sizeof (short_pass));
714 		return (PAM_SYSTEM_ERR);
715 	}
716 
717 	/*
718 	 * We got here because the OLDAUTHTOK doesn't match the Secure RPC
719 	 * password. In compliance with the old behavior, we give the
720 	 * user two chances to get the password right. If that succeeds
721 	 * all is well; if it doesn't, we'll return an error.
722 	 */
723 
724 	(void) msg(pamh, dgettext(TEXT_DOMAIN,
725 		"This password differs from your secure RPC password."));
726 
727 	tries = 0;
728 	oldpw_ok = 0;
729 
730 	while (oldpw_ok == 0 && ++tries < 3) {
731 		if (tries > 1)
732 			(void) msg(pamh, dgettext(TEXT_DOMAIN,
733 				"This password does not decrypt your "
734 				"secure RPC password."));
735 		res = __pam_get_authtok(pamh, PAM_PROMPT, 0,
736 		    dgettext(TEXT_DOMAIN,
737 		    "Please enter your old Secure RPC password: "), &oldpw);
738 		if (res != PAM_SUCCESS) {
739 			if (pwu_rep != PWU_DEFAULT_REP)
740 				free(pwu_rep);
741 			return (res);
742 		}
743 		(void) strlcpy(short_pass, oldpw, sizeof (short_pass));
744 		(void) memset(oldpw, 0, strlen(oldpw));
745 		free(oldpw);
746 		oldpw = NULL;
747 		if (__verify_rpc_passwd(user, short_pass, pwu_rep) ==
748 		    PWU_SUCCESS)
749 			oldpw_ok = 1;
750 	}
751 
752 	if (pwu_rep != PWU_DEFAULT_REP)
753 		free(pwu_rep);
754 
755 	if (oldpw_ok == 0) {
756 		(void) memset(short_pass, '\0', sizeof (short_pass));
757 		return (PAM_AUTHTOK_ERR);
758 	}
759 
760 	/*
761 	 * Since the PAM framework only provides space for two different
762 	 * password (one old and one current), there is officially no
763 	 * place to put additional passwords (like our old rpc password).
764 	 * We have no choice but to stuff it in a data item, and hope it
765 	 * will be picked up by the password-update routines.
766 	 */
767 
768 	oldrpcpw = strdup(short_pass);
769 	(void) memset(short_pass, '\0', sizeof (short_pass));
770 
771 	if (oldrpcpw == NULL)
772 		return (PAM_BUF_ERR);
773 
774 	res = pam_set_data(pamh, SUNW_OLDRPCPASS, oldrpcpw, rpc_cleanup);
775 
776 	return (res);
777 }
778