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 2004 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 <kadm5/admin.h>
30 #include <krb5.h>
31 
32 #include <security/pam_appl.h>
33 #include <security/pam_modules.h>
34 #include <security/pam_impl.h>
35 #include <syslog.h>
36 #include <string.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <sys/types.h>
40 #include <pwd.h>
41 #include <libintl.h>
42 #include <netdb.h>
43 #include "utils.h"
44 #include "krb5_repository.h"
45 
46 #define	PAMTXD		"SUNW_OST_SYSOSPAM"
47 #define	MISC_EXIT_STATUS 6
48 #define	DONT_DISP_POLICY	0
49 #define	DISP_POLICY		1
50 
51 extern int attempt_krb5_auth(void *, krb5_module_data_t *, char *, char **,
52 			boolean_t, boolean_t);
53 extern int krb5_verifypw(pam_handle_t *, char *, char *, boolean_t, int);
54 
55 static char *get_passwd(pam_handle_t *, char *);
56 static void display_msg(pam_handle_t *, int, char *);
57 static void display_msgs(pam_handle_t *, int, int,
58 		char msgs[][PAM_MAX_MSG_SIZE]);
59 static int krb5_changepw(pam_handle_t *, char *, char *, char *, int);
60 
61 /*
62  * set_ccname()
63  *
64  * set KRB5CCNAME shell var
65  */
66 static void
67 set_ccname(
68 	pam_handle_t *pamh,
69 	krb5_module_data_t *kmd,
70 	int login_result,
71 	int debug)
72 {
73 	int result;
74 
75 	if (debug)
76 		syslog(LOG_DEBUG,
77 		    "PAM-KRB5 (password): password: finalize"
78 		    " ccname env, login_result =%d, env ='%s'",
79 		    login_result, kmd->env ? kmd->env : "<null>");
80 
81 	if (kmd->env) {
82 
83 		if (login_result == PAM_SUCCESS) {
84 				/*
85 				 * Put ccname into the pamh so that login
86 				 * apps can pick this up when they run
87 				 * pam_getenvlist().
88 				 */
89 			if ((result = pam_putenv(pamh, kmd->env))
90 			    != PAM_SUCCESS) {
91 				/* should not happen but... */
92 				syslog(LOG_ERR,
93 				    dgettext(TEXT_DOMAIN,
94 					    "PAM-KRB5 (password):"
95 					    " pam_putenv failed: result: %d"),
96 				    result);
97 				goto cleanupccname;
98 			}
99 		} else {
100 		cleanupccname:
101 				/* for lack of a Solaris unputenv() */
102 			krb5_unsetenv(KRB5_ENV_CCNAME);
103 			free(kmd->env);
104 			kmd->env = NULL;
105 		}
106 	}
107 }
108 
109 /*
110  * get_set_creds()
111  *
112  * do a krb5 login to get and set krb5 creds (needed after a pw change
113  * on pw expire on login)
114  */
115 static void
116 get_set_creds(
117 	pam_handle_t *pamh,
118 	krb5_module_data_t *kmd,
119 	char *user,
120 	char *newpass,
121 	int debug)
122 {
123 	int login_result;
124 
125 	if (!kmd || kmd->age_status != PAM_NEW_AUTHTOK_REQD)
126 		return;
127 
128 	/*
129 	 * if pw has expired, get/set krb5 creds ala auth mod
130 	 *
131 	 * pwchange verified user sufficiently, so don't request strict
132 	 * tgt verification (will cause rcache perm issues possibly anyways)
133 	 */
134 	login_result = attempt_krb5_auth(pamh, kmd, user,
135 					&newpass, 0, 0);
136 	if (debug)
137 		syslog(LOG_DEBUG,
138 		    "PAM-KRB5 (password): get_set_creds: login_result= %d",
139 		    login_result);
140 	/*
141 	 * the krb5 login should not fail, but if so,
142 	 * warn the user they have to kinit(1)
143 	 */
144 	if (login_result != PAM_SUCCESS) {
145 		display_msg(pamh, PAM_TEXT_INFO,
146 			    dgettext(TEXT_DOMAIN,
147 				    "Warning: "
148 				    "Could not cache Kerberos"
149 				    " credentials, please run "
150 				    "kinit(1) or re-login\n"));
151 	}
152 	set_ccname(pamh, kmd, login_result, debug);
153 }
154 /*
155  * This is the PAM Kerberos Password Change module
156  *
157  */
158 
159 int
160 pam_sm_chauthtok(
161 	pam_handle_t		*pamh,
162 	int			flags,
163 	int			argc,
164 	const char		**argv)
165 {
166 
167 	char			*user;
168 	int			err, result = PAM_AUTH_ERR;
169 	char			*newpass = NULL, *vnewpass = NULL;
170 	char			*oldpass = NULL;
171 	int			i;
172 	int			debug = 0;
173 	uid_t			pw_uid;
174 	krb5_module_data_t	*kmd = NULL;
175 	char			*pam_service;
176 	int			promptforold = 0;
177 	int			promptfornew = 0;
178 	pam_repository_t	*rep_data = NULL;
179 
180 	for (i = 0; i < argc; i++) {
181 		if (strcmp(argv[i], "debug") == 0)
182 			debug = 1;
183 		else
184 			syslog(LOG_ERR,
185 			    dgettext(TEXT_DOMAIN,
186 				    "PAM-KRB5 (password): illegal option %s"),
187 			    argv[i]);
188 	}
189 
190 	if (debug)
191 		syslog(LOG_DEBUG,
192 		    "PAM-KRB5 (password): start: flags = %x",
193 		    flags);
194 
195 	err = pam_get_item(pamh, PAM_REPOSITORY, (void **)&rep_data);
196 	if (rep_data != NULL) {
197 		if (strcmp(rep_data->type, KRB5_REPOSITORY_NAME) != 0) {
198 			if (debug)
199 				syslog(LOG_DEBUG, "PAM-KRB5 (auth): wrong"
200 					"repository found (%s), returning "
201 					"PAM_IGNORE", rep_data->type);
202 			return (PAM_IGNORE);
203 		}
204 	}
205 
206 	if (flags & PAM_PRELIM_CHECK) {
207 		/* Nothing to do here */
208 		if (debug)
209 			syslog(LOG_DEBUG,
210 			    "PAM-KRB5 (password): prelim check");
211 		return (PAM_IGNORE);
212 	}
213 
214 	/* make sure PAM framework is telling us to update passwords */
215 	if (!(flags & PAM_UPDATE_AUTHTOK)) {
216 		syslog(LOG_ERR, dgettext(TEXT_DOMAIN,
217 			"PAM-KRB5 (password): bad flags: %d"),
218 			flags);
219 		return (PAM_SYSTEM_ERR);
220 	}
221 
222 
223 	if ((err = pam_get_data(pamh, KRB5_DATA, (const void **)&kmd))
224 	    != PAM_SUCCESS) {
225 		if (debug)
226 			syslog(LOG_DEBUG,
227 			    "PAM-KRB5 (password): get mod data failed %d",
228 			    err);
229 		kmd = NULL;
230 	}
231 
232 	if (flags & PAM_CHANGE_EXPIRED_AUTHTOK) {
233 		/* let's make sure we know the krb5 pw has expired */
234 
235 		if (debug)
236 			syslog(LOG_DEBUG,
237 			    "PAM-KRB5 (password): kmd age status %d",
238 			    kmd ? kmd->age_status : -99);
239 
240 		if (!kmd || kmd->age_status != PAM_NEW_AUTHTOK_REQD)
241 			return (PAM_IGNORE);
242 	}
243 
244 	err = pam_get_item(pamh, PAM_SERVICE, (void **)&pam_service);
245 	if (err != PAM_SUCCESS) {
246 		syslog(LOG_ERR,
247 		    "PAM-KRB5 (password): error getting SERVICE");
248 		return (PAM_SYSTEM_ERR);
249 	}
250 
251 	err = pam_get_item(pamh, PAM_USER, (void **)&user);
252 	if (err != PAM_SUCCESS) {
253 		syslog(LOG_ERR,
254 		    "PAM-KRB5 (password): error getting USER");
255 		return (PAM_SYSTEM_ERR);
256 	}
257 
258 	if (user == NULL || user == '\0') {
259 		syslog(LOG_ERR,
260 		    "PAM-KRB5 (password): username is empty");
261 		return (PAM_SYSTEM_ERR);
262 	}
263 
264 	if (!get_pw_uid(user, &pw_uid)) {
265 		syslog(LOG_ERR,
266 		    "PAM-KRB5 (password): can't get uid for %s",
267 		    user);
268 		return (PAM_AUTHTOK_ERR);
269 	}
270 
271 	/*
272 	 * if root key exists in the keytab, it's a random key so no
273 	 * need to prompt for pw and we just return IGNORE
274 	 */
275 	if ((strcmp(user, ROOT_UNAME) == 0) &&
276 	    key_in_keytab(user, debug)) {
277 		if (debug)
278 			syslog(LOG_DEBUG,
279 			    "PAM-KRB5 (password): "
280 			    "key for '%s' in keytab, returning IGNORE", user);
281 		result = PAM_IGNORE;
282 		goto out;
283 	}
284 
285 	if ((err = pam_get_item(pamh, PAM_AUTHTOK,
286 				(void **) &newpass)) < 0)
287 		return (err);
288 
289 	if ((err = pam_get_item(pamh, PAM_OLDAUTHTOK,
290 				(void **) &oldpass)) < 0)
291 		return (err);
292 
293 	if (!newpass && !oldpass) {
294 		promptforold = 1;
295 		promptfornew = 1;
296 	} else {
297 		/*
298 		 * OLDAUTHTOK not set, we're probably the first password
299 		 * module but the AUTHTOK is probably set from an auth mod
300 		 */
301 		if (newpass && !oldpass) {
302 			oldpass = newpass;
303 			newpass = NULL;
304 			promptfornew = 1;
305 		}
306 
307 		result = krb5_verifypw(pamh, user, oldpass,
308 				    DONT_DISP_POLICY, debug);
309 		if (debug)
310 			syslog(LOG_DEBUG,
311 			    "PAM-KRB5 (password): verifypw first %d",
312 			    result);
313 		/*
314 		 * If this fails and is not bad passwd, then it might
315 		 * be a non-rpcsec_gss KDC so drop thru.
316 		 *
317 		 * (note in S9 change pw should work on non-rpcsec_gss KDCs
318 		 *  such as MIT & MS)
319 		 */
320 		if (result != 0)
321 			promptforold = 1;
322 	}
323 
324 	if (promptforold) {
325 
326 		oldpass = get_passwd(pamh,
327 				    dgettext(TEXT_DOMAIN,
328 					    "Old Kerberos password: "));
329 
330 		if (oldpass == NULL || oldpass[0] == '\0') {
331 			/* Need a password to proceed */
332 			display_msg(pamh, PAM_ERROR_MSG,
333 				    dgettext(TEXT_DOMAIN,
334 					    "Need the old password"
335 					    " to proceed \n"));
336 			free(oldpass);
337 			return (PAM_AUTHTOK_ERR);
338 		}
339 
340 		result = krb5_verifypw(pamh, user, oldpass,
341 				    DISP_POLICY, debug);
342 		if (debug)
343 			syslog(LOG_DEBUG,
344 			    "PAM-KRB5 (password): verifypw prforold %d",
345 			    result);
346 		/*
347 		 * If it's a bad password, we are done.
348 		 * Else, continue and try the pwch with oldpass.
349 		 */
350 		if (result == 2) {
351 			display_msg(pamh, PAM_ERROR_MSG,
352 				    dgettext(TEXT_DOMAIN,
353 					    "Old Kerberos"
354 					    " password incorrect\n"));
355 			(void) memset(oldpass, 0, strlen(oldpass));
356 			free(oldpass);
357 			return (PAM_AUTHTOK_ERR);
358 		}
359 	}
360 
361 	if (promptfornew) {
362 		newpass = get_passwd(pamh, dgettext(TEXT_DOMAIN,
363 			"New Kerberos password: "));
364 
365 		if (newpass == NULL || newpass[0] == '\0') {
366 			/* Need a password to proceed */
367 			display_msg(pamh, PAM_ERROR_MSG,
368 			    dgettext(TEXT_DOMAIN,
369 			    "Need a password to proceed \n"));
370 			result = PAM_AUTHTOK_ERR;
371 			goto out;
372 		}
373 
374 		vnewpass = get_passwd(pamh,
375 				dgettext(TEXT_DOMAIN,
376 			"Re-enter new Kerberos password: "));
377 
378 		if (vnewpass == NULL || vnewpass[0] == '\0') {
379 			/* Need a password to proceed */
380 			display_msg(pamh, PAM_ERROR_MSG,
381 			    dgettext(TEXT_DOMAIN,
382 				"Need a password to proceed \n"));
383 			result = PAM_AUTHTOK_ERR;
384 			goto out;
385 		}
386 
387 		if (strcmp(newpass, vnewpass)) {
388 			display_msg(pamh, PAM_ERROR_MSG,
389 			    dgettext(TEXT_DOMAIN,
390 				"Passwords do not match \n"));
391 			result = PAM_AUTHTOK_ERR;
392 			goto out;
393 		}
394 	}
395 
396 	result = krb5_changepw(pamh, user, oldpass, newpass, debug);
397 	if (result == PAM_SUCCESS) {
398 		display_msg(pamh, PAM_TEXT_INFO,
399 			    dgettext(TEXT_DOMAIN,
400 				    "Kerberos password "
401 				    "successfully changed\n"));
402 
403 		get_set_creds(pamh, kmd, user, newpass, debug);
404 
405 		(void) pam_set_item(pamh, PAM_AUTHTOK, newpass);
406 		(void) pam_set_item(pamh, PAM_OLDAUTHTOK, oldpass);
407 	}
408 
409 out:
410 	if (promptforold && oldpass) {
411 		(void) memset(oldpass, 0, strlen(oldpass));
412 		free(oldpass);
413 	}
414 	if (newpass) {
415 		(void) memset(newpass, 0, strlen(newpass));
416 		free(newpass);
417 	}
418 
419 	if (vnewpass) {
420 		(void) memset(vnewpass, 0, strlen(vnewpass));
421 		free(vnewpass);
422 	}
423 
424 	if (debug)
425 		syslog(LOG_DEBUG,
426 		    "PAM-KRB5 (password): out: returns %d",
427 		    result);
428 
429 	return (result);
430 }
431 
432 
433 int
434 pam_sm_get_authtokattr(
435 	/*ARGSUSED*/
436 	pam_handle_t		*pamh,
437 	char			***ga_getattr,
438 	int			repository,
439 	const char		*nisdomain,
440 	int			argc,
441 	const char		**argv)
442 {
443 	return (PAM_SUCCESS);
444 }
445 
446 int
447 pam_sm_set_authtokattr(
448 	/*ARGSUSED*/
449 	pam_handle_t		*pamh,
450 	const char 		**pam_setattr,
451 	int			repository,
452 	const char		*nisdomain,
453 	int			argc,
454 	const char		**argv)
455 {
456 	return (PAM_SUCCESS);
457 }
458 
459 int
460 krb5_verifypw(
461 	pam_handle_t *pamh,
462 	char 	*princ_str,
463 	char	*old_password,
464 	boolean_t disp_flag,
465 	int debug)
466 {
467 	kadm5_ret_t		code;
468 	krb5_principal 		princ = 0;
469 	char 			admin_realm[1024];
470 	char			kprinc[2*MAXHOSTNAMELEN];
471 	char			*cpw_service;
472 	kadm5_principal_ent_rec principal_entry;
473 	kadm5_policy_ent_rec	 policy_entry;
474 	void 			*server_handle;
475 	krb5_context		context;
476 	kadm5_config_params	params;
477 #define	MSG_ROWS		5
478 	char			msgs[MSG_ROWS][PAM_MAX_MSG_SIZE];
479 
480 	(void) memset((char *)&params, 0, sizeof (params));
481 	(void) memset(&principal_entry, 0, sizeof (principal_entry));
482 	(void) memset(&policy_entry, 0, sizeof (policy_entry));
483 
484 	if (code = krb5_init_context(&context)) {
485 		return (6);
486 	}
487 
488 	if ((code = get_kmd_kuser(context, (const char *)princ_str, kprinc,
489 		2*MAXHOSTNAMELEN)) != 0) {
490 		return (code);
491 	}
492 
493 	/* Need to get a krb5_principal struct */
494 
495 	code = krb5_parse_name(context, kprinc, &princ);
496 
497 	if (code != 0) {
498 		return (MISC_EXIT_STATUS);
499 	}
500 
501 	if (strlen(old_password) == 0) {
502 		krb5_free_principal(context, princ);
503 		return (5);
504 	}
505 
506 	(void) strlcpy(admin_realm,
507 		    krb5_princ_realm(context, princ)->data,
508 		    sizeof (admin_realm));
509 
510 	params.mask |= KADM5_CONFIG_REALM;
511 	params.realm = admin_realm;
512 
513 
514 	if (kadm5_get_cpw_host_srv_name(context, admin_realm, &cpw_service)) {
515 		syslog(LOG_ERR,
516 		    dgettext(TEXT_DOMAIN,
517 			"PAM-KRB5 (password): unable to get host based "
518 			"service name for realm %s\n"),
519 			admin_realm);
520 		return (3);
521 	}
522 
523 	code = kadm5_init_with_password(kprinc, old_password, cpw_service,
524 					&params, KADM5_STRUCT_VERSION,
525 					KADM5_API_VERSION_2, &server_handle);
526 	if (code != 0) {
527 		if (debug)
528 			syslog(LOG_DEBUG,
529 			    "PAM-KRB5: krb5_verifypw: init_with_pw"
530 			    " failed: (%s)", error_message(code));
531 		krb5_free_principal(context, princ);
532 		return ((code == KADM5_BAD_PASSWORD) ? 2 : 3);
533 	}
534 
535 	if (disp_flag &&
536 	    _kadm5_get_kpasswd_protocol(server_handle) == KRB5_CHGPWD_RPCSEC) {
537 		/*
538 		 * Note: copy of this exists in login
539 		 * (kverify.c/get_verified_in_tkt).
540 		 */
541 
542 		code = kadm5_get_principal(server_handle, princ,
543 						&principal_entry,
544 						KADM5_PRINCIPAL_NORMAL_MASK);
545 		if (code != 0) {
546 			krb5_free_principal(context, princ);
547 			(void) kadm5_destroy(server_handle);
548 			return ((code == KADM5_UNK_PRINC) ? 1 :
549 				MISC_EXIT_STATUS);
550 		}
551 
552 		if ((principal_entry.aux_attributes & KADM5_POLICY) != 0) {
553 			code = kadm5_get_policy(server_handle,
554 						principal_entry.policy,
555 						&policy_entry);
556 			if (code != 0) {
557 				/*
558 				 * doesn't matter which error comes back,
559 				 * there's no nice recovery or need to
560 				 * differentiate to the user
561 				 */
562 				(void) kadm5_free_principal_ent(server_handle,
563 							&principal_entry);
564 				krb5_free_principal(context, princ);
565 				(void) kadm5_destroy(server_handle);
566 				return (MISC_EXIT_STATUS);
567 			}
568 
569 			(void) snprintf(msgs[0], PAM_MAX_MSG_SIZE,
570 				dgettext(TEXT_DOMAIN, "POLICY_EXPLANATION:"));
571 			(void) snprintf(msgs[1], PAM_MAX_MSG_SIZE,
572 				dgettext(TEXT_DOMAIN,
573 					"Principal string is %s"), princ_str);
574 			(void) snprintf(msgs[2], PAM_MAX_MSG_SIZE,
575 				dgettext(TEXT_DOMAIN, "Policy Name is  %s"),
576 				principal_entry.policy);
577 			(void) snprintf(msgs[3], PAM_MAX_MSG_SIZE,
578 				dgettext(TEXT_DOMAIN,
579 					"Minimum password length is %d"),
580 					policy_entry.pw_min_length);
581 			(void) snprintf(msgs[4], PAM_MAX_MSG_SIZE,
582 				dgettext(TEXT_DOMAIN,
583 					"Minimum password classes is %d"),
584 					policy_entry.pw_min_classes);
585 			display_msgs(pamh, PAM_TEXT_INFO, MSG_ROWS, msgs);
586 
587 			if (code = kadm5_free_principal_ent(server_handle,
588 							    &principal_entry)) {
589 				(void) kadm5_free_policy_ent(server_handle,
590 							&policy_entry);
591 				krb5_free_principal(context, princ);
592 				(void) kadm5_destroy(server_handle);
593 				return (MISC_EXIT_STATUS);
594 			}
595 			if (code = kadm5_free_policy_ent(server_handle,
596 							&policy_entry)) {
597 				krb5_free_principal(context, princ);
598 
599 				(void) kadm5_destroy(server_handle);
600 				return (MISC_EXIT_STATUS);
601 			}
602 		} else {
603 			/*
604 			 * kpasswd *COULD* output something here to encourage
605 			 * the choice of good passwords, in the absence of
606 			 * an enforced policy.
607 			 */
608 			if (code = kadm5_free_principal_ent(server_handle,
609 							    &principal_entry)) {
610 				krb5_free_principal(context, princ);
611 				(void) kadm5_destroy(server_handle);
612 				return (MISC_EXIT_STATUS);
613 			}
614 		}
615 	}
616 	krb5_free_principal(context, princ);
617 
618 	(void) kadm5_destroy(server_handle);
619 
620 	return (0);
621 }
622 
623 /*
624  * Function: krb5_changepw
625  *
626  * Purpose: Initialize and call lower level routines to change a password
627  *
628  * Arguments:
629  *
630  *	princ_str	principal name to use, optional
631  *	old_password 	old password
632  *	new_password  	new password
633  *
634  * Returns:
635  *                      exit status of PAM_SUCCESS for success
636  *			1 principal unknown
637  *			2 old password wrong
638  *			3 cannot initialize admin server session
639  *			4 new passwd mismatch or error trying to change pw
640  *                      5 password not typed
641  *                      6 misc error
642  *                      7 incorrect usage
643  *
644  * Requires:
645  *	Passwords cannot be more than 255 characters long.
646  *
647  * Modifies:
648  *
649  * Changes the principal's password.
650  *
651  */
652 static int
653 krb5_changepw(
654 	pam_handle_t *pamh,
655 	char *princ_str,
656 	char *old_password,
657 	char *new_password,
658 	int debug)
659 {
660 	kadm5_ret_t		code;
661 	krb5_principal 		princ = 0;
662 	char 			msg_ret[1024], admin_realm[1024];
663 	char			kprinc[2*MAXHOSTNAMELEN];
664 	char			*cpw_service;
665 	kadm5_principal_ent_rec principal_entry;
666 	kadm5_policy_ent_rec	policy_entry;
667 	void 			*server_handle;
668 	krb5_context		context;
669 	kadm5_config_params	params;
670 
671 	(void) memset((char *)&params, 0, sizeof (params));
672 	(void) memset(&principal_entry, 0, sizeof (principal_entry));
673 	(void) memset(&policy_entry, 0, sizeof (policy_entry));
674 
675 	if (code = krb5_init_context(&context)) {
676 		return (6);
677 	}
678 
679 	if ((code = get_kmd_kuser(context, (const char *)princ_str, kprinc,
680 		2*MAXHOSTNAMELEN)) != 0) {
681 		return (code);
682 	}
683 
684 	/* Need to get a krb5_principal struct */
685 
686 	code = krb5_parse_name(context, kprinc, &princ);
687 
688 	if (code != 0) {
689 		return (MISC_EXIT_STATUS);
690 	}
691 
692 	if (strlen(old_password) == 0) {
693 		krb5_free_principal(context, princ);
694 		return (5);
695 	}
696 
697 	(void) snprintf(admin_realm, sizeof (admin_realm), "%s",
698 		krb5_princ_realm(context, princ)->data);
699 	params.mask |= KADM5_CONFIG_REALM;
700 	params.realm = admin_realm;
701 
702 
703 	if (kadm5_get_cpw_host_srv_name(context, admin_realm, &cpw_service)) {
704 		syslog(LOG_ERR,
705 			dgettext(TEXT_DOMAIN,
706 				"PAM-KRB5 (password):unable to get host based "
707 				"service name for realm %s\n"),
708 			admin_realm);
709 		return (3);
710 	}
711 
712 	code = kadm5_init_with_password(kprinc, old_password, cpw_service,
713 					&params, KADM5_STRUCT_VERSION,
714 					KADM5_API_VERSION_2, &server_handle);
715 	free(cpw_service);
716 	if (code != 0) {
717 		if (debug)
718 			syslog(LOG_DEBUG,
719 			    "PAM-KRB5 (password): changepw: "
720 			    "init_with_pw failed:  (%s)", error_message(code));
721 		krb5_free_principal(context, princ);
722 		return ((code == KADM5_BAD_PASSWORD) ? 2 : 3);
723 	}
724 
725 	code = kadm5_chpass_principal_util(server_handle, princ,
726 					new_password,
727 					NULL /* don't need pw back */,
728 					msg_ret,
729 					sizeof (msg_ret));
730 
731 	if (code) {
732 		char msgs[2][PAM_MAX_MSG_SIZE];
733 
734 		(void) snprintf(msgs[0], PAM_MAX_MSG_SIZE, "%s",
735 			dgettext(TEXT_DOMAIN,
736 				"Kerberos password not changed: "));
737 		(void) snprintf(msgs[1], PAM_MAX_MSG_SIZE, "%s", msg_ret);
738 
739 		display_msgs(pamh, PAM_ERROR_MSG, 2, msgs);
740 	}
741 
742 	krb5_free_principal(context, princ);
743 
744 	(void) kadm5_destroy(server_handle);
745 
746 	if (debug)
747 		syslog(LOG_DEBUG,
748 		    "PAM-KRB5 (password): changepw: end %d", code);
749 
750 	if (code == KRB5_LIBOS_CANTREADPWD)
751 		return (5);
752 	else if (code)
753 		return (4);
754 	else
755 		return (PAM_SUCCESS);
756 }
757 
758 static char *
759 get_passwd(
760 	pam_handle_t *pamh,
761 	char *prompt)
762 {
763 	int		err;
764 	char		*p;
765 
766 	err = __pam_get_authtok(pamh, PAM_PROMPT, 0, prompt, &p);
767 
768 	if (err != PAM_SUCCESS) {
769 		return (NULL);
770 	}
771 
772 	return (p);
773 }
774 
775 
776 static void
777 display_msgs(pam_handle_t *pamh,
778 	int msg_style, int nmsg, char msgs[][PAM_MAX_MSG_SIZE])
779 {
780 	(void) __pam_display_msg(pamh, msg_style, nmsg, msgs, NULL);
781 }
782 
783 
784 static void
785 display_msg(pam_handle_t *pamh, int msg_style, char *msg)
786 {
787 	char pam_msg[1][PAM_MAX_MSG_SIZE];
788 
789 	(void) snprintf(pam_msg[0], PAM_MAX_MSG_SIZE, "%s", msg);
790 	display_msgs(pamh, msg_style, 1, pam_msg);
791 }
792