2 * Copyright (c) 2004-2018, 2020 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>
45 #define my_log(__level, __format, ...) SCPrint(TRUE, stdout, CFSTR(__format "\n"), ## __VA_ARGS__)
47 #include "ip_plugin.h"
50 static SCDynamicStoreRef store
= NULL
;
51 static CFRunLoopRef rl
= NULL
;
52 static CFRunLoopSourceRef rls
= NULL
;
53 static dispatch_queue_t queue
= NULL
;
55 static int notify_token
= -1;
57 static struct timeval ptrQueryStart
;
58 static SCNetworkReachabilityRef ptrTarget
= NULL
;
61 #define HOSTNAME_NOTIFY_KEY "com.apple.system.hostname"
62 #define SET_HOSTNAME_QUEUE "com.apple.config.set-hostname"
64 CFStringRef
copy_dhcp_hostname(CFStringRef serviceID
);
67 set_hostname(CFStringRef hostname
)
69 if (hostname
!= NULL
) {
70 char old_name
[MAXHOSTNAMELEN
];
71 char new_name
[MAXHOSTNAMELEN
];
73 if (gethostname(old_name
, sizeof(old_name
)) == -1) {
74 my_log(LOG_ERR
, "gethostname() failed: %s", strerror(errno
));
78 if (_SC_cfstring_to_cstring(hostname
,
81 kCFStringEncodingUTF8
) == NULL
) {
82 my_log(LOG_NOTICE
, "could not convert [new] hostname");
86 old_name
[sizeof(old_name
)-1] = '\0';
87 new_name
[sizeof(new_name
)-1] = '\0';
88 if (strcmp(old_name
, new_name
) != 0) {
89 if (sethostname(new_name
, (int)strlen(new_name
)) == 0) {
93 "setting hostname to \"%s\"",
96 status
= notify_post(HOSTNAME_NOTIFY_KEY
);
97 if (status
!= NOTIFY_STATUS_OK
) {
99 "notify_post(" HOSTNAME_NOTIFY_KEY
") failed: error=%u",
104 "sethostname(%s, %ld) failed: %s",
117 copy_prefs_hostname(SCDynamicStoreRef store
)
119 CFDictionaryRef dict
;
121 CFStringRef name
= NULL
;
123 key
= SCDynamicStoreKeyCreateComputerName(NULL
);
124 dict
= SCDynamicStoreCopyValue(store
, key
);
129 if (!isA_CFDictionary(dict
)) {
133 name
= isA_CFString(CFDictionaryGetValue(dict
, kSCPropSystemHostName
));
141 if (dict
!= NULL
) CFRelease(dict
);
148 copy_primary_service(SCDynamicStoreRef store
)
150 CFDictionaryRef dict
;
152 CFStringRef serviceID
= NULL
;
154 key
= SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL
,
155 kSCDynamicStoreDomainState
,
157 dict
= SCDynamicStoreCopyValue(store
, key
);
161 if (isA_CFDictionary(dict
)) {
162 serviceID
= CFDictionaryGetValue(dict
, kSCDynamicStorePropNetPrimaryService
);
163 if (isA_CFString(serviceID
)) {
177 copy_primary_ip(SCDynamicStoreRef store
, CFStringRef serviceID
)
179 CFDictionaryRef dict
;
181 CFStringRef address
= NULL
;
183 key
= SCDynamicStoreKeyCreateNetworkServiceEntity(NULL
,
184 kSCDynamicStoreDomainState
,
187 dict
= SCDynamicStoreCopyValue(store
, key
);
191 if (isA_CFDictionary(dict
)) {
192 CFArrayRef addresses
;
194 addresses
= CFDictionaryGetValue(dict
, kSCPropNetIPv4Addresses
);
195 if (isA_CFArray(addresses
) && (CFArrayGetCount(addresses
) > 0)) {
196 address
= CFArrayGetValueAtIndex(addresses
, 0);
197 if (isA_CFString(address
)) {
214 if (ptrTarget
== NULL
) {
218 my_log(LOG_INFO
, "hostname: ptr query stop");
220 SCNetworkReachabilitySetCallback(ptrTarget
, NULL
, NULL
);
221 SCNetworkReachabilityUnscheduleFromRunLoop(ptrTarget
, rl
, kCFRunLoopDefaultMode
);
222 CFRelease(ptrTarget
);
229 /* Return a ptr record if the sharing pref name is a matching FQDN */
231 hostname_match_full(CFArrayRef hosts
, CFIndex count
, CFStringRef nameToMatch
)
234 CFStringRef matchedHostName
= NULL
;
236 for (i
= 0; i
< count
; i
++) {
237 CFStringRef tempHostName
;
239 tempHostName
= CFArrayGetValueAtIndex(hosts
, i
);
240 if (CFStringCompare(tempHostName
, nameToMatch
, kCFCompareCaseInsensitive
) == 0) {
241 matchedHostName
= tempHostName
;
245 return matchedHostName
;
249 /* Return a ptr record if the sharing pref name matches DNS record's first label */
251 hostname_match_first_label(CFArrayRef hosts
, CFIndex count
, CFStringRef nameToMatch
)
254 CFStringRef matchedHostName
= NULL
;
256 for (i
= 0; i
< count
; i
++) {
257 CFArrayRef fqdnSeparated
;
258 CFStringRef tempHostName
;
260 tempHostName
= CFArrayGetValueAtIndex(hosts
, i
);
261 fqdnSeparated
= CFStringCreateArrayBySeparatingStrings(NULL
, tempHostName
, CFSTR("."));
262 if (fqdnSeparated
!= NULL
) {
263 CFStringRef firstLabel
;
266 firstLabel
= CFArrayGetValueAtIndex(fqdnSeparated
, 0);
267 matchFound
= (CFStringCompare(firstLabel
, nameToMatch
, kCFCompareCaseInsensitive
) == 0);
268 CFRelease(fqdnSeparated
);
270 matchedHostName
= tempHostName
;
275 return matchedHostName
;
280 ptr_query_callback(SCNetworkReachabilityRef target
, SCNetworkReachabilityFlags flags
, void *info
)
283 CFStringRef hostname
= NULL
;
284 struct timeval ptrQueryComplete
;
285 struct timeval ptrQueryElapsed
;
287 (void) gettimeofday(&ptrQueryComplete
, NULL
);
288 timersub(&ptrQueryComplete
, &ptrQueryStart
, &ptrQueryElapsed
);
290 // use reverse DNS name, if available
292 if (flags
& kSCNetworkReachabilityFlagsReachable
) {
297 * if [reverse] DNS query was successful
299 hosts
= SCNetworkReachabilityCopyResolvedAddress(target
, &error_num
);
301 CFIndex count
= CFArrayGetCount(hosts
);
303 CFStringRef computerName
;
304 CFStringRef localHostName
;
306 my_log(LOG_INFO
, "hostname: ptr query complete (query time = %ld.%3.3d)",
307 ptrQueryElapsed
.tv_sec
,
308 ptrQueryElapsed
.tv_usec
/ 1000);
310 // first, check if ComputerName is dns-clean
311 computerName
= _SCPreferencesCopyComputerName(NULL
, NULL
);
312 if (computerName
!= NULL
) {
313 if (_SC_CFStringIsValidDNSName(computerName
)) {
316 dotsCheck
= CFStringFind(computerName
, CFSTR("."), 0);
317 if (dotsCheck
.length
== 0) {
318 hostname
= hostname_match_first_label(hosts
, count
, computerName
);
320 hostname
= hostname_match_full(hosts
, count
, computerName
);
323 CFRelease(computerName
);
326 // if no match, check LocalHostName against the first label of FQDN
327 localHostName
= (hostname
== NULL
) ? SCDynamicStoreCopyLocalHostName(store
) : NULL
;
328 if (localHostName
!= NULL
) {
329 hostname
= hostname_match_first_label(hosts
, count
, localHostName
);
330 CFRelease(localHostName
);
333 // if no match, use the first of the returned names
334 if (hostname
== NULL
) {
335 hostname
= CFArrayGetValueAtIndex(hosts
, 0);
338 my_log(LOG_INFO
, "hostname (reverse DNS query) = %@", hostname
);
339 set_hostname(hostname
);
341 my_log(LOG_INFO
, "hostname: ptr query complete w/no hosts (query time = %ld.%3.3d)",
342 ptrQueryElapsed
.tv_sec
,
343 ptrQueryElapsed
.tv_usec
/ 1000);
347 if (hostname
!= NULL
) {
351 // if kSCNetworkReachabilityFlagsReachable and hosts == NULL
352 // it means the PTR request has not come back yet
353 // we must wait for this callback to be called again
354 my_log(LOG_INFO
, "hostname: ptr query reply w/no hosts (query time = %ld.%3.3d)",
355 ptrQueryElapsed
.tv_sec
,
356 ptrQueryElapsed
.tv_usec
/ 1000);
360 my_log(LOG_INFO
, "hostname: ptr query complete, host not found (query time = %ld.%3.3d)",
361 ptrQueryElapsed
.tv_sec
,
362 ptrQueryElapsed
.tv_usec
/ 1000);
365 // get local (multicast DNS) name, if available
367 hostname
= SCDynamicStoreCopyLocalHostName(store
);
368 if (hostname
!= NULL
) {
369 CFMutableStringRef localHostName
;
371 my_log(LOG_INFO
, "hostname (multicast DNS) = %@", hostname
);
372 localHostName
= CFStringCreateMutableCopy(NULL
, 0, hostname
);
373 assert(localHostName
!= NULL
);
374 CFStringAppend(localHostName
, CFSTR(".local"));
375 set_hostname(localHostName
);
376 CFRelease(localHostName
);
381 // use "localhost" if not other name is available
383 my_log(LOG_INFO
, "hostname (localhost)");
384 set_hostname(CFSTR("localhost"));
399 ptr_query_start(CFStringRef address
)
403 struct sockaddr_in sin
;
404 struct sockaddr_in6 sin6
;
408 CFMutableDictionaryRef options
;
410 if (_SC_cfstring_to_cstring(address
, buf
, sizeof(buf
), kCFStringEncodingASCII
) == NULL
) {
411 my_log(LOG_ERR
, "could not convert [primary] address string");
415 if (_SC_string_to_sockaddr(buf
, AF_UNSPEC
, (void *)&addr
, sizeof(addr
)) == NULL
) {
416 my_log(LOG_ERR
, "could not convert [primary] address");
420 options
= CFDictionaryCreateMutable(NULL
,
422 &kCFTypeDictionaryKeyCallBacks
,
423 &kCFTypeDictionaryValueCallBacks
);
424 data
= CFDataCreate(NULL
, (const UInt8
*)&addr
.sa
, addr
.sa
.sa_len
);
425 CFDictionarySetValue(options
, kSCNetworkReachabilityOptionPTRAddress
, data
);
427 ptrTarget
= SCNetworkReachabilityCreateWithOptions(NULL
, options
);
429 if (ptrTarget
== NULL
) {
430 my_log(LOG_ERR
, "could not resolve [primary] address");
434 my_log(LOG_INFO
, "hostname: ptr query start");
436 (void) gettimeofday(&ptrQueryStart
, NULL
);
437 (void) SCNetworkReachabilitySetCallback(ptrTarget
, ptr_query_callback
, NULL
);
438 (void) SCNetworkReachabilityScheduleWithRunLoop(ptrTarget
, rl
, kCFRunLoopDefaultMode
);
445 update_hostname(SCDynamicStoreRef store
, CFArrayRef changedKeys
, void *info
)
447 #pragma unused(changedKeys)
449 CFStringRef address
= NULL
;
450 CFStringRef hostname
= NULL
;
451 CFStringRef serviceID
= NULL
;
453 // if active, cancel any in-progress attempt to resolve the primary IP address
455 if (ptrTarget
!= NULL
) {
459 // get [prefs] hostname, if available
461 hostname
= copy_prefs_hostname(store
);
462 if (hostname
!= NULL
) {
463 my_log(LOG_INFO
, "hostname (prefs) = %@", hostname
);
464 set_hostname(hostname
);
468 // get primary service ID
470 serviceID
= copy_primary_service(store
);
471 if (serviceID
== NULL
) {
475 // get DHCP provided name, if available
477 hostname
= copy_dhcp_hostname(serviceID
);
478 if (hostname
!= NULL
) {
479 my_log(LOG_INFO
, "hostname (DHCP) = %@", hostname
);
480 set_hostname(hostname
);
484 // get DNS name associated with primary IP, if available
486 address
= copy_primary_ip(store
, serviceID
);
487 if (address
!= NULL
) {
488 boolean_t isExpensive
;
490 // start reverse DNS query using primary IP address
491 // if primary service is not expensive
492 isExpensive
= check_if_service_expensive(serviceID
);
496 ok
= ptr_query_start(address
);
506 // get local (multicast DNS) name, if available
508 hostname
= SCDynamicStoreCopyLocalHostName(store
);
509 if (hostname
!= NULL
) {
510 CFMutableStringRef localHostName
;
512 my_log(LOG_INFO
, "hostname (multicast DNS) = %@", hostname
);
513 localHostName
= CFStringCreateMutableCopy(NULL
, 0, hostname
);
514 assert(localHostName
!= NULL
);
515 CFStringAppend(localHostName
, CFSTR(".local"));
516 set_hostname(localHostName
);
517 CFRelease(localHostName
);
521 // use "localhost" if not other name is available
523 set_hostname(CFSTR("localhost"));
527 if (address
) CFRelease(address
);
528 if (hostname
) CFRelease(hostname
);
529 if (serviceID
) CFRelease(serviceID
);
537 load_hostname(Boolean verbose
)
539 #pragma unused(verbose)
541 CFMutableArrayRef keys
= NULL
;
542 dispatch_block_t notify_block
;
544 CFMutableArrayRef patterns
= NULL
;
547 /* initialize a few globals */
549 store
= SCDynamicStoreCreate(NULL
, CFSTR("set-hostname"), update_hostname
, NULL
);
552 "SCDynamicStoreCreate() failed: %s",
553 SCErrorString(SCError()));
557 queue
= dispatch_queue_create(SET_HOSTNAME_QUEUE
, NULL
);
560 "dispatch_queue_create() failed");
564 /* establish notification keys and patterns */
566 keys
= CFArrayCreateMutable(NULL
, 0, &kCFTypeArrayCallBacks
);
567 patterns
= CFArrayCreateMutable(NULL
, 0, &kCFTypeArrayCallBacks
);
569 /* ...watch for (per-service) DHCP option changes */
570 key
= SCDynamicStoreKeyCreateNetworkServiceEntity(NULL
,
571 kSCDynamicStoreDomainState
,
574 CFArrayAppendValue(patterns
, key
);
577 /* ...watch for (BSD) hostname changes */
578 key
= SCDynamicStoreKeyCreateComputerName(NULL
);
579 CFArrayAppendValue(keys
, key
);
582 /* ...watch for local (multicast DNS) hostname changes */
583 key
= SCDynamicStoreKeyCreateHostNames(NULL
);
584 CFArrayAppendValue(keys
, key
);
587 /* register the keys/patterns */
588 ok
= SCDynamicStoreSetNotificationKeys(store
, keys
, patterns
);
593 "SCDynamicStoreSetNotificationKeys() failed: %s",
594 SCErrorString(SCError()));
598 rl
= CFRunLoopGetCurrent();
599 rls
= SCDynamicStoreCreateRunLoopSource(NULL
, store
, 0);
602 "SCDynamicStoreCreateRunLoopSource() failed: %s",
603 SCErrorString(SCError()));
606 CFRunLoopAddSource(rl
, rls
, kCFRunLoopDefaultMode
);
608 /* ...watch for primary service/interface and DNS configuration changes */
613 key
= SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL
,
614 kSCDynamicStoreDomainState
,
616 changes
= CFArrayCreate(NULL
, (const void **)&key
, 1, &kCFTypeArrayCallBacks
);
617 (*update_hostname
)(store
, changes
, NULL
);
623 status
= notify_register_dispatch(_SC_NOTIFY_NETWORK_CHANGE
,
627 #pragma unused(token)
628 CFRunLoopPerformBlock(rl
,
629 kCFRunLoopDefaultMode
,
633 if (status
!= NOTIFY_STATUS_OK
) {
634 my_log(LOG_ERR
, "notify_register_dispatch() failed: %u", status
);
643 CFRunLoopRemoveSource(rl
, rls
, kCFRunLoopDefaultMode
);
652 dispatch_release(queue
);
662 main(int argc
, char **argv
)
667 _sc_log
= kSCLogDestinationFile
;
668 if ((argc
> 1) && (strcmp(argv
[1], "-d") == 0)) {
675 CFStringRef hostname
;
676 CFStringRef serviceID
;
677 SCDynamicStoreRef store
;
679 store
= SCDynamicStoreCreate(NULL
, CFSTR("set-hostname"), NULL
, NULL
);
681 SCPrint(TRUE
, stdout
,
682 CFSTR("SCDynamicStoreCreate() failed: %s\n"),
683 SCErrorString(SCError()));
687 // get [prefs] hostname, if available
688 hostname
= copy_prefs_hostname(store
);
689 if (hostname
!= NULL
) {
690 SCPrint(TRUE
, stdout
, CFSTR("hostname (prefs) = %@\n"), hostname
);
694 // get local (multicast DNS) name, if available
696 hostname
= SCDynamicStoreCopyLocalHostName(store
);
697 if (hostname
!= NULL
) {
698 SCPrint(TRUE
, stdout
, CFSTR("hostname (multicast DNS) = %@\n"), hostname
);
702 // get primary service
703 serviceID
= copy_primary_service(store
);
704 if (serviceID
!= NULL
) {
705 SCPrint(TRUE
, stdout
, CFSTR("primary service ID = %@\n"), serviceID
);
707 SCPrint(TRUE
, stdout
, CFSTR("No primary service\n"));
710 if ((argc
== (2+1)) && (argv
[1][0] == 's')) {
711 if (serviceID
!= NULL
) CFRelease(serviceID
);
712 serviceID
= CFStringCreateWithCString(NULL
, argv
[2], kCFStringEncodingUTF8
);
713 SCPrint(TRUE
, stdout
, CFSTR("alternate service ID = %@\n"), serviceID
);
716 if (serviceID
!= NULL
) {
717 // get DHCP provided name
718 hostname
= copy_dhcp_hostname(serviceID
);
719 if (hostname
!= NULL
) {
720 SCPrint(TRUE
, stdout
, CFSTR("hostname (DHCP) = %@\n"), hostname
);
724 // get primary IP address
725 address
= copy_primary_ip(store
, serviceID
);
726 if (address
!= NULL
) {
727 SCPrint(TRUE
, stdout
, CFSTR("primary address = %@\n"), address
);
729 if ((argc
== (2+1)) && (argv
[1][0] == 'a')) {
730 if (address
!= NULL
) CFRelease(address
);
731 address
= CFStringCreateWithCString(NULL
, argv
[2], kCFStringEncodingUTF8
);
732 SCPrint(TRUE
, stdout
, CFSTR("alternate primary address = %@\n"), address
);
735 // start reverse DNS query using primary IP address
736 (void) ptr_query_start(address
);
740 CFRelease(serviceID
);
749 _sc_log
= kSCLogDestinationFile
;
750 _sc_verbose
= (argc
> 1) ? TRUE
: FALSE
;
752 load_hostname((argc
> 1) ? TRUE
: FALSE
);