/* * Copyright (c) 2010, 2011 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * DHCPDUIDIAID.c * - routines to set/access the DHCP client DUID and the IAIDs for particular * interfaces */ /* * Modification History * * May 14, 2010 * - created */ #include #include #include #include "util.h" #include "globals.h" #include "dhcp_thread.h" #include "cfutil.h" #include "DHCPDUID.h" #include "DHCPDUIDIAID.h" #define DUID_IA_FILE DHCPCLIENT_DIR "/DUID_IA.plist" #define kDUIDKey CFSTR("DUID") /* data */ #define kIAIDListKey CFSTR("IAIDList") /* array[string] */ #define kHostUUIDKey CFSTR("HostUUID") /* data */ STATIC CFDataRef S_DUID; STATIC CFMutableArrayRef S_IAIDList; /* * Function: seconds_since_Jan_1_2000 * Purpose: * Return the number of seconds since midnight (UTC), January 1, 2000, the * epoch for the DHCP DUID LLT. */ STATIC uint32_t S_seconds_since_Jan_1_2000(void) { time_t DHCPDUID_epoch; uint32_t seconds; struct tm tm; bzero(&tm, sizeof(tm)); tm.tm_year = 100; /* 2000 (100 years since 1900) */ tm.tm_mon = 0; /* January (0 months since January) */ tm.tm_mday = 1; /* 1st (day of the month) */ DHCPDUID_epoch = timegm(&tm); seconds = time(NULL) - DHCPDUID_epoch; return (seconds); } static void create_dhcpclient_path(void) { static int done = 0; if (done != 0) { return; } if (create_path(DHCPCLIENT_DIR, 0700) < 0) { my_log(LOG_DEBUG, "failed to create " DHCPCLIENT_DIR ", %s (%d)", strerror(errno), errno); return; } done = 1; return; } STATIC CFDataRef get_host_UUID(void) { STATIC CFMutableDataRef host_UUID; struct timespec ts = { 0, 0 }; if (host_UUID != NULL) { return (host_UUID); } host_UUID = CFDataCreateMutable(NULL, sizeof(uuid_t)); CFDataSetLength(host_UUID, sizeof(uuid_t)); if (gethostuuid(CFDataGetMutableBytePtr(host_UUID), &ts) != 0) { my_CFRelease(&host_UUID); } return (host_UUID); } STATIC void save_DUID_info(void) { CFMutableDictionaryRef duid_ia; CFDataRef host_UUID; if (S_DUID == NULL) { return; } duid_ia = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFDictionarySetValue(duid_ia, kDUIDKey, S_DUID); if (S_IAIDList != NULL) { CFDictionarySetValue(duid_ia, kIAIDListKey, S_IAIDList); } host_UUID = get_host_UUID(); if (host_UUID != NULL) { CFDictionarySetValue(duid_ia, kHostUUIDKey, host_UUID); } create_dhcpclient_path(); if (my_CFPropertyListWriteFile(duid_ia, DUID_IA_FILE) < 0) { /* * An ENOENT error is expected on a read-only filesystem. All * other errors should be reported. */ if (errno != ENOENT) { my_log(LOG_NOTICE, "DHCPDUID: failed to write " DUID_IA_FILE ", %s", strerror(errno)); } } CFRelease(duid_ia); return; } STATIC bool load_DUID_info(void) { CFDataRef duid; CFDictionaryRef duid_ia; CFDataRef host_uuid; CFArrayRef ia_list; duid_ia = my_CFPropertyListCreateFromFile(DUID_IA_FILE); if (isA_CFDictionary(duid_ia) == NULL) { goto done; } duid = CFDictionaryGetValue(duid_ia, kDUIDKey); if (isA_CFData(duid) == NULL) { goto done; } ia_list = CFDictionaryGetValue(duid_ia, kIAIDListKey); ia_list = isA_CFArray(ia_list); if (ia_list != NULL) { int count; int i; count = CFArrayGetCount(ia_list); for (i = 0; i < count; i++) { CFStringRef name = CFArrayGetValueAtIndex(ia_list, i); if (isA_CFString(name) == NULL) { /* invalid property */ ia_list = NULL; break; } } } host_uuid = CFDictionaryGetValue(duid_ia, kHostUUIDKey); if (isA_CFData(host_uuid) != NULL && CFDataGetLength(host_uuid) == sizeof(uuid_t)) { CFDataRef our_UUID; our_UUID = get_host_UUID(); if (our_UUID != NULL && CFEqual(host_uuid, our_UUID) == FALSE) { syslog(LOG_NOTICE, "DHCPDUID: ignoring DUID - host UUID doesn't match"); goto done; } } S_DUID = CFRetain(duid); if (ia_list != NULL) { S_IAIDList = CFArrayCreateMutableCopy(NULL, 0, ia_list); } done: my_CFRelease(&duid_ia); return (S_DUID != NULL); } STATIC CFDataRef make_DUID_LL_data(interface_t * if_p) { CFMutableDataRef data; int duid_len; DHCPDUID_LLRef ll_p; duid_len = offsetof(DHCPDUID_LL, linklayer_address) + if_link_length(if_p); data = CFDataCreateMutable(NULL, duid_len); CFDataSetLength(data, duid_len); ll_p = (DHCPDUID_LLRef)CFDataGetMutableBytePtr(data); DHCPDUIDSetType((DHCPDUIDRef)ll_p, kDHCPDUIDTypeLL); DHCPDUID_LLSetHardwareType(ll_p, if_link_arptype(if_p)); bcopy(if_link_address(if_p), ll_p->linklayer_address, if_link_length(if_p)); return (data); } STATIC CFDataRef make_DUID_LLT_data(interface_t * if_p) { CFMutableDataRef data; int duid_len; DHCPDUID_LLTRef llt_p; duid_len = offsetof(DHCPDUID_LLT, linklayer_address) + if_link_length(if_p); data = CFDataCreateMutable(NULL, duid_len); CFDataSetLength(data, duid_len); llt_p = (DHCPDUID_LLTRef)CFDataGetMutableBytePtr(data); DHCPDUIDSetType((DHCPDUIDRef)llt_p, kDHCPDUIDTypeLLT); DHCPDUID_LLTSetHardwareType(llt_p, if_link_arptype(if_p)); bcopy(if_link_address(if_p), llt_p->linklayer_address, if_link_length(if_p)); DHCPDUID_LLTSetTime(llt_p, S_seconds_since_Jan_1_2000()); return (data); } PRIVATE_EXTERN CFDataRef DHCPDUIDGet(interface_list_t * interfaces) { interface_t * if_p; interface_t * if_with_linkaddr_p = NULL; if (S_DUID != NULL) { goto done; } /* try to load the DUID from filesystem */ if (G_is_netboot == FALSE && load_DUID_info()) { goto done; } if (interfaces == NULL) { goto done; } if_p = ifl_find_name(interfaces, "en0"); if (if_p == NULL) { int count; int i; count = ifl_count(interfaces); for (i = 0; i < count; i++) { interface_t * scan = ifl_at_index(interfaces, i); switch (if_ift_type(scan)) { case IFT_ETHER: case IFT_IEEE1394: break; default: if (if_with_linkaddr_p == NULL && if_link_length(scan) > 0) { if_with_linkaddr_p = scan; } continue; } if (if_p == NULL) { if_p = scan; } else if (strcmp(if_name(scan), if_name(if_p)) < 0) { /* pick "lowest" named interface */ if_p = scan; } } } if (if_p == NULL) { if (G_dhcp_duid_type == kDHCPDUIDTypeLL || if_with_linkaddr_p == NULL) { my_log(LOG_NOTICE, "DHCPv6Client: can't determine interface for DUID"); goto done; } if_p = if_with_linkaddr_p; } if (G_IPConfiguration_verbose) { my_log(LOG_NOTICE, "DHCPv6Client: chose %s for DUID", if_name(if_p)); } if (G_dhcp_duid_type == kDHCPDUIDTypeLL || G_is_netboot) { S_DUID = make_DUID_LL_data(if_p); } else { S_DUID = make_DUID_LLT_data(if_p); } save_DUID_info(); done: return (S_DUID); } PRIVATE_EXTERN DHCPIAID DHCPIAIDGet(const char * ifname) { int count; DHCPIAID iaid; CFStringRef ifname_cf; CFIndex where = kCFNotFound; ifname_cf = CFStringCreateWithCString(NULL, ifname, kCFStringEncodingASCII); if (S_IAIDList == NULL) { S_IAIDList = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); count = 0; } else { CFRange range; count = CFArrayGetCount(S_IAIDList); range = CFRangeMake(0, count); where = CFArrayGetFirstIndexOfValue(S_IAIDList, range, ifname_cf); } if (where != kCFNotFound) { iaid = where; } else { CFArrayAppendValue(S_IAIDList, ifname_cf); iaid = count; save_DUID_info(); } CFRelease(ifname_cf); return (iaid); } #ifdef TEST_DHCPDUIDIAID int G_IPConfiguration_verbose = 1; int G_dhcp_duid_type; boolean_t G_is_netboot; int main(int argc, char * argv[]) { int i; CFDataRef duid; interface_list_t * interfaces; (void) openlog("DHCPDUIDIAID", LOG_PERROR | LOG_PID, LOG_DAEMON); interfaces = ifl_init(); duid = DHCPDUIDGet(interfaces); if (duid == NULL) { fprintf(stderr, "Couldn't determine DUID\n"); exit(1); } DHCPDUIDFPrint(stdout, (const DHCPDUIDRef)CFDataGetBytePtr(duid), CFDataGetLength(duid)); printf("\n"); if (argc > 1) { for (i = 1; i < argc; i++) { DHCPIAID iaid; iaid = DHCPIAIDGet(argv[i]); printf("%s = %d\n", argv[i], iaid); } } exit(0); return (0); } #endif /* TEST_DHCPDUIDIAID */