1/*
2 * Copyright (c) 2010-2013 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
25/*
26 * DHCPDUIDIAID.c
27 * - routines to set/access the DHCP client DUID and the IAIDs for particular
28 *   interfaces
29 */
30
31/*
32 * Modification History
33 *
34 * May 14, 2010
35 * - created
36 */
37
38#include <CoreFoundation/CFString.h>
39#include <SystemConfiguration/SCValidation.h>
40#include <SystemConfiguration/SCPrivate.h>
41#include <unistd.h>
42#include "util.h"
43#include "globals.h"
44#include "ipconfigd_globals.h"
45#include "cfutil.h"
46#include "HostUUID.h"
47#include "DHCPDUID.h"
48#include "DHCPDUIDIAID.h"
49
50#define DUID_IA_FILE		IPCONFIGURATION_PRIVATE_DIR "/DUID_IA.plist"
51
52#define kDUIDKey		CFSTR("DUID")		/* data */
53#define kIAIDListKey		CFSTR("IAIDList")	/* array[string] */
54#define kHostUUIDKey		CFSTR("HostUUID")	/* data */
55
56STATIC CFDataRef		S_DUID;
57STATIC CFMutableArrayRef	S_IAIDList;
58
59/*
60 * Function: seconds_since_Jan_1_2000
61 * Purpose:
62 *   Return the number of seconds since midnight (UTC), January 1, 2000, the
63 *   epoch for the DHCP DUID LLT.
64 */
65STATIC uint32_t
66S_seconds_since_Jan_1_2000(void)
67{
68    time_t		DHCPDUID_epoch;
69    uint32_t	      	seconds;
70    struct tm		tm;
71
72    bzero(&tm, sizeof(tm));
73    tm.tm_year = 100; 	/* 2000 (100 years since 1900) */
74    tm.tm_mon = 0;	/* January (0 months since January) */
75    tm.tm_mday = 1;	/* 1st (day of the month) */
76    DHCPDUID_epoch = timegm(&tm);
77    seconds = time(NULL) - DHCPDUID_epoch;
78    return (seconds);
79}
80
81STATIC void
82save_DUID_info(void)
83{
84    CFMutableDictionaryRef      duid_ia;
85    CFDataRef			host_UUID;
86
87    if (S_DUID == NULL) {
88	return;
89    }
90    duid_ia = CFDictionaryCreateMutable(NULL, 0,
91					&kCFTypeDictionaryKeyCallBacks,
92					&kCFTypeDictionaryValueCallBacks);
93    CFDictionarySetValue(duid_ia, kDUIDKey, S_DUID);
94    if (S_IAIDList != NULL) {
95	CFDictionarySetValue(duid_ia, kIAIDListKey, S_IAIDList);
96    }
97    host_UUID = HostUUIDGet();
98    if (host_UUID != NULL) {
99	CFDictionarySetValue(duid_ia, kHostUUIDKey, host_UUID);
100    }
101    if (my_CFPropertyListWriteFile(duid_ia, DUID_IA_FILE, 0644) < 0) {
102	/*
103	 * An ENOENT error is expected on a read-only filesystem.  All
104	 * other errors should be reported.
105	 */
106	if (errno != ENOENT) {
107	    my_log(LOG_ERR,
108		   "DHCPDUID: failed to write %s, %s", DUID_IA_FILE,
109		   strerror(errno));
110	}
111    }
112    CFRelease(duid_ia);
113    return;
114}
115
116STATIC bool
117load_DUID_info(void)
118{
119    CFDataRef		duid;
120    CFDictionaryRef	duid_ia;
121    CFDataRef		host_uuid;
122    CFArrayRef		ia_list;
123
124    duid_ia = my_CFPropertyListCreateFromFile(DUID_IA_FILE);
125    if (isA_CFDictionary(duid_ia) == NULL) {
126	goto done;
127    }
128    duid = CFDictionaryGetValue(duid_ia, kDUIDKey);
129    if (isA_CFData(duid) == NULL) {
130	goto done;
131    }
132    ia_list = CFDictionaryGetValue(duid_ia, kIAIDListKey);
133    ia_list = isA_CFArray(ia_list);
134    if (ia_list != NULL) {
135	int		count;
136	int		i;
137
138	count = CFArrayGetCount(ia_list);
139	for (i = 0; i < count; i++) {
140	    CFStringRef	name = CFArrayGetValueAtIndex(ia_list, i);
141	    if (isA_CFString(name) == NULL) {
142		/* invalid property */
143		ia_list = NULL;
144		break;
145	    }
146	}
147    }
148    host_uuid = CFDictionaryGetValue(duid_ia, kHostUUIDKey);
149    if (isA_CFData(host_uuid) != NULL
150	&& CFDataGetLength(host_uuid) == sizeof(uuid_t)) {
151	CFDataRef	our_UUID;
152
153	our_UUID = HostUUIDGet();
154	if (our_UUID != NULL && CFEqual(host_uuid, our_UUID) == FALSE) {
155	    syslog(LOG_NOTICE,
156		   "DHCPDUID: ignoring DUID - host UUID doesn't match");
157	    goto done;
158	}
159    }
160    S_DUID = CFRetain(duid);
161    if (ia_list != NULL) {
162	S_IAIDList = CFArrayCreateMutableCopy(NULL, 0, ia_list);
163    }
164
165 done:
166    my_CFRelease(&duid_ia);
167    return (S_DUID != NULL);
168}
169
170STATIC CFDataRef
171make_DUID_LL_data(interface_t * if_p)
172{
173    CFMutableDataRef	data;
174    int			duid_len;
175    DHCPDUID_LLRef	ll_p;
176
177    duid_len = offsetof(DHCPDUID_LL, linklayer_address) + if_link_length(if_p);
178    data = CFDataCreateMutable(NULL, duid_len);
179    CFDataSetLength(data, duid_len);
180    ll_p = (DHCPDUID_LLRef)CFDataGetMutableBytePtr(data);
181    DHCPDUIDSetType((DHCPDUIDRef)ll_p, kDHCPDUIDTypeLL);
182    DHCPDUID_LLSetHardwareType(ll_p, if_link_arptype(if_p));
183    bcopy(if_link_address(if_p), ll_p->linklayer_address,
184	  if_link_length(if_p));
185    return (data);
186}
187
188STATIC CFDataRef
189make_DUID_LLT_data(interface_t * if_p)
190{
191    CFMutableDataRef	data;
192    int			duid_len;
193    DHCPDUID_LLTRef	llt_p;
194
195    duid_len = offsetof(DHCPDUID_LLT, linklayer_address) + if_link_length(if_p);
196    data = CFDataCreateMutable(NULL, duid_len);
197    CFDataSetLength(data, duid_len);
198    llt_p = (DHCPDUID_LLTRef)CFDataGetMutableBytePtr(data);
199    DHCPDUIDSetType((DHCPDUIDRef)llt_p, kDHCPDUIDTypeLLT);
200    DHCPDUID_LLTSetHardwareType(llt_p, if_link_arptype(if_p));
201    bcopy(if_link_address(if_p), llt_p->linklayer_address,
202	  if_link_length(if_p));
203    DHCPDUID_LLTSetTime(llt_p, S_seconds_since_Jan_1_2000());
204    return (data);
205}
206
207PRIVATE_EXTERN CFDataRef
208DHCPDUIDGet(interface_list_t * interfaces)
209{
210    interface_t *		if_p;
211    interface_t *	       	if_with_linkaddr_p = NULL;
212
213    if (S_DUID != NULL) {
214	goto done;
215    }
216    /* try to load the DUID from filesystem */
217    if (G_is_netboot == FALSE && load_DUID_info()) {
218	goto done;
219    }
220    if (interfaces == NULL) {
221	goto done;
222    }
223    if_p = ifl_find_name(interfaces, "en0");
224    if (if_p == NULL) {
225	int		count;
226	int		i;
227
228	count = ifl_count(interfaces);
229	for (i = 0; i < count; i++) {
230	    interface_t *	scan = ifl_at_index(interfaces, i);
231
232	    switch (if_ift_type(scan)) {
233	    case IFT_ETHER:
234	    case IFT_IEEE1394:
235		break;
236	    default:
237		if (if_with_linkaddr_p == NULL
238		    && if_link_length(scan) > 0) {
239		    if_with_linkaddr_p = scan;
240		}
241		continue;
242	    }
243	    if (if_p == NULL) {
244		if_p = scan;
245	    }
246	    else if (strcmp(if_name(scan), if_name(if_p)) < 0) {
247		/* pick "lowest" named interface */
248		if_p = scan;
249	    }
250	}
251    }
252    if (if_p == NULL) {
253	if (G_dhcp_duid_type == kDHCPDUIDTypeLL || if_with_linkaddr_p == NULL) {
254	    my_log(LOG_ERR,
255		   "DHCPv6Client: can't determine interface for DUID");
256	    goto done;
257	}
258	if_p = if_with_linkaddr_p;
259    }
260    if (G_IPConfiguration_verbose) {
261	my_log(LOG_DEBUG, "DHCPv6Client: chose %s for DUID",
262	       if_name(if_p));
263    }
264    if (G_dhcp_duid_type == kDHCPDUIDTypeLL || G_is_netboot) {
265	S_DUID = make_DUID_LL_data(if_p);
266    }
267    else {
268	S_DUID = make_DUID_LLT_data(if_p);
269    }
270    save_DUID_info();
271
272 done:
273    return (S_DUID);
274}
275
276PRIVATE_EXTERN DHCPIAID
277DHCPIAIDGet(const char * ifname)
278{
279    int			count;
280    DHCPIAID 		iaid;
281    CFStringRef		ifname_cf;
282    CFIndex		where = kCFNotFound;
283
284    ifname_cf = CFStringCreateWithCString(NULL, ifname, kCFStringEncodingASCII);
285    if (S_IAIDList == NULL) {
286	S_IAIDList = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
287	count = 0;
288    }
289    else {
290	CFRange 	range;
291
292	count = CFArrayGetCount(S_IAIDList);
293	range = CFRangeMake(0, count);
294	where = CFArrayGetFirstIndexOfValue(S_IAIDList, range, ifname_cf);
295    }
296    if (where != kCFNotFound) {
297	iaid = where;
298    }
299    else {
300	CFArrayAppendValue(S_IAIDList, ifname_cf);
301	iaid = count;
302	save_DUID_info();
303    }
304    CFRelease(ifname_cf);
305    return (iaid);
306}
307
308#ifdef TEST_DHCPDUIDIAID
309
310Boolean		G_IPConfiguration_verbose = 1;
311int		G_dhcp_duid_type;
312boolean_t	G_is_netboot;
313
314int
315main(int argc, char * argv[])
316{
317    CFDataRef		duid;
318    int			i;
319    interface_list_t *	interfaces;
320    CFMutableStringRef 	str;
321
322    (void) openlog("DHCPDUIDIAID", LOG_PERROR | LOG_PID, LOG_DAEMON);
323    interfaces = ifl_init();
324
325    ipconfigd_create_paths();
326    duid = DHCPDUIDGet(interfaces);
327    if (duid == NULL) {
328	fprintf(stderr, "Couldn't determine DUID\n");
329	exit(1);
330    }
331
332    str = CFStringCreateMutable(NULL, 0);
333    DHCPDUIDPrintToString(str, (const DHCPDUIDRef)CFDataGetBytePtr(duid),
334			  CFDataGetLength(duid));
335    SCPrint(TRUE, stdout, CFSTR("%@\n"), str);
336    CFRelease(str);
337    if (argc > 1) {
338	for (i = 1; i < argc; i++) {
339	    DHCPIAID	iaid;
340
341	    iaid = DHCPIAIDGet(argv[i]);
342	    printf("%s = %d\n", argv[i], iaid);
343	}
344    }
345    exit(0);
346    return (0);
347}
348
349#endif /* TEST_DHCPDUIDIAID */
350