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
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 *
CFString2utf8(CFStringRef string)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
retry_timer(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
create_dns_sd(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
domain_add(const char * domain,const char * realm,int flag)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
domains_add(const void * key,const void * value,void * context)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
dnsCallback(DNSServiceRef sdRef,DNSRecordRef RecordRef,DNSServiceFlags flags,DNSServiceErrorType errorCode,void * context)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
register_srv(const char * realm,const char * hostname,int port)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
unregister_srv_realms(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
register_srv_realms(CFStringRef host)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
update_dns(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", (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
401 static void
update_entries(SCDynamicStoreRef store,const char * realm,int flags)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
update_all(SCDynamicStoreRef store,CFArrayRef changedKeys,void * info)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
delete_all(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
destroy_dns_sd(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
register_notification(void)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
bonjour_announce(krb5_context context,krb5_kdc_configuration * config)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