#include <ctype.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/param.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 Boolean dnsActive = FALSE;
static CFMachPortRef dnsPort = NULL;
static CFRunLoopSourceRef dnsRLS = NULL;
static struct timeval dnsQueryStart;
static Boolean _verbose = FALSE;
Boolean
_SC_checkResolverReachability(SCDynamicStoreRef *storeP,
SCNetworkConnectionFlags *flags,
Boolean *haveDNS,
const char * nodename);
static Boolean
checkResolverReachabilityByAddress(SCDynamicStoreRef store, struct sockaddr *sa)
{
SCNetworkConnectionFlags flags;
Boolean haveDNS;
int i;
Boolean ok = FALSE;
char ptr_name[128];
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;
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
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
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
break;
}
default :
goto done;
}
ok = _SC_checkResolverReachability(&store, &flags, &haveDNS, ptr_name);
if (ok) {
if (!(flags & kSCNetworkFlagsReachable) ||
(flags & kSCNetworkFlagsConnectionRequired)) {
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') {
buf[n-1] = '\0';
} else {
do {
n = fgetc(f);
} while ((n != '\n') && (n != EOF));
}
bp = &buf[0];
while (isspace(*bp)) {
bp++;
}
if (strncmp(bp, HOSTNAME_KEY, HOSTNAME_KEY_LEN) != 0) {
continue; }
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) {
break;
}
if (strcmp(buf, AUTOMATIC) == 0) {
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);
switch (status) {
case 0 :
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 :
break;
default :
SCLog(TRUE, LOG_ERR, CFSTR("getnameinfo() failed: %s"), gai_strerror(status));
}
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;
}
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) {
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) {
sa = (struct sockaddr *)&sin;
} else if (inet_pton(AF_INET6, addr, &sin6.sin6_addr) == 1) {
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, 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 (dnsPort != NULL) {
lu_async_call_cancel(CFMachPortGetPort(dnsPort));
CFRunLoopSourceInvalidate(dnsRLS);
CFRelease(dnsRLS);
dnsRLS = NULL;
CFRelease(dnsPort);
dnsPort = NULL;
}
hostname = copy_static_name();
if (hostname != NULL) {
SCLog(TRUE, LOG_INFO, CFSTR("hostname (static) = %@"), hostname);
set_hostname(hostname);
goto done;
}
hostname = copy_prefs_hostname(store);
if (hostname != NULL) {
SCLog(TRUE, LOG_INFO, CFSTR("hostname (prefs) = %@"), hostname);
set_hostname(hostname);
goto done;
}
serviceID = copy_primary_service(store);
if (serviceID == NULL) {
goto mDNS;
}
hostname = copy_dhcp_name(store, serviceID);
if (hostname != NULL) {
SCLog(TRUE, LOG_INFO, CFSTR("hostname (DHCP) = %@"), hostname);
set_hostname(hostname);
goto done;
}
address = copy_primary_ip(store, serviceID);
if (address != NULL) {
(void) start_dns_query(store, address);
goto done;
}
mDNS :
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;
}
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;
}
store = SCDynamicStoreCreate(NULL, CFSTR("set-hostname"), update_hostname, NULL);
if (store == NULL) {
SCLog(TRUE, LOG_ERR,
CFSTR("SCDynamicStoreCreate() failed: %s"),
SCErrorString(SCError()));
goto error;
}
keys = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
patterns = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
key = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL,
kSCDynamicStoreDomainState,
kSCEntNetIPv4);
CFArrayAppendValue(keys, key);
CFRelease(key);
key = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL,
kSCDynamicStoreDomainState,
kSCEntNetDNS);
CFArrayAppendValue(keys, key);
CFRelease(key);
key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
kSCDynamicStoreDomainState,
kSCCompAnyRegex,
kSCEntNetDHCP);
CFArrayAppendValue(patterns, key);
CFRelease(key);
key = SCDynamicStoreKeyCreateComputerName(NULL);
CFArrayAppendValue(keys, key);
CFRelease(key);
key = SCDynamicStoreKeyCreateHostNames(NULL);
CFArrayAppendValue(keys, key);
CFRelease(key);
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);
}
hostname = copy_static_name();
if (hostname != NULL) {
SCPrint(TRUE, stdout, CFSTR("hostname (static) = %@\n"), hostname);
CFRelease(hostname);
}
hostname = copy_prefs_hostname(store);
if (hostname != NULL) {
SCPrint(TRUE, stdout, CFSTR("hostname (prefs) = %@\n"), hostname);
CFRelease(hostname);
}
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);
}
hostname = copy_dhcp_name(store, serviceID);
if (hostname != NULL) {
SCPrint(TRUE, stdout, CFSTR("hostname (DHCP) = %@\n"), hostname);
CFRelease(hostname);
}
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_dns_query(store, address);
CFRelease(address);
}
CFRelease(serviceID);
mDNS :
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
_sc_log = FALSE;
_sc_verbose = (argc > 1) ? TRUE : FALSE;
load_hostname((argc > 1) ? TRUE : FALSE);
CFRunLoopRun();
#endif
exit(0);
return 0;
}
#endif