1/* 2 * Copyright (c) 2008, 2009 Apple Inc. All Rights Reserved. 3 * 4 * Export of this software from the United States of America may require 5 * a specific license from the United States Government. It is the 6 * responsibility of any person or organization contemplating export to 7 * obtain such a license before exporting. 8 * 9 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and 10 * distribute this software and its documentation for any purpose and 11 * without fee is hereby granted, provided that the above copyright 12 * notice appear in all copies and that both that copyright notice and 13 * this permission notice appear in supporting documentation, and that 14 * the name of Apple Inc. not be used in advertising or publicity pertaining 15 * to distribution of the software without specific, written prior 16 * permission. Apple Inc. makes no representations about the suitability of 17 * this software for any purpose. It is provided "as is" without express 18 * or implied warranty. 19 * 20 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR 21 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 22 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. 23 * 24 */ 25 26#include "kdc_locl.h" 27 28#if defined(__APPLE__) && defined(HAVE_GCD) 29 30#include <CoreFoundation/CoreFoundation.h> 31#include <SystemConfiguration/SCDynamicStore.h> 32#include <SystemConfiguration/SCDynamicStoreCopySpecific.h> 33#include <SystemConfiguration/SCDynamicStoreKey.h> 34 35#include <dispatch/dispatch.h> 36 37#include <asl.h> 38#include <resolv.h> 39 40#include <dns_sd.h> 41#include <err.h> 42 43#include "kdc_locl.h" 44 45#ifndef __APPLE_TARGET_EMBEDDED__ 46 47static heim_array_t (*announce_get_realms)(void); 48 49struct entry { 50 DNSRecordRef recordRef; 51 char *domain; 52 char *realm; 53#define F_EXISTS 1 54#define F_PUSH 2 55 int flags; 56 struct entry *next; 57}; 58 59/* #define REGISTER_SRV_RR */ 60 61static struct entry *g_entries = NULL; 62static CFStringRef g_hostname = NULL; 63static DNSServiceRef g_dnsRef = NULL; 64static dispatch_source_t g_restart_timer = NULL; 65static SCDynamicStoreRef g_store = NULL; 66static dispatch_queue_t g_queue = NULL; 67 68#define LOG(...) asl_log(NULL, NULL, ASL_LEVEL_INFO, __VA_ARGS__) 69 70static void create_dns_sd(void); 71static void destroy_dns_sd(void); 72static void update_all(SCDynamicStoreRef, CFArrayRef, void *); 73 74 75/* parameters */ 76static CFStringRef NetworkChangedKey_BackToMyMac = CFSTR("Setup:/Network/BackToMyMac"); 77 78 79static char * 80CFString2utf8(CFStringRef string) 81{ 82 size_t size; 83 char *str; 84 85 size = 1 + CFStringGetMaximumSizeForEncoding(CFStringGetLength(string), kCFStringEncodingUTF8); 86 str = malloc(size); 87 if (str == NULL) 88 return NULL; 89 90 if (CFStringGetCString(string, str, size, kCFStringEncodingUTF8) == false) { 91 free(str); 92 return NULL; 93 } 94 return str; 95} 96 97/* 98 * 99 */ 100 101static void 102retry_timer(void) 103{ 104 dispatch_time_t t; 105 106 heim_assert(g_dnsRef == NULL, "called create when a connection already existed"); 107 108 if (g_restart_timer) 109 return; 110 111 g_restart_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, g_queue); 112 t = dispatch_time(DISPATCH_TIME_NOW, 5ull * NSEC_PER_SEC); 113 dispatch_source_set_timer(g_restart_timer, t, 0, NSEC_PER_SEC); 114 dispatch_source_set_event_handler(g_restart_timer, ^{ 115 create_dns_sd(); 116 dispatch_release(g_restart_timer); 117 g_restart_timer = NULL; 118 }); 119 dispatch_resume(g_restart_timer); 120} 121 122/* 123 * 124 */ 125 126static void 127create_dns_sd(void) 128{ 129 DNSServiceErrorType error; 130 131 heim_assert(g_dnsRef == NULL, "called create when a connection already existed"); 132 133 error = DNSServiceCreateConnection(&g_dnsRef); 134 if (error) { 135 retry_timer(); 136 return; 137 } 138 139 error = DNSServiceSetDispatchQueue(g_dnsRef, g_queue); 140 if (error) { 141 destroy_dns_sd(); 142 retry_timer(); 143 return ; 144 } 145 146 /* Do the first update ourself */ 147 update_all(g_store, NULL, NULL); 148} 149 150static void 151domain_add(const char *domain, const char *realm, int flag) 152{ 153 struct entry *e; 154 155 for (e = g_entries; e != NULL; e = e->next) { 156 if (strcmp(domain, e->domain) == 0 && strcmp(realm, e->realm) == 0) { 157 e->flags |= flag; 158 return; 159 } 160 } 161 162 LOG("Adding realm %s to domain %s", realm, domain); 163 164 e = calloc(1, sizeof(*e)); 165 if (e == NULL) 166 return; 167 e->domain = strdup(domain); 168 e->realm = strdup(realm); 169 if (e->domain == NULL || e->realm == NULL) { 170 free(e->domain); 171 free(e->realm); 172 free(e); 173 return; 174 } 175 e->flags = flag | F_PUSH; /* if we allocate, we push */ 176 e->next = g_entries; 177 g_entries = e; 178} 179 180struct addctx { 181 int flags; 182 const char *realm; 183}; 184 185static void 186domains_add(const void *key, const void *value, void *context) 187{ 188 char *str = CFString2utf8((CFStringRef)value); 189 struct addctx *ctx = context; 190 191 if (str == NULL) 192 return; 193 if (str[0] != '\0') 194 domain_add(str, ctx->realm, F_EXISTS | ctx->flags); 195 free(str); 196} 197 198 199static void 200dnsCallback(DNSServiceRef sdRef __attribute__((unused)), 201 DNSRecordRef RecordRef __attribute__((unused)), 202 DNSServiceFlags flags __attribute__((unused)), 203 DNSServiceErrorType errorCode, 204 void *context __attribute__((unused))) 205{ 206 if (errorCode == kDNSServiceErr_ServiceNotRunning) { 207 destroy_dns_sd(); 208 retry_timer(); 209 } 210} 211 212#ifdef REGISTER_SRV_RR 213 214/* 215 * Register DNS SRV rr for the realm. 216 */ 217 218static const char *register_names[2] = { 219 "_kerberos._tcp", 220 "_kerberos._udp" 221}; 222 223static struct { 224 DNSRecordRef *val; 225 size_t len; 226} srvRefs = { NULL, 0 }; 227 228static void 229register_srv(const char *realm, const char *hostname, int port) 230{ 231 unsigned char target[1024]; 232 int i; 233 int size; 234 235 /* skip registering LKDC realms */ 236 if (strncmp(realm, "LKDC:", 5) == 0) 237 return; 238 239 /* encode SRV-RR */ 240 target[0] = 0; /* priority */ 241 target[1] = 0; /* priority */ 242 target[2] = 0; /* weight */ 243 target[3] = 0; /* weigth */ 244 target[4] = (port >> 8) & 0xff; /* port */ 245 target[5] = (port >> 0) & 0xff; /* port */ 246 247 size = dn_comp(hostname, target + 6, sizeof(target) - 6, NULL, NULL); 248 if (size < 0) 249 return; 250 251 size += 6; 252 253 LOG("register SRV rr for realm %s hostname %s:%d", realm, hostname, port); 254 255 for (i = 0; i < sizeof(register_names)/sizeof(register_names[0]); i++) { 256 char name[kDNSServiceMaxDomainName]; 257 DNSServiceErrorType error; 258 void *ptr; 259 260 ptr = realloc(srvRefs.val, sizeof(srvRefs.val[0]) * (srvRefs.len + 1)); 261 if (ptr == NULL) 262 errx(1, "malloc: out of memory"); 263 srvRefs.val = ptr; 264 265 DNSServiceConstructFullName(name, NULL, register_names[i], realm); 266 267 error = DNSServiceRegisterRecord(g_dnsRef, 268 &srvRefs.val[srvRefs.len], 269 kDNSServiceFlagsUnique | kDNSServiceFlagsShareConnection, 270 0, 271 name, 272 kDNSServiceType_SRV, 273 kDNSServiceClass_IN, 274 size, 275 target, 276 0, 277 dnsCallback, 278 NULL); 279 if (error) { 280 LOG("Failed to register SRV rr for realm %s: %d", realm, error); 281 } else 282 srvRefs.len++; 283 } 284} 285 286static void 287unregister_srv_realms(void) 288{ 289 if (g_dnsRef) { 290 for (i = 0; i < srvRefs.len; i++) 291 DNSServiceRemoveRecord(g_dnsRef, srvRefs.val[i], 0); 292 } 293 free(srvRefs.val); 294 srvRefs.len = 0; 295 srvRefs.val = NULL; 296} 297 298static void 299register_srv_realms(CFStringRef host) 300{ 301 krb5_error_code ret; 302 heim_array_t array; 303 char *hostname; 304 size_t i; 305 306 /* first unregister old names */ 307 308 hostname = CFString2utf8(host); 309 if (hostname == NULL) 310 return; 311 312 array = announce_get_realms(); 313 314 heim_array_iterate(array, ^(heim_object_t item) { 315 char *r = heim_string_copy_utf8(item); 316 register_srv(r, hostname, 88); 317 free(r); 318 }); 319 320 heim_release(array); 321 322 free(hostname); 323} 324#endif /* REGISTER_SRV_RR */ 325 326static void 327update_dns(void) 328{ 329 DNSServiceErrorType error; 330 struct entry **e = &g_entries; 331 char *hostname; 332 333 if (g_hostname == NULL) 334 return; 335 336 hostname = CFString2utf8(g_hostname); 337 if (hostname == NULL) 338 return; 339 340 while (*e != NULL) { 341 /* remove if this wasn't updated */ 342 if (((*e)->flags & F_EXISTS) == 0) { 343 struct entry *drop = *e; 344 *e = (*e)->next; 345 346 LOG("Deleting realm %s from domain %s", 347 drop->realm, drop->domain); 348 349 if (drop->recordRef && g_dnsRef) 350 DNSServiceRemoveRecord(g_dnsRef, drop->recordRef, 0); 351 free(drop->domain); 352 free(drop->realm); 353 free(drop); 354 continue; 355 } 356 if ((*e)->flags & F_PUSH) { 357 struct entry *update = *e; 358 char *dnsdata, *name; 359 size_t len; 360 361 len = strlen(update->realm); 362 asprintf(&dnsdata, "%c%s", (unsigned char)len, update->realm); 363 if (dnsdata == NULL) 364 errx(1, "malloc"); 365 366 asprintf(&name, "_kerberos.%s.%s", hostname, update->domain); 367 if (name == NULL) 368 errx(1, "malloc"); 369 370 if (update->recordRef) { 371 DNSServiceRemoveRecord(g_dnsRef, update->recordRef, 0); 372 update->recordRef = NULL; 373 } 374 375 error = DNSServiceRegisterRecord(g_dnsRef, 376 &update->recordRef, 377 kDNSServiceFlagsShared | kDNSServiceFlagsAllowRemoteQuery, 378 0, 379 name, 380 kDNSServiceType_TXT, 381 kDNSServiceClass_IN, 382 len+1, 383 dnsdata, 384 0, 385 dnsCallback, 386 NULL); 387 free(name); 388 free(dnsdata); 389 if (error) 390 errx(1, "failure to update entry for %s/%s", 391 update->domain, update->realm); 392 } 393 e = &(*e)->next; 394 } 395 free(hostname); 396} 397 398static void 399update_entries(SCDynamicStoreRef store, const char *realm, int flags) 400{ 401 CFDictionaryRef btmm; 402 403 /* we always announce in the local domain */ 404 domain_add("local", realm, F_EXISTS | flags); 405 406 /* announce btmm */ 407 btmm = SCDynamicStoreCopyValue(store, NetworkChangedKey_BackToMyMac); 408 if (btmm) { 409 struct addctx addctx; 410 411 addctx.flags = flags; 412 addctx.realm = realm; 413 414 CFDictionaryApplyFunction(btmm, domains_add, &addctx); 415 CFRelease(btmm); 416 } 417} 418 419static void 420update_all(SCDynamicStoreRef store, CFArrayRef changedKeys, void *info) 421{ 422 heim_array_t array; 423 struct entry *e; 424 CFStringRef host; 425 int flags = 0; 426 427 LOG("something changed, running update"); 428 429 host = SCDynamicStoreCopyLocalHostName(store); 430 if (host == NULL) 431 return; 432 433 if (g_hostname == NULL || CFStringCompare(host, g_hostname, 0) != kCFCompareEqualTo) { 434 if (g_hostname) 435 CFRelease(g_hostname); 436 g_hostname = CFRetain(host); 437 flags = F_PUSH; /* if hostname has changed, force push */ 438 439#ifdef REGISTER_SRV_RR 440 register_srv_realms(g_hostname); 441#endif 442 } 443 444 for (e = g_entries; e != NULL; e = e->next) 445 e->flags &= ~(F_EXISTS|F_PUSH); 446 447 array = announce_get_realms(); 448 449 heim_array_iterate(array, ^(heim_object_t item, int *stop) { 450 char *r = heim_string_copy_utf8(item); 451 update_entries(store, r, flags); 452 free(r); 453 }); 454 455 heim_release(array); 456 457 update_dns(); 458 459 CFRelease(host); 460} 461 462static void 463delete_all(void) 464{ 465 struct entry *e; 466 467 for (e = g_entries; e != NULL; e = e->next) 468 e->flags &= ~(F_EXISTS|F_PUSH); 469 470 update_dns(); 471 if (g_entries != NULL) 472 errx(1, "Failed to remove all bonjour entries"); 473} 474 475static void 476destroy_dns_sd(void) 477{ 478 if (g_dnsRef == NULL) 479 return; 480 481 delete_all(); 482#ifdef REGISTER_SRV_RR 483 unregister_srv_realms(); 484#endif 485 486 DNSServiceRefDeallocate(g_dnsRef); 487 g_dnsRef = NULL; 488} 489 490 491static SCDynamicStoreRef 492register_notification(void) 493{ 494 SCDynamicStoreRef store; 495 CFStringRef computerNameKey; 496 CFMutableArrayRef keys; 497 498 computerNameKey = SCDynamicStoreKeyCreateHostNames(kCFAllocatorDefault); 499 500 store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("Network watcher"), 501 update_all, NULL); 502 if (store == NULL) 503 errx(1, "SCDynamicStoreCreate"); 504 505 keys = CFArrayCreateMutable(kCFAllocatorDefault, 2, &kCFTypeArrayCallBacks); 506 if (keys == NULL) 507 errx(1, "CFArrayCreateMutable"); 508 509 CFArrayAppendValue(keys, computerNameKey); 510 CFArrayAppendValue(keys, NetworkChangedKey_BackToMyMac); 511 512 if (SCDynamicStoreSetNotificationKeys(store, keys, NULL) == false) 513 errx(1, "SCDynamicStoreSetNotificationKeys"); 514 515 CFRelease(computerNameKey); 516 CFRelease(keys); 517 518 if (!SCDynamicStoreSetDispatchQueue(store, g_queue)) 519 errx(1, "SCDynamicStoreSetDispatchQueue"); 520 521 return store; 522} 523#endif 524 525#endif /* __APPLE_TARGET_EMBEDDED__ */ 526 527 528 529void 530bonjour_announce(heim_array_t (*get_realms)(void)) 531{ 532#ifndef __APPLE_TARGET_EMBEDDED__ 533#if defined(__APPLE__) && defined(HAVE_GCD) 534 announce_get_realms = get_realms; 535 536 g_queue = dispatch_queue_create("com.apple.kdc_announce", NULL); 537 if (!g_queue) 538 errx(1, "dispatch_queue_create"); 539 540 g_store = register_notification(); 541 542#if defined(HAVE_GCD) && defined(HAVE_NOTIFY_H) 543 /* 544 * On KDC change notifications, lets re-announce configuration 545 */ 546 { 547 int token; 548 notify_register_dispatch("com.apple.kdc.update", &token, g_queue, ^(int t) { 549 update_all(g_store, NULL, NULL); 550 }); 551 } 552#endif 553 554 create_dns_sd(); 555#endif 556#endif /* __APPLE_TARGET_EMBEDDED__ */ 557 558} 559