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 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(krb5_module_data_t *, char *, char **, 44 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(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 if (newpass == NULL) 262 return (PAM_SYSTEM_ERR); 263 264 (void) pam_get_item(pamh, PAM_OLDAUTHTOK, (void **)&oldpass); 265 266 if (oldpass == NULL) 267 return (PAM_SYSTEM_ERR); 268 269 result = krb5_verifypw(user, oldpass, debug); 270 if (debug) 271 __pam_log(LOG_AUTH | LOG_DEBUG, 272 "PAM-KRB5 (password): verifypw %d", result); 273 274 /* 275 * If it's a bad password or general failure, we are done. 276 */ 277 if (result != 0) { 278 if (result == 2) 279 display_msg(pamh, PAM_ERROR_MSG, dgettext(TEXT_DOMAIN, 280 "Old Kerberos password incorrect\n")); 281 return (PAM_AUTHTOK_ERR); 282 } 283 284 result = krb5_changepw(pamh, user, oldpass, newpass, debug); 285 if (result == PAM_SUCCESS) { 286 display_msg(pamh, PAM_TEXT_INFO, dgettext(TEXT_DOMAIN, 287 "Kerberos password successfully changed\n")); 288 289 get_set_creds(pamh, kmd, user, newpass, debug); 290 } 291 292 out: 293 if (debug) 294 __pam_log(LOG_AUTH | LOG_DEBUG, 295 "PAM-KRB5 (password): out: returns %d", 296 result); 297 298 return (result); 299 } 300 301 int 302 krb5_verifypw( 303 char *princ_str, 304 char *old_password, 305 int debug) 306 { 307 kadm5_ret_t code; 308 krb5_principal princ = 0; 309 char admin_realm[1024]; 310 char kprinc[2*MAXHOSTNAMELEN]; 311 char *cpw_service; 312 void *server_handle; 313 krb5_context context; 314 kadm5_config_params params; 315 316 (void) memset((char *)¶ms, 0, sizeof (params)); 317 318 if (code = krb5_init_secure_context(&context)) { 319 return (6); 320 } 321 322 if ((code = get_kmd_kuser(context, (const char *)princ_str, kprinc, 323 2*MAXHOSTNAMELEN)) != 0) { 324 return (code); 325 } 326 327 /* Need to get a krb5_principal struct */ 328 329 code = krb5_parse_name(context, kprinc, &princ); 330 331 if (code != 0) 332 return (6); 333 334 if (strlen(old_password) == 0) { 335 krb5_free_principal(context, princ); 336 return (5); 337 } 338 339 (void) strlcpy(admin_realm, 340 krb5_princ_realm(context, princ)->data, 341 sizeof (admin_realm)); 342 343 params.mask |= KADM5_CONFIG_REALM; 344 params.realm = admin_realm; 345 346 347 if (kadm5_get_cpw_host_srv_name(context, admin_realm, &cpw_service)) { 348 __pam_log(LOG_AUTH | LOG_ERR, 349 "PAM-KRB5 (password): unable to get host based " 350 "service name for realm %s\n", 351 admin_realm); 352 krb5_free_principal(context, princ); 353 return (3); 354 } 355 356 code = kadm5_init_with_password(kprinc, old_password, cpw_service, 357 ¶ms, KADM5_STRUCT_VERSION, 358 KADM5_API_VERSION_2, NULL, 359 &server_handle); 360 if (code != 0) { 361 if (debug) 362 __pam_log(LOG_AUTH | LOG_DEBUG, 363 "PAM-KRB5: krb5_verifypw: init_with_pw" 364 " failed: (%s)", error_message(code)); 365 krb5_free_principal(context, princ); 366 return ((code == KADM5_BAD_PASSWORD) ? 2 : 3); 367 } 368 369 krb5_free_principal(context, princ); 370 371 (void) kadm5_destroy(server_handle); 372 373 return (0); 374 } 375 376 /* 377 * Function: krb5_changepw 378 * 379 * Purpose: Initialize and call lower level routines to change a password 380 * 381 * Arguments: 382 * 383 * princ_str principal name to use, optional 384 * old_password old password 385 * new_password new password 386 * 387 * Returns: 388 * exit status of PAM_SUCCESS for success 389 * else returns PAM failure 390 * 391 * Requires: 392 * Passwords cannot be more than 255 characters long. 393 * 394 * Modifies: 395 * 396 * Changes the principal's password. 397 * 398 */ 399 static int 400 krb5_changepw( 401 pam_handle_t *pamh, 402 char *princ_str, 403 char *old_password, 404 char *new_password, 405 int debug) 406 { 407 kadm5_ret_t code; 408 krb5_principal princ = 0; 409 char msg_ret[1024], admin_realm[1024]; 410 char kprinc[2*MAXHOSTNAMELEN]; 411 char *cpw_service; 412 void *server_handle; 413 krb5_context context; 414 kadm5_config_params params; 415 416 (void) memset((char *)¶ms, 0, sizeof (params)); 417 418 if (krb5_init_secure_context(&context) != 0) 419 return (PAM_SYSTEM_ERR); 420 421 if ((code = get_kmd_kuser(context, (const char *)princ_str, kprinc, 422 2*MAXHOSTNAMELEN)) != 0) { 423 return (code); 424 } 425 426 /* Need to get a krb5_principal struct */ 427 428 code = krb5_parse_name(context, kprinc, &princ); 429 if (code != 0) 430 return (PAM_SYSTEM_ERR); 431 432 if (strlen(old_password) == 0) { 433 krb5_free_principal(context, princ); 434 return (PAM_AUTHTOK_ERR); 435 } 436 437 (void) snprintf(admin_realm, sizeof (admin_realm), "%s", 438 krb5_princ_realm(context, princ)->data); 439 params.mask |= KADM5_CONFIG_REALM; 440 params.realm = admin_realm; 441 442 443 if (kadm5_get_cpw_host_srv_name(context, admin_realm, &cpw_service)) { 444 __pam_log(LOG_AUTH | LOG_ERR, 445 "PAM-KRB5 (password):unable to get host based " 446 "service name for realm %s\n", 447 admin_realm); 448 return (PAM_SYSTEM_ERR); 449 } 450 451 code = kadm5_init_with_password(kprinc, old_password, cpw_service, 452 ¶ms, KADM5_STRUCT_VERSION, 453 KADM5_API_VERSION_2, NULL, 454 &server_handle); 455 free(cpw_service); 456 if (code != 0) { 457 if (debug) 458 __pam_log(LOG_AUTH | LOG_DEBUG, 459 "PAM-KRB5 (password): changepw: " 460 "init_with_pw failed: (%s)", error_message(code)); 461 krb5_free_principal(context, princ); 462 return ((code == KADM5_BAD_PASSWORD) ? 463 PAM_AUTHTOK_ERR : PAM_SYSTEM_ERR); 464 } 465 466 code = kadm5_chpass_principal_util(server_handle, princ, 467 new_password, 468 NULL /* don't need pw back */, 469 msg_ret, 470 sizeof (msg_ret)); 471 472 if (code) { 473 char msgs[2][PAM_MAX_MSG_SIZE]; 474 475 (void) snprintf(msgs[0], PAM_MAX_MSG_SIZE, "%s", 476 dgettext(TEXT_DOMAIN, 477 "Kerberos password not changed: ")); 478 (void) snprintf(msgs[1], PAM_MAX_MSG_SIZE, "%s", msg_ret); 479 480 display_msgs(pamh, PAM_ERROR_MSG, 2, msgs); 481 } 482 483 krb5_free_principal(context, princ); 484 485 (void) kadm5_destroy(server_handle); 486 487 if (debug) 488 __pam_log(LOG_AUTH | LOG_DEBUG, 489 "PAM-KRB5 (password): changepw: end %d", code); 490 491 if (code != 0) 492 return (PAM_AUTHTOK_ERR); 493 494 return (PAM_SUCCESS); 495 } 496 497 static void 498 display_msgs(pam_handle_t *pamh, 499 int msg_style, int nmsg, char msgs[][PAM_MAX_MSG_SIZE]) 500 { 501 (void) __pam_display_msg(pamh, msg_style, nmsg, msgs, NULL); 502 } 503 504 505 static void 506 display_msg(pam_handle_t *pamh, int msg_style, char *msg) 507 { 508 char pam_msg[1][PAM_MAX_MSG_SIZE]; 509 510 (void) snprintf(pam_msg[0], PAM_MAX_MSG_SIZE, "%s", msg); 511 display_msgs(pamh, msg_style, 1, pam_msg); 512 } 513