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 krb5_free_context(context); 84 return (PAM_SYSTEM_ERR); 85 } 86 87 if (strlen(password) == 0) { 88 krb5_free_principal(context, princ); 89 krb5_free_context(context); 90 if (debug) 91 __pam_log(LOG_AUTH | LOG_DEBUG, 92 "PAM-KRB5 (acct): fetch_princ_entry: pwlen=0"); 93 return (PAM_AUTH_ERR); 94 } 95 96 (void) strlcpy(admin_realm, 97 krb5_princ_realm(context, princ)->data, 98 sizeof (admin_realm)); 99 100 (void) memset((char *)¶ms, 0, sizeof (params)); 101 params.mask |= KADM5_CONFIG_REALM; 102 params.realm = admin_realm; 103 104 if (kadm5_get_cpw_host_srv_name(context, admin_realm, &cpw_service)) { 105 __pam_log(LOG_AUTH | LOG_ERR, 106 "PAM-KRB5 (acct): unable to get host based " 107 "service name for realm '%s'", 108 admin_realm); 109 krb5_free_principal(context, princ); 110 krb5_free_context(context); 111 return (PAM_SYSTEM_ERR); 112 } 113 114 code = kadm5_init_with_password(kprinc, password, cpw_service, 115 ¶ms, KADM5_STRUCT_VERSION, 116 KADM5_API_VERSION_2, &server_handle); 117 if (code != 0) { 118 if (debug) 119 __pam_log(LOG_AUTH | LOG_DEBUG, 120 "PAM-KRB5 (acct): fetch_princ_entry: " 121 "init_with_pw failed: code = %d", code); 122 krb5_free_principal(context, princ); 123 krb5_free_context(context); 124 return ((code == KADM5_BAD_PASSWORD) ? 125 PAM_AUTH_ERR : PAM_SYSTEM_ERR); 126 } 127 128 if (_kadm5_get_kpasswd_protocol(server_handle) != KRB5_CHGPWD_RPCSEC) { 129 if (debug) 130 __pam_log(LOG_AUTH | LOG_DEBUG, 131 "PAM-KRB5 (acct): fetch_princ_entry: " 132 "non-RPCSEC_GSS chpw server, can't get " 133 "princ entry"); 134 (void) kadm5_destroy(server_handle); 135 krb5_free_principal(context, princ); 136 krb5_free_context(context); 137 return (PAM_SYSTEM_ERR); 138 } 139 140 code = kadm5_get_principal(server_handle, princ, prent, 141 KADM5_PRINCIPAL_NORMAL_MASK); 142 143 if (code != 0) { 144 (void) kadm5_destroy(server_handle); 145 krb5_free_principal(context, princ); 146 krb5_free_context(context); 147 return ((code == KADM5_UNK_PRINC) ? 148 PAM_USER_UNKNOWN : PAM_SYSTEM_ERR); 149 } 150 151 (void) kadm5_destroy(server_handle); 152 krb5_free_principal(context, princ); 153 krb5_free_context(context); 154 155 return (PAM_SUCCESS); 156 } 157 158 /* 159 * exp_warn 160 * 161 * Warn the user if their pw is set to expire. 162 * 163 * We first check to see if the KDC had set any account or password 164 * expiration information in the key expiration field. If this was 165 * not set then we must assume that the KDC could be broken and revert 166 * to fetching pw/account expiration information from kadm. We can not 167 * determine the difference between broken KDCs that do not send key-exp 168 * vs. principals that do not have an expiration policy. The up-shot 169 * is that pam_krb5 will probably not be stacked for acct mgmt if the 170 * environment does not have an exp policy, avoiding the second exchange 171 * using the kadm protocol. 172 */ 173 static int 174 exp_warn( 175 pam_handle_t *pamh, 176 char *user, 177 krb5_module_data_t *kmd, 178 int debug) 179 180 { 181 int err; 182 kadm5_principal_ent_rec prent; 183 krb5_timestamp now, days, expiration; 184 char messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE], *password; 185 krb5_error_code code; 186 187 if (debug) 188 __pam_log(LOG_AUTH | LOG_DEBUG, 189 "PAM-KRB5 (acct): exp_warn start: user = '%s'", 190 user ? user : "<null>"); 191 192 password = kmd->password; 193 194 if (!pamh || !user || !password) { 195 err = PAM_SERVICE_ERR; 196 goto out; 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 out; 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 if (debug) 278 __pam_log(LOG_AUTH | LOG_DEBUG, 279 "PAM-KRB5 (acct): exp_warn end: err = %d", err); 280 281 return (err); 282 } 283 284 /* 285 * pam_krb5 acct_mgmt 286 * 287 * we do 288 * - check if pw expired (flag set in auth) 289 * - warn user if pw is set to expire 290 * 291 * notes 292 * - we require the auth module to have already run (sets module data) 293 * - we don't worry about an expired princ cuz if that's the case, 294 * auth would have failed 295 */ 296 int 297 pam_sm_acct_mgmt( 298 pam_handle_t *pamh, 299 int flags, 300 int argc, 301 const char **argv) 302 303 { 304 char *user = NULL; 305 char *userdata = NULL; 306 int err; 307 int i; 308 krb5_module_data_t *kmd = NULL; 309 char messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE]; 310 int debug = 0; /* pam.conf entry option */ 311 int nowarn = 0; /* pam.conf entry option, no expire warnings */ 312 pam_repository_t *rep_data = NULL; 313 314 for (i = 0; i < argc; i++) { 315 if (strcasecmp(argv[i], "debug") == 0) 316 debug = 1; 317 else if (strcasecmp(argv[i], "nowarn") == 0) { 318 nowarn = 1; 319 flags = flags | PAM_SILENT; 320 } else { 321 __pam_log(LOG_AUTH | LOG_ERR, 322 "PAM-KRB5 (acct): illegal option %s", 323 argv[i]); 324 } 325 } 326 327 if (debug) 328 __pam_log(LOG_AUTH | LOG_DEBUG, 329 "PAM-KRB5 (acct): debug=%d, nowarn=%d", 330 debug, nowarn); 331 332 (void) pam_get_item(pamh, PAM_REPOSITORY, (void **)&rep_data); 333 334 if (rep_data != NULL) { 335 /* 336 * If the repository is not ours, 337 * return PAM_IGNORE. 338 */ 339 if (strcmp(rep_data->type, KRB5_REPOSITORY_NAME) != 0) { 340 if (debug) 341 __pam_log(LOG_AUTH | LOG_DEBUG, 342 "PAM-KRB5 (acct): wrong" 343 "repository found (%s), returning " 344 "PAM_IGNORE", rep_data->type); 345 return (PAM_IGNORE); 346 } 347 } 348 349 350 /* get user name */ 351 (void) pam_get_item(pamh, PAM_USER, (void **) &user); 352 353 if (user == NULL || *user == '\0') { 354 err = PAM_USER_UNKNOWN; 355 goto out; 356 } 357 358 /* get pam_krb5_migrate specific data */ 359 err = pam_get_data(pamh, KRB5_AUTOMIGRATE_DATA, 360 (const void **)&userdata); 361 if (err != PAM_SUCCESS) { 362 if (debug) 363 __pam_log(LOG_AUTH | LOG_DEBUG, "PAM-KRB5 (acct): " 364 "no module data for KRB5_AUTOMIGRATE_DATA"); 365 } else { 366 /* 367 * We try and reauthenticate, since this user has a 368 * newly created krb5 principal via the pam_krb5_migrate 369 * auth module. That way, this new user will have fresh 370 * creds (assuming pam_sm_authenticate() succeeds). 371 */ 372 if (strcmp(user, userdata) == 0) 373 (void) pam_sm_authenticate(pamh, flags, argc, 374 (const char **)argv); 375 else 376 if (debug) 377 __pam_log(LOG_AUTH | LOG_DEBUG, 378 "PAM-KRB5 (acct): PAM_USER %s" 379 "does not match user %s from pam_get_data()", 380 user, (char *)userdata); 381 } 382 383 /* get krb5 module data */ 384 if ((err = pam_get_data(pamh, KRB5_DATA, (const void **)&kmd)) 385 != PAM_SUCCESS) { 386 if (err == PAM_NO_MODULE_DATA) { 387 /* 388 * pam_auth never called (possible config 389 * error; no pam_krb5 auth entry in pam.conf), 390 */ 391 if (debug) { 392 __pam_log(LOG_AUTH | LOG_DEBUG, 393 "PAM-KRB5 (acct): no module data"); 394 } 395 err = PAM_IGNORE; 396 goto out; 397 } else { 398 __pam_log(LOG_AUTH | LOG_ERR, 399 "PAM-KRB5 (acct): get module" 400 " data failed: err=%d", 401 err); 402 } 403 goto out; 404 } 405 406 debug = debug || kmd->debug; 407 408 /* 409 * auth mod set status to ignore, most likely cuz root key is 410 * in keytab, so skip other checks and return ignore 411 */ 412 if (kmd->auth_status == PAM_IGNORE) { 413 if (debug) 414 __pam_log(LOG_AUTH | LOG_DEBUG, 415 "PAM-KRB5 (acct): kmd auth_status is IGNORE"); 416 err = PAM_IGNORE; 417 goto out; 418 } 419 420 /* 421 * If there is no Kerberos related user and there is authentication 422 * data, this means that while the user has successfully passed 423 * authentication, Kerberos is not the account authority because there 424 * is no valid Kerberos principal. PAM_IGNORE is returned since 425 * Kerberos is not authoritative for this user. Other modules in the 426 * account stack will need to determine the success or failure for this 427 * user. 428 */ 429 if (kmd->auth_status == PAM_USER_UNKNOWN) { 430 if (debug) 431 syslog(LOG_DEBUG, 432 "PAM-KRB5 (acct): kmd auth_status is USER UNKNOWN"); 433 err = PAM_IGNORE; 434 goto out; 435 } 436 437 /* 438 * age_status will be set to PAM_NEW_AUTHTOK_REQD in pam_krb5's 439 * 'auth' if the user's key/pw has expired and needs to be changed 440 */ 441 if (kmd->age_status == PAM_NEW_AUTHTOK_REQD) { 442 if (!nowarn) { 443 (void) snprintf(messages[0], sizeof (messages[0]), 444 dgettext(TEXT_DOMAIN, 445 "Your Kerberos password has expired.\n")); 446 (void) __pam_display_msg(pamh, PAM_TEXT_INFO, 447 1, messages, NULL); 448 } 449 err = PAM_NEW_AUTHTOK_REQD; 450 goto out; 451 } 452 453 if (kmd->auth_status == PAM_SUCCESS && !(flags & PAM_SILENT) && 454 !nowarn && kmd->password) { 455 /* if we fail, let it slide, it's only a warning brah */ 456 (void) exp_warn(pamh, user, kmd, debug); 457 } 458 459 /* 460 * If Kerberos is treated as optional in the PAM stack, it is possible 461 * that there is a KRB5_DATA item and a non-Kerberos account authority. 462 * In that case, PAM_IGNORE is returned. 463 */ 464 err = kmd->auth_status != PAM_SUCCESS ? PAM_IGNORE : kmd->auth_status; 465 466 out: 467 if (debug) 468 __pam_log(LOG_AUTH | LOG_DEBUG, 469 "PAM-KRB5 (acct): end: %s", pam_strerror(pamh, err)); 470 471 return (err); 472 } 473