xref: /illumos-gate/usr/src/lib/pam_modules/krb5/krb5_password.c (revision 60a3f738d56f92ae8b80e4b62a2331c6e1f2311f)
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 2006 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 <kadm5/admin.h>
29 #include <krb5.h>
30 
31 #include <security/pam_appl.h>
32 #include <security/pam_modules.h>
33 #include <security/pam_impl.h>
34 #include <syslog.h>
35 #include <string.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <sys/types.h>
39 #include <pwd.h>
40 #include <libintl.h>
41 #include <netdb.h>
42 #include "utils.h"
43 #include "krb5_repository.h"
44 
45 extern int attempt_krb5_auth(krb5_module_data_t *, char *, char **,
46 			boolean_t);
47 extern int krb5_verifypw(char *, char *, int);
48 
49 static void display_msg(pam_handle_t *, int, char *);
50 static void display_msgs(pam_handle_t *, int, int,
51 		char msgs[][PAM_MAX_MSG_SIZE]);
52 static int krb5_changepw(pam_handle_t *, char *, char *, char *, int);
53 
54 /*
55  * set_ccname()
56  *
57  * set KRB5CCNAME shell var
58  */
59 static void
60 set_ccname(
61 	pam_handle_t *pamh,
62 	krb5_module_data_t *kmd,
63 	int login_result,
64 	int debug)
65 {
66 	int result;
67 
68 	if (debug)
69 		syslog(LOG_DEBUG,
70 		    "PAM-KRB5 (password): password: finalize"
71 		    " ccname env, login_result =%d, env ='%s'",
72 		    login_result, kmd->env ? kmd->env : "<null>");
73 
74 	if (kmd->env) {
75 
76 		if (login_result == PAM_SUCCESS) {
77 				/*
78 				 * Put ccname into the pamh so that login
79 				 * apps can pick this up when they run
80 				 * pam_getenvlist().
81 				 */
82 			if ((result = pam_putenv(pamh, kmd->env))
83 			    != PAM_SUCCESS) {
84 				/* should not happen but... */
85 				syslog(LOG_ERR,
86 				    dgettext(TEXT_DOMAIN,
87 					    "PAM-KRB5 (password):"
88 					    " pam_putenv failed: result: %d"),
89 				    result);
90 				goto cleanupccname;
91 			}
92 		} else {
93 		cleanupccname:
94 				/* for lack of a Solaris unputenv() */
95 			krb5_unsetenv(KRB5_ENV_CCNAME);
96 			free(kmd->env);
97 			kmd->env = NULL;
98 		}
99 	}
100 }
101 
102 /*
103  * get_set_creds()
104  *
105  * do a krb5 login to get and set krb5 creds (needed after a pw change
106  * on pw expire on login)
107  */
108 static void
109 get_set_creds(
110 	pam_handle_t *pamh,
111 	krb5_module_data_t *kmd,
112 	char *user,
113 	char *newpass,
114 	int debug)
115 {
116 	int login_result;
117 
118 	if (!kmd || kmd->age_status != PAM_NEW_AUTHTOK_REQD)
119 		return;
120 
121 	/*
122 	 * if pw has expired, get/set krb5 creds ala auth mod
123 	 *
124 	 * pwchange verified user sufficiently, so don't request strict
125 	 * tgt verification (will cause rcache perm issues possibly anyways)
126 	 */
127 	login_result = attempt_krb5_auth(kmd, user, &newpass, 0);
128 	if (debug)
129 		syslog(LOG_DEBUG,
130 		    "PAM-KRB5 (password): get_set_creds: login_result= %d",
131 		    login_result);
132 	/*
133 	 * the krb5 login should not fail, but if so,
134 	 * warn the user they have to kinit(1)
135 	 */
136 	if (login_result != PAM_SUCCESS) {
137 		display_msg(pamh, PAM_TEXT_INFO,
138 			    dgettext(TEXT_DOMAIN,
139 				    "Warning: "
140 				    "Could not cache Kerberos"
141 				    " credentials, please run "
142 				    "kinit(1) or re-login\n"));
143 	}
144 	set_ccname(pamh, kmd, login_result, debug);
145 }
146 /*
147  * This is the PAM Kerberos Password Change module
148  *
149  */
150 
151 int
152 pam_sm_chauthtok(
153 	pam_handle_t		*pamh,
154 	int			flags,
155 	int			argc,
156 	const char		**argv)
157 {
158 
159 	char			*user;
160 	int			err, result = PAM_AUTHTOK_ERR;
161 	char			*newpass = NULL;
162 	char			*oldpass = NULL;
163 	int			i;
164 	int			debug = 0;
165 	uid_t			pw_uid;
166 	krb5_module_data_t	*kmd = NULL;
167 	pam_repository_t	*rep_data = NULL;
168 
169 	for (i = 0; i < argc; i++) {
170 		if (strcmp(argv[i], "debug") == 0)
171 			debug = 1;
172 		else
173 			syslog(LOG_ERR,
174 			    dgettext(TEXT_DOMAIN,
175 				    "PAM-KRB5 (password): illegal option %s"),
176 			    argv[i]);
177 	}
178 
179 	if (debug)
180 		syslog(LOG_DEBUG,
181 		    "PAM-KRB5 (password): start: flags = %x",
182 		    flags);
183 
184 	(void) pam_get_item(pamh, PAM_REPOSITORY, (void **)&rep_data);
185 
186 	if (rep_data != NULL) {
187 		if (strcmp(rep_data->type, KRB5_REPOSITORY_NAME) != 0) {
188 			if (debug)
189 				syslog(LOG_DEBUG, "PAM-KRB5 (auth): wrong"
190 					"repository found (%s), returning "
191 					"PAM_IGNORE", rep_data->type);
192 			return (PAM_IGNORE);
193 		}
194 	}
195 
196 	if (flags & PAM_PRELIM_CHECK) {
197 		/* Nothing to do here */
198 		if (debug)
199 			syslog(LOG_DEBUG,
200 			    "PAM-KRB5 (password): prelim check");
201 		return (PAM_IGNORE);
202 	}
203 
204 	/* make sure PAM framework is telling us to update passwords */
205 	if (!(flags & PAM_UPDATE_AUTHTOK)) {
206 		syslog(LOG_ERR, dgettext(TEXT_DOMAIN,
207 			"PAM-KRB5 (password): bad flags: %d"),
208 			flags);
209 		return (PAM_SYSTEM_ERR);
210 	}
211 
212 
213 	if ((err = pam_get_data(pamh, KRB5_DATA, (const void **)&kmd))
214 	    != PAM_SUCCESS) {
215 		if (debug)
216 			syslog(LOG_DEBUG,
217 			    "PAM-KRB5 (password): get mod data failed %d",
218 			    err);
219 		kmd = NULL;
220 	}
221 
222 	if (flags & PAM_CHANGE_EXPIRED_AUTHTOK) {
223 		/* let's make sure we know the krb5 pw has expired */
224 
225 		if (debug)
226 			syslog(LOG_DEBUG,
227 			    "PAM-KRB5 (password): kmd age status %d",
228 			    kmd ? kmd->age_status : -99);
229 
230 		if (!kmd || kmd->age_status != PAM_NEW_AUTHTOK_REQD)
231 			return (PAM_IGNORE);
232 	}
233 
234 	(void) pam_get_item(pamh, PAM_USER, (void **)&user);
235 
236 	if (user == NULL || user == '\0') {
237 		syslog(LOG_ERR, "PAM-KRB5 (password): username is empty");
238 		return (PAM_USER_UNKNOWN);
239 	}
240 
241 	if (!get_pw_uid(user, &pw_uid)) {
242 		syslog(LOG_ERR,
243 		    "PAM-KRB5 (password): can't get uid for %s", user);
244 		return (PAM_USER_UNKNOWN);
245 	}
246 
247 	/*
248 	 * if root key exists in the keytab, it's a random key so no
249 	 * need to prompt for pw and we just return IGNORE
250 	 */
251 	if ((strcmp(user, ROOT_UNAME) == 0) &&
252 	    key_in_keytab(user, debug)) {
253 		if (debug)
254 			syslog(LOG_DEBUG,
255 			    "PAM-KRB5 (password): "
256 			    "key for '%s' in keytab, returning IGNORE", user);
257 		result = PAM_IGNORE;
258 		goto out;
259 	}
260 
261 	(void) pam_get_item(pamh, PAM_AUTHTOK, (void **)&newpass);
262 
263 	if (newpass == NULL)
264 		return (PAM_SYSTEM_ERR);
265 
266 	(void) pam_get_item(pamh, PAM_OLDAUTHTOK, (void **)&oldpass);
267 
268 	if (oldpass == NULL)
269 		return (PAM_SYSTEM_ERR);
270 
271 	result = krb5_verifypw(user, oldpass, debug);
272 	if (debug)
273 		syslog(LOG_DEBUG, "PAM-KRB5 (password): verifypw %d", result);
274 
275 	/*
276 	 * If it's a bad password or general failure, we are done.
277 	 */
278 	if (result != 0) {
279 		if (result == 2)
280 			display_msg(pamh, PAM_ERROR_MSG, dgettext(TEXT_DOMAIN,
281 				"Old Kerberos password incorrect\n"));
282 		return (PAM_AUTHTOK_ERR);
283 	}
284 
285 	result = krb5_changepw(pamh, user, oldpass, newpass, debug);
286 	if (result == PAM_SUCCESS) {
287 		display_msg(pamh, PAM_TEXT_INFO, dgettext(TEXT_DOMAIN,
288 		    "Kerberos password successfully changed\n"));
289 
290 		get_set_creds(pamh, kmd, user, newpass, debug);
291 	}
292 
293 out:
294 	if (debug)
295 		syslog(LOG_DEBUG, "PAM-KRB5 (password): out: returns %d",
296 		    result);
297 
298 	return (result);
299 }
300 
301 int
302 krb5_verifypw(
303 	char 	*princ_str,
304 	char	*old_password,
305 	int debug)
306 {
307 	kadm5_ret_t		code;
308 	krb5_principal 		princ = 0;
309 	char 			admin_realm[1024];
310 	char			kprinc[2*MAXHOSTNAMELEN];
311 	char			*cpw_service;
312 	void 			*server_handle;
313 	krb5_context		context;
314 	kadm5_config_params	params;
315 
316 	(void) memset((char *)&params, 0, sizeof (params));
317 
318 	if (code = krb5_init_context(&context)) {
319 		return (6);
320 	}
321 
322 	if ((code = get_kmd_kuser(context, (const char *)princ_str, kprinc,
323 		2*MAXHOSTNAMELEN)) != 0) {
324 		return (code);
325 	}
326 
327 	/* Need to get a krb5_principal struct */
328 
329 	code = krb5_parse_name(context, kprinc, &princ);
330 
331 	if (code != 0)
332 		return (6);
333 
334 	if (strlen(old_password) == 0) {
335 		krb5_free_principal(context, princ);
336 		return (5);
337 	}
338 
339 	(void) strlcpy(admin_realm,
340 		    krb5_princ_realm(context, princ)->data,
341 		    sizeof (admin_realm));
342 
343 	params.mask |= KADM5_CONFIG_REALM;
344 	params.realm = admin_realm;
345 
346 
347 	if (kadm5_get_cpw_host_srv_name(context, admin_realm, &cpw_service)) {
348 		syslog(LOG_ERR,
349 		    dgettext(TEXT_DOMAIN,
350 			"PAM-KRB5 (password): unable to get host based "
351 			"service name for realm %s\n"),
352 			admin_realm);
353 		krb5_free_principal(context, princ);
354 		return (3);
355 	}
356 
357 	code = kadm5_init_with_password(kprinc, old_password, cpw_service,
358 					&params, KADM5_STRUCT_VERSION,
359 					KADM5_API_VERSION_2, &server_handle);
360 	if (code != 0) {
361 		if (debug)
362 			syslog(LOG_DEBUG,
363 			    "PAM-KRB5: krb5_verifypw: init_with_pw"
364 			    " failed: (%s)", error_message(code));
365 		krb5_free_principal(context, princ);
366 		return ((code == KADM5_BAD_PASSWORD) ? 2 : 3);
367 	}
368 
369 	krb5_free_principal(context, princ);
370 
371 	(void) kadm5_destroy(server_handle);
372 
373 	return (0);
374 }
375 
376 /*
377  * Function: krb5_changepw
378  *
379  * Purpose: Initialize and call lower level routines to change a password
380  *
381  * Arguments:
382  *
383  *	princ_str	principal name to use, optional
384  *	old_password 	old password
385  *	new_password  	new password
386  *
387  * Returns:
388  *                      exit status of PAM_SUCCESS for success
389  *			else returns PAM failure
390  *
391  * Requires:
392  *	Passwords cannot be more than 255 characters long.
393  *
394  * Modifies:
395  *
396  * Changes the principal's password.
397  *
398  */
399 static int
400 krb5_changepw(
401 	pam_handle_t *pamh,
402 	char *princ_str,
403 	char *old_password,
404 	char *new_password,
405 	int debug)
406 {
407 	kadm5_ret_t		code;
408 	krb5_principal 		princ = 0;
409 	char 			msg_ret[1024], admin_realm[1024];
410 	char			kprinc[2*MAXHOSTNAMELEN];
411 	char			*cpw_service;
412 	void 			*server_handle;
413 	krb5_context		context;
414 	kadm5_config_params	params;
415 
416 	(void) memset((char *)&params, 0, sizeof (params));
417 
418 	if (krb5_init_context(&context) != 0)
419 		return (PAM_SYSTEM_ERR);
420 
421 	if ((code = get_kmd_kuser(context, (const char *)princ_str, kprinc,
422 		2*MAXHOSTNAMELEN)) != 0) {
423 		return (code);
424 	}
425 
426 	/* Need to get a krb5_principal struct */
427 
428 	code = krb5_parse_name(context, kprinc, &princ);
429 	if (code != 0)
430 		return (PAM_SYSTEM_ERR);
431 
432 	if (strlen(old_password) == 0) {
433 		krb5_free_principal(context, princ);
434 		return (PAM_AUTHTOK_ERR);
435 	}
436 
437 	(void) snprintf(admin_realm, sizeof (admin_realm), "%s",
438 		krb5_princ_realm(context, princ)->data);
439 	params.mask |= KADM5_CONFIG_REALM;
440 	params.realm = admin_realm;
441 
442 
443 	if (kadm5_get_cpw_host_srv_name(context, admin_realm, &cpw_service)) {
444 		syslog(LOG_ERR,
445 			dgettext(TEXT_DOMAIN,
446 				"PAM-KRB5 (password):unable to get host based "
447 				"service name for realm %s\n"),
448 			admin_realm);
449 		return (PAM_SYSTEM_ERR);
450 	}
451 
452 	code = kadm5_init_with_password(kprinc, old_password, cpw_service,
453 					&params, KADM5_STRUCT_VERSION,
454 					KADM5_API_VERSION_2, &server_handle);
455 	free(cpw_service);
456 	if (code != 0) {
457 		if (debug)
458 			syslog(LOG_DEBUG,
459 			    "PAM-KRB5 (password): changepw: "
460 			    "init_with_pw failed:  (%s)", error_message(code));
461 		krb5_free_principal(context, princ);
462 		return ((code == KADM5_BAD_PASSWORD) ?
463 			PAM_AUTHTOK_ERR : PAM_SYSTEM_ERR);
464 	}
465 
466 	code = kadm5_chpass_principal_util(server_handle, princ,
467 					new_password,
468 					NULL /* don't need pw back */,
469 					msg_ret,
470 					sizeof (msg_ret));
471 
472 	if (code) {
473 		char msgs[2][PAM_MAX_MSG_SIZE];
474 
475 		(void) snprintf(msgs[0], PAM_MAX_MSG_SIZE, "%s",
476 			dgettext(TEXT_DOMAIN,
477 				"Kerberos password not changed: "));
478 		(void) snprintf(msgs[1], PAM_MAX_MSG_SIZE, "%s", msg_ret);
479 
480 		display_msgs(pamh, PAM_ERROR_MSG, 2, msgs);
481 	}
482 
483 	krb5_free_principal(context, princ);
484 
485 	(void) kadm5_destroy(server_handle);
486 
487 	if (debug)
488 		syslog(LOG_DEBUG,
489 		    "PAM-KRB5 (password): changepw: end %d", code);
490 
491 	if (code != 0)
492 		return (PAM_AUTHTOK_ERR);
493 
494 	return (PAM_SUCCESS);
495 }
496 
497 static void
498 display_msgs(pam_handle_t *pamh,
499 	int msg_style, int nmsg, char msgs[][PAM_MAX_MSG_SIZE])
500 {
501 	(void) __pam_display_msg(pamh, msg_style, nmsg, msgs, NULL);
502 }
503 
504 
505 static void
506 display_msg(pam_handle_t *pamh, int msg_style, char *msg)
507 {
508 	char pam_msg[1][PAM_MAX_MSG_SIZE];
509 
510 	(void) snprintf(pam_msg[0], PAM_MAX_MSG_SIZE, "%s", msg);
511 	display_msgs(pamh, msg_style, 1, pam_msg);
512 }
513