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
45static krb5_kdc_configuration *announce_config;
46static krb5_context announce_context;
47
48struct 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
60static struct entry *g_entries = NULL;
61static CFStringRef g_hostname = NULL;
62static DNSServiceRef g_dnsRef = NULL;
63static SCDynamicStoreRef g_store = NULL;
64static dispatch_queue_t g_queue = NULL;
65
66#define LOG(...) asl_log(NULL, NULL, ASL_LEVEL_INFO, __VA_ARGS__)
67
68static void create_dns_sd(void);
69static void destroy_dns_sd(void);
70static void update_all(SCDynamicStoreRef, CFArrayRef, void *);
71
72
73/* parameters */
74static CFStringRef NetworkChangedKey_BackToMyMac = CFSTR("Setup:/Network/BackToMyMac");
75
76
77static char *
78CFString2utf8(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
99static void
100retry_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
120static void
121create_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
159static void
160domain_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
189struct addctx {
190    int flags;
191    const char *realm;
192};
193
194static void
195domains_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
208static void
209dnsCallback(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
223static const char *register_names[2] = {
224    "_kerberos._tcp",
225    "_kerberos._udp"
226};
227
228static struct {
229    DNSRecordRef *val;
230    size_t len;
231} srvRefs = { NULL, 0 };
232
233static void
234register_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
291static void
292unregister_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
303static void
304register_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
334static void
335update_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
401static void
402update_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
422static void
423update_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
469static void
470delete_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
482static void
483destroy_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
498static SCDynamicStoreRef
499register_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
532void
533bonjour_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