2 * Copyright (c) 2008 Apple Inc. All Rights Reserved.
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.
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.
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.
28 #if defined(__APPLE__) && defined(HAVE_GCD)
30 #include <CoreFoundation/CoreFoundation.h>
31 #include <SystemConfiguration/SCDynamicStore.h>
32 #include <SystemConfiguration/SCDynamicStoreCopySpecific.h>
33 #include <SystemConfiguration/SCDynamicStoreKey.h>
35 #include <dispatch/dispatch.h>
43 static krb5_kdc_configuration *announce_config;
44 static krb5_context announce_context;
47 DNSRecordRef recordRef;
56 /* #define REGISTER_SRV_RR */
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;
64 #define LOG(...) asl_log(NULL, NULL, ASL_LEVEL_INFO, __VA_ARGS__)
66 static void create_dns_sd(void);
67 static void destroy_dns_sd(void);
68 static void update_all(SCDynamicStoreRef, CFArrayRef, void *);
72 static CFStringRef NetworkChangedKey_BackToMyMac = CFSTR("Setup:/Network/BackToMyMac");
76 CFString2utf8(CFStringRef string)
81 size = 1 + CFStringGetMaximumSizeForEncoding(CFStringGetLength(string), kCFStringEncodingUTF8);
86 if (CFStringGetCString(string, str, size, kCFStringEncodingUTF8) == false) {
103 s = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,
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, ^{
121 DNSServiceErrorType error;
124 error = DNSServiceCreateConnection(&g_dnsRef);
130 dispatch_suspend(g_queue);
132 s = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,
133 DNSServiceRefSockFD(g_dnsRef),
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);
144 dispatch_source_set_cancel_handler(s, ^{
152 /* Do the first update ourself */
153 update_all(g_store, NULL, NULL);
154 dispatch_resume(g_queue);
158 domain_add(const char *domain, const char *realm, int flag)
162 for (e = g_entries; e != NULL; e = e->next) {
163 if (strcmp(domain, e->domain) == 0 && strcmp(realm, e->realm) == 0) {
169 LOG("Adding realm %s to domain %s", realm, domain);
171 e = calloc(1, sizeof(*e));
174 e->domain = strdup(domain);
175 e->realm = strdup(realm);
176 if (e->domain == NULL || e->realm == NULL) {
182 e->flags = flag | F_PUSH; /* if we allocate, we push */
193 domains_add(const void *key, const void *value, void *context)
195 char *str = CFString2utf8((CFStringRef)value);
196 struct addctx *ctx = context;
201 domain_add(str, ctx->realm, F_EXISTS | ctx->flags);
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)))
215 #ifdef REGISTER_SRV_RR
218 * Register DNS SRV rr for the realm.
221 static const char *register_names[2] = {
229 } srvRefs = { NULL, 0 };
232 register_srv(const char *realm, const char *hostname, int port)
234 unsigned char target[1024];
238 /* skip registering LKDC realms */
239 if (strncmp(realm, "LKDC:", 5) == 0)
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 */
250 size = dn_comp(hostname, target + 6, sizeof(target) - 6, NULL, NULL);
256 LOG("register SRV rr for realm %s hostname %s:%d", realm, hostname, port);
258 for (i = 0; i < sizeof(register_names)/sizeof(register_names[0]); i++) {
259 char name[kDNSServiceMaxDomainName];
260 DNSServiceErrorType error;
263 ptr = realloc(srvRefs.val, sizeof(srvRefs.val[0]) * (srvRefs.len + 1));
265 errx(1, "malloc: out of memory");
268 DNSServiceConstructFullName(name, NULL, register_names[i], realm);
270 error = DNSServiceRegisterRecord(g_dnsRef,
271 &srvRefs.val[srvRefs.len],
272 kDNSServiceFlagsUnique | kDNSServiceFlagsShareConnection,
283 LOG("Failed to register SRV rr for realm %s: %d", realm, error);
290 unregister_srv_realms(void)
293 for (i = 0; i < srvRefs.len; i++)
294 DNSServiceRemoveRecord(g_dnsRef, srvRefs.val[i], 0);
302 register_srv_realms(CFStringRef host)
308 /* first unregister old names */
310 hostname = CFString2utf8(host);
311 if (hostname == NULL)
314 for(i = 0; i < announce_config->num_db; i++) {
317 if (announce_config->db[i]->hdb_get_realms == NULL)
320 ret = (announce_config->db[i]->hdb_get_realms)(announce_context, &realms);
322 for (r = realms; r && *r; r++)
323 register_srv(*r, hostname, 88);
324 krb5_free_host_realm(announce_context, realms);
330 #endif /* REGISTER_SRV_RR */
335 DNSServiceErrorType error;
336 struct entry **e = &g_entries;
339 hostname = CFString2utf8(g_hostname);
340 if (hostname == NULL)
344 /* remove if this wasn't updated */
345 if (((*e)->flags & F_EXISTS) == 0) {
346 struct entry *drop = *e;
349 LOG("Deleting realm %s from domain %s",
350 drop->realm, drop->domain);
352 if (drop->recordRef && g_dnsRef)
353 DNSServiceRemoveRecord(g_dnsRef, drop->recordRef, 0);
359 if ((*e)->flags & F_PUSH) {
360 struct entry *update = *e;
361 char *dnsdata, *name;
364 len = strlen(update->realm);
365 asprintf(&dnsdata, "%c%s", (int)len, update->realm);
369 asprintf(&name, "_kerberos.%s.%s", hostname, update->domain);
373 if (update->recordRef)
374 DNSServiceRemoveRecord(g_dnsRef, update->recordRef, 0);
376 error = DNSServiceRegisterRecord(g_dnsRef,
378 kDNSServiceFlagsShared | kDNSServiceFlagsAllowRemoteQuery,
391 errx(1, "failure to update entry for %s/%s",
392 update->domain, update->realm);
400 update_entries(SCDynamicStoreRef store, const char *realm, int flags)
402 CFDictionaryRef btmm;
404 /* we always announce in the local domain */
405 domain_add("local", realm, F_EXISTS | flags);
408 btmm = SCDynamicStoreCopyValue(store, NetworkChangedKey_BackToMyMac);
410 struct addctx addctx;
412 addctx.flags = flags;
413 addctx.realm = realm;
415 CFDictionaryApplyFunction(btmm, domains_add, &addctx);
421 update_all(SCDynamicStoreRef store, CFArrayRef changedKeys, void *info)
427 LOG("something changed, running update");
429 host = SCDynamicStoreCopyLocalHostName(store);
433 if (g_hostname == NULL || CFStringCompare(host, g_hostname, 0) != kCFCompareEqualTo) {
435 CFRelease(g_hostname);
436 g_hostname = CFRetain(host);
437 flags = F_PUSH; /* if hostname has changed, force push */
439 #ifdef REGISTER_SRV_RR
440 register_srv_realms(g_hostname);
444 for (e = g_entries; e != NULL; e = e->next)
445 e->flags &= ~(F_EXISTS|F_PUSH);
447 for(i = 0; i < announce_config->num_db; i++) {
451 if (announce_config->db[i]->hdb_get_realms == NULL)
454 ret = (announce_config->db[i]->hdb_get_realms)(announce_context, announce_config->db[i], &realms);
456 for (r = realms; r && *r; r++)
457 update_entries(store, *r, flags);
458 krb5_free_host_realm(announce_context, realms);
472 for (e = g_entries; e != NULL; e = e->next)
473 e->flags &= ~(F_EXISTS|F_PUSH);
476 if (g_entries != NULL)
477 errx(1, "Failed to remove all bonjour entries");
483 if (g_dnsRef == NULL)
487 #ifdef REGISTER_SRV_RR
488 unregister_srv_realms();
491 DNSServiceRefDeallocate(g_dnsRef);
496 static SCDynamicStoreRef
497 register_notification(void)
499 SCDynamicStoreRef store;
500 CFStringRef computerNameKey;
501 CFMutableArrayRef keys;
503 computerNameKey = SCDynamicStoreKeyCreateHostNames(kCFAllocatorDefault);
505 store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("Network watcher"),
508 errx(1, "SCDynamicStoreCreate");
510 keys = CFArrayCreateMutable(kCFAllocatorDefault, 2, &kCFTypeArrayCallBacks);
512 errx(1, "CFArrayCreateMutable");
514 CFArrayAppendValue(keys, computerNameKey);
515 CFArrayAppendValue(keys, NetworkChangedKey_BackToMyMac);
517 if (SCDynamicStoreSetNotificationKeys(store, keys, NULL) == false)
518 errx(1, "SCDynamicStoreSetNotificationKeys");
520 CFRelease(computerNameKey);
523 if (!SCDynamicStoreSetDispatchQueue(store, g_queue))
524 errx(1, "SCDynamicStoreSetDispatchQueue");
531 bonjour_announce(krb5_context context, krb5_kdc_configuration *config)
533 #if defined(__APPLE__) && defined(HAVE_GCD)
534 g_queue = dispatch_queue_create("com.apple.kdc_announce", NULL);
536 errx(1, "dispatch_queue_create");
538 g_store = register_notification();
539 announce_config = config;
540 announce_context = context;