ip6config.c   [plain text]


/*
 * Copyright (c) 1999-2002 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * The contents of this file constitute Original Code as defined in and
 * are subject to the Apple Public Source License Version 1.1 (the
 * "License").  You may not use this file except in compliance with the
 * License.  Please obtain a copy of the License at
 * http://www.apple.com/publicsource and read it before using this file.
 *
 * This 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 OR NON-INFRINGEMENT.  Please see the
 * License for the specific language governing rights and limitations
 * under the License.
 *
 * @APPLE_LICENSE_HEADER_END@
 */

/*
 * ip6config.c
 * - plugin that configures IPv6 on the various interfaces.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <sys/time.h>
#include <netinet/in.h>

#include <SystemConfiguration/SystemConfiguration.h>
#include <SystemConfiguration/SCValidation.h>
#include <SystemConfiguration/SCPrivate.h>

#include "configthreads_common.h"
#include "globals.h"
#include "config_method.h"
#include "ip6config_utils.h"


/* Local Variables */
static interface_list_t * S_interfaces = NULL;
static CFStringRef	S_state_interface_prefix = NULL;
static CFStringRef	S_setup_service_prefix = NULL;

/* Global Variables */
int			G_verbose = FALSE;
CFBundleRef		G_bundle = NULL;
SCDynamicStoreRef	G_scd_session = NULL;
int			G_debug = TRUE;
const struct in6_addr	G_ip6_zeroes = IN6ADDR_ANY_INIT;
IFStateList_t		G_ifstate_list;

static boolean_t
update_interface_list()
{
    interface_list_t *	new_interfaces = NULL;
    
    new_interfaces = ifl_init();
    
    if (new_interfaces == NULL) {
        my_log(LOG_ERR, "ip6config: ifl_init failed");
        return (FALSE);
    }
    
    if (S_interfaces) {
        ifl_free(&S_interfaces);
    }
    
    S_interfaces = new_interfaces;
    
    return (TRUE);
}

/*
 * Function: check_for_detached_interfaces
 * Purpose:
 *   Remove interface state for any interface that has been removed.
 *   Create a temporary list to store the name of each interface that
 *   has been removed.  Iterate through that list to remove individual
 *   interface state records.  This is done to avoid problems with
 *   iterating over a list while it is modified.
 */
static void
check_for_detached_interfaces()
{
    int		count = dynarray_count(&G_ifstate_list);
    char * *	names = NULL;
    int		names_count = 0;
    int 	i;

    if (count == 0) {
        return;
    }

    /* allocate worst case scenario in which each ifstate needs to be removed */
    names = (char * *)malloc(sizeof(char *) * count);
    if (names == NULL) {
        return;
    }

    for (i = 0; i < count; i++) {
        IFState_t *	ifstate = dynarray_element(&G_ifstate_list, i);

        if (ifl_find_name(S_interfaces, if_name(ifstate->if_p)) == NULL) {
            names[names_count++] = if_name(ifstate->if_p);
        }
    }

    for (i = 0; i < names_count; i++) {
        IFStateList_ifstate_free(&G_ifstate_list, names[i]);
    }

    free(names);
    return;
}

/*
 * Function: parse_component
 * Purpose:
 *   Given a string 'key' and a string prefix 'prefix',
 *   return the next component in the slash '/' separated
 *   key.  If no slash follows the prefix, return NULL.
 *
 * Examples:
 * 1. key = "a/b/c" prefix = "a/"
 *    returns "b"
 * 2. key = "a/b/c" prefix = "a/b/"
 *    returns NULL
 */
static CFStringRef
parse_component(CFStringRef key, CFStringRef prefix)
{
    CFMutableStringRef	comp;
    CFRange		range;

    if (CFStringHasPrefix(key, prefix) == FALSE) {
        return (NULL);
    }

    comp = CFStringCreateMutableCopy(NULL, 0, key);
    if (comp == NULL) {
        return (NULL);
    }

    CFStringDelete(comp, CFRangeMake(0, CFStringGetLength(prefix)));

    range = CFStringFind(comp, CFSTR("/"), 0);
    if (range.location == kCFNotFound) {
        CFRelease(comp);
        return (NULL);
    }

    range.length = CFStringGetLength(comp) - range.location;
    CFStringDelete(comp, range);

    return (comp);
}

