/* * Copyright (c) 2008 Apple Inc. All Rights Reserved. * * Export of this software from the United States of America may require * a specific license from the United States Government. It is the * responsibility of any person or organization contemplating export to * obtain such a license before exporting. * * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and * distribute this software and its documentation for any purpose and * without fee is hereby granted, provided that the above copyright * notice appear in all copies and that both that copyright notice and * this permission notice appear in supporting documentation, and that * the name of Apple Inc. not be used in advertising or publicity pertaining * to distribution of the software without specific, written prior * permission. Apple Inc. makes no representations about the suitability of * this software for any purpose. It is provided "as is" without express * or implied warranty. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. * */ #include "kdc_locl.h" #if defined(__APPLE__) && defined(HAVE_GCD) #include #include #include #include #include #include #include #include #include static krb5_kdc_configuration *announce_config; static krb5_context announce_context; struct entry { DNSRecordRef recordRef; char *domain; char *realm; #define F_EXISTS 1 #define F_PUSH 2 int flags; struct entry *next; }; /* #define REGISTER_SRV_RR */ static struct entry *g_entries = NULL; static CFStringRef g_hostname = NULL; static DNSServiceRef g_dnsRef = NULL; static SCDynamicStoreRef g_store = NULL; static dispatch_queue_t g_queue = NULL; #define LOG(...) asl_log(NULL, NULL, ASL_LEVEL_INFO, __VA_ARGS__) static void create_dns_sd(void); static void destroy_dns_sd(void); static void update_all(SCDynamicStoreRef, CFArrayRef, void *); /* parameters */ static CFStringRef NetworkChangedKey_BackToMyMac = CFSTR("Setup:/Network/BackToMyMac"); static char * CFString2utf8(CFStringRef string) { size_t size; char *str; size = 1 + CFStringGetMaximumSizeForEncoding(CFStringGetLength(string), kCFStringEncodingUTF8); str = malloc(size); if (str == NULL) return NULL; if (CFStringGetCString(string, str, size, kCFStringEncodingUTF8) == false) { free(str); return NULL; } return str; } /* * */ static void retry_timer(void) { dispatch_source_t s; dispatch_time_t t; s = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, g_queue); t = dispatch_time(DISPATCH_TIME_NOW, 5ull * NSEC_PER_SEC); dispatch_source_set_timer(s, t, 0, NSEC_PER_SEC); dispatch_source_set_event_handler(s, ^{ create_dns_sd(); dispatch_release(s); }); dispatch_resume(s); } /* * */ static void create_dns_sd(void) { DNSServiceErrorType error; dispatch_source_t s; error = DNSServiceCreateConnection(&g_dnsRef); if (error) { retry_timer(); return; } dispatch_suspend(g_queue); s = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, DNSServiceRefSockFD(g_dnsRef), 0, g_queue); dispatch_source_set_event_handler(s, ^{ DNSServiceErrorType ret = DNSServiceProcessResult(g_dnsRef); /* on error tear down and set timer to recreate */ if (ret != kDNSServiceErr_NoError && ret != kDNSServiceErr_Transient) { dispatch_source_cancel(s); } }); dispatch_source_set_cancel_handler(s, ^{ destroy_dns_sd(); retry_timer(); dispatch_release(s); }); dispatch_resume(s); /* Do the first update ourself */ update_all(g_store, NULL, NULL); dispatch_resume(g_queue); } static void domain_add(const char *domain, const char *realm, int flag) { struct entry *e; for (e = g_entries; e != NULL; e = e->next) { if (strcmp(domain, e->domain) == 0 && strcmp(realm, e->realm) == 0) { e->flags |= flag; return; } } LOG("Adding realm %s to domain %s", realm, domain); e = calloc(1, sizeof(*e)); if (e == NULL) return; e->domain = strdup(domain); e->realm = strdup(realm); if (e->domain == NULL || e->realm == NULL) { free(e->domain); free(e->realm); free(e); return; } e->flags = flag | F_PUSH; /* if we allocate, we push */ e->next = g_entries; g_entries = e; } struct addctx { int flags; const char *realm; }; static void domains_add(const void *key, const void *value, void *context) { char *str = CFString2utf8((CFStringRef)value); struct addctx *ctx = context; if (str == NULL) return; if (str[0] != '\0') domain_add(str, ctx->realm, F_EXISTS | ctx->flags); free(str); } static void dnsCallback(DNSServiceRef sdRef __attribute__((unused)), DNSRecordRef RecordRef __attribute__((unused)), DNSServiceFlags flags __attribute__((unused)), DNSServiceErrorType errorCode __attribute__((unused)), void *context __attribute__((unused))) { } #ifdef REGISTER_SRV_RR /* * Register DNS SRV rr for the realm. */ static const char *register_names[2] = { "_kerberos._tcp", "_kerberos._udp" }; static struct { DNSRecordRef *val; size_t len; } srvRefs = { NULL, 0 }; static void register_srv(const char *realm, const char *hostname, int port) { unsigned char target[1024]; int i; int size; /* skip registering LKDC realms */ if (strncmp(realm, "LKDC:", 5) == 0) return; /* encode SRV-RR */ target[0] = 0; /* priority */ target[1] = 0; /* priority */ target[2] = 0; /* weight */ target[3] = 0; /* weigth */ target[4] = (port >> 8) & 0xff; /* port */ target[5] = (port >> 0) & 0xff; /* port */ size = dn_comp(hostname, target + 6, sizeof(target) - 6, NULL, NULL); if (size < 0) return; size += 6; LOG("register SRV rr for realm %s hostname %s:%d", realm, hostname, port); for (i = 0; i < sizeof(register_names)/sizeof(register_names[0]); i++) { char name[kDNSServiceMaxDomainName]; DNSServiceErrorType error; void *ptr; ptr = realloc(srvRefs.val, sizeof(srvRefs.val[0]) * (srvRefs.len + 1)); if (ptr == NULL) errx(1, "malloc: out of memory"); srvRefs.val = ptr; DNSServiceConstructFullName(name, NULL, register_names[i], realm); error = DNSServiceRegisterRecord(g_dnsRef, &srvRefs.val[srvRefs.len], kDNSServiceFlagsUnique | kDNSServiceFlagsShareConnection, 0, name, kDNSServiceType_SRV, kDNSServiceClass_IN, size, target, 0, dnsCallback, NULL); if (error) { LOG("Failed to register SRV rr for realm %s: %d", realm, error); } else srvRefs.len++; } } static void unregister_srv_realms(void) { if (g_dnsRef) { for (i = 0; i < srvRefs.len; i++) DNSServiceRemoveRecord(g_dnsRef, srvRefs.val[i], 0); } free(srvRefs.val); srvRefs.len = 0; srvRefs.val = NULL; } static void register_srv_realms(CFStringRef host) { krb5_error_code ret; char *hostname; size_t i; /* first unregister old names */ hostname = CFString2utf8(host); if (hostname == NULL) return; for(i = 0; i < announce_config->num_db; i++) { char **realms, **r; if (announce_config->db[i]->hdb_get_realms == NULL) continue; ret = (announce_config->db[i]->hdb_get_realms)(announce_context, &realms); if (ret == 0) { for (r = realms; r && *r; r++) register_srv(*r, hostname, 88); krb5_free_host_realm(announce_context, realms); } } free(hostname); } #endif /* REGISTER_SRV_RR */ static void update_dns(void) { DNSServiceErrorType error; struct entry **e = &g_entries; char *hostname; hostname = CFString2utf8(g_hostname); if (hostname == NULL) return; while (*e != NULL) { /* remove if this wasn't updated */ if (((*e)->flags & F_EXISTS) == 0) { struct entry *drop = *e; *e = (*e)->next; LOG("Deleting realm %s from domain %s", drop->realm, drop->domain); if (drop->recordRef && g_dnsRef) DNSServiceRemoveRecord(g_dnsRef, drop->recordRef, 0); free(drop->domain); free(drop->realm); free(drop); continue; } if ((*e)->flags & F_PUSH) { struct entry *update = *e; char *dnsdata, *name; size_t len; len = strlen(update->realm); asprintf(&dnsdata, "%c%s", (int)len, update->realm); if (dnsdata == NULL) errx(1, "malloc"); asprintf(&name, "_kerberos.%s.%s", hostname, update->domain); if (name == NULL) errx(1, "malloc"); if (update->recordRef) DNSServiceRemoveRecord(g_dnsRef, update->recordRef, 0); error = DNSServiceRegisterRecord(g_dnsRef, &update->recordRef, kDNSServiceFlagsShared | kDNSServiceFlagsAllowRemoteQuery, 0, name, kDNSServiceType_TXT, kDNSServiceClass_IN, len+1, dnsdata, 0, dnsCallback, NULL); free(name); free(dnsdata); if (error) errx(1, "failure to update entry for %s/%s", update->domain, update->realm); } e = &(*e)->next; } free(hostname); } static void update_entries(SCDynamicStoreRef store, const char *realm, int flags) { CFDictionaryRef btmm; /* we always announce in the local domain */ domain_add("local", realm, F_EXISTS | flags); /* announce btmm */ btmm = SCDynamicStoreCopyValue(store, NetworkChangedKey_BackToMyMac); if (btmm) { struct addctx addctx; addctx.flags = flags; addctx.realm = realm; CFDictionaryApplyFunction(btmm, domains_add, &addctx); CFRelease(btmm); } } static void update_all(SCDynamicStoreRef store, CFArrayRef changedKeys, void *info) { struct entry *e; CFStringRef host; int i, flags = 0; LOG("something changed, running update"); host = SCDynamicStoreCopyLocalHostName(store); if (host == NULL) return; if (g_hostname == NULL || CFStringCompare(host, g_hostname, 0) != kCFCompareEqualTo) { if (g_hostname) CFRelease(g_hostname); g_hostname = CFRetain(host); flags = F_PUSH; /* if hostname has changed, force push */ #ifdef REGISTER_SRV_RR register_srv_realms(g_hostname); #endif } for (e = g_entries; e != NULL; e = e->next) e->flags &= ~(F_EXISTS|F_PUSH); for(i = 0; i < announce_config->num_db; i++) { krb5_error_code ret; char **realms, **r; if (announce_config->db[i]->hdb_get_realms == NULL) continue; ret = (announce_config->db[i]->hdb_get_realms)(announce_context, announce_config->db[i], &realms); if (ret == 0) { for (r = realms; r && *r; r++) update_entries(store, *r, flags); krb5_free_host_realm(announce_context, realms); } } update_dns(); CFRelease(host); } static void delete_all(void) { struct entry *e; for (e = g_entries; e != NULL; e = e->next) e->flags &= ~(F_EXISTS|F_PUSH); update_dns(); if (g_entries != NULL) errx(1, "Failed to remove all bonjour entries"); } static void destroy_dns_sd(void) { if (g_dnsRef == NULL) return; delete_all(); #ifdef REGISTER_SRV_RR unregister_srv_realms(); #endif DNSServiceRefDeallocate(g_dnsRef); g_dnsRef = NULL; } static SCDynamicStoreRef register_notification(void) { SCDynamicStoreRef store; CFStringRef computerNameKey; CFMutableArrayRef keys; computerNameKey = SCDynamicStoreKeyCreateHostNames(kCFAllocatorDefault); store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("Network watcher"), update_all, NULL); if (store == NULL) errx(1, "SCDynamicStoreCreate"); keys = CFArrayCreateMutable(kCFAllocatorDefault, 2, &kCFTypeArrayCallBacks); if (keys == NULL) errx(1, "CFArrayCreateMutable"); CFArrayAppendValue(keys, computerNameKey); CFArrayAppendValue(keys, NetworkChangedKey_BackToMyMac); if (SCDynamicStoreSetNotificationKeys(store, keys, NULL) == false) errx(1, "SCDynamicStoreSetNotificationKeys"); CFRelease(computerNameKey); CFRelease(keys); if (!SCDynamicStoreSetDispatchQueue(store, g_queue)) errx(1, "SCDynamicStoreSetDispatchQueue"); return store; } #endif void bonjour_announce(krb5_context context, krb5_kdc_configuration *config) { #if defined(__APPLE__) && defined(HAVE_GCD) g_queue = dispatch_queue_create("com.apple.kdc_announce", NULL); if (!g_queue) errx(1, "dispatch_queue_create"); g_store = register_notification(); announce_config = config; announce_context = context; create_dns_sd(); #endif }