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 2010 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(pam_handle_t *, krb5_module_data_t *, char *,
44 	char **, 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
set_ccname(pam_handle_t * pamh,krb5_module_data_t * kmd,int login_result,int debug)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
get_set_creds(pam_handle_t * pamh,krb5_module_data_t * kmd,char * user,char * newpass,int debug)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(pamh, 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
pam_sm_chauthtok(pam_handle_t * pamh,int flags,int argc,const char ** argv)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 	/*
262 	 * If the preauth type done didn't use a passwd just ignore the error.
263 	 */
264 	if (newpass == NULL)
265 		if (kmd && kmd->preauth_type == KRB_PKINIT)
266 			return (PAM_IGNORE);
267 		else
268 			return (PAM_SYSTEM_ERR);
269 
270 	(void) pam_get_item(pamh, PAM_OLDAUTHTOK, (void **)&oldpass);
271 
272 	if (oldpass == NULL)
273 		if (kmd && kmd->preauth_type == KRB_PKINIT)
274 			return (PAM_IGNORE);
275 		else
276 			return (PAM_SYSTEM_ERR);
277 
278 	result = krb5_verifypw(user, oldpass, debug);
279 	if (debug)
280 		__pam_log(LOG_AUTH | LOG_DEBUG,
281 		    "PAM-KRB5 (password): verifypw %d", result);
282 
283 	/*
284 	 * If it's a bad password or general failure, we are done.
285 	 */
286 	if (result != 0) {
287 		/*
288 		 * if the preauth type done didn't use a passwd just ignore the
289 		 * error.
290 		 */
291 		if (kmd && kmd->preauth_type == KRB_PKINIT)
292 			return (PAM_IGNORE);
293 
294 		if (result == 2)
295 			display_msg(pamh, PAM_ERROR_MSG, dgettext(TEXT_DOMAIN,
296 			    "Old Kerberos password incorrect\n"));
297 		return (PAM_AUTHTOK_ERR);
298 	}
299 
300 	/*
301 	 * If the old password verifies try to change it regardless of the
302 	 * preauth type and do not ignore the error.
303 	 */
304 	result = krb5_changepw(pamh, user, oldpass, newpass, debug);
305 	if (result == PAM_SUCCESS) {
306 		display_msg(pamh, PAM_TEXT_INFO, dgettext(TEXT_DOMAIN,
307 		    "Kerberos password successfully changed\n"));
308 
309 		get_set_creds(pamh, kmd, user, newpass, debug);
310 	}
311 
312 out:
313 	if (debug)
314 		__pam_log(LOG_AUTH | LOG_DEBUG,
315 		    "PAM-KRB5 (password): out: returns %d",
316 		    result);
317 
318 	return (result);
319 }
320 
321 int
krb5_verifypw(char * princ_str,char * old_password,int debug)322 krb5_verifypw(
323 	char 	*princ_str,
324 	char	*old_password,
325 	int debug)
326 {
327 	kadm5_ret_t		code;
328 	krb5_principal 		princ = 0;
329 	char 			admin_realm[1024];
330 	char			kprinc[2*MAXHOSTNAMELEN];
331 	char			*cpw_service;
332 	void 			*server_handle;
333 	krb5_context		context;
334 	kadm5_config_params	params;
335 
336 	(void) memset((char *)&params, 0, sizeof (params));
337 
338 	if (code = krb5_init_secure_context(&context)) {
339 		return (6);
340 	}
341 
342 	if ((code = get_kmd_kuser(context, (const char *)princ_str, kprinc,
343 	    2*MAXHOSTNAMELEN)) != 0) {
344 		return (code);
345 	}
346 
347 	/* Need to get a krb5_principal struct */
348 
349 	code = krb5_parse_name(context, kprinc, &princ);
350 
351 	if (code != 0)
352 		return (6);
353 
354 	if (strlen(old_password) == 0) {
355 		krb5_free_principal(context, princ);
356 		return (5);
357 	}
358 
359 	(void) strlcpy(admin_realm,
360 	    krb5_princ_realm(context, princ)->data,
361 	    sizeof (admin_realm));
362 
363 	params.mask |= KADM5_CONFIG_REALM;
364 	params.realm = admin_realm;
365 
366 
367 	if (kadm5_get_cpw_host_srv_name(context, admin_realm, &cpw_service)) {
368 		__pam_log(LOG_AUTH | LOG_ERR,
369 		    "PAM-KRB5 (password): unable to get host based "
370 		    "service name for realm %s\n",
371 		    admin_realm);
372 		krb5_free_principal(context, princ);
373 		return (3);
374 	}
375 
376 	code = kadm5_init_with_password(kprinc, old_password, cpw_service,
377 	    &params, KADM5_STRUCT_VERSION,
378 	    KADM5_API_VERSION_2, NULL,
379 	    &server_handle);
380 	if (code != 0) {
381 		if (debug)
382 			__pam_log(LOG_AUTH | LOG_DEBUG,
383 			    "PAM-KRB5: krb5_verifypw: init_with_pw"
384 			    " failed: (%s)", error_message(code));
385 		krb5_free_principal(context, princ);
386 		return ((code == KADM5_BAD_PASSWORD) ? 2 : 3);
387 	}
388 
389 	krb5_free_principal(context, princ);
390 
391 	(void) kadm5_destroy(server_handle);
392 
393 	return (0);
394 }
395 
396 /*
397  * Function: krb5_changepw
398  *
399  * Purpose: Initialize and call lower level routines to change a password
400  *
401  * Arguments:
402  *
403  *	princ_str	principal name to use, optional
404  *	old_password 	old password
405  *	new_password  	new password
406  *
407  * Returns:
408  *                      exit status of PAM_SUCCESS for success
409  *			else returns PAM failure
410  *
411  * Requires:
412  *	Passwords cannot be more than 255 characters long.
413  *
414  * Modifies:
415  *
416  * Changes the principal's password.
417  *
418  */
419 static int
krb5_changepw(pam_handle_t * pamh,char * princ_str,char * old_password,char * new_password,int debug)420 krb5_changepw(
421 	pam_handle_t *pamh,
422 	char *princ_str,
423 	char *old_password,
424 	char *new_password,
425 	int debug)
426 {
427 	kadm5_ret_t		code;
428 	krb5_principal 		princ = 0;
429 	char 			msg_ret[1024], admin_realm[1024];
430 	char			kprinc[2*MAXHOSTNAMELEN];
431 	char			*cpw_service;
432 	void 			*server_handle;
433 	krb5_context		context;
434 	kadm5_config_params	params;
435 
436 	(void) memset((char *)&params, 0, sizeof (params));
437 
438 	if (krb5_init_secure_context(&context) != 0)
439 		return (PAM_SYSTEM_ERR);
440 
441 	if ((code = get_kmd_kuser(context, (const char *)princ_str, kprinc,
442 	    2*MAXHOSTNAMELEN)) != 0) {
443 		return (code);
444 	}
445 
446 	/* Need to get a krb5_principal struct */
447 
448 	code = krb5_parse_name(context, kprinc, &princ);
449 	if (code != 0)
450 		return (PAM_SYSTEM_ERR);
451 
452 	if (strlen(old_password) == 0) {
453 		krb5_free_principal(context, princ);
454 		return (PAM_AUTHTOK_ERR);
455 	}
456 
457 	(void) snprintf(admin_realm, sizeof (admin_realm), "%s",
458 	    krb5_princ_realm(context, princ)->data);
459 	params.mask |= KADM5_CONFIG_REALM;
460 	params.realm = admin_realm;
461 
462 
463 	if (kadm5_get_cpw_host_srv_name(context, admin_realm, &cpw_service)) {
464 		__pam_log(LOG_AUTH | LOG_ERR,
465 		    "PAM-KRB5 (password):unable to get host based "
466 		    "service name for realm %s\n",
467 		    admin_realm);
468 		return (PAM_SYSTEM_ERR);
469 	}
470 
471 	code = kadm5_init_with_password(kprinc, old_password, cpw_service,
472 	    &params, KADM5_STRUCT_VERSION,
473 	    KADM5_API_VERSION_2, NULL,
474 	    &server_handle);
475 	free(cpw_service);
476 	if (code != 0) {
477 		if (debug)
478 			__pam_log(LOG_AUTH | LOG_DEBUG,
479 			    "PAM-KRB5 (password): changepw: "
480 			    "init_with_pw failed:  (%s)", error_message(code));
481 		krb5_free_principal(context, princ);
482 		return ((code == KADM5_BAD_PASSWORD) ?
483 		    PAM_AUTHTOK_ERR : PAM_SYSTEM_ERR);
484 	}
485 
486 	code = kadm5_chpass_principal_util(server_handle, princ,
487 	    new_password,
488 	    NULL /* don't need pw back */,
489 	    msg_ret,
490 	    sizeof (msg_ret));
491 
492 	if (code) {
493 		char msgs[2][PAM_MAX_MSG_SIZE];
494 
495 		(void) snprintf(msgs[0], PAM_MAX_MSG_SIZE, "%s",
496 		    dgettext(TEXT_DOMAIN,
497 		    "Kerberos password not changed: "));
498 		(void) snprintf(msgs[1], PAM_MAX_MSG_SIZE, "%s", msg_ret);
499 
500 		display_msgs(pamh, PAM_ERROR_MSG, 2, msgs);
501 	}
502 
503 	krb5_free_principal(context, princ);
504 
505 	(void) kadm5_destroy(server_handle);
506 
507 	if (debug)
508 		__pam_log(LOG_AUTH | LOG_DEBUG,
509 		    "PAM-KRB5 (password): changepw: end %d", code);
510 
511 	if (code != 0)
512 		return (PAM_AUTHTOK_ERR);
513 
514 	return (PAM_SUCCESS);
515 }
516 
517 static void
display_msgs(pam_handle_t * pamh,int msg_style,int nmsg,char msgs[][PAM_MAX_MSG_SIZE])518 display_msgs(pam_handle_t *pamh,
519 	int msg_style, int nmsg, char msgs[][PAM_MAX_MSG_SIZE])
520 {
521 	(void) __pam_display_msg(pamh, msg_style, nmsg, msgs, NULL);
522 }
523 
524 
525 static void
display_msg(pam_handle_t * pamh,int msg_style,char * msg)526 display_msg(pam_handle_t *pamh, int msg_style, char *msg)
527 {
528 	char pam_msg[1][PAM_MAX_MSG_SIZE];
529 
530 	(void) snprintf(pam_msg[0], PAM_MAX_MSG_SIZE, "%s", msg);
531 	display_msgs(pamh, msg_style, 1, pam_msg);
532 }
533