1/*
2 * Copyright (c) 2007-2011 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
24#include <string.h>
25#include <stdio.h>
26#include <unistd.h>
27#include <syslog.h>
28
29#include <sys/types.h>
30#include <sys/socket.h>
31#include <ifaddrs.h>
32#include <netdb.h>
33
34#include <CoreFoundation/CoreFoundation.h>
35#include <SystemConfiguration/SystemConfiguration.h>
36
37#include "autofs.h"
38#include "automount.h"
39
40static int hosts_match(const char *host, size_t hostlen, const char *thishost);
41static int get_local_host_name(char *localhost, size_t localhost_len);
42static int convert_to_write_lock(void);
43static void get_my_host_names(void);
44static void free_hostinfo_list(void);
45
46/*
47 * XXX - is self_check() sufficient for this?  It'll handle the "localhost"
48 * case (as 127.0.0.1 will be one of our addresses), and it should
49 * handle our primary host name, with or without qualifications.  We
50 * need to call it anyway, to handle multi-homing and .local hostnames,
51 * and if we can just use self_check(), that avoids a gethostname() system
52 * call and some compares.
53 *
54 * One problem with self_check() is that it can be expensive if you're
55 * using it on a lot of names, as it looks up the host name and compares
56 * all the IP addresses for that host name with all of the IP addresses
57 * for the machine.  If that's done for all the entries in the -fstab
58 * map, as would be the case for an "ls -l" done on /Network/Servers or
59 * if the Finder's looking at everything in /Network/Servers - as it would
60 * in column view when you're looking at anything under /Network/Servers -
61 * then, the first time that happens, it does a host name lookup for every
62 * server listed there, meaning it could do a DNS lookup for every such
63 * host.
64 *
65 * We use host_is_us() as a "fast path" check; instead of trying to look
66 * up the host name, we do a quick comparison against the result of
67 * gethostname() (the primary DNS host name) and against the result
68 * of SCDynamicStoreCopyLocalHostName() (the local host name) and
69 * "localhost", and then check whether it matches the result of a
70 * reverse lookup of any of our IP addresses.
71 *
72 * When we have a loopback file system, so we can use that for entries
73 * in -fstab that refer to us, rather than making those entries a symlink
74 * to /, we should be able to avoid this hack.
75 */
76
77static u_int num_hostinfo;
78static struct hostent **hostinfo_list;
79static u_int num_host_names;
80static char **my_host_names;
81static int have_my_host_names;
82
83/*
84 * Read/write lock on the host name information.
85 */
86static pthread_rwlock_t host_name_cache_lock = PTHREAD_RWLOCK_INITIALIZER;
87
88int
89host_is_us(const char *host, size_t hostlen)
90{
91	int err;
92	static const char localhost[] = "localhost";
93	static char ourhostname[MAXHOSTNAMELEN];
94	static char ourlocalhostname[MAXHOSTNAMELEN];
95	size_t ourlocalhostnamelen;
96	u_int i;
97
98	/*
99	 * This is, by definition, us.
100	 */
101	if (hostlen == sizeof localhost - 1 &&
102	    strncasecmp(host, localhost, sizeof localhost - 1) == 0)
103		return (1);
104
105	/*
106	 * Get our hostname, and compare the counted string we were
107	 * handed with the host name - and the first component of
108	 * the host name, if it has more than one component.
109	 *
110	 * For now, we call gethostname() every time, as that
111	 * should be reasonably cheap and it avoids us having
112	 * to catch notifications for the host name changing.
113	 */
114	if (gethostname(ourhostname, sizeof ourhostname) == 0) {
115		if (hosts_match(host, hostlen, ourhostname))
116			return (1);
117	}
118
119	/*
120	 * Try to get the local host name.  If that works, compare the
121	 * counted string we were handed with the host name.
122	 *
123	 * For now, we call get_local_host_name() every time, as
124	 * that should be reasonably cheap (although, if it contacts
125	 * configd, it's probably not as cheap as gethostname())
126	 * and it avoids us having to catch notifications for the
127	 * local host name changing.
128	 */
129	if (get_local_host_name(ourlocalhostname, sizeof ourlocalhostname)
130	    == 0) {
131		ourlocalhostnamelen = strlen(ourlocalhostname);
132		if (hostlen == ourlocalhostnamelen &&
133		    strncasecmp(host, ourlocalhostname, ourlocalhostnamelen)
134		       == 0)
135			return (1);
136	}
137
138	/*
139	 * Now we check against all the host names for this host.
140	 * We cache those, as that's potentially a bit more
141	 * expensive; we do flush that cache if we get a
142	 * cache flush notification from automount, as it gets
143	 * run with the "-c" flag whenever there's a network
144	 * change, so that should be sufficient to catch changes
145	 * in our IP addresses.
146	 */
147
148	/*
149	 * Get a read lock, so the cache doesn't get modified out
150	 * from under us.
151	 */
152	err = pthread_rwlock_rdlock(&host_name_cache_lock);
153	if (err != 0) {
154		pr_msg("Can't get read lock on host name cache: %s",
155		    strerror(err));
156		return (0);
157	}
158
159	/*
160	 * OK, now get the names for all the IP addresses for this host.
161	 */
162	if (!have_my_host_names) {
163		/*
164		 * We have to get the local host name; convert the read lock
165		 * to a write lock, if we haven't done so already.
166		 */
167		if (!convert_to_write_lock()) {
168			/* convert_to_write_lock() released the read lock */
169			return (0);
170		}
171		get_my_host_names();
172	}
173
174	/*
175	 * Check against all of those names.
176	 */
177	if (have_my_host_names) {
178		for (i = 0; i < num_host_names; i++) {
179			if (hosts_match(host, hostlen, my_host_names[i])) {
180				pthread_rwlock_unlock(&host_name_cache_lock);
181				return (1);
182			}
183		}
184	}
185
186	/* Not us. */
187	pthread_rwlock_unlock(&host_name_cache_lock);
188	return (0);
189}
190
191static int
192hosts_match(const char *host, size_t hostlen, const char *matchhost)
193{
194	size_t matchhost_len;
195	const char *p;
196
197	matchhost_len = strlen(matchhost);
198	if (hostlen == matchhost_len &&
199	    strncasecmp(host, matchhost, matchhost_len) == 0)
200		return (1);
201
202	/*
203	 * Compare the counted string we were handed with the first
204	 * component of the host name, if it has more than one component.
205	 */
206	p = strchr(matchhost, '.');
207	if (p != NULL) {
208		matchhost_len = p - matchhost;
209		if (hostlen == matchhost_len &&
210		    strncasecmp(host, matchhost, matchhost_len) == 0)
211			return(1);
212	}
213
214	return (0);
215}
216
217static int
218get_local_host_name(char *ourlocalhostname, size_t ourlocalhostnamelen)
219{
220	SCDynamicStoreRef store;
221	CFStringRef ourlocalhostname_CFString;
222	Boolean ret;
223
224	store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("automountd"),
225	    NULL, NULL);
226	if (store == NULL)
227		return (-1);
228
229	ourlocalhostname_CFString = SCDynamicStoreCopyLocalHostName(store);
230	CFRelease(store);
231	if (ourlocalhostname_CFString == NULL)
232		return (-1);
233
234	ret = CFStringGetCString(ourlocalhostname_CFString, ourlocalhostname,
235	    ourlocalhostnamelen, kCFStringEncodingUTF8);
236	CFRelease(ourlocalhostname_CFString);
237	if (!ret)
238		return (-1);
239
240	/*
241	 * That won't have ".local" at the end; add it.
242	 */
243	if (strlcat(ourlocalhostname, ".local", ourlocalhostnamelen)
244	    >= ourlocalhostnamelen)
245		return (-1);	/* didn't fit in the buffer */
246	return (0);
247}
248
249static int
250convert_to_write_lock(void)
251{
252	int err;
253
254	pthread_rwlock_unlock(&host_name_cache_lock);
255	err = pthread_rwlock_wrlock(&host_name_cache_lock);
256	if (err != 0) {
257		pr_msg("Error attempting to get write lock on host name cache: %s",
258		    strerror(err));
259		return (0);
260	}
261	return (1);
262}
263
264static void
265get_my_host_names(void)
266{
267	struct ifaddrs *ifaddrs, *ifaddr;
268	struct sockaddr *addr;
269	struct sockaddr_in *addr_in;
270#if 0
271	struct sockaddr_in6 *addr_in6;
272#endif
273	int error_num;
274	struct hostent **hostinfop;
275	struct hostent *hostinfo;
276	u_int i;
277	char **host_namep;
278	char **aliasp;
279
280	/*
281	 * If we already have the list of host names, presumably
282	 * that was fetched by another thread in between releasing
283	 * the read lock on the list and getting the write lock;
284	 * just return.  (have_my_host_names is modified only
285	 * when the write lock is held.)
286	 */
287	if (have_my_host_names)
288		return;
289
290	if (getifaddrs(&ifaddrs) == -1) {
291		pr_msg("getifaddrs failed: %s\n", strerror(errno));
292		return;
293	}
294
295	/*
296	 * What's the maximum number of hostinfo structures we'd have?
297	 * (This counts all IPv4 and IPv6 addresses; we might not be
298	 * able to get information for some of them, so we won't
299	 * necessarily store hostinfo pointers for all of them.)
300	 */
301	num_hostinfo = 0;
302	for (ifaddr = ifaddrs; ifaddr != NULL; ifaddr = ifaddr->ifa_next) {
303		addr = ifaddr->ifa_addr;
304		switch (addr->sa_family) {
305
306		case AF_INET:
307		case AF_INET6:
308			num_hostinfo++;
309			break;
310
311		default:
312			break;
313		}
314	}
315
316	/*
317	 * Allocate the array of hostinfo structures.
318	 */
319	hostinfo_list = malloc(num_hostinfo * sizeof *hostinfo_list);
320	if (hostinfo_list == NULL) {
321		freeifaddrs(ifaddrs);
322		pr_msg("Couldn't allocate array of hostinfo pointers\n");
323		return;
324	}
325
326	/*
327	 * Fill in the array of hostinfo pointers, and count how many
328	 * hostinfo pointers and host names we have.
329	 */
330	hostinfop = hostinfo_list;
331	num_hostinfo = 0;
332	num_host_names = 0;
333	for (ifaddr = ifaddrs; ifaddr != NULL; ifaddr = ifaddr->ifa_next) {
334		addr = ifaddr->ifa_addr;
335		switch (addr->sa_family) {
336
337		case AF_INET:
338			addr_in = (struct sockaddr_in *)addr;
339			hostinfo = getipnodebyaddr(&addr_in->sin_addr,
340			    sizeof addr_in->sin_addr, addr->sa_family,
341			    &error_num);
342			break;
343
344#if 0	// until IPv6 reverse-DNS lookups are fixed - 8650817
345		case AF_INET6:
346			addr_in6 = (struct sockaddr_in6 *)addr;
347			hostinfo = getipnodebyaddr(&addr_in6->sin6_addr,
348			    sizeof addr_in6->sin6_addr, addr->sa_family,
349			    &error_num);
350			break;
351#endif
352
353		default:
354			hostinfo = NULL;
355			break;
356		}
357		if (hostinfo != NULL) {
358			*hostinfop++ = hostinfo;
359			num_hostinfo++;
360			num_host_names++;		/* main name */
361			for (aliasp = hostinfo->h_aliases; *aliasp != NULL;
362			    aliasp++)
363				num_host_names++;	/* alias */
364		}
365	}
366	freeifaddrs(ifaddrs);
367
368	/*
369	 * Allocate the array of host name pointers.
370	 */
371	my_host_names = malloc(num_host_names * sizeof *my_host_names);
372	if (my_host_names == NULL) {
373		free_hostinfo_list();
374		pr_msg("Couldn't allocate array of host name pointers\n");
375		return;
376	}
377
378	/*
379	 * Fill in the array of host name pointers.
380	 */
381	host_namep = my_host_names;
382	for (i = 0; i < num_hostinfo; i++) {
383		hostinfo = hostinfo_list[i];
384		*host_namep++ = hostinfo->h_name;
385		for (aliasp = hostinfo->h_aliases; *aliasp != NULL; aliasp++)
386			*host_namep++ = *aliasp;
387	}
388
389	have_my_host_names = 1;
390}
391
392static void
393free_hostinfo_list(void)
394{
395	u_int i;
396
397	for (i = 0; i < num_hostinfo; i++)
398		freehostent(hostinfo_list[i]);
399	free(hostinfo_list);
400	hostinfo_list = NULL;
401	num_hostinfo = 0;
402}
403
404void
405flush_host_name_cache(void)
406{
407	int err;
408
409	err = pthread_rwlock_wrlock(&host_name_cache_lock);
410	if (err != 0) {
411		pr_msg("Error attempting to get write lock on host name cache: %s",
412		    strerror(err));
413		return;
414	}
415
416	/*
417	 * Discard the host information, if we have any.
418	 */
419	if (have_my_host_names) {
420		free_hostinfo_list();
421		free(my_host_names);
422		my_host_names = NULL;
423		num_host_names = 0;
424		have_my_host_names = 0;
425	}
426
427	pthread_rwlock_unlock(&host_name_cache_lock);
428}
429