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