make make rules silent
[heimdal.git] / kdc / announce.c
blobcf3fdc36363934f47b1f409c8c00b87754ab43e4
1 /*
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.
26 #include "kdc_locl.h"
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>
37 #include <asl.h>
38 #include <resolv.h>
40 #include <dns_sd.h>
41 #include <err.h>
43 static krb5_kdc_configuration *announce_config;
44 static krb5_context announce_context;
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;
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 *);
71 /* parameters */
72 static CFStringRef NetworkChangedKey_BackToMyMac = CFSTR("Setup:/Network/BackToMyMac");
75 static char *
76 CFString2utf8(CFStringRef string)
78 size_t size;
79 char *str;
81 size = 1 + CFStringGetMaximumSizeForEncoding(CFStringGetLength(string), kCFStringEncodingUTF8);
82 str = malloc(size);
83 if (str == NULL)
84 return NULL;
86 if (CFStringGetCString(string, str, size, kCFStringEncodingUTF8) == false) {
87 free(str);
88 return NULL;
90 return str;
97 static void
98 retry_timer(void)
100 dispatch_source_t s;
101 dispatch_time_t t;
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);
111 dispatch_resume(s);
118 static void
119 create_dns_sd(void)
121 DNSServiceErrorType error;
122 dispatch_source_t s;
124 error = DNSServiceCreateConnection(&g_dnsRef);
125 if (error) {
126 retry_timer();
127 return;
130 dispatch_suspend(g_queue);
132 s = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,
133 DNSServiceRefSockFD(g_dnsRef),
134 0, g_queue);
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, ^{
145 destroy_dns_sd();
146 retry_timer();
147 dispatch_release(s);
150 dispatch_resume(s);
152 /* Do the first update ourself */
153 update_all(g_store, NULL, NULL);
154 dispatch_resume(g_queue);
157 static void
158 domain_add(const char *domain, const char *realm, int flag)
160 struct entry *e;
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;
169 LOG("Adding realm %s to domain %s", realm, domain);
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;
182 e->flags = flag | F_PUSH; /* if we allocate, we push */
183 e->next = g_entries;
184 g_entries = e;
187 struct addctx {
188 int flags;
189 const char *realm;
192 static void
193 domains_add(const void *key, const void *value, void *context)
195 char *str = CFString2utf8((CFStringRef)value);
196 struct addctx *ctx = context;
198 if (str == NULL)
199 return;
200 if (str[0] != '\0')
201 domain_add(str, ctx->realm, F_EXISTS | ctx->flags);
202 free(str);
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)))
215 #ifdef REGISTER_SRV_RR
218 * Register DNS SRV rr for the realm.
221 static const char *register_names[2] = {
222 "_kerberos._tcp",
223 "_kerberos._udp"
226 static struct {
227 DNSRecordRef *val;
228 size_t len;
229 } srvRefs = { NULL, 0 };
231 static void
232 register_srv(const char *realm, const char *hostname, int port)
234 unsigned char target[1024];
235 int i;
236 int size;
238 /* skip registering LKDC realms */
239 if (strncmp(realm, "LKDC:", 5) == 0)
240 return;
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 */
250 size = dn_comp(hostname, target + 6, sizeof(target) - 6, NULL, NULL);
251 if (size < 0)
252 return;
254 size += 6;
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;
261 void *ptr;
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;
268 DNSServiceConstructFullName(name, NULL, register_names[i], realm);
270 error = DNSServiceRegisterRecord(g_dnsRef,
271 &srvRefs.val[srvRefs.len],
272 kDNSServiceFlagsUnique | kDNSServiceFlagsShareConnection,
274 name,
275 kDNSServiceType_SRV,
276 kDNSServiceClass_IN,
277 size,
278 target,
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++;
289 static void
290 unregister_srv_realms(void)
292 if (g_dnsRef) {
293 for (i = 0; i < srvRefs.len; i++)
294 DNSServiceRemoveRecord(g_dnsRef, srvRefs.val[i], 0);
296 free(srvRefs.val);
297 srvRefs.len = 0;
298 srvRefs.val = NULL;
301 static void
302 register_srv_realms(CFStringRef host)
304 krb5_error_code ret;
305 char *hostname;
306 size_t i;
308 /* first unregister old names */
310 hostname = CFString2utf8(host);
311 if (hostname == NULL)
312 return;
314 for(i = 0; i < announce_config->num_db; i++) {
315 char **realms, **r;
317 if (announce_config->db[i]->hdb_get_realms == NULL)
318 continue;
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);
328 free(hostname);
330 #endif /* REGISTER_SRV_RR */
332 static void
333 update_dns(void)
335 DNSServiceErrorType error;
336 struct entry **e = &g_entries;
337 char *hostname;
339 hostname = CFString2utf8(g_hostname);
340 if (hostname == NULL)
341 return;
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;
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);
354 free(drop->domain);
355 free(drop->realm);
356 free(drop);
357 continue;
359 if ((*e)->flags & F_PUSH) {
360 struct entry *update = *e;
361 char *dnsdata, *name;
362 size_t len;
364 len = strlen(update->realm);
365 asprintf(&dnsdata, "%c%s", (int)len, update->realm);
366 if (dnsdata == NULL)
367 errx(1, "malloc");
369 asprintf(&name, "_kerberos.%s.%s", hostname, update->domain);
370 if (name == NULL)
371 errx(1, "malloc");
373 if (update->recordRef)
374 DNSServiceRemoveRecord(g_dnsRef, update->recordRef, 0);
376 error = DNSServiceRegisterRecord(g_dnsRef,
377 &update->recordRef,
378 kDNSServiceFlagsShared | kDNSServiceFlagsAllowRemoteQuery,
380 name,
381 kDNSServiceType_TXT,
382 kDNSServiceClass_IN,
383 len+1,
384 dnsdata,
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);
394 e = &(*e)->next;
396 free(hostname);
399 static void
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);
407 /* announce btmm */
408 btmm = SCDynamicStoreCopyValue(store, NetworkChangedKey_BackToMyMac);
409 if (btmm) {
410 struct addctx addctx;
412 addctx.flags = flags;
413 addctx.realm = realm;
415 CFDictionaryApplyFunction(btmm, domains_add, &addctx);
416 CFRelease(btmm);
420 static void
421 update_all(SCDynamicStoreRef store, CFArrayRef changedKeys, void *info)
423 struct entry *e;
424 CFStringRef host;
425 int i, flags = 0;
427 LOG("something changed, running update");
429 host = SCDynamicStoreCopyLocalHostName(store);
430 if (host == NULL)
431 return;
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 */
439 #ifdef REGISTER_SRV_RR
440 register_srv_realms(g_hostname);
441 #endif
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++) {
448 krb5_error_code ret;
449 char **realms, **r;
451 if (announce_config->db[i]->hdb_get_realms == NULL)
452 continue;
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);
462 update_dns();
464 CFRelease(host);
467 static void
468 delete_all(void)
470 struct entry *e;
472 for (e = g_entries; e != NULL; e = e->next)
473 e->flags &= ~(F_EXISTS|F_PUSH);
475 update_dns();
476 if (g_entries != NULL)
477 errx(1, "Failed to remove all bonjour entries");
480 static void
481 destroy_dns_sd(void)
483 if (g_dnsRef == NULL)
484 return;
486 delete_all();
487 #ifdef REGISTER_SRV_RR
488 unregister_srv_realms();
489 #endif
491 DNSServiceRefDeallocate(g_dnsRef);
492 g_dnsRef = NULL;
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"),
506 update_all, NULL);
507 if (store == NULL)
508 errx(1, "SCDynamicStoreCreate");
510 keys = CFArrayCreateMutable(kCFAllocatorDefault, 2, &kCFTypeArrayCallBacks);
511 if (keys == NULL)
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);
521 CFRelease(keys);
523 if (!SCDynamicStoreSetDispatchQueue(store, g_queue))
524 errx(1, "SCDynamicStoreSetDispatchQueue");
526 return store;
528 #endif
530 void
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);
535 if (!g_queue)
536 errx(1, "dispatch_queue_create");
538 g_store = register_notification();
539 announce_config = config;
540 announce_context = context;
542 create_dns_sd();
543 #endif