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 2006 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 #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(krb5_module_data_t *, char *, char **, 46 boolean_t); 47 extern int krb5_verifypw(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 *, 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 syslog(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 syslog(LOG_ERR, 86 dgettext(TEXT_DOMAIN, 87 "PAM-KRB5 (password):" 88 " pam_putenv failed: result: %d"), 89 result); 90 goto cleanupccname; 91 } 92 } else { 93 cleanupccname: 94 /* for lack of a Solaris unputenv() */ 95 krb5_unsetenv(KRB5_ENV_CCNAME); 96 free(kmd->env); 97 kmd->env = NULL; 98 } 99 } 100 } 101 102 /* 103 * get_set_creds() 104 * 105 * do a krb5 login to get and set krb5 creds (needed after a pw change 106 * on pw expire on login) 107 */ 108 static void 109 get_set_creds( 110 pam_handle_t *pamh, 111 krb5_module_data_t *kmd, 112 char *user, 113 char *newpass, 114 int debug) 115 { 116 int login_result; 117 118 if (!kmd || kmd->age_status != PAM_NEW_AUTHTOK_REQD) 119 return; 120 121 /* 122 * if pw has expired, get/set krb5 creds ala auth mod 123 * 124 * pwchange verified user sufficiently, so don't request strict 125 * tgt verification (will cause rcache perm issues possibly anyways) 126 */ 127 login_result = attempt_krb5_auth(kmd, user, &newpass, 0); 128 if (debug) 129 syslog(LOG_DEBUG, 130 "PAM-KRB5 (password): get_set_creds: login_result= %d", 131 login_result); 132 /* 133 * the krb5 login should not fail, but if so, 134 * warn the user they have to kinit(1) 135 */ 136 if (login_result != PAM_SUCCESS) { 137 display_msg(pamh, PAM_TEXT_INFO, 138 dgettext(TEXT_DOMAIN, 139 "Warning: " 140 "Could not cache Kerberos" 141 " credentials, please run " 142 "kinit(1) or re-login\n")); 143 } 144 set_ccname(pamh, kmd, login_result, debug); 145 } 146 /* 147 * This is the PAM Kerberos Password Change module 148 * 149 */ 150 151 int 152 pam_sm_chauthtok( 153 pam_handle_t *pamh, 154 int flags, 155 int argc, 156 const char **argv) 157 { 158 159 char *user; 160 int err, result = PAM_AUTHTOK_ERR; 161 char *newpass = NULL; 162 char *oldpass = NULL; 163 int i; 164 int debug = 0; 165 uid_t pw_uid; 166 krb5_module_data_t *kmd = NULL; 167 pam_repository_t *rep_data = NULL; 168 169 for (i = 0; i < argc; i++) { 170 if (strcmp(argv[i], "debug") == 0) 171 debug = 1; 172 else 173 syslog(LOG_ERR, 174 dgettext(TEXT_DOMAIN, 175 "PAM-KRB5 (password): illegal option %s"), 176 argv[i]); 177 } 178 179 if (debug) 180 syslog(LOG_DEBUG, 181 "PAM-KRB5 (password): start: flags = %x", 182 flags); 183 184 (void) pam_get_item(pamh, PAM_REPOSITORY, (void **)&rep_data); 185 186 if (rep_data != NULL) { 187 if (strcmp(rep_data->type, KRB5_REPOSITORY_NAME) != 0) { 188 if (debug) 189 syslog(LOG_DEBUG, "PAM-KRB5 (auth): wrong" 190 "repository found (%s), returning " 191 "PAM_IGNORE", rep_data->type); 192 return (PAM_IGNORE); 193 } 194 } 195 196 if (flags & PAM_PRELIM_CHECK) { 197 /* Nothing to do here */ 198 if (debug) 199 syslog(LOG_DEBUG, 200 "PAM-KRB5 (password): prelim check"); 201 return (PAM_IGNORE); 202 } 203 204 /* make sure PAM framework is telling us to update passwords */ 205 if (!(flags & PAM_UPDATE_AUTHTOK)) { 206 syslog(LOG_ERR, dgettext(TEXT_DOMAIN, 207 "PAM-KRB5 (password): bad flags: %d"), 208 flags); 209 return (PAM_SYSTEM_ERR); 210 } 211 212 213 if ((err = pam_get_data(pamh, KRB5_DATA, (const void **)&kmd)) 214 != PAM_SUCCESS) { 215 if (debug) 216 syslog(LOG_DEBUG, 217 "PAM-KRB5 (password): get mod data failed %d", 218 err); 219 kmd = NULL; 220 } 221 222 if (flags & PAM_CHANGE_EXPIRED_AUTHTOK) { 223 /* let's make sure we know the krb5 pw has expired */ 224 225 if (debug) 226 syslog(LOG_DEBUG, 227 "PAM-KRB5 (password): kmd age status %d", 228 kmd ? kmd->age_status : -99); 229 230 if (!kmd || kmd->age_status != PAM_NEW_AUTHTOK_REQD) 231 return (PAM_IGNORE); 232 } 233 234 (void) pam_get_item(pamh, PAM_USER, (void **)&user); 235 236 if (user == NULL || user == '\0') { 237 syslog(LOG_ERR, "PAM-KRB5 (password): username is empty"); 238 return (PAM_USER_UNKNOWN); 239 } 240 241 if (!get_pw_uid(user, &pw_uid)) { 242 syslog(LOG_ERR, 243 "PAM-KRB5 (password): can't get uid for %s", user); 244 return (PAM_USER_UNKNOWN); 245 } 246 247 /* 248 * if root key exists in the keytab, it's a random key so no 249 * need to prompt for pw and we just return IGNORE 250 */ 251 if ((strcmp(user, ROOT_UNAME) == 0) && 252 key_in_keytab(user, debug)) { 253 if (debug) 254 syslog(LOG_DEBUG, 255 "PAM-KRB5 (password): " 256 "key for '%s' in keytab, returning IGNORE", user); 257 result = PAM_IGNORE; 258 goto out; 259 } 260 261 (void) pam_get_item(pamh, PAM_AUTHTOK, (void **)&newpass); 262 263 if (newpass == NULL) 264 return (PAM_SYSTEM_ERR); 265 266 (void) pam_get_item(pamh, PAM_OLDAUTHTOK, (void **)&oldpass); 267 268 if (oldpass == NULL) 269 return (PAM_SYSTEM_ERR); 270 271 result = krb5_verifypw(user, oldpass, debug); 272 if (debug) 273 syslog(LOG_DEBUG, "PAM-KRB5 (password): verifypw %d", result); 274 275 /* 276 * If it's a bad password or general failure, we are done. 277 */ 278 if (result != 0) { 279 if (result == 2) 280 display_msg(pamh, PAM_ERROR_MSG, dgettext(TEXT_DOMAIN, 281 "Old Kerberos password incorrect\n")); 282 return (PAM_AUTHTOK_ERR); 283 } 284 285 result = krb5_changepw(pamh, user, oldpass, newpass, debug); 286 if (result == PAM_SUCCESS) { 287 display_msg(pamh, PAM_TEXT_INFO, dgettext(TEXT_DOMAIN, 288 "Kerberos password successfully changed\n")); 289 290 get_set_creds(pamh, kmd, user, newpass, debug); 291 } 292 293 out: 294 if (debug) 295 syslog(LOG_DEBUG, "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_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 syslog(LOG_ERR, 349 dgettext(TEXT_DOMAIN, 350 "PAM-KRB5 (password): unable to get host based " 351 "service name for realm %s\n"), 352 admin_realm); 353 krb5_free_principal(context, princ); 354 return (3); 355 } 356 357 code = kadm5_init_with_password(kprinc, old_password, cpw_service, 358 ¶ms, KADM5_STRUCT_VERSION, 359 KADM5_API_VERSION_2, &server_handle); 360 if (code != 0) { 361 if (debug) 362 syslog(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_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 syslog(LOG_ERR, 445 dgettext(TEXT_DOMAIN, 446 "PAM-KRB5 (password):unable to get host based " 447 "service name for realm %s\n"), 448 admin_realm); 449 return (PAM_SYSTEM_ERR); 450 } 451 452 code = kadm5_init_with_password(kprinc, old_password, cpw_service, 453 ¶ms, KADM5_STRUCT_VERSION, 454 KADM5_API_VERSION_2, &server_handle); 455 free(cpw_service); 456 if (code != 0) { 457 if (debug) 458 syslog(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 syslog(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