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