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