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
43static krb5_kdc_configuration *announce_config;
44static krb5_context announce_context;
45
46struct 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
58static struct entry *g_entries = NULL;
59static CFStringRef g_hostname = NULL;
60static DNSServiceRef g_dnsRef = NULL;
61static SCDynamicStoreRef g_store = NULL;
62static dispatch_queue_t g_queue = NULL;
63
64#define LOG(...) asl_log(NULL, NULL, ASL_LEVEL_INFO, __VA_ARGS__)
65
66static void create_dns_sd(void);
67static void destroy_dns_sd(void);
68static void update_all(SCDynamicStoreRef, CFArrayRef, void *);
69
70
71/* parameters */
72static CFStringRef NetworkChangedKey_BackToMyMac = CFSTR("Setup:/Network/BackToMyMac");
73
74
75static char *
76CFString2utf8(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
97static void
98retry_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
118static void
119create_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
157static void
158domain_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
187struct addctx {
188    int flags;
189    const char *realm;
190};
191
192static void
193domains_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
206static void
207dnsCallback(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
221static const char *register_names[2] = {
222    "_kerberos._tcp",
223    "_kerberos._udp"
224};
225
226static struct {
227    DNSRecordRef *val;
228    size_t len;
229} srvRefs = { NULL, 0 };
230
231static void
232register_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
289static void
290unregister_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
301static void
302register_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
332static void
333update_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
399static void
400update_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
420static void
421update_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
467static void
468delete_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
480static void
481destroy_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
496static SCDynamicStoreRef
497register_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
530void
531bonjour_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