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