--- /dev/null
+/*
+ * Copyright (c) 2005-2007 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@
+ */
+
+/*
+ * NetworkIdentification.c
+ * - maintains a history of networks that the system has connected to by
+ * watching the Network Services that post data to the SCDynamicStore
+ */
+
+/*
+ * Modification History
+ *
+ * November 9, 2006 Dieter Siegmund (dieter@apple.com)
+ * - created
+ */
+
+#include <notify.h>
+#include <SystemConfiguration/SystemConfiguration.h>
+#include <SystemConfiguration/SCValidation.h>
+#include <SystemConfiguration/SCPrivate.h>
+#include <CoreFoundation/CFDictionary.h>
+#include <SystemConfiguration/SCNetworkSignature.h>
+#include <SystemConfiguration/SCNetworkSignaturePrivate.h>
+
+/* debug output on/off */
+static Boolean S_NetworkIdentification_debug;
+
+/* should we bother keeping track of networks? */
+static Boolean S_NetworkIdentification_disabled;
+
+typedef struct ServiceWatcher_s ServiceWatcher, * ServiceWatcherRef;
+
+/* returns an array of currently available information */
+static CFArrayRef
+ServiceWatcherCopyCurrent(ServiceWatcherRef watcher);
+
+static ServiceWatcherRef
+ServiceWatcherCreate();
+
+static void
+ServiceWatcherFree(ServiceWatcherRef * watcher_p);
+
+/* XXX these should be made tunable */
+#define SIGNATURE_HISTORY_MAX 150
+#define SERVICE_HISTORY_MAX 5
+
+/* don't re-write the prefs file unless this time interval has elapsed */
+#define SIGNATURE_UPDATE_INTERVAL_SECS (24 * 3600) /* 24 hours */
+
+struct ServiceWatcher_s {
+ CFRunLoopSourceRef rls;
+ SCDynamicStoreRef store;
+ CFMutableArrayRef signatures;
+ CFArrayRef active_signatures;
+ CFStringRef primary_ipv4;
+ CFStringRef setup_ipv4_key;
+ CFStringRef state_ipv4_key;
+};
+
+#define kIdentifier CFSTR("Identifier")
+#define kService CFSTR("Service")
+#define kServices CFSTR("Services")
+#define kSignature CFSTR("Signature")
+#define kSignatures CFSTR("Signatures")
+#define kTimestamp CFSTR("Timestamp")
+#define kServiceID CFSTR("ServiceID")
+#define kNetworkSignature CFSTR("NetworkSignature")
+#define kServiceIdentifiers kStoreKeyServiceIdentifiers
+
+static CFArrayRef
+make_service_entity_pattern_array(CFStringRef * keys, int n_keys)
+{
+ int i;
+ CFArrayRef list;
+
+ for (i = 0; i < n_keys; i++) {
+ /* re-use the array that was passed in to get the pattern */
+ keys[i] = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
+ kSCDynamicStoreDomainState,
+ kSCCompAnyRegex,
+ keys[i]);
+ }
+ list = CFArrayCreate(NULL, (const void * *)keys, n_keys,
+ &kCFTypeArrayCallBacks);
+ for (i = 0; i < n_keys; i++) {
+ /* then release the allocated patterns */
+ CFRelease(keys[i]);
+ }
+ return (list);
+}
+
+static CFArrayRef
+ServiceWatcherNotificationPatterns(void)
+{
+ CFStringRef keys[1] = { kSCEntNetIPv4 };
+
+ return (make_service_entity_pattern_array(keys,
+ sizeof(keys) / sizeof(keys[0])));
+}
+
+static CFArrayRef
+ServiceWatcherPatterns(void)
+{
+ CFStringRef keys[2] = { kSCEntNetIPv4, kSCEntNetDNS };
+
+ return (make_service_entity_pattern_array(keys,
+ sizeof(keys) / sizeof(keys[0])));
+}
+
+static CFTypeRef
+myCFDictionaryArrayGetValue(CFArrayRef array, CFStringRef key, CFTypeRef value,
+ int * ret_index)
+{
+ int count = 0;
+ int i;
+
+ if (array != NULL) {
+ count = CFArrayGetCount(array);
+ }
+ if (count == 0) {
+ goto done;
+ }
+ for (i = 0; i < count; i++) {
+ CFDictionaryRef dict;
+ CFTypeRef this_val;
+
+ dict = CFArrayGetValueAtIndex(array, i);
+ if (isA_CFDictionary(dict) == NULL) {
+ continue;
+ }
+ this_val = CFDictionaryGetValue(dict, key);
+ if (CFEqual(this_val, value)) {
+ if (ret_index != NULL) {
+ *ret_index = i;
+ }
+ return (dict);
+ }
+ }
+ done:
+ if (ret_index != NULL) {
+ *ret_index = -1;
+ }
+ return (NULL);
+}
+
+static CFDictionaryRef
+copy_airport_dict(SCDynamicStoreRef store, CFStringRef if_name)
+{
+ CFDictionaryRef dict;
+ CFStringRef key;
+
+ key = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL,
+ kSCDynamicStoreDomainState,
+ if_name,
+ kSCEntNetAirPort);
+ dict = SCDynamicStoreCopyValue(store, key);
+ CFRelease(key);
+ return (dict);
+}
+
+static void
+add_airport_info(SCDynamicStoreRef store, CFMutableDictionaryRef dict)
+{
+ CFDictionaryRef airport_dict = NULL;
+ CFStringRef key;
+ CFStringRef if_name;
+ CFDictionaryRef simple_dict;
+ CFStringRef value;
+
+ if_name = CFDictionaryGetValue(dict, kSCPropInterfaceName);
+ if (isA_CFString(if_name) == NULL) {
+ goto done;
+ }
+ airport_dict = copy_airport_dict(store, if_name);
+ if (airport_dict == NULL) {
+ goto done;
+ }
+ key = CFSTR("SSID");
+ value = CFDictionaryGetValue(airport_dict, key);
+ if (value == NULL) {
+ goto done;
+ }
+ simple_dict =
+ CFDictionaryCreate(NULL,
+ (const void * *)&key, (const void * *)&value, 1,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ CFDictionarySetValue(dict, kSCEntNetAirPort, simple_dict);
+ CFRelease(simple_dict);
+
+ done:
+ if (airport_dict != NULL) {
+ CFRelease(airport_dict);
+ }
+ return;
+}
+
+static CFDictionaryRef
+get_current_dict(CFDictionaryRef current, CFStringRef entity,
+ CFArrayRef components)
+{
+ CFDictionaryRef dict;
+ CFStringRef key;
+
+ if (CFArrayGetCount(components) < 5) {
+ /* this can't happen, we already checked */
+ return (NULL);
+ }
+ key = CFStringCreateWithFormat(NULL, NULL,
+ CFSTR("%@/%@/%@/%@/%@"),
+ CFArrayGetValueAtIndex(components, 0),
+ CFArrayGetValueAtIndex(components, 1),
+ CFArrayGetValueAtIndex(components, 2),
+ CFArrayGetValueAtIndex(components, 3),
+ entity);
+ dict = CFDictionaryGetValue(current, key);
+ CFRelease(key);
+ return (isA_CFDictionary(dict));
+}
+
+static CFArrayRef
+process_dict(SCDynamicStoreRef store, CFDictionaryRef current)
+{
+ CFMutableArrayRef array = NULL;
+ int count = 0;
+ int i;
+ const void * * keys = NULL;
+ const void * * values = NULL;
+
+ count = CFDictionaryGetCount(current);
+ if (count == 0) {
+ goto done;
+ }
+ array = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ keys = (const void * *)malloc(sizeof(keys) * count);
+ values = (const void * *)malloc(sizeof(values) * count);
+ CFDictionaryGetKeysAndValues(current, keys, values);
+ for (i = 0; i < count; i++) {
+ CFArrayRef components = NULL;
+ CFDictionaryRef dns_dict;
+ CFStringRef entity;
+ CFMutableDictionaryRef entity_dict = NULL;
+ CFMutableDictionaryRef new_dict = NULL;
+ CFStringRef sig_str = NULL;
+ CFMutableDictionaryRef service_dict = NULL;
+ CFStringRef serviceID;
+
+ if (isA_CFDictionary(values[i]) == NULL) {
+ goto loop_done;
+ }
+ components = CFStringCreateArrayBySeparatingStrings(NULL, keys[i],
+ CFSTR("/"));
+ if (components == NULL) {
+ goto loop_done;
+ }
+ if (CFArrayGetCount(components) < 5) {
+ /* too few components */
+ goto loop_done;
+ }
+ entity = CFArrayGetValueAtIndex(components, 4);
+ if (!CFEqual(entity, kSCEntNetIPv4)) {
+ goto loop_done;
+ }
+ serviceID = CFArrayGetValueAtIndex(components, 3);
+ sig_str = CFDictionaryGetValue(values[i], kNetworkSignature);
+ if (isA_CFString(sig_str) == NULL
+ || CFStringGetLength(sig_str) == 0) {
+ goto loop_done;
+ }
+ /* create a new entry */
+ new_dict = CFDictionaryCreateMutable(NULL, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ CFDictionarySetValue(new_dict, kSignature, sig_str);
+ service_dict = CFDictionaryCreateMutable(NULL, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ CFDictionarySetValue(service_dict, kServiceID, serviceID);
+ add_airport_info(store, service_dict);
+ entity_dict = CFDictionaryCreateMutableCopy(NULL, 0, values[i]);
+ CFDictionaryRemoveValue(entity_dict, kNetworkSignature);
+ CFDictionarySetValue(service_dict, kSCEntNetIPv4, entity_dict);
+ dns_dict = get_current_dict(current, kSCEntNetDNS, components);
+ if (dns_dict != NULL) {
+ CFDictionarySetValue(service_dict, kSCEntNetDNS, dns_dict);
+ }
+ CFDictionarySetValue(new_dict, kService, service_dict);
+ CFArrayAppendValue(array, new_dict);
+
+ loop_done:
+ if (entity_dict != NULL) {
+ CFRelease(entity_dict);
+ }
+ if (service_dict != NULL) {
+ CFRelease(service_dict);
+ }
+ if (components != NULL) {
+ CFRelease(components);
+ }
+ if (new_dict != NULL) {
+ CFRelease(new_dict);
+ }
+ }
+ count = CFArrayGetCount(array);
+ if (count == 0) {
+ CFRelease(array);
+ array = NULL;
+ goto done;
+ }
+
+ done:
+ if (keys != NULL) {
+ free(keys);
+ }
+ if (values != NULL) {
+ free(values);
+ }
+ return (array);
+
+}
+
+static CFArrayRef
+ServiceWatcherCopyCurrent(ServiceWatcherRef watcher)
+{
+ CFDictionaryRef current;
+ CFArrayRef list;
+ CFArrayRef ret = NULL;
+
+ list = ServiceWatcherPatterns();
+ current = SCDynamicStoreCopyMultiple(watcher->store, NULL, list);
+ CFRelease(list);
+ if (current == NULL) {
+ goto done;
+ }
+ ret = process_dict(watcher->store, current);
+ done:
+ if (current != NULL) {
+ CFRelease(current);
+ }
+ return (ret);
+}
+
+static Boolean
+ServiceWatcherSetActiveSignatures(ServiceWatcherRef watcher, CFArrayRef active)
+{
+ Boolean changed = FALSE;
+ CFArrayRef prev_active;
+
+ prev_active = watcher->active_signatures;
+ if (prev_active == NULL && active == NULL) {
+ /* nothing to do */
+ goto done;
+ }
+ if (prev_active != NULL && active != NULL) {
+ changed = !CFEqual(prev_active, active);
+ }
+ else {
+ changed = TRUE;
+ }
+ if (active != NULL) {
+ CFRetain(active);
+ }
+ if (prev_active != NULL) {
+ CFRelease(prev_active);
+ }
+ watcher->active_signatures = active;
+ if (changed) {
+ if (active != NULL) {
+ SCLog(S_NetworkIdentification_debug,
+ LOG_NOTICE, CFSTR("Active Signatures %@"), active);
+ }
+ else {
+ SCLog(S_NetworkIdentification_debug,
+ LOG_NOTICE, CFSTR("No Active Signatures"));
+ }
+ }
+ done:
+ return (changed);
+}
+
+static Boolean
+ServiceWatcherSetPrimaryIPv4(ServiceWatcherRef watcher,
+ CFStringRef primary_ipv4)
+{
+ Boolean changed = FALSE;
+ CFStringRef prev_ipv4_primary;
+
+ prev_ipv4_primary = watcher->primary_ipv4;
+ if (prev_ipv4_primary == NULL && primary_ipv4 == NULL) {
+ /* nothing to do */
+ goto done;
+ }
+ if (prev_ipv4_primary != NULL && primary_ipv4 != NULL) {
+ changed = !CFEqual(prev_ipv4_primary, primary_ipv4);
+ }
+ else {
+ changed = TRUE;
+ }
+ if (primary_ipv4 != NULL) {
+ CFRetain(primary_ipv4);
+ }
+ if (prev_ipv4_primary != NULL) {
+ CFRelease(prev_ipv4_primary);
+ }
+ watcher->primary_ipv4 = primary_ipv4;
+ if (changed) {
+ if (primary_ipv4 != NULL) {
+ SCLog(S_NetworkIdentification_debug,
+ LOG_NOTICE, CFSTR("Primary IPv4 %@"), primary_ipv4);
+ }
+ else {
+ SCLog(S_NetworkIdentification_debug, LOG_NOTICE,
+ CFSTR("No Primary IPv4"));
+ }
+ }
+ done:
+ return (changed);
+}
+
+
+static CFDictionaryRef
+signature_add_service(CFDictionaryRef sig_dict, CFDictionaryRef service,
+ CFArrayRef active_services)
+{
+ CFArrayRef list;
+ CFMutableDictionaryRef new_dict = NULL;
+ CFDateRef now;
+
+ list = CFDictionaryGetValue(sig_dict, kServices);
+ now = CFDateCreate(NULL, CFAbsoluteTimeGetCurrent());
+ if (list == NULL) {
+ list = CFArrayCreate(NULL, (const void * *)&service, 1,
+ &kCFTypeArrayCallBacks);
+ }
+ else {
+ int list_count = CFArrayGetCount(list);
+ CFMutableArrayRef new_list = NULL;
+ CFRange range = CFRangeMake(0, list_count);
+ int where;
+
+ where = CFArrayGetFirstIndexOfValue(list, range, service);
+ if (where != kCFNotFound) {
+ CFDateRef date;
+
+ date = CFDictionaryGetValue(sig_dict, kTimestamp);
+ if (date != NULL) {
+ CFTimeInterval time_interval;
+
+ time_interval = CFDateGetTimeIntervalSinceDate(now, date);
+ /* don't bother updating timestamp until interval has passed */
+ if (time_interval < (SIGNATURE_UPDATE_INTERVAL_SECS)) {
+ goto done;
+ }
+ }
+ if (where == 0) {
+ /* it's already in the right place */
+ list = NULL;
+ }
+ }
+
+ if (list != NULL) {
+ new_list = CFArrayCreateMutableCopy(NULL, 0, list);
+ if (where != kCFNotFound) {
+ CFArrayRemoveValueAtIndex(new_list, where);
+ }
+ else {
+ list_count++;
+ }
+ CFArrayInsertValueAtIndex(new_list, 0, service);
+ /* try to remove stale entries */
+ if (list_count > SERVICE_HISTORY_MAX) {
+ int i;
+ int remove_count = list_count - SERVICE_HISTORY_MAX;
+
+ SCLog(S_NetworkIdentification_debug,
+ LOG_NOTICE, CFSTR("Attempting to remove %d services"),
+ remove_count);
+ for (i = list_count - 1; i >= 0 && remove_count > 0; i--) {
+ CFDictionaryRef dict;
+
+ dict = CFArrayGetValueAtIndex(new_list, i);
+ if (myCFDictionaryArrayGetValue(active_services,
+ kService, dict, NULL)
+ != NULL) {
+ /* skip anything that's currently active */
+ SCLog(S_NetworkIdentification_debug,
+ LOG_NOTICE, CFSTR("Skipping Service %@"),
+ dict);
+ }
+ else {
+ SCLog(S_NetworkIdentification_debug, LOG_NOTICE,
+ CFSTR("Removing Service %@"), dict);
+ CFArrayRemoveValueAtIndex(new_list, i);
+ remove_count--;
+ }
+ }
+ }
+ list = (CFArrayRef)new_list;
+
+ }
+ }
+
+ new_dict = CFDictionaryCreateMutableCopy(NULL, 0, sig_dict);
+ if (list != NULL) {
+ CFDictionarySetValue(new_dict, kServices, list);
+ CFRelease(list);
+ }
+ CFDictionarySetValue(new_dict, kTimestamp, now);
+
+ done:
+ CFRelease(now);
+ return (new_dict);
+}
+
+#define ARBITRARILY_LARGE_NUMBER (1024 * 1024)
+static CFStringRef
+get_best_serviceID(CFArrayRef serviceID_list, CFArrayRef order)
+{
+ int best_rank;
+ CFStringRef best_serviceID;
+ int count;
+ int i;
+ CFRange range;
+
+ count = CFArrayGetCount(serviceID_list);
+ if (count == 1 || order == NULL) {
+ return (CFArrayGetValueAtIndex(serviceID_list, 0));
+ }
+ best_serviceID = NULL;
+ best_rank = ARBITRARILY_LARGE_NUMBER;
+ range = CFRangeMake(0, CFArrayGetCount(order));
+ for (i = 0; i < count; i++) {
+ CFStringRef serviceID = CFArrayGetValueAtIndex(serviceID_list, i);
+ int this_rank;
+
+ this_rank = CFArrayGetFirstIndexOfValue(order, range, serviceID);
+ if (this_rank == kCFNotFound) {
+ this_rank = ARBITRARILY_LARGE_NUMBER;
+ }
+ if (best_serviceID == NULL || this_rank < best_rank) {
+ best_serviceID = serviceID;
+ best_rank = this_rank;
+ }
+ }
+ return (best_serviceID);
+}
+
+static CFArrayRef
+copy_service_order(SCDynamicStoreRef session, CFStringRef ipv4_key)
+{
+ CFArrayRef order = NULL;
+ CFDictionaryRef ipv4_dict = NULL;
+
+ if (session == NULL) {
+ return (NULL);
+ }
+ ipv4_dict = SCDynamicStoreCopyValue(session, ipv4_key);
+ if (isA_CFDictionary(ipv4_dict) != NULL) {
+ order = CFDictionaryGetValue(ipv4_dict, kSCPropNetServiceOrder);
+ order = isA_CFArray(order);
+ if (order) {
+ CFRetain(order);
+ }
+ }
+ if (ipv4_dict != NULL) {
+ CFRelease(ipv4_dict);
+ }
+ return (order);
+}
+
+typedef struct service_order_with_range {
+ CFArrayRef service_order;
+ CFRange range;
+} service_order_with_range_t;
+
+static void
+add_netID_and_serviceID(service_order_with_range_t * order, int count,
+ CFMutableArrayRef netID_list, CFStringRef netID,
+ CFMutableArrayRef serviceID_list, CFStringRef serviceID)
+
+{
+ int i;
+ int serviceID_index;
+
+ if (count == 0 || order->service_order == NULL) {
+ goto add_to_end;
+ }
+ serviceID_index = CFArrayGetFirstIndexOfValue(order->service_order,
+ order->range,
+ serviceID);
+ if (serviceID_index == kCFNotFound) {
+ goto add_to_end;
+ }
+ for (i = 0; i < count; i++) {
+ CFStringRef scan = CFArrayGetValueAtIndex(serviceID_list, i);
+ int scan_index;
+
+ scan_index = CFArrayGetFirstIndexOfValue(order->service_order,
+ order->range,
+ scan);
+ if (scan_index == kCFNotFound
+ || serviceID_index < scan_index) {
+ /* found our insertion point */
+ CFArrayInsertValueAtIndex(netID_list, i, netID);
+ CFArrayInsertValueAtIndex(serviceID_list, i, serviceID);
+ return;
+ }
+ }
+
+ add_to_end:
+ CFArrayAppendValue(netID_list, netID);
+ CFArrayAppendValue(serviceID_list, serviceID);
+ return;
+}
+
+static Boolean
+ServiceWatcherPublishActiveIdentifiers(ServiceWatcherRef watcher)
+{
+ Boolean updated = FALSE;
+
+ if (watcher->active_signatures == NULL) {
+ CFDictionaryRef dict;
+
+ dict = SCDynamicStoreCopyValue(watcher->store,
+ kSCNetworkIdentificationStoreKey);
+ if (dict != NULL) {
+ updated = TRUE;
+ SCLog(S_NetworkIdentification_debug,
+ LOG_NOTICE, CFSTR("Removing %@"),
+ kSCNetworkIdentificationStoreKey);
+ SCDynamicStoreRemoveValue(watcher->store,
+ kSCNetworkIdentificationStoreKey);
+ CFRelease(dict);
+ }
+ }
+ else {
+ int count;
+ CFDictionaryRef dict;
+ int i;
+ CFMutableArrayRef id_list;
+ CFStringRef keys[3];
+ int keys_count;
+ service_order_with_range_t order;
+ CFStringRef primary_ipv4_id = NULL;
+ CFMutableArrayRef serviceID_list;
+ CFDictionaryRef store_dict;
+ CFTypeRef values[3];
+
+ order.service_order = copy_service_order(watcher->store,
+ watcher->setup_ipv4_key);
+ if (order.service_order != NULL) {
+ order.range = CFRangeMake(0, CFArrayGetCount(order.service_order));
+ }
+ id_list = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ serviceID_list = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ count = CFArrayGetCount(watcher->active_signatures);
+ for (i = 0; i < count; i++) {
+ CFStringRef this_id;
+ CFStringRef serviceID;
+ CFArrayRef this_list;
+
+ dict = CFArrayGetValueAtIndex(watcher->active_signatures, i);
+ this_id = CFDictionaryGetValue(dict, kIdentifier);
+ this_list = CFDictionaryGetValue(dict, kServiceIdentifiers);
+ if (primary_ipv4_id == NULL && watcher->primary_ipv4 != NULL) {
+ CFRange range;
+
+ range = CFRangeMake(0, CFArrayGetCount(this_list));
+ if (CFArrayContainsValue(this_list, range,
+ watcher->primary_ipv4)) {
+ primary_ipv4_id = this_id;
+ }
+ }
+ serviceID = get_best_serviceID(this_list, order.service_order);
+ add_netID_and_serviceID(&order, i, id_list, this_id,
+ serviceID_list, serviceID);
+ }
+ keys[0] = kStoreKeyActiveIdentifiers;
+ values[0] = id_list;
+ keys[1] = kStoreKeyServiceIdentifiers;
+ values[1] = serviceID_list;
+ if (primary_ipv4_id != NULL) {
+ keys_count = 3;
+ keys[2] = kStoreKeyPrimaryIPv4Identifier;
+ values[2] = primary_ipv4_id;
+ }
+ else {
+ keys_count = 2;
+ }
+ dict = CFDictionaryCreate(NULL, (const void * *)keys,
+ (const void * *)values, keys_count,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ store_dict
+ = SCDynamicStoreCopyValue(watcher->store,
+ kSCNetworkIdentificationStoreKey);
+ if (isA_CFDictionary(store_dict) == NULL
+ || CFEqual(store_dict, dict) == FALSE) {
+ updated = TRUE;
+ SCDynamicStoreSetValue(watcher->store,
+ kSCNetworkIdentificationStoreKey, dict);
+ SCLog(S_NetworkIdentification_debug,
+ LOG_NOTICE, CFSTR("Setting %@ = %@"),
+ kSCNetworkIdentificationStoreKey,
+ dict);
+ }
+ else {
+ SCLog(S_NetworkIdentification_debug,
+ LOG_NOTICE, CFSTR("Not setting %@"),
+ kSCNetworkIdentificationStoreKey);
+ }
+ CFRelease(dict);
+ CFRelease(id_list);
+ CFRelease(serviceID_list);
+ if (order.service_order != NULL) {
+ CFRelease(order.service_order);
+ }
+ if (store_dict != NULL) {
+ CFRelease(store_dict);
+ }
+ }
+ return (updated);
+}
+
+static CFDictionaryRef
+signature_dict_create(CFStringRef this_sig, CFDictionaryRef service)
+{
+ CFDictionaryRef dict;
+ const void * keys[4];
+ const void * values[4];
+
+ keys[0] = kSignature;
+ values[0] = this_sig;
+
+ keys[1] = kServices;
+ values[1] = CFArrayCreate(NULL, (const void * *)&service, 1,
+ &kCFTypeArrayCallBacks);
+ keys[2] = kIdentifier;
+ values[2] = this_sig;
+
+ keys[3] = kTimestamp;
+ values[3] = CFDateCreate(NULL, CFAbsoluteTimeGetCurrent());
+
+ dict = CFDictionaryCreate(NULL, keys, values,
+ sizeof(keys) / sizeof(keys[0]),
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ CFRelease(values[1]);
+ CFRelease(values[3]);
+ return (dict);
+}
+
+static void
+ServiceWatcherRemoveStaleSignatures(ServiceWatcherRef watcher)
+{
+ int active_count = 0;
+ int count;
+ int i;
+ int remove_count;
+
+ count = CFArrayGetCount(watcher->signatures);
+ if (watcher->active_signatures != NULL) {
+ active_count = CFArrayGetCount(watcher->active_signatures);
+ }
+ if ((count - active_count) <= SIGNATURE_HISTORY_MAX) {
+ return;
+ }
+ remove_count = count - active_count - SIGNATURE_HISTORY_MAX;
+ for (i = count - 1; i >= 0 && remove_count > 0; i--) {
+ CFDictionaryRef sig_dict;
+ CFStringRef sig_str;
+
+ sig_dict = CFArrayGetValueAtIndex(watcher->signatures, i);
+ sig_str = CFDictionaryGetValue(sig_dict, kSignature);
+
+ if (myCFDictionaryArrayGetValue(watcher->active_signatures,
+ kSignature, sig_str, NULL)
+ != NULL) {
+ /* skip anything that's currently active */
+ SCLog(S_NetworkIdentification_debug,
+ LOG_NOTICE, CFSTR("Skipping %@"), sig_dict);
+ }
+ else {
+ SCLog(S_NetworkIdentification_debug,
+ LOG_NOTICE, CFSTR("ServiceWatcher: Removing %@"),
+ sig_dict);
+ CFArrayRemoveValueAtIndex(watcher->signatures, i);
+ remove_count--;
+ }
+ }
+ return;
+}
+
+static void
+ServiceWatcherSaveSignatures(ServiceWatcherRef watcher)
+{
+ SCPreferencesRef prefs;
+
+ prefs = SCPreferencesCreate(NULL, CFSTR("ServiceWatcher"),
+ kSCNetworkIdentificationPrefsKey);
+ if (prefs == NULL) {
+ SCLog(TRUE, LOG_NOTICE, CFSTR("ServiceWatcherSaveSignatures: Create failed %s"),
+ SCErrorString(SCError()));
+ return;
+ }
+ ServiceWatcherRemoveStaleSignatures(watcher);
+ if (SCPreferencesSetValue(prefs, kSignatures, watcher->signatures)
+ == FALSE) {
+ SCLog(TRUE, LOG_NOTICE, CFSTR("ServiceWatcherSaveSignatures: Set failed %s"),
+ SCErrorString(SCError()));
+ }
+ else if (SCPreferencesCommitChanges(prefs) == FALSE) {
+ // An EROFS error is expected during installation. All other
+ // errors should be reported.
+ if (SCError() != EROFS) {
+ SCLog(TRUE, LOG_NOTICE, CFSTR("ServiceWatcherSaveSignatures: Commit failed %s"),
+ SCErrorString(SCError()));
+ }
+ }
+ CFRelease(prefs);
+ return;
+
+}
+
+static void
+ServiceWatcherLoadSignatures(ServiceWatcherRef watcher)
+{
+ int count;
+ int i;
+ SCPreferencesRef prefs;
+ CFArrayRef signatures;
+
+ watcher->signatures
+ = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ prefs = SCPreferencesCreate(NULL, CFSTR("ServiceWatcher"),
+ kSCNetworkIdentificationPrefsKey);
+ if (prefs == NULL) {
+ SCLog(TRUE, LOG_NOTICE, CFSTR("ServiceWatcherLoadSignatures: Create failed %s"),
+ SCErrorString(SCError()));
+ return;
+ }
+ signatures = SCPreferencesGetValue(prefs, kSignatures);
+ if (signatures == NULL) {
+ goto done;
+ }
+ if (isA_CFArray(signatures) == NULL) {
+ SCLog(TRUE, LOG_NOTICE,
+ CFSTR("ServiceWatcherLoadSignatures: Signatures is not an array"));
+ goto done;
+ }
+ count = CFArrayGetCount(signatures);
+ for (i = 0; i < count; i++) {
+ CFDictionaryRef dict;
+ CFArrayRef services;
+ CFStringRef sig_id;
+ CFStringRef sig_str;
+ CFDateRef timestamp;
+
+ dict = CFArrayGetValueAtIndex(signatures, i);
+ if (isA_CFDictionary(dict) == NULL) {
+ continue;
+ }
+ sig_id = CFDictionaryGetValue(dict, kIdentifier);
+ if (isA_CFString(sig_id) == NULL) {
+ continue;
+ }
+ sig_str = CFDictionaryGetValue(dict, kSignature);
+ if (isA_CFString(sig_str) == NULL) {
+ continue;
+ }
+ timestamp = CFDictionaryGetValue(dict, kTimestamp);
+ if (isA_CFDate(timestamp) == NULL) {
+ continue;
+ }
+ services = CFDictionaryGetValue(dict, kServices);
+ if (isA_CFArray(services) == NULL) {
+ continue;
+ }
+ CFArrayAppendValue(watcher->signatures, dict);
+ }
+
+ done:
+ CFRelease(prefs);
+ return;
+
+}
+
+static void
+ServiceWatcherUpdate(ServiceWatcherRef watcher, Boolean update_signatures)
+{
+ CFMutableArrayRef active_signatures = NULL;
+ int count;
+ int i;
+ Boolean save_signatures = FALSE;
+ CFArrayRef service_list;
+ Boolean update_store = FALSE;
+
+ service_list = ServiceWatcherCopyCurrent(watcher);
+ SCLog(S_NetworkIdentification_debug,
+ LOG_NOTICE, CFSTR("service_list = %@"), service_list);
+ if (service_list == NULL) {
+ goto done;
+ }
+ active_signatures = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ count = CFArrayGetCount(service_list);
+ for (i = 0; i < count; i++) {
+ CFDictionaryRef dict;
+ CFDictionaryRef active_dict;
+ CFArrayRef id_list;
+ CFMutableDictionaryRef new_active_dict;
+ CFDictionaryRef new_sig_dict;
+ CFStringRef serviceID;
+ CFStringRef sig_id;
+ CFDictionaryRef service;
+ CFDictionaryRef sig_dict;
+ CFStringRef this_sig;
+ int where;
+
+ dict = CFArrayGetValueAtIndex(service_list, i);
+ service = CFDictionaryGetValue(dict, kService);
+ this_sig = CFDictionaryGetValue(dict, kSignature);
+ if (this_sig == NULL) {
+ /* service has no signature */
+ continue;
+ }
+ sig_dict = myCFDictionaryArrayGetValue(watcher->signatures, kSignature,
+ this_sig, &where);
+ if (sig_dict == NULL) {
+ /* add a new signature entry */
+ sig_dict = signature_dict_create(this_sig, service);
+ CFArrayInsertValueAtIndex(watcher->signatures, 0, sig_dict);
+ CFRelease(sig_dict);
+ save_signatures = TRUE;
+ sig_id = CFDictionaryGetValue(sig_dict, kIdentifier);
+ active_dict = NULL;
+ }
+ else {
+ /* update an existing signature entry */
+
+ sig_id = CFDictionaryGetValue(sig_dict, kIdentifier);
+ new_sig_dict = signature_add_service(sig_dict, service,
+ service_list);
+ if (new_sig_dict != NULL) {
+ CFArrayRemoveValueAtIndex(watcher->signatures, where);
+ CFArrayInsertValueAtIndex(watcher->signatures, 0,
+ new_sig_dict);
+ CFRelease(new_sig_dict);
+ sig_dict = new_sig_dict;
+ save_signatures = TRUE;
+ }
+ active_dict
+ = myCFDictionaryArrayGetValue(active_signatures,
+ kSignature, this_sig,
+ &where);
+ }
+ if (active_dict == NULL) {
+ /* signature now active, this is the first/only service */
+ new_active_dict
+ = CFDictionaryCreateMutable(NULL, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ CFDictionarySetValue(new_active_dict, kSignature, this_sig);
+ CFDictionarySetValue(new_active_dict, kIdentifier, sig_id);
+ serviceID = CFDictionaryGetValue(service, kServiceID);
+ id_list = CFArrayCreate(NULL, (const void * *)&serviceID, 1,
+ &kCFTypeArrayCallBacks);
+ CFDictionarySetValue(new_active_dict, kServiceIdentifiers,
+ id_list);
+ CFArrayAppendValue(active_signatures, new_active_dict);
+ CFRelease(new_active_dict);
+ CFRelease(id_list);
+ }
+ else {
+ /* signature already active, add this serviceID */
+ CFRange range;
+
+ id_list = CFDictionaryGetValue(active_dict,
+ kServiceIdentifiers);
+ range = CFRangeMake(0, CFArrayGetCount(id_list));
+ serviceID = CFDictionaryGetValue(service, kServiceID);
+ if (CFArrayContainsValue(id_list, range, serviceID) == FALSE) {
+ CFMutableDictionaryRef new_active_dict;
+ CFMutableArrayRef new_id_list;
+
+ new_id_list = CFArrayCreateMutableCopy(NULL, 0, id_list);
+ CFArrayAppendValue(new_id_list, serviceID);
+ new_active_dict
+ = CFDictionaryCreateMutableCopy(NULL, 0, active_dict);
+ CFDictionarySetValue(new_active_dict, kServiceIdentifiers,
+ new_id_list);
+ CFArraySetValueAtIndex(active_signatures, where,
+ new_active_dict);
+ CFRelease(new_active_dict);
+ CFRelease(new_id_list);
+ }
+ }
+ }
+ done:
+ if (active_signatures == NULL
+ || CFArrayGetCount(active_signatures) == 0) {
+ update_store
+ = ServiceWatcherSetActiveSignatures(watcher, NULL);
+ }
+ else {
+ update_store
+ = ServiceWatcherSetActiveSignatures(watcher, active_signatures);
+ }
+ if (save_signatures) {
+ /* write out the file */
+ ServiceWatcherSaveSignatures(watcher);
+ }
+
+ if (service_list != NULL) {
+ CFRelease(service_list);
+ }
+ if (active_signatures != NULL) {
+ CFRelease(active_signatures);
+ }
+ if (update_signatures || update_store) {
+ if (ServiceWatcherPublishActiveIdentifiers(watcher)) {
+ notify_post(kSCNetworkSignatureActiveChangedNotifyName);
+ }
+ }
+ return;
+}
+
+static Boolean
+update_primary_ipv4(ServiceWatcherRef watcher)
+{
+ Boolean changed = FALSE;
+ CFDictionaryRef global_ipv4;
+
+ global_ipv4 = SCDynamicStoreCopyValue(watcher->store,
+ watcher->state_ipv4_key);
+ if (isA_CFDictionary(global_ipv4) != NULL) {
+ CFStringRef primary_ipv4;
+
+ primary_ipv4
+ = CFDictionaryGetValue(global_ipv4,
+ kSCDynamicStorePropNetPrimaryService);
+ changed = ServiceWatcherSetPrimaryIPv4(watcher,
+ isA_CFString(primary_ipv4));
+ }
+ if (global_ipv4 != NULL) {
+ CFRelease(global_ipv4);
+ }
+ return (changed);
+}
+
+static void
+ServiceWatcherNotifier(SCDynamicStoreRef not_used, CFArrayRef changes,
+ void * info)
+{
+ int count;
+ int i;
+ Boolean order_changed = FALSE;
+ Boolean global_ipv4_changed = FALSE;
+ Boolean primary_ipv4_changed = FALSE;
+ ServiceWatcherRef watcher = (ServiceWatcherRef)info;
+
+ count = CFArrayGetCount(changes);
+ if (count == 0) {
+ return;
+ }
+ for (i = 0; i < count; i++) {
+ CFStringRef key = CFArrayGetValueAtIndex(changes, i);
+
+ if (CFStringHasPrefix(key, kSCDynamicStoreDomainSetup)) {
+ order_changed = TRUE;
+ }
+ else if (CFEqual(key, watcher->state_ipv4_key)) {
+ global_ipv4_changed = TRUE;
+ }
+ }
+ if (global_ipv4_changed) {
+ primary_ipv4_changed = update_primary_ipv4(watcher);
+ }
+ if (count == 1
+ && (order_changed || primary_ipv4_changed)) {
+ /* just the service order or the primary service changed */
+ if (ServiceWatcherPublishActiveIdentifiers(watcher)) {
+ notify_post(kSCNetworkSignatureActiveChangedNotifyName);
+ }
+ }
+ else {
+ ServiceWatcherUpdate(watcher, order_changed || primary_ipv4_changed);
+ }
+ return;
+}
+
+static ServiceWatcherRef
+ServiceWatcherCreate()
+{
+ SCDynamicStoreContext context = { 0, 0, 0, 0, 0};
+ CFArrayRef patterns;
+ CFStringRef keys[2];
+ CFArrayRef key_list;
+ ServiceWatcherRef watcher;
+
+ watcher = malloc(sizeof(*watcher));
+ bzero(watcher, sizeof(*watcher));
+ context.info = watcher;
+ watcher->store = SCDynamicStoreCreate(NULL, CFSTR("Service Watcher"),
+ ServiceWatcherNotifier, &context);
+ if (watcher->store == NULL) {
+ SCLog(TRUE, LOG_NOTICE, CFSTR("SCDynamicStoreCreate failed: %s"),
+ SCErrorString(SCError()));
+ goto failed;
+ }
+ watcher->setup_ipv4_key
+ = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL,
+ kSCDynamicStoreDomainSetup,
+ kSCEntNetIPv4);
+ watcher->state_ipv4_key
+ = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL,
+ kSCDynamicStoreDomainState,
+ kSCEntNetIPv4);
+ keys[0] = watcher->setup_ipv4_key;
+ keys[1] = watcher->state_ipv4_key;
+ key_list = CFArrayCreate(NULL, (const void * *)keys, sizeof(keys) / sizeof(keys[0]),
+ &kCFTypeArrayCallBacks);
+ patterns = ServiceWatcherNotificationPatterns();
+ (void)SCDynamicStoreSetNotificationKeys(watcher->store, key_list, patterns);
+ CFRelease(patterns);
+ CFRelease(key_list);
+ watcher->rls = SCDynamicStoreCreateRunLoopSource(NULL, watcher->store, 0);
+ CFRunLoopAddSource(CFRunLoopGetCurrent(), watcher->rls,
+ kCFRunLoopDefaultMode);
+ ServiceWatcherLoadSignatures(watcher);
+ update_primary_ipv4(watcher);
+ return (watcher);
+ failed:
+ ServiceWatcherFree(&watcher);
+ return (NULL);
+}
+
+void
+ServiceWatcherFree(ServiceWatcherRef * watcher_p)
+{
+ ServiceWatcherRef watcher;
+
+ if (watcher_p == NULL) {
+ return;
+ }
+ watcher = *watcher_p;
+ if (watcher == NULL) {
+ return;
+ }
+ *watcher_p = NULL;
+ if (watcher->store != NULL) {
+ CFRelease(watcher->store);
+ watcher->store = NULL;
+ }
+ if (watcher->rls != NULL) {
+ CFRunLoopSourceInvalidate(watcher->rls);
+ CFRelease(watcher->rls);
+ watcher->rls = NULL;
+ }
+ if (watcher->signatures != NULL) {
+ CFRelease(watcher->signatures);
+ watcher->signatures = NULL;
+ }
+ if (watcher->state_ipv4_key != NULL) {
+ CFRelease(watcher->state_ipv4_key);
+ watcher->state_ipv4_key = NULL;
+ }
+ if (watcher->setup_ipv4_key != NULL) {
+ CFRelease(watcher->setup_ipv4_key);
+ watcher->setup_ipv4_key = NULL;
+ }
+ free(watcher);
+ return;
+}
+
+/* global service watcher instance */
+static ServiceWatcherRef S_watcher;
+
+__private_extern__
+void
+prime_NetworkIdentification()
+{
+ if (S_NetworkIdentification_disabled) {
+ return;
+ }
+ S_watcher = ServiceWatcherCreate();
+ ServiceWatcherUpdate(S_watcher, TRUE);
+}
+
+__private_extern__
+void
+load_NetworkIdentification(CFBundleRef bundle, Boolean bundleVerbose)
+{
+ if (bundleVerbose) {
+ S_NetworkIdentification_debug = 1;
+ }
+ return;
+}
+
+__private_extern__
+void
+stop_NetworkIdentification(CFRunLoopSourceRef stopRls)
+{
+ if (S_watcher != NULL) {
+ ServiceWatcherSaveSignatures(S_watcher);
+ }
+ CFRunLoopSourceSignal(stopRls);
+}
+
+
+#ifdef TEST_NETWORKIDENTIFICATION
+#undef TEST_NETWORKIDENTIFICATION
+
+int
+main(int argc, char **argv)
+{
+ _sc_log = FALSE;
+ _sc_verbose = (argc > 1) ? TRUE : FALSE;
+
+ load_NetworkIdentification(CFBundleGetMainBundle(), (argc > 1) ? TRUE : FALSE);
+ prime_NetworkIdentification();
+ CFRunLoopRun();
+ /* not reached */
+ exit(0);
+ return 0;
+}
+#endif
+