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