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