announce realm via bonjour
This commit is contained in:
535
kdc/announce.c
Normal file
535
kdc/announce.c
Normal file
@@ -0,0 +1,535 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <SystemConfiguration/SCDynamicStore.h>
|
||||
#include <SystemConfiguration/SCDynamicStoreCopySpecific.h>
|
||||
#include <SystemConfiguration/SCDynamicStoreKey.h>
|
||||
|
||||
#include <dispatch/dispatch.h>
|
||||
|
||||
#include <asl.h>
|
||||
#include <resolv.h>
|
||||
|
||||
#include <dns_sd.h>
|
||||
#include <err.h>
|
||||
|
||||
#include "kdc_locl.h"
|
||||
|
||||
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_timer_create(DISPATCH_TIMER_ONESHOT,
|
||||
5ull * NSEC_PER_SEC,
|
||||
0,
|
||||
NULL,
|
||||
g_queue,
|
||||
^(dispatch_event_t event){
|
||||
create_dns_sd();
|
||||
dispatch_release(dispatch_event_get_source(event));
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
|
||||
static void
|
||||
create_dns_sd(void)
|
||||
{
|
||||
DNSServiceErrorType error;
|
||||
|
||||
error = DNSServiceCreateConnection(&g_dnsRef);
|
||||
if (error) {
|
||||
retry_timer();
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch_suspend(g_queue);
|
||||
dispatch_source_read_create(DNSServiceRefSockFD(g_dnsRef),
|
||||
NULL,
|
||||
g_queue,
|
||||
^(dispatch_source_t ds){
|
||||
if (dispatch_source_get_error(ds, NULL)) {
|
||||
dispatch_release(ds);
|
||||
return;
|
||||
}
|
||||
DNSServiceErrorType ret = DNSServiceProcessResult(g_dnsRef);
|
||||
/* on error tear down and set timer to recreate */
|
||||
if (ret != kDNSServiceErr_NoError && ret != kDNSServiceErr_Transient) {
|
||||
destroy_dns_sd();
|
||||
retry_timer();
|
||||
dispatch_cancel(ds);
|
||||
}
|
||||
});
|
||||
|
||||
/* 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", 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;
|
||||
}
|
||||
|
||||
void
|
||||
bonjour_announce(krb5_context context, krb5_kdc_configuration *config)
|
||||
{
|
||||
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 /* __APPLE__ */
|
Reference in New Issue
Block a user