]> git.saurik.com Git - apple/configd.git/blobdiff - Plugins/IPMonitor/set-hostname.c
configd-130.tar.gz
[apple/configd.git] / Plugins / IPMonitor / set-hostname.c
diff --git a/Plugins/IPMonitor/set-hostname.c b/Plugins/IPMonitor/set-hostname.c
new file mode 100644 (file)
index 0000000..b5408f9
--- /dev/null
@@ -0,0 +1,932 @@
+/*
+ * Copyright (c) 2004-2005 Apple Computer, Inc. All rights reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ * 
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ * 
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ * 
+ * @APPLE_LICENSE_HEADER_END@
+ */
+#include <ctype.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb_async.h>
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <SystemConfiguration/SystemConfiguration.h>
+#include <SystemConfiguration/SCDynamicStoreCopyDHCPInfo.h>
+#include <SystemConfiguration/SCValidation.h>
+#include <SystemConfiguration/SCPrivate.h>     // for SCLog(), SCPrint()
+
+#include <notify.h>
+
+
+static SCDynamicStoreRef       store           = NULL;
+static CFRunLoopSourceRef      rls             = NULL;
+
+static CFMachPortRef           dnsPort         = NULL;
+static CFRunLoopSourceRef      dnsRLS          = NULL;
+static struct timeval          dnsQueryStart;
+
+static Boolean                 _verbose        = FALSE;
+
+
+/* SPI (from SCNetworkReachability.c) */
+Boolean
+_SC_checkResolverReachability(SCDynamicStoreRef         *storeP,
+                             SCNetworkConnectionFlags  *flags,
+                             Boolean                   *haveDNS,
+                             const char *              nodename);
+
+
+/*
+ * checkResolverReachabilityByAddress()
+ *
+ * Given an IP address, determine whether a reverse DNS query can be issued
+ * using the current network configuration.
+ */
+static Boolean
+checkResolverReachabilityByAddress(SCDynamicStoreRef store, struct sockaddr *sa)
+{
+       SCNetworkConnectionFlags        flags;
+       Boolean                         haveDNS;
+       int                             i;
+       Boolean                         ok              = FALSE;
+       char                            ptr_name[128];
+
+       /*
+        * Ideally, we would have an API that given a local IP
+        * address would return the DNS server(s) that would field
+        * a given PTR query.  Fortunately, we do have an SPI which
+        * which will provide this information given a "name" so we
+        * take the address, convert it into the inverse query name,
+        * and find out which servers should be consulted.
+        */
+
+       switch (sa->sa_family) {
+               case AF_INET : {
+                       union {
+                               in_addr_t       s_addr;
+                               unsigned char   b[4];
+                       } rev;
+                       struct sockaddr_in      *sin    = (struct sockaddr_in *)sa;
+
+                       /*
+                        * build "PTR" query name
+                        *   NNN.NNN.NNN.NNN.in-addr.arpa.
+                        */
+                       rev.s_addr = sin->sin_addr.s_addr;
+                       (void) snprintf(ptr_name, sizeof(ptr_name), "%u.%u.%u.%u.in-addr.arpa.",
+                                       rev.b[3],
+                                       rev.b[2],
+                                       rev.b[1],
+                                       rev.b[0]);
+
+                       break;
+               }
+
+               case AF_INET6 : {
+                       int                     s       = 0;
+                       struct sockaddr_in6     *sin6   = (struct sockaddr_in6 *)sa;
+                       int                     x       = sizeof(ptr_name);
+                       int                     n;
+
+#define        USE_NIBBLE_QUERY
+#ifdef USE_NIBBLE_QUERY
+                       /*
+                        * build IPv6 "nibble" PTR query name (RFC 1886, RFC 3152)
+                        *   N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.ip6.arpa.
+                        */
+                       for (i = sizeof(sin6->sin6_addr) - 1; i >= 0; i--) {
+                               n = snprintf(&ptr_name[s], x, "%x.%x.",
+                                            ( sin6->sin6_addr.s6_addr[i]       & 0xf),
+                                            ((sin6->sin6_addr.s6_addr[i] >> 4) & 0xf));
+                               if ((n == -1) || (n >= x)) {
+                                       goto done;
+                               }
+
+                               s += n;
+                               x -= n;
+                       }
+
+                       n = snprintf(&ptr_name[s], x, "ip6.arpa.");
+                       if ((n == -1) || (n >= x)) {
+                               goto done;
+                       }
+#else  /* USE_NIBBLE_QUERY */
+                       /*
+                        * build IPv6 "bit-string" PTR query name (RFC 2673)
+                        *   \[xNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN].ip6.arpa.
+                        */
+                       n = snprintf(&ptr_name[0], x, "\\[x");
+                       if ((n == -1) || (n >= x)) {
+                               goto done;
+                       }
+
+                       s += n;
+                       x -= n;
+                       for (i = 0; i < 16; i++) {
+                               n = snprintf(&ptr_name[s], x, "%2.2x", sin6->sin6_addr.s6_addr[i]);
+                               if ((n == -1) || (n >= x)) {
+                                       goto done;
+                               }
+
+                               s += n;
+                               x -= n;
+                       }
+
+                       n = snprintf(&ptr_name[s], x, "].ip6.arpa.");
+                       if ((n == -1) || (n >= x)) {
+                               goto done;
+                       }
+#endif /* USE_NIBBLE_QUERY */
+
+                       break;
+               }
+
+               default :
+                       goto done;
+       }
+
+       ok = _SC_checkResolverReachability(&store, &flags, &haveDNS, ptr_name);
+       if (ok) {
+               if (!(flags & kSCNetworkFlagsReachable) ||
+                       (flags & kSCNetworkFlagsConnectionRequired)) {
+                       // if not reachable *OR* connection required
+                       ok = FALSE;
+               }
+       }
+
+    done :
+
+       return ok;
+}
+
+
+#define        HOSTNAME_NOTIFY_KEY     "com.apple.system.hostname"
+
+
+static void
+set_hostname(CFStringRef hostname)
+{
+       if (hostname != NULL) {
+               char    old_name[MAXHOSTNAMELEN];
+               char    new_name[MAXHOSTNAMELEN];
+
+               if (gethostname(old_name, sizeof(old_name)) == -1) {
+                       SCLog(TRUE, LOG_ERR, CFSTR("gethostname() failed: %s"), strerror(errno));
+                       old_name[0] = '\0';
+               }
+
+               if (_SC_cfstring_to_cstring(hostname,
+                                           new_name,
+                                           sizeof(new_name),
+                                           kCFStringEncodingUTF8) == NULL) {
+                       SCLog(TRUE, LOG_ERR, CFSTR("could not convert [new] hostname"));
+                       new_name[0] = '\0';
+               }
+
+               old_name[sizeof(old_name)-1] = '\0';
+               new_name[sizeof(new_name)-1] = '\0';
+               if (strcmp(old_name, new_name) != 0) {
+                       if (sethostname(new_name, strlen(new_name)) == 0) {
+                               uint32_t        status;
+
+                               SCLog(TRUE, LOG_NOTICE,
+                                     CFSTR("setting hostname to \"%s\""),
+                                     new_name);
+
+                               status = notify_post(HOSTNAME_NOTIFY_KEY);
+                               if (status != NOTIFY_STATUS_OK) {
+                                       SCLog(TRUE, LOG_ERR,
+                                             CFSTR("notify_post(" HOSTNAME_NOTIFY_KEY ") failed: error=%lu"),
+                                             status);
+                               }
+                       } else {
+                               SCLog(TRUE, LOG_ERR,
+                                     CFSTR("sethostname(%s, %d) failed: %s"),
+                                     new_name,
+                                     strlen(new_name),
+                                     strerror(errno));
+                       }
+               }
+       }
+
+       return;
+}
+
+
+#define        HOSTCONFIG      "/etc/hostconfig"
+#define HOSTNAME_KEY   "HOSTNAME="
+#define        AUTOMATIC       "-AUTOMATIC-"
+
+#define HOSTNAME_KEY_LEN       (sizeof(HOSTNAME_KEY) - 1)
+
+static CFStringRef
+copy_static_name()
+{
+       FILE *          f;
+       char            buf[256];
+       CFStringRef     name    = NULL;
+
+       f = fopen(HOSTCONFIG, "r");
+       if (f == NULL) {
+               return NULL;
+       }
+
+       while (fgets(buf, sizeof(buf), f) != NULL) {
+               char *  bp;
+               int     n;
+               char *  np;
+               Boolean str_escape;
+               Boolean str_quote;
+
+               n = strlen(buf);
+               if (buf[n-1] == '\n') {
+                       /* the entire line fit in the buffer, remove the newline */
+                       buf[n-1] = '\0';
+               } else {
+                       /* eat the remainder of the line */
+                       do {
+                               n = fgetc(f);
+                       } while ((n != '\n') && (n != EOF));
+               }
+
+               // skip leading white space
+               bp = &buf[0];
+               while (isspace(*bp)) {
+                       bp++;
+               }
+
+               // find "HOSTNAME=" key
+               if (strncmp(bp, HOSTNAME_KEY, HOSTNAME_KEY_LEN) != 0) {
+                       continue;       // if not
+               }
+
+               // get the hostname string
+               bp += HOSTNAME_KEY_LEN;
+               str_escape = FALSE;
+               str_quote  = FALSE;
+
+               np = &buf[0];
+               while (*bp != '\0') {
+                       char    ch = *bp;
+
+                       switch (ch) {
+                               case '\\' :
+                                       if (!str_escape) {
+                                               str_escape = TRUE;
+                                               bp++;
+                                               continue;
+                                       }
+                                       break;
+                               case '"' :
+                                       if (!str_escape) {
+                                               str_quote = !str_quote;
+                                               bp++;
+                                               continue;
+                                       }
+                                       break;
+                               default :
+                                       break;
+                       }
+
+                       if (str_escape) {
+                               str_escape = FALSE;
+                       } else if (!str_quote && (isspace(ch) || (ch == '#'))) {
+                               break;
+                       }
+
+                       *np++ = ch;
+                       bp++;
+               }
+
+               *np = '\0';
+
+               if (name != NULL) {
+                       CFRelease(name);
+                       name = NULL;
+               }
+
+               if (str_quote) {
+                       // the shell won't parse this file so neither will we
+                       break;
+               }
+
+               if (strcmp(buf, AUTOMATIC) == 0) {
+                       // skip "-AUTOMATIC-"
+                       continue;
+               }
+
+               name = CFStringCreateWithCString(NULL, buf, kCFStringEncodingUTF8);
+       }
+
+       (void) fclose(f);
+       return name;
+}
+
+
+#ifndef        kSCPropNetHostName
+#define        kSCPropNetHostName      CFSTR("HostName")
+#endif
+
+
+static CFStringRef
+copy_prefs_hostname(SCDynamicStoreRef store)
+{
+       CFDictionaryRef         dict;
+       CFStringRef             key;
+       CFStringRef             name            = NULL;
+
+       key  = SCDynamicStoreKeyCreateComputerName(NULL);
+       dict = SCDynamicStoreCopyValue(store, key);
+       CFRelease(key);
+       if (dict == NULL) {
+               goto done;
+       }
+       if (!isA_CFDictionary(dict)) {
+               goto done;
+       }
+
+       name = isA_CFString(CFDictionaryGetValue(dict, kSCPropNetHostName));
+       if (name == NULL) {
+               goto done;
+       }
+       CFRetain(name);
+
+    done :
+
+       if (dict != NULL)       CFRelease(dict);
+       
+       return name;
+}
+
+
+static CFStringRef
+copy_primary_service(SCDynamicStoreRef store)
+{
+       CFDictionaryRef dict;
+       CFStringRef     key;
+       CFStringRef     serviceID       = NULL;
+
+       key = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL,
+                                                        kSCDynamicStoreDomainState,
+                                                        kSCEntNetIPv4);
+       dict = SCDynamicStoreCopyValue(store, key);
+       CFRelease(key);
+
+       if (dict != NULL) {
+               if (isA_CFDictionary(dict)) {
+                       serviceID = CFDictionaryGetValue(dict, kSCDynamicStorePropNetPrimaryService);
+                       if (isA_CFString(serviceID)) {
+                               CFRetain(serviceID);
+                       } else {
+                               serviceID = NULL;
+                       }
+               }
+               CFRelease(dict);
+       }
+
+       return serviceID;
+}
+
+
+static CFStringRef
+copy_primary_ip(SCDynamicStoreRef store, CFStringRef serviceID)
+{
+       CFDictionaryRef dict;
+       CFStringRef     key;
+       CFStringRef     address = NULL;
+
+       key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
+                                                         kSCDynamicStoreDomainState,
+                                                         serviceID,
+                                                         kSCEntNetIPv4);
+       dict = SCDynamicStoreCopyValue(store, key);
+       CFRelease(key);
+
+       if (dict != NULL) {
+               if (isA_CFDictionary(dict)) {
+                       CFArrayRef      addresses;
+
+                       addresses = CFDictionaryGetValue(dict, kSCPropNetIPv4Addresses);
+                       if (isA_CFArray(addresses) && (CFArrayGetCount(addresses) > 0)) {
+                               address = CFArrayGetValueAtIndex(addresses, 0);
+                               if (isA_CFString(address)) {
+                                       CFRetain(address);
+                               } else {
+                                       address = NULL;
+                               }
+                       }
+               }
+               CFRelease(dict);
+       }
+
+       return address;
+}
+
+
+#define        DHCP_OPTION_HOSTNAME    12
+
+static CFStringRef
+copy_dhcp_name(SCDynamicStoreRef store, CFStringRef serviceID)
+{
+       CFDictionaryRef info;
+       CFStringRef     name    = NULL;
+
+       info = SCDynamicStoreCopyDHCPInfo(store, serviceID);
+       if (info != NULL) {
+               CFDataRef       data;
+
+               data = DHCPInfoGetOptionData(info, DHCP_OPTION_HOSTNAME);
+               if (data != NULL) {
+                       name = CFStringCreateFromExternalRepresentation(NULL, data, kCFStringEncodingUTF8);
+               }
+
+               CFRelease(info);
+       }
+
+       return name;
+}
+
+
+static void
+reverseDNSComplete(int32_t status, char *host, char *serv, void *context)
+{
+       struct timeval          dnsQueryComplete;
+       struct timeval          dnsQueryElapsed;
+       CFStringRef             hostname;
+       SCDynamicStoreRef       store   = (SCDynamicStoreRef)context;
+
+       (void) gettimeofday(&dnsQueryComplete, NULL);
+       timersub(&dnsQueryComplete, &dnsQueryStart, &dnsQueryElapsed);
+       SCLog(_verbose, LOG_INFO,
+             CFSTR("async DNS complete%s (query time = %d.%3.3d)"),
+             ((status == 0) && (host != NULL)) ? "" : ", host not found",
+             dnsQueryElapsed.tv_sec,
+             dnsQueryElapsed.tv_usec / 1000);
+
+       // use reverse DNS name, if available
+
+       switch (status) {
+               case 0 :
+                       /*
+                        * if [reverse] DNS query was successful
+                        */
+                       if (host != NULL) {
+                               hostname = CFStringCreateWithCString(NULL, host, kCFStringEncodingUTF8);
+                               SCLog(TRUE, LOG_INFO, CFSTR("hostname (reverse DNS query) = %@"), hostname);
+                               set_hostname(hostname);
+                               CFRelease(hostname);
+                               goto done;
+                       }
+                       break;
+
+               case EAI_NONAME :
+                       /*
+                        * if no name available
+                        */
+                       break;
+
+               default :
+                       /*
+                        * Hmmmm...
+                        */
+                       SCLog(TRUE, LOG_ERR, CFSTR("getnameinfo() failed: %s"), gai_strerror(status));
+       }
+
+       // get local (multicast DNS) name, if available
+
+       hostname = SCDynamicStoreCopyLocalHostName(store);
+       if (hostname != NULL) {
+               CFMutableStringRef      localName;
+
+               SCLog(TRUE, LOG_INFO, CFSTR("hostname (multicast DNS) = %@"), hostname);
+               localName = CFStringCreateMutableCopy(NULL, 0, hostname);
+               CFStringAppend(localName, CFSTR(".local"));
+               set_hostname(localName);
+               CFRelease(localName);
+               CFRelease(hostname);
+               goto done;
+       }
+
+       // use "localhost" if not other name is available
+
+       set_hostname(CFSTR("localhost"));
+
+    done :
+
+       if (host != NULL)       free(host);
+       if (serv != NULL)       free(serv);
+       return;
+}
+
+
+static void
+getnameinfo_async_handleCFReply(CFMachPortRef port, void *msg, CFIndex size, void *info)
+{
+       getnameinfo_async_handle_reply(msg);
+
+       if (port == dnsPort) {
+               CFRelease(dnsRLS);
+               dnsRLS = NULL;
+               CFRelease(dnsPort);
+               dnsPort = NULL;
+       }
+
+       return;
+}
+
+
+static void
+start_dns_query(SCDynamicStoreRef store, CFStringRef address)
+{
+       char                    addr[64];
+       Boolean                 ok;
+       struct sockaddr         *sa;
+       struct sockaddr_in      sin;
+       struct sockaddr_in6     sin6;
+
+       if (_SC_cfstring_to_cstring(address, addr, sizeof(addr), kCFStringEncodingASCII) == NULL) {
+               SCLog(TRUE, LOG_ERR, CFSTR("could not convert [primary] address"));
+               return;
+       }
+
+       bzero(&sin, sizeof(sin));
+       sin.sin_len    = sizeof(sin);
+       sin.sin_family = AF_INET;
+
+       bzero(&sin6, sizeof(sin6));
+       sin6.sin6_len    = sizeof(sin6);
+       sin6.sin6_family = AF_INET6;
+
+       if (inet_aton(addr, &sin.sin_addr) == 1) {
+               /*
+                * if IPv4 address
+                */
+               sa = (struct sockaddr *)&sin;
+       } else if (inet_pton(AF_INET6, addr, &sin6.sin6_addr) == 1) {
+               /*
+                * if IPv6 address
+                */
+               char    *p;
+
+               p = strchr(addr, '%');
+               if (p != NULL) {
+                       sin6.sin6_scope_id = if_nametoindex(p+1);
+               }
+
+               sa = (struct sockaddr *)&sin6;
+       } else {
+               goto done;
+       }
+
+       ok = checkResolverReachabilityByAddress(store, sa);
+       if (ok) {
+               CFMachPortContext       context = { 0, (void *)store, CFRetain, CFRelease, CFCopyDescription };
+               mach_port_t             port;
+               int32_t                 error;
+
+               if ((dnsPort != NULL) && (dnsRLS != NULL)) {
+                       /* if we already have an active async DNS query */
+                       lu_async_call_cancel(CFMachPortGetPort(dnsPort));
+                       CFRelease(dnsRLS);
+                       dnsRLS = NULL;
+                       CFRelease(dnsPort);
+                       dnsPort = NULL;
+               }
+
+               (void) gettimeofday(&dnsQueryStart, NULL);
+
+               error = getnameinfo_async_start(&port,
+                                               sa,
+                                               sa->sa_len,
+                                               0,              // flags
+                                               reverseDNSComplete,
+                                               NULL);
+               if (error != 0) {
+                       goto done;
+               }
+
+               dnsPort = CFMachPortCreateWithPort(NULL,
+                                                  port,
+                                                  getnameinfo_async_handleCFReply,
+                                                  &context,
+                                                  NULL);
+               dnsRLS = CFMachPortCreateRunLoopSource(NULL, dnsPort, 0);
+               CFRunLoopAddSource(CFRunLoopGetCurrent(), dnsRLS, kCFRunLoopDefaultMode);
+       }
+
+    done :
+
+       return;
+}
+
+
+static void
+update_hostname(SCDynamicStoreRef store, CFArrayRef changedKeys, void *info)
+{
+       CFStringRef     address         = NULL;
+       CFStringRef     hostname        = NULL;
+       CFStringRef     serviceID       = NULL;
+
+       // get static hostname, if available
+
+       hostname = copy_static_name();
+       if (hostname != NULL) {
+               SCLog(TRUE, LOG_INFO, CFSTR("hostname (static) = %@"), hostname);
+               set_hostname(hostname);
+               goto done;
+       }
+
+       // get [prefs] hostname, if available
+
+       hostname = copy_prefs_hostname(store);
+       if (hostname != NULL) {
+               SCLog(TRUE, LOG_INFO, CFSTR("hostname (prefs) = %@"), hostname);
+               set_hostname(hostname);
+               goto done;
+       }
+
+       // get primary service ID
+
+       serviceID = copy_primary_service(store);
+       if (serviceID == NULL) {
+               goto mDNS;
+       }
+
+       // get DHCP provided name, if available
+
+       hostname = copy_dhcp_name(store, serviceID);
+       if (hostname != NULL) {
+               SCLog(TRUE, LOG_INFO, CFSTR("hostname (DHCP) = %@"), hostname);
+               set_hostname(hostname);
+               goto done;
+       }
+
+       // get DNS name associated with primary IP, if available
+
+       address = copy_primary_ip(store, serviceID);
+       if (address != NULL) {
+               // start reverse DNS query using primary IP address
+               (void) start_dns_query(store, address);
+               goto done;
+       }
+
+    mDNS :
+
+       // get local (multicast DNS) name, if available
+
+       hostname = SCDynamicStoreCopyLocalHostName(store);
+       if (hostname != NULL) {
+               CFMutableStringRef      localName;
+
+               SCLog(TRUE, LOG_INFO, CFSTR("hostname (multicast DNS) = %@"), hostname);
+               localName = CFStringCreateMutableCopy(NULL, 0, hostname);
+               CFStringAppend(localName, CFSTR(".local"));
+               set_hostname(localName);
+               CFRelease(localName);
+               goto done;
+       }
+
+       // use "localhost" if not other name is available
+
+       set_hostname(CFSTR("localhost"));
+
+    done :
+
+       if (address)    CFRelease(address);
+       if (hostname)   CFRelease(hostname);
+       if (serviceID)  CFRelease(serviceID);
+
+       return;
+}
+
+
+__private_extern__
+void
+load_hostname(Boolean verbose)
+{
+       CFStringRef             key;
+       CFMutableArrayRef       keys            = NULL;
+       CFMutableArrayRef       patterns        = NULL;
+
+       if (verbose) {
+               _verbose = TRUE;
+       }
+
+       /* initialize a few globals */
+
+       store = SCDynamicStoreCreate(NULL, CFSTR("set-hostname"), update_hostname, NULL);
+       if (store == NULL) {
+               SCLog(TRUE, LOG_ERR,
+                     CFSTR("SCDynamicStoreCreate() failed: %s"),
+                     SCErrorString(SCError()));
+               goto error;
+       }
+
+       /* establish notification keys and patterns */
+
+       keys     = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+       patterns = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+
+       /* ...watch for primary service / interface changes */
+       key = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL,
+                                                        kSCDynamicStoreDomainState,
+                                                        kSCEntNetIPv4);
+       CFArrayAppendValue(keys, key);
+       CFRelease(key);
+
+       /* ...watch for DNS configuration changes */
+       key = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL,
+                                                        kSCDynamicStoreDomainState,
+                                                        kSCEntNetDNS);
+       CFArrayAppendValue(keys, key);
+       CFRelease(key);
+
+       /* ...watch for (per-service) DHCP option changes */
+       key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
+                                                         kSCDynamicStoreDomainState,
+                                                         kSCCompAnyRegex,
+                                                         kSCEntNetDHCP);
+       CFArrayAppendValue(patterns, key);
+       CFRelease(key);
+
+       /* ...watch for (BSD) hostname changes */
+       key = SCDynamicStoreKeyCreateComputerName(NULL);
+       CFArrayAppendValue(keys, key);
+       CFRelease(key);
+
+       /* ...watch for local (multicast DNS) hostname changes */
+       key = SCDynamicStoreKeyCreateHostNames(NULL);
+       CFArrayAppendValue(keys, key);
+       CFRelease(key);
+
+       /* register the keys/patterns */
+       if (!SCDynamicStoreSetNotificationKeys(store, keys, patterns)) {
+               SCLog(TRUE, LOG_ERR,
+                     CFSTR("SCDynamicStoreSetNotificationKeys() failed: %s"),
+                     SCErrorString(SCError()));
+               goto error;
+       }
+
+       rls = SCDynamicStoreCreateRunLoopSource(NULL, store, 0);
+       if (!rls) {
+               SCLog(TRUE, LOG_ERR,
+                     CFSTR("SCDynamicStoreCreateRunLoopSource() failed: %s"),
+                     SCErrorString(SCError()));
+               goto error;
+       }
+       CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
+
+       CFRelease(keys);
+       CFRelease(patterns);
+       return;
+
+    error :
+
+       if (keys != NULL)       CFRelease(keys);
+       if (patterns != NULL)   CFRelease(patterns);
+       if (store != NULL)      CFRelease(store);
+       return;
+}
+
+
+#ifdef MAIN
+int
+main(int argc, char **argv)
+{
+
+#ifdef DEBUG
+
+       _sc_log = FALSE;
+       if ((argc > 1) && (strcmp(argv[1], "-d") == 0)) {
+               _sc_verbose = TRUE;
+               argv++;
+               argc--;
+       }
+
+       CFStringRef             address;
+       CFStringRef             hostname;
+       CFStringRef             serviceID;
+       SCDynamicStoreRef       store;
+
+       store = SCDynamicStoreCreate(NULL, CFSTR("set-hostname"), NULL, NULL);
+       if (store == NULL) {
+               SCPrint(TRUE, stdout,
+                       CFSTR("SCDynamicStoreCreate() failed: %s\n"),
+                       SCErrorString(SCError()));
+               exit(1);
+       }
+
+       // get static hostname
+       hostname = copy_static_name();
+       if (hostname != NULL) {
+               SCPrint(TRUE, stdout, CFSTR("hostname (static) = %@\n"), hostname);
+               CFRelease(hostname);
+       }
+
+       // get [prefs] hostname, if available
+       hostname = copy_prefs_hostname(store);
+       if (hostname != NULL) {
+               SCPrint(TRUE, stdout, CFSTR("hostname (prefs) = %@\n"), hostname);
+               CFRelease(hostname);
+       }
+
+       // get primary service
+       serviceID = copy_primary_service(store);
+       if (serviceID != NULL) {
+               SCPrint(TRUE, stdout, CFSTR("primary service ID = %@\n"), serviceID);
+       } else {
+               SCPrint(TRUE, stdout, CFSTR("No primary service\n"));
+               goto mDNS;
+       }
+
+       if ((argc == (2+1)) && (argv[1][0] == 's')) {
+               if (serviceID != NULL)  CFRelease(serviceID);
+               serviceID = CFStringCreateWithCString(NULL, argv[2], kCFStringEncodingUTF8);
+               SCPrint(TRUE, stdout, CFSTR("alternate service ID = %@\n"), serviceID);
+       }
+
+       // get DHCP provided name
+       hostname = copy_dhcp_name(store, serviceID);
+       if (hostname != NULL) {
+               SCPrint(TRUE, stdout, CFSTR("hostname (DHCP) = %@\n"), hostname);
+               CFRelease(hostname);
+       }
+
+       // get primary IP address
+       address = copy_primary_ip(store, serviceID);
+       if (address != NULL) {
+               SCPrint(TRUE, stdout, CFSTR("primary address = %@\n"), address);
+
+               if ((argc == (2+1)) && (argv[1][0] == 'a')) {
+                       if (address != NULL)    CFRelease(address);
+                       address = CFStringCreateWithCString(NULL, argv[2], kCFStringEncodingUTF8);
+                       SCPrint(TRUE, stdout, CFSTR("alternate primary address = %@\n"), address);
+               }
+
+               // start reverse DNS query using primary IP address
+               start_dns_query(store, address);
+               CFRelease(address);
+       }
+
+       CFRelease(serviceID);
+
+    mDNS :
+
+       // get local (multicast DNS) name, if available
+
+       hostname = SCDynamicStoreCopyLocalHostName(store);
+       if (hostname != NULL) {
+               CFMutableStringRef      localName;
+
+               SCPrint(TRUE, stdout, CFSTR("hostname (multicast DNS) = %@\n"), hostname);
+               localName = CFStringCreateMutableCopy(NULL, 0, hostname);
+               CFStringAppend(localName, CFSTR(".local"));
+               CFRelease(localName);
+       }
+
+       if (hostname != NULL)   CFRelease(hostname);
+
+       update_hostname(store, NULL, NULL);
+
+       CFRelease(store);
+
+       CFRunLoopRun();
+
+#else  /* DEBUG */
+
+       _sc_log     = FALSE;
+       _sc_verbose = (argc > 1) ? TRUE : FALSE;
+
+       load_hostname((argc > 1) ? TRUE : FALSE);
+       CFRunLoopRun();
+       /* not reached */
+
+#endif /* DEBUG */
+
+       exit(0);
+       return 0;
+}
+#endif /* MAIN */