1/*
2 * Copyright (c) 2008, 2009 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#include "kdc_locl.h"
44
45#ifndef __APPLE_TARGET_EMBEDDED__
46
47static heim_array_t (*announce_get_realms)(void);
48
49struct entry {
50    DNSRecordRef recordRef;
51    char *domain;
52    char *realm;
53#define F_EXISTS 1
54#define F_PUSH 2
55    int flags;
56    struct entry *next;
57};
58
59/* #define REGISTER_SRV_RR */
60
61static struct entry *g_entries = NULL;
62static CFStringRef g_hostname = NULL;
63static DNSServiceRef g_dnsRef = NULL;
64static dispatch_source_t g_restart_timer = NULL;
65static SCDynamicStoreRef g_store = NULL;
66static dispatch_queue_t g_queue = NULL;
67
68#define LOG(...) asl_log(NULL, NULL, ASL_LEVEL_INFO, __VA_ARGS__)
69
70static void create_dns_sd(void);
71static void destroy_dns_sd(void);
72static void update_all(SCDynamicStoreRef, CFArrayRef, void *);
73
74
75/* parameters */
76static CFStringRef NetworkChangedKey_BackToMyMac = CFSTR("Setup:/Network/BackToMyMac");
77
78
79static char *
80CFString2utf8(CFStringRef string)
81{
82    size_t size;
83    char *str;
84
85    size = 1 + CFStringGetMaximumSizeForEncoding(CFStringGetLength(string), kCFStringEncodingUTF8);
86    str = malloc(size);
87    if (str == NULL)
88	return NULL;
89
90    if (CFStringGetCString(string, str, size, kCFStringEncodingUTF8) == false) {
91	free(str);
92	return NULL;
93    }
94    return str;
95}
96
97/*
98 *
99 */
100
101static void
102retry_timer(void)
103{
104    dispatch_time_t t;
105
106    heim_assert(g_dnsRef == NULL, "called create when a connection already existed");
107
108    if (g_restart_timer)
109	return;
110
111    g_restart_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, g_queue);
112    t = dispatch_time(DISPATCH_TIME_NOW, 5ull * NSEC_PER_SEC);
113    dispatch_source_set_timer(g_restart_timer, t, 0, NSEC_PER_SEC);
114    dispatch_source_set_event_handler(g_restart_timer, ^{
115	    create_dns_sd();
116	    dispatch_release(g_restart_timer);
117	    g_restart_timer = NULL;
118	});
119    dispatch_resume(g_restart_timer);
120}
121
122/*
123 *
124 */
125
126static void
127create_dns_sd(void)
128{
129    DNSServiceErrorType error;
130
131    heim_assert(g_dnsRef == NULL, "called create when a connection already existed");
132
133    error = DNSServiceCreateConnection(&g_dnsRef);
134    if (error) {
135	retry_timer();
136	return;
137    }
138
139    error = DNSServiceSetDispatchQueue(g_dnsRef, g_queue);
140    if (error) {
141	destroy_dns_sd();
142	retry_timer();
143	return ;
144    }
145
146    /* Do the first update ourself */
147    update_all(g_store, NULL, NULL);
148}
149
150static void
151domain_add(const char *domain, const char *realm, int flag)
152{
153    struct entry *e;
154
155    for (e = g_entries; e != NULL; e = e->next) {
156	if (strcmp(domain, e->domain) == 0 && strcmp(realm, e->realm) == 0) {
157	    e->flags |= flag;
158	    return;
159	}
160    }
161
162    LOG("Adding realm %s to domain %s", realm, domain);
163
164    e = calloc(1, sizeof(*e));
165    if (e == NULL)
166	return;
167    e->domain = strdup(domain);
168    e->realm = strdup(realm);
169    if (e->domain == NULL || e->realm == NULL) {
170	free(e->domain);
171	free(e->realm);
172	free(e);
173	return;
174    }
175    e->flags = flag | F_PUSH; /* if we allocate, we push */
176    e->next = g_entries;
177    g_entries = e;
178}
179
180struct addctx {
181    int flags;
182    const char *realm;
183};
184
185static void
186domains_add(const void *key, const void *value, void *context)
187{
188    char *str = CFString2utf8((CFStringRef)value);
189    struct addctx *ctx = context;
190
191    if (str == NULL)
192	return;
193    if (str[0] != '\0')
194	domain_add(str, ctx->realm, F_EXISTS | ctx->flags);
195    free(str);
196}
197
198
199static void
200dnsCallback(DNSServiceRef sdRef __attribute__((unused)),
201	    DNSRecordRef RecordRef __attribute__((unused)),
202	    DNSServiceFlags flags __attribute__((unused)),
203	    DNSServiceErrorType errorCode,
204	    void *context __attribute__((unused)))
205{
206    if (errorCode == kDNSServiceErr_ServiceNotRunning) {
207	destroy_dns_sd();
208	retry_timer();
209    }
210}
211
212#ifdef REGISTER_SRV_RR
213
214/*
215 * Register DNS SRV rr for the realm.
216 */
217
218static const char *register_names[2] = {
219    "_kerberos._tcp",
220    "_kerberos._udp"
221};
222
223static struct {
224    DNSRecordRef *val;
225    size_t len;
226} srvRefs = { NULL, 0 };
227
228static void
229register_srv(const char *realm, const char *hostname, int port)
230{
231    unsigned char target[1024];
232    int i;
233    int size;
234
235    /* skip registering LKDC realms */
236    if (strncmp(realm, "LKDC:", 5) == 0)
237	return;
238
239    /* encode SRV-RR */
240    target[0] = 0; /* priority */
241    target[1] = 0; /* priority */
242    target[2] = 0; /* weight */
243    target[3] = 0; /* weigth */
244    target[4] = (port >> 8) & 0xff; /* port */
245    target[5] = (port >> 0) & 0xff; /* port */
246
247    size = dn_comp(hostname, target + 6, sizeof(target) - 6, NULL, NULL);
248    if (size < 0)
249	return;
250
251    size += 6;
252
253    LOG("register SRV rr for realm %s hostname %s:%d", realm, hostname, port);
254
255    for (i = 0; i < sizeof(register_names)/sizeof(register_names[0]); i++) {
256	char name[kDNSServiceMaxDomainName];
257	DNSServiceErrorType error;
258	void *ptr;
259
260	ptr = realloc(srvRefs.val, sizeof(srvRefs.val[0]) * (srvRefs.len + 1));
261	if (ptr == NULL)
262	    errx(1, "malloc: out of memory");
263	srvRefs.val = ptr;
264
265	DNSServiceConstructFullName(name, NULL, register_names[i], realm);
266
267	error = DNSServiceRegisterRecord(g_dnsRef,
268					 &srvRefs.val[srvRefs.len],
269					 kDNSServiceFlagsUnique | kDNSServiceFlagsShareConnection,
270					 0,
271					 name,
272					 kDNSServiceType_SRV,
273					 kDNSServiceClass_IN,
274					 size,
275					 target,
276					 0,
277					 dnsCallback,
278					 NULL);
279	if (error) {
280	    LOG("Failed to register SRV rr for realm %s: %d", realm, error);
281	} else
282	    srvRefs.len++;
283    }
284}
285
286static void
287unregister_srv_realms(void)
288{
289    if (g_dnsRef) {
290	for (i = 0; i < srvRefs.len; i++)
291	    DNSServiceRemoveRecord(g_dnsRef, srvRefs.val[i], 0);
292    }
293    free(srvRefs.val);
294    srvRefs.len = 0;
295    srvRefs.val = NULL;
296}
297
298static void
299register_srv_realms(CFStringRef host)
300{
301    krb5_error_code ret;
302    heim_array_t array;
303    char *hostname;
304    size_t i;
305
306    /* first unregister old names */
307
308    hostname = CFString2utf8(host);
309    if (hostname == NULL)
310	return;
311
312    array = announce_get_realms();
313
314    heim_array_iterate(array, ^(heim_object_t item) {
315	    char *r = heim_string_copy_utf8(item);
316	    register_srv(r, hostname, 88);
317	    free(r);
318	});
319
320    heim_release(array);
321
322    free(hostname);
323}
324#endif /* REGISTER_SRV_RR */
325
326static void
327update_dns(void)
328{
329    DNSServiceErrorType error;
330    struct entry **e = &g_entries;
331    char *hostname;
332
333    if (g_hostname == NULL)
334	return;
335
336    hostname = CFString2utf8(g_hostname);
337    if (hostname == NULL)
338	return;
339
340    while (*e != NULL) {
341	/* remove if this wasn't updated */
342	if (((*e)->flags & F_EXISTS) == 0) {
343	    struct entry *drop = *e;
344	    *e = (*e)->next;
345
346	    LOG("Deleting realm %s from domain %s",
347		drop->realm, drop->domain);
348
349	    if (drop->recordRef && g_dnsRef)
350		DNSServiceRemoveRecord(g_dnsRef, drop->recordRef, 0);
351	    free(drop->domain);
352	    free(drop->realm);
353	    free(drop);
354	    continue;
355	}
356	if ((*e)->flags & F_PUSH) {
357	    struct entry *update = *e;
358	    char *dnsdata, *name;
359	    size_t len;
360
361	    len = strlen(update->realm);
362	    asprintf(&dnsdata, "%c%s", (unsigned char)len, update->realm);
363	    if (dnsdata == NULL)
364		errx(1, "malloc");
365
366	    asprintf(&name, "_kerberos.%s.%s", hostname, update->domain);
367	    if (name == NULL)
368		errx(1, "malloc");
369
370	    if (update->recordRef) {
371		DNSServiceRemoveRecord(g_dnsRef, update->recordRef, 0);
372		update->recordRef = NULL;
373	    }
374
375	    error = DNSServiceRegisterRecord(g_dnsRef,
376					     &update->recordRef,
377					     kDNSServiceFlagsShared | kDNSServiceFlagsAllowRemoteQuery,
378					     0,
379					     name,
380					     kDNSServiceType_TXT,
381					     kDNSServiceClass_IN,
382					     len+1,
383					     dnsdata,
384					     0,
385					     dnsCallback,
386					     NULL);
387	    free(name);
388	    free(dnsdata);
389	    if (error)
390		errx(1, "failure to update entry for %s/%s",
391		     update->domain, update->realm);
392	}
393	e = &(*e)->next;
394    }
395    free(hostname);
396}
397
398static void
399update_entries(SCDynamicStoreRef store, const char *realm, int flags)
400{
401    CFDictionaryRef btmm;
402
403    /* we always announce in the local domain */
404    domain_add("local", realm, F_EXISTS | flags);
405
406    /* announce btmm */
407    btmm = SCDynamicStoreCopyValue(store, NetworkChangedKey_BackToMyMac);
408    if (btmm) {
409	struct addctx addctx;
410
411	addctx.flags = flags;
412	addctx.realm = realm;
413
414	CFDictionaryApplyFunction(btmm, domains_add, &addctx);
415	CFRelease(btmm);
416    }
417}
418
419static void
420update_all(SCDynamicStoreRef store, CFArrayRef changedKeys, void *info)
421{
422    heim_array_t array;
423    struct entry *e;
424    CFStringRef host;
425    int 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    array = announce_get_realms();
448
449    heim_array_iterate(array, ^(heim_object_t item, int *stop) {
450	    char *r = heim_string_copy_utf8(item);
451	    update_entries(store, r, flags);
452	    free(r);
453	});
454
455    heim_release(array);
456
457    update_dns();
458
459    CFRelease(host);
460}
461
462static void
463delete_all(void)
464{
465    struct entry *e;
466
467    for (e = g_entries; e != NULL; e = e->next)
468	e->flags &= ~(F_EXISTS|F_PUSH);
469
470    update_dns();
471    if (g_entries != NULL)
472	errx(1, "Failed to remove all bonjour entries");
473}
474
475static void
476destroy_dns_sd(void)
477{
478    if (g_dnsRef == NULL)
479	return;
480
481    delete_all();
482#ifdef REGISTER_SRV_RR
483    unregister_srv_realms();
484#endif
485
486    DNSServiceRefDeallocate(g_dnsRef);
487    g_dnsRef = NULL;
488}
489
490
491static SCDynamicStoreRef
492register_notification(void)
493{
494    SCDynamicStoreRef store;
495    CFStringRef computerNameKey;
496    CFMutableArrayRef keys;
497
498    computerNameKey = SCDynamicStoreKeyCreateHostNames(kCFAllocatorDefault);
499
500    store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("Network watcher"),
501				 update_all, NULL);
502    if (store == NULL)
503	errx(1, "SCDynamicStoreCreate");
504
505    keys = CFArrayCreateMutable(kCFAllocatorDefault, 2, &kCFTypeArrayCallBacks);
506    if (keys == NULL)
507	errx(1, "CFArrayCreateMutable");
508
509    CFArrayAppendValue(keys, computerNameKey);
510    CFArrayAppendValue(keys, NetworkChangedKey_BackToMyMac);
511
512    if (SCDynamicStoreSetNotificationKeys(store, keys, NULL) == false)
513	errx(1, "SCDynamicStoreSetNotificationKeys");
514
515    CFRelease(computerNameKey);
516    CFRelease(keys);
517
518    if (!SCDynamicStoreSetDispatchQueue(store, g_queue))
519	errx(1, "SCDynamicStoreSetDispatchQueue");
520
521    return store;
522}
523#endif
524
525#endif /* __APPLE_TARGET_EMBEDDED__ */
526
527
528
529void
530bonjour_announce(heim_array_t (*get_realms)(void))
531{
532#ifndef __APPLE_TARGET_EMBEDDED__
533#if defined(__APPLE__) && defined(HAVE_GCD)
534    announce_get_realms = get_realms;
535
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
542#if defined(HAVE_GCD) && defined(HAVE_NOTIFY_H)
543    /*
544     * On KDC change notifications, lets re-announce configuration
545     */
546    {
547	int token;
548	notify_register_dispatch("com.apple.kdc.update", &token, g_queue, ^(int t) {
549		update_all(g_store, NULL, NULL);
550	    });
551    }
552#endif
553
554    create_dns_sd();
555#endif
556#endif /* __APPLE_TARGET_EMBEDDED__ */
557
558}
559