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