1/* $NetBSD: announce.c,v 1.2 2017/01/28 21:31:44 christos 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 45static krb5_kdc_configuration *announce_config; 46static krb5_context announce_context; 47 48struct 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 60static struct entry *g_entries = NULL; 61static CFStringRef g_hostname = NULL; 62static DNSServiceRef g_dnsRef = NULL; 63static SCDynamicStoreRef g_store = NULL; 64static dispatch_queue_t g_queue = NULL; 65 66#define LOG(...) asl_log(NULL, NULL, ASL_LEVEL_INFO, __VA_ARGS__) 67 68static void create_dns_sd(void); 69static void destroy_dns_sd(void); 70static void update_all(SCDynamicStoreRef, CFArrayRef, void *); 71 72 73/* parameters */ 74static CFStringRef NetworkChangedKey_BackToMyMac = CFSTR("Setup:/Network/BackToMyMac"); 75 76 77static char * 78CFString2utf8(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 99static void 100retry_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 120static void 121create_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 159static void 160domain_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 189struct addctx { 190 int flags; 191 const char *realm; 192}; 193 194static void 195domains_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 208static void 209dnsCallback(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 223static const char *register_names[2] = { 224 "_kerberos._tcp", 225 "_kerberos._udp" 226}; 227 228static struct { 229 DNSRecordRef *val; 230 size_t len; 231} srvRefs = { NULL, 0 }; 232 233static void 234register_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 291static void 292unregister_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 303static void 304register_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 334static void 335update_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", (int)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 401static void 402update_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 422static void 423update_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 469static void 470delete_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 482static void 483destroy_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 498static SCDynamicStoreRef 499register_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 532void 533bonjour_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