freebsd-nq/crypto/heimdal/kdc/announce.c
Stanislav Sedov ae77177087 - Update FreeBSD Heimdal distribution to version 1.5.1. This also brings
several new kerberos related libraries and applications to FreeBSD:
  o kgetcred(1) allows one to manually get a ticket for a particular service.
  o kf(1) securily forwards ticket to another host through an authenticated
    and encrypted stream.
  o kcc(1) is an umbrella program around klist(1), kswitch(1), kgetcred(1)
    and other user kerberos operations. klist and kswitch are just symlinks
    to kcc(1) now.
  o kswitch(1) allows you to easily switch between kerberos credentials if
    you're running KCM.
  o hxtool(1) is a certificate management tool to use with PKINIT.
  o string2key(1) maps a password into key.
  o kdigest(8) is a userland tool to access the KDC's digest interface.
  o kimpersonate(8) creates a "fake" ticket for a service.

  We also now install manpages for some lirbaries that were not installed
  before, libheimntlm and libhx509.

- The new HEIMDAL version no longer supports Kerberos 4.  All users are
  recommended to switch to Kerberos 5.

- Weak ciphers are now disabled by default.  To enable DES support (used
  by telnet(8)), use "allow_weak_crypto" option in krb5.conf.

- libtelnet, pam_ksu and pam_krb5 are now compiled with error on warnings
  disabled due to the function they use (krb5_get_err_text(3)) being
  deprecated.  I plan to work on this next.

- Heimdal's KDC now require sqlite to operate.  We use the bundled version
  and install it as libheimsqlite.  If some other FreeBSD components will
  require it in the future we can rename it to libbsdsqlite and use for these
  components as well.

- This is not a latest Heimdal version, the new one was released while I was
  working on the update.  I will update it to 1.5.2 soon, as it fixes some
  important bugs and security issues.
2012-03-22 08:48:42 +00:00

545 lines
12 KiB
C

/*
* 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 <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>
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
}