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 <shadow.h>
     42 
     43 #include "krb5_repository.h"
     44 
     45 #define	KRB5_AUTOMIGRATE_DATA	"SUNW-KRB5-AUTOMIGRATE-DATA"
     46 
     47 #define	min(a, b) ((a) < (b) ? (a) : (b))
     48 
     49 /*
     50  * pam_sm_acct_mgmt	  main account managment routine.
     51  */
     52 
     53 static int
     54 fetch_princ_entry(
     55 	krb5_module_data_t *kmd,
     56 	char *princ_str,
     57 	kadm5_principal_ent_rec *prent,	/* out */
     58 	int debug)
     59 
     60 {
     61 	kadm5_ret_t		code;
     62 	krb5_principal 		princ = 0;
     63 	char 			admin_realm[1024];
     64 	char			kprinc[2*MAXHOSTNAMELEN];
     65 	char			*cpw_service, *password;
     66 	void 			*server_handle;
     67 	krb5_context		context;
     68 	kadm5_config_params	params;
     69 
     70 	password = kmd->password;
     71 	context = kmd->kcontext;
     72 
     73 	if ((code = get_kmd_kuser(context, (const char *)princ_str,
     74 	    kprinc, 2*MAXHOSTNAMELEN)) != 0) {
     75 		return (code);
     76 	}
     77 
     78 	code = krb5_parse_name(context, kprinc, &princ);
     79 	if (code != 0) {
     80 		return (PAM_SYSTEM_ERR);
     81 	}
     82 
     83 	if (strlen(password) == 0) {
     84 		krb5_free_principal(context, princ);
     85 		if (debug)
     86 			__pam_log(LOG_AUTH | LOG_DEBUG,
     87 			    "PAM-KRB5 (acct): fetch_princ_entry: pwlen=0");
     88 		return (PAM_AUTH_ERR);
     89 	}
     90 
     91 	(void) strlcpy(admin_realm,
     92 		    krb5_princ_realm(context, princ)->data,
     93 		    sizeof (admin_realm));
     94 
     95 	(void) memset((char *)&params, 0, sizeof (params));
     96 	params.mask |= KADM5_CONFIG_REALM;
     97 	params.realm = admin_realm;
     98 
     99 	if (kadm5_get_cpw_host_srv_name(context, admin_realm, &cpw_service)) {
    100 		__pam_log(LOG_AUTH | LOG_ERR,
    101 			"PAM-KRB5 (acct):  unable to get host based "
    102 			"service name for realm '%s'",
    103 			admin_realm);
    104 		krb5_free_principal(context, princ);
    105 		return (PAM_SYSTEM_ERR);
    106 	}
    107 
    108 	code = kadm5_init_with_password(kprinc, password, cpw_service,
    109 					&params, KADM5_STRUCT_VERSION,
    110 					KADM5_API_VERSION_2, NULL,
    111 					&server_handle);
    112 	if (code != 0) {
    113 		if (debug)
    114 			__pam_log(LOG_AUTH | LOG_DEBUG,
    115 			    "PAM-KRB5 (acct): fetch_princ_entry: "
    116 			    "init_with_pw failed: code = %d", code);
    117 		krb5_free_principal(context, princ);
    118 		return ((code == KADM5_BAD_PASSWORD) ?
    119 			PAM_AUTH_ERR : PAM_SYSTEM_ERR);
    120 	}
    121 
    122 	if (_kadm5_get_kpasswd_protocol(server_handle) != KRB5_CHGPWD_RPCSEC) {
    123 		if (debug)
    124 			__pam_log(LOG_AUTH | LOG_DEBUG,
    125 			    "PAM-KRB5 (acct): fetch_princ_entry: "
    126 			    "non-RPCSEC_GSS chpw server, can't get "
    127 			    "princ entry");
    128 		(void) kadm5_destroy(server_handle);
    129 		krb5_free_principal(context, princ);
    130 		return (PAM_SYSTEM_ERR);
    131 	}
    132 
    133 	code = kadm5_get_principal(server_handle, princ, prent,
    134 				KADM5_PRINCIPAL_NORMAL_MASK);
    135 
    136 	if (code != 0) {
    137 		(void) kadm5_destroy(server_handle);
    138 		krb5_free_principal(context, princ);
    139 		return ((code == KADM5_UNK_PRINC) ?
    140 			PAM_USER_UNKNOWN : PAM_SYSTEM_ERR);
    141 	}
    142 
    143 	(void) kadm5_destroy(server_handle);
    144 	krb5_free_principal(context, princ);
    145 
    146 	return (PAM_SUCCESS);
    147 }
    148 
    149 /*
    150  * exp_warn
    151  *
    152  * Warn the user if their pw is set to expire.
    153  *
    154  * We first check to see if the KDC had set any account or password
    155  * expiration information in the key expiration field.  If this was
    156  * not set then we must assume that the KDC could be broken and revert
    157  * to fetching pw/account expiration information from kadm.  We can not
    158  * determine the difference between broken KDCs that do not send key-exp
    159  * vs. principals that do not have an expiration policy.  The up-shot
    160  * is that pam_krb5 will probably not be stacked for acct mgmt if the
    161  * environment does not have an exp policy, avoiding the second exchange
    162  * using the kadm protocol.
    163  */
    164 static int
    165 exp_warn(
    166 	pam_handle_t *pamh,
    167 	char *user,
    168 	krb5_module_data_t *kmd,
    169 	int debug)
    170 
    171 {
    172 	int err;
    173 	kadm5_principal_ent_rec prent;
    174 	krb5_timestamp  now, days, expiration;
    175 	char    messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE], *password;
    176 	krb5_error_code code;
    177 
    178 	if (debug)
    179 		__pam_log(LOG_AUTH | LOG_DEBUG,
    180 		    "PAM-KRB5 (acct): exp_warn start: user = '%s'",
    181 		    user ? user : "<null>");
    182 
    183 	password = kmd->password;
    184 
    185 	if (!pamh || !user || !password) {
    186 		err = PAM_SERVICE_ERR;
    187 		goto exit;
    188 	}
    189 
    190 	/*
    191 	 * If we error out from krb5_init_secure_context, then just set error
    192 	 * code, check to see about debug message and exit out of routine as the
    193 	 * context could not possibly have been setup.
    194 	 */
    195 
    196 	if (code = krb5_init_secure_context(&kmd->kcontext)) {
    197 		err = PAM_SYSTEM_ERR;
    198 		if (debug)
    199 			__pam_log(LOG_AUTH | LOG_ERR, "PAM-KRB5 (acct): "
    200 			    "krb5_init_secure_context failed: code=%d",
    201 			    code);
    202 		goto exit;
    203 	}
    204 	if (code = krb5_timeofday(kmd->kcontext, &now)) {
    205 		err = PAM_SYSTEM_ERR;
    206 		if (debug)
    207 			__pam_log(LOG_AUTH | LOG_ERR,
    208 			    "PAM-KRB5 (acct): krb5_timeofday failed: code=%d",
    209 			    code);
    210 		goto out;
    211 	}
    212 
    213 	if (kmd->expiration != 0) {
    214 		expiration = kmd->expiration;
    215 	} else {
    216 		(void) memset(&prent, 0, sizeof (prent));
    217 		if ((err = fetch_princ_entry(kmd, user, &prent, debug))
    218 		    != PAM_SUCCESS) {
    219 			if (debug)
    220 				__pam_log(LOG_AUTH | LOG_DEBUG,
    221 				"PAM-KRB5 (acct): exp_warn: fetch_pr failed %d",
    222 				err);
    223 			goto out;
    224 		}
    225 		if (prent.princ_expire_time != 0 && prent.pw_expiration != 0)
    226 			expiration = min(prent.princ_expire_time,
    227 				prent.pw_expiration);
    228 		else
    229 			expiration = prent.princ_expire_time ?
    230 				prent.princ_expire_time : prent.pw_expiration;
    231 	}
    232 
    233 	if (debug)
    234 		__pam_log(LOG_AUTH | LOG_DEBUG,
    235 		    "PAM-KRB5 (acct): exp_warn: "
    236 		    "princ/pw_exp exp=%ld, now =%ld, days=%ld",
    237 		    expiration,
    238 		    now,
    239 		    expiration > 0
    240 		    ? ((expiration - now) / DAY)
    241 		    : 0);
    242 
    243 	/* warn user if principal's pw is set to expire */
    244 	if (expiration > 0) {
    245 		days = (expiration - now) / DAY;
    246 		if (days <= 0)
    247 			(void) snprintf(messages[0],
    248 				sizeof (messages[0]),
    249 				dgettext(TEXT_DOMAIN,
    250 				"Your Kerberos account/password will expire "
    251 				"within 24 hours.\n"));
    252 		else if (days == 1)
    253 			(void) snprintf(messages[0],
    254 				sizeof (messages[0]),
    255 				dgettext(TEXT_DOMAIN,
    256 				"Your Kerberos account/password will expire "
    257 				"in 1 day.\n"));
    258 		else
    259 			(void) snprintf(messages[0],
    260 				sizeof (messages[0]),
    261 				dgettext(TEXT_DOMAIN,
    262 				"Your Kerberos account/password will expire in "
    263 				"%d days.\n"),
    264 				(int)days);
    265 
    266 		(void) __pam_display_msg(pamh, PAM_TEXT_INFO, 1,
    267 					messages, NULL);
    268 	}
    269 
    270 	/* things went smooth */
    271 	err = PAM_SUCCESS;
    272 
    273 out:
    274 
    275 	if (kmd->kcontext) {
    276 		krb5_free_context(kmd->kcontext);
    277 		kmd->kcontext = NULL;
    278 	}
    279 
    280 exit:
    281 
    282 	if (debug)
    283 		__pam_log(LOG_AUTH | LOG_DEBUG,
    284 		    "PAM-KRB5 (acct): exp_warn end: err = %d", err);
    285 
    286 	return (err);
    287 }
    288 
    289 /*
    290  * pam_krb5 acct_mgmt
    291  *
    292  * we do
    293  *    - check if pw expired (flag set in auth)
    294  *    - warn user if pw is set to expire
    295  *
    296  * notes
    297  *    - we require the auth module to have already run (sets module data)
    298  *    - we don't worry about an expired princ cuz if that's the case,
    299  *      auth would have failed
    300  */
    301 int
    302 pam_sm_acct_mgmt(
    303 	pam_handle_t *pamh,
    304 	int	flags,
    305 	int	argc,
    306 	const char **argv)
    307 
    308 {
    309 	char *user = NULL;
    310 	char *userdata = NULL;
    311 	int err;
    312 	int i;
    313 	krb5_module_data_t *kmd = NULL;
    314 	char    messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
    315 	int debug = 0;  /* pam.conf entry option */
    316 	int nowarn = 0; /* pam.conf entry option, no expire warnings */
    317 	pam_repository_t	*rep_data = NULL;
    318 
    319 	for (i = 0; i < argc; i++) {
    320 		if (strcasecmp(argv[i], "debug") == 0)
    321 			debug = 1;
    322 		else if (strcasecmp(argv[i], "nowarn") == 0) {
    323 			nowarn = 1;
    324 			flags = flags | PAM_SILENT;
    325 		} else {
    326 			__pam_log(LOG_AUTH | LOG_ERR,
    327 			    "PAM-KRB5 (acct): illegal option %s",
    328 			    argv[i]);
    329 		}
    330 	}
    331 
    332 	if (debug)
    333 		__pam_log(LOG_AUTH | LOG_DEBUG,
    334 		    "PAM-KRB5 (acct): debug=%d, nowarn=%d",
    335 		    debug, nowarn);
    336 
    337 	(void) pam_get_item(pamh, PAM_REPOSITORY, (void **)&rep_data);
    338 
    339 	if (rep_data != NULL) {
    340 		/*
    341 		 * If the repository is not ours,
    342 		 * return PAM_IGNORE.
    343 		 */
    344 		if (strcmp(rep_data->type, KRB5_REPOSITORY_NAME) != 0) {
    345 			if (debug)
    346 				__pam_log(LOG_AUTH | LOG_DEBUG,
    347 					"PAM-KRB5 (acct): wrong"
    348 					"repository found (%s), returning "
    349 					"PAM_IGNORE", rep_data->type);
    350 			return (PAM_IGNORE);
    351 		}
    352 	}
    353 
    354 
    355 	/* get user name */
    356 	(void) pam_get_item(pamh, PAM_USER, (void **) &user);
    357 
    358 	if (user == NULL || *user == '\0') {
    359 		err = PAM_USER_UNKNOWN;
    360 		goto out;
    361 	}
    362 
    363 	/* get pam_krb5_migrate specific data */
    364 	err = pam_get_data(pamh, KRB5_AUTOMIGRATE_DATA,
    365 					(const void **)&userdata);
    366 	if (err != PAM_SUCCESS) {
    367 		if (debug)
    368 			__pam_log(LOG_AUTH | LOG_DEBUG, "PAM-KRB5 (acct): "
    369 				"no module data for KRB5_AUTOMIGRATE_DATA");
    370 	} else {
    371 		/*
    372 		 * We try and reauthenticate, since this user has a
    373 		 * newly created krb5 principal via the pam_krb5_migrate
    374 		 * auth module. That way, this new user will have fresh
    375 		 * creds (assuming pam_sm_authenticate() succeeds).
    376 		 */
    377 		if (strcmp(user, userdata) == 0)
    378 			(void) pam_sm_authenticate(pamh, flags, argc,
    379 					(const char **)argv);
    380 		else
    381 			if (debug)
    382 				__pam_log(LOG_AUTH | LOG_DEBUG,
    383 				"PAM-KRB5 (acct): PAM_USER %s"
    384 				"does not match user %s from pam_get_data()",
    385 				user, (char *)userdata);
    386 	}
    387 
    388 	/* get krb5 module data  */
    389 	if ((err = pam_get_data(pamh, KRB5_DATA, (const void **)&kmd))
    390 	    != PAM_SUCCESS) {
    391 		if (err == PAM_NO_MODULE_DATA) {
    392 			/*
    393 			 * pam_auth never called (possible config
    394 			 * error; no pam_krb5 auth entry in pam.conf),
    395 			 */
    396 			if (debug) {
    397 				__pam_log(LOG_AUTH | LOG_DEBUG,
    398 				    "PAM-KRB5 (acct): no module data");
    399 			}
    400 			err = PAM_IGNORE;
    401 			goto out;
    402 		} else {
    403 			__pam_log(LOG_AUTH | LOG_ERR,
    404 				    "PAM-KRB5 (acct): get module"
    405 				    " data failed: err=%d",
    406 			    err);
    407 		}
    408 		goto out;
    409 	}
    410 
    411 	debug = debug || kmd->debug;
    412 
    413 	/*
    414 	 * auth mod set status to ignore, most likely cuz root key is
    415 	 * in keytab, so skip other checks and return ignore
    416 	 */
    417 	if (kmd->auth_status == PAM_IGNORE) {
    418 		if (debug)
    419 			__pam_log(LOG_AUTH | LOG_DEBUG,
    420 			    "PAM-KRB5 (acct): kmd auth_status is IGNORE");
    421 		err = PAM_IGNORE;
    422 		goto out;
    423 	}
    424 
    425 	/*
    426 	 * If there is no Kerberos related user and there is authentication
    427 	 * data, this means that while the user has successfully passed
    428 	 * authentication, Kerberos is not the account authority because there
    429 	 * is no valid Kerberos principal.  PAM_IGNORE is returned since
    430 	 * Kerberos is not authoritative for this user.  Other modules in the
    431 	 * account stack will need to determine the success or failure for this
    432 	 * user.
    433 	 */
    434 	if (kmd->auth_status == PAM_USER_UNKNOWN) {
    435 		if (debug)
    436 			syslog(LOG_DEBUG,
    437 			    "PAM-KRB5 (acct): kmd auth_status is USER UNKNOWN");
    438 		err = PAM_IGNORE;
    439 		goto out;
    440 	}
    441 
    442 	/*
    443 	 * age_status will be set to PAM_NEW_AUTHTOK_REQD in pam_krb5's
    444 	 * 'auth' if the user's key/pw has expired and needs to be changed
    445 	 */
    446 	if (kmd->age_status == PAM_NEW_AUTHTOK_REQD) {
    447 		if (!nowarn) {
    448 			(void) snprintf(messages[0], sizeof (messages[0]),
    449 				dgettext(TEXT_DOMAIN,
    450 				"Your Kerberos password has expired.\n"));
    451 			(void) __pam_display_msg(pamh, PAM_TEXT_INFO,
    452 					1, messages, NULL);
    453 		}
    454 		err = PAM_NEW_AUTHTOK_REQD;
    455 		goto out;
    456 	}
    457 
    458 	if (kmd->auth_status == PAM_SUCCESS && !(flags & PAM_SILENT) &&
    459 	    !nowarn && kmd->password) {
    460 		/* if we fail, let it slide, it's only a warning brah */
    461 		(void) exp_warn(pamh, user, kmd, debug);
    462 	}
    463 
    464 	/*
    465 	 * If Kerberos is treated as optional in the PAM stack, it is possible
    466 	 * that there is a KRB5_DATA item and a non-Kerberos account authority.
    467 	 * In that case, PAM_IGNORE is returned.
    468 	 */
    469 	err = kmd->auth_status != PAM_SUCCESS ? PAM_IGNORE : kmd->auth_status;
    470 
    471 out:
    472 	if (debug)
    473 		__pam_log(LOG_AUTH | LOG_DEBUG,
    474 		    "PAM-KRB5 (acct): end: %s", pam_strerror(pamh, err));
    475 
    476 	return (err);
    477 }
    478