xref: /illumos-gate/usr/src/lib/pam_modules/krb5/krb5_password.c (revision 7d1e83948cb684521e72cab96020be241508f449)
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 2009 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #include <kadm5/admin.h>
27 #include <krb5.h>
28 
29 #include <security/pam_appl.h>
30 #include <security/pam_modules.h>
31 #include <security/pam_impl.h>
32 #include <syslog.h>
33 #include <string.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <sys/types.h>
37 #include <pwd.h>
38 #include <libintl.h>
39 #include <netdb.h>
40 #include "utils.h"
41 #include "krb5_repository.h"
42 
43 extern int attempt_krb5_auth(krb5_module_data_t *, char *, char **,
44 			boolean_t);
45 extern int krb5_verifypw(char *, char *, int);
46 
47 static void display_msg(pam_handle_t *, int, char *);
48 static void display_msgs(pam_handle_t *, int, int,
49 		char msgs[][PAM_MAX_MSG_SIZE]);
50 static int krb5_changepw(pam_handle_t *, char *, char *, char *, int);
51 
52 /*
53  * set_ccname()
54  *
55  * set KRB5CCNAME shell var
56  */
57 static void
58 set_ccname(
59 	pam_handle_t *pamh,
60 	krb5_module_data_t *kmd,
61 	int login_result,
62 	int debug)
63 {
64 	int result;
65 
66 	if (debug)
67 		__pam_log(LOG_AUTH | LOG_DEBUG,
68 		    "PAM-KRB5 (password): password: finalize"
69 		    " ccname env, login_result =%d, env ='%s'",
70 		    login_result, kmd->env ? kmd->env : "<null>");
71 
72 	if (kmd->env) {
73 
74 		if (login_result == PAM_SUCCESS) {
75 				/*
76 				 * Put ccname into the pamh so that login
77 				 * apps can pick this up when they run
78 				 * pam_getenvlist().
79 				 */
80 			if ((result = pam_putenv(pamh, kmd->env))
81 			    != PAM_SUCCESS) {
82 				/* should not happen but... */
83 				__pam_log(LOG_AUTH | LOG_ERR,
84 					    "PAM-KRB5 (password):"
85 					    " pam_putenv failed: result: %d",
86 				    result);
87 				goto cleanupccname;
88 			}
89 		} else {
90 		cleanupccname:
91 				/* for lack of a Solaris unputenv() */
92 			krb5_unsetenv(KRB5_ENV_CCNAME);
93 			free(kmd->env);
94 			kmd->env = NULL;
95 		}
96 	}
97 }
98 
99 /*
100  * get_set_creds()
101  *
102  * do a krb5 login to get and set krb5 creds (needed after a pw change
103  * on pw expire on login)
104  */
105 static void
106 get_set_creds(
107 	pam_handle_t *pamh,
108 	krb5_module_data_t *kmd,
109 	char *user,
110 	char *newpass,
111 	int debug)
112 {
113 	int login_result;
114 
115 	if (!kmd || kmd->age_status != PAM_NEW_AUTHTOK_REQD)
116 		return;
117 
118 	/*
119 	 * if pw has expired, get/set krb5 creds ala auth mod
120 	 *
121 	 * pwchange verified user sufficiently, so don't request strict
122 	 * tgt verification (will cause rcache perm issues possibly anyways)
123 	 */
124 	login_result = attempt_krb5_auth(kmd, user, &newpass, 0);
125 	if (debug)
126 		__pam_log(LOG_AUTH | LOG_DEBUG,
127 		    "PAM-KRB5 (password): get_set_creds: login_result= %d",
128 		    login_result);
129 	/*
130 	 * the krb5 login should not fail, but if so,
131 	 * warn the user they have to kinit(1)
132 	 */
133 	if (login_result != PAM_SUCCESS) {
134 		display_msg(pamh, PAM_TEXT_INFO,
135 			    dgettext(TEXT_DOMAIN,
136 				    "Warning: "
137 				    "Could not cache Kerberos"
138 				    " credentials, please run "
139 				    "kinit(1) or re-login\n"));
140 	}
141 	set_ccname(pamh, kmd, login_result, debug);
142 }
143 /*
144  * This is the PAM Kerberos Password Change module
145  *
146  */
147 
148 int
149 pam_sm_chauthtok(
150 	pam_handle_t		*pamh,
151 	int			flags,
152 	int			argc,
153 	const char		**argv)
154 {
155 
156 	char			*user;
157 	int			err, result = PAM_AUTHTOK_ERR;
158 	char			*newpass = NULL;
159 	char			*oldpass = NULL;
160 	int			i;
161 	int			debug = 0;
162 	uid_t			pw_uid;
163 	krb5_module_data_t	*kmd = NULL;
164 	pam_repository_t	*rep_data = NULL;
165 
166 	for (i = 0; i < argc; i++) {
167 		if (strcmp(argv[i], "debug") == 0)
168 			debug = 1;
169 		else
170 			__pam_log(LOG_AUTH | LOG_ERR,
171 				    "PAM-KRB5 (password): illegal option %s",
172 			    argv[i]);
173 	}
174 
175 	if (debug)
176 		__pam_log(LOG_AUTH | LOG_DEBUG,
177 		    "PAM-KRB5 (password): start: flags = %x",
178 		    flags);
179 
180 	(void) pam_get_item(pamh, PAM_REPOSITORY, (void **)&rep_data);
181 
182 	if (rep_data != NULL) {
183 		if (strcmp(rep_data->type, KRB5_REPOSITORY_NAME) != 0) {
184 			if (debug)
185 				__pam_log(LOG_AUTH | LOG_DEBUG,
186 					"PAM-KRB5 (auth): wrong"
187 					"repository found (%s), returning "
188 					"PAM_IGNORE", rep_data->type);
189 			return (PAM_IGNORE);
190 		}
191 	}
192 
193 	if (flags & PAM_PRELIM_CHECK) {
194 		/* Nothing to do here */
195 		if (debug)
196 			__pam_log(LOG_AUTH | LOG_DEBUG,
197 			    "PAM-KRB5 (password): prelim check");
198 		return (PAM_IGNORE);
199 	}
200 
201 	/* make sure PAM framework is telling us to update passwords */
202 	if (!(flags & PAM_UPDATE_AUTHTOK)) {
203 		__pam_log(LOG_AUTH | LOG_ERR,
204 			"PAM-KRB5 (password): bad flags: %d",
205 			flags);
206 		return (PAM_SYSTEM_ERR);
207 	}
208 
209 
210 	if ((err = pam_get_data(pamh, KRB5_DATA, (const void **)&kmd))
211 	    != PAM_SUCCESS) {
212 		if (debug)
213 			__pam_log(LOG_AUTH | LOG_DEBUG,
214 			    "PAM-KRB5 (password): get mod data failed %d",
215 			    err);
216 		kmd = NULL;
217 	}
218 
219 	if (flags & PAM_CHANGE_EXPIRED_AUTHTOK) {
220 		/* let's make sure we know the krb5 pw has expired */
221 
222 		if (debug)
223 			__pam_log(LOG_AUTH | LOG_DEBUG,
224 			    "PAM-KRB5 (password): kmd age status %d",
225 			    kmd ? kmd->age_status : -99);
226 
227 		if (!kmd || kmd->age_status != PAM_NEW_AUTHTOK_REQD)
228 			return (PAM_IGNORE);
229 	}
230 
231 	(void) pam_get_item(pamh, PAM_USER, (void **)&user);
232 
233 	if (user == NULL || *user == '\0') {
234 		__pam_log(LOG_AUTH | LOG_ERR,
235 			"PAM-KRB5 (password): username is empty");
236 		return (PAM_USER_UNKNOWN);
237 	}
238 
239 	if (!get_pw_uid(user, &pw_uid)) {
240 		__pam_log(LOG_AUTH | LOG_ERR,
241 		    "PAM-KRB5 (password): can't get uid for %s", user);
242 		return (PAM_USER_UNKNOWN);
243 	}
244 
245 	/*
246 	 * if root key exists in the keytab, it's a random key so no
247 	 * need to prompt for pw and we just return IGNORE
248 	 */
249 	if ((strcmp(user, ROOT_UNAME) == 0) &&
250 	    key_in_keytab(user, debug)) {
251 		if (debug)
252 			__pam_log(LOG_AUTH | LOG_DEBUG,
253 			    "PAM-KRB5 (password): "
254 			    "key for '%s' in keytab, returning IGNORE", user);
255 		result = PAM_IGNORE;
256 		goto out;
257 	}
258 
259 	(void) pam_get_item(pamh, PAM_AUTHTOK, (void **)&newpass);
260 
261 	if (newpass == NULL)
262 		return (PAM_SYSTEM_ERR);
263 
264 	(void) pam_get_item(pamh, PAM_OLDAUTHTOK, (void **)&oldpass);
265 
266 	if (oldpass == NULL)
267 		return (PAM_SYSTEM_ERR);
268 
269 	result = krb5_verifypw(user, oldpass, debug);
270 	if (debug)
271 		__pam_log(LOG_AUTH | LOG_DEBUG,
272 			"PAM-KRB5 (password): verifypw %d", result);
273 
274 	/*
275 	 * If it's a bad password or general failure, we are done.
276 	 */
277 	if (result != 0) {
278 		if (result == 2)
279 			display_msg(pamh, PAM_ERROR_MSG, dgettext(TEXT_DOMAIN,
280 				"Old Kerberos password incorrect\n"));
281 		return (PAM_AUTHTOK_ERR);
282 	}
283 
284 	result = krb5_changepw(pamh, user, oldpass, newpass, debug);
285 	if (result == PAM_SUCCESS) {
286 		display_msg(pamh, PAM_TEXT_INFO, dgettext(TEXT_DOMAIN,
287 		    "Kerberos password successfully changed\n"));
288 
289 		get_set_creds(pamh, kmd, user, newpass, debug);
290 	}
291 
292 out:
293 	if (debug)
294 		__pam_log(LOG_AUTH | LOG_DEBUG,
295 			"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_secure_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 		__pam_log(LOG_AUTH | LOG_ERR,
349 			"PAM-KRB5 (password): unable to get host based "
350 			"service name for realm %s\n",
351 			admin_realm);
352 		krb5_free_principal(context, princ);
353 		return (3);
354 	}
355 
356 	code = kadm5_init_with_password(kprinc, old_password, cpw_service,
357 					&params, KADM5_STRUCT_VERSION,
358 					KADM5_API_VERSION_2, NULL,
359 					&server_handle);
360 	if (code != 0) {
361 		if (debug)
362 			__pam_log(LOG_AUTH | 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_secure_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 		__pam_log(LOG_AUTH | LOG_ERR,
445 				"PAM-KRB5 (password):unable to get host based "
446 				"service name for realm %s\n",
447 			admin_realm);
448 		return (PAM_SYSTEM_ERR);
449 	}
450 
451 	code = kadm5_init_with_password(kprinc, old_password, cpw_service,
452 					&params, KADM5_STRUCT_VERSION,
453 					KADM5_API_VERSION_2, NULL,
454 					&server_handle);
455 	free(cpw_service);
456 	if (code != 0) {
457 		if (debug)
458 			__pam_log(LOG_AUTH | 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 		__pam_log(LOG_AUTH | 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