1226031Sstas/* 2226031Sstas * Copyright (c) 2008 Apple Inc. All Rights Reserved. 3226031Sstas * 4226031Sstas * Export of this software from the United States of America may require 5226031Sstas * a specific license from the United States Government. It is the 6226031Sstas * responsibility of any person or organization contemplating export to 7226031Sstas * obtain such a license before exporting. 8226031Sstas * 9226031Sstas * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and 10226031Sstas * distribute this software and its documentation for any purpose and 11226031Sstas * without fee is hereby granted, provided that the above copyright 12226031Sstas * notice appear in all copies and that both that copyright notice and 13226031Sstas * this permission notice appear in supporting documentation, and that 14226031Sstas * the name of Apple Inc. not be used in advertising or publicity pertaining 15226031Sstas * to distribution of the software without specific, written prior 16226031Sstas * permission. Apple Inc. makes no representations about the suitability of 17226031Sstas * this software for any purpose. It is provided "as is" without express 18226031Sstas * or implied warranty. 19226031Sstas * 20226031Sstas * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR 21226031Sstas * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 22226031Sstas * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. 23226031Sstas * 24226031Sstas */ 25226031Sstas 26226031Sstas#include "kdc_locl.h" 27226031Sstas 28226031Sstas#if defined(__APPLE__) && defined(HAVE_GCD) 29226031Sstas 30226031Sstas#include <CoreFoundation/CoreFoundation.h> 31226031Sstas#include <SystemConfiguration/SCDynamicStore.h> 32226031Sstas#include <SystemConfiguration/SCDynamicStoreCopySpecific.h> 33226031Sstas#include <SystemConfiguration/SCDynamicStoreKey.h> 34226031Sstas 35226031Sstas#include <dispatch/dispatch.h> 36226031Sstas 37226031Sstas#include <asl.h> 38226031Sstas#include <resolv.h> 39226031Sstas 40226031Sstas#include <dns_sd.h> 41226031Sstas#include <err.h> 42226031Sstas 43226031Sstasstatic krb5_kdc_configuration *announce_config; 44226031Sstasstatic krb5_context announce_context; 45226031Sstas 46226031Sstasstruct entry { 47226031Sstas DNSRecordRef recordRef; 48226031Sstas char *domain; 49226031Sstas char *realm; 50226031Sstas#define F_EXISTS 1 51226031Sstas#define F_PUSH 2 52226031Sstas int flags; 53226031Sstas struct entry *next; 54226031Sstas}; 55226031Sstas 56226031Sstas/* #define REGISTER_SRV_RR */ 57226031Sstas 58226031Sstasstatic struct entry *g_entries = NULL; 59226031Sstasstatic CFStringRef g_hostname = NULL; 60226031Sstasstatic DNSServiceRef g_dnsRef = NULL; 61226031Sstasstatic SCDynamicStoreRef g_store = NULL; 62226031Sstasstatic dispatch_queue_t g_queue = NULL; 63226031Sstas 64226031Sstas#define LOG(...) asl_log(NULL, NULL, ASL_LEVEL_INFO, __VA_ARGS__) 65226031Sstas 66226031Sstasstatic void create_dns_sd(void); 67226031Sstasstatic void destroy_dns_sd(void); 68226031Sstasstatic void update_all(SCDynamicStoreRef, CFArrayRef, void *); 69226031Sstas 70226031Sstas 71226031Sstas/* parameters */ 72226031Sstasstatic CFStringRef NetworkChangedKey_BackToMyMac = CFSTR("Setup:/Network/BackToMyMac"); 73226031Sstas 74226031Sstas 75226031Sstasstatic char * 76226031SstasCFString2utf8(CFStringRef string) 77226031Sstas{ 78226031Sstas size_t size; 79226031Sstas char *str; 80226031Sstas 81226031Sstas size = 1 + CFStringGetMaximumSizeForEncoding(CFStringGetLength(string), kCFStringEncodingUTF8); 82226031Sstas str = malloc(size); 83226031Sstas if (str == NULL) 84226031Sstas return NULL; 85226031Sstas 86226031Sstas if (CFStringGetCString(string, str, size, kCFStringEncodingUTF8) == false) { 87226031Sstas free(str); 88226031Sstas return NULL; 89226031Sstas } 90226031Sstas return str; 91226031Sstas} 92226031Sstas 93226031Sstas/* 94226031Sstas * 95226031Sstas */ 96226031Sstas 97226031Sstasstatic void 98226031Sstasretry_timer(void) 99226031Sstas{ 100226031Sstas dispatch_source_t s; 101226031Sstas dispatch_time_t t; 102226031Sstas 103226031Sstas s = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 104226031Sstas 0, 0, g_queue); 105226031Sstas t = dispatch_time(DISPATCH_TIME_NOW, 5ull * NSEC_PER_SEC); 106226031Sstas dispatch_source_set_timer(s, t, 0, NSEC_PER_SEC); 107226031Sstas dispatch_source_set_event_handler(s, ^{ 108226031Sstas create_dns_sd(); 109226031Sstas dispatch_release(s); 110226031Sstas }); 111226031Sstas dispatch_resume(s); 112226031Sstas} 113226031Sstas 114226031Sstas/* 115226031Sstas * 116226031Sstas */ 117226031Sstas 118226031Sstasstatic void 119226031Sstascreate_dns_sd(void) 120226031Sstas{ 121226031Sstas DNSServiceErrorType error; 122226031Sstas dispatch_source_t s; 123226031Sstas 124226031Sstas error = DNSServiceCreateConnection(&g_dnsRef); 125226031Sstas if (error) { 126226031Sstas retry_timer(); 127226031Sstas return; 128226031Sstas } 129226031Sstas 130226031Sstas dispatch_suspend(g_queue); 131226031Sstas 132226031Sstas s = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, 133226031Sstas DNSServiceRefSockFD(g_dnsRef), 134226031Sstas 0, g_queue); 135226031Sstas 136226031Sstas dispatch_source_set_event_handler(s, ^{ 137226031Sstas DNSServiceErrorType ret = DNSServiceProcessResult(g_dnsRef); 138226031Sstas /* on error tear down and set timer to recreate */ 139226031Sstas if (ret != kDNSServiceErr_NoError && ret != kDNSServiceErr_Transient) { 140226031Sstas dispatch_source_cancel(s); 141226031Sstas } 142226031Sstas }); 143226031Sstas 144226031Sstas dispatch_source_set_cancel_handler(s, ^{ 145226031Sstas destroy_dns_sd(); 146226031Sstas retry_timer(); 147226031Sstas dispatch_release(s); 148226031Sstas }); 149226031Sstas 150226031Sstas dispatch_resume(s); 151226031Sstas 152226031Sstas /* Do the first update ourself */ 153226031Sstas update_all(g_store, NULL, NULL); 154226031Sstas dispatch_resume(g_queue); 155226031Sstas} 156226031Sstas 157226031Sstasstatic void 158226031Sstasdomain_add(const char *domain, const char *realm, int flag) 159226031Sstas{ 160226031Sstas struct entry *e; 161226031Sstas 162226031Sstas for (e = g_entries; e != NULL; e = e->next) { 163226031Sstas if (strcmp(domain, e->domain) == 0 && strcmp(realm, e->realm) == 0) { 164226031Sstas e->flags |= flag; 165226031Sstas return; 166226031Sstas } 167226031Sstas } 168226031Sstas 169226031Sstas LOG("Adding realm %s to domain %s", realm, domain); 170226031Sstas 171226031Sstas e = calloc(1, sizeof(*e)); 172226031Sstas if (e == NULL) 173226031Sstas return; 174226031Sstas e->domain = strdup(domain); 175226031Sstas e->realm = strdup(realm); 176226031Sstas if (e->domain == NULL || e->realm == NULL) { 177226031Sstas free(e->domain); 178226031Sstas free(e->realm); 179226031Sstas free(e); 180226031Sstas return; 181226031Sstas } 182226031Sstas e->flags = flag | F_PUSH; /* if we allocate, we push */ 183226031Sstas e->next = g_entries; 184226031Sstas g_entries = e; 185226031Sstas} 186226031Sstas 187226031Sstasstruct addctx { 188226031Sstas int flags; 189226031Sstas const char *realm; 190226031Sstas}; 191226031Sstas 192226031Sstasstatic void 193226031Sstasdomains_add(const void *key, const void *value, void *context) 194226031Sstas{ 195226031Sstas char *str = CFString2utf8((CFStringRef)value); 196226031Sstas struct addctx *ctx = context; 197226031Sstas 198226031Sstas if (str == NULL) 199226031Sstas return; 200226031Sstas if (str[0] != '\0') 201226031Sstas domain_add(str, ctx->realm, F_EXISTS | ctx->flags); 202226031Sstas free(str); 203226031Sstas} 204226031Sstas 205226031Sstas 206226031Sstasstatic void 207226031SstasdnsCallback(DNSServiceRef sdRef __attribute__((unused)), 208226031Sstas DNSRecordRef RecordRef __attribute__((unused)), 209226031Sstas DNSServiceFlags flags __attribute__((unused)), 210226031Sstas DNSServiceErrorType errorCode __attribute__((unused)), 211226031Sstas void *context __attribute__((unused))) 212226031Sstas{ 213226031Sstas} 214226031Sstas 215226031Sstas#ifdef REGISTER_SRV_RR 216226031Sstas 217226031Sstas/* 218226031Sstas * Register DNS SRV rr for the realm. 219226031Sstas */ 220226031Sstas 221226031Sstasstatic const char *register_names[2] = { 222226031Sstas "_kerberos._tcp", 223226031Sstas "_kerberos._udp" 224226031Sstas}; 225226031Sstas 226226031Sstasstatic struct { 227226031Sstas DNSRecordRef *val; 228226031Sstas size_t len; 229226031Sstas} srvRefs = { NULL, 0 }; 230226031Sstas 231226031Sstasstatic void 232226031Sstasregister_srv(const char *realm, const char *hostname, int port) 233226031Sstas{ 234226031Sstas unsigned char target[1024]; 235226031Sstas int i; 236226031Sstas int size; 237226031Sstas 238226031Sstas /* skip registering LKDC realms */ 239226031Sstas if (strncmp(realm, "LKDC:", 5) == 0) 240226031Sstas return; 241226031Sstas 242226031Sstas /* encode SRV-RR */ 243226031Sstas target[0] = 0; /* priority */ 244226031Sstas target[1] = 0; /* priority */ 245226031Sstas target[2] = 0; /* weight */ 246226031Sstas target[3] = 0; /* weigth */ 247226031Sstas target[4] = (port >> 8) & 0xff; /* port */ 248226031Sstas target[5] = (port >> 0) & 0xff; /* port */ 249226031Sstas 250226031Sstas size = dn_comp(hostname, target + 6, sizeof(target) - 6, NULL, NULL); 251226031Sstas if (size < 0) 252226031Sstas return; 253226031Sstas 254226031Sstas size += 6; 255226031Sstas 256226031Sstas LOG("register SRV rr for realm %s hostname %s:%d", realm, hostname, port); 257226031Sstas 258226031Sstas for (i = 0; i < sizeof(register_names)/sizeof(register_names[0]); i++) { 259226031Sstas char name[kDNSServiceMaxDomainName]; 260226031Sstas DNSServiceErrorType error; 261226031Sstas void *ptr; 262226031Sstas 263226031Sstas ptr = realloc(srvRefs.val, sizeof(srvRefs.val[0]) * (srvRefs.len + 1)); 264226031Sstas if (ptr == NULL) 265226031Sstas errx(1, "malloc: out of memory"); 266226031Sstas srvRefs.val = ptr; 267226031Sstas 268226031Sstas DNSServiceConstructFullName(name, NULL, register_names[i], realm); 269226031Sstas 270226031Sstas error = DNSServiceRegisterRecord(g_dnsRef, 271226031Sstas &srvRefs.val[srvRefs.len], 272226031Sstas kDNSServiceFlagsUnique | kDNSServiceFlagsShareConnection, 273226031Sstas 0, 274226031Sstas name, 275226031Sstas kDNSServiceType_SRV, 276226031Sstas kDNSServiceClass_IN, 277226031Sstas size, 278226031Sstas target, 279226031Sstas 0, 280226031Sstas dnsCallback, 281226031Sstas NULL); 282226031Sstas if (error) { 283226031Sstas LOG("Failed to register SRV rr for realm %s: %d", realm, error); 284226031Sstas } else 285226031Sstas srvRefs.len++; 286226031Sstas } 287226031Sstas} 288226031Sstas 289226031Sstasstatic void 290226031Sstasunregister_srv_realms(void) 291226031Sstas{ 292226031Sstas if (g_dnsRef) { 293226031Sstas for (i = 0; i < srvRefs.len; i++) 294226031Sstas DNSServiceRemoveRecord(g_dnsRef, srvRefs.val[i], 0); 295226031Sstas } 296226031Sstas free(srvRefs.val); 297226031Sstas srvRefs.len = 0; 298226031Sstas srvRefs.val = NULL; 299226031Sstas} 300226031Sstas 301226031Sstasstatic void 302226031Sstasregister_srv_realms(CFStringRef host) 303226031Sstas{ 304226031Sstas krb5_error_code ret; 305226031Sstas char *hostname; 306226031Sstas size_t i; 307226031Sstas 308226031Sstas /* first unregister old names */ 309226031Sstas 310226031Sstas hostname = CFString2utf8(host); 311226031Sstas if (hostname == NULL) 312226031Sstas return; 313226031Sstas 314226031Sstas for(i = 0; i < announce_config->num_db; i++) { 315226031Sstas char **realms, **r; 316226031Sstas 317226031Sstas if (announce_config->db[i]->hdb_get_realms == NULL) 318226031Sstas continue; 319226031Sstas 320226031Sstas ret = (announce_config->db[i]->hdb_get_realms)(announce_context, &realms); 321226031Sstas if (ret == 0) { 322226031Sstas for (r = realms; r && *r; r++) 323226031Sstas register_srv(*r, hostname, 88); 324226031Sstas krb5_free_host_realm(announce_context, realms); 325226031Sstas } 326226031Sstas } 327226031Sstas 328226031Sstas free(hostname); 329226031Sstas} 330226031Sstas#endif /* REGISTER_SRV_RR */ 331226031Sstas 332226031Sstasstatic void 333226031Sstasupdate_dns(void) 334226031Sstas{ 335226031Sstas DNSServiceErrorType error; 336226031Sstas struct entry **e = &g_entries; 337226031Sstas char *hostname; 338226031Sstas 339226031Sstas hostname = CFString2utf8(g_hostname); 340226031Sstas if (hostname == NULL) 341226031Sstas return; 342226031Sstas 343226031Sstas while (*e != NULL) { 344226031Sstas /* remove if this wasn't updated */ 345226031Sstas if (((*e)->flags & F_EXISTS) == 0) { 346226031Sstas struct entry *drop = *e; 347226031Sstas *e = (*e)->next; 348226031Sstas 349226031Sstas LOG("Deleting realm %s from domain %s", 350226031Sstas drop->realm, drop->domain); 351226031Sstas 352226031Sstas if (drop->recordRef && g_dnsRef) 353226031Sstas DNSServiceRemoveRecord(g_dnsRef, drop->recordRef, 0); 354226031Sstas free(drop->domain); 355226031Sstas free(drop->realm); 356226031Sstas free(drop); 357226031Sstas continue; 358226031Sstas } 359226031Sstas if ((*e)->flags & F_PUSH) { 360226031Sstas struct entry *update = *e; 361226031Sstas char *dnsdata, *name; 362226031Sstas size_t len; 363226031Sstas 364226031Sstas len = strlen(update->realm); 365226031Sstas asprintf(&dnsdata, "%c%s", (int)len, update->realm); 366226031Sstas if (dnsdata == NULL) 367226031Sstas errx(1, "malloc"); 368226031Sstas 369226031Sstas asprintf(&name, "_kerberos.%s.%s", hostname, update->domain); 370226031Sstas if (name == NULL) 371226031Sstas errx(1, "malloc"); 372226031Sstas 373226031Sstas if (update->recordRef) 374226031Sstas DNSServiceRemoveRecord(g_dnsRef, update->recordRef, 0); 375226031Sstas 376226031Sstas error = DNSServiceRegisterRecord(g_dnsRef, 377226031Sstas &update->recordRef, 378226031Sstas kDNSServiceFlagsShared | kDNSServiceFlagsAllowRemoteQuery, 379226031Sstas 0, 380226031Sstas name, 381226031Sstas kDNSServiceType_TXT, 382226031Sstas kDNSServiceClass_IN, 383226031Sstas len+1, 384226031Sstas dnsdata, 385226031Sstas 0, 386226031Sstas dnsCallback, 387226031Sstas NULL); 388226031Sstas free(name); 389226031Sstas free(dnsdata); 390226031Sstas if (error) 391226031Sstas errx(1, "failure to update entry for %s/%s", 392226031Sstas update->domain, update->realm); 393226031Sstas } 394226031Sstas e = &(*e)->next; 395226031Sstas } 396226031Sstas free(hostname); 397226031Sstas} 398226031Sstas 399226031Sstasstatic void 400226031Sstasupdate_entries(SCDynamicStoreRef store, const char *realm, int flags) 401226031Sstas{ 402226031Sstas CFDictionaryRef btmm; 403226031Sstas 404226031Sstas /* we always announce in the local domain */ 405226031Sstas domain_add("local", realm, F_EXISTS | flags); 406226031Sstas 407226031Sstas /* announce btmm */ 408226031Sstas btmm = SCDynamicStoreCopyValue(store, NetworkChangedKey_BackToMyMac); 409226031Sstas if (btmm) { 410226031Sstas struct addctx addctx; 411226031Sstas 412226031Sstas addctx.flags = flags; 413226031Sstas addctx.realm = realm; 414226031Sstas 415226031Sstas CFDictionaryApplyFunction(btmm, domains_add, &addctx); 416226031Sstas CFRelease(btmm); 417226031Sstas } 418226031Sstas} 419226031Sstas 420226031Sstasstatic void 421226031Sstasupdate_all(SCDynamicStoreRef store, CFArrayRef changedKeys, void *info) 422226031Sstas{ 423226031Sstas struct entry *e; 424226031Sstas CFStringRef host; 425226031Sstas int i, flags = 0; 426226031Sstas 427226031Sstas LOG("something changed, running update"); 428226031Sstas 429226031Sstas host = SCDynamicStoreCopyLocalHostName(store); 430226031Sstas if (host == NULL) 431226031Sstas return; 432226031Sstas 433226031Sstas if (g_hostname == NULL || CFStringCompare(host, g_hostname, 0) != kCFCompareEqualTo) { 434226031Sstas if (g_hostname) 435226031Sstas CFRelease(g_hostname); 436226031Sstas g_hostname = CFRetain(host); 437226031Sstas flags = F_PUSH; /* if hostname has changed, force push */ 438226031Sstas 439226031Sstas#ifdef REGISTER_SRV_RR 440226031Sstas register_srv_realms(g_hostname); 441226031Sstas#endif 442226031Sstas } 443226031Sstas 444226031Sstas for (e = g_entries; e != NULL; e = e->next) 445226031Sstas e->flags &= ~(F_EXISTS|F_PUSH); 446226031Sstas 447226031Sstas for(i = 0; i < announce_config->num_db; i++) { 448226031Sstas krb5_error_code ret; 449226031Sstas char **realms, **r; 450226031Sstas 451226031Sstas if (announce_config->db[i]->hdb_get_realms == NULL) 452226031Sstas continue; 453226031Sstas 454226031Sstas ret = (announce_config->db[i]->hdb_get_realms)(announce_context, announce_config->db[i], &realms); 455226031Sstas if (ret == 0) { 456226031Sstas for (r = realms; r && *r; r++) 457226031Sstas update_entries(store, *r, flags); 458226031Sstas krb5_free_host_realm(announce_context, realms); 459226031Sstas } 460226031Sstas } 461226031Sstas 462226031Sstas update_dns(); 463226031Sstas 464226031Sstas CFRelease(host); 465226031Sstas} 466226031Sstas 467226031Sstasstatic void 468226031Sstasdelete_all(void) 469226031Sstas{ 470226031Sstas struct entry *e; 471226031Sstas 472226031Sstas for (e = g_entries; e != NULL; e = e->next) 473226031Sstas e->flags &= ~(F_EXISTS|F_PUSH); 474226031Sstas 475226031Sstas update_dns(); 476226031Sstas if (g_entries != NULL) 477226031Sstas errx(1, "Failed to remove all bonjour entries"); 478226031Sstas} 479226031Sstas 480226031Sstasstatic void 481226031Sstasdestroy_dns_sd(void) 482226031Sstas{ 483226031Sstas if (g_dnsRef == NULL) 484226031Sstas return; 485226031Sstas 486226031Sstas delete_all(); 487226031Sstas#ifdef REGISTER_SRV_RR 488226031Sstas unregister_srv_realms(); 489226031Sstas#endif 490226031Sstas 491226031Sstas DNSServiceRefDeallocate(g_dnsRef); 492226031Sstas g_dnsRef = NULL; 493226031Sstas} 494226031Sstas 495226031Sstas 496226031Sstasstatic SCDynamicStoreRef 497226031Sstasregister_notification(void) 498226031Sstas{ 499226031Sstas SCDynamicStoreRef store; 500226031Sstas CFStringRef computerNameKey; 501226031Sstas CFMutableArrayRef keys; 502226031Sstas 503226031Sstas computerNameKey = SCDynamicStoreKeyCreateHostNames(kCFAllocatorDefault); 504226031Sstas 505226031Sstas store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("Network watcher"), 506226031Sstas update_all, NULL); 507226031Sstas if (store == NULL) 508226031Sstas errx(1, "SCDynamicStoreCreate"); 509226031Sstas 510226031Sstas keys = CFArrayCreateMutable(kCFAllocatorDefault, 2, &kCFTypeArrayCallBacks); 511226031Sstas if (keys == NULL) 512226031Sstas errx(1, "CFArrayCreateMutable"); 513226031Sstas 514226031Sstas CFArrayAppendValue(keys, computerNameKey); 515226031Sstas CFArrayAppendValue(keys, NetworkChangedKey_BackToMyMac); 516226031Sstas 517226031Sstas if (SCDynamicStoreSetNotificationKeys(store, keys, NULL) == false) 518226031Sstas errx(1, "SCDynamicStoreSetNotificationKeys"); 519226031Sstas 520226031Sstas CFRelease(computerNameKey); 521226031Sstas CFRelease(keys); 522226031Sstas 523226031Sstas if (!SCDynamicStoreSetDispatchQueue(store, g_queue)) 524226031Sstas errx(1, "SCDynamicStoreSetDispatchQueue"); 525226031Sstas 526226031Sstas return store; 527226031Sstas} 528226031Sstas#endif 529226031Sstas 530226031Sstasvoid 531226031Sstasbonjour_announce(krb5_context context, krb5_kdc_configuration *config) 532226031Sstas{ 533226031Sstas#if defined(__APPLE__) && defined(HAVE_GCD) 534226031Sstas g_queue = dispatch_queue_create("com.apple.kdc_announce", NULL); 535226031Sstas if (!g_queue) 536226031Sstas errx(1, "dispatch_queue_create"); 537226031Sstas 538226031Sstas g_store = register_notification(); 539226031Sstas announce_config = config; 540226031Sstas announce_context = context; 541226031Sstas 542226031Sstas create_dns_sd(); 543226031Sstas#endif 544226031Sstas} 545