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 2007 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 29 #include <kadm5/admin.h> 30 #include <krb5.h> 31 32 #include <security/pam_appl.h> 33 #include <security/pam_modules.h> 34 #include <security/pam_impl.h> 35 #include <syslog.h> 36 #include <string.h> 37 #include <stdio.h> 38 #include <stdlib.h> 39 #include <sys/types.h> 40 #include <pwd.h> 41 #include <libintl.h> 42 #include <netdb.h> 43 #include "utils.h" 44 #include <shadow.h> 45 46 #include "krb5_repository.h" 47 48 #define KRB5_AUTOMIGRATE_DATA "SUNW-KRB5-AUTOMIGRATE-DATA" 49 50 /* 51 * pam_sm_acct_mgmt main account managment routine. 52 */ 53 54 static int 55 fetch_princ_entry( 56 char *princ_str, 57 char *password, 58 kadm5_principal_ent_rec *prent, /* out */ 59 krb5_timestamp *now, /* 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; 68 void *server_handle; 69 krb5_context context; 70 kadm5_config_params params; 71 72 if (code = krb5_init_context(&context)) { 73 return (PAM_SYSTEM_ERR); 74 } 75 76 if ((code = get_kmd_kuser(context, (const char *)princ_str, kprinc, 77 2*MAXHOSTNAMELEN)) != 0) { 78 return (code); 79 } 80 81 code = krb5_parse_name(context, kprinc, &princ); 82 if (code != 0) { 83 krb5_free_context(context); 84 return (PAM_SYSTEM_ERR); 85 } 86 87 if (strlen(password) == 0) { 88 krb5_free_principal(context, princ); 89 krb5_free_context(context); 90 if (debug) 91 __pam_log(LOG_AUTH | LOG_DEBUG, 92 "PAM-KRB5 (acct): fetch_princ_entry: pwlen=0"); 93 return (PAM_AUTH_ERR); 94 } 95 96 (void) strlcpy(admin_realm, 97 krb5_princ_realm(context, princ)->data, 98 sizeof (admin_realm)); 99 100 (void) memset((char *)¶ms, 0, sizeof (params)); 101 params.mask |= KADM5_CONFIG_REALM; 102 params.realm = admin_realm; 103 104 if (kadm5_get_cpw_host_srv_name(context, admin_realm, &cpw_service)) { 105 __pam_log(LOG_AUTH | LOG_ERR, 106 "PAM-KRB5 (acct): unable to get host based " 107 "service name for realm '%s'", 108 admin_realm); 109 krb5_free_principal(context, princ); 110 krb5_free_context(context); 111 return (PAM_SYSTEM_ERR); 112 } 113 114 code = kadm5_init_with_password(kprinc, password, cpw_service, 115 ¶ms, KADM5_STRUCT_VERSION, 116 KADM5_API_VERSION_2, &server_handle); 117 if (code != 0) { 118 if (debug) 119 __pam_log(LOG_AUTH | LOG_DEBUG, 120 "PAM-KRB5 (acct): fetch_princ_entry: " 121 "init_with_pw failed: code = %d", code); 122 krb5_free_principal(context, princ); 123 krb5_free_context(context); 124 return ((code == KADM5_BAD_PASSWORD) ? 125 PAM_AUTH_ERR : PAM_SYSTEM_ERR); 126 } 127 128 if (_kadm5_get_kpasswd_protocol(server_handle) != KRB5_CHGPWD_RPCSEC) { 129 if (debug) 130 __pam_log(LOG_AUTH | LOG_DEBUG, 131 "PAM-KRB5 (acct): fetch_princ_entry: " 132 "non-RPCSEC_GSS chpw server, can't get " 133 "princ entry"); 134 (void) kadm5_destroy(server_handle); 135 krb5_free_principal(context, princ); 136 krb5_free_context(context); 137 return (PAM_SYSTEM_ERR); 138 } 139 140 code = kadm5_get_principal(server_handle, princ, prent, 141 KADM5_PRINCIPAL_NORMAL_MASK); 142 143 if (code != 0) { 144 (void) kadm5_destroy(server_handle); 145 krb5_free_principal(context, princ); 146 krb5_free_context(context); 147 return ((code == KADM5_UNK_PRINC) ? 148 PAM_USER_UNKNOWN : PAM_SYSTEM_ERR); 149 } 150 151 if (code = krb5_timeofday(context, now)) { 152 (void) kadm5_destroy(server_handle); 153 krb5_free_principal(context, princ); 154 krb5_free_context(context); 155 __pam_log(LOG_AUTH | LOG_ERR, 156 "PAM-KRB5 (acct): krb5_timeofday fail: code=%d", 157 code); 158 return (PAM_SYSTEM_ERR); 159 } 160 161 (void) kadm5_destroy(server_handle); 162 krb5_free_principal(context, princ); 163 krb5_free_context(context); 164 165 return (PAM_SUCCESS); 166 } 167 168 /* 169 * exp_warn 170 * 171 * warn the user if her pw is set to expire 172 * 173 * We use the kadm protocol and chpw svc to fetch the user's KDC db 174 * entry. The user's default perms on the KDC db should allow this. 175 * Note since the SEAM kadm API uses rpcsec_gss (which is diff from 176 * what MS and MIT 1.2 and before uses), this probably only works 177 * with a SEAM KDC. 178 */ 179 180 static int 181 exp_warn( 182 pam_handle_t *pamh, 183 char *user, 184 char *password, 185 int debug) 186 187 { 188 int err; 189 kadm5_principal_ent_rec prent; 190 krb5_timestamp now, days; 191 char messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE]; 192 193 if (debug) 194 __pam_log(LOG_AUTH | LOG_DEBUG, 195 "PAM-KRB5 (acct): exp_warn start: user = '%s'", 196 user ? user : "<null>"); 197 198 if (!pamh || !user || !password) { 199 err = PAM_SERVICE_ERR; 200 goto out; 201 } 202 203 (void) memset(&prent, 0, sizeof (prent)); 204 if ((err = fetch_princ_entry(user, password, &prent, 205 &now, debug)) != PAM_SUCCESS) { 206 if (debug) 207 __pam_log(LOG_AUTH | LOG_DEBUG, 208 "PAM-KRB5 (acct): exp_warn: fetch_pr failed %d", 209 err); 210 goto out; 211 } 212 213 if (debug) 214 __pam_log(LOG_AUTH | LOG_DEBUG, 215 "PAM-KRB5 (acct): exp_warn: fetch_princ success:" 216 " princ exp=%ld pw_exp = %ld, now =%ld, days=%ld", 217 prent.princ_expire_time, 218 prent.pw_expiration, now, 219 prent.pw_expiration > 0 220 ? ((prent.pw_expiration - now) / DAY) 221 : 0); 222 223 /* warn user if principal's pw is set to expire */ 224 if (prent.pw_expiration > 0) { 225 days = (prent.pw_expiration - now) / DAY; 226 if (days <= 0) 227 (void) snprintf(messages[0], 228 sizeof (messages[0]), 229 dgettext(TEXT_DOMAIN, 230 "Your Kerberos password will expire within 24 hours.\n")); 231 else if (days == 1) 232 (void) snprintf(messages[0], 233 sizeof (messages[0]), 234 dgettext(TEXT_DOMAIN, 235 "Your Kerberos password will expire in 1 day.\n")); 236 else 237 (void) snprintf(messages[0], 238 sizeof (messages[0]), 239 dgettext(TEXT_DOMAIN, 240 "Your Kerberos password will expire in %d days.\n"), 241 (int)days); 242 243 (void) __pam_display_msg(pamh, PAM_TEXT_INFO, 1, 244 messages, NULL); 245 } 246 247 /* things went smooth */ 248 err = PAM_SUCCESS; 249 250 out: 251 if (debug) 252 __pam_log(LOG_AUTH | LOG_DEBUG, 253 "PAM-KRB5 (acct): exp_warn end: err = %d", err); 254 255 return (err); 256 } 257 258 /* 259 * pam_krb5 acct_mgmt 260 * 261 * we do 262 * - check if pw expired (flag set in auth) 263 * - warn user if pw is set to expire 264 * 265 * notes 266 * - we require the auth module to have already run (sets module data) 267 * - we don't worry about an expired princ cuz if that's the case, 268 * auth would have failed 269 */ 270 int 271 pam_sm_acct_mgmt( 272 pam_handle_t *pamh, 273 int flags, 274 int argc, 275 const char **argv) 276 277 { 278 char *user = NULL; 279 char *userdata = NULL; 280 int err; 281 int i; 282 krb5_module_data_t *kmd = NULL; 283 char messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE]; 284 int debug = 0; /* pam.conf entry option */ 285 int nowarn = 0; /* pam.conf entry option, no expire warnings */ 286 pam_repository_t *rep_data = NULL; 287 288 for (i = 0; i < argc; i++) { 289 if (strcasecmp(argv[i], "debug") == 0) 290 debug = 1; 291 else if (strcasecmp(argv[i], "nowarn") == 0) { 292 nowarn = 1; 293 flags = flags | PAM_SILENT; 294 } else { 295 __pam_log(LOG_AUTH | LOG_ERR, 296 "PAM-KRB5 (acct): illegal option %s", 297 argv[i]); 298 } 299 } 300 301 if (debug) 302 __pam_log(LOG_AUTH | LOG_DEBUG, 303 "PAM-KRB5 (acct): debug=%d, nowarn=%d", 304 debug, nowarn); 305 306 (void) pam_get_item(pamh, PAM_REPOSITORY, (void **)&rep_data); 307 308 if (rep_data != NULL) { 309 /* 310 * If the repository is not ours, 311 * return PAM_IGNORE. 312 */ 313 if (strcmp(rep_data->type, KRB5_REPOSITORY_NAME) != 0) { 314 if (debug) 315 __pam_log(LOG_AUTH | LOG_DEBUG, 316 "PAM-KRB5 (acct): wrong" 317 "repository found (%s), returning " 318 "PAM_IGNORE", rep_data->type); 319 return (PAM_IGNORE); 320 } 321 } 322 323 324 /* get user name */ 325 (void) pam_get_item(pamh, PAM_USER, (void **) &user); 326 327 if (user == NULL || *user == '\0') { 328 err = PAM_USER_UNKNOWN; 329 goto out; 330 } 331 332 /* get pam_krb5_migrate specific data */ 333 err = pam_get_data(pamh, KRB5_AUTOMIGRATE_DATA, 334 (const void **)&userdata); 335 if (err != PAM_SUCCESS) { 336 if (debug) 337 __pam_log(LOG_AUTH | LOG_DEBUG, "PAM-KRB5 (acct): " 338 "no module data for KRB5_AUTOMIGRATE_DATA"); 339 } else { 340 /* 341 * We try and reauthenticate, since this user has a 342 * newly created krb5 principal via the pam_krb5_migrate 343 * auth module. That way, this new user will have fresh 344 * creds (assuming pam_sm_authenticate() succeeds). 345 */ 346 if (strcmp(user, userdata) == 0) 347 (void) pam_sm_authenticate(pamh, flags, argc, 348 (const char **)argv); 349 else 350 if (debug) 351 __pam_log(LOG_AUTH | LOG_DEBUG, 352 "PAM-KRB5 (acct): PAM_USER %s" 353 "does not match user %s from pam_get_data()", 354 user, (char *)userdata); 355 } 356 357 /* get krb5 module data */ 358 if ((err = pam_get_data(pamh, KRB5_DATA, (const void **)&kmd)) 359 != PAM_SUCCESS) { 360 if (err == PAM_NO_MODULE_DATA) { 361 /* 362 * pam_auth never called (possible config 363 * error; no pam_krb5 auth entry in pam.conf) 364 * or 365 * auth module returned before module data 366 * was instantiated (normal for auth 'acceptor') 367 */ 368 if (debug) 369 __pam_log(LOG_AUTH | LOG_DEBUG, 370 "PAM-KRB5 (acct): no module data"); 371 err = PAM_IGNORE; 372 goto out; 373 } else { 374 __pam_log(LOG_AUTH | LOG_ERR, 375 "PAM-KRB5 (acct): get module" 376 " data failed: err=%d", 377 err); 378 } 379 goto out; 380 } 381 382 debug = debug || kmd->debug; 383 384 /* 385 * auth mod set status to ignore, most likely cuz root key is 386 * in keytab, so skip other checks and return ignore 387 */ 388 if (kmd->auth_status == PAM_IGNORE) { 389 if (debug) 390 __pam_log(LOG_AUTH | LOG_DEBUG, 391 "PAM-KRB5 (acct): kmd auth_status is IGNORE"); 392 err = PAM_IGNORE; 393 goto out; 394 } 395 396 /* 397 * auth mod set status to user_unknown, most likely cuz user is 398 * not a kerberos user. 399 */ 400 if (kmd->auth_status == PAM_USER_UNKNOWN) { 401 if (debug) 402 syslog(LOG_DEBUG, 403 "PAM-KRB5 (acct): kmd auth_status is USER UNKNOWN"); 404 err = PAM_USER_UNKNOWN; 405 goto out; 406 } 407 408 /* 409 * age_status will be set to PAM_NEW_AUTHTOK_REQD in pam_krb5's 410 * 'auth' if the user's key/pw has expired and needs to be changed 411 */ 412 if (kmd->age_status == PAM_NEW_AUTHTOK_REQD) { 413 if (!nowarn) { 414 (void) snprintf(messages[0], sizeof (messages[0]), 415 dgettext(TEXT_DOMAIN, 416 "Your Kerberos password has expired.\n")); 417 (void) __pam_display_msg(pamh, PAM_TEXT_INFO, 418 1, messages, NULL); 419 } 420 err = PAM_NEW_AUTHTOK_REQD; 421 goto out; 422 } 423 424 if (!(flags & PAM_SILENT) && !nowarn && kmd->password) { 425 /* if we fail, let it slide, it's only a warning brah */ 426 (void) exp_warn(pamh, user, kmd->password, debug); 427 } 428 429 /* 430 * Here we return any errors during the auth pass, if any. 431 */ 432 err = kmd->auth_status; 433 434 out: 435 if (debug) 436 __pam_log(LOG_AUTH | LOG_DEBUG, 437 "PAM-KRB5 (acct): end: %s", pam_strerror(pamh, err)); 438 439 return (err); 440 } 441