xref: /freebsd/crypto/heimdal/kdc/announce.c (revision f078c492a9b57877c723586db26d789cda1b98ea)
1 /*
2  * Copyright (c) 2008 Apple Inc.  All Rights Reserved.
3  *
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.
8  *
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.
19  *
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.
23  *
24  */
25 
26 #include "kdc_locl.h"
27 
28 #if defined(__APPLE__) && defined(HAVE_GCD)
29 
30 #include <CoreFoundation/CoreFoundation.h>
31 #include <SystemConfiguration/SCDynamicStore.h>
32 #include <SystemConfiguration/SCDynamicStoreCopySpecific.h>
33 #include <SystemConfiguration/SCDynamicStoreKey.h>
34 
35 #include <dispatch/dispatch.h>
36 
37 #include <asl.h>
38 #include <resolv.h>
39 
40 #include <dns_sd.h>
41 #include <err.h>
42 
43 static krb5_kdc_configuration *announce_config;
44 static krb5_context announce_context;
45 
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;
54 };
55 
56 /* #define REGISTER_SRV_RR */
57 
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;
63 
64 #define LOG(...) asl_log(NULL, NULL, ASL_LEVEL_INFO, __VA_ARGS__)
65 
66 static void create_dns_sd(void);
67 static void destroy_dns_sd(void);
68 static void update_all(SCDynamicStoreRef, CFArrayRef, void *);
69 
70 
71 /* parameters */
72 static CFStringRef NetworkChangedKey_BackToMyMac = CFSTR("Setup:/Network/BackToMyMac");
73 
74 
75 static char *
76 CFString2utf8(CFStringRef string)
77 {
78     size_t size;
79     char *str;
80 
81     size = 1 + CFStringGetMaximumSizeForEncoding(CFStringGetLength(string), kCFStringEncodingUTF8);
82     str = malloc(size);
83     if (str == NULL)
84 	return NULL;
85 
86     if (CFStringGetCString(string, str, size, kCFStringEncodingUTF8) == false) {
87 	free(str);
88 	return NULL;
89     }
90     return str;
91 }
92 
93 /*
94  *
95  */
96 
97 static void
98 retry_timer(void)
99 {
100     dispatch_source_t s;
101     dispatch_time_t t;
102 
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);
110 	});
111     dispatch_resume(s);
112 }
113 
114 /*
115  *
116  */
117 
118 static void
119 create_dns_sd(void)
120 {
121     DNSServiceErrorType error;
122     dispatch_source_t s;
123 
124     error = DNSServiceCreateConnection(&g_dnsRef);
125     if (error) {
126 	retry_timer();
127 	return;
128     }
129 
130     dispatch_suspend(g_queue);
131 
132     s = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,
133 			       DNSServiceRefSockFD(g_dnsRef),
134 			       0, g_queue);
135 
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);
141 	    }
142 	});
143 
144     dispatch_source_set_cancel_handler(s, ^{
145 	    destroy_dns_sd();
146 	    retry_timer();
147 	    dispatch_release(s);
148 	});
149 
150     dispatch_resume(s);
151 
152     /* Do the first update ourself */
153     update_all(g_store, NULL, NULL);
154     dispatch_resume(g_queue);
155 }
156 
157 static void
158 domain_add(const char *domain, const char *realm, int flag)
159 {
160     struct entry *e;
161 
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;
166 	}
167     }
168 
169     LOG("Adding realm %s to domain %s", realm, domain);
170 
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;
181     }
182     e->flags = flag | F_PUSH; /* if we allocate, we push */
183     e->next = g_entries;
184     g_entries = e;
185 }
186 
187 struct addctx {
188     int flags;
189     const char *realm;
190 };
191 
192 static void
193 domains_add(const void *key, const void *value, void *context)
194 {
195     char *str = CFString2utf8((CFStringRef)value);
196     struct addctx *ctx = context;
197 
198     if (str == NULL)
199 	return;
200     if (str[0] != '\0')
201 	domain_add(str, ctx->realm, F_EXISTS | ctx->flags);
202     free(str);
203 }
204 
205 
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)))
212 {
213 }
214 
215 #ifdef REGISTER_SRV_RR
216 
217 /*
218  * Register DNS SRV rr for the realm.
219  */
220 
221 static const char *register_names[2] = {
222     "_kerberos._tcp",
223     "_kerberos._udp"
224 };
225 
226 static struct {
227     DNSRecordRef *val;
228     size_t len;
229 } srvRefs = { NULL, 0 };
230 
231 static void
232 register_srv(const char *realm, const char *hostname, int port)
233 {
234     unsigned char target[1024];
235     int i;
236     int size;
237 
238     /* skip registering LKDC realms */
239     if (strncmp(realm, "LKDC:", 5) == 0)
240 	return;
241 
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 */
249 
250     size = dn_comp(hostname, target + 6, sizeof(target) - 6, NULL, NULL);
251     if (size < 0)
252 	return;
253 
254     size += 6;
255 
256     LOG("register SRV rr for realm %s hostname %s:%d", realm, hostname, port);
257 
258     for (i = 0; i < sizeof(register_names)/sizeof(register_names[0]); i++) {
259 	char name[kDNSServiceMaxDomainName];
260 	DNSServiceErrorType error;
261 	void *ptr;
262 
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;
267 
268 	DNSServiceConstructFullName(name, NULL, register_names[i], realm);
269 
270 	error = DNSServiceRegisterRecord(g_dnsRef,
271 					 &srvRefs.val[srvRefs.len],
272 					 kDNSServiceFlagsUnique | kDNSServiceFlagsShareConnection,
273 					 0,
274 					 name,
275 					 kDNSServiceType_SRV,
276 					 kDNSServiceClass_IN,
277 					 size,
278 					 target,
279 					 0,
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++;
286     }
287 }
288 
289 static void
290 unregister_srv_realms(void)
291 {
292     if (g_dnsRef) {
293 	for (i = 0; i < srvRefs.len; i++)
294 	    DNSServiceRemoveRecord(g_dnsRef, srvRefs.val[i], 0);
295     }
296     free(srvRefs.val);
297     srvRefs.len = 0;
298     srvRefs.val = NULL;
299 }
300 
301 static void
302 register_srv_realms(CFStringRef host)
303 {
304     krb5_error_code ret;
305     char *hostname;
306     size_t i;
307 
308     /* first unregister old names */
309 
310     hostname = CFString2utf8(host);
311     if (hostname == NULL)
312 	return;
313 
314     for(i = 0; i < announce_config->num_db; i++) {
315 	char **realms, **r;
316 
317 	if (announce_config->db[i]->hdb_get_realms == NULL)
318 	    continue;
319 
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);
325 	}
326     }
327 
328     free(hostname);
329 }
330 #endif /* REGISTER_SRV_RR */
331 
332 static void
333 update_dns(void)
334 {
335     DNSServiceErrorType error;
336     struct entry **e = &g_entries;
337     char *hostname;
338 
339     hostname = CFString2utf8(g_hostname);
340     if (hostname == NULL)
341 	return;
342 
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;
348 
349 	    LOG("Deleting realm %s from domain %s",
350 		drop->realm, drop->domain);
351 
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;
358 	}
359 	if ((*e)->flags & F_PUSH) {
360 	    struct entry *update = *e;
361 	    char *dnsdata, *name;
362 	    size_t len;
363 
364 	    len = strlen(update->realm);
365 	    asprintf(&dnsdata, "%c%s", (int)len, update->realm);
366 	    if (dnsdata == NULL)
367 		errx(1, "malloc");
368 
369 	    asprintf(&name, "_kerberos.%s.%s", hostname, update->domain);
370 	    if (name == NULL)
371 		errx(1, "malloc");
372 
373 	    if (update->recordRef)
374 		DNSServiceRemoveRecord(g_dnsRef, update->recordRef, 0);
375 
376 	    error = DNSServiceRegisterRecord(g_dnsRef,
377 					     &update->recordRef,
378 					     kDNSServiceFlagsShared | kDNSServiceFlagsAllowRemoteQuery,
379 					     0,
380 					     name,
381 					     kDNSServiceType_TXT,
382 					     kDNSServiceClass_IN,
383 					     len+1,
384 					     dnsdata,
385 					     0,
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);
393 	}
394 	e = &(*e)->next;
395     }
396     free(hostname);
397 }
398 
399 static void
400 update_entries(SCDynamicStoreRef store, const char *realm, int flags)
401 {
402     CFDictionaryRef btmm;
403 
404     /* we always announce in the local domain */
405     domain_add("local", realm, F_EXISTS | flags);
406 
407     /* announce btmm */
408     btmm = SCDynamicStoreCopyValue(store, NetworkChangedKey_BackToMyMac);
409     if (btmm) {
410 	struct addctx addctx;
411 
412 	addctx.flags = flags;
413 	addctx.realm = realm;
414 
415 	CFDictionaryApplyFunction(btmm, domains_add, &addctx);
416 	CFRelease(btmm);
417     }
418 }
419 
420 static void
421 update_all(SCDynamicStoreRef store, CFArrayRef changedKeys, void *info)
422 {
423     struct entry *e;
424     CFStringRef host;
425     int i, flags = 0;
426 
427     LOG("something changed, running update");
428 
429     host = SCDynamicStoreCopyLocalHostName(store);
430     if (host == NULL)
431 	return;
432 
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 */
438 
439 #ifdef REGISTER_SRV_RR
440 	register_srv_realms(g_hostname);
441 #endif
442     }
443 
444     for (e = g_entries; e != NULL; e = e->next)
445 	e->flags &= ~(F_EXISTS|F_PUSH);
446 
447     for(i = 0; i < announce_config->num_db; i++) {
448 	krb5_error_code ret;
449 	char **realms, **r;
450 
451 	if (announce_config->db[i]->hdb_get_realms == NULL)
452 	    continue;
453 
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);
459 	}
460     }
461 
462     update_dns();
463 
464     CFRelease(host);
465 }
466 
467 static void
468 delete_all(void)
469 {
470     struct entry *e;
471 
472     for (e = g_entries; e != NULL; e = e->next)
473 	e->flags &= ~(F_EXISTS|F_PUSH);
474 
475     update_dns();
476     if (g_entries != NULL)
477 	errx(1, "Failed to remove all bonjour entries");
478 }
479 
480 static void
481 destroy_dns_sd(void)
482 {
483     if (g_dnsRef == NULL)
484 	return;
485 
486     delete_all();
487 #ifdef REGISTER_SRV_RR
488     unregister_srv_realms();
489 #endif
490 
491     DNSServiceRefDeallocate(g_dnsRef);
492     g_dnsRef = NULL;
493 }
494 
495 
496 static SCDynamicStoreRef
497 register_notification(void)
498 {
499     SCDynamicStoreRef store;
500     CFStringRef computerNameKey;
501     CFMutableArrayRef keys;
502 
503     computerNameKey = SCDynamicStoreKeyCreateHostNames(kCFAllocatorDefault);
504 
505     store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("Network watcher"),
506 				 update_all, NULL);
507     if (store == NULL)
508 	errx(1, "SCDynamicStoreCreate");
509 
510     keys = CFArrayCreateMutable(kCFAllocatorDefault, 2, &kCFTypeArrayCallBacks);
511     if (keys == NULL)
512 	errx(1, "CFArrayCreateMutable");
513 
514     CFArrayAppendValue(keys, computerNameKey);
515     CFArrayAppendValue(keys, NetworkChangedKey_BackToMyMac);
516 
517     if (SCDynamicStoreSetNotificationKeys(store, keys, NULL) == false)
518 	errx(1, "SCDynamicStoreSetNotificationKeys");
519 
520     CFRelease(computerNameKey);
521     CFRelease(keys);
522 
523     if (!SCDynamicStoreSetDispatchQueue(store, g_queue))
524 	errx(1, "SCDynamicStoreSetDispatchQueue");
525 
526     return store;
527 }
528 #endif
529 
530 void
531 bonjour_announce(krb5_context context, krb5_kdc_configuration *config)
532 {
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");
537 
538     g_store = register_notification();
539     announce_config = config;
540     announce_context = context;
541 
542     create_dns_sd();
543 #endif
544 }
545