]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - crypto/heimdal/kdc/announce.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / crypto / heimdal / kdc / announce.c
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 *
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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 }