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