/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // for SCLog(), SCPrint() #include static SCDynamicStoreRef store = NULL; static CFRunLoopSourceRef rls = NULL; static Boolean dnsActive = FALSE; 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); dnsActive = FALSE; return; } static void getnameinfo_async_handleCFReply(CFMachPortRef port, void *msg, CFIndex size, void *info) { int32_t status; status = getnameinfo_async_handle_reply(msg); if ((status == 0) && dnsActive) { // if request has been re-queued return; } if (port == dnsPort) { CFRunLoopSourceInvalidate(dnsRLS); 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; (void) gettimeofday(&dnsQueryStart, NULL); error = getnameinfo_async_start(&port, sa, sa->sa_len, 0, // flags reverseDNSComplete, NULL); if (error != 0) { goto done; } dnsActive = TRUE; 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; // if active, cancel any in-progress attempt to resolve the primary IP address if (dnsPort != NULL) { /* cancel the outstanding DNS query */ lu_async_call_cancel(CFMachPortGetPort(dnsPort)); CFRunLoopSourceInvalidate(dnsRLS); CFRelease(dnsRLS); dnsRLS = NULL; CFRelease(dnsPort); dnsPort = 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 */