1/* 2 * Copyright (c) 2009 Kungliga Tekniska Högskolan 3 * (Royal Institute of Technology, Stockholm, Sweden). 4 * All rights reserved. 5 * 6 * Portions Copyright (c) 2010 Apple Inc. All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * 3. Neither the name of the Institute nor the names of its contributors 20 * may be used to endorse or promote products derived from this software 21 * without specific prior written permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 */ 35 36#include "mech_locl.h" 37#include "heim_threads.h" 38#include "heimbase.h" 39 40#include <asl.h> 41#include <CoreFoundation/CoreFoundation.h> 42#include <krb5.h> 43#include <notify.h> 44 45#undef HEIMDAL_PRINTF_ATTRIBUTE 46#define HEIMDAL_PRINTF_ATTRIBUTE(x) 47 48struct mg_thread_ctx { 49 gss_OID mech; 50 OM_uint32 min_stat; 51 gss_buffer_desc min_error; 52 aslclient asl; 53 aslmsg msg; 54}; 55 56static HEIMDAL_MUTEX context_mutex = HEIMDAL_MUTEX_INITIALIZER; 57static int created_key; 58static HEIMDAL_thread_key context_key; 59 60 61static void 62destroy_context(void *ptr) 63{ 64 struct mg_thread_ctx *mg = ptr; 65 OM_uint32 junk; 66 67 if (mg == NULL) 68 return; 69 70 gss_release_buffer(&junk, &mg->min_error); 71 72 if (mg->msg) 73 asl_free(mg->msg); 74 if (mg->asl) 75 asl_close(mg->asl); 76 77 free(mg); 78} 79 80 81static struct mg_thread_ctx * 82_gss_mechglue_thread(void) 83{ 84 struct mg_thread_ctx *ctx; 85 int ret = 0; 86 87 HEIMDAL_MUTEX_lock(&context_mutex); 88 89 if (!created_key) { 90 HEIMDAL_key_create(&context_key, destroy_context, ret); 91 if (ret) { 92 HEIMDAL_MUTEX_unlock(&context_mutex); 93 return NULL; 94 } 95 created_key = 1; 96 } 97 HEIMDAL_MUTEX_unlock(&context_mutex); 98 99 ctx = HEIMDAL_getspecific(context_key); 100 if (ctx == NULL) { 101 102 ctx = calloc(1, sizeof(*ctx)); 103 if (ctx == NULL) 104 return NULL; 105 HEIMDAL_setspecific(context_key, ctx, ret); 106 if (ret) { 107 free(ctx); 108 return NULL; 109 } 110 111 ctx->asl = asl_open(getprogname(), NULL, 0); 112 ctx->msg = asl_new(ASL_TYPE_MSG); 113 asl_set(ctx->msg, "org.h5l.asl", "gssapi"); 114 } 115 return ctx; 116} 117 118OM_uint32 119_gss_mg_get_error(const gss_OID mech, OM_uint32 value, gss_buffer_t string) 120{ 121 struct mg_thread_ctx *mg; 122 123 mg = _gss_mechglue_thread(); 124 if (mg == NULL) 125 return GSS_S_BAD_STATUS; 126 127 if (value != mg->min_stat || mg->min_error.length == 0) { 128 _mg_buffer_zero(string); 129 return GSS_S_BAD_STATUS; 130 } 131 string->value = malloc(mg->min_error.length); 132 if (string->value == NULL) { 133 _mg_buffer_zero(string); 134 return GSS_S_FAILURE; 135 } 136 string->length = mg->min_error.length; 137 memcpy(string->value, mg->min_error.value, mg->min_error.length); 138 return GSS_S_COMPLETE; 139} 140 141void 142_gss_mg_error(struct gssapi_mech_interface_desc *m, OM_uint32 min) 143{ 144 OM_uint32 major_status, minor_status; 145 OM_uint32 message_content = 0; 146 struct mg_thread_ctx *mg; 147 148 /* 149 * Mechs without gss_display_status() does 150 * gss_mg_collect_error() by themself. 151 */ 152 if (m->gm_display_status == NULL) 153 return ; 154 155 mg = _gss_mechglue_thread(); 156 if (mg == NULL) 157 return; 158 159 gss_release_buffer(&minor_status, &mg->min_error); 160 161 mg->mech = &m->gm_mech_oid; 162 mg->min_stat = min; 163 164 major_status = m->gm_display_status(&minor_status, 165 min, 166 GSS_C_MECH_CODE, 167 &m->gm_mech_oid, 168 &message_content, 169 &mg->min_error); 170 if (major_status != GSS_S_COMPLETE) { 171 _mg_buffer_zero(&mg->min_error); 172 } else { 173 _gss_mg_log(5, "_gss_mg_error: captured %.*s (%d) from underlaying mech %s", 174 (int)mg->min_error.length, (const char *)mg->min_error.value, 175 (int)min, m->gm_name); 176 } 177} 178 179void 180gss_mg_collect_error(gss_OID mech, OM_uint32 maj, OM_uint32 min) 181{ 182 gssapi_mech_interface m = __gss_get_mechanism(mech); 183 if (m == NULL) 184 return; 185 _gss_mg_error(m, min); 186} 187 188OM_uint32 189gss_mg_set_error_string(gss_OID mech, 190 OM_uint32 maj, OM_uint32 min, 191 const char *fmt, ...) 192 HEIMDAL_PRINTF_ATTRIBUTE((printf, 4, 5)) 193{ 194 struct mg_thread_ctx *mg; 195 char *str = NULL; 196 OM_uint32 junk; 197 va_list ap; 198 199 mg = _gss_mechglue_thread(); 200 if (mg == NULL) 201 return maj; 202 203 va_start(ap, fmt); 204 vasprintf(&str, fmt, ap); 205 va_end(ap); 206 207 if (str) { 208 gss_release_buffer(&junk, &mg->min_error); 209 210 mg->mech = mech; 211 mg->min_stat = min; 212 213 mg->min_error.value = str; 214 mg->min_error.length = strlen(str); 215 216 _gss_mg_log(5, "gss_mg_set_error_string: %.*s (%d/%d)", 217 (int)mg->min_error.length, (const char *)mg->min_error.value, 218 (int)maj, (int)min); 219 } 220 return maj; 221} 222 223#ifdef __APPLE__ 224 225#include <CoreFoundation/CoreFoundation.h> 226 227CFErrorRef 228_gss_mg_create_cferror(OM_uint32 major_status, 229 OM_uint32 minor_status, 230 gss_const_OID mech) 231{ 232 struct mg_thread_ctx *mg; 233 CFErrorRef e; 234#define NUM_ERROR_DESC 5 235 void const *keys[NUM_ERROR_DESC] = { 236 CFSTR("kGSSMajorErrorCode"), 237 CFSTR("kGSSMinorErrorCode"), 238 CFSTR("kGSSMechanismOID"), 239 CFSTR("kGSSMechanism"), 240 kCFErrorDescriptionKey 241 }; 242 void const *values[NUM_ERROR_DESC] = { 0 }; 243 gss_buffer_desc oid; 244 const char *name; 245 OM_uint32 junk; 246 size_t n; 247 248 values[0] = CFNumberCreate(NULL, kCFNumberSInt32Type, &major_status); 249 values[1] = CFNumberCreate(NULL, kCFNumberSInt32Type, &minor_status); 250 251 if (mech && gss_oid_to_str(&junk, (gss_OID)mech, &oid) == GSS_S_COMPLETE) { 252 values[2] = CFStringCreateWithFormat(NULL, NULL, CFSTR("%.*s"), (int)oid.length, (char *)oid.value); 253 gss_release_buffer(&junk, &oid); 254 } else { 255 values[2] = CFStringCreateWithFormat(NULL, NULL, CFSTR("no-mech")); 256 } 257 258 if (mech && (name = gss_oid_to_name(mech)) != NULL) { 259 values[3] = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s"), name);; 260 } else { 261 values[3] = CFStringCreateWithFormat(NULL, NULL, CFSTR("no mech given")); 262 } 263 264 mg = _gss_mechglue_thread(); 265 if (mg && minor_status == mg->min_stat && mg->min_error.length != 0) { 266 values[4] = CFStringCreateWithFormat(NULL, NULL, CFSTR("%.*s"), 267 (int)mg->min_error.length, 268 mg->min_error.value); 269 } else { 270 values[4] = CFStringCreateWithFormat(NULL, NULL, CFSTR("Unknown minor status: %d"), (int)minor_status); 271 } 272 273 e = CFErrorCreateWithUserInfoKeysAndValues(NULL, 274 CFSTR("org.h5l.GSS"), 275 (CFIndex)major_status, 276 keys, 277 values, 278 NUM_ERROR_DESC); 279 for (n = 0; n < sizeof(values) / sizeof(values[0]); n++) 280 CFRelease(values[n]); 281 282 return e; 283} 284 285 286static CFTypeRef 287CopyKeyFromFile(CFStringRef file, CFStringRef key) 288{ 289 CFReadStreamRef s; 290 CFDictionaryRef d; 291 CFErrorRef e; 292 CFURLRef url; 293 CFTypeRef val; 294 295 url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, file, kCFURLPOSIXPathStyle, false); 296 if (url == NULL) 297 return NULL; 298 299 s = CFReadStreamCreateWithFile(kCFAllocatorDefault, url); 300 CFRelease(url); 301 if (s == NULL) 302 return NULL; 303 304 if (!CFReadStreamOpen(s)) { 305 CFRelease(s); 306 return NULL; 307 } 308 309 d = (CFDictionaryRef)CFPropertyListCreateWithStream (kCFAllocatorDefault, s, 0, kCFPropertyListImmutable, NULL, &e); 310 CFRelease(s); 311 if (d == NULL) 312 return NULL; 313 314 if (CFGetTypeID(d) != CFDictionaryGetTypeID()) { 315 CFRelease(d); 316 return NULL; 317 } 318 319 val = CFDictionaryGetValue(d, key); 320 if (val) 321 CFRetain(val); 322 CFRelease(d); 323 return val; 324} 325 326 327static CFTypeRef 328CopyKeyFromDomain(CFStringRef domain, CFStringRef key) 329{ 330 CFStringRef file; 331 CFTypeRef val = NULL; 332 333 file = CFStringCreateWithFormat(NULL, 0, CFSTR("/Library/Preferences/%@.plist"), domain); 334 if (file) { 335 val = CopyKeyFromFile(file, key); 336 CFRelease(file); 337 } 338 339 return val; 340} 341 342static CFTypeRef 343CopyMangedPreference(CFStringRef key) 344{ 345#if __APPLE_TARGET_EMBEDDED__ 346#define GLOBAL_PREFERENCE_FILE CFSTR("/Library/Managed Preferences/mobile/.GlobalPreferences.plist") 347#else 348#define GLOBAL_PREFERENCE_FILE CFSTR("/Library/Managed Preferences/.GlobalPreferences.plist") 349#endif 350 351 return CopyKeyFromFile(GLOBAL_PREFERENCE_FILE, key); 352} 353 354CFTypeRef 355_gss_mg_copy_key(CFStringRef domain, CFStringRef key) 356{ 357 CFTypeRef val; 358 359 /* 360 * First prefer system file, then user copy if we are allowed to 361 * touch user home directory. 362 */ 363 364 val = CopyKeyFromDomain(domain, key); 365 366 if (val == NULL && krb5_homedir_access(NULL)) { 367 val = CFPreferencesCopyAppValue(key, domain); 368 if (val == NULL) 369 val = CFPreferencesCopyValue(key, domain, kCFPreferencesAnyUser, kCFPreferencesAnyHost); 370 } 371 return val; 372} 373 374#endif 375 376static int log_level = 0; 377static int asl_level = ASL_LEVEL_ERR; 378static void *log_ctx = NULL; 379static HEIMDAL_MUTEX log_mutex = HEIMDAL_MUTEX_INITIALIZER; 380static int config_token = -1; 381static void (*log_func)(void *ctx, int level, const char *fmt, va_list) = NULL; 382 383void 384gss_set_log_function(void *ctx, void (*func)(void *ctx, int level, const char *fmt, va_list)) 385{ 386 if (log_func == NULL) { 387 log_func = func; 388 log_ctx = ctx; 389 } 390} 391 392static void 393init_log(void) 394{ 395 CFTypeRef val; 396 397 val = _gss_mg_copy_key(CFSTR("com.apple.GSS"), CFSTR("DebugLevel")); 398 if (val == NULL) { 399 /* 400 * Pick up global preferences that can be configured via a 401 * profile. 402 */ 403 if (geteuid() == 0 || !krb5_homedir_access(NULL)) { 404 val = CopyMangedPreference(CFSTR("GSSDebugLevel")); 405 } else { 406 val = CFPreferencesCopyAppValue(CFSTR("GSSDebugLevel"), 407 CFSTR(".GlobalPreferences")); 408 } 409 } 410 411 if (val == NULL) 412 return; 413 414 HEIMDAL_MUTEX_lock(&log_mutex); 415 416 if (CFGetTypeID(val) == CFBooleanGetTypeID()) 417 log_level = CFBooleanGetValue(val) ? 1 : 0; 418 else if (CFGetTypeID(val) == CFNumberGetTypeID()) 419 CFNumberGetValue(val, kCFNumberIntType, &log_level); 420 else 421 /* ignore other types */; 422 423 CFRelease(val); 424 425 if (log_level > 0) 426 asl_level = ASL_LEVEL_ERR; 427 428 HEIMDAL_MUTEX_unlock(&log_mutex); 429} 430 431static void 432setup_logging(void *ptr) 433{ 434 init_log(); 435 notify_register_check("com.apple.ManagedConfiguration.profileListChanged", &config_token); 436} 437 438int 439_gss_mg_log_level(int level) 440{ 441 static heim_base_once_t once = HEIM_BASE_ONCE_INIT; 442 443 heim_base_once_f(&once, NULL, setup_logging); 444 445 if (config_token != -1) { 446 int ret, check = 0; 447 ret = notify_check(config_token, &check); 448 if (ret == NOTIFY_STATUS_OK && check) 449 init_log(); 450 } 451 452 return (level > log_level) ? 0 : 1; 453} 454 455void 456_gss_mg_log(int level, const char *fmt, ...) 457 HEIMDAL_PRINTF_ATTRIBUTE((printf, 2, 3)) 458{ 459 struct mg_thread_ctx *mg; 460 va_list ap; 461 462 if (!_gss_mg_log_level(level)) 463 return; 464 465 mg = _gss_mechglue_thread(); 466 if (mg == NULL) 467 return; 468 469 va_start(ap, fmt); 470 asl_vlog(mg->asl, mg->msg, asl_level, fmt, ap); 471 va_end(ap); 472 473 if (log_func) { 474 va_start(ap, fmt); 475 log_func(log_ctx, level, fmt, ap); 476 va_end(ap); 477 } 478} 479 480void 481_gss_mg_log_name(int level, struct _gss_name *name, gss_OID mech_type, const char *fmt, ...) 482 HEIMDAL_PRINTF_ATTRIBUTE((printf, 4, 5)) 483{ 484 struct _gss_mechanism_name *mn = NULL; 485 gssapi_mech_interface m; 486 OM_uint32 junk; 487 488 if (!_gss_mg_log_level(level)) 489 return; 490 491 m = __gss_get_mechanism(mech_type); 492 if (m == NULL) 493 return; 494 495 if (_gss_find_mn(&junk, name, mech_type, &mn) == GSS_S_COMPLETE) { 496 OM_uint32 maj_stat = GSS_S_COMPLETE; 497 gss_buffer_desc namebuf; 498 499 if (mn == NULL) { 500 namebuf.value = "no name"; 501 namebuf.length = strlen((char *)namebuf.value); 502 } else { 503 maj_stat = m->gm_display_name(&junk, mn->gmn_name, 504 &namebuf, NULL); 505 } 506 if (maj_stat == GSS_S_COMPLETE) { 507 char *str = NULL; 508 va_list ap; 509 510 va_start(ap, fmt); 511 vasprintf(&str, fmt, ap); 512 va_end(ap); 513 514 if (str) 515 _gss_mg_log(level, "%s %.*s", str, 516 (int)namebuf.length, (char *)namebuf.value); 517 free(str); 518 if (mn != NULL) 519 gss_release_buffer(&junk, &namebuf); 520 } 521 } 522 523} 524 525void 526_gss_mg_log_cred(int level, struct _gss_cred *cred, const char *fmt, ...) 527 HEIMDAL_PRINTF_ATTRIBUTE((printf, 3, 4)) 528{ 529 struct _gss_mechanism_cred *mc; 530 char *str; 531 va_list ap; 532 533 if (!_gss_mg_log_level(level)) 534 return; 535 536 va_start(ap, fmt); 537 vasprintf(&str, fmt, ap); 538 va_end(ap); 539 540 if (cred) { 541 HEIM_SLIST_FOREACH(mc, &cred->gc_mc, gmc_link) { 542 _gss_mg_log(1, "%s: %s", str, mc->gmc_mech->gm_name); 543 } 544 } else { 545 _gss_mg_log(1, "%s: GSS_C_NO_CREDENTIAL", str); 546 } 547 free(str); 548} 549 550#if TARGET_IPHONE_SIMULATOR 551#define PLUGIN_PREFIX "%{IPHONE_SIMULATOR_ROOT}" 552#else 553#define PLUGIN_PREFIX "" 554#endif 555 556 557static const char *paths[] = { 558 PLUGIN_PREFIX "/Library/KerberosPlugins/GSSAPI", 559 PLUGIN_PREFIX "/System/Library/KerberosPlugins/GSSAPI", 560 NULL 561}; 562 563static void 564load_plugins(void *ptr) 565{ 566 krb5_context context; 567 if (krb5_init_context(&context)) 568 return; 569 krb5_load_plugins(context, "gss", paths); 570 krb5_free_context(context); 571} 572 573 574void 575_gss_load_plugins(void) 576{ 577 static heim_base_once_t once = HEIM_BASE_ONCE_INIT; 578 heim_base_once_f(&once, NULL, load_plugins); 579} 580