static void
handle_configuration_changed(SCDynamicStoreRef session, CFArrayRef all_ipv6)
{
    int i, n;

    n = ifl_count(S_interfaces);
    for (i = 0; i < n; i++) {
	ServiceConfig_t *	config;
	int			count = 0;
	IFState_t *		ifstate;
	ServiceConfig_t *	if_services = NULL;
	interface_t *		if_p = ifl_at_index(S_interfaces, i);

	if (strcmp(if_name(if_p), "lo0") == 0) {
	    my_log(LOG_DEBUG, "HANDLE_configuration_changed: skipping loopback");
	    continue;
	}

	if_services = service_config_list_init(session, all_ipv6, if_name(if_p), &count);
	if (if_services == NULL) {
            ifstate =  IFStateList_ifstate_with_name(&G_ifstate_list,
                            if_name(if_p), NULL);
            if (ifstate != NULL) {
                IFState_services_free(ifstate);
            }
            continue;
	}

	/* stop services that are no longer active */
	service_free_inactive_services(if_name(if_p), if_services, count);

	ifstate = IFStateList_ifstate_create(&G_ifstate_list, if_p);
	if (ifstate) {
            int k;
                
            /* update each of the services that are configured */
            for (k = 0, config = if_services; k < count; k++, config++) {
                (void)service_set_service(ifstate, config);
            }
	}
	service_config_list_free(&if_services, count);
    }
    return;
}

