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 2010 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 "krb5_repository.h" 44 45 extern int attempt_krb5_auth(pam_handle_t *, krb5_module_data_t *, const char *, 46 char **, boolean_t); 47 extern int krb5_verifypw(const char *, char *, int); 48 49 static void display_msg(pam_handle_t *, int, char *); 50 static void display_msgs(pam_handle_t *, int, int, 51 char msgs[][PAM_MAX_MSG_SIZE]); 52 static int krb5_changepw(pam_handle_t *, const char *, char *, char *, int); 53 54 /* 55 * set_ccname() 56 * 57 * set KRB5CCNAME shell var 58 */ 59 static void 60 set_ccname( 61 pam_handle_t *pamh, 62 krb5_module_data_t *kmd, 63 int login_result, 64 int debug) 65 { 66 int result; 67 68 if (debug) 69 __pam_log(LOG_AUTH | LOG_DEBUG, 70 "PAM-KRB5 (password): password: finalize" 71 " ccname env, login_result =%d, env ='%s'", 72 login_result, kmd->env ? kmd->env : "<null>"); 73 74 if (kmd->env) { 75 76 if (login_result == PAM_SUCCESS) { 77 /* 78 * Put ccname into the pamh so that login 79 * apps can pick this up when they run 80 * pam_getenvlist(). 81 */ 82 if ((result = pam_putenv(pamh, kmd->env)) 83 != PAM_SUCCESS) { 84 /* should not happen but... */ 85 __pam_log(LOG_AUTH | LOG_ERR, 86 "PAM-KRB5 (password):" 87 " pam_putenv failed: result: %d", 88 result); 89 goto cleanupccname; 90 } 91 } else { 92 cleanupccname: 93 /* for lack of a Solaris unputenv() */ 94 krb5_unsetenv(KRB5_ENV_CCNAME); 95 free(kmd->env); 96 kmd->env = NULL; 97 } 98 } 99 } 100 101 /* 102 * get_set_creds() 103 * 104 * do a krb5 login to get and set krb5 creds (needed after a pw change 105 * on pw expire on login) 106 */ 107 static void 108 get_set_creds( 109 pam_handle_t *pamh, 110 krb5_module_data_t *kmd, 111 const char *user, 112 char *newpass, 113 int debug) 114 { 115 int login_result; 116 117 if (!kmd || kmd->age_status != PAM_NEW_AUTHTOK_REQD) 118 return; 119 120 /* 121 * if pw has expired, get/set krb5 creds ala auth mod 122 * 123 * pwchange verified user sufficiently, so don't request strict 124 * tgt verification (will cause rcache perm issues possibly anyways) 125 */ 126 login_result = attempt_krb5_auth(pamh, kmd, user, &newpass, 0); 127 if (debug) 128 __pam_log(LOG_AUTH | LOG_DEBUG, 129 "PAM-KRB5 (password): get_set_creds: login_result= %d", 130 login_result); 131 /* 132 * the krb5 login should not fail, but if so, 133 * warn the user they have to kinit(1) 134 */ 135 if (login_result != PAM_SUCCESS) { 136 display_msg(pamh, PAM_TEXT_INFO, 137 dgettext(TEXT_DOMAIN, 138 "Warning: " 139 "Could not cache Kerberos" 140 " credentials, please run " 141 "kinit(1) or re-login\n")); 142 } 143 set_ccname(pamh, kmd, login_result, debug); 144 } 145 /* 146 * This is the PAM Kerberos Password Change module 147 * 148 */ 149 150 int 151 pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv) 152 { 153 154 const char *user; 155 int err, result = PAM_AUTHTOK_ERR; 156 char *newpass = NULL; 157 char *oldpass = NULL; 158 int i; 159 int debug = 0; 160 uid_t pw_uid; 161 krb5_module_data_t *kmd = NULL; 162 const pam_repository_t *rep_data = NULL; 163 164 for (i = 0; i < argc; i++) { 165 if (strcmp(argv[i], "debug") == 0) 166 debug = 1; 167 else 168 __pam_log(LOG_AUTH | LOG_ERR, 169 "PAM-KRB5 (password): illegal option %s", 170 argv[i]); 171 } 172 173 if (debug) 174 __pam_log(LOG_AUTH | LOG_DEBUG, 175 "PAM-KRB5 (password): start: flags = %x", 176 flags); 177 178 (void) pam_get_item(pamh, PAM_REPOSITORY, (const void **)&rep_data); 179 180 if (rep_data != NULL) { 181 if (strcmp(rep_data->type, KRB5_REPOSITORY_NAME) != 0) { 182 if (debug) 183 __pam_log(LOG_AUTH | LOG_DEBUG, 184 "PAM-KRB5 (auth): wrong" 185 "repository found (%s), returning " 186 "PAM_IGNORE", rep_data->type); 187 return (PAM_IGNORE); 188 } 189 } 190 191 if (flags & PAM_PRELIM_CHECK) { 192 /* Nothing to do here */ 193 if (debug) 194 __pam_log(LOG_AUTH | LOG_DEBUG, 195 "PAM-KRB5 (password): prelim check"); 196 return (PAM_IGNORE); 197 } 198 199 /* make sure PAM framework is telling us to update passwords */ 200 if (!(flags & PAM_UPDATE_AUTHTOK)) { 201 __pam_log(LOG_AUTH | LOG_ERR, 202 "PAM-KRB5 (password): bad flags: %d", 203 flags); 204 return (PAM_SYSTEM_ERR); 205 } 206 207 208 if ((err = pam_get_data(pamh, KRB5_DATA, (const void **)&kmd)) 209 != PAM_SUCCESS) { 210 if (debug) 211 __pam_log(LOG_AUTH | LOG_DEBUG, 212 "PAM-KRB5 (password): get mod data failed %d", 213 err); 214 kmd = NULL; 215 } 216 217 if (flags & PAM_CHANGE_EXPIRED_AUTHTOK) { 218 /* let's make sure we know the krb5 pw has expired */ 219 220 if (debug) 221 __pam_log(LOG_AUTH | LOG_DEBUG, 222 "PAM-KRB5 (password): kmd age status %d", 223 kmd ? kmd->age_status : -99); 224 225 if (!kmd || kmd->age_status != PAM_NEW_AUTHTOK_REQD) 226 return (PAM_IGNORE); 227 } 228 229 (void) pam_get_item(pamh, PAM_USER, (const void **)&user); 230 231 if (user == NULL || *user == '\0') { 232 __pam_log(LOG_AUTH | LOG_ERR, 233 "PAM-KRB5 (password): username is empty"); 234 return (PAM_USER_UNKNOWN); 235 } 236 237 if (!get_pw_uid(user, &pw_uid)) { 238 __pam_log(LOG_AUTH | LOG_ERR, 239 "PAM-KRB5 (password): can't get uid for %s", user); 240 return (PAM_USER_UNKNOWN); 241 } 242 243 /* 244 * if root key exists in the keytab, it's a random key so no 245 * need to prompt for pw and we just return IGNORE 246 */ 247 if ((strcmp(user, ROOT_UNAME) == 0) && 248 key_in_keytab(user, debug)) { 249 if (debug) 250 __pam_log(LOG_AUTH | LOG_DEBUG, 251 "PAM-KRB5 (password): " 252 "key for '%s' in keytab, returning IGNORE", user); 253 result = PAM_IGNORE; 254 goto out; 255 } 256 257 (void) pam_get_item(pamh, PAM_AUTHTOK, (const void **)&newpass); 258 259 /* 260 * If the preauth type done didn't use a passwd just ignore the error. 261 */ 262 if (newpass == NULL) 263 if (kmd && kmd->preauth_type == KRB_PKINIT) 264 return (PAM_IGNORE); 265 else 266 return (PAM_SYSTEM_ERR); 267 268 (void) pam_get_item(pamh, PAM_OLDAUTHTOK, (const void **)&oldpass); 269 270 if (oldpass == NULL) 271 if (kmd && kmd->preauth_type == KRB_PKINIT) 272 return (PAM_IGNORE); 273 else 274 return (PAM_SYSTEM_ERR); 275 276 result = krb5_verifypw(user, oldpass, debug); 277 if (debug) 278 __pam_log(LOG_AUTH | LOG_DEBUG, 279 "PAM-KRB5 (password): verifypw %d", result); 280 281 /* 282 * If it's a bad password or general failure, we are done. 283 */ 284 if (result != 0) { 285 /* 286 * if the preauth type done didn't use a passwd just ignore the 287 * error. 288 */ 289 if (kmd && kmd->preauth_type == KRB_PKINIT) 290 return (PAM_IGNORE); 291 292 if (result == 2) 293 display_msg(pamh, PAM_ERROR_MSG, dgettext(TEXT_DOMAIN, 294 "Old Kerberos password incorrect\n")); 295 return (PAM_AUTHTOK_ERR); 296 } 297 298 /* 299 * If the old password verifies try to change it regardless of the 300 * preauth type and do not ignore the error. 301 */ 302 result = krb5_changepw(pamh, user, oldpass, newpass, debug); 303 if (result == PAM_SUCCESS) { 304 display_msg(pamh, PAM_TEXT_INFO, dgettext(TEXT_DOMAIN, 305 "Kerberos password successfully changed\n")); 306 307 get_set_creds(pamh, kmd, user, newpass, debug); 308 } 309 310 out: 311 if (debug) 312 __pam_log(LOG_AUTH | LOG_DEBUG, 313 "PAM-KRB5 (password): out: returns %d", 314 result); 315 316 return (result); 317 } 318 319 int 320 krb5_verifypw( 321 const char *princ_str, 322 char *old_password, 323 int debug) 324 { 325 kadm5_ret_t code; 326 krb5_principal princ = 0; 327 char admin_realm[1024]; 328 char kprinc[2*MAXHOSTNAMELEN]; 329 char *cpw_service; 330 void *server_handle; 331 krb5_context context; 332 kadm5_config_params params; 333 334 (void) memset((char *)¶ms, 0, sizeof (params)); 335 336 if (code = krb5_init_secure_context(&context)) { 337 return (6); 338 } 339 340 if ((code = get_kmd_kuser(context, princ_str, kprinc, 341 2*MAXHOSTNAMELEN)) != 0) { 342 return (code); 343 } 344 345 /* Need to get a krb5_principal struct */ 346 347 code = krb5_parse_name(context, kprinc, &princ); 348 349 if (code != 0) 350 return (6); 351 352 if (strlen(old_password) == 0) { 353 krb5_free_principal(context, princ); 354 return (5); 355 } 356 357 (void) strlcpy(admin_realm, 358 krb5_princ_realm(context, princ)->data, 359 sizeof (admin_realm)); 360 361 params.mask |= KADM5_CONFIG_REALM; 362 params.realm = admin_realm; 363 364 365 if (kadm5_get_cpw_host_srv_name(context, admin_realm, &cpw_service)) { 366 __pam_log(LOG_AUTH | LOG_ERR, 367 "PAM-KRB5 (password): unable to get host based " 368 "service name for realm %s\n", 369 admin_realm); 370 krb5_free_principal(context, princ); 371 return (3); 372 } 373 374 code = kadm5_init_with_password(kprinc, old_password, cpw_service, 375 ¶ms, KADM5_STRUCT_VERSION, 376 KADM5_API_VERSION_2, NULL, 377 &server_handle); 378 if (code != 0) { 379 if (debug) 380 __pam_log(LOG_AUTH | LOG_DEBUG, 381 "PAM-KRB5: krb5_verifypw: init_with_pw" 382 " failed: (%s)", error_message(code)); 383 krb5_free_principal(context, princ); 384 return ((code == KADM5_BAD_PASSWORD) ? 2 : 3); 385 } 386 387 krb5_free_principal(context, princ); 388 389 (void) kadm5_destroy(server_handle); 390 391 return (0); 392 } 393 394 /* 395 * Function: krb5_changepw 396 * 397 * Purpose: Initialize and call lower level routines to change a password 398 * 399 * Arguments: 400 * 401 * princ_str principal name to use, optional 402 * old_password old password 403 * new_password new password 404 * 405 * Returns: 406 * exit status of PAM_SUCCESS for success 407 * else returns PAM failure 408 * 409 * Requires: 410 * Passwords cannot be more than 255 characters long. 411 * 412 * Modifies: 413 * 414 * Changes the principal's password. 415 * 416 */ 417 static int 418 krb5_changepw( 419 pam_handle_t *pamh, 420 const char *princ_str, 421 char *old_password, 422 char *new_password, 423 int debug) 424 { 425 kadm5_ret_t code; 426 krb5_principal princ = 0; 427 char msg_ret[1024], admin_realm[1024]; 428 char kprinc[2*MAXHOSTNAMELEN]; 429 char *cpw_service; 430 void *server_handle; 431 krb5_context context; 432 kadm5_config_params params; 433 434 (void) memset((char *)¶ms, 0, sizeof (params)); 435 436 if (krb5_init_secure_context(&context) != 0) 437 return (PAM_SYSTEM_ERR); 438 439 if ((code = get_kmd_kuser(context, princ_str, kprinc, 440 2*MAXHOSTNAMELEN)) != 0) { 441 return (code); 442 } 443 444 /* Need to get a krb5_principal struct */ 445 446 code = krb5_parse_name(context, kprinc, &princ); 447 if (code != 0) 448 return (PAM_SYSTEM_ERR); 449 450 if (strlen(old_password) == 0) { 451 krb5_free_principal(context, princ); 452 return (PAM_AUTHTOK_ERR); 453 } 454 455 (void) snprintf(admin_realm, sizeof (admin_realm), "%s", 456 krb5_princ_realm(context, princ)->data); 457 params.mask |= KADM5_CONFIG_REALM; 458 params.realm = admin_realm; 459 460 461 if (kadm5_get_cpw_host_srv_name(context, admin_realm, &cpw_service)) { 462 __pam_log(LOG_AUTH | LOG_ERR, 463 "PAM-KRB5 (password):unable to get host based " 464 "service name for realm %s\n", 465 admin_realm); 466 return (PAM_SYSTEM_ERR); 467 } 468 469 code = kadm5_init_with_password(kprinc, old_password, cpw_service, 470 ¶ms, KADM5_STRUCT_VERSION, 471 KADM5_API_VERSION_2, NULL, 472 &server_handle); 473 free(cpw_service); 474 if (code != 0) { 475 if (debug) 476 __pam_log(LOG_AUTH | LOG_DEBUG, 477 "PAM-KRB5 (password): changepw: " 478 "init_with_pw failed: (%s)", error_message(code)); 479 krb5_free_principal(context, princ); 480 return ((code == KADM5_BAD_PASSWORD) ? 481 PAM_AUTHTOK_ERR : PAM_SYSTEM_ERR); 482 } 483 484 code = kadm5_chpass_principal_util(server_handle, princ, 485 new_password, 486 NULL /* don't need pw back */, 487 msg_ret, 488 sizeof (msg_ret)); 489 490 if (code) { 491 char msgs[2][PAM_MAX_MSG_SIZE]; 492 493 (void) snprintf(msgs[0], PAM_MAX_MSG_SIZE, "%s", 494 dgettext(TEXT_DOMAIN, 495 "Kerberos password not changed: ")); 496 (void) snprintf(msgs[1], PAM_MAX_MSG_SIZE, "%s", msg_ret); 497 498 display_msgs(pamh, PAM_ERROR_MSG, 2, msgs); 499 } 500 501 krb5_free_principal(context, princ); 502 503 (void) kadm5_destroy(server_handle); 504 505 if (debug) 506 __pam_log(LOG_AUTH | LOG_DEBUG, 507 "PAM-KRB5 (password): changepw: end %d", code); 508 509 if (code != 0) 510 return (PAM_AUTHTOK_ERR); 511 512 return (PAM_SUCCESS); 513 } 514 515 static void 516 display_msgs(pam_handle_t *pamh, 517 int msg_style, int nmsg, char msgs[][PAM_MAX_MSG_SIZE]) 518 { 519 (void) __pam_display_msg(pamh, msg_style, nmsg, msgs, NULL); 520 } 521 522 523 static void 524 display_msg(pam_handle_t *pamh, int msg_style, char *msg) 525 { 526 char pam_msg[1][PAM_MAX_MSG_SIZE]; 527 528 (void) snprintf(pam_msg[0], PAM_MAX_MSG_SIZE, "%s", msg); 529 display_msgs(pamh, msg_style, 1, pam_msg); 530 } 531