2 * Copyright (c) 2004, 2005 Apple Computer, 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/types.h>
27 #include <sys/socket.h>
30 #include <net/if_dl.h>
31 #include <netinet/in.h>
32 #include <arpa/inet.h>
33 #include <netdb_async.h>
35 #include <CoreFoundation/CoreFoundation.h>
36 #include <SystemConfiguration/SystemConfiguration.h>
37 #include <SystemConfiguration/SCDynamicStoreCopyDHCPInfo.h>
38 #include <SystemConfiguration/SCValidation.h>
39 #include <SystemConfiguration/SCPrivate.h> // for SCLog(), SCPrint()
44 static SCDynamicStoreRef store
= NULL
;
45 static CFRunLoopSourceRef rls
= NULL
;
47 static Boolean dnsActive
= FALSE
;
48 static CFMachPortRef dnsPort
= NULL
;
49 static CFRunLoopSourceRef dnsRLS
= NULL
;
50 static struct timeval dnsQueryStart
;
52 static Boolean _verbose
= FALSE
;
55 /* SPI (from SCNetworkReachability.c) */
57 _SC_checkResolverReachability(SCDynamicStoreRef
*storeP
,
58 SCNetworkConnectionFlags
*flags
,
60 const char * nodename
);
64 * checkResolverReachabilityByAddress()
66 * Given an IP address, determine whether a reverse DNS query can be issued
67 * using the current network configuration.
70 checkResolverReachabilityByAddress(SCDynamicStoreRef store
, struct sockaddr
*sa
)
72 SCNetworkConnectionFlags flags
;
79 * Ideally, we would have an API that given a local IP
80 * address would return the DNS server(s) that would field
81 * a given PTR query. Fortunately, we do have an SPI which
82 * which will provide this information given a "name" so we
83 * take the address, convert it into the inverse query name,
84 * and find out which servers should be consulted.
87 switch (sa
->sa_family
) {
93 struct sockaddr_in
*sin
= (struct sockaddr_in
*)sa
;
96 * build "PTR" query name
97 * NNN.NNN.NNN.NNN.in-addr.arpa.
99 rev
.s_addr
= sin
->sin_addr
.s_addr
;
100 (void) snprintf(ptr_name
, sizeof(ptr_name
), "%u.%u.%u.%u.in-addr.arpa.",
111 struct sockaddr_in6
*sin6
= (struct sockaddr_in6
*)sa
;
112 int x
= sizeof(ptr_name
);
115 #define USE_NIBBLE_QUERY
116 #ifdef USE_NIBBLE_QUERY
118 * build IPv6 "nibble" PTR query name (RFC 1886, RFC 3152)
119 * 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.
121 for (i
= sizeof(sin6
->sin6_addr
) - 1; i
>= 0; i
--) {
122 n
= snprintf(&ptr_name
[s
], x
, "%x.%x.",
123 ( sin6
->sin6_addr
.s6_addr
[i
] & 0xf),
124 ((sin6
->sin6_addr
.s6_addr
[i
] >> 4) & 0xf));
125 if ((n
== -1) || (n
>= x
)) {
133 n
= snprintf(&ptr_name
[s
], x
, "ip6.arpa.");
134 if ((n
== -1) || (n
>= x
)) {
137 #else /* USE_NIBBLE_QUERY */
139 * build IPv6 "bit-string" PTR query name (RFC 2673)
140 * \[xNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN].ip6.arpa.
142 n
= snprintf(&ptr_name
[0], x
, "\\[x");
143 if ((n
== -1) || (n
>= x
)) {
149 for (i
= 0; i
< 16; i
++) {
150 n
= snprintf(&ptr_name
[s
], x
, "%2.2x", sin6
->sin6_addr
.s6_addr
[i
]);
151 if ((n
== -1) || (n
>= x
)) {
159 n
= snprintf(&ptr_name
[s
], x
, "].ip6.arpa.");
160 if ((n
== -1) || (n
>= x
)) {
163 #endif /* USE_NIBBLE_QUERY */
172 ok
= _SC_checkResolverReachability(&store
, &flags
, &haveDNS
, ptr_name
);
174 if (!(flags
& kSCNetworkFlagsReachable
) ||
175 (flags
& kSCNetworkFlagsConnectionRequired
)) {
176 // if not reachable *OR* connection required
187 #define HOSTNAME_NOTIFY_KEY "com.apple.system.hostname"
191 set_hostname(CFStringRef hostname
)
193 if (hostname
!= NULL
) {
194 char old_name
[MAXHOSTNAMELEN
];
195 char new_name
[MAXHOSTNAMELEN
];
197 if (gethostname(old_name
, sizeof(old_name
)) == -1) {
198 SCLog(TRUE
, LOG_ERR
, CFSTR("gethostname() failed: %s"), strerror(errno
));
202 if (_SC_cfstring_to_cstring(hostname
,
205 kCFStringEncodingUTF8
) == NULL
) {
206 SCLog(TRUE
, LOG_ERR
, CFSTR("could not convert [new] hostname"));
210 old_name
[sizeof(old_name
)-1] = '\0';
211 new_name
[sizeof(new_name
)-1] = '\0';
212 if (strcmp(old_name
, new_name
) != 0) {
213 if (sethostname(new_name
, strlen(new_name
)) == 0) {
216 SCLog(TRUE
, LOG_NOTICE
,
217 CFSTR("setting hostname to \"%s\""),
220 status
= notify_post(HOSTNAME_NOTIFY_KEY
);
221 if (status
!= NOTIFY_STATUS_OK
) {
223 CFSTR("notify_post(" HOSTNAME_NOTIFY_KEY
") failed: error=%lu"),
228 CFSTR("sethostname(%s, %d) failed: %s"),
240 #define HOSTCONFIG "/etc/hostconfig"
241 #define HOSTNAME_KEY "HOSTNAME="
242 #define AUTOMATIC "-AUTOMATIC-"
244 #define HOSTNAME_KEY_LEN (sizeof(HOSTNAME_KEY) - 1)
251 CFStringRef name
= NULL
;
253 f
= fopen(HOSTCONFIG
, "r");
258 while (fgets(buf
, sizeof(buf
), f
) != NULL
) {
266 if (buf
[n
-1] == '\n') {
267 /* the entire line fit in the buffer, remove the newline */
270 /* eat the remainder of the line */
273 } while ((n
!= '\n') && (n
!= EOF
));
276 // skip leading white space
278 while (isspace(*bp
)) {
282 // find "HOSTNAME=" key
283 if (strncmp(bp
, HOSTNAME_KEY
, HOSTNAME_KEY_LEN
) != 0) {
287 // get the hostname string
288 bp
+= HOSTNAME_KEY_LEN
;
293 while (*bp
!= '\0') {
306 str_quote
= !str_quote
;
317 } else if (!str_quote
&& (isspace(ch
) || (ch
== '#'))) {
333 // the shell won't parse this file so neither will we
337 if (strcmp(buf
, AUTOMATIC
) == 0) {
338 // skip "-AUTOMATIC-"
342 name
= CFStringCreateWithCString(NULL
, buf
, kCFStringEncodingUTF8
);
350 #ifndef kSCPropNetHostName
351 #define kSCPropNetHostName CFSTR("HostName")
356 copy_prefs_hostname(SCDynamicStoreRef store
)
358 CFDictionaryRef dict
;
360 CFStringRef name
= NULL
;
362 key
= SCDynamicStoreKeyCreateComputerName(NULL
);
363 dict
= SCDynamicStoreCopyValue(store
, key
);
368 if (!isA_CFDictionary(dict
)) {
372 name
= isA_CFString(CFDictionaryGetValue(dict
, kSCPropNetHostName
));
380 if (dict
!= NULL
) CFRelease(dict
);
387 copy_primary_service(SCDynamicStoreRef store
)
389 CFDictionaryRef dict
;
391 CFStringRef serviceID
= NULL
;
393 key
= SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL
,
394 kSCDynamicStoreDomainState
,
396 dict
= SCDynamicStoreCopyValue(store
, key
);
400 if (isA_CFDictionary(dict
)) {
401 serviceID
= CFDictionaryGetValue(dict
, kSCDynamicStorePropNetPrimaryService
);
402 if (isA_CFString(serviceID
)) {
416 copy_primary_ip(SCDynamicStoreRef store
, CFStringRef serviceID
)
418 CFDictionaryRef dict
;
420 CFStringRef address
= NULL
;
422 key
= SCDynamicStoreKeyCreateNetworkServiceEntity(NULL
,
423 kSCDynamicStoreDomainState
,
426 dict
= SCDynamicStoreCopyValue(store
, key
);
430 if (isA_CFDictionary(dict
)) {
431 CFArrayRef addresses
;
433 addresses
= CFDictionaryGetValue(dict
, kSCPropNetIPv4Addresses
);
434 if (isA_CFArray(addresses
) && (CFArrayGetCount(addresses
) > 0)) {
435 address
= CFArrayGetValueAtIndex(addresses
, 0);
436 if (isA_CFString(address
)) {
450 #define DHCP_OPTION_HOSTNAME 12
453 copy_dhcp_name(SCDynamicStoreRef store
, CFStringRef serviceID
)
455 CFDictionaryRef info
;
456 CFStringRef name
= NULL
;
458 info
= SCDynamicStoreCopyDHCPInfo(store
, serviceID
);
462 data
= DHCPInfoGetOptionData(info
, DHCP_OPTION_HOSTNAME
);
464 name
= CFStringCreateFromExternalRepresentation(NULL
, data
, kCFStringEncodingUTF8
);
475 reverseDNSComplete(int32_t status
, char *host
, char *serv
, void *context
)
477 struct timeval dnsQueryComplete
;
478 struct timeval dnsQueryElapsed
;
479 CFStringRef hostname
;
480 SCDynamicStoreRef store
= (SCDynamicStoreRef
)context
;
482 (void) gettimeofday(&dnsQueryComplete
, NULL
);
483 timersub(&dnsQueryComplete
, &dnsQueryStart
, &dnsQueryElapsed
);
484 SCLog(_verbose
, LOG_INFO
,
485 CFSTR("async DNS complete%s (query time = %d.%3.3d)"),
486 ((status
== 0) && (host
!= NULL
)) ? "" : ", host not found",
487 dnsQueryElapsed
.tv_sec
,
488 dnsQueryElapsed
.tv_usec
/ 1000);
490 // use reverse DNS name, if available
495 * if [reverse] DNS query was successful
498 hostname
= CFStringCreateWithCString(NULL
, host
, kCFStringEncodingUTF8
);
499 SCLog(TRUE
, LOG_INFO
, CFSTR("hostname (reverse DNS query) = %@"), hostname
);
500 set_hostname(hostname
);
508 * if no name available
516 SCLog(TRUE
, LOG_ERR
, CFSTR("getnameinfo() failed: %s"), gai_strerror(status
));
519 // get local (multicast DNS) name, if available
521 hostname
= SCDynamicStoreCopyLocalHostName(store
);
522 if (hostname
!= NULL
) {
523 CFMutableStringRef localName
;
525 SCLog(TRUE
, LOG_INFO
, CFSTR("hostname (multicast DNS) = %@"), hostname
);
526 localName
= CFStringCreateMutableCopy(NULL
, 0, hostname
);
527 CFStringAppend(localName
, CFSTR(".local"));
528 set_hostname(localName
);
529 CFRelease(localName
);
534 // use "localhost" if not other name is available
536 set_hostname(CFSTR("localhost"));
540 if (host
!= NULL
) free(host
);
541 if (serv
!= NULL
) free(serv
);
548 getnameinfo_async_handleCFReply(CFMachPortRef port
, void *msg
, CFIndex size
, void *info
)
552 status
= getnameinfo_async_handle_reply(msg
);
553 if ((status
== 0) && dnsActive
) {
554 // if request has been re-queued
558 if (port
== dnsPort
) {
559 CFRunLoopSourceInvalidate(dnsRLS
);
571 start_dns_query(SCDynamicStoreRef store
, CFStringRef address
)
576 struct sockaddr_in sin
;
577 struct sockaddr_in6 sin6
;
579 if (_SC_cfstring_to_cstring(address
, addr
, sizeof(addr
), kCFStringEncodingASCII
) == NULL
) {
580 SCLog(TRUE
, LOG_ERR
, CFSTR("could not convert [primary] address"));
584 bzero(&sin
, sizeof(sin
));
585 sin
.sin_len
= sizeof(sin
);
586 sin
.sin_family
= AF_INET
;
588 bzero(&sin6
, sizeof(sin6
));
589 sin6
.sin6_len
= sizeof(sin6
);
590 sin6
.sin6_family
= AF_INET6
;
592 if (inet_aton(addr
, &sin
.sin_addr
) == 1) {
596 sa
= (struct sockaddr
*)&sin
;
597 } else if (inet_pton(AF_INET6
, addr
, &sin6
.sin6_addr
) == 1) {
603 p
= strchr(addr
, '%');
605 sin6
.sin6_scope_id
= if_nametoindex(p
+1);
608 sa
= (struct sockaddr
*)&sin6
;
613 ok
= checkResolverReachabilityByAddress(store
, sa
);
615 CFMachPortContext context
= { 0, (void *)store
, CFRetain
, CFRelease
, CFCopyDescription
};
619 (void) gettimeofday(&dnsQueryStart
, NULL
);
621 error
= getnameinfo_async_start(&port
,
632 dnsPort
= CFMachPortCreateWithPort(NULL
,
634 getnameinfo_async_handleCFReply
,
637 dnsRLS
= CFMachPortCreateRunLoopSource(NULL
, dnsPort
, 0);
638 CFRunLoopAddSource(CFRunLoopGetCurrent(), dnsRLS
, kCFRunLoopDefaultMode
);
648 update_hostname(SCDynamicStoreRef store
, CFArrayRef changedKeys
, void *info
)
650 CFStringRef address
= NULL
;
651 CFStringRef hostname
= NULL
;
652 CFStringRef serviceID
= NULL
;
654 // if active, cancel any in-progress attempt to resolve the primary IP address
656 if (dnsPort
!= NULL
) {
657 /* cancel the outstanding DNS query */
658 lu_async_call_cancel(CFMachPortGetPort(dnsPort
));
659 CFRunLoopSourceInvalidate(dnsRLS
);
666 // get static hostname, if available
668 hostname
= copy_static_name();
669 if (hostname
!= NULL
) {
670 SCLog(TRUE
, LOG_INFO
, CFSTR("hostname (static) = %@"), hostname
);
671 set_hostname(hostname
);
675 // get [prefs] hostname, if available
677 hostname
= copy_prefs_hostname(store
);
678 if (hostname
!= NULL
) {
679 SCLog(TRUE
, LOG_INFO
, CFSTR("hostname (prefs) = %@"), hostname
);
680 set_hostname(hostname
);
684 // get primary service ID
686 serviceID
= copy_primary_service(store
);
687 if (serviceID
== NULL
) {
691 // get DHCP provided name, if available
693 hostname
= copy_dhcp_name(store
, serviceID
);
694 if (hostname
!= NULL
) {
695 SCLog(TRUE
, LOG_INFO
, CFSTR("hostname (DHCP) = %@"), hostname
);
696 set_hostname(hostname
);
700 // get DNS name associated with primary IP, if available
702 address
= copy_primary_ip(store
, serviceID
);
703 if (address
!= NULL
) {
704 // start reverse DNS query using primary IP address
705 (void) start_dns_query(store
, address
);
711 // get local (multicast DNS) name, if available
713 hostname
= SCDynamicStoreCopyLocalHostName(store
);
714 if (hostname
!= NULL
) {
715 CFMutableStringRef localName
;
717 SCLog(TRUE
, LOG_INFO
, CFSTR("hostname (multicast DNS) = %@"), hostname
);
718 localName
= CFStringCreateMutableCopy(NULL
, 0, hostname
);
719 CFStringAppend(localName
, CFSTR(".local"));
720 set_hostname(localName
);
721 CFRelease(localName
);
725 // use "localhost" if not other name is available
727 set_hostname(CFSTR("localhost"));
731 if (address
) CFRelease(address
);
732 if (hostname
) CFRelease(hostname
);
733 if (serviceID
) CFRelease(serviceID
);
741 load_hostname(Boolean verbose
)
744 CFMutableArrayRef keys
= NULL
;
745 CFMutableArrayRef patterns
= NULL
;
751 /* initialize a few globals */
753 store
= SCDynamicStoreCreate(NULL
, CFSTR("set-hostname"), update_hostname
, NULL
);
756 CFSTR("SCDynamicStoreCreate() failed: %s"),
757 SCErrorString(SCError()));
761 /* establish notification keys and patterns */
763 keys
= CFArrayCreateMutable(NULL
, 0, &kCFTypeArrayCallBacks
);
764 patterns
= CFArrayCreateMutable(NULL
, 0, &kCFTypeArrayCallBacks
);
766 /* ...watch for primary service / interface changes */
767 key
= SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL
,
768 kSCDynamicStoreDomainState
,
770 CFArrayAppendValue(keys
, key
);
773 /* ...watch for DNS configuration changes */
774 key
= SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL
,
775 kSCDynamicStoreDomainState
,
777 CFArrayAppendValue(keys
, key
);
780 /* ...watch for (per-service) DHCP option changes */
781 key
= SCDynamicStoreKeyCreateNetworkServiceEntity(NULL
,
782 kSCDynamicStoreDomainState
,
785 CFArrayAppendValue(patterns
, key
);
788 /* ...watch for (BSD) hostname changes */
789 key
= SCDynamicStoreKeyCreateComputerName(NULL
);
790 CFArrayAppendValue(keys
, key
);
793 /* ...watch for local (multicast DNS) hostname changes */
794 key
= SCDynamicStoreKeyCreateHostNames(NULL
);
795 CFArrayAppendValue(keys
, key
);
798 /* register the keys/patterns */
799 if (!SCDynamicStoreSetNotificationKeys(store
, keys
, patterns
)) {
801 CFSTR("SCDynamicStoreSetNotificationKeys() failed: %s"),
802 SCErrorString(SCError()));
806 rls
= SCDynamicStoreCreateRunLoopSource(NULL
, store
, 0);
809 CFSTR("SCDynamicStoreCreateRunLoopSource() failed: %s"),
810 SCErrorString(SCError()));
813 CFRunLoopAddSource(CFRunLoopGetCurrent(), rls
, kCFRunLoopDefaultMode
);
821 if (keys
!= NULL
) CFRelease(keys
);
822 if (patterns
!= NULL
) CFRelease(patterns
);
823 if (store
!= NULL
) CFRelease(store
);
830 main(int argc
, char **argv
)
836 if ((argc
> 1) && (strcmp(argv
[1], "-d") == 0)) {
843 CFStringRef hostname
;
844 CFStringRef serviceID
;
845 SCDynamicStoreRef store
;
847 store
= SCDynamicStoreCreate(NULL
, CFSTR("set-hostname"), NULL
, NULL
);
849 SCPrint(TRUE
, stdout
,
850 CFSTR("SCDynamicStoreCreate() failed: %s\n"),
851 SCErrorString(SCError()));
855 // get static hostname
856 hostname
= copy_static_name();
857 if (hostname
!= NULL
) {
858 SCPrint(TRUE
, stdout
, CFSTR("hostname (static) = %@\n"), hostname
);
862 // get [prefs] hostname, if available
863 hostname
= copy_prefs_hostname(store
);
864 if (hostname
!= NULL
) {
865 SCPrint(TRUE
, stdout
, CFSTR("hostname (prefs) = %@\n"), hostname
);
869 // get primary service
870 serviceID
= copy_primary_service(store
);
871 if (serviceID
!= NULL
) {
872 SCPrint(TRUE
, stdout
, CFSTR("primary service ID = %@\n"), serviceID
);
874 SCPrint(TRUE
, stdout
, CFSTR("No primary service\n"));
878 if ((argc
== (2+1)) && (argv
[1][0] == 's')) {
879 if (serviceID
!= NULL
) CFRelease(serviceID
);
880 serviceID
= CFStringCreateWithCString(NULL
, argv
[2], kCFStringEncodingUTF8
);
881 SCPrint(TRUE
, stdout
, CFSTR("alternate service ID = %@\n"), serviceID
);
884 // get DHCP provided name
885 hostname
= copy_dhcp_name(store
, serviceID
);
886 if (hostname
!= NULL
) {
887 SCPrint(TRUE
, stdout
, CFSTR("hostname (DHCP) = %@\n"), hostname
);
891 // get primary IP address
892 address
= copy_primary_ip(store
, serviceID
);
893 if (address
!= NULL
) {
894 SCPrint(TRUE
, stdout
, CFSTR("primary address = %@\n"), address
);
896 if ((argc
== (2+1)) && (argv
[1][0] == 'a')) {
897 if (address
!= NULL
) CFRelease(address
);
898 address
= CFStringCreateWithCString(NULL
, argv
[2], kCFStringEncodingUTF8
);
899 SCPrint(TRUE
, stdout
, CFSTR("alternate primary address = %@\n"), address
);
902 // start reverse DNS query using primary IP address
903 start_dns_query(store
, address
);
907 CFRelease(serviceID
);
911 // get local (multicast DNS) name, if available
913 hostname
= SCDynamicStoreCopyLocalHostName(store
);
914 if (hostname
!= NULL
) {
915 CFMutableStringRef localName
;
917 SCPrint(TRUE
, stdout
, CFSTR("hostname (multicast DNS) = %@\n"), hostname
);
918 localName
= CFStringCreateMutableCopy(NULL
, 0, hostname
);
919 CFStringAppend(localName
, CFSTR(".local"));
920 CFRelease(localName
);
923 if (hostname
!= NULL
) CFRelease(hostname
);
925 update_hostname(store
, NULL
, NULL
);
934 _sc_verbose
= (argc
> 1) ? TRUE
: FALSE
;
936 load_hostname((argc
> 1) ? TRUE
: FALSE
);