2 * Copyright (c) 2004-2012 Apple Inc. All rights reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
26 #include <sys/param.h>
27 #include <sys/types.h>
28 #include <sys/socket.h>
31 #include <net/if_dl.h>
32 #include <netinet/in.h>
33 #include <arpa/inet.h>
34 #include <netdb_async.h>
36 #include <CoreFoundation/CoreFoundation.h>
37 #include <SystemConfiguration/SystemConfiguration.h>
38 #include <SystemConfiguration/SCDynamicStoreCopyDHCPInfo.h>
39 #include <SystemConfiguration/SCValidation.h>
40 #include <SystemConfiguration/SCPrivate.h>
43 #include "ip_plugin.h"
45 static SCDynamicStoreRef store
= NULL
;
46 static CFRunLoopSourceRef rls
= NULL
;
48 static Boolean dnsActive
= FALSE
;
49 static CFMachPortRef dnsPort
= NULL
;
50 static struct timeval dnsQueryStart
;
52 static Boolean _verbose
= FALSE
;
55 #define HOSTNAME_NOTIFY_KEY "com.apple.system.hostname"
57 CFStringRef
copy_dhcp_hostname(CFStringRef serviceID
);
60 set_hostname(CFStringRef hostname
)
62 if (hostname
!= NULL
) {
63 char old_name
[MAXHOSTNAMELEN
];
64 char new_name
[MAXHOSTNAMELEN
];
66 if (gethostname(old_name
, sizeof(old_name
)) == -1) {
67 my_log(LOG_ERR
, "gethostname() failed: %s", strerror(errno
));
71 if (_SC_cfstring_to_cstring(hostname
,
74 kCFStringEncodingUTF8
) == NULL
) {
75 my_log(LOG_ERR
, "could not convert [new] hostname");
79 old_name
[sizeof(old_name
)-1] = '\0';
80 new_name
[sizeof(new_name
)-1] = '\0';
81 if (strcmp(old_name
, new_name
) != 0) {
82 if (sethostname(new_name
, strlen(new_name
)) == 0) {
86 "setting hostname to \"%s\"",
89 status
= notify_post(HOSTNAME_NOTIFY_KEY
);
90 if (status
!= NOTIFY_STATUS_OK
) {
92 "notify_post(" HOSTNAME_NOTIFY_KEY
") failed: error=%lu",
97 "sethostname(%s, %d) failed: %s",
109 #define HOSTCONFIG "/etc/hostconfig"
110 #define HOSTNAME_KEY "HOSTNAME="
111 #define AUTOMATIC "-AUTOMATIC-"
113 #define HOSTNAME_KEY_LEN (sizeof(HOSTNAME_KEY) - 1)
120 CFStringRef name
= NULL
;
122 f
= fopen(HOSTCONFIG
, "r");
127 while (fgets(buf
, sizeof(buf
), f
) != NULL
) {
135 if (buf
[n
-1] == '\n') {
136 /* the entire line fit in the buffer, remove the newline */
139 /* eat the remainder of the line */
142 } while ((n
!= '\n') && (n
!= EOF
));
145 // skip leading white space
147 while (isspace(*bp
)) {
151 // find "HOSTNAME=" key
152 if (strncmp(bp
, HOSTNAME_KEY
, HOSTNAME_KEY_LEN
) != 0) {
156 // get the hostname string
157 bp
+= HOSTNAME_KEY_LEN
;
162 while (*bp
!= '\0') {
175 str_quote
= !str_quote
;
186 } else if (!str_quote
&& (isspace(ch
) || (ch
== '#'))) {
202 // the shell won't parse this file so neither will we
206 if (strcmp(buf
, AUTOMATIC
) == 0) {
207 // skip "-AUTOMATIC-"
211 name
= CFStringCreateWithCString(NULL
, buf
, kCFStringEncodingUTF8
);
220 copy_prefs_hostname(SCDynamicStoreRef store
)
222 CFDictionaryRef dict
;
224 CFStringRef name
= NULL
;
226 key
= SCDynamicStoreKeyCreateComputerName(NULL
);
227 dict
= SCDynamicStoreCopyValue(store
, key
);
232 if (!isA_CFDictionary(dict
)) {
236 name
= isA_CFString(CFDictionaryGetValue(dict
, kSCPropSystemHostName
));
244 if (dict
!= NULL
) CFRelease(dict
);
251 copy_primary_service(SCDynamicStoreRef store
)
253 CFDictionaryRef dict
;
255 CFStringRef serviceID
= NULL
;
257 key
= SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL
,
258 kSCDynamicStoreDomainState
,
260 dict
= SCDynamicStoreCopyValue(store
, key
);
264 if (isA_CFDictionary(dict
)) {
265 serviceID
= CFDictionaryGetValue(dict
, kSCDynamicStorePropNetPrimaryService
);
266 if (isA_CFString(serviceID
)) {
280 copy_primary_ip(SCDynamicStoreRef store
, CFStringRef serviceID
)
282 CFDictionaryRef dict
;
284 CFStringRef address
= NULL
;
286 key
= SCDynamicStoreKeyCreateNetworkServiceEntity(NULL
,
287 kSCDynamicStoreDomainState
,
290 dict
= SCDynamicStoreCopyValue(store
, key
);
294 if (isA_CFDictionary(dict
)) {
295 CFArrayRef addresses
;
297 addresses
= CFDictionaryGetValue(dict
, kSCPropNetIPv4Addresses
);
298 if (isA_CFArray(addresses
) && (CFArrayGetCount(addresses
) > 0)) {
299 address
= CFArrayGetValueAtIndex(addresses
, 0);
300 if (isA_CFString(address
)) {
314 reverseDNSComplete(int32_t status
, char *host
, char *serv
, void *context
)
316 struct timeval dnsQueryComplete
;
317 struct timeval dnsQueryElapsed
;
318 CFStringRef hostname
;
319 SCDynamicStoreRef store
= (SCDynamicStoreRef
)context
;
321 (void) gettimeofday(&dnsQueryComplete
, NULL
);
322 timersub(&dnsQueryComplete
, &dnsQueryStart
, &dnsQueryElapsed
);
324 my_log(LOG_INFO
, "async DNS complete%s (query time = %d.%3.3d)",
325 ((status
== 0) && (host
!= NULL
)) ? "" : ", host not found",
326 dnsQueryElapsed
.tv_sec
,
327 dnsQueryElapsed
.tv_usec
/ 1000);
330 // use reverse DNS name, if available
335 * if [reverse] DNS query was successful
338 hostname
= CFStringCreateWithCString(NULL
, host
, kCFStringEncodingUTF8
);
339 if (hostname
!= NULL
) {
340 my_log(LOG_INFO
, "hostname (reverse DNS query) = %@", hostname
);
341 set_hostname(hostname
);
349 #if defined(EAI_NODATA) && (EAI_NODATA != EAI_NONAME)
353 * if no name available
361 my_log(LOG_ERR
, "getnameinfo() failed: %s", gai_strerror(status
));
364 // get local (multicast DNS) name, if available
366 hostname
= SCDynamicStoreCopyLocalHostName(store
);
367 if (hostname
!= NULL
) {
368 CFMutableStringRef localName
;
370 my_log(LOG_INFO
, "hostname (multicast DNS) = %@", hostname
);
371 localName
= CFStringCreateMutableCopy(NULL
, 0, hostname
);
372 assert(localName
!= NULL
);
373 CFStringAppend(localName
, CFSTR(".local"));
374 set_hostname(localName
);
375 CFRelease(localName
);
380 // use "localhost" if not other name is available
382 set_hostname(CFSTR("localhost"));
386 if (host
!= NULL
) free(host
);
387 if (serv
!= NULL
) free(serv
);
394 replyMPCopyDescription(const void *info
)
396 SCDynamicStoreRef store
= (SCDynamicStoreRef
)info
;
398 return CFStringCreateWithFormat(NULL
,
400 CFSTR("<getnameinfo_async_start reply MP> {store = %p}"),
406 getnameinfo_async_handleCFReply(CFMachPortRef port
, void *msg
, CFIndex size
, void *info
)
408 mach_port_t mp
= MACH_PORT_NULL
;
411 if (port
!= dnsPort
) {
412 // we've received a callback on the async DNS port but since the
413 // associated CFMachPort doesn't match than the request must have
414 // already been cancelled.
415 my_log(LOG_ERR
, "getnameinfo_async_handleCFReply(): port != dnsPort");
419 mp
= CFMachPortGetPort(port
);
420 CFMachPortInvalidate(dnsPort
);
423 __MACH_PORT_DEBUG(mp
!= MACH_PORT_NULL
, "*** set-hostname (after unscheduling)", mp
);
425 status
= getnameinfo_async_handle_reply(msg
);
426 __MACH_PORT_DEBUG(mp
!= MACH_PORT_NULL
, "*** set-hostname (after getnameinfo_async_handle_reply)", mp
);
427 if ((status
== 0) && dnsActive
&& (mp
!= MACH_PORT_NULL
)) {
428 CFMachPortContext context
= { 0
432 , replyMPCopyDescription
434 CFRunLoopSourceRef rls
;
436 // if request has been re-queued
437 dnsPort
= _SC_CFMachPortCreateWithPort("IPMonitor/set-hostname/re-queue",
439 getnameinfo_async_handleCFReply
,
441 rls
= CFMachPortCreateRunLoopSource(NULL
, dnsPort
, 0);
442 CFRunLoopAddSource(CFRunLoopGetCurrent(), rls
, kCFRunLoopDefaultMode
);
444 __MACH_PORT_DEBUG(mp
!= MACH_PORT_NULL
, "*** set-hostname (after rescheduling)", mp
);
452 start_dns_query(SCDynamicStoreRef store
, CFStringRef address
)
456 struct sockaddr_in sin
;
457 struct sockaddr_in6 sin6
;
460 SCNetworkReachabilityFlags flags
;
464 if (_SC_cfstring_to_cstring(address
, buf
, sizeof(buf
), kCFStringEncodingASCII
) == NULL
) {
465 my_log(LOG_ERR
, "could not convert [primary] address");
469 if (_SC_string_to_sockaddr(buf
, AF_UNSPEC
, (void *)&addr
, sizeof(addr
)) == NULL
) {
470 /* if not an IP[v6] address */
471 my_log(LOG_ERR
, "could not parse [primary] address");
475 ok
= _SC_checkResolverReachabilityByAddress(&store
, &flags
, &haveDNS
, &addr
.sa
);
477 if (!(flags
& kSCNetworkReachabilityFlagsReachable
) ||
478 (flags
& kSCNetworkReachabilityFlagsConnectionRequired
)) {
479 // if not reachable *OR* connection required
485 CFMachPortContext context
= { 0
489 , replyMPCopyDescription
493 CFRunLoopSourceRef rls
;
495 (void) gettimeofday(&dnsQueryStart
, NULL
);
497 error
= getnameinfo_async_start(&mp
,
500 NI_NAMEREQD
, // flags
506 __MACH_PORT_DEBUG(TRUE
, "*** set-hostname (after getnameinfo_async_start)", mp
);
509 dnsPort
= _SC_CFMachPortCreateWithPort("IPMonitor/set-hostname",
511 getnameinfo_async_handleCFReply
,
513 rls
= CFMachPortCreateRunLoopSource(NULL
, dnsPort
, 0);
514 CFRunLoopAddSource(CFRunLoopGetCurrent(), rls
, kCFRunLoopDefaultMode
);
516 __MACH_PORT_DEBUG(TRUE
, "*** set-hostname (after scheduling)", mp
);
526 update_hostname(SCDynamicStoreRef store
, CFArrayRef changedKeys
, void *info
)
528 CFStringRef address
= NULL
;
529 CFStringRef hostname
= NULL
;
530 CFStringRef serviceID
= NULL
;
532 // if active, cancel any in-progress attempt to resolve the primary IP address
534 if (dnsPort
!= NULL
) {
535 mach_port_t mp
= CFMachPortGetPort(dnsPort
);
537 /* cancel the outstanding DNS query */
538 CFMachPortInvalidate(dnsPort
);
542 getnameinfo_async_cancel(mp
);
545 // get static hostname, if available
547 hostname
= copy_static_name();
548 if (hostname
!= NULL
) {
549 my_log(LOG_INFO
, "hostname (static) = %@", hostname
);
550 set_hostname(hostname
);
554 // get [prefs] hostname, if available
556 hostname
= copy_prefs_hostname(store
);
557 if (hostname
!= NULL
) {
558 my_log(LOG_INFO
, "hostname (prefs) = %@", hostname
);
559 set_hostname(hostname
);
563 // get primary service ID
565 serviceID
= copy_primary_service(store
);
566 if (serviceID
== NULL
) {
570 // get DHCP provided name, if available
572 hostname
= copy_dhcp_hostname(serviceID
);
573 if (hostname
!= NULL
) {
574 my_log(LOG_INFO
, "hostname (DHCP) = %@", hostname
);
575 set_hostname(hostname
);
579 // get DNS name associated with primary IP, if available
581 address
= copy_primary_ip(store
, serviceID
);
582 if (address
!= NULL
) {
583 // start reverse DNS query using primary IP address
584 (void) start_dns_query(store
, address
);
590 // get local (multicast DNS) name, if available
592 hostname
= SCDynamicStoreCopyLocalHostName(store
);
593 if (hostname
!= NULL
) {
594 CFMutableStringRef localName
;
596 my_log(LOG_INFO
, "hostname (multicast DNS) = %@", hostname
);
597 localName
= CFStringCreateMutableCopy(NULL
, 0, hostname
);
598 assert(localName
!= NULL
);
599 CFStringAppend(localName
, CFSTR(".local"));
600 set_hostname(localName
);
601 CFRelease(localName
);
605 // use "localhost" if not other name is available
607 set_hostname(CFSTR("localhost"));
611 if (address
) CFRelease(address
);
612 if (hostname
) CFRelease(hostname
);
613 if (serviceID
) CFRelease(serviceID
);
621 load_hostname(Boolean verbose
)
624 CFMutableArrayRef keys
= NULL
;
625 CFMutableArrayRef patterns
= NULL
;
631 /* initialize a few globals */
633 store
= SCDynamicStoreCreate(NULL
, CFSTR("set-hostname"), update_hostname
, NULL
);
636 "SCDynamicStoreCreate() failed: %s",
637 SCErrorString(SCError()));
641 /* establish notification keys and patterns */
643 keys
= CFArrayCreateMutable(NULL
, 0, &kCFTypeArrayCallBacks
);
644 patterns
= CFArrayCreateMutable(NULL
, 0, &kCFTypeArrayCallBacks
);
646 /* ...watch for primary service / interface changes */
647 key
= SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL
,
648 kSCDynamicStoreDomainState
,
650 CFArrayAppendValue(keys
, key
);
653 /* ...watch for DNS configuration changes */
654 key
= SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL
,
655 kSCDynamicStoreDomainState
,
657 CFArrayAppendValue(keys
, key
);
660 /* ...watch for (per-service) DHCP option changes */
661 key
= SCDynamicStoreKeyCreateNetworkServiceEntity(NULL
,
662 kSCDynamicStoreDomainState
,
665 CFArrayAppendValue(patterns
, key
);
668 /* ...watch for (BSD) hostname changes */
669 key
= SCDynamicStoreKeyCreateComputerName(NULL
);
670 CFArrayAppendValue(keys
, key
);
673 /* ...watch for local (multicast DNS) hostname changes */
674 key
= SCDynamicStoreKeyCreateHostNames(NULL
);
675 CFArrayAppendValue(keys
, key
);
678 /* register the keys/patterns */
679 if (!SCDynamicStoreSetNotificationKeys(store
, keys
, patterns
)) {
681 "SCDynamicStoreSetNotificationKeys() failed: %s",
682 SCErrorString(SCError()));
686 rls
= SCDynamicStoreCreateRunLoopSource(NULL
, store
, 0);
689 "SCDynamicStoreCreateRunLoopSource() failed: %s",
690 SCErrorString(SCError()));
693 CFRunLoopAddSource(CFRunLoopGetCurrent(), rls
, kCFRunLoopDefaultMode
);
701 if (keys
!= NULL
) CFRelease(keys
);
702 if (patterns
!= NULL
) CFRelease(patterns
);
703 if (store
!= NULL
) CFRelease(store
);
710 main(int argc
, char **argv
)
716 if ((argc
> 1) && (strcmp(argv
[1], "-d") == 0)) {
723 CFStringRef hostname
;
724 CFStringRef serviceID
;
725 SCDynamicStoreRef store
;
727 store
= SCDynamicStoreCreate(NULL
, CFSTR("set-hostname"), NULL
, NULL
);
729 SCPrint(TRUE
, stdout
,
730 CFSTR("SCDynamicStoreCreate() failed: %s\n"),
731 SCErrorString(SCError()));
735 // get static hostname
736 hostname
= copy_static_name();
737 if (hostname
!= NULL
) {
738 SCPrint(TRUE
, stdout
, CFSTR("hostname (static) = %@\n"), hostname
);
742 // get [prefs] hostname, if available
743 hostname
= copy_prefs_hostname(store
);
744 if (hostname
!= NULL
) {
745 SCPrint(TRUE
, stdout
, CFSTR("hostname (prefs) = %@\n"), hostname
);
749 // get primary service
750 serviceID
= copy_primary_service(store
);
751 if (serviceID
!= NULL
) {
752 SCPrint(TRUE
, stdout
, CFSTR("primary service ID = %@\n"), serviceID
);
754 SCPrint(TRUE
, stdout
, CFSTR("No primary service\n"));
758 if ((argc
== (2+1)) && (argv
[1][0] == 's')) {
759 if (serviceID
!= NULL
) CFRelease(serviceID
);
760 serviceID
= CFStringCreateWithCString(NULL
, argv
[2], kCFStringEncodingUTF8
);
761 SCPrint(TRUE
, stdout
, CFSTR("alternate service ID = %@\n"), serviceID
);
764 // get DHCP provided name
765 hostname
= copy_dhcp_name(store
, serviceID
);
766 if (hostname
!= NULL
) {
767 SCPrint(TRUE
, stdout
, CFSTR("hostname (DHCP) = %@\n"), hostname
);
771 // get primary IP address
772 address
= copy_primary_ip(store
, serviceID
);
773 if (address
!= NULL
) {
774 SCPrint(TRUE
, stdout
, CFSTR("primary address = %@\n"), address
);
776 if ((argc
== (2+1)) && (argv
[1][0] == 'a')) {
777 if (address
!= NULL
) CFRelease(address
);
778 address
= CFStringCreateWithCString(NULL
, argv
[2], kCFStringEncodingUTF8
);
779 SCPrint(TRUE
, stdout
, CFSTR("alternate primary address = %@\n"), address
);
782 // start reverse DNS query using primary IP address
783 start_dns_query(store
, address
);
787 CFRelease(serviceID
);
791 // get local (multicast DNS) name, if available
793 hostname
= SCDynamicStoreCopyLocalHostName(store
);
794 if (hostname
!= NULL
) {
795 CFMutableStringRef localName
;
797 SCPrint(TRUE
, stdout
, CFSTR("hostname (multicast DNS) = %@\n"), hostname
);
798 localName
= CFStringCreateMutableCopy(NULL
, 0, hostname
);
799 CFStringAppend(localName
, CFSTR(".local"));
800 CFRelease(localName
);
803 if (hostname
!= NULL
) CFRelease(hostname
);
805 update_hostname(store
, NULL
, NULL
);
814 _sc_verbose
= (argc
> 1) ? TRUE
: FALSE
;
816 load_hostname((argc
> 1) ? TRUE
: FALSE
);