#include <CoreFoundation/CFString.h>
#include <SystemConfiguration/SCValidation.h>
#include <unistd.h>
#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")
#define kIAIDListKey CFSTR("IAIDList")
#define kHostUUIDKey CFSTR("HostUUID")
STATIC CFDataRef S_DUID;
STATIC CFMutableArrayRef S_IAIDList;
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;
tm.tm_mon = 0;
tm.tm_mday = 1;
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) {
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) {
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;
}
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) {
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