Home | History | Annotate | Download | only in krb5
      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