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 * Copyright 1990 by the Massachusetts Institute of Technology. 30 * All Rights Reserved. 31 * 32 * Export of this software from the United States of America may 33 * require a specific license from the United States Government. 34 * It is the responsibility of any person or organization contemplating 35 * export to obtain such a license before exporting. 36 * 37 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and 38 * distribute this software and its documentation for any purpose and 39 * without fee is hereby granted, provided that the above copyright 40 * notice appear in all copies and that both that copyright notice and 41 * this permission notice appear in supporting documentation, and that 42 * the name of M.I.T. not be used in advertising or publicity pertaining 43 * to distribution of the software without specific, written prior 44 * permission. Furthermore if you modify this software you must label 45 * your software as modified software and not distribute it in such a 46 * fashion that it might be confused with the original M.I.T. software. 47 * M.I.T. makes no representations about the suitability of 48 * this software for any purpose. It is provided "as is" without express 49 * or implied warranty. 50 * 51 * 52 * Initialize a credentials cache. 53 */ 54 #include <kerberosv5/krb5.h> 55 #include <kerberosv5/com_err.h> 56 #include <string.h> 57 #include <stdio.h> 58 #include <time.h> 59 #include <netdb.h> 60 #include <syslog.h> 61 #include <locale.h> 62 #include <strings.h> 63 #include <sys/synch.h> 64 #include <gssapi/gssapi.h> 65 66 #include <smbsrv/libsmbns.h> 67 68 #include <smbns_krb.h> 69 70 static int krb5_acquire_cred_kinit_main(); 71 72 typedef enum { INIT_PW, INIT_KT, RENEW, VALIDATE } action_type; 73 74 struct k_opts { 75 /* in seconds */ 76 krb5_deltat starttime; 77 krb5_deltat lifetime; 78 krb5_deltat rlife; 79 80 int forwardable; 81 int proxiable; 82 int addresses; 83 84 int not_forwardable; 85 int not_proxiable; 86 int no_addresses; 87 88 int verbose; 89 90 char *principal_name; 91 char *principal_passwd; 92 char *service_name; 93 char *keytab_name; 94 char *k5_cache_name; 95 char *k4_cache_name; 96 97 action_type action; 98 }; 99 100 struct k5_data { 101 krb5_context ctx; 102 krb5_ccache cc; 103 krb5_principal me; 104 char *name; 105 }; 106 107 static int 108 k5_begin(struct k_opts *opts, struct k5_data *k5) 109 { 110 int code; 111 code = krb5_init_context(&k5->ctx); 112 if (code) { 113 return (code); 114 } 115 116 if ((code = krb5_cc_default(k5->ctx, &k5->cc))) { 117 return (code); 118 } 119 120 /* Use specified name */ 121 if ((code = krb5_parse_name(k5->ctx, opts->principal_name, &k5->me))) { 122 return (code); 123 } 124 125 code = krb5_unparse_name(k5->ctx, k5->me, &k5->name); 126 if (code) { 127 return (code); 128 } 129 opts->principal_name = k5->name; 130 131 return (0); 132 } 133 134 static void 135 k5_end(struct k5_data *k5) 136 { 137 if (k5->name) 138 krb5_free_unparsed_name(k5->ctx, k5->name); 139 if (k5->me) 140 krb5_free_principal(k5->ctx, k5->me); 141 if (k5->cc) 142 krb5_cc_close(k5->ctx, k5->cc); 143 if (k5->ctx) 144 krb5_free_context(k5->ctx); 145 (void) memset(k5, 0, sizeof (*k5)); 146 } 147 148 static int 149 k5_kinit(struct k_opts *opts, struct k5_data *k5) 150 { 151 int notix = 1; 152 krb5_keytab keytab = 0; 153 krb5_creds my_creds; 154 krb5_error_code code = 0; 155 krb5_get_init_creds_opt options; 156 const char *errmsg; 157 158 krb5_get_init_creds_opt_init(&options); 159 (void) memset(&my_creds, 0, sizeof (my_creds)); 160 161 /* 162 * From this point on, we can goto cleanup because my_creds is 163 * initialized. 164 */ 165 if (opts->lifetime) 166 krb5_get_init_creds_opt_set_tkt_life(&options, opts->lifetime); 167 if (opts->rlife) 168 krb5_get_init_creds_opt_set_renew_life(&options, opts->rlife); 169 if (opts->forwardable) 170 krb5_get_init_creds_opt_set_forwardable(&options, 1); 171 if (opts->not_forwardable) 172 krb5_get_init_creds_opt_set_forwardable(&options, 0); 173 if (opts->proxiable) 174 krb5_get_init_creds_opt_set_proxiable(&options, 1); 175 if (opts->not_proxiable) 176 krb5_get_init_creds_opt_set_proxiable(&options, 0); 177 if (opts->addresses) { 178 krb5_address **addresses = NULL; 179 code = krb5_os_localaddr(k5->ctx, &addresses); 180 if (code != 0) { 181 errmsg = error_message(code); 182 syslog(LOG_ERR, dgettext(TEXT_DOMAIN, "k5_kinit: " 183 "getting local addresses (%s)"), errmsg); 184 goto cleanup; 185 } 186 krb5_get_init_creds_opt_set_address_list(&options, addresses); 187 } 188 if (opts->no_addresses) 189 krb5_get_init_creds_opt_set_address_list(&options, NULL); 190 191 if ((opts->action == INIT_KT) && opts->keytab_name) { 192 code = krb5_kt_resolve(k5->ctx, opts->keytab_name, &keytab); 193 if (code != 0) { 194 errmsg = error_message(code); 195 syslog(LOG_ERR, dgettext(TEXT_DOMAIN, "k5_kinit: " 196 "resolving keytab %s (%s)"), errmsg, 197 opts->keytab_name); 198 goto cleanup; 199 } 200 } 201 202 switch (opts->action) { 203 case INIT_PW: 204 code = krb5_get_init_creds_password(k5->ctx, &my_creds, k5->me, 205 opts->principal_passwd, NULL, 0, opts->starttime, 206 opts->service_name, &options); 207 break; 208 case INIT_KT: 209 code = krb5_get_init_creds_keytab(k5->ctx, &my_creds, k5->me, 210 keytab, opts->starttime, opts->service_name, &options); 211 break; 212 case VALIDATE: 213 code = krb5_get_validated_creds(k5->ctx, &my_creds, k5->me, 214 k5->cc, opts->service_name); 215 break; 216 case RENEW: 217 code = krb5_get_renewed_creds(k5->ctx, &my_creds, k5->me, 218 k5->cc, opts->service_name); 219 break; 220 } 221 222 if (code) { 223 char *doing = 0; 224 switch (opts->action) { 225 case INIT_PW: 226 case INIT_KT: 227 doing = dgettext(TEXT_DOMAIN, "k5_kinit: " 228 "getting initial credentials"); 229 break; 230 case VALIDATE: 231 doing = dgettext(TEXT_DOMAIN, "k5_kinit: " 232 "validating credentials"); 233 break; 234 case RENEW: 235 doing = dgettext(TEXT_DOMAIN, "k5_kinit: " 236 "renewing credentials"); 237 break; 238 } 239 240 /* 241 * If got code == KRB5_AP_ERR_V4_REPLY && got_k4, we should 242 * let the user know that maybe he/she wants -4. 243 */ 244 if (code == KRB5KRB_AP_ERR_V4_REPLY) { 245 syslog(LOG_ERR, "%s\n" 246 "The KDC doesn't support v5. " 247 "You may want the -4 option in the future", doing); 248 return (1); 249 } else if (code == KRB5KRB_AP_ERR_BAD_INTEGRITY) { 250 syslog(LOG_ERR, dgettext(TEXT_DOMAIN, "%s " 251 "(Password incorrect)"), doing); 252 } else { 253 errmsg = error_message(code); 254 syslog(LOG_ERR, dgettext(TEXT_DOMAIN, "%s (%s)"), 255 doing, errmsg); 256 } 257 goto cleanup; 258 } 259 260 if (!opts->lifetime) { 261 /* We need to figure out what lifetime to use for Kerberos 4. */ 262 opts->lifetime = my_creds.times.endtime - 263 my_creds.times.authtime; 264 } 265 266 code = krb5_cc_initialize(k5->ctx, k5->cc, k5->me); 267 if (code) { 268 errmsg = error_message(code); 269 syslog(LOG_ERR, dgettext(TEXT_DOMAIN, "k5_kinit: " 270 "initializing cache %s (%s)"), 271 opts->k5_cache_name?opts->k5_cache_name:"", errmsg); 272 goto cleanup; 273 } 274 275 code = krb5_cc_store_cred(k5->ctx, k5->cc, &my_creds); 276 if (code) { 277 errmsg = error_message(code); 278 syslog(LOG_ERR, dgettext(TEXT_DOMAIN, "k5_kinit: " 279 "storing credentials (%s)"), errmsg); 280 goto cleanup; 281 } 282 283 notix = 0; 284 285 cleanup: 286 if (my_creds.client == k5->me) { 287 my_creds.client = 0; 288 } 289 krb5_free_cred_contents(k5->ctx, &my_creds); 290 if (keytab) 291 krb5_kt_close(k5->ctx, keytab); 292 return (notix?0:1); 293 } 294 295 int 296 smb_kinit(char *user, char *passwd) 297 { 298 struct k_opts opts; 299 struct k5_data k5; 300 int authed_k5 = 0; 301 302 (void) memset(&opts, 0, sizeof (opts)); 303 opts.action = INIT_PW; 304 opts.principal_name = strdup(user); 305 opts.principal_passwd = strdup(passwd); 306 307 (void) memset(&k5, 0, sizeof (k5)); 308 309 if (k5_begin(&opts, &k5) != 0) { 310 syslog(LOG_ERR, dgettext(TEXT_DOMAIN, "smb_kinit: " 311 "NOT Authenticated to Kerberos v5 k5_begin failed\n")); 312 return (0); 313 } 314 315 authed_k5 = k5_kinit(&opts, &k5); 316 if (authed_k5) { 317 syslog(LOG_DEBUG, dgettext(TEXT_DOMAIN, "smb_kinit: " 318 "Authenticated to Kerberos v5\n")); 319 } else { 320 syslog(LOG_DEBUG, dgettext(TEXT_DOMAIN, "smb_kinit: " 321 "NOT Authenticated to Kerberos v5\n")); 322 } 323 324 k5_end(&k5); 325 326 return (authed_k5); 327 } 328 329 /* 330 * krb5_display_stat 331 * Display error message for GSS-API routines. 332 * Parameters: 333 * maj : GSS major status 334 * min : GSS minor status 335 * caller_mod: module name that calls this routine so that the module name 336 * can be displayed with the error messages 337 * Returns: 338 * None 339 */ 340 static void 341 krb5_display_stat(OM_uint32 maj, OM_uint32 min, char *caller_mod) 342 { 343 gss_buffer_desc msg; 344 OM_uint32 msg_ctx = 0; 345 OM_uint32 min2; 346 (void) gss_display_status(&min2, maj, GSS_C_GSS_CODE, GSS_C_NULL_OID, 347 &msg_ctx, &msg); 348 syslog(LOG_ERR, "%s: major status error: %s\n", 349 caller_mod, (char *)msg.value); 350 (void) gss_display_status(&min2, min, GSS_C_MECH_CODE, GSS_C_NULL_OID, 351 &msg_ctx, &msg); 352 syslog(LOG_ERR, "%s: minor status error: %s\n", 353 caller_mod, (char *)msg.value); 354 } 355 356 /* 357 * krb5_acquire_cred_kinit 358 * 359 * Wrapper for krb5_acquire_cred_kinit_main with mutex to protect credential 360 * cache file when calling krb5_acquire_cred or kinit. 361 */ 362 363 int 364 krb5_acquire_cred_kinit(char *user, char *pwd, gss_cred_id_t *cred_handle, 365 gss_OID *oid, int *kinit_retry, char *caller_mod) 366 { 367 int ret; 368 369 ret = krb5_acquire_cred_kinit_main(user, pwd, 370 cred_handle, oid, kinit_retry, caller_mod); 371 return (ret); 372 } 373 374 /* 375 * krb5_acquire_cred_kinit_main 376 * 377 * This routine is called both by ADS and Dyn DNS modules to get a handle to 378 * administrative user's credential stored locally on the system. The 379 * credential is the TGT. If the attempt at getting handle fails then a second 380 * attempt will be made after getting a new TGT. 381 * 382 * Paramters: 383 * user : username to retrieve a handle to its credential 384 * pwd : password of username in case obtaining a new TGT is needed 385 * kinit_retry: if 0 then a second attempt will be made to get handle to the 386 * credential if the first attempt fails 387 * caller_mod : name of module that call this routine so that the module name 388 * can be included with error messages 389 * Returns: 390 * cred_handle: handle to the administrative user's credential (TGT) 391 * oid : contains Kerberos 5 object identifier 392 * kinit_retry: A 1 indicates that a second attempt has been made to get 393 * handle to the credential and no further attempts can be made 394 * -1 : error 395 * 0 : success 396 */ 397 static int 398 krb5_acquire_cred_kinit_main(char *user, char *pwd, gss_cred_id_t *cred_handle, 399 gss_OID *oid, int *kinit_retry, char *caller_mod) 400 { 401 OM_uint32 maj, min; 402 gss_name_t desired_name; 403 gss_OID_set desired_mechs; 404 gss_buffer_desc oidstr, name_buf; 405 char str[50], user_name[50]; 406 407 acquire_cred: 408 409 /* Object Identifier for Kerberos 5 */ 410 (void) strcpy(str, "{ 1 2 840 113554 1 2 2 }"); 411 oidstr.value = str; 412 oidstr.length = strlen(str); 413 if ((maj = gss_str_to_oid(&min, &oidstr, oid)) != GSS_S_COMPLETE) { 414 krb5_display_stat(maj, min, caller_mod); 415 return (-1); 416 } 417 if ((maj = gss_create_empty_oid_set(&min, &desired_mechs)) 418 != GSS_S_COMPLETE) { 419 krb5_display_stat(maj, min, caller_mod); 420 (void) gss_release_oid(&min, oid); 421 return (-1); 422 } 423 if ((maj = gss_add_oid_set_member(&min, *oid, &desired_mechs)) 424 != GSS_S_COMPLETE) { 425 krb5_display_stat(maj, min, caller_mod); 426 (void) gss_release_oid(&min, oid); 427 (void) gss_release_oid_set(&min, &desired_mechs); 428 return (-1); 429 } 430 431 (void) strcpy(user_name, user); 432 name_buf.value = user_name; 433 name_buf.length = strlen(user_name)+1; 434 if ((maj = gss_import_name(&min, &name_buf, GSS_C_NT_USER_NAME, 435 &desired_name)) != GSS_S_COMPLETE) { 436 krb5_display_stat(maj, min, caller_mod); 437 (void) gss_release_oid(&min, oid); 438 (void) gss_release_oid_set(&min, &desired_mechs); 439 return (-1); 440 } 441 442 if ((maj = gss_acquire_cred(&min, desired_name, 0, desired_mechs, 443 GSS_C_INITIATE, cred_handle, NULL, NULL)) != GSS_S_COMPLETE) { 444 if (!*kinit_retry) { 445 (void) gss_release_oid(&min, oid); 446 (void) gss_release_oid_set(&min, &desired_mechs); 447 (void) gss_release_name(&min, &desired_name); 448 syslog(LOG_ERR, "%s: Retry kinit to " 449 "acquire credential.\n", caller_mod); 450 (void) smb_kinit(user, pwd); 451 *kinit_retry = 1; 452 goto acquire_cred; 453 } else { 454 krb5_display_stat(maj, min, caller_mod); 455 (void) gss_release_oid(&min, oid); 456 (void) gss_release_oid_set(&min, &desired_mechs); 457 (void) gss_release_name(&min, &desired_name); 458 return (-1); 459 } 460 } 461 (void) gss_release_oid_set(&min, &desired_mechs); 462 (void) gss_release_name(&min, &desired_name); 463 464 return (0); 465 } 466 467 /* 468 * krb5_establish_sec_ctx_kinit 469 * 470 * This routine is called by both the ADS and Dyn DNS modules to establish a 471 * security context before ADS or Dyn DNS updates are allowed. If establishing 472 * a security context fails for any reason, a second attempt will be made after 473 * a new TGT is obtained. This routine is called many time as needed until 474 * a security context is established. 475 * 476 * The resources use for the security context must be released if security 477 * context establishment process fails. 478 * Parameters: 479 * user : user used in establishing a security context for. Is used for 480 * obtaining a new TGT for a second attempt at establishing 481 * security context 482 * pwd : password of above user 483 * cred_handle: a handle to the user credential (TGT) stored locally 484 * gss_context: initially set to GSS_C_NO_CONTEXT but will contain a handle 485 * to a security context 486 * target_name: contains service name to establish a security context with, 487 * ie ldap or dns 488 * gss_flags : flags used in establishing security context 489 * inputptr : initially set to GSS_C_NO_BUFFER but will be token data 490 * received from service's server to be processed to generate 491 * further token to be sent back to service's server during 492 * security context establishment 493 * kinit_retry: if 0 then a second attempt will be made to get handle to the 494 * credential if the first attempt fails 495 * caller_mod : name of module that call this routine so that the module name 496 * can be included with error messages 497 * Returns: 498 * gss_context : a handle to a security context 499 * out_tok : token data to be sent to service's server to establish 500 * security context 501 * ret_flags : return flags 502 * time_rec : valid time for security context, not currently used 503 * kinit_retry : A 1 indicates that a second attempt has been made to get 504 * handle to the credential and no further attempts can be 505 * made 506 * do_acquire_cred: A 1 indicates that a new handle to the local credential 507 * is needed for second attempt at security context 508 * establishment 509 * maj : major status code used if determining is security context 510 * establishment is successful 511 */ 512 int 513 krb5_establish_sec_ctx_kinit(char *user, char *pwd, 514 gss_cred_id_t cred_handle, gss_ctx_id_t *gss_context, 515 gss_name_t target_name, gss_OID oid, int gss_flags, 516 gss_buffer_desc *inputptr, gss_buffer_desc* out_tok, 517 OM_uint32 *ret_flags, OM_uint32 *time_rec, 518 int *kinit_retry, int *do_acquire_cred, 519 OM_uint32 *maj, char *caller_mod) 520 { 521 OM_uint32 min; 522 523 *maj = gss_init_sec_context(&min, cred_handle, gss_context, 524 target_name, oid, gss_flags, 0, NULL, inputptr, NULL, 525 out_tok, ret_flags, time_rec); 526 if (*maj != GSS_S_COMPLETE && *maj != GSS_S_CONTINUE_NEEDED) { 527 if (*gss_context != NULL) 528 (void) gss_delete_sec_context(&min, gss_context, NULL); 529 530 if (!*kinit_retry) { 531 syslog(LOG_ERR, "%s: Retry kinit to establish " 532 "security context.\n", caller_mod); 533 (void) smb_kinit(user, pwd); 534 *kinit_retry = 1; 535 *do_acquire_cred = 1; 536 return (-1); 537 } else { 538 krb5_display_stat(*maj, min, caller_mod); 539 return (-1); 540 } 541 } 542 return (0); 543 } 544