1226031Sstas/*
2226031Sstas * Copyright (c) 2008 Apple Inc.  All Rights Reserved.
3226031Sstas *
4226031Sstas * Export of this software from the United States of America may require
5226031Sstas * a specific license from the United States Government.  It is the
6226031Sstas * responsibility of any person or organization contemplating export to
7226031Sstas * obtain such a license before exporting.
8226031Sstas *
9226031Sstas * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
10226031Sstas * distribute this software and its documentation for any purpose and
11226031Sstas * without fee is hereby granted, provided that the above copyright
12226031Sstas * notice appear in all copies and that both that copyright notice and
13226031Sstas * this permission notice appear in supporting documentation, and that
14226031Sstas * the name of Apple Inc. not be used in advertising or publicity pertaining
15226031Sstas * to distribution of the software without specific, written prior
16226031Sstas * permission.  Apple Inc. makes no representations about the suitability of
17226031Sstas * this software for any purpose.  It is provided "as is" without express
18226031Sstas * or implied warranty.
19226031Sstas *
20226031Sstas * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
21226031Sstas * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
22226031Sstas * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
23226031Sstas *
24226031Sstas */
25226031Sstas
26226031Sstas#include "kdc_locl.h"
27226031Sstas
28226031Sstas#if defined(__APPLE__) && defined(HAVE_GCD)
29226031Sstas
30226031Sstas#include <CoreFoundation/CoreFoundation.h>
31226031Sstas#include <SystemConfiguration/SCDynamicStore.h>
32226031Sstas#include <SystemConfiguration/SCDynamicStoreCopySpecific.h>
33226031Sstas#include <SystemConfiguration/SCDynamicStoreKey.h>
34226031Sstas
35226031Sstas#include <dispatch/dispatch.h>
36226031Sstas
37226031Sstas#include <asl.h>
38226031Sstas#include <resolv.h>
39226031Sstas
40226031Sstas#include <dns_sd.h>
41226031Sstas#include <err.h>
42226031Sstas
43226031Sstasstatic krb5_kdc_configuration *announce_config;
44226031Sstasstatic krb5_context announce_context;
45226031Sstas
46226031Sstasstruct entry {
47226031Sstas    DNSRecordRef recordRef;
48226031Sstas    char *domain;
49226031Sstas    char *realm;
50226031Sstas#define F_EXISTS 1
51226031Sstas#define F_PUSH 2
52226031Sstas    int flags;
53226031Sstas    struct entry *next;
54226031Sstas};
55226031Sstas
56226031Sstas/* #define REGISTER_SRV_RR */
57226031Sstas
58226031Sstasstatic struct entry *g_entries = NULL;
59226031Sstasstatic CFStringRef g_hostname = NULL;
60226031Sstasstatic DNSServiceRef g_dnsRef = NULL;
61226031Sstasstatic SCDynamicStoreRef g_store = NULL;
62226031Sstasstatic dispatch_queue_t g_queue = NULL;
63226031Sstas
64226031Sstas#define LOG(...) asl_log(NULL, NULL, ASL_LEVEL_INFO, __VA_ARGS__)
65226031Sstas
66226031Sstasstatic void create_dns_sd(void);
67226031Sstasstatic void destroy_dns_sd(void);
68226031Sstasstatic void update_all(SCDynamicStoreRef, CFArrayRef, void *);
69226031Sstas
70226031Sstas
71226031Sstas/* parameters */
72226031Sstasstatic CFStringRef NetworkChangedKey_BackToMyMac = CFSTR("Setup:/Network/BackToMyMac");
73226031Sstas
74226031Sstas
75226031Sstasstatic char *
76226031SstasCFString2utf8(CFStringRef string)
77226031Sstas{
78226031Sstas    size_t size;
79226031Sstas    char *str;
80226031Sstas
81226031Sstas    size = 1 + CFStringGetMaximumSizeForEncoding(CFStringGetLength(string), kCFStringEncodingUTF8);
82226031Sstas    str = malloc(size);
83226031Sstas    if (str == NULL)
84226031Sstas	return NULL;
85226031Sstas
86226031Sstas    if (CFStringGetCString(string, str, size, kCFStringEncodingUTF8) == false) {
87226031Sstas	free(str);
88226031Sstas	return NULL;
89226031Sstas    }
90226031Sstas    return str;
91226031Sstas}
92226031Sstas
93226031Sstas/*
94226031Sstas *
95226031Sstas */
96226031Sstas
97226031Sstasstatic void
98226031Sstasretry_timer(void)
99226031Sstas{
100226031Sstas    dispatch_source_t s;
101226031Sstas    dispatch_time_t t;
102226031Sstas
103226031Sstas    s = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,
104226031Sstas			       0, 0, g_queue);
105226031Sstas    t = dispatch_time(DISPATCH_TIME_NOW, 5ull * NSEC_PER_SEC);
106226031Sstas    dispatch_source_set_timer(s, t, 0, NSEC_PER_SEC);
107226031Sstas    dispatch_source_set_event_handler(s, ^{
108226031Sstas	    create_dns_sd();
109226031Sstas	    dispatch_release(s);
110226031Sstas	});
111226031Sstas    dispatch_resume(s);
112226031Sstas}
113226031Sstas
114226031Sstas/*
115226031Sstas *
116226031Sstas */
117226031Sstas
118226031Sstasstatic void
119226031Sstascreate_dns_sd(void)
120226031Sstas{
121226031Sstas    DNSServiceErrorType error;
122226031Sstas    dispatch_source_t s;
123226031Sstas
124226031Sstas    error = DNSServiceCreateConnection(&g_dnsRef);
125226031Sstas    if (error) {
126226031Sstas	retry_timer();
127226031Sstas	return;
128226031Sstas    }
129226031Sstas
130226031Sstas    dispatch_suspend(g_queue);
131226031Sstas
132226031Sstas    s = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,
133226031Sstas			       DNSServiceRefSockFD(g_dnsRef),
134226031Sstas			       0, g_queue);
135226031Sstas
136226031Sstas    dispatch_source_set_event_handler(s, ^{
137226031Sstas	    DNSServiceErrorType ret = DNSServiceProcessResult(g_dnsRef);
138226031Sstas	    /* on error tear down and set timer to recreate */
139226031Sstas	    if (ret != kDNSServiceErr_NoError && ret != kDNSServiceErr_Transient) {
140226031Sstas		dispatch_source_cancel(s);
141226031Sstas	    }
142226031Sstas	});
143226031Sstas
144226031Sstas    dispatch_source_set_cancel_handler(s, ^{
145226031Sstas	    destroy_dns_sd();
146226031Sstas	    retry_timer();
147226031Sstas	    dispatch_release(s);
148226031Sstas	});
149226031Sstas
150226031Sstas    dispatch_resume(s);
151226031Sstas
152226031Sstas    /* Do the first update ourself */
153226031Sstas    update_all(g_store, NULL, NULL);
154226031Sstas    dispatch_resume(g_queue);
155226031Sstas}
156226031Sstas
157226031Sstasstatic void
158226031Sstasdomain_add(const char *domain, const char *realm, int flag)
159226031Sstas{
160226031Sstas    struct entry *e;
161226031Sstas
162226031Sstas    for (e = g_entries; e != NULL; e = e->next) {
163226031Sstas	if (strcmp(domain, e->domain) == 0 && strcmp(realm, e->realm) == 0) {
164226031Sstas	    e->flags |= flag;
165226031Sstas	    return;
166226031Sstas	}
167226031Sstas    }
168226031Sstas
169226031Sstas    LOG("Adding realm %s to domain %s", realm, domain);
170226031Sstas
171226031Sstas    e = calloc(1, sizeof(*e));
172226031Sstas    if (e == NULL)
173226031Sstas	return;
174226031Sstas    e->domain = strdup(domain);
175226031Sstas    e->realm = strdup(realm);
176226031Sstas    if (e->domain == NULL || e->realm == NULL) {
177226031Sstas	free(e->domain);
178226031Sstas	free(e->realm);
179226031Sstas	free(e);
180226031Sstas	return;
181226031Sstas    }
182226031Sstas    e->flags = flag | F_PUSH; /* if we allocate, we push */
183226031Sstas    e->next = g_entries;
184226031Sstas    g_entries = e;
185226031Sstas}
186226031Sstas
187226031Sstasstruct addctx {
188226031Sstas    int flags;
189226031Sstas    const char *realm;
190226031Sstas};
191226031Sstas
192226031Sstasstatic void
193226031Sstasdomains_add(const void *key, const void *value, void *context)
194226031Sstas{
195226031Sstas    char *str = CFString2utf8((CFStringRef)value);
196226031Sstas    struct addctx *ctx = context;
197226031Sstas
198226031Sstas    if (str == NULL)
199226031Sstas	return;
200226031Sstas    if (str[0] != '\0')
201226031Sstas	domain_add(str, ctx->realm, F_EXISTS | ctx->flags);
202226031Sstas    free(str);
203226031Sstas}
204226031Sstas
205226031Sstas
206226031Sstasstatic void
207226031SstasdnsCallback(DNSServiceRef sdRef __attribute__((unused)),
208226031Sstas	    DNSRecordRef RecordRef __attribute__((unused)),
209226031Sstas	    DNSServiceFlags flags __attribute__((unused)),
210226031Sstas	    DNSServiceErrorType errorCode __attribute__((unused)),
211226031Sstas	    void *context __attribute__((unused)))
212226031Sstas{
213226031Sstas}
214226031Sstas
215226031Sstas#ifdef REGISTER_SRV_RR
216226031Sstas
217226031Sstas/*
218226031Sstas * Register DNS SRV rr for the realm.
219226031Sstas */
220226031Sstas
221226031Sstasstatic const char *register_names[2] = {
222226031Sstas    "_kerberos._tcp",
223226031Sstas    "_kerberos._udp"
224226031Sstas};
225226031Sstas
226226031Sstasstatic struct {
227226031Sstas    DNSRecordRef *val;
228226031Sstas    size_t len;
229226031Sstas} srvRefs = { NULL, 0 };
230226031Sstas
231226031Sstasstatic void
232226031Sstasregister_srv(const char *realm, const char *hostname, int port)
233226031Sstas{
234226031Sstas    unsigned char target[1024];
235226031Sstas    int i;
236226031Sstas    int size;
237226031Sstas
238226031Sstas    /* skip registering LKDC realms */
239226031Sstas    if (strncmp(realm, "LKDC:", 5) == 0)
240226031Sstas	return;
241226031Sstas
242226031Sstas    /* encode SRV-RR */
243226031Sstas    target[0] = 0; /* priority */
244226031Sstas    target[1] = 0; /* priority */
245226031Sstas    target[2] = 0; /* weight */
246226031Sstas    target[3] = 0; /* weigth */
247226031Sstas    target[4] = (port >> 8) & 0xff; /* port */
248226031Sstas    target[5] = (port >> 0) & 0xff; /* port */
249226031Sstas
250226031Sstas    size = dn_comp(hostname, target + 6, sizeof(target) - 6, NULL, NULL);
251226031Sstas    if (size < 0)
252226031Sstas	return;
253226031Sstas
254226031Sstas    size += 6;
255226031Sstas
256226031Sstas    LOG("register SRV rr for realm %s hostname %s:%d", realm, hostname, port);
257226031Sstas
258226031Sstas    for (i = 0; i < sizeof(register_names)/sizeof(register_names[0]); i++) {
259226031Sstas	char name[kDNSServiceMaxDomainName];
260226031Sstas	DNSServiceErrorType error;
261226031Sstas	void *ptr;
262226031Sstas
263226031Sstas	ptr = realloc(srvRefs.val, sizeof(srvRefs.val[0]) * (srvRefs.len + 1));
264226031Sstas	if (ptr == NULL)
265226031Sstas	    errx(1, "malloc: out of memory");
266226031Sstas	srvRefs.val = ptr;
267226031Sstas
268226031Sstas	DNSServiceConstructFullName(name, NULL, register_names[i], realm);
269226031Sstas
270226031Sstas	error = DNSServiceRegisterRecord(g_dnsRef,
271226031Sstas					 &srvRefs.val[srvRefs.len],
272226031Sstas					 kDNSServiceFlagsUnique | kDNSServiceFlagsShareConnection,
273226031Sstas					 0,
274226031Sstas					 name,
275226031Sstas					 kDNSServiceType_SRV,
276226031Sstas					 kDNSServiceClass_IN,
277226031Sstas					 size,
278226031Sstas					 target,
279226031Sstas					 0,
280226031Sstas					 dnsCallback,
281226031Sstas					 NULL);
282226031Sstas	if (error) {
283226031Sstas	    LOG("Failed to register SRV rr for realm %s: %d", realm, error);
284226031Sstas	} else
285226031Sstas	    srvRefs.len++;
286226031Sstas    }
287226031Sstas}
288226031Sstas
289226031Sstasstatic void
290226031Sstasunregister_srv_realms(void)
291226031Sstas{
292226031Sstas    if (g_dnsRef) {
293226031Sstas	for (i = 0; i < srvRefs.len; i++)
294226031Sstas	    DNSServiceRemoveRecord(g_dnsRef, srvRefs.val[i], 0);
295226031Sstas    }
296226031Sstas    free(srvRefs.val);
297226031Sstas    srvRefs.len = 0;
298226031Sstas    srvRefs.val = NULL;
299226031Sstas}
300226031Sstas
301226031Sstasstatic void
302226031Sstasregister_srv_realms(CFStringRef host)
303226031Sstas{
304226031Sstas    krb5_error_code ret;
305226031Sstas    char *hostname;
306226031Sstas    size_t i;
307226031Sstas
308226031Sstas    /* first unregister old names */
309226031Sstas
310226031Sstas    hostname = CFString2utf8(host);
311226031Sstas    if (hostname == NULL)
312226031Sstas	return;
313226031Sstas
314226031Sstas    for(i = 0; i < announce_config->num_db; i++) {
315226031Sstas	char **realms, **r;
316226031Sstas
317226031Sstas	if (announce_config->db[i]->hdb_get_realms == NULL)
318226031Sstas	    continue;
319226031Sstas
320226031Sstas	ret = (announce_config->db[i]->hdb_get_realms)(announce_context, &realms);
321226031Sstas	if (ret == 0) {
322226031Sstas	    for (r = realms; r && *r; r++)
323226031Sstas		register_srv(*r, hostname, 88);
324226031Sstas	    krb5_free_host_realm(announce_context, realms);
325226031Sstas	}
326226031Sstas    }
327226031Sstas
328226031Sstas    free(hostname);
329226031Sstas}
330226031Sstas#endif /* REGISTER_SRV_RR */
331226031Sstas
332226031Sstasstatic void
333226031Sstasupdate_dns(void)
334226031Sstas{
335226031Sstas    DNSServiceErrorType error;
336226031Sstas    struct entry **e = &g_entries;
337226031Sstas    char *hostname;
338226031Sstas
339226031Sstas    hostname = CFString2utf8(g_hostname);
340226031Sstas    if (hostname == NULL)
341226031Sstas	return;
342226031Sstas
343226031Sstas    while (*e != NULL) {
344226031Sstas	/* remove if this wasn't updated */
345226031Sstas	if (((*e)->flags & F_EXISTS) == 0) {
346226031Sstas	    struct entry *drop = *e;
347226031Sstas	    *e = (*e)->next;
348226031Sstas
349226031Sstas	    LOG("Deleting realm %s from domain %s",
350226031Sstas		drop->realm, drop->domain);
351226031Sstas
352226031Sstas	    if (drop->recordRef && g_dnsRef)
353226031Sstas		DNSServiceRemoveRecord(g_dnsRef, drop->recordRef, 0);
354226031Sstas	    free(drop->domain);
355226031Sstas	    free(drop->realm);
356226031Sstas	    free(drop);
357226031Sstas	    continue;
358226031Sstas	}
359226031Sstas	if ((*e)->flags & F_PUSH) {
360226031Sstas	    struct entry *update = *e;
361226031Sstas	    char *dnsdata, *name;
362226031Sstas	    size_t len;
363226031Sstas
364226031Sstas	    len = strlen(update->realm);
365226031Sstas	    asprintf(&dnsdata, "%c%s", (int)len, update->realm);
366226031Sstas	    if (dnsdata == NULL)
367226031Sstas		errx(1, "malloc");
368226031Sstas
369226031Sstas	    asprintf(&name, "_kerberos.%s.%s", hostname, update->domain);
370226031Sstas	    if (name == NULL)
371226031Sstas		errx(1, "malloc");
372226031Sstas
373226031Sstas	    if (update->recordRef)
374226031Sstas		DNSServiceRemoveRecord(g_dnsRef, update->recordRef, 0);
375226031Sstas
376226031Sstas	    error = DNSServiceRegisterRecord(g_dnsRef,
377226031Sstas					     &update->recordRef,
378226031Sstas					     kDNSServiceFlagsShared | kDNSServiceFlagsAllowRemoteQuery,
379226031Sstas					     0,
380226031Sstas					     name,
381226031Sstas					     kDNSServiceType_TXT,
382226031Sstas					     kDNSServiceClass_IN,
383226031Sstas					     len+1,
384226031Sstas					     dnsdata,
385226031Sstas					     0,
386226031Sstas					     dnsCallback,
387226031Sstas					     NULL);
388226031Sstas	    free(name);
389226031Sstas	    free(dnsdata);
390226031Sstas	    if (error)
391226031Sstas		errx(1, "failure to update entry for %s/%s",
392226031Sstas		     update->domain, update->realm);
393226031Sstas	}
394226031Sstas	e = &(*e)->next;
395226031Sstas    }
396226031Sstas    free(hostname);
397226031Sstas}
398226031Sstas
399226031Sstasstatic void
400226031Sstasupdate_entries(SCDynamicStoreRef store, const char *realm, int flags)
401226031Sstas{
402226031Sstas    CFDictionaryRef btmm;
403226031Sstas
404226031Sstas    /* we always announce in the local domain */
405226031Sstas    domain_add("local", realm, F_EXISTS | flags);
406226031Sstas
407226031Sstas    /* announce btmm */
408226031Sstas    btmm = SCDynamicStoreCopyValue(store, NetworkChangedKey_BackToMyMac);
409226031Sstas    if (btmm) {
410226031Sstas	struct addctx addctx;
411226031Sstas
412226031Sstas	addctx.flags = flags;
413226031Sstas	addctx.realm = realm;
414226031Sstas
415226031Sstas	CFDictionaryApplyFunction(btmm, domains_add, &addctx);
416226031Sstas	CFRelease(btmm);
417226031Sstas    }
418226031Sstas}
419226031Sstas
420226031Sstasstatic void
421226031Sstasupdate_all(SCDynamicStoreRef store, CFArrayRef changedKeys, void *info)
422226031Sstas{
423226031Sstas    struct entry *e;
424226031Sstas    CFStringRef host;
425226031Sstas    int i, flags = 0;
426226031Sstas
427226031Sstas    LOG("something changed, running update");
428226031Sstas
429226031Sstas    host = SCDynamicStoreCopyLocalHostName(store);
430226031Sstas    if (host == NULL)
431226031Sstas	return;
432226031Sstas
433226031Sstas    if (g_hostname == NULL || CFStringCompare(host, g_hostname, 0) != kCFCompareEqualTo) {
434226031Sstas	if (g_hostname)
435226031Sstas	    CFRelease(g_hostname);
436226031Sstas	g_hostname = CFRetain(host);
437226031Sstas	flags = F_PUSH; /* if hostname has changed, force push */
438226031Sstas
439226031Sstas#ifdef REGISTER_SRV_RR
440226031Sstas	register_srv_realms(g_hostname);
441226031Sstas#endif
442226031Sstas    }
443226031Sstas
444226031Sstas    for (e = g_entries; e != NULL; e = e->next)
445226031Sstas	e->flags &= ~(F_EXISTS|F_PUSH);
446226031Sstas
447226031Sstas    for(i = 0; i < announce_config->num_db; i++) {
448226031Sstas	krb5_error_code ret;
449226031Sstas	char **realms, **r;
450226031Sstas
451226031Sstas	if (announce_config->db[i]->hdb_get_realms == NULL)
452226031Sstas	    continue;
453226031Sstas
454226031Sstas	ret = (announce_config->db[i]->hdb_get_realms)(announce_context, announce_config->db[i], &realms);
455226031Sstas	if (ret == 0) {
456226031Sstas	    for (r = realms; r && *r; r++)
457226031Sstas		update_entries(store, *r, flags);
458226031Sstas	    krb5_free_host_realm(announce_context, realms);
459226031Sstas	}
460226031Sstas    }
461226031Sstas
462226031Sstas    update_dns();
463226031Sstas
464226031Sstas    CFRelease(host);
465226031Sstas}
466226031Sstas
467226031Sstasstatic void
468226031Sstasdelete_all(void)
469226031Sstas{
470226031Sstas    struct entry *e;
471226031Sstas
472226031Sstas    for (e = g_entries; e != NULL; e = e->next)
473226031Sstas	e->flags &= ~(F_EXISTS|F_PUSH);
474226031Sstas
475226031Sstas    update_dns();
476226031Sstas    if (g_entries != NULL)
477226031Sstas	errx(1, "Failed to remove all bonjour entries");
478226031Sstas}
479226031Sstas
480226031Sstasstatic void
481226031Sstasdestroy_dns_sd(void)
482226031Sstas{
483226031Sstas    if (g_dnsRef == NULL)
484226031Sstas	return;
485226031Sstas
486226031Sstas    delete_all();
487226031Sstas#ifdef REGISTER_SRV_RR
488226031Sstas    unregister_srv_realms();
489226031Sstas#endif
490226031Sstas
491226031Sstas    DNSServiceRefDeallocate(g_dnsRef);
492226031Sstas    g_dnsRef = NULL;
493226031Sstas}
494226031Sstas
495226031Sstas
496226031Sstasstatic SCDynamicStoreRef
497226031Sstasregister_notification(void)
498226031Sstas{
499226031Sstas    SCDynamicStoreRef store;
500226031Sstas    CFStringRef computerNameKey;
501226031Sstas    CFMutableArrayRef keys;
502226031Sstas
503226031Sstas    computerNameKey = SCDynamicStoreKeyCreateHostNames(kCFAllocatorDefault);
504226031Sstas
505226031Sstas    store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("Network watcher"),
506226031Sstas				 update_all, NULL);
507226031Sstas    if (store == NULL)
508226031Sstas	errx(1, "SCDynamicStoreCreate");
509226031Sstas
510226031Sstas    keys = CFArrayCreateMutable(kCFAllocatorDefault, 2, &kCFTypeArrayCallBacks);
511226031Sstas    if (keys == NULL)
512226031Sstas	errx(1, "CFArrayCreateMutable");
513226031Sstas
514226031Sstas    CFArrayAppendValue(keys, computerNameKey);
515226031Sstas    CFArrayAppendValue(keys, NetworkChangedKey_BackToMyMac);
516226031Sstas
517226031Sstas    if (SCDynamicStoreSetNotificationKeys(store, keys, NULL) == false)
518226031Sstas	errx(1, "SCDynamicStoreSetNotificationKeys");
519226031Sstas
520226031Sstas    CFRelease(computerNameKey);
521226031Sstas    CFRelease(keys);
522226031Sstas
523226031Sstas    if (!SCDynamicStoreSetDispatchQueue(store, g_queue))
524226031Sstas	errx(1, "SCDynamicStoreSetDispatchQueue");
525226031Sstas
526226031Sstas    return store;
527226031Sstas}
528226031Sstas#endif
529226031Sstas
530226031Sstasvoid
531226031Sstasbonjour_announce(krb5_context context, krb5_kdc_configuration *config)
532226031Sstas{
533226031Sstas#if defined(__APPLE__) && defined(HAVE_GCD)
534226031Sstas    g_queue = dispatch_queue_create("com.apple.kdc_announce", NULL);
535226031Sstas    if (!g_queue)
536226031Sstas	errx(1, "dispatch_queue_create");
537226031Sstas
538226031Sstas    g_store = register_notification();
539226031Sstas    announce_config = config;
540226031Sstas    announce_context = context;
541226031Sstas
542226031Sstas    create_dns_sd();
543226031Sstas#endif
544226031Sstas}
545