1 /* 2 * Copyright (c) 2008 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 static krb5_kdc_configuration *announce_config; 44 static krb5_context announce_context; 45 46 struct entry { 47 DNSRecordRef recordRef; 48 char *domain; 49 char *realm; 50 #define F_EXISTS 1 51 #define F_PUSH 2 52 int flags; 53 struct entry *next; 54 }; 55 56 /* #define REGISTER_SRV_RR */ 57 58 static struct entry *g_entries = NULL; 59 static CFStringRef g_hostname = NULL; 60 static DNSServiceRef g_dnsRef = NULL; 61 static SCDynamicStoreRef g_store = NULL; 62 static dispatch_queue_t g_queue = NULL; 63 64 #define LOG(...) asl_log(NULL, NULL, ASL_LEVEL_INFO, __VA_ARGS__) 65 66 static void create_dns_sd(void); 67 static void destroy_dns_sd(void); 68 static void update_all(SCDynamicStoreRef, CFArrayRef, void *); 69 70 71 /* parameters */ 72 static CFStringRef NetworkChangedKey_BackToMyMac = CFSTR("Setup:/Network/BackToMyMac"); 73 74 75 static char * 76 CFString2utf8(CFStringRef string) 77 { 78 size_t size; 79 char *str; 80 81 size = 1 + CFStringGetMaximumSizeForEncoding(CFStringGetLength(string), kCFStringEncodingUTF8); 82 str = malloc(size); 83 if (str == NULL) 84 return NULL; 85 86 if (CFStringGetCString(string, str, size, kCFStringEncodingUTF8) == false) { 87 free(str); 88 return NULL; 89 } 90 return str; 91 } 92 93 /* 94 * 95 */ 96 97 static void 98 retry_timer(void) 99 { 100 dispatch_source_t s; 101 dispatch_time_t t; 102 103 s = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 104 0, 0, g_queue); 105 t = dispatch_time(DISPATCH_TIME_NOW, 5ull * NSEC_PER_SEC); 106 dispatch_source_set_timer(s, t, 0, NSEC_PER_SEC); 107 dispatch_source_set_event_handler(s, ^{ 108 create_dns_sd(); 109 dispatch_release(s); 110 }); 111 dispatch_resume(s); 112 } 113 114 /* 115 * 116 */ 117 118 static void 119 create_dns_sd(void) 120 { 121 DNSServiceErrorType error; 122 dispatch_source_t s; 123 124 error = DNSServiceCreateConnection(&g_dnsRef); 125 if (error) { 126 retry_timer(); 127 return; 128 } 129 130 dispatch_suspend(g_queue); 131 132 s = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, 133 DNSServiceRefSockFD(g_dnsRef), 134 0, g_queue); 135 136 dispatch_source_set_event_handler(s, ^{ 137 DNSServiceErrorType ret = DNSServiceProcessResult(g_dnsRef); 138 /* on error tear down and set timer to recreate */ 139 if (ret != kDNSServiceErr_NoError && ret != kDNSServiceErr_Transient) { 140 dispatch_source_cancel(s); 141 } 142 }); 143 144 dispatch_source_set_cancel_handler(s, ^{ 145 destroy_dns_sd(); 146 retry_timer(); 147 dispatch_release(s); 148 }); 149 150 dispatch_resume(s); 151 152 /* Do the first update ourself */ 153 update_all(g_store, NULL, NULL); 154 dispatch_resume(g_queue); 155 } 156 157 static void 158 domain_add(const char *domain, const char *realm, int flag) 159 { 160 struct entry *e; 161 162 for (e = g_entries; e != NULL; e = e->next) { 163 if (strcmp(domain, e->domain) == 0 && strcmp(realm, e->realm) == 0) { 164 e->flags |= flag; 165 return; 166 } 167 } 168 169 LOG("Adding realm %s to domain %s", realm, domain); 170 171 e = calloc(1, sizeof(*e)); 172 if (e == NULL) 173 return; 174 e->domain = strdup(domain); 175 e->realm = strdup(realm); 176 if (e->domain == NULL || e->realm == NULL) { 177 free(e->domain); 178 free(e->realm); 179 free(e); 180 return; 181 } 182 e->flags = flag | F_PUSH; /* if we allocate, we push */ 183 e->next = g_entries; 184 g_entries = e; 185 } 186 187 struct addctx { 188 int flags; 189 const char *realm; 190 }; 191 192 static void 193 domains_add(const void *key, const void *value, void *context) 194 { 195 char *str = CFString2utf8((CFStringRef)value); 196 struct addctx *ctx = context; 197 198 if (str == NULL) 199 return; 200 if (str[0] != '\0') 201 domain_add(str, ctx->realm, F_EXISTS | ctx->flags); 202 free(str); 203 } 204 205 206 static void 207 dnsCallback(DNSServiceRef sdRef __attribute__((unused)), 208 DNSRecordRef RecordRef __attribute__((unused)), 209 DNSServiceFlags flags __attribute__((unused)), 210 DNSServiceErrorType errorCode __attribute__((unused)), 211 void *context __attribute__((unused))) 212 { 213 } 214 215 #ifdef REGISTER_SRV_RR 216 217 /* 218 * Register DNS SRV rr for the realm. 219 */ 220 221 static const char *register_names[2] = { 222 "_kerberos._tcp", 223 "_kerberos._udp" 224 }; 225 226 static struct { 227 DNSRecordRef *val; 228 size_t len; 229 } srvRefs = { NULL, 0 }; 230 231 static void 232 register_srv(const char *realm, const char *hostname, int port) 233 { 234 unsigned char target[1024]; 235 int i; 236 int size; 237 238 /* skip registering LKDC realms */ 239 if (strncmp(realm, "LKDC:", 5) == 0) 240 return; 241 242 /* encode SRV-RR */ 243 target[0] = 0; /* priority */ 244 target[1] = 0; /* priority */ 245 target[2] = 0; /* weight */ 246 target[3] = 0; /* weigth */ 247 target[4] = (port >> 8) & 0xff; /* port */ 248 target[5] = (port >> 0) & 0xff; /* port */ 249 250 size = dn_comp(hostname, target + 6, sizeof(target) - 6, NULL, NULL); 251 if (size < 0) 252 return; 253 254 size += 6; 255 256 LOG("register SRV rr for realm %s hostname %s:%d", realm, hostname, port); 257 258 for (i = 0; i < sizeof(register_names)/sizeof(register_names[0]); i++) { 259 char name[kDNSServiceMaxDomainName]; 260 DNSServiceErrorType error; 261 void *ptr; 262 263 ptr = realloc(srvRefs.val, sizeof(srvRefs.val[0]) * (srvRefs.len + 1)); 264 if (ptr == NULL) 265 errx(1, "malloc: out of memory"); 266 srvRefs.val = ptr; 267 268 DNSServiceConstructFullName(name, NULL, register_names[i], realm); 269 270 error = DNSServiceRegisterRecord(g_dnsRef, 271 &srvRefs.val[srvRefs.len], 272 kDNSServiceFlagsUnique | kDNSServiceFlagsShareConnection, 273 0, 274 name, 275 kDNSServiceType_SRV, 276 kDNSServiceClass_IN, 277 size, 278 target, 279 0, 280 dnsCallback, 281 NULL); 282 if (error) { 283 LOG("Failed to register SRV rr for realm %s: %d", realm, error); 284 } else 285 srvRefs.len++; 286 } 287 } 288 289 static void 290 unregister_srv_realms(void) 291 { 292 if (g_dnsRef) { 293 for (i = 0; i < srvRefs.len; i++) 294 DNSServiceRemoveRecord(g_dnsRef, srvRefs.val[i], 0); 295 } 296 free(srvRefs.val); 297 srvRefs.len = 0; 298 srvRefs.val = NULL; 299 } 300 301 static void 302 register_srv_realms(CFStringRef host) 303 { 304 krb5_error_code ret; 305 char *hostname; 306 size_t i; 307 308 /* first unregister old names */ 309 310 hostname = CFString2utf8(host); 311 if (hostname == NULL) 312 return; 313 314 for(i = 0; i < announce_config->num_db; i++) { 315 char **realms, **r; 316 317 if (announce_config->db[i]->hdb_get_realms == NULL) 318 continue; 319 320 ret = (announce_config->db[i]->hdb_get_realms)(announce_context, &realms); 321 if (ret == 0) { 322 for (r = realms; r && *r; r++) 323 register_srv(*r, hostname, 88); 324 krb5_free_host_realm(announce_context, realms); 325 } 326 } 327 328 free(hostname); 329 } 330 #endif /* REGISTER_SRV_RR */ 331 332 static void 333 update_dns(void) 334 { 335 DNSServiceErrorType error; 336 struct entry **e = &g_entries; 337 char *hostname; 338 339 hostname = CFString2utf8(g_hostname); 340 if (hostname == NULL) 341 return; 342 343 while (*e != NULL) { 344 /* remove if this wasn't updated */ 345 if (((*e)->flags & F_EXISTS) == 0) { 346 struct entry *drop = *e; 347 *e = (*e)->next; 348 349 LOG("Deleting realm %s from domain %s", 350 drop->realm, drop->domain); 351 352 if (drop->recordRef && g_dnsRef) 353 DNSServiceRemoveRecord(g_dnsRef, drop->recordRef, 0); 354 free(drop->domain); 355 free(drop->realm); 356 free(drop); 357 continue; 358 } 359 if ((*e)->flags & F_PUSH) { 360 struct entry *update = *e; 361 char *dnsdata, *name; 362 size_t len; 363 364 len = strlen(update->realm); 365 asprintf(&dnsdata, "%c%s", (int)len, update->realm); 366 if (dnsdata == NULL) 367 errx(1, "malloc"); 368 369 asprintf(&name, "_kerberos.%s.%s", hostname, update->domain); 370 if (name == NULL) 371 errx(1, "malloc"); 372 373 if (update->recordRef) 374 DNSServiceRemoveRecord(g_dnsRef, update->recordRef, 0); 375 376 error = DNSServiceRegisterRecord(g_dnsRef, 377 &update->recordRef, 378 kDNSServiceFlagsShared | kDNSServiceFlagsAllowRemoteQuery, 379 0, 380 name, 381 kDNSServiceType_TXT, 382 kDNSServiceClass_IN, 383 len+1, 384 dnsdata, 385 0, 386 dnsCallback, 387 NULL); 388 free(name); 389 free(dnsdata); 390 if (error) 391 errx(1, "failure to update entry for %s/%s", 392 update->domain, update->realm); 393 } 394 e = &(*e)->next; 395 } 396 free(hostname); 397 } 398 399 static void 400 update_entries(SCDynamicStoreRef store, const char *realm, int flags) 401 { 402 CFDictionaryRef btmm; 403 404 /* we always announce in the local domain */ 405 domain_add("local", realm, F_EXISTS | flags); 406 407 /* announce btmm */ 408 btmm = SCDynamicStoreCopyValue(store, NetworkChangedKey_BackToMyMac); 409 if (btmm) { 410 struct addctx addctx; 411 412 addctx.flags = flags; 413 addctx.realm = realm; 414 415 CFDictionaryApplyFunction(btmm, domains_add, &addctx); 416 CFRelease(btmm); 417 } 418 } 419 420 static void 421 update_all(SCDynamicStoreRef store, CFArrayRef changedKeys, void *info) 422 { 423 struct entry *e; 424 CFStringRef host; 425 int i, 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 for(i = 0; i < announce_config->num_db; i++) { 448 krb5_error_code ret; 449 char **realms, **r; 450 451 if (announce_config->db[i]->hdb_get_realms == NULL) 452 continue; 453 454 ret = (announce_config->db[i]->hdb_get_realms)(announce_context, announce_config->db[i], &realms); 455 if (ret == 0) { 456 for (r = realms; r && *r; r++) 457 update_entries(store, *r, flags); 458 krb5_free_host_realm(announce_context, realms); 459 } 460 } 461 462 update_dns(); 463 464 CFRelease(host); 465 } 466 467 static void 468 delete_all(void) 469 { 470 struct entry *e; 471 472 for (e = g_entries; e != NULL; e = e->next) 473 e->flags &= ~(F_EXISTS|F_PUSH); 474 475 update_dns(); 476 if (g_entries != NULL) 477 errx(1, "Failed to remove all bonjour entries"); 478 } 479 480 static void 481 destroy_dns_sd(void) 482 { 483 if (g_dnsRef == NULL) 484 return; 485 486 delete_all(); 487 #ifdef REGISTER_SRV_RR 488 unregister_srv_realms(); 489 #endif 490 491 DNSServiceRefDeallocate(g_dnsRef); 492 g_dnsRef = NULL; 493 } 494 495 496 static SCDynamicStoreRef 497 register_notification(void) 498 { 499 SCDynamicStoreRef store; 500 CFStringRef computerNameKey; 501 CFMutableArrayRef keys; 502 503 computerNameKey = SCDynamicStoreKeyCreateHostNames(kCFAllocatorDefault); 504 505 store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("Network watcher"), 506 update_all, NULL); 507 if (store == NULL) 508 errx(1, "SCDynamicStoreCreate"); 509 510 keys = CFArrayCreateMutable(kCFAllocatorDefault, 2, &kCFTypeArrayCallBacks); 511 if (keys == NULL) 512 errx(1, "CFArrayCreateMutable"); 513 514 CFArrayAppendValue(keys, computerNameKey); 515 CFArrayAppendValue(keys, NetworkChangedKey_BackToMyMac); 516 517 if (SCDynamicStoreSetNotificationKeys(store, keys, NULL) == false) 518 errx(1, "SCDynamicStoreSetNotificationKeys"); 519 520 CFRelease(computerNameKey); 521 CFRelease(keys); 522 523 if (!SCDynamicStoreSetDispatchQueue(store, g_queue)) 524 errx(1, "SCDynamicStoreSetDispatchQueue"); 525 526 return store; 527 } 528 #endif 529 530 void 531 bonjour_announce(krb5_context context, krb5_kdc_configuration *config) 532 { 533 #if defined(__APPLE__) && defined(HAVE_GCD) 534 g_queue = dispatch_queue_create("com.apple.kdc_announce", NULL); 535 if (!g_queue) 536 errx(1, "dispatch_queue_create"); 537 538 g_store = register_notification(); 539 announce_config = config; 540 announce_context = context; 541 542 create_dns_sd(); 543 #endif 544 } 545