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 *)¶ms, 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 ¶ms, 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