static CFArrayRef
entity_all(SCDynamicStoreRef session)
{
    CFMutableArrayRef	all_services = NULL;
    int			count;
    int			id_count;
    CFMutableArrayRef	get_patterns = NULL;
    int			i;
    CFStringRef		key = NULL;
    void * *	 	keys = NULL;
    CFMutableArrayRef	service_IDs = NULL;
    CFDictionaryRef	values = NULL;

    get_patterns = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
    service_IDs = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
    all_services = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);

    if (get_patterns == NULL || service_IDs == NULL || all_services == NULL) {
        goto done;
    }

    /* populate patterns array for Setup:/Network/Service/any/{IPv6,Interface, 6to4} */
    key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
						      kSCDynamicStoreDomainSetup,
						      kSCCompAnyRegex,
						      kSCEntNetIPv6);
    if (key == NULL) {
        goto done;
    }
    CFArrayAppendValue(get_patterns, key);
    my_CFRelease(&key);

    key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
						      kSCDynamicStoreDomainSetup,
						      kSCCompAnyRegex,
						      kSCEntNetInterface);
    if (key == NULL) {
        goto done;
    }
    CFArrayAppendValue(get_patterns, key);
    my_CFRelease(&key);

    key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
						      kSCDynamicStoreDomainSetup,
						      kSCCompAnyRegex,
						      kSCEntNet6to4);
   if (key == NULL) {
        goto done;
    }
    CFArrayAppendValue(get_patterns, key);
    my_CFRelease(&key);

    /* get keys and values atomically */
    values = SCDynamicStoreCopyMultiple(session, NULL, get_patterns);
    if (values == NULL) {
        goto done;
    }

    /* if there are no values, we're done */
    count = CFDictionaryGetCount(values);
    if (count == 0) {
        goto done;
    }

    /* build a list of configured service ID's */
    keys = (void * *)malloc(sizeof(void *) * count);
    if (keys == NULL) {
        goto done;
    }
    CFDictionaryGetKeysAndValues(values, (const void * *)keys, NULL);

    for (i = 0; i < count; i++) {
        CFStringRef	serviceID;

        serviceID = parse_component(keys[i], S_setup_service_prefix);
        if (serviceID == NULL) {
            continue;
        }
        my_CFArrayAppendUniqueValue(service_IDs, serviceID);
        my_CFRelease(&serviceID);
    }
    free(keys);
    keys = NULL;
    
    /* populate all_services array with annotated IPv6 dict's */
    id_count = CFArrayGetCount(service_IDs);
    for (i = 0; i < id_count; i++) {
        CFStringRef 		key = NULL;
        CFDictionaryRef		if_dict;
        CFStringRef 		ifn_cf;
        CFDictionaryRef		ipv6_dict;
        CFMutableDictionaryRef	service_dict = NULL;
        CFStringRef		serviceID;
        CFStringRef		type = NULL;
        CFDictionaryRef         stf_dict;
        CFStringRef             relay = NULL;

        serviceID = CFArrayGetValueAtIndex(service_IDs, i);
        key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
                                                        kSCDynamicStoreDomainSetup,
                                                        serviceID,
                                                        kSCEntNetInterface);
        if (key == NULL) {
            goto loop_done;
        }

        if_dict = CFDictionaryGetValue(values, key);
        my_CFRelease(&key);

        if_dict = isA_CFDictionary(if_dict);
        if (if_dict == NULL) {
            goto loop_done;
        }

        type = CFDictionaryGetValue(if_dict, kSCPropNetInterfaceType);
        
        if (type == NULL) {
            goto loop_done;
        }

        if (CFEqual(type, kSCValNetInterfaceTypeEthernet) == FALSE 
            && CFEqual(type, kSCValNetInterfaceTypeFireWire) == FALSE 
            && CFEqual(type, kSCValNetInterfaceType6to4) == FALSE) {
            /* we only configure ethernet, firewire and 6to4 currently */
            goto loop_done;
        }
        
        ifn_cf = CFDictionaryGetValue(if_dict, kSCPropNetInterfaceDeviceName);
        if (ifn_cf == NULL) {
            goto loop_done;
        }

        key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
                                                        kSCDynamicStoreDomainSetup,
                                                        serviceID,
                                                        kSCEntNetIPv6);
        if (key == NULL) {
            goto loop_done;
        }

        ipv6_dict = CFDictionaryGetValue(values, key);
        my_CFRelease(&key);

        ipv6_dict = isA_CFDictionary(ipv6_dict);
        if (ipv6_dict == NULL) {
            goto loop_done;
        }

        service_dict = CFDictionaryCreateMutableCopy(NULL, 0, ipv6_dict);
        if (service_dict == NULL) {
            goto loop_done;
        }
        
        key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
                                                        kSCDynamicStoreDomainSetup,
                                                        serviceID,
                                                        kSCEntNet6to4);
        if (key != NULL) {
            stf_dict = CFDictionaryGetValue(values, key);
            my_CFRelease(&key);
            
            stf_dict = isA_CFDictionary(stf_dict);
            if (stf_dict != NULL) {
                relay = CFDictionaryGetValue(stf_dict, kSCPropNet6to4Relay);
                if (relay == NULL) {
                    my_log(LOG_DEBUG, "entity_all: error getting 6to4 relay");
                }
                else {
                    CFDictionarySetValue(service_dict, kSCPropNet6to4Relay, relay);
                }
            }
        }


        /* annotate with serviceID and interface name */
        CFDictionarySetValue(service_dict, kSCPropNetInterfaceDeviceName, ifn_cf);
        CFDictionarySetValue(service_dict, PROP_SERVICEID, serviceID);
        CFArrayAppendValue(all_services, service_dict);

     loop_done:
        my_CFRelease(&service_dict);
    }

    SCLog(G_verbose, LOG_INFO, CFSTR("ALL_SERVICES: %@ (%d)"), all_services,
            CFArrayGetCount(all_services));

 done:
    my_CFRelease(&values);
    my_CFRelease(&get_patterns);
    my_CFRelease(&service_IDs);
    if (all_services == NULL || CFArrayGetCount(all_services) == 0) {
        my_CFRelease(&all_services);
    }
    return (all_services);
}

