xref: /illumos-gate/usr/src/lib/pam_modules/dhkeys/dhkeys.c (revision 573374d31f6a3da992a4aa77ca650864b49d7a76)
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 2007 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 established the Secure RPC Credentials for the
172  * user specified in PAM_USER, using the password in PAM_AUTHTOK.
173  *
174  * Because this routine is used for both pam_authenticate *and*
175  * pam_setcred, we have to be somewhat careful:
176  *
177  *      - if called from pam_sm_authenticate:
178  *		1. if we don't need creds (no NIS+ or not tight), we don't
179  *		   set them (they will be set by pam_sm_setcred()) and return
180  *		   PAM_IGNORE.
181  *              2. if we do need to set them (passwd == "*NP*"), we try to
182  *		   do so. Not having credentials in this case results in
183  *		   PAM_AUTH_ERR.
184  *
185  *	- if called from pam_sm_setcred:
186  *		If we are root (uid == 0), we do nothing and return PAM_IGNORE.
187  *		Otherwise, we try to establish the credentials.
188  *		Not having credentials in this case results in PAM_IGNORE.
189  *
190  *	For both modi, we return PAM_IGNORE if the creds are established.
191  *	If we fail, we return
192  *	   - PAM_AUTH_ERR if the password didn't decrypt the cred
193  *	   - PAM_SYSTEM_ERR if the cred's could not be stored.
194  *
195  * This routine returns the user's netname in "netname".
196  *
197  * All tools--but the PAM stack--currently use getpass() to obtain
198  * the user's secure RPC password. We must make sure we don't use more than
199  * the first des_block (eight) characters of whatever is handed down to us.
200  * Therefore, we use a local variable "short_pass" to hold those 8 char's.
201  */
202 static int
203 establish_key(pam_handle_t *pamh, int flags, int codepath, int debug,
204 	char *netname)
205 {
206 	char	*user;
207 	char	*passwd;
208 	char	short_pass[sizeof (des_block)+1], *short_passp;
209 	int	result;
210 	uid_t	uid;
211 	gid_t	gid;
212 	int	err;
213 
214 	struct passwd pw;	/* Needed to obtain uid */
215 	char	*scratch;
216 	int	scratchlen;
217 
218 	int	need_cred;	/* is not having credentials set a failure? */
219 	char *repository_name = NULL;	/* which repository are we using */
220 	char *repository_pass = NULL;	/* user's password from that rep */
221 	pwu_repository_t *pwu_rep;
222 	struct pam_repository *auth_rep;
223 	attrlist attr_pw[2];
224 
225 	mechanism_t	**mechs;
226 	mechanism_t	**mpp;
227 	int	get_seckey_cnt = 0;
228 	int	set_seckey_cnt = 0;
229 	int	good_pw_cnt = 0;
230 	int	valid_mech_cnt = 0;
231 
232 	(void) pam_get_item(pamh, PAM_USER, (void **)&user);
233 
234 	if (user == NULL || *user == '\0') {
235 		if (debug)
236 			syslog(LOG_DEBUG, "pam_dhkeys: user NULL or empty");
237 		return (PAM_USER_UNKNOWN);
238 	}
239 
240 	(void) pam_get_item(pamh, PAM_AUTHTOK, (void **)&passwd);
241 
242 	scratchlen = sysconf(_SC_GETPW_R_SIZE_MAX);
243 	if ((scratch = malloc(scratchlen)) == NULL)
244 		return (PAM_BUF_ERR);
245 
246 	if (getpwnam_r(user, &pw, scratch, scratchlen) == NULL) {
247 		result = PAM_USER_UNKNOWN;
248 		goto out;
249 	}
250 
251 	uid = pw.pw_uid;
252 	gid = pw.pw_gid;
253 
254 	/*
255 	 * We don't set credentials when root logs in.
256 	 * We do, however, need to set the credentials if the NIS+ permissions
257 	 * require so. Thus, we only bail out if we're root and we're
258 	 * called from pam_setcred.
259 	 */
260 	if (uid == 0 && codepath == CODEPATH_PAM_SM_SETCRED) {
261 		result = PAM_IGNORE;
262 		goto out;
263 	}
264 
265 	/*
266 	 * Check to see if we REALLY need to set the credentials, i.e.
267 	 * whether not being able to do so is an error or whether we
268 	 * can ignore it.
269 	 * We need to get the password from the repository that we're
270 	 * currently authenticating against. IFF this password equals
271 	 * "*NP" *AND* we are authenticating against NIS+, we actually
272 	 * do need to set the credentials. In all other cases, we
273 	 * can forget about them.
274 	 */
275 	(void) pam_get_item(pamh, PAM_REPOSITORY, (void **)&auth_rep);
276 	if (auth_rep != NULL) {
277 		if ((pwu_rep = calloc(1, sizeof (*pwu_rep))) == NULL)
278 			return (PAM_BUF_ERR);
279 		pwu_rep->type = auth_rep->type;
280 		pwu_rep->scope = auth_rep->scope;
281 		pwu_rep->scope_len = auth_rep->scope_len;
282 	} else
283 		pwu_rep = PWU_DEFAULT_REP;
284 
285 	attr_pw[0].type = ATTR_PASSWD; attr_pw[0].next = &attr_pw[1];
286 	attr_pw[1].type = ATTR_REP_NAME; attr_pw[1].next = NULL;
287 	result = __get_authtoken_attr(user, pwu_rep, attr_pw);
288 
289 	if (pwu_rep != PWU_DEFAULT_REP)
290 		free(pwu_rep);
291 
292 	if (result == PWU_NOT_FOUND) {
293 		if (debug)
294 			syslog(LOG_DEBUG, "pam_dhkeys: user %s not found",
295 			    user);
296 		result = PAM_USER_UNKNOWN;
297 		goto out;
298 	} else if (result != PWU_SUCCESS) {
299 		result = PAM_PERM_DENIED;
300 		goto out;
301 	}
302 
303 	repository_name = attr_pw[1].data.val_s;
304 	repository_pass = attr_pw[0].data.val_s;
305 
306 	need_cred = (strcmp(repository_name, "nisplus") == 0 &&
307 	    strcmp(repository_pass, "*NP*") == 0);
308 
309 	if (codepath == CODEPATH_PAM_SM_AUTHENTICATE && need_cred == 0) {
310 		/*
311 		 * No need to set credentials right now.
312 		 * Will do so later through pam_sm_setcred()
313 		 */
314 		result = PAM_IGNORE;
315 		goto out;
316 	}
317 
318 	if (uid == 0)		/* "root", need to create a host-netname */
319 		err = host2netname(netname, NULL, NULL);
320 	else
321 		err = user2netname(netname, uid, NULL);
322 
323 	if (err != 1) {
324 		if (debug)
325 			syslog(LOG_DEBUG, "pam_dhkeys: user2netname failed");
326 		if (need_cred) {
327 			syslog(LOG_ALERT, "pam_dhkeys: user %s needs "
328 			    "Secure RPC Credentials to login.", user);
329 			result = PAM_SERVICE_ERR;
330 		} else
331 			result = PAM_SYSTEM_ERR;
332 		goto out;
333 	}
334 
335 	/* passwd can be NULL (no passwd or su as root) */
336 	if (passwd) {
337 		(void) strlcpy(short_pass, passwd, sizeof (short_pass));
338 		short_passp = short_pass;
339 	} else
340 		short_passp = NULL;
341 
342 	if (mechs = __nis_get_mechanisms(FALSE)) {
343 
344 		for (mpp = mechs; *mpp; mpp++) {
345 			mechanism_t *mp = *mpp;
346 
347 			if (AUTH_DES_COMPAT_CHK(mp))
348 				break;	/* fall through to AUTH_DES below */
349 
350 			if (!VALID_MECH_ENTRY(mp))
351 				continue;
352 
353 			if (debug)
354 				syslog(LOG_DEBUG, "pam_dhkeys: trying "
355 				    "key type = %d-%d", mp->keylen,
356 				    mp->algtype);
357 			valid_mech_cnt++;
358 			if (!get_and_set_seckey(pamh, netname, mp->keylen,
359 			    mp->algtype, short_passp, uid, gid,
360 			    &get_seckey_cnt, &good_pw_cnt, &set_seckey_cnt,
361 			    flags, 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 
453 typedef struct argres {
454 	uid_t uid;
455 	int result;
456 } argres_t;
457 
458 /*
459  * Revoke NFS DES credentials.
460  * NFS may not be installed so we need to deal with SIGSYS
461  * when we call _nfssys(); we thus call _nfssys() in a seperate thread that
462  * is created specifically for this call. The thread specific signalmask
463  * is set to ignore SIGSYS. After the call to _nfssys(), the thread
464  * ceases to exist.
465  */
466 static void *
467 revoke_nfs_cred(void *ap)
468 {
469 	struct nfs_revauth_args nra;
470 	sigset_t isigset;
471 	argres_t *argres = (argres_t *)ap;
472 
473 	nra.authtype = AUTH_DES;
474 	nra.uid = argres->uid;
475 
476 	(void) sigemptyset(&isigset);
477 	(void) sigaddset(&isigset, SIGSYS);
478 
479 	if (pthread_sigmask(SIG_BLOCK, &isigset, NULL) == 0) {
480 		argres->result = _nfssys(NFS_REVAUTH, &nra);
481 		if (argres->result < 0 && errno == ENOSYS) {
482 			argres->result = 0;
483 		}
484 	} else {
485 		argres->result = -1;
486 	}
487 	return (NULL);
488 }
489 
490 static int
491 remove_key(pam_handle_t *pamh, int flags, int debug)
492 {
493 	int result;
494 	char *uname;
495 	attrlist attr_pw[2];
496 	struct pam_repository *auth_rep = NULL;
497 	pwu_repository_t *pwu_rep;
498 	uid_t uid;
499 	gid_t gid;
500 	argres_t argres;
501 	thread_t tid;
502 
503 	(void) pam_get_item(pamh, PAM_USER, (void **)&uname);
504 	if (uname == NULL || *uname == NULL) {
505 		if (debug)
506 			syslog(LOG_DEBUG,
507 			    "pam_dhkeys: user NULL or empty in remove_key()");
508 		return (PAM_USER_UNKNOWN);
509 	}
510 
511 	if (strcmp(uname, "root") == 0) {
512 		if ((flags & PAM_SILENT) == 0) {
513 			char msg[3][PAM_MAX_MSG_SIZE];
514 			(void) snprintf(msg[0], sizeof (msg[0]),
515 			    dgettext(TEXT_DOMAIN,
516 			    "removing root credentials would"
517 			    " break the rpc services that"));
518 			(void) snprintf(msg[1], sizeof (msg[1]),
519 			    dgettext(TEXT_DOMAIN,
520 			    "use secure rpc on this host!"));
521 			(void) snprintf(msg[2], sizeof (msg[2]),
522 			    dgettext(TEXT_DOMAIN,
523 			    "root may use keylogout -f to do"
524 			    " this (at your own risk)!"));
525 			(void) __pam_display_msg(pamh, PAM_ERROR_MSG, 3,
526 			    msg, NULL);
527 		}
528 		return (PAM_PERM_DENIED);
529 	}
530 
531 	(void) pam_get_item(pamh, PAM_REPOSITORY, (void **)&auth_rep);
532 	if (auth_rep != NULL) {
533 		if ((pwu_rep = calloc(1, sizeof (*pwu_rep))) == NULL)
534 			return (PAM_BUF_ERR);
535 		pwu_rep->type = auth_rep->type;
536 		pwu_rep->scope = auth_rep->scope;
537 		pwu_rep->scope_len = auth_rep->scope_len;
538 	} else
539 		pwu_rep = PWU_DEFAULT_REP;
540 
541 	/* Retrieve user's uid/gid from the password repository */
542 	attr_pw[0].type = ATTR_UID; attr_pw[0].next = &attr_pw[1];
543 	attr_pw[1].type = ATTR_GID; attr_pw[1].next = NULL;
544 
545 	result = __get_authtoken_attr(uname, pwu_rep, attr_pw);
546 
547 	if (pwu_rep != PWU_DEFAULT_REP)
548 		free(pwu_rep);
549 
550 	if (result == PWU_NOT_FOUND)
551 		return (PAM_USER_UNKNOWN);
552 	if (result == PWU_DENIED)
553 		return (PAM_PERM_DENIED);
554 	if (result != PWU_SUCCESS)
555 		return (PAM_SYSTEM_ERR);
556 
557 	uid = (uid_t)attr_pw[0].data.val_i;
558 	gid = (gid_t)attr_pw[1].data.val_i;
559 
560 	(void) key_removesecret_g_uid(uid, gid);
561 
562 	argres.uid = uid;
563 	argres.result = -1;
564 
565 	if (pthread_create(&tid, NULL, revoke_nfs_cred, (void *)&argres) == 0)
566 		(void) pthread_join(tid, NULL);
567 
568 	if (argres.result < 0) {
569 		if ((flags & PAM_SILENT) == 0) {
570 			(void) msg(pamh, dgettext(TEXT_DOMAIN,
571 			    "Warning: NFS credentials not destroyed"));
572 		}
573 		return (PAM_AUTH_ERR);
574 	}
575 
576 	return (PAM_IGNORE);
577 }
578 
579 int
580 pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv)
581 {
582 	int	i;
583 	int	debug = 0;
584 	int	result;
585 	char	netname[MAXNETNAMELEN + 1];
586 
587 	for (i = 0; i < argc; i++) {
588 		if (strcmp(argv[i], "debug") == 0)
589 			debug = 1;
590 		else if (strcmp(argv[i], "nowarn") == 0)
591 			flags |= PAM_SILENT;
592 	}
593 
594 	/* Check for invalid flags */
595 	if (flags && (flags & PAM_ESTABLISH_CRED) == 0 &&
596 	    (flags & PAM_REINITIALIZE_CRED) == 0 &&
597 	    (flags & PAM_REFRESH_CRED) == 0 &&
598 	    (flags & PAM_DELETE_CRED) == 0 &&
599 	    (flags & PAM_SILENT) == 0) {
600 		syslog(LOG_ERR, "pam_dhkeys: pam_setcred: illegal flags %d",
601 		    flags);
602 		return (PAM_SYSTEM_ERR);
603 	}
604 
605 
606 	if ((flags & PAM_REINITIALIZE_CRED) || (flags & PAM_REFRESH_CRED)) {
607 		/* doesn't apply to UNIX */
608 		if (debug)
609 			syslog(LOG_DEBUG, "pam_dhkeys: cred reinit/refresh "
610 			    "ignored\n");
611 		return (PAM_IGNORE);
612 	}
613 
614 	if (flags & PAM_DELETE_CRED) {
615 		if (debug)
616 			syslog(LOG_DEBUG, "pam_dhkeys: removing creds\n");
617 		result = remove_key(pamh, flags, debug);
618 	} else {
619 		result = establish_key(pamh, flags, CODEPATH_PAM_SM_SETCRED,
620 		    debug, netname);
621 		/* Some diagnostics */
622 		if ((flags & PAM_SILENT) == 0) {
623 			if (result == PAM_AUTH_ERR)
624 				(void) msg(pamh, dgettext(TEXT_DOMAIN,
625 				    "Password does not decrypt any secret "
626 				    "keys for %s."), netname);
627 			else if (result == PAM_SYSTEM_ERR && netname[0])
628 				(void) msg(pamh, dgettext(TEXT_DOMAIN,
629 				    "Could not set secret key(s) for %s. "
630 				    "The key server may be down."), netname);
631 		}
632 
633 		/* Not having credentials set is not an error... */
634 		result = PAM_IGNORE;
635 	}
636 
637 	return (result);
638 }
639 
640 /*ARGSUSED*/
641 void
642 rpc_cleanup(pam_handle_t *pamh, void *data, int pam_status)
643 {
644 	if (data) {
645 		(void) memset(data, 0, strlen(data));
646 		free(data);
647 	}
648 }
649 
650 int
651 pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv)
652 {
653 	int i;
654 	int debug = 0;
655 	int res;
656 	pam_repository_t *pam_rep;
657 	pwu_repository_t *pwu_rep;
658 	char *oldpw;
659 	char *user;
660 	int tries;
661 	int oldpw_ok;
662 	char *oldrpcpw;
663 	char *oldrpcpass;
664 	char *data;
665 	/* password truncated at 8 chars, see comment at establish_key() */
666 	char short_pass[sizeof (des_block)+1], *short_passp;
667 
668 	for (i = 0; i < argc; i++)
669 		if (strcmp(argv[i], "debug") == 0)
670 			debug = 1;
671 
672 	if (debug)
673 		syslog(LOG_DEBUG, "pam_dhkeys: entered pam_sm_chauthtok()");
674 
675 	if ((flags & PAM_PRELIM_CHECK) == 0)
676 		return (PAM_IGNORE);
677 
678 	/*
679 	 * See if the old secure-rpc password has already been set
680 	 */
681 	res = pam_get_data(pamh, SUNW_OLDRPCPASS, (const void **)&oldrpcpass);
682 	if (res == PAM_SUCCESS) {
683 		if (debug)
684 			syslog(LOG_DEBUG,
685 			    "pam_dhkeys: OLDRPCPASS already set");
686 		return (PAM_IGNORE);
687 	}
688 
689 	(void) pam_get_item(pamh, PAM_REPOSITORY, (void **)&pam_rep);
690 
691 	(void) pam_get_item(pamh, PAM_USER, (void **)&user);
692 
693 	(void) pam_get_item(pamh, PAM_AUTHTOK, (void **)&oldpw);
694 
695 	if (user == NULL || *user == '\0') {
696 		if (debug)
697 			syslog(LOG_DEBUG, "pam_dhkeys: user NULL or empty");
698 		return (PAM_USER_UNKNOWN);
699 	}
700 
701 	/* oldpw can be NULL (eg. root changing someone's passwd) */
702 	if (oldpw) {
703 		(void) strlcpy(short_pass, oldpw, sizeof (short_pass));
704 		short_passp = short_pass;
705 	} else
706 		short_passp = NULL;
707 
708 	/*
709 	 * For NIS+ we need to check whether the old password equals
710 	 * the RPC password. If it doesn't, we won't be able to update
711 	 * the secure RPC credentials later on in the process.
712 	 */
713 
714 	if (pam_rep == NULL)
715 		pwu_rep = PWU_DEFAULT_REP;
716 	else {
717 		if ((pwu_rep = calloc(1, sizeof (*pwu_rep))) == NULL)
718 			return (PAM_BUF_ERR);
719 		pwu_rep->type = pam_rep->type;
720 		pwu_rep->scope = pam_rep->scope;
721 		pwu_rep->scope_len = pam_rep->scope_len;
722 	}
723 
724 	switch (__verify_rpc_passwd(user, short_passp, pwu_rep)) {
725 	case PWU_SUCCESS:
726 		/* oldpw matches RPC password, or no RPC password needed */
727 
728 		if (pwu_rep != PWU_DEFAULT_REP)
729 			free(pwu_rep);
730 
731 		if (short_passp) {
732 			if ((data = strdup(short_pass)) == NULL) {
733 				(void) memset(short_pass, '\0',
734 				    sizeof (short_pass));
735 				return (PAM_BUF_ERR);
736 			}
737 		} else
738 			data = NULL;
739 
740 		(void) pam_set_data(pamh, SUNW_OLDRPCPASS, data, rpc_cleanup);
741 		return (PAM_IGNORE);
742 
743 	case PWU_NOT_FOUND:
744 		if (pwu_rep != PWU_DEFAULT_REP)
745 			free(pwu_rep);
746 		(void) memset(short_pass, '\0', sizeof (short_pass));
747 		return (PAM_USER_UNKNOWN);
748 	case PWU_BAD_CREDPASS:
749 		/* The old password does not decrypt any credentials */
750 		break;
751 	case PWU_CRED_ERROR:
752 		/*
753 		 * Indicates that the user's credentials could not be
754 		 * retrieved or removed.  This could occur when a NIS+
755 		 * user is in transition to another account authority.
756 		 */
757 		if (pwu_rep != PWU_DEFAULT_REP)
758 			free(pwu_rep);
759 		(void) memset(short_pass, '\0', sizeof (short_pass));
760 		return (PAM_AUTHTOK_ERR);
761 	default:
762 		if (pwu_rep != PWU_DEFAULT_REP)
763 			free(pwu_rep);
764 		(void) memset(short_pass, '\0', sizeof (short_pass));
765 		return (PAM_SYSTEM_ERR);
766 	}
767 
768 	/*
769 	 * We got here because the OLDAUTHTOK doesn't match the Secure RPC
770 	 * password. In compliance with the old behavior, we give the
771 	 * user two chances to get the password right. If that succeeds
772 	 * all is well; if it doesn't, we'll return an error.
773 	 */
774 
775 	(void) msg(pamh, dgettext(TEXT_DOMAIN,
776 	    "This password differs from your secure RPC password."));
777 
778 	tries = 0;
779 	oldpw_ok = 0;
780 
781 	while (oldpw_ok == 0 && ++tries < 3) {
782 		if (tries > 1)
783 			(void) msg(pamh, dgettext(TEXT_DOMAIN,
784 			    "This password does not decrypt your "
785 			    "secure RPC password."));
786 		res = __pam_get_authtok(pamh, PAM_PROMPT, 0,
787 		    dgettext(TEXT_DOMAIN,
788 		    "Please enter your old Secure RPC password: "), &oldpw);
789 		if (res != PAM_SUCCESS) {
790 			if (pwu_rep != PWU_DEFAULT_REP)
791 				free(pwu_rep);
792 			return (res);
793 		}
794 		(void) strlcpy(short_pass, oldpw, sizeof (short_pass));
795 		(void) memset(oldpw, 0, strlen(oldpw));
796 		free(oldpw);
797 		oldpw = NULL;
798 		if (__verify_rpc_passwd(user, short_pass, pwu_rep) ==
799 		    PWU_SUCCESS)
800 			oldpw_ok = 1;
801 	}
802 
803 	if (pwu_rep != PWU_DEFAULT_REP)
804 		free(pwu_rep);
805 
806 	if (oldpw_ok == 0) {
807 		(void) memset(short_pass, '\0', sizeof (short_pass));
808 		return (PAM_AUTHTOK_ERR);
809 	}
810 
811 	/*
812 	 * Since the PAM framework only provides space for two different
813 	 * password (one old and one current), there is officially no
814 	 * place to put additional passwords (like our old rpc password).
815 	 * We have no choice but to stuff it in a data item, and hope it
816 	 * will be picked up by the password-update routines.
817 	 */
818 
819 	oldrpcpw = strdup(short_pass);
820 	(void) memset(short_pass, '\0', sizeof (short_pass));
821 
822 	if (oldrpcpw == NULL)
823 		return (PAM_BUF_ERR);
824 
825 	res = pam_set_data(pamh, SUNW_OLDRPCPASS, oldrpcpw, rpc_cleanup);
826 
827 	return (res);
828 }
829