1/* 2 * Copyright (c) 2004-2014 Apple Inc. All rights reserved. 3 * 4 * @APPLE_LICENSE_HEADER_START@ 5 * 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 11 * file. 12 * 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. 20 * 21 * @APPLE_LICENSE_HEADER_END@ 22 */ 23#include <ctype.h> 24#include <stdio.h> 25#include <unistd.h> 26#include <sys/param.h> 27#include <sys/types.h> 28#include <sys/socket.h> 29#include <sys/time.h> 30#include <net/if.h> 31#include <net/if_dl.h> 32#include <netinet/in.h> 33#include <arpa/inet.h> 34#include <netdb_async.h> 35 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> 41 42#include <notify.h> 43 44#ifdef MAIN 45#define my_log(__level, fmt, ...) SCPrint(TRUE, stdout, CFSTR(fmt "\n"), ## __VA_ARGS__) 46#else // MAIN 47#include "ip_plugin.h" 48#endif // MAIN 49 50static SCDynamicStoreRef store = NULL; 51static CFRunLoopSourceRef rls = NULL; 52 53static struct timeval ptrQueryStart; 54static SCNetworkReachabilityRef ptrTarget = NULL; 55 56static Boolean _verbose = FALSE; 57 58 59#define HOSTNAME_NOTIFY_KEY "com.apple.system.hostname" 60 61CFStringRef copy_dhcp_hostname(CFStringRef serviceID); 62 63static void 64set_hostname(CFStringRef hostname) 65{ 66 if (hostname != NULL) { 67 char old_name[MAXHOSTNAMELEN]; 68 char new_name[MAXHOSTNAMELEN]; 69 70 if (gethostname(old_name, sizeof(old_name)) == -1) { 71 my_log(LOG_ERR, "gethostname() failed: %s", strerror(errno)); 72 old_name[0] = '\0'; 73 } 74 75 if (_SC_cfstring_to_cstring(hostname, 76 new_name, 77 sizeof(new_name), 78 kCFStringEncodingUTF8) == NULL) { 79 my_log(LOG_ERR, "could not convert [new] hostname"); 80 new_name[0] = '\0'; 81 } 82 83 old_name[sizeof(old_name)-1] = '\0'; 84 new_name[sizeof(new_name)-1] = '\0'; 85 if (strcmp(old_name, new_name) != 0) { 86 if (sethostname(new_name, (int)strlen(new_name)) == 0) { 87 uint32_t status; 88 89 my_log(LOG_NOTICE, 90 "setting hostname to \"%s\"", 91 new_name); 92 93 status = notify_post(HOSTNAME_NOTIFY_KEY); 94 if (status != NOTIFY_STATUS_OK) { 95 my_log(LOG_ERR, 96 "notify_post(" HOSTNAME_NOTIFY_KEY ") failed: error=%u", 97 status); 98 } 99 } else { 100 my_log(LOG_ERR, 101 "sethostname(%s, %ld) failed: %s", 102 new_name, 103 strlen(new_name), 104 strerror(errno)); 105 } 106 } 107 } 108 109 return; 110} 111 112 113static CFStringRef 114copy_prefs_hostname(SCDynamicStoreRef store) 115{ 116 CFDictionaryRef dict; 117 CFStringRef key; 118 CFStringRef name = NULL; 119 120 key = SCDynamicStoreKeyCreateComputerName(NULL); 121 dict = SCDynamicStoreCopyValue(store, key); 122 CFRelease(key); 123 if (dict == NULL) { 124 goto done; 125 } 126 if (!isA_CFDictionary(dict)) { 127 goto done; 128 } 129 130 name = isA_CFString(CFDictionaryGetValue(dict, kSCPropSystemHostName)); 131 if (name == NULL) { 132 goto done; 133 } 134 CFRetain(name); 135 136 done : 137 138 if (dict != NULL) CFRelease(dict); 139 140 return name; 141} 142 143 144static CFStringRef 145copy_primary_service(SCDynamicStoreRef store) 146{ 147 CFDictionaryRef dict; 148 CFStringRef key; 149 CFStringRef serviceID = NULL; 150 151 key = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, 152 kSCDynamicStoreDomainState, 153 kSCEntNetIPv4); 154 dict = SCDynamicStoreCopyValue(store, key); 155 CFRelease(key); 156 157 if (dict != NULL) { 158 if (isA_CFDictionary(dict)) { 159 serviceID = CFDictionaryGetValue(dict, kSCDynamicStorePropNetPrimaryService); 160 if (isA_CFString(serviceID)) { 161 CFRetain(serviceID); 162 } else { 163 serviceID = NULL; 164 } 165 } 166 CFRelease(dict); 167 } 168 169 return serviceID; 170} 171 172 173static CFStringRef 174copy_primary_ip(SCDynamicStoreRef store, CFStringRef serviceID) 175{ 176 CFDictionaryRef dict; 177 CFStringRef key; 178 CFStringRef address = NULL; 179 180 key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, 181 kSCDynamicStoreDomainState, 182 serviceID, 183 kSCEntNetIPv4); 184 dict = SCDynamicStoreCopyValue(store, key); 185 CFRelease(key); 186 187 if (dict != NULL) { 188 if (isA_CFDictionary(dict)) { 189 CFArrayRef addresses; 190 191 addresses = CFDictionaryGetValue(dict, kSCPropNetIPv4Addresses); 192 if (isA_CFArray(addresses) && (CFArrayGetCount(addresses) > 0)) { 193 address = CFArrayGetValueAtIndex(addresses, 0); 194 if (isA_CFString(address)) { 195 CFRetain(address); 196 } else { 197 address = NULL; 198 } 199 } 200 } 201 CFRelease(dict); 202 } 203 204 return address; 205} 206 207 208static void 209ptr_query_stop() 210{ 211 if (ptrTarget == NULL) { 212 return; 213 } 214 215 SCNetworkReachabilitySetCallback(ptrTarget, NULL, NULL); 216 SCNetworkReachabilityUnscheduleFromRunLoop(ptrTarget, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); 217 CFRelease(ptrTarget); 218 ptrTarget = NULL; 219 220 return; 221} 222 223 224static void 225ptr_query_callback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info) 226{ 227 CFStringRef hostname = NULL; 228 struct timeval ptrQueryComplete; 229 struct timeval ptrQueryElapsed; 230 231 (void) gettimeofday(&ptrQueryComplete, NULL); 232 timersub(&ptrQueryComplete, &ptrQueryStart, &ptrQueryElapsed); 233 if (_verbose) { 234 my_log(LOG_DEBUG, "ptr query complete%s (query time = %ld.%3.3d)", 235 (flags & kSCNetworkReachabilityFlagsReachable) ? "" : ", host not found", 236 ptrQueryElapsed.tv_sec, 237 ptrQueryElapsed.tv_usec / 1000); 238 } 239 240 // use reverse DNS name, if available 241 242 if (flags & kSCNetworkReachabilityFlagsReachable) { 243 int error_num; 244 CFArrayRef hosts; 245 246 /* 247 * if [reverse] DNS query was successful 248 */ 249 hosts = SCNetworkReachabilityCopyResolvedAddress(target, &error_num); 250 if (hosts != NULL) { 251 if (CFArrayGetCount(hosts) > 0) { 252 253 hostname = CFArrayGetValueAtIndex(hosts, 0); 254 my_log(LOG_DEBUG, "hostname (reverse DNS query) = %@", hostname); 255 set_hostname(hostname); 256 } 257 CFRelease(hosts); 258 259 if (hostname != NULL) { 260 goto done; 261 } 262 } 263 } 264 265 // get local (multicast DNS) name, if available 266 267 hostname = SCDynamicStoreCopyLocalHostName(store); 268 if (hostname != NULL) { 269 CFMutableStringRef localName; 270 271 my_log(LOG_DEBUG, "hostname (multicast DNS) = %@", hostname); 272 localName = CFStringCreateMutableCopy(NULL, 0, hostname); 273 assert(localName != NULL); 274 CFStringAppend(localName, CFSTR(".local")); 275 set_hostname(localName); 276 CFRelease(localName); 277 CFRelease(hostname); 278 goto done; 279 } 280 281 // use "localhost" if not other name is available 282 283 set_hostname(CFSTR("localhost")); 284 285 done : 286 287 ptr_query_stop(); 288 289#ifdef MAIN 290 CFRunLoopStop(CFRunLoopGetCurrent()); 291#endif // MAIN 292 293 return; 294} 295 296 297static Boolean 298ptr_query_start(CFStringRef address) 299{ 300 union { 301 struct sockaddr sa; 302 struct sockaddr_in sin; 303 struct sockaddr_in6 sin6; 304 } addr; 305 char buf[64]; 306 CFDataRef data; 307 CFMutableDictionaryRef options; 308 309 if (_SC_cfstring_to_cstring(address, buf, sizeof(buf), kCFStringEncodingASCII) == NULL) { 310 my_log(LOG_ERR, "could not convert [primary] address string"); 311 return FALSE; 312 } 313 314 if (_SC_string_to_sockaddr(buf, AF_UNSPEC, (void *)&addr, sizeof(addr)) == NULL) { 315 my_log(LOG_ERR, "could not convert [primary] address"); 316 return FALSE; 317 } 318 319 options = CFDictionaryCreateMutable(NULL, 320 0, 321 &kCFTypeDictionaryKeyCallBacks, 322 &kCFTypeDictionaryValueCallBacks); 323 data = CFDataCreate(NULL, (const UInt8 *)&addr.sa, addr.sa.sa_len); 324 CFDictionarySetValue(options, kSCNetworkReachabilityOptionPTRAddress, data); 325 CFRelease(data); 326 ptrTarget = SCNetworkReachabilityCreateWithOptions(NULL, options); 327 CFRelease(options); 328 if (ptrTarget == NULL) { 329 my_log(LOG_ERR, "could not resolve [primary] address"); 330 return FALSE; 331 } 332 333 (void) SCNetworkReachabilitySetCallback(ptrTarget, ptr_query_callback, NULL); 334 (void) SCNetworkReachabilityScheduleWithRunLoop(ptrTarget, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); 335 336 return TRUE; 337} 338 339 340static void 341update_hostname(SCDynamicStoreRef store, CFArrayRef changedKeys, void *info) 342{ 343 CFStringRef address = NULL; 344 CFStringRef hostname = NULL; 345 CFStringRef serviceID = NULL; 346 347 // if active, cancel any in-progress attempt to resolve the primary IP address 348 349 if (ptrTarget != NULL) { 350 ptr_query_stop(); 351 } 352 353 // get [prefs] hostname, if available 354 355 hostname = copy_prefs_hostname(store); 356 if (hostname != NULL) { 357 my_log(LOG_DEBUG, "hostname (prefs) = %@", hostname); 358 set_hostname(hostname); 359 goto done; 360 } 361 362 // get primary service ID 363 364 serviceID = copy_primary_service(store); 365 if (serviceID == NULL) { 366 goto mDNS; 367 } 368 369 // get DHCP provided name, if available 370 371 hostname = copy_dhcp_hostname(serviceID); 372 if (hostname != NULL) { 373 my_log(LOG_DEBUG, "hostname (DHCP) = %@", hostname); 374 set_hostname(hostname); 375 goto done; 376 } 377 378 // get DNS name associated with primary IP, if available 379 380 address = copy_primary_ip(store, serviceID); 381 if (address != NULL) { 382 Boolean ok; 383 384 // start reverse DNS query using primary IP address 385 ok = ptr_query_start(address); 386 if (ok) { 387 // if query started 388 goto done; 389 } 390 } 391 392 mDNS : 393 394 // get local (multicast DNS) name, if available 395 396 hostname = SCDynamicStoreCopyLocalHostName(store); 397 if (hostname != NULL) { 398 CFMutableStringRef localName; 399 400 my_log(LOG_DEBUG, "hostname (multicast DNS) = %@", hostname); 401 localName = CFStringCreateMutableCopy(NULL, 0, hostname); 402 assert(localName != NULL); 403 CFStringAppend(localName, CFSTR(".local")); 404 set_hostname(localName); 405 CFRelease(localName); 406 goto done; 407 } 408 409 // use "localhost" if not other name is available 410 411 set_hostname(CFSTR("localhost")); 412 413 done : 414 415 if (address) CFRelease(address); 416 if (hostname) CFRelease(hostname); 417 if (serviceID) CFRelease(serviceID); 418 419 return; 420} 421 422 423__private_extern__ 424void 425load_hostname(Boolean verbose) 426{ 427 CFStringRef key; 428 CFMutableArrayRef keys = NULL; 429 CFMutableArrayRef patterns = NULL; 430 431 if (verbose) { 432 _verbose = TRUE; 433 } 434 435 /* initialize a few globals */ 436 437 store = SCDynamicStoreCreate(NULL, CFSTR("set-hostname"), update_hostname, NULL); 438 if (store == NULL) { 439 my_log(LOG_ERR, 440 "SCDynamicStoreCreate() failed: %s", 441 SCErrorString(SCError())); 442 goto error; 443 } 444 445 /* establish notification keys and patterns */ 446 447 keys = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); 448 patterns = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); 449 450 /* ...watch for primary service / interface changes */ 451 key = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, 452 kSCDynamicStoreDomainState, 453 kSCEntNetIPv4); 454 CFArrayAppendValue(keys, key); 455 CFRelease(key); 456 457 /* ...watch for DNS configuration changes */ 458 key = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, 459 kSCDynamicStoreDomainState, 460 kSCEntNetDNS); 461 CFArrayAppendValue(keys, key); 462 CFRelease(key); 463 464 /* ...watch for (per-service) DHCP option changes */ 465 key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, 466 kSCDynamicStoreDomainState, 467 kSCCompAnyRegex, 468 kSCEntNetDHCP); 469 CFArrayAppendValue(patterns, key); 470 CFRelease(key); 471 472 /* ...watch for (BSD) hostname changes */ 473 key = SCDynamicStoreKeyCreateComputerName(NULL); 474 CFArrayAppendValue(keys, key); 475 CFRelease(key); 476 477 /* ...watch for local (multicast DNS) hostname changes */ 478 key = SCDynamicStoreKeyCreateHostNames(NULL); 479 CFArrayAppendValue(keys, key); 480 CFRelease(key); 481 482 /* register the keys/patterns */ 483 if (!SCDynamicStoreSetNotificationKeys(store, keys, patterns)) { 484 my_log(LOG_ERR, 485 "SCDynamicStoreSetNotificationKeys() failed: %s", 486 SCErrorString(SCError())); 487 goto error; 488 } 489 490 rls = SCDynamicStoreCreateRunLoopSource(NULL, store, 0); 491 if (!rls) { 492 my_log(LOG_ERR, 493 "SCDynamicStoreCreateRunLoopSource() failed: %s", 494 SCErrorString(SCError())); 495 goto error; 496 } 497 CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode); 498 499 CFRelease(keys); 500 CFRelease(patterns); 501 return; 502 503 error : 504 505 if (keys != NULL) CFRelease(keys); 506 if (patterns != NULL) CFRelease(patterns); 507 if (store != NULL) CFRelease(store); 508 return; 509} 510 511 512#ifdef MAIN 513int 514main(int argc, char **argv) 515{ 516 517#ifdef DEBUG 518 519 _sc_log = FALSE; 520 if ((argc > 1) && (strcmp(argv[1], "-d") == 0)) { 521 _sc_verbose = TRUE; 522 _verbose = TRUE; 523 argv++; 524 argc--; 525 } 526 527 CFStringRef address; 528 CFStringRef hostname; 529 CFStringRef serviceID; 530 SCDynamicStoreRef store; 531 532 store = SCDynamicStoreCreate(NULL, CFSTR("set-hostname"), NULL, NULL); 533 if (store == NULL) { 534 SCPrint(TRUE, stdout, 535 CFSTR("SCDynamicStoreCreate() failed: %s\n"), 536 SCErrorString(SCError())); 537 exit(1); 538 } 539 540 // get [prefs] hostname, if available 541 hostname = copy_prefs_hostname(store); 542 if (hostname != NULL) { 543 SCPrint(TRUE, stdout, CFSTR("hostname (prefs) = %@\n"), hostname); 544 CFRelease(hostname); 545 } 546 547 // get local (multicast DNS) name, if available 548 549 hostname = SCDynamicStoreCopyLocalHostName(store); 550 if (hostname != NULL) { 551 SCPrint(TRUE, stdout, CFSTR("hostname (multicast DNS) = %@\n"), hostname); 552 CFRelease(hostname); 553 } 554 555 // get primary service 556 serviceID = copy_primary_service(store); 557 if (serviceID != NULL) { 558 SCPrint(TRUE, stdout, CFSTR("primary service ID = %@\n"), serviceID); 559 } else { 560 SCPrint(TRUE, stdout, CFSTR("No primary service\n")); 561 } 562 563 if ((argc == (2+1)) && (argv[1][0] == 's')) { 564 if (serviceID != NULL) CFRelease(serviceID); 565 serviceID = CFStringCreateWithCString(NULL, argv[2], kCFStringEncodingUTF8); 566 SCPrint(TRUE, stdout, CFSTR("alternate service ID = %@\n"), serviceID); 567 } 568 569 if (serviceID != NULL) { 570 // get DHCP provided name 571 hostname = copy_dhcp_hostname(serviceID); 572 if (hostname != NULL) { 573 SCPrint(TRUE, stdout, CFSTR("hostname (DHCP) = %@\n"), hostname); 574 CFRelease(hostname); 575 } 576 577 // get primary IP address 578 address = copy_primary_ip(store, serviceID); 579 if (address != NULL) { 580 SCPrint(TRUE, stdout, CFSTR("primary address = %@\n"), address); 581 582 if ((argc == (2+1)) && (argv[1][0] == 'a')) { 583 if (address != NULL) CFRelease(address); 584 address = CFStringCreateWithCString(NULL, argv[2], kCFStringEncodingUTF8); 585 SCPrint(TRUE, stdout, CFSTR("alternate primary address = %@\n"), address); 586 } 587 588 // start reverse DNS query using primary IP address 589 (void) ptr_query_start(address); 590 CFRelease(address); 591 } 592 593 CFRelease(serviceID); 594 } 595 596 CFRelease(store); 597 598 CFRunLoopRun(); 599 600#else /* DEBUG */ 601 602 _sc_log = FALSE; 603 _sc_verbose = (argc > 1) ? TRUE : FALSE; 604 605 load_hostname((argc > 1) ? TRUE : FALSE); 606 CFRunLoopRun(); 607 /* not reached */ 608 609#endif /* DEBUG */ 610 611 exit(0); 612 return 0; 613} 614#endif /* MAIN */ 615