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