static void
configure_from_cache(SCDynamicStoreRef session)
{
    CFArrayRef	all_ipv6 = NULL;

    all_ipv6 = entity_all(session);
    handle_configuration_changed(session, all_ipv6);
    my_CFRelease(&all_ipv6);

    return;
}

static void
link_key_changed(SCDynamicStoreRef session, CFStringRef cache_key)
{
    CFDictionaryRef	dict = NULL;
	interface_t *		if_p = NULL;
    CFStringRef		ifn_cf = NULL;
    char		ifn[IFNAMSIZ + 1];
    IFState_t *   	ifstate;
    int			j;
	int			service_count;
	link_status_t		link;
	CFBooleanRef		link_val = NULL;

	ifn_cf = parse_component(cache_key, S_state_interface_prefix);
	if (ifn_cf == NULL) {
		return;
	}
	cfstring_to_cstring(ifn_cf, ifn, sizeof(ifn));
    dict = my_SCDynamicStoreCopyValue(session, cache_key);
    if (dict != NULL) {
        if (CFDictionaryContainsKey(dict, kSCPropNetLinkDetaching)) {
			ifstate = IFStateList_ifstate_with_name(&G_ifstate_list,
								ifn, NULL);
			if (ifstate != NULL) {
				IFState_services_free(ifstate);
			}
			goto done;
		}
		link_val = CFDictionaryGetValue(dict, kSCPropNetLinkActive);
		link_val = isA_CFBoolean(link_val);
	}
	if (link_val == NULL) {
		link.valid = link.active = FALSE;
	}
	else {
		link.valid = TRUE;
		link.active = CFEqual(link_val, kCFBooleanTrue);
	}
	if (link.valid) {
		if_p = ifl_find_name(S_interfaces, ifn);
		if (if_p != NULL) {
			/* make sure address information is up to date */
			if_link_update(if_p);
		}
		else {
			if_p = NULL;
		}
	}
	ifstate = IFStateList_ifstate_with_name(&G_ifstate_list, ifn, NULL);
	if (ifstate == NULL) {
		goto done;
	}
	ifstate->link = link;
	if (if_p != NULL) {
		/* update address information in ifstate too */
		if_link_copy(ifstate->if_p, if_p);
	}
	if (link.valid == FALSE) {
		my_log(LOG_DEBUG, "link_key_changed: %s link is unknown", ifn);
	}
	else {
		my_log(LOG_DEBUG, "%s link is %s",
		       ifn, link.active ? "up" : "down");
	}
	if (link.active == TRUE) {
        /* do linklocal first */
        if (ifstate->llocal_service != NULL) {
            config_method_media(ifstate->llocal_service);
        }
	}
	service_count = dynarray_count(&ifstate->services);
	for (j = 0; j < service_count; j++) {
		Service_t * service_p = dynarray_element(&ifstate->services, j);
		
		config_method_media(service_p);
	}
	if (link.active == FALSE) {
		/* do linklocal after all others are taken care of */
		if (ifstate->llocal_service != NULL) {
			config_method_media(ifstate->llocal_service);
		}
        }

 done:
	my_CFRelease(&dict);
	my_CFRelease(&ifn_cf);
	return;
}

