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