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