static void
interface_ipv6_changed(SCDynamicStoreRef session, CFStringRef cache_key)
{
    CFDictionaryRef	dict = NULL;
    CFStringRef		ifn_cf = NULL;
    char		ifn[IFNAMSIZ + 1];
    IFState_t *   	ifstate;
    ip6_addrinfo_list_t	ip6_addrs;
    int			j, service_count;

    ifn_cf = parse_component(cache_key, S_state_interface_prefix);
    if (ifn_cf == NULL) {
        return;
    }
    ip6_addrs.addr_list = NULL;
    cfstring_to_cstring(ifn_cf, ifn, sizeof(ifn));
    ifstate = IFStateList_ifstate_with_name(&G_ifstate_list, ifn, NULL);
    if (ifstate == NULL) {
        goto done;
    }
    dict = my_SCDynamicStoreCopyValue(session, cache_key);
    if (dict == NULL) {
        goto done;
    }
    if (ip6config_address_data_from_state(dict, &ip6_addrs) != 0) {
        goto done;
    }
    service_count = dynarray_count(&ifstate->services);
    for (j = 0; j < service_count; j++) {
        Service_t * service_p = dynarray_element(&ifstate->services, j);
        (void)config_method_state_change(service_p, &ip6_addrs);
    }

    /* do linklocal after all others are taken care of */
    if (ifstate->llocal_service != NULL) {
        (void)config_method_state_change(ifstate->llocal_service, &ip6_addrs);
    }
        
 done:
    my_CFRelease(&dict);
    my_CFRelease(&ifn_cf);
    if (ip6_addrs.addr_list) {
        free(ip6_addrs.addr_list);
    }
    return;
}

static void
ipv4_primary_service_changed(SCDynamicStoreRef session, CFStringRef cache_key)
{
    IFState_t *   		ifstate;
    ip6config_method_t		method;
    ip6config_method_data_t	method_data = { 0 };
    int				i, service_count;

    ifstate = IFStateList_ifstate_with_name(&G_ifstate_list, "stf0", NULL);
    if (ifstate == NULL) {
        goto done;
    }
    
    method = ip6config_method_6to4_e;
    ip6config_get_6to4_address_data(session, &method_data);
        
    service_count = dynarray_count(&ifstate->services);
    for (i = 0; i < service_count; i++) {
        Service_t * service_p = dynarray_element(&ifstate->services, i);
        (void)config_method_ipv4_primary_change(service_p, method, &method_data);
    }
    
 done:
    if (method_data.stf_data.ip4_addrs_list) {
        free(method_data.stf_data.ip4_addrs_list);
    }
    return;
}

static void
handle_change(SCDynamicStoreRef session, CFArrayRef changes, void * arg)
{
    boolean_t	config_changed = FALSE;
    CFIndex	count;
    CFIndex	i;
    boolean_t	iflist_changed = FALSE;
    
    count = CFArrayGetCount(changes);
    if (count == 0) {
        goto done;
    }
    
    SCLog(G_verbose, LOG_INFO, CFSTR("Changes: %@ (%d)"), changes,
            count);
    
    for (i = 0; i < count; i++) {
        CFStringRef	cache_key = CFArrayGetValueAtIndex(changes, i);
        
        if (CFStringHasPrefix(cache_key, kSCDynamicStoreDomainSetup)) {
            /* IPv6 configuration changed */
            config_changed = TRUE;
        }
        else if (CFStringHasSuffix(cache_key, kSCCompInterface)) {
            /* list of interfaces changed */
            iflist_changed = TRUE;
        }
    }
    
    /* an interface was added/removed */
    if (iflist_changed) {
        if (update_interface_list()) {
            config_changed = TRUE;
            check_for_detached_interfaces();
        }
    }
    
    /* configuration changed */
    if (config_changed) {
        configure_from_cache(session);
    }
    
    for (i = 0; i < count; i++) {
        CFStringRef	cache_key = CFArrayGetValueAtIndex(changes, i);
        
        if (CFStringHasSuffix(cache_key, kSCEntNetLink)) {
            link_key_changed(session, cache_key);
        }
        else if (CFStringHasSuffix(cache_key, kSCEntNetIPv6)) {
            interface_ipv6_changed(session, cache_key);
        }
        else if (CFStringHasSuffix(cache_key, kSCEntNetIPv4)) {
            ipv4_primary_service_changed(session, cache_key);
        }
    }
    
 done:
    return;
}

static void
notifier_init(SCDynamicStoreRef session)
{
    CFMutableArrayRef	keys = NULL;
    CFStringRef		key;
    CFMutableArrayRef	patterns = NULL;
    CFStringRef		pattern;
    CFRunLoopSourceRef	rls;

    if (session == NULL) {
		return;
    }
    keys = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
    patterns = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);

    /* notify when IPv6 config of any service changes */
    pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
						      kSCDynamicStoreDomainSetup,
						      kSCCompAnyRegex,
						      kSCEntNetIPv6);
    CFArrayAppendValue(patterns, pattern);
    my_CFRelease(&pattern);

    /* notify when Interface service <-> interface binding changes */
    pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
						      kSCDynamicStoreDomainSetup,
						      kSCCompAnyRegex,
						      kSCEntNetInterface);
    CFArrayAppendValue(patterns, pattern);
    my_CFRelease(&pattern);

    /* notify when the 6to4 relay changes */
    pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
							kSCDynamicStoreDomainSetup,
							kSCCompAnyRegex,
							kSCEntNet6to4);
    CFArrayAppendValue(patterns, pattern);
    my_CFRelease(&pattern);

    /* notify when the status of any address changes */
    pattern = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL,
							kSCDynamicStoreDomainState,
							kSCCompAnyRegex,
							kSCEntNetIPv6);
    CFArrayAppendValue(patterns, pattern);
    my_CFRelease(&pattern);

    /* notify when the link status of any interface changes */
    pattern = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL,
							kSCDynamicStoreDomainState,
							kSCCompAnyRegex,
							kSCEntNetLink);
    CFArrayAppendValue(patterns, pattern);
    my_CFRelease(&pattern);

    /* notify when list of interfaces changes */
    key = SCDynamicStoreKeyCreateNetworkInterface(NULL,
                        kSCDynamicStoreDomainState);
    CFArrayAppendValue(keys, key);
    my_CFRelease(&key);
    
    /* notify when the IPv4 primary service changes (for 6to4) */
    key = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL,
						     kSCDynamicStoreDomainState,
						     kSCEntNetIPv4);
    CFArrayAppendValue(keys, key);
    my_CFRelease(&key);

    SCDynamicStoreSetNotificationKeys(session, keys, patterns);
    my_CFRelease(&keys);
    my_CFRelease(&patterns);

    rls = SCDynamicStoreCreateRunLoopSource(NULL, session, 0);
    CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
    CFRelease(rls);

    return;
}

void
load(CFBundleRef bundle, Boolean bundleVerbose)
{
    G_bundle = (CFBundleRef)CFRetain(bundle);
    G_verbose = bundleVerbose;
    {
	struct timeval	start_time;

	gettimeofday(&start_time, 0);
	srandom(start_time.tv_usec & ~start_time.tv_sec);
    }

    G_scd_session = SCDynamicStoreCreate(NULL,
					 CFSTR("IP6Configuration"),
					 handle_change, NULL);
    if (G_scd_session == NULL) {
        G_scd_session = NULL;
        my_log(LOG_ERR, "SCDynamicStoreCreate failed: %s",
                SCErrorString(SCError()));
    }

    dynarray_init(&G_ifstate_list, IFState_free, NULL);

    return;
}

void
prime()
{
    CFPropertyListRef	value = NULL;

    if (G_scd_session == NULL) {
	return;
    }

    /* begin interface initialization */
    S_setup_service_prefix = SCDynamicStoreKeyCreate(NULL,
						     CFSTR("%@/%@/%@/"),
						     kSCDynamicStoreDomainSetup,
						     kSCCompNetwork,
						     kSCCompService);

    S_state_interface_prefix = SCDynamicStoreKeyCreate(NULL,
						       CFSTR("%@/%@/%@/"),
						       kSCDynamicStoreDomainState,
						       kSCCompNetwork,
						       kSCCompInterface);

    value = SCDynamicStoreCopyValue(G_scd_session, kSCDynamicStoreDomainSetup);
    if (value == NULL) {
	my_log(LOG_INFO, "IP6Configuration needs PreferencesMonitor to run first");
    }
    my_CFRelease(&value);

    /* install run-time notifiers */
    notifier_init(G_scd_session);

    (void)update_interface_list();

    /* cache should already be populated by prefs monitor */
    configure_from_cache(G_scd_session);

    return;
}