xref: /illumos-gate/usr/src/lib/pam_modules/krb5/krb5_password.c (revision cbea7aca3fd7787405cbdbd93752998f03dfc25f)
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  * Copyright 2023 OmniOS Community Edition (OmniOSce) Association.
26  */
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(pam_handle_t *, krb5_module_data_t *, const char *,
46     char **, boolean_t);
47 extern int krb5_verifypw(const 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 *, const char *, char *, char *, int);
53 
54 /*
55  * set_ccname()
56  *
57  * set KRB5CCNAME shell var
58  */
59 static void
set_ccname(pam_handle_t * pamh,krb5_module_data_t * kmd,int login_result,int debug)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
get_set_creds(pam_handle_t * pamh,krb5_module_data_t * kmd,const char * user,char * newpass,int debug)108 get_set_creds(
109 	pam_handle_t *pamh,
110 	krb5_module_data_t *kmd,
111 	const 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(pamh, 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
pam_sm_chauthtok(pam_handle_t * pamh,int flags,int argc,const char ** argv)151 pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv)
152 {
153 
154 	const char *user;
155 	int err, result = PAM_AUTHTOK_ERR;
156 	char *newpass = NULL;
157 	char *oldpass = NULL;
158 	int i;
159 	int debug = 0;
160 	uid_t pw_uid;
161 	krb5_module_data_t *kmd = NULL;
162 	const pam_repository_t *rep_data = NULL;
163 
164 	for (i = 0; i < argc; i++) {
165 		if (strcmp(argv[i], "debug") == 0)
166 			debug = 1;
167 		else
168 			__pam_log(LOG_AUTH | LOG_ERR,
169 			    "PAM-KRB5 (password): illegal option %s",
170 			    argv[i]);
171 	}
172 
173 	if (debug)
174 		__pam_log(LOG_AUTH | LOG_DEBUG,
175 		    "PAM-KRB5 (password): start: flags = %x",
176 		    flags);
177 
178 	(void) pam_get_item(pamh, PAM_REPOSITORY, (const void **)&rep_data);
179 
180 	if (rep_data != NULL) {
181 		if (strcmp(rep_data->type, KRB5_REPOSITORY_NAME) != 0) {
182 			if (debug)
183 				__pam_log(LOG_AUTH | LOG_DEBUG,
184 				    "PAM-KRB5 (auth): wrong"
185 				    "repository found (%s), returning "
186 				    "PAM_IGNORE", rep_data->type);
187 			return (PAM_IGNORE);
188 		}
189 	}
190 
191 	if (flags & PAM_PRELIM_CHECK) {
192 		/* Nothing to do here */
193 		if (debug)
194 			__pam_log(LOG_AUTH | LOG_DEBUG,
195 			    "PAM-KRB5 (password): prelim check");
196 		return (PAM_IGNORE);
197 	}
198 
199 	/* make sure PAM framework is telling us to update passwords */
200 	if (!(flags & PAM_UPDATE_AUTHTOK)) {
201 		__pam_log(LOG_AUTH | LOG_ERR,
202 		    "PAM-KRB5 (password): bad flags: %d",
203 		    flags);
204 		return (PAM_SYSTEM_ERR);
205 	}
206 
207 
208 	if ((err = pam_get_data(pamh, KRB5_DATA, (const void **)&kmd))
209 	    != PAM_SUCCESS) {
210 		if (debug)
211 			__pam_log(LOG_AUTH | LOG_DEBUG,
212 			    "PAM-KRB5 (password): get mod data failed %d",
213 			    err);
214 		kmd = NULL;
215 	}
216 
217 	if (flags & PAM_CHANGE_EXPIRED_AUTHTOK) {
218 		/* let's make sure we know the krb5 pw has expired */
219 
220 		if (debug)
221 			__pam_log(LOG_AUTH | LOG_DEBUG,
222 			    "PAM-KRB5 (password): kmd age status %d",
223 			    kmd ? kmd->age_status : -99);
224 
225 		if (!kmd || kmd->age_status != PAM_NEW_AUTHTOK_REQD)
226 			return (PAM_IGNORE);
227 	}
228 
229 	(void) pam_get_item(pamh, PAM_USER, (const void **)&user);
230 
231 	if (user == NULL || *user == '\0') {
232 		__pam_log(LOG_AUTH | LOG_ERR,
233 		    "PAM-KRB5 (password): username is empty");
234 		return (PAM_USER_UNKNOWN);
235 	}
236 
237 	if (!get_pw_uid(user, &pw_uid)) {
238 		__pam_log(LOG_AUTH | LOG_ERR,
239 		    "PAM-KRB5 (password): can't get uid for %s", user);
240 		return (PAM_USER_UNKNOWN);
241 	}
242 
243 	/*
244 	 * if root key exists in the keytab, it's a random key so no
245 	 * need to prompt for pw and we just return IGNORE
246 	 */
247 	if ((strcmp(user, ROOT_UNAME) == 0) &&
248 	    key_in_keytab(user, debug)) {
249 		if (debug)
250 			__pam_log(LOG_AUTH | LOG_DEBUG,
251 			    "PAM-KRB5 (password): "
252 			    "key for '%s' in keytab, returning IGNORE", user);
253 		result = PAM_IGNORE;
254 		goto out;
255 	}
256 
257 	(void) pam_get_item(pamh, PAM_AUTHTOK, (const void **)&newpass);
258 
259 	/*
260 	 * If the preauth type done didn't use a passwd just ignore the error.
261 	 */
262 	if (newpass == NULL)
263 		if (kmd && kmd->preauth_type == KRB_PKINIT)
264 			return (PAM_IGNORE);
265 		else
266 			return (PAM_SYSTEM_ERR);
267 
268 	(void) pam_get_item(pamh, PAM_OLDAUTHTOK, (const void **)&oldpass);
269 
270 	if (oldpass == NULL)
271 		if (kmd && kmd->preauth_type == KRB_PKINIT)
272 			return (PAM_IGNORE);
273 		else
274 			return (PAM_SYSTEM_ERR);
275 
276 	result = krb5_verifypw(user, oldpass, debug);
277 	if (debug)
278 		__pam_log(LOG_AUTH | LOG_DEBUG,
279 		    "PAM-KRB5 (password): verifypw %d", result);
280 
281 	/*
282 	 * If it's a bad password or general failure, we are done.
283 	 */
284 	if (result != 0) {
285 		/*
286 		 * if the preauth type done didn't use a passwd just ignore the
287 		 * error.
288 		 */
289 		if (kmd && kmd->preauth_type == KRB_PKINIT)
290 			return (PAM_IGNORE);
291 
292 		if (result == 2)
293 			display_msg(pamh, PAM_ERROR_MSG, dgettext(TEXT_DOMAIN,
294 			    "Old Kerberos password incorrect\n"));
295 		return (PAM_AUTHTOK_ERR);
296 	}
297 
298 	/*
299 	 * If the old password verifies try to change it regardless of the
300 	 * preauth type and do not ignore the error.
301 	 */
302 	result = krb5_changepw(pamh, user, oldpass, newpass, debug);
303 	if (result == PAM_SUCCESS) {
304 		display_msg(pamh, PAM_TEXT_INFO, dgettext(TEXT_DOMAIN,
305 		    "Kerberos password successfully changed\n"));
306 
307 		get_set_creds(pamh, kmd, user, newpass, debug);
308 	}
309 
310 out:
311 	if (debug)
312 		__pam_log(LOG_AUTH | LOG_DEBUG,
313 		    "PAM-KRB5 (password): out: returns %d",
314 		    result);
315 
316 	return (result);
317 }
318 
319 int
krb5_verifypw(const char * princ_str,char * old_password,int debug)320 krb5_verifypw(
321 	const char 	*princ_str,
322 	char	*old_password,
323 	int debug)
324 {
325 	kadm5_ret_t		code;
326 	krb5_principal 		princ = 0;
327 	char 			admin_realm[1024];
328 	char			kprinc[2*MAXHOSTNAMELEN];
329 	char			*cpw_service;
330 	void 			*server_handle;
331 	krb5_context		context;
332 	kadm5_config_params	params;
333 
334 	(void) memset((char *)&params, 0, sizeof (params));
335 
336 	if (code = krb5_init_secure_context(&context)) {
337 		return (6);
338 	}
339 
340 	if ((code = get_kmd_kuser(context, princ_str, kprinc,
341 	    2*MAXHOSTNAMELEN)) != 0) {
342 		return (code);
343 	}
344 
345 	/* Need to get a krb5_principal struct */
346 
347 	code = krb5_parse_name(context, kprinc, &princ);
348 
349 	if (code != 0)
350 		return (6);
351 
352 	if (strlen(old_password) == 0) {
353 		krb5_free_principal(context, princ);
354 		return (5);
355 	}
356 
357 	(void) strlcpy(admin_realm,
358 	    krb5_princ_realm(context, princ)->data,
359 	    sizeof (admin_realm));
360 
361 	params.mask |= KADM5_CONFIG_REALM;
362 	params.realm = admin_realm;
363 
364 
365 	if (kadm5_get_cpw_host_srv_name(context, admin_realm, &cpw_service)) {
366 		__pam_log(LOG_AUTH | LOG_ERR,
367 		    "PAM-KRB5 (password): unable to get host based "
368 		    "service name for realm %s\n",
369 		    admin_realm);
370 		krb5_free_principal(context, princ);
371 		return (3);
372 	}
373 
374 	code = kadm5_init_with_password(kprinc, old_password, cpw_service,
375 	    &params, KADM5_STRUCT_VERSION,
376 	    KADM5_API_VERSION_2, NULL,
377 	    &server_handle);
378 	if (code != 0) {
379 		if (debug)
380 			__pam_log(LOG_AUTH | LOG_DEBUG,
381 			    "PAM-KRB5: krb5_verifypw: init_with_pw"
382 			    " failed: (%s)", error_message(code));
383 		krb5_free_principal(context, princ);
384 		return ((code == KADM5_BAD_PASSWORD) ? 2 : 3);
385 	}
386 
387 	krb5_free_principal(context, princ);
388 
389 	(void) kadm5_destroy(server_handle);
390 
391 	return (0);
392 }
393 
394 /*
395  * Function: krb5_changepw
396  *
397  * Purpose: Initialize and call lower level routines to change a password
398  *
399  * Arguments:
400  *
401  *	princ_str	principal name to use, optional
402  *	old_password 	old password
403  *	new_password  	new password
404  *
405  * Returns:
406  *                      exit status of PAM_SUCCESS for success
407  *			else returns PAM failure
408  *
409  * Requires:
410  *	Passwords cannot be more than 255 characters long.
411  *
412  * Modifies:
413  *
414  * Changes the principal's password.
415  *
416  */
417 static int
krb5_changepw(pam_handle_t * pamh,const char * princ_str,char * old_password,char * new_password,int debug)418 krb5_changepw(
419 	pam_handle_t *pamh,
420 	const char *princ_str,
421 	char *old_password,
422 	char *new_password,
423 	int debug)
424 {
425 	kadm5_ret_t		code;
426 	krb5_principal 		princ = 0;
427 	char 			msg_ret[1024], admin_realm[1024];
428 	char			kprinc[2*MAXHOSTNAMELEN];
429 	char			*cpw_service;
430 	void 			*server_handle;
431 	krb5_context		context;
432 	kadm5_config_params	params;
433 
434 	(void) memset((char *)&params, 0, sizeof (params));
435 
436 	if (krb5_init_secure_context(&context) != 0)
437 		return (PAM_SYSTEM_ERR);
438 
439 	if ((code = get_kmd_kuser(context, princ_str, kprinc,
440 	    2*MAXHOSTNAMELEN)) != 0) {
441 		return (code);
442 	}
443 
444 	/* Need to get a krb5_principal struct */
445 
446 	code = krb5_parse_name(context, kprinc, &princ);
447 	if (code != 0)
448 		return (PAM_SYSTEM_ERR);
449 
450 	if (strlen(old_password) == 0) {
451 		krb5_free_principal(context, princ);
452 		return (PAM_AUTHTOK_ERR);
453 	}
454 
455 	(void) snprintf(admin_realm, sizeof (admin_realm), "%s",
456 	    krb5_princ_realm(context, princ)->data);
457 	params.mask |= KADM5_CONFIG_REALM;
458 	params.realm = admin_realm;
459 
460 
461 	if (kadm5_get_cpw_host_srv_name(context, admin_realm, &cpw_service)) {
462 		__pam_log(LOG_AUTH | LOG_ERR,
463 		    "PAM-KRB5 (password):unable to get host based "
464 		    "service name for realm %s\n",
465 		    admin_realm);
466 		return (PAM_SYSTEM_ERR);
467 	}
468 
469 	code = kadm5_init_with_password(kprinc, old_password, cpw_service,
470 	    &params, KADM5_STRUCT_VERSION,
471 	    KADM5_API_VERSION_2, NULL,
472 	    &server_handle);
473 	free(cpw_service);
474 	if (code != 0) {
475 		if (debug)
476 			__pam_log(LOG_AUTH | LOG_DEBUG,
477 			    "PAM-KRB5 (password): changepw: "
478 			    "init_with_pw failed:  (%s)", error_message(code));
479 		krb5_free_principal(context, princ);
480 		return ((code == KADM5_BAD_PASSWORD) ?
481 		    PAM_AUTHTOK_ERR : PAM_SYSTEM_ERR);
482 	}
483 
484 	code = kadm5_chpass_principal_util(server_handle, princ,
485 	    new_password,
486 	    NULL /* don't need pw back */,
487 	    msg_ret,
488 	    sizeof (msg_ret));
489 
490 	if (code) {
491 		char msgs[2][PAM_MAX_MSG_SIZE];
492 
493 		(void) snprintf(msgs[0], PAM_MAX_MSG_SIZE, "%s",
494 		    dgettext(TEXT_DOMAIN,
495 		    "Kerberos password not changed: "));
496 		(void) snprintf(msgs[1], PAM_MAX_MSG_SIZE, "%s", msg_ret);
497 
498 		display_msgs(pamh, PAM_ERROR_MSG, 2, msgs);
499 	}
500 
501 	krb5_free_principal(context, princ);
502 
503 	(void) kadm5_destroy(server_handle);
504 
505 	if (debug)
506 		__pam_log(LOG_AUTH | LOG_DEBUG,
507 		    "PAM-KRB5 (password): changepw: end %d", code);
508 
509 	if (code != 0)
510 		return (PAM_AUTHTOK_ERR);
511 
512 	return (PAM_SUCCESS);
513 }
514 
515 static void
display_msgs(pam_handle_t * pamh,int msg_style,int nmsg,char msgs[][PAM_MAX_MSG_SIZE])516 display_msgs(pam_handle_t *pamh,
517 	int msg_style, int nmsg, char msgs[][PAM_MAX_MSG_SIZE])
518 {
519 	(void) __pam_display_msg(pamh, msg_style, nmsg, msgs, NULL);
520 }
521 
522 
523 static void
display_msg(pam_handle_t * pamh,int msg_style,char * msg)524 display_msg(pam_handle_t *pamh, int msg_style, char *msg)
525 {
526 	char pam_msg[1][PAM_MAX_MSG_SIZE];
527 
528 	(void) snprintf(pam_msg[0], PAM_MAX_MSG_SIZE, "%s", msg);
529 	display_msgs(pamh, msg_style, 1, pam_msg);
530 }
531