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 * Copyright 2023 OmniOS Community Edition (OmniOSce) Association. 26 */ 27 28 #include <kadm5/admin.h> 29 #include <krb5.h> 30 31 #include <security/pam_appl.h> 32 #include <security/pam_modules.h> 33 #include <security/pam_impl.h> 34 #include <syslog.h> 35 #include <string.h> 36 #include <stdio.h> 37 #include <stdlib.h> 38 #include <sys/types.h> 39 #include <pwd.h> 40 #include <libintl.h> 41 #include <netdb.h> 42 #include "utils.h" 43 #include <shadow.h> 44 45 #include "krb5_repository.h" 46 47 #define KRB5_AUTOMIGRATE_DATA "SUNW-KRB5-AUTOMIGRATE-DATA" 48 49 #define min(a, b) ((a) < (b) ? (a) : (b)) 50 51 /* 52 * pam_sm_acct_mgmt main account managment routine. 53 */ 54 55 static int 56 fetch_princ_entry( 57 krb5_module_data_t *kmd, 58 const char *princ_str, 59 kadm5_principal_ent_rec *prent, /* out */ 60 int debug) 61 62 { 63 kadm5_ret_t code; 64 krb5_principal princ = 0; 65 char admin_realm[1024]; 66 char kprinc[2*MAXHOSTNAMELEN]; 67 char *cpw_service, *password; 68 void *server_handle; 69 krb5_context context; 70 kadm5_config_params params; 71 72 password = kmd->password; 73 context = kmd->kcontext; 74 75 if ((code = get_kmd_kuser(context, princ_str, 76 kprinc, 2 * MAXHOSTNAMELEN)) != 0) { 77 return (code); 78 } 79 80 code = krb5_parse_name(context, kprinc, &princ); 81 if (code != 0) { 82 return (PAM_SYSTEM_ERR); 83 } 84 85 if (strlen(password) == 0) { 86 krb5_free_principal(context, princ); 87 if (debug) 88 __pam_log(LOG_AUTH | LOG_DEBUG, 89 "PAM-KRB5 (acct): fetch_princ_entry: pwlen=0"); 90 return (PAM_AUTH_ERR); 91 } 92 93 (void) strlcpy(admin_realm, 94 krb5_princ_realm(context, princ)->data, 95 sizeof (admin_realm)); 96 97 (void) memset((char *)¶ms, 0, sizeof (params)); 98 params.mask |= KADM5_CONFIG_REALM; 99 params.realm = admin_realm; 100 101 if (kadm5_get_cpw_host_srv_name(context, admin_realm, &cpw_service)) { 102 __pam_log(LOG_AUTH | LOG_ERR, 103 "PAM-KRB5 (acct): unable to get host based " 104 "service name for realm '%s'", 105 admin_realm); 106 krb5_free_principal(context, princ); 107 return (PAM_SYSTEM_ERR); 108 } 109 110 code = kadm5_init_with_password(kprinc, password, cpw_service, 111 ¶ms, KADM5_STRUCT_VERSION, 112 KADM5_API_VERSION_2, NULL, 113 &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 const 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_secure_context, then just set error 194 * code, 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_secure_context(&kmd->kcontext)) { 199 err = PAM_SYSTEM_ERR; 200 if (debug) 201 __pam_log(LOG_AUTH | LOG_ERR, "PAM-KRB5 (acct): " 202 "krb5_init_secure_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(pam_handle_t *pamh, int flags, int argc, const char **argv) 305 { 306 const char *user = NULL; 307 char *userdata = NULL; 308 int err; 309 int i; 310 krb5_module_data_t *kmd = NULL; 311 char messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE]; 312 int debug = 0; /* pam.conf entry option */ 313 int nowarn = 0; /* pam.conf entry option, no expire warnings */ 314 const pam_repository_t *rep_data = NULL; 315 316 for (i = 0; i < argc; i++) { 317 if (strcasecmp(argv[i], "debug") == 0) 318 debug = 1; 319 else if (strcasecmp(argv[i], "nowarn") == 0) { 320 nowarn = 1; 321 flags = flags | PAM_SILENT; 322 } else { 323 __pam_log(LOG_AUTH | LOG_ERR, 324 "PAM-KRB5 (acct): illegal option %s", 325 argv[i]); 326 } 327 } 328 329 if (debug) 330 __pam_log(LOG_AUTH | LOG_DEBUG, 331 "PAM-KRB5 (acct): debug=%d, nowarn=%d", 332 debug, nowarn); 333 334 (void) pam_get_item(pamh, PAM_REPOSITORY, (const void **)&rep_data); 335 336 if (rep_data != NULL) { 337 /* 338 * If the repository is not ours, 339 * return PAM_IGNORE. 340 */ 341 if (strcmp(rep_data->type, KRB5_REPOSITORY_NAME) != 0) { 342 if (debug) 343 __pam_log(LOG_AUTH | LOG_DEBUG, 344 "PAM-KRB5 (acct): wrong" 345 "repository found (%s), returning " 346 "PAM_IGNORE", rep_data->type); 347 return (PAM_IGNORE); 348 } 349 } 350 351 352 /* get user name */ 353 (void) pam_get_item(pamh, PAM_USER, (const void **)&user); 354 355 if (user == NULL || *user == '\0') { 356 err = PAM_USER_UNKNOWN; 357 goto out; 358 } 359 360 /* get pam_krb5_migrate specific data */ 361 err = pam_get_data(pamh, KRB5_AUTOMIGRATE_DATA, 362 (const void **)&userdata); 363 if (err != PAM_SUCCESS) { 364 if (debug) 365 __pam_log(LOG_AUTH | LOG_DEBUG, "PAM-KRB5 (acct): " 366 "no module data for KRB5_AUTOMIGRATE_DATA"); 367 } else { 368 /* 369 * We try and reauthenticate, since this user has a 370 * newly created krb5 principal via the pam_krb5_migrate 371 * auth module. That way, this new user will have fresh 372 * creds (assuming pam_sm_authenticate() succeeds). 373 */ 374 if (strcmp(user, userdata) == 0) 375 (void) pam_sm_authenticate(pamh, flags, argc, argv); 376 else 377 if (debug) 378 __pam_log(LOG_AUTH | LOG_DEBUG, 379 "PAM-KRB5 (acct): PAM_USER %s" 380 "does not match user %s from pam_get_data()", 381 user, (char *)userdata); 382 } 383 384 /* get krb5 module data */ 385 if ((err = pam_get_data(pamh, KRB5_DATA, (const void **)&kmd)) 386 != PAM_SUCCESS) { 387 if (err == PAM_NO_MODULE_DATA) { 388 /* 389 * pam_auth never called (possible config 390 * error; no pam_krb5 auth entry in pam.conf), 391 */ 392 if (debug) { 393 __pam_log(LOG_AUTH | LOG_DEBUG, 394 "PAM-KRB5 (acct): no module data"); 395 } 396 err = PAM_IGNORE; 397 goto out; 398 } else { 399 __pam_log(LOG_AUTH | LOG_ERR, 400 "PAM-KRB5 (acct): get module" 401 " data failed: err=%d", 402 err); 403 } 404 goto out; 405 } 406 407 debug = debug || kmd->debug; 408 409 /* 410 * auth mod set status to ignore, most likely cuz root key is 411 * in keytab, so skip other checks and return ignore 412 */ 413 if (kmd->auth_status == PAM_IGNORE) { 414 if (debug) 415 __pam_log(LOG_AUTH | LOG_DEBUG, 416 "PAM-KRB5 (acct): kmd auth_status is IGNORE"); 417 err = PAM_IGNORE; 418 goto out; 419 } 420 421 /* 422 * If there is no Kerberos related user and there is authentication 423 * data, this means that while the user has successfully passed 424 * authentication, Kerberos is not the account authority because there 425 * is no valid Kerberos principal. PAM_IGNORE is returned since 426 * Kerberos is not authoritative for this user. Other modules in the 427 * account stack will need to determine the success or failure for this 428 * user. 429 */ 430 if (kmd->auth_status == PAM_USER_UNKNOWN) { 431 if (debug) 432 syslog(LOG_DEBUG, 433 "PAM-KRB5 (acct): kmd auth_status is USER UNKNOWN"); 434 err = PAM_IGNORE; 435 goto out; 436 } 437 438 /* 439 * age_status will be set to PAM_NEW_AUTHTOK_REQD in pam_krb5's 440 * 'auth' if the user's key/pw has expired and needs to be changed 441 */ 442 if (kmd->age_status == PAM_NEW_AUTHTOK_REQD) { 443 if (!nowarn) { 444 (void) snprintf(messages[0], sizeof (messages[0]), 445 dgettext(TEXT_DOMAIN, 446 "Your Kerberos password has expired.\n")); 447 (void) __pam_display_msg(pamh, PAM_TEXT_INFO, 448 1, messages, NULL); 449 } 450 err = PAM_NEW_AUTHTOK_REQD; 451 goto out; 452 } 453 454 if (kmd->auth_status == PAM_SUCCESS && !(flags & PAM_SILENT) && 455 !nowarn && kmd->password) { 456 /* if we fail, let it slide, it's only a warning brah */ 457 (void) exp_warn(pamh, user, kmd, debug); 458 } 459 460 /* 461 * If Kerberos is treated as optional in the PAM stack, it is possible 462 * that there is a KRB5_DATA item and a non-Kerberos account authority. 463 * In that case, PAM_IGNORE is returned. 464 */ 465 err = kmd->auth_status != PAM_SUCCESS ? PAM_IGNORE : kmd->auth_status; 466 467 out: 468 if (debug) 469 __pam_log(LOG_AUTH | LOG_DEBUG, 470 "PAM-KRB5 (acct): end: %s", pam_strerror(pamh, err)); 471 472 return (err); 473 } 474