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