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