]> git.saurik.com Git - apple/configd.git/blobdiff - Plugins/ATconfig/atconfig.c
configd-130.tar.gz
[apple/configd.git] / Plugins / ATconfig / atconfig.c
diff --git a/Plugins/ATconfig/atconfig.c b/Plugins/ATconfig/atconfig.c
new file mode 100644 (file)
index 0000000..7bf129e
--- /dev/null
@@ -0,0 +1,1570 @@
+/*
+ * Copyright (c) 2000-2003 Apple Computer, 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@
+ */
+
+/*
+ * Modification History
+ *
+ * March 15, 2003              Allan Nathanson <ajn@apple.com>
+ * - startup/shutdown AT networking without Kicker's help and
+ *   publish the state information after the configuration is
+ *   active.
+ *
+ * April 29, 2002              Allan Nathanson <ajn@apple.com>
+ * - add global state information (primary service, interface)
+ *
+ * June 24, 2001               Allan Nathanson <ajn@apple.com>
+ * - update to public SystemConfiguration.framework APIs
+ *
+ * July 7, 2000                        Allan Nathanson <ajn@apple.com>
+ * - initial revision
+ */
+
+
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/utsname.h>
+#include <sys/wait.h>
+#include <net/if.h>
+#include <netat/appletalk.h>
+#include <netat/at_var.h>
+#include <AppleTalk/at_paths.h>
+#include <AppleTalk/at_proto.h>
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <SystemConfiguration/SystemConfiguration.h>
+#include <SystemConfiguration/SCPrivate.h>
+#include <SystemConfiguration/SCDPlugin.h>
+#include <SystemConfiguration/SCValidation.h>
+
+#include "cache.h"
+#include "cfManager.h"
+
+#define HOSTCONFIG     "/etc/hostconfig"
+
+static SCDynamicStoreRef       store           = NULL;
+static CFRunLoopSourceRef      storeRls        = NULL;
+
+static int                     curState        = 0;    // abs(state) == sequence #, < 0 == stop, > 0 == start
+static CFMutableDictionaryRef  curGlobals      = NULL;
+static CFMutableArrayRef       curConfigFile   = NULL;
+static CFMutableDictionaryRef  curDefaults     = NULL;
+static CFMutableDictionaryRef  curStartup      = NULL;
+
+static Boolean                 _verbose        = FALSE;
+
+
+static void    stopAppleTalk (CFRunLoopTimerRef timer, void *info);
+static void    startAppleTalk(CFRunLoopTimerRef timer, void *info);
+
+
+static void
+updateDefaults(const void *key, const void *val, void *context)
+{
+       CFStringRef             ifName          = (CFStringRef)key;
+       CFDictionaryRef         oldDict;
+       CFDictionaryRef         newDict         = (CFDictionaryRef)val;
+       CFNumberRef             defaultNode;
+       CFNumberRef             defaultNetwork;
+       CFStringRef             defaultZone;
+
+       if (!CFDictionaryGetValueIfPresent(curDefaults, ifName, (const void **)&oldDict) ||
+           !CFEqual(oldDict, newDict)) {
+               char            ifr_name[IFNAMSIZ+1];
+
+               bzero(&ifr_name, sizeof(ifr_name));
+               if (!_SC_cfstring_to_cstring(ifName, ifr_name, sizeof(ifr_name), kCFStringEncodingASCII)) {
+                       SCLog(TRUE, LOG_ERR, CFSTR("could not convert interface name to C string"));
+                       return;
+               }
+
+               /*
+                * Set preferred Network and Node ID
+                */
+               if (CFDictionaryGetValueIfPresent(newDict,
+                                                 kSCPropNetAppleTalkNetworkID,
+                                                 (const void **)&defaultNetwork) &&
+                   CFDictionaryGetValueIfPresent(newDict,
+                                                 kSCPropNetAppleTalkNodeID,
+                                                 (const void **)&defaultNode)
+                   ) {
+                       struct at_addr  init_address;
+                       int             status;
+
+                       /*
+                        * set the default node and network
+                        */
+                       CFNumberGetValue(defaultNetwork, kCFNumberShortType, &init_address.s_net);
+                       CFNumberGetValue(defaultNode,    kCFNumberCharType,  &init_address.s_node);
+                       status = at_setdefaultaddr(ifr_name, &init_address);
+                       if (status == -1) {
+                               SCLog(TRUE, LOG_ERR, CFSTR("at_setdefaultaddr() failed"));
+                               return;
+                       }
+               }
+
+               /*
+                * Set default zone
+                */
+               if (CFDictionaryGetValueIfPresent(newDict,
+                                                 kSCPropNetAppleTalkDefaultZone,
+                                                 (const void **)&defaultZone)
+                   ) {
+                       int             status;
+                       at_nvestr_t     zone;
+
+                       /*
+                        * set the "default zone" for this interface
+                        */
+                       bzero(&zone, sizeof(zone));
+                       if (!_SC_cfstring_to_cstring(defaultZone, zone.str, sizeof(zone.str), kCFStringEncodingASCII)) {
+                               SCLog(TRUE, LOG_ERR, CFSTR("could not convert default zone to C string"));
+                               return;
+                       }
+
+                       zone.len = strlen(zone.str);
+                       status = at_setdefaultzone(ifr_name, &zone);
+                       if (status == -1) {
+                               SCLog(TRUE, LOG_ERR, CFSTR("at_setdefaultzone() failed"));
+                               return;
+                       }
+               }
+       }
+
+       return;
+}
+
+
+static void
+addZoneToPorts(const void *key, const void *val, void *context)
+{
+       CFStringRef             zone            = (CFStringRef)key;
+       CFArrayRef              ifArray         = (CFArrayRef)val;
+       CFMutableArrayRef       zones           = (CFMutableArrayRef)context;
+       CFStringRef             ifList;
+       CFStringRef             configInfo;
+
+       ifList = CFStringCreateByCombiningStrings(NULL, ifArray, CFSTR(":"));
+       configInfo = CFStringCreateWithFormat(NULL, NULL, CFSTR(":%@:%@"), zone, ifList);
+       CFArrayAppendValue(zones, configInfo);
+       CFRelease(configInfo);
+       CFRelease(ifList);
+       return;
+}
+
+
+/*
+ * Function: parse_component
+ * Purpose:
+ *   Given a string 'key' and a string prefix 'prefix',
+ *   return the next component in the slash '/' separated
+ *   key.
+ *
+ * Examples:
+ * 1. key = "a/b/c" prefix = "a/"
+ *    returns "b"
+ * 2. key = "a/b/c" prefix = "a/b/"
+ *    returns "c"
+ */
+static CFStringRef
+parse_component(CFStringRef key, CFStringRef prefix)
+{
+       CFMutableStringRef      comp;
+       CFRange                 range;
+
+       if (CFStringHasPrefix(key, prefix) == FALSE) {
+               return NULL;
+       }
+       comp = CFStringCreateMutableCopy(NULL, 0, key);
+       CFStringDelete(comp, CFRangeMake(0, CFStringGetLength(prefix)));
+       range = CFStringFind(comp, CFSTR("/"), 0);
+       if (range.location == kCFNotFound) {
+               return comp;
+       }
+       range.length = CFStringGetLength(comp) - range.location;
+       CFStringDelete(comp, range);
+       return comp;
+}
+
+
+static CFDictionaryRef
+entity_one(SCDynamicStoreRef store, CFStringRef key)
+{
+       CFDictionaryRef         ent_dict        = NULL;
+       CFDictionaryRef         if_dict         = NULL;
+       CFStringRef             if_key          = NULL;
+       CFStringRef             if_port;
+       CFMutableDictionaryRef  new_dict        = NULL;
+       static CFStringRef      pre             = NULL;
+       CFStringRef             serviceID       = NULL;
+       CFStringRef             serviceType;
+
+       if (!pre) {
+               pre = SCDynamicStoreKeyCreate(NULL,
+                                             CFSTR("%@/%@/%@/"),
+                                             kSCDynamicStoreDomainSetup,
+                                             kSCCompNetwork,
+                                             kSCCompService);
+       }
+
+       /*
+        * get entity dictionary for service
+        */
+       ent_dict = cache_SCDynamicStoreCopyValue(store, key);
+       if (!isA_CFDictionary(ent_dict)) {
+               goto done;
+       }
+
+       /*
+        * get interface dictionary for service
+        */
+       serviceID = parse_component(key, pre);
+       if (!serviceID) {
+               goto done;
+       }
+
+       if_key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
+                                                            kSCDynamicStoreDomainSetup,
+                                                            serviceID,
+                                                            kSCEntNetInterface);
+       if_dict = cache_SCDynamicStoreCopyValue(store, if_key);
+       CFRelease(if_key);
+       if (!isA_CFDictionary(if_dict)) {
+               goto done;
+       }
+
+       /* check the interface type */
+       serviceType = CFDictionaryGetValue(if_dict,
+                                          kSCPropNetInterfaceType);
+       if (!isA_CFString(serviceType) ||
+           !CFEqual(serviceType, kSCValNetInterfaceTypeEthernet)) {
+               /* sorry, no AT networking on this interface */
+               goto done;
+       }
+
+       /*
+        * get port name (from interface dictionary).
+        */
+       if_port = CFDictionaryGetValue(if_dict, kSCPropNetInterfaceDeviceName);
+       if (!isA_CFString(if_port)) {
+               goto done;
+       }
+
+       /*
+        * add ServiceID and interface port name to entity dictionary.
+        */
+       new_dict = CFDictionaryCreateMutableCopy(NULL, 0, ent_dict);
+       CFDictionarySetValue(new_dict, CFSTR("ServiceID"), serviceID);
+       CFDictionarySetValue(new_dict, kSCPropNetInterfaceDeviceName, if_port);
+
+    done:
+
+       if (ent_dict)   CFRelease(ent_dict);
+       if (if_dict)    CFRelease(if_dict);
+       if (serviceID)  CFRelease(serviceID);
+       return (CFDictionaryRef)new_dict;
+}
+
+
+static CFArrayRef
+entity_all(SCDynamicStoreRef store, CFStringRef entity, CFArrayRef order)
+{
+       CFMutableArrayRef       defined = NULL;
+       CFIndex                 i;
+       CFIndex                 n;
+       CFMutableArrayRef       ordered = NULL;
+       CFStringRef             pattern;
+
+       ordered = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+
+       pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
+                                                             kSCDynamicStoreDomainSetup,
+                                                             kSCCompAnyRegex,
+                                                             entity);
+       defined = (CFMutableArrayRef)SCDynamicStoreCopyKeyList(store, pattern);
+       CFRelease(pattern);
+       if (defined && (CFArrayGetCount(defined) > 0)) {
+               CFArrayRef      tmp;
+
+               tmp = defined;
+               defined = CFArrayCreateMutableCopy(NULL, 0, tmp);
+               CFRelease(tmp);
+       } else {
+               goto done;
+       }
+
+       n = order ? CFArrayGetCount(order) : 0;
+       for (i = 0; i < n; i++) {
+               CFDictionaryRef dict;
+               CFStringRef     key;
+               CFIndex         j;
+               CFStringRef     service;
+
+               service = CFArrayGetValueAtIndex(order, i);
+               if (!isA_CFString(service)) {
+                       continue;
+               }
+
+               key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
+                                                                 kSCDynamicStoreDomainSetup,
+                                                                 service,
+                                                                 entity);
+               dict = entity_one(store, key);
+               if (dict) {
+                       CFArrayAppendValue(ordered, dict);
+                       CFRelease(dict);
+               }
+
+               j = CFArrayGetFirstIndexOfValue(defined,
+                                               CFRangeMake(0, CFArrayGetCount(defined)),
+                                               key);
+               if (j != kCFNotFound) {
+                       CFArrayRemoveValueAtIndex(defined, j);
+               }
+
+               CFRelease(key);
+       }
+
+       n = CFArrayGetCount(defined);
+       for (i = 0; i < n; i++) {
+               CFDictionaryRef dict;
+               CFStringRef     key;
+
+               key  = CFArrayGetValueAtIndex(defined, i);
+               dict = entity_one(store, key);
+               if (dict) {
+                       CFArrayAppendValue(ordered, dict);
+                       CFRelease(dict);
+               }
+       }
+
+    done:
+
+       if (defined)    CFRelease(defined);
+       if (CFArrayGetCount(ordered) == 0) {
+               CFRelease(ordered);
+               ordered = NULL;
+       }
+       return ordered;
+}
+
+
+static void
+encodeName(CFStringRef                 name,
+          CFStringEncoding             encoding,
+          CFMutableDictionaryRef       startup,
+          CFMutableDictionaryRef       globals)
+{
+       CFDataRef               bytes;
+       CFMutableStringRef      encodedName = NULL;
+       CFIndex                 len;
+
+       if (!isA_CFString(name)) {
+               return;
+       }
+
+       if (encoding == kCFStringEncodingASCII) {
+               encodedName = (CFMutableStringRef)CFStringCreateCopy(NULL, name);
+               goto done;
+       }
+
+       /*
+        * encode the potentially non-printable string
+        */
+       bytes = CFStringCreateExternalRepresentation(NULL,
+                                                    name,
+                                                    encoding,
+                                                    0);
+       if (bytes) {
+               unsigned char   *byte;
+               CFIndex         i;
+
+               /*
+                * check if the MacRoman string can be represented as ASCII
+                */
+               if (encoding == kCFStringEncodingMacRoman) {
+                       CFDataRef       ascii;
+
+                       ascii = CFStringCreateExternalRepresentation(NULL,
+                                                                    name,
+                                                                    kCFStringEncodingASCII,
+                                                                    0);
+                       if (ascii) {
+                               CFRelease(ascii);
+                               CFRelease(bytes);
+                               encodedName = (CFMutableStringRef)CFStringCreateCopy(NULL, name);
+                               goto done;
+                       }
+               }
+
+               encodedName = CFStringCreateMutable(NULL, 0);
+
+               len  = CFDataGetLength(bytes);
+               byte = (unsigned char *)CFDataGetBytePtr(bytes);
+               for (i = 0; i < len; i++, byte++) {
+                       CFStringAppendFormat(encodedName,
+                                            NULL,
+                                            CFSTR("%02x"),
+                                            *byte);
+               }
+
+               /*
+                * add "encoded string" markers
+                */
+               CFStringInsert(encodedName, 0, CFSTR("*"));
+               CFStringAppend(encodedName,    CFSTR("*"));
+
+               CFRelease(bytes);
+       }
+
+    done :
+
+       if (encodedName) {
+               if (startup) {
+                       /* update "startup" dictionary */
+                       CFDictionaryAddValue(startup, CFSTR("APPLETALK_HOSTNAME"), encodedName);
+               }
+
+               if (globals) {
+                       CFNumberRef     num;
+
+                       /* update "global" dictionary */
+                       num = CFNumberCreate(NULL, kCFNumberIntType, &encoding);
+                       CFDictionaryAddValue(globals, kSCPropNetAppleTalkComputerName,         name);
+                       CFDictionaryAddValue(globals, kSCPropNetAppleTalkComputerNameEncoding, num);
+                       CFRelease(num);
+               }
+
+               CFRelease(encodedName);
+       }
+
+       return;
+}
+
+
+static boolean_t
+updateConfiguration(int *newState)
+{
+       boolean_t               changed                 = FALSE;
+       CFStringRef             computerName;
+       CFStringEncoding        computerNameEncoding;
+       CFArrayRef              configuredServices      = NULL;
+       CFDictionaryRef         dict;
+       CFIndex                 i;
+       CFIndex                 ifCount                 = 0;
+       CFMutableArrayRef       info                    = NULL;
+       CFArrayRef              interfaces              = NULL;
+       CFStringRef             key;
+       CFArrayRef              keys;
+       CFIndex                 n;
+       CFMutableArrayRef       newConfigFile;
+       CFMutableDictionaryRef  newDefaults;
+       CFMutableDictionaryRef  newDict;
+       CFMutableDictionaryRef  newGlobals;
+       CFMutableDictionaryRef  newGlobalsX;                    /* newGlobals without ServiceID */
+       CFMutableDictionaryRef  newStartup;
+       CFMutableDictionaryRef  newZones;
+       CFNumberRef             num;
+       CFMutableDictionaryRef  curGlobalsX;                    /* curGlobals without ServiceID */
+       CFStringRef             pattern;
+       boolean_t               postGlobals             = FALSE;
+       CFStringRef             primaryPort             = NULL; /* primary interface */
+       CFStringRef             primaryZone             = NULL;
+       CFArrayRef              serviceOrder            = NULL;
+       CFDictionaryRef         setGlobals              = NULL;
+
+       cache_open();
+
+       /*
+        * establish the "new" AppleTalk configuration
+        */
+       *newState     = curState;
+       newConfigFile = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+       newGlobals    = CFDictionaryCreateMutable(NULL,
+                                                 0,
+                                                 &kCFTypeDictionaryKeyCallBacks,
+                                                 &kCFTypeDictionaryValueCallBacks);
+       newDefaults   = CFDictionaryCreateMutable(NULL,
+                                                 0,
+                                                 &kCFTypeDictionaryKeyCallBacks,
+                                                 &kCFTypeDictionaryValueCallBacks);
+       newStartup    = CFDictionaryCreateMutable(NULL,
+                                                 0,
+                                                 &kCFTypeDictionaryKeyCallBacks,
+                                                 &kCFTypeDictionaryValueCallBacks);
+       newZones      = CFDictionaryCreateMutable(NULL,
+                                                 0,
+                                                 &kCFTypeDictionaryKeyCallBacks,
+                                                 &kCFTypeDictionaryValueCallBacks);
+
+       /* initialize overall state */
+       CFDictionarySetValue(newStartup, CFSTR("APPLETALK"), CFSTR("-NO-"));
+
+       /*
+        * get the global settings (ServiceOrder, ComputerName, ...)
+        */
+       key = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL,
+                                                        kSCDynamicStoreDomainSetup,
+                                                        kSCEntNetAppleTalk);
+       setGlobals = cache_SCDynamicStoreCopyValue(store, key);
+       CFRelease(key);
+       if (setGlobals) {
+               if (isA_CFDictionary(setGlobals)) {
+                       /* get service order */
+                       serviceOrder = CFDictionaryGetValue(setGlobals,
+                                                           kSCPropNetServiceOrder);
+                       serviceOrder = isA_CFArray(serviceOrder);
+                       if (serviceOrder) {
+                               CFRetain(serviceOrder);
+                       }
+               } else {
+                       CFRelease(setGlobals);
+                       setGlobals = NULL;
+               }
+       }
+
+       /*
+        * if we don't have an AppleTalk ServiceOrder, use IPv4's (if defined)
+        */
+       if (!serviceOrder) {
+               key = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL,
+                                                                kSCDynamicStoreDomainSetup,
+                                                                kSCEntNetIPv4);
+               dict = cache_SCDynamicStoreCopyValue(store, key);
+               CFRelease(key);
+               if (dict) {
+                       if (isA_CFDictionary(dict)) {
+                               serviceOrder = CFDictionaryGetValue(dict,
+                                                                   kSCPropNetServiceOrder);
+                               serviceOrder = isA_CFArray(serviceOrder);
+                               if (serviceOrder) {
+                                       CFRetain(serviceOrder);
+                               }
+                       }
+                       CFRelease(dict);
+               }
+       }
+
+       /*
+        * get the list of ALL configured services
+        */
+       configuredServices = entity_all(store, kSCEntNetAppleTalk, serviceOrder);
+       if (configuredServices) {
+               ifCount = CFArrayGetCount(configuredServices);
+       }
+
+       if (serviceOrder)       CFRelease(serviceOrder);
+
+       /*
+        * get the list of ALL active interfaces
+        */
+       key  = SCDynamicStoreKeyCreateNetworkInterface(NULL, kSCDynamicStoreDomainState);
+       dict = cache_SCDynamicStoreCopyValue(store, key);
+       CFRelease(key);
+       if (dict) {
+               if (isA_CFDictionary(dict)) {
+                       interfaces = CFDictionaryGetValue(dict,
+                                                         kSCDynamicStorePropNetInterfaces);
+                       interfaces = isA_CFArray(interfaces);
+                       if (interfaces) {
+                               CFRetain(interfaces);
+                       }
+               }
+               CFRelease(dict);
+       }
+
+       /*
+        * get the list of previously configured services
+        */
+       pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
+                                                             kSCDynamicStoreDomainState,
+                                                             kSCCompAnyRegex,
+                                                             kSCEntNetAppleTalk);
+       keys = SCDynamicStoreCopyKeyList(store, pattern);
+       CFRelease(pattern);
+       if (keys) {
+               info = CFArrayCreateMutableCopy(NULL, 0, keys);
+               CFRelease(keys);
+       }
+
+       /*
+        * iterate over each configured service to establish the new
+        * configuration.
+        */
+       for (i = 0; i < ifCount; i++) {
+               CFDictionaryRef         service;
+               CFStringRef             ifName;
+               CFStringRef             configMethod;
+               CFMutableStringRef      portConfig      = NULL;
+               CFArrayRef              networkRange;   /* for seed ports, CFArray[2] of CFNumber (lo, hi) */
+               int                     sNetwork;
+               int                     eNetwork;
+               CFArrayRef              zoneList;       /* for seed ports, CFArray[] of CFString (zones names) */
+               CFIndex                 zCount;
+               CFIndex                 j;
+               CFMutableDictionaryRef  ifDefaults      = NULL;
+               CFNumberRef             defaultNetwork;
+               CFNumberRef             defaultNode;
+               CFStringRef             defaultZone;
+
+               /* get AppleTalk service dictionary */
+               service = CFArrayGetValueAtIndex(configuredServices, i);
+
+               /* get interface name */
+               ifName  = CFDictionaryGetValue(service, kSCPropNetInterfaceDeviceName);
+
+               /* check inteface availability */
+               if (!interfaces ||
+                   !CFArrayContainsValue(interfaces, CFRangeMake(0, CFArrayGetCount(interfaces)), ifName)) {
+                       /* if interface not available */
+                       goto nextIF;
+               }
+
+               /* check interface link status */
+               key = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL,
+                                                                   kSCDynamicStoreDomainState,
+                                                                   ifName,
+                                                                   kSCEntNetLink);
+               dict = cache_SCDynamicStoreCopyValue(store, key);
+               CFRelease(key);
+               if (dict) {
+                       Boolean linkStatus      = TRUE;  /* assume the link is "up" */
+                       Boolean ifDetaching     = FALSE; /* assume link is not detaching */
+
+                       /* the link key for this interface is available */
+                       if (isA_CFDictionary(dict)) {
+                               CFBooleanRef    bVal;
+
+                               bVal = CFDictionaryGetValue(dict, kSCPropNetLinkActive);
+                               if (isA_CFBoolean(bVal)) {
+                                       linkStatus = CFBooleanGetValue(bVal);
+                               }
+
+                               /* check if interface is detaching - value
+                                  doesn't really matter, only that it exists */
+                               ifDetaching = CFDictionaryContainsKey(dict, kSCPropNetLinkDetaching);
+                       }
+                       CFRelease(dict);
+
+                       if (!linkStatus || ifDetaching) {
+                               /* if link status down or the interface is detaching */
+                               goto nextIF;
+                       }
+               }
+
+               /*
+                * Determine configuration method for this service
+                */
+               configMethod = CFDictionaryGetValue(service, kSCPropNetAppleTalkConfigMethod);
+               if (!isA_CFString(configMethod)) {
+                       /* if no ConfigMethod */
+                       goto nextIF;
+               }
+
+               if (!CFEqual(configMethod, kSCValNetAppleTalkConfigMethodNode      ) &&
+                   !CFEqual(configMethod, kSCValNetAppleTalkConfigMethodRouter    ) &&
+                   !CFEqual(configMethod, kSCValNetAppleTalkConfigMethodSeedRouter)) {
+                       /* if not one of the expected values, disable */
+                       SCLog(TRUE, LOG_NOTICE,
+                             CFSTR("Unexpected AppleTalk ConfigMethod: %@"),
+                             configMethod);
+                       goto nextIF;
+               }
+
+               /*
+                * the first service to be defined will always be "primary"
+                */
+               if (CFArrayGetCount(newConfigFile) == 0) {
+                       CFDictionaryRef active;
+
+                       CFDictionarySetValue(newGlobals,
+                                            kSCDynamicStorePropNetPrimaryService,
+                                            CFDictionaryGetValue(service, CFSTR("ServiceID")));
+                       CFDictionarySetValue(newGlobals,
+                                            kSCDynamicStorePropNetPrimaryInterface,
+                                            ifName);
+
+                       /* and check if AT newtorking is active on the primary interface */
+                       key = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL,
+                                                                           kSCDynamicStoreDomainState,
+                                                                           ifName,
+                                                                           kSCEntNetAppleTalk);
+                       active = cache_SCDynamicStoreCopyValue(store, key);
+                       CFRelease(key);
+                       if (active) {
+                               if (isA_CFDictionary(active)) {
+                                       postGlobals = TRUE;
+                               }
+                               CFRelease(active);
+                       }
+               }
+
+               /*
+                * define the port
+                */
+               portConfig = CFStringCreateMutable(NULL, 0);
+               CFStringAppendFormat(portConfig, NULL, CFSTR("%@:"), ifName);
+
+               if (CFEqual(configMethod, kSCValNetAppleTalkConfigMethodSeedRouter)) {
+                       CFNumberRef     num;
+
+                       /*
+                        * we have been asked to configure this interface as a
+                        * seed port. Ensure that we have been provided at least
+                        * one network number, have been provided with at least
+                        * one zonename, ...
+                        */
+
+                       networkRange = CFDictionaryGetValue(service,
+                                                           kSCPropNetAppleTalkSeedNetworkRange);
+                       if (!isA_CFArray(networkRange) || (CFArrayGetCount(networkRange) == 0)) {
+                               SCLog(TRUE, LOG_NOTICE,
+                                     CFSTR("AppleTalk configuration error (%@)"),
+                                     kSCPropNetAppleTalkSeedNetworkRange);
+                               goto nextIF;
+                       }
+
+                       /*
+                        * establish the starting and ending network numbers
+                        */
+                       num = CFArrayGetValueAtIndex(networkRange, 0);
+                       if (!isA_CFNumber(num)) {
+                               SCLog(TRUE, LOG_NOTICE,
+                                     CFSTR("AppleTalk configuration error (%@)"),
+                                     kSCPropNetAppleTalkSeedNetworkRange);
+                               goto nextIF;
+                       }
+                       CFNumberGetValue(num, kCFNumberIntType, &sNetwork);
+                       eNetwork = sNetwork;
+
+                       if (CFArrayGetCount(networkRange) > 1) {
+                               num = CFArrayGetValueAtIndex(networkRange, 1);
+                               if (!isA_CFNumber(num)) {
+                                       SCLog(TRUE, LOG_NOTICE,
+                                             CFSTR("AppleTalk configuration error (%@)"),
+                                             kSCPropNetAppleTalkSeedNetworkRange);
+                                       goto nextIF;
+                               }
+                               CFNumberGetValue(num, kCFNumberIntType, &eNetwork);
+                       }
+                       CFStringAppendFormat(portConfig, NULL, CFSTR("%d:%d:"), sNetwork, eNetwork);
+
+                       /*
+                        * establish the zones associated with this port
+                        */
+                       zoneList = CFDictionaryGetValue(service,
+                                                       kSCPropNetAppleTalkSeedZones);
+                       if (!isA_CFArray(zoneList)) {
+                               SCLog(TRUE, LOG_NOTICE,
+                                     CFSTR("AppleTalk configuration error (%@)"),
+                                     kSCPropNetAppleTalkSeedZones);
+                               goto nextIF;
+                       }
+
+                       zCount = CFArrayGetCount(zoneList);
+                       for (j = 0; j < zCount; j++) {
+                               CFStringRef             zone;
+                               CFArrayRef              ifList;
+                               CFMutableArrayRef       newIFList;
+
+                               zone = CFArrayGetValueAtIndex(zoneList, j);
+                               if (!isA_CFString(zone)) {
+                                       continue;
+                               }
+
+                               if (CFDictionaryGetValueIfPresent(newZones, zone, (const void **)&ifList)) {
+                                       /* known zone */
+                                       newIFList = CFArrayCreateMutableCopy(NULL, 0, ifList);
+                               } else {
+                                       /* new zone */
+                                       newIFList = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+                               }
+                               CFArrayAppendValue(newIFList, ifName);
+                               CFArraySortValues(newIFList,
+                                                 CFRangeMake(0, CFArrayGetCount(newIFList)),
+                                                 (CFComparatorFunction)CFStringCompare,
+                                                 NULL);
+                               CFDictionarySetValue(newZones, zone, newIFList);
+                               CFRelease(newIFList);
+
+                               /*
+                                * flag the default zone
+                                */
+                               if (!primaryZone) {
+                                       primaryZone = CFRetain(zone);
+                               }
+                       }
+                       if (!primaryZone) {
+                               SCLog(TRUE, LOG_NOTICE,
+                                     CFSTR("AppleTalk configuration error (%@)"),
+                                     kSCPropNetAppleTalkSeedZones);
+                               goto nextIF;
+                       }
+               }
+
+               /* get the (per-interface) "Computer Name" */
+               computerName = CFDictionaryGetValue(service,
+                                                   kSCPropNetAppleTalkComputerName);
+               if (CFDictionaryGetValueIfPresent(service,
+                                                 kSCPropNetAppleTalkComputerNameEncoding,
+                                                 (const void **)&num) &&
+                   isA_CFNumber(num)) {
+                       CFNumberGetValue(num, kCFNumberIntType, &computerNameEncoding);
+               } else {
+                       computerNameEncoding = CFStringGetSystemEncoding();
+               }
+               encodeName(computerName, computerNameEncoding, newStartup, newGlobals);
+
+               /*
+                * declare the first configured AppleTalk service / interface
+                * as the "home port".
+                */
+               if (CFArrayGetCount(newConfigFile) == 0) {
+                       CFStringAppend(portConfig, CFSTR("*"));
+                       primaryPort = CFRetain(ifName);
+               }
+               CFArrayAppendValue(newConfigFile, portConfig);
+
+               /*
+                * get the per-interface defaults
+                */
+               ifDefaults = CFDictionaryCreateMutable(NULL,
+                                                      0,
+                                                      &kCFTypeDictionaryKeyCallBacks,
+                                                      &kCFTypeDictionaryValueCallBacks);
+
+               defaultNetwork = CFDictionaryGetValue(service, kSCPropNetAppleTalkNetworkID);
+               defaultNode    = CFDictionaryGetValue(service, kSCPropNetAppleTalkNodeID);
+               if (isA_CFNumber(defaultNetwork) && isA_CFNumber(defaultNode)) {
+                       /*
+                        * set the default node and network
+                        */
+                       CFDictionarySetValue(ifDefaults,
+                                            kSCPropNetAppleTalkNetworkID,
+                                            defaultNetwork);
+                       CFDictionarySetValue(ifDefaults,
+                                            kSCPropNetAppleTalkNodeID,
+                                            defaultNode);
+               }
+
+               if ((CFDictionaryGetValueIfPresent(service,
+                                                  kSCPropNetAppleTalkDefaultZone,
+                                                  (const void **)&defaultZone) == TRUE)) {
+                       /*
+                        * set the default zone for this interface
+                        */
+                       CFDictionarySetValue(ifDefaults,
+                                            kSCPropNetAppleTalkDefaultZone,
+                                            defaultZone);
+               }
+
+               CFDictionarySetValue(newDefaults, ifName, ifDefaults);
+               CFRelease(ifDefaults);
+
+               switch (CFArrayGetCount(newConfigFile)) {
+                       case 1:
+                               /*
+                                * first AppleTalk interface
+                                */
+                               CFDictionarySetValue(newStartup, CFSTR("APPLETALK"), ifName);
+                               break;
+                       case 2:
+                               /* second AppleTalk interface */
+                               if (!CFEqual(CFDictionaryGetValue(newStartup, CFSTR("APPLETALK")),
+                                            CFSTR("-ROUTER-"))) {
+                                       /*
+                                        * if not routing (yet), configure as multi-home
+                                        */
+                                       CFDictionarySetValue(newStartup, CFSTR("APPLETALK"), CFSTR("-MULTIHOME-"));
+                               }
+                               break;
+               }
+
+               if (CFEqual(configMethod, kSCValNetAppleTalkConfigMethodRouter) ||
+                   CFEqual(configMethod, kSCValNetAppleTalkConfigMethodSeedRouter)) {
+                       /* if not a simple node, enable routing */
+                       CFDictionarySetValue(newStartup, CFSTR("APPLETALK"), CFSTR("-ROUTER-"));
+               }
+
+               /*
+                * establish the State:/Network/Service/nnn/AppleTalk key info
+                */
+               key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
+                                                                 kSCDynamicStoreDomainState,
+                                                                 CFDictionaryGetValue(service, CFSTR("ServiceID")),
+                                                                 kSCEntNetAppleTalk);
+               newDict = CFDictionaryCreateMutable(NULL,
+                                                   0,
+                                                   &kCFTypeDictionaryKeyCallBacks,
+                                                   &kCFTypeDictionaryValueCallBacks);
+               CFDictionaryAddValue(newDict, kSCPropInterfaceName, ifName);
+               cache_SCDynamicStoreSetValue(store, key, newDict);
+               CFRelease(newDict);
+               if (info) {
+                       j = CFArrayGetFirstIndexOfValue(info,
+                                                       CFRangeMake(0, CFArrayGetCount(info)),
+                                                       key);
+                       if (j != kCFNotFound) {
+                               CFArrayRemoveValueAtIndex(info, j);
+                       }
+               }
+               CFRelease(key);
+
+           nextIF :
+
+               if (portConfig) CFRelease(portConfig);
+       }
+
+       if (primaryZone) {
+               CFArrayRef              ifList;
+               CFMutableArrayRef       newIFList;
+
+               ifList = CFDictionaryGetValue(newZones, primaryZone);
+               if (CFArrayContainsValue(ifList,
+                                        CFRangeMake(0, CFArrayGetCount(ifList)),
+                                        primaryPort)) {
+                       newIFList = CFArrayCreateMutableCopy(NULL, 0, ifList);
+                       CFArrayAppendValue(newIFList, CFSTR("*"));
+                       CFDictionarySetValue(newZones, primaryZone, newIFList);
+                       CFRelease(newIFList);
+               }
+               CFRelease(primaryZone);
+       }
+       if (primaryPort) {
+               CFRelease(primaryPort);
+       }
+
+       /* sort the ports */
+       i = CFArrayGetCount(newConfigFile);
+       CFArraySortValues(newConfigFile,
+                         CFRangeMake(0, i),
+                         (CFComparatorFunction)CFStringCompare,
+                         NULL);
+
+       /* add the zones to the configuration */
+       CFDictionaryApplyFunction(newZones, addZoneToPorts, newConfigFile);
+       CFRelease(newZones);
+
+       /* sort the zones */
+       CFArraySortValues(newConfigFile,
+                         CFRangeMake(i, CFArrayGetCount(newConfigFile)-i),
+                         (CFComparatorFunction)CFStringCompare,
+                         NULL);
+
+       /* ensure that the last line of the configuration file is terminated */
+       CFArrayAppendValue(newConfigFile, CFSTR(""));
+
+       /*
+        * Check if we have a "ComputerName" and look elsewhere if we don't have
+        * one yet.
+        */
+       if (!CFDictionaryContainsKey(newStartup, CFSTR("APPLETALK_HOSTNAME")) &&
+           (setGlobals != NULL)) {
+               computerName = CFDictionaryGetValue(setGlobals,
+                                                   kSCPropNetAppleTalkComputerName);
+               if (CFDictionaryGetValueIfPresent(setGlobals,
+                                                 kSCPropNetAppleTalkComputerNameEncoding,
+                                                 (const void **)&num) &&
+                   isA_CFNumber(num)) {
+                       CFNumberGetValue(num, kCFNumberIntType, &computerNameEncoding);
+               } else {
+                       computerNameEncoding = CFStringGetSystemEncoding();
+               }
+               encodeName(computerName, computerNameEncoding, newStartup, newGlobals);
+       }
+       if (!CFDictionaryContainsKey(newStartup, CFSTR("APPLETALK_HOSTNAME"))) {
+               computerName = SCDynamicStoreCopyComputerName(store, &computerNameEncoding);
+               if (computerName) {
+                       encodeName(computerName, computerNameEncoding, newStartup, newGlobals);
+                       CFRelease(computerName);
+               }
+       }
+       if (!CFDictionaryContainsKey(newStartup, CFSTR("APPLETALK_HOSTNAME"))) {
+               struct utsname  name;
+
+               if (uname(&name) == 0) {
+                       computerName = CFStringCreateWithCString(NULL, name.nodename, kCFStringEncodingASCII);
+                       if (computerName) {
+                               encodeName(computerName, kCFStringEncodingASCII, NULL, newGlobals);
+                               CFRelease(computerName);
+                       }
+               }
+       }
+
+       /* compare the previous and current configurations */
+
+       curGlobalsX = CFDictionaryCreateMutableCopy(NULL, 0, curGlobals);
+       CFDictionaryRemoveValue(curGlobalsX, kSCDynamicStorePropNetPrimaryService);
+
+       newGlobalsX = CFDictionaryCreateMutableCopy(NULL, 0, newGlobals);
+       CFDictionaryRemoveValue(newGlobalsX, kSCDynamicStorePropNetPrimaryService);
+
+       key = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL,
+                                                        kSCDynamicStoreDomainState,
+                                                        kSCEntNetAppleTalk);
+
+       if (CFEqual(curGlobalsX   , newGlobalsX   ) &&
+           CFEqual(curConfigFile , newConfigFile) &&
+           CFEqual(curDefaults   , newDefaults  ) &&
+           CFEqual(curStartup    , newStartup   )
+           ) {
+               /*
+                * the configuration has not changed.
+                */
+
+               if (postGlobals) {
+                       /*
+                        * the requested configuration hasn't changed but we
+                        * now need to tell everyone that AppleTalk is active.
+                        */
+                       if (!SCDynamicStoreSetValue(store, key, newGlobals)) {
+                               SCLog(TRUE,
+                                     LOG_ERR,
+                                     CFSTR("SCDynamicStoreSetValue() failed: %s"),
+                                     SCErrorString(SCError()));
+                       }
+               }
+
+               CFRelease(newGlobals);
+               CFRelease(newConfigFile);
+               CFRelease(newDefaults);
+               CFRelease(newStartup);
+       } else if (CFArrayGetCount(newConfigFile) <= 1) {
+               /*
+                * the configuration has changed but there are no
+                * longer any interfaces configured for AppleTalk
+                * networking.
+                */
+
+               /*
+                * remove the global (State:/Network/Global/AppleTalk) key.
+                *
+                * Note: it will be restored later after AT networking has
+                *       been activated.
+                */
+
+               /* remove the (/etc/appletalk.cfg) configuration file */
+               (void)unlink(AT_CFG_FILE);
+
+               /*
+                * update the per-service (and global) state
+                */
+               cache_SCDynamicStoreRemoveValue(store, key);    // remove State:/Network/Global/AppleTalk
+               n = CFArrayGetCount(info);
+               for (i = 0; i < n; i++) {
+                       CFStringRef     xKey    = CFArrayGetValueAtIndex(info, i);
+
+                       cache_SCDynamicStoreRemoveValue(store, xKey);
+               }
+               cache_write(store);
+
+               /* flag this as a new configuration */
+               *newState = -(abs(curState) + 1);
+               changed = TRUE;
+       } else {
+               /*
+                * the configuration has changed.
+                */
+
+               /* update the (/etc/appletalk.cfg) configuration file */
+               configWrite(AT_CFG_FILE, newConfigFile);
+
+               /*
+                * update the per-service (and global) state
+                *
+                * Note: if present, we remove any existing global state key and allow it
+                *       to be restored after the stack has been re-started.
+                */
+               CFDictionaryApplyFunction(newDefaults, updateDefaults, NULL);
+               cache_SCDynamicStoreRemoveValue(store, key);    // remove State:/Network/Global/AppleTalk
+               n = CFArrayGetCount(info);
+               for (i = 0; i < n; i++) {
+                       CFStringRef     xKey    = CFArrayGetValueAtIndex(info, i);
+
+                       cache_SCDynamicStoreRemoveValue(store, xKey);
+               }
+               cache_write(store);
+
+               /* flag this as a new configuration */
+               *newState = abs(curState) + 1;
+               changed = TRUE;
+       }
+
+       CFRelease(curGlobalsX);
+       CFRelease(newGlobalsX);
+       CFRelease(key);
+
+       if (changed) {
+               CFRelease(curGlobals);
+               curGlobals    = newGlobals;
+               CFRelease(curConfigFile);
+               curConfigFile = newConfigFile;
+               CFRelease(curDefaults);
+               curDefaults   = newDefaults;
+               CFRelease(curStartup);
+               curStartup    = newStartup;
+       }
+
+       if (info)               CFRelease(info);
+       if (interfaces)         CFRelease(interfaces);
+       if (configuredServices) CFRelease(configuredServices);
+       if (setGlobals)         CFRelease(setGlobals);
+
+       cache_close();
+
+       return changed;
+}
+
+
+#include <sysexits.h>
+#define AT_CMD_SUCCESS         EX_OK   /* success */
+#define AT_CMD_ALREADY_RUNNING EX__MAX + 10
+#define AT_CMD_NOT_RUNNING     EX__MAX + 11
+
+
+static int
+stackState()
+{
+       int             ret;
+       int             sock;
+       at_state_t      state;
+
+       sock = socket(AF_APPLETALK, SOCK_RAW, 0);
+       ret  = ioctl(sock, AIOCGETSTATE, (caddr_t)&state);
+       (void)close(sock);
+       if (ret == -1) {
+               SCLog(TRUE, LOG_DEBUG, CFSTR("ioctl(AIOCGETSTATE) failed: %s"), strerror(errno));
+               return FALSE;
+       }
+
+       if (state.flags & AT_ST_STARTED) {
+               return abs(curState);
+       } else {
+               return -(abs(curState));
+       }
+}
+
+
+static pid_t   execCommand     = 0;
+static int     execRetry;
+
+
+static void
+stopComplete(pid_t pid, int status, struct rusage *rusage, void *context)
+{
+       execCommand = 0;
+
+       if (WIFEXITED(status)) {
+               switch (WEXITSTATUS(status)) {
+                       case AT_CMD_SUCCESS :
+                       case AT_CMD_NOT_RUNNING :
+                               SCLog(TRUE, LOG_NOTICE, CFSTR("AppleTalk shutdown complete"));
+                               if (curState > 0) {
+                                       // the stack is down but we really want it up
+                                       startAppleTalk(NULL, (void *)curState);
+                               }
+                               return;
+                       default :
+                               break;
+               }
+       }
+
+       SCLog(TRUE, LOG_ERR,
+             CFSTR("AppleTalk shutdown failed, status = %d%s"),
+             WEXITSTATUS(status),
+             (execRetry > 1) ? " (retrying)" : "");
+
+       // shutdown failed, retry
+       if (--execRetry > 0) {
+               CFRunLoopTimerContext   timerContext    = { 0, (void *)curState, NULL, NULL, NULL };
+               CFRunLoopTimerRef       timer;
+
+               timer = CFRunLoopTimerCreate(NULL,
+                                            CFAbsoluteTimeGetCurrent() + 1.0,
+                                            0.0,
+                                            0,
+                                            0,
+                                            stopAppleTalk,
+                                            &timerContext);
+               CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
+               CFRelease(timer);
+               return;
+       } else {
+               // we weren't able to stop
+               curState = stackState();
+       }
+
+       return;
+}
+
+
+static void
+stopAppleTalk(CFRunLoopTimerRef timer, void *info)
+{
+       char    *argv[] = { "appletalk",
+                           "-d",
+                           NULL };
+
+       if (execCommand == 0) {
+               SCLog(TRUE, LOG_NOTICE, CFSTR("AppleTalk shutdown"));
+       } else {
+               SCLog(TRUE, LOG_NOTICE, CFSTR("AppleTalk shutdown skipped, transition in progress"));
+               return;
+       }
+
+       execCommand = _SCDPluginExecCommand(stopComplete,               // callback
+                                           info,                       // context
+                                           0,                          // uid
+                                           0,                          // gid
+                                           "/usr/sbin/appletalk",      // path
+                                           argv);                      // argv
+
+       if (!timer) {
+               execRetry = 5;  // initialize retry count
+       }
+
+       return;
+}
+
+
+static void
+startComplete(pid_t pid, int status, struct rusage *rusage, void *context)
+{
+       execCommand = 0;
+
+       if (WIFEXITED(status)) {
+               switch (WEXITSTATUS(status)) {
+                       case AT_CMD_SUCCESS :
+                               SCLog(TRUE, LOG_NOTICE, CFSTR("AppleTalk startup complete"));
+                               if ((curState < 0) || (curState > (int)context)) {
+                                       // the stack is now up but we really want it down
+                                       stopAppleTalk(NULL, (void *)curState);
+                               }
+                               return;
+                       case AT_CMD_ALREADY_RUNNING :
+                               // the stack is already up but we're not sure
+                               // if the configuration is correct, restart
+                               SCLog(TRUE, LOG_NOTICE, CFSTR("AppleTalk already running, restarting"));
+                               stopAppleTalk(NULL, (void *)curState);
+                               return;
+                       default :
+                               break;
+               }
+       }
+
+       SCLog(TRUE, LOG_ERR,
+             CFSTR("AppleTalk startup failed, status = %d%s"),
+             WEXITSTATUS(status),
+             (execRetry > 1) ? " (retrying)" : "");
+
+       // startup failed, retry
+       if (--execRetry > 0) {
+               CFRunLoopTimerContext   timerContext    = { 0, (void *)curState, NULL, NULL, NULL };
+               CFRunLoopTimerRef       timer;
+
+               timer = CFRunLoopTimerCreate(NULL,
+                                            CFAbsoluteTimeGetCurrent() + 1.0,
+                                            0.0,
+                                            0,
+                                            0,
+                                            startAppleTalk,
+                                            &timerContext);
+               CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
+               CFRelease(timer);
+               return;
+       } else {
+               // we weren't able to start
+               curState = stackState();
+       }
+
+       return;
+}
+
+
+static void
+startAppleTalk(CFRunLoopTimerRef timer, void *info)
+{
+       int             argc            = 0;
+       char            *argv[8];
+       char            *computerName   = NULL;
+       char            *interface      = NULL;
+       CFStringRef     mode            = CFDictionaryGetValue(curStartup, CFSTR("APPLETALK"));
+       CFStringRef     name            = CFDictionaryGetValue(curStartup, CFSTR("APPLETALK_HOSTNAME"));
+
+       if (execCommand == 0) {
+               SCLog(TRUE, LOG_NOTICE, CFSTR("AppleTalk startup"));
+       } else {
+               SCLog(TRUE, LOG_NOTICE, CFSTR("AppleTalk startup skipped, transition in progress"));
+               return;
+       }
+
+       if (!mode) {
+               // Huh?
+               return;
+       }
+
+       // set command name
+       argv[argc++] = "appletalk";
+
+       // set hostname
+       if (name) {
+               computerName = _SC_cfstring_to_cstring(name, NULL, 0, kCFStringEncodingASCII);
+               if (computerName) {
+                       argv[argc++] = "-C";
+                       argv[argc++] = computerName;
+               } else {
+                       // could not convert name
+                       goto done;
+               }
+       }
+
+       // set mode
+       if (CFEqual(mode, CFSTR("-ROUTER-"))) {
+               argv[argc++] = "-r";
+       } else if (CFEqual(mode, CFSTR("-MULTIHOME-"))) {
+               argv[argc++] = "-x";
+       } else {
+               interface = _SC_cfstring_to_cstring(mode, NULL, 0, kCFStringEncodingASCII);
+               if (interface) {
+                       argv[argc++] = "-u";
+                       argv[argc++] = interface;
+               } else {
+                       // could not convert interface
+                       goto done;
+               }
+       }
+
+       // set non-interactive
+       argv[argc++] = "-q";
+
+       // close argument list
+       argv[argc++] = NULL;
+
+       execCommand = _SCDPluginExecCommand(startComplete,              // callback
+                                           info,                       // context
+                                           0,                          // uid
+                                           0,                          // gid
+                                           "/usr/sbin/appletalk",      // path
+                                           argv);                      // argv
+
+       if (!timer) {
+               execRetry = 5;  // initialize retry count
+       }
+
+    done :
+
+       if (computerName)       CFAllocatorDeallocate(NULL, computerName);
+       if (interface)          CFAllocatorDeallocate(NULL, interface);
+
+       return;
+}
+
+
+static void
+atConfigChangedCallback(SCDynamicStoreRef store, CFArrayRef changedKeys, void *arg)
+{
+       boolean_t       configChanged;
+       int             newState;
+
+       configChanged = updateConfiguration(&newState);
+
+       if (configChanged && (execCommand == 0)) {
+               // if the configuration has changed and we're not already transitioning
+               if (newState > 0) {
+                       if (curState > 0) {
+                               // already running, restart [with new configuration]
+                               stopAppleTalk(NULL, (void *)newState);
+                       } else {
+                               startAppleTalk(NULL, (void *)newState);
+                       }
+               } else {
+                       if (curState > 0) {
+                               stopAppleTalk(NULL, (void *)newState);
+                       }
+               }
+       }
+
+       curState = newState;
+
+       return;
+}
+
+
+void
+stop_ATconfig(CFRunLoopSourceRef stopRls)
+{
+       // cleanup
+
+       if (storeRls != NULL) {
+               CFRunLoopSourceInvalidate(storeRls);
+               CFRelease(storeRls);
+               storeRls = NULL;
+       }
+
+       if (store != NULL) {
+               CFRelease(store);
+               store = NULL;
+               CFRelease(curGlobals);
+               CFRelease(curConfigFile);
+               CFRelease(curDefaults);
+               CFRelease(curStartup);
+       }
+
+       CFRunLoopSourceSignal(stopRls);
+       return;
+}
+
+
+void
+load_ATconfig(CFBundleRef bundle, Boolean bundleVerbose)
+{
+       CFStringRef             key;
+       CFMutableArrayRef       keys            = NULL;
+       CFStringRef             pattern;
+       CFMutableArrayRef       patterns        = NULL;
+
+       if (bundleVerbose) {
+               _verbose = TRUE;
+       }
+
+       SCLog(_verbose, LOG_DEBUG, CFSTR("load() called"));
+       SCLog(_verbose, LOG_DEBUG, CFSTR("  bundle ID = %@"), CFBundleGetIdentifier(bundle));
+
+       /* initialize a few globals */
+
+       curGlobals    = CFDictionaryCreateMutable(NULL,
+                                                 0,
+                                                 &kCFTypeDictionaryKeyCallBacks,
+                                                 &kCFTypeDictionaryValueCallBacks);
+       curConfigFile = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+       curDefaults   = CFDictionaryCreateMutable(NULL,
+                                                 0,
+                                                 &kCFTypeDictionaryKeyCallBacks,
+                                                 &kCFTypeDictionaryValueCallBacks);
+       curStartup    = CFDictionaryCreateMutable(NULL,
+                                                 0,
+                                                 &kCFTypeDictionaryKeyCallBacks,
+                                                 &kCFTypeDictionaryValueCallBacks);
+
+       /* open a "configd" store to allow cache updates */
+       store = SCDynamicStoreCreate(NULL,
+                                    CFSTR("AppleTalk Configuraton plug-in"),
+                                    atConfigChangedCallback,
+                                    NULL);
+       if (!store) {
+               SCLog(TRUE, LOG_ERR, CFSTR("SCDynamicStoreCreate() failed: %s"), SCErrorString(SCError()));
+               goto error;
+       }
+
+       /* establish notification keys and patterns */
+
+       keys     = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+       patterns = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+
+       /* ...watch for (global) AppleTalk configuration changes */
+       key = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL,
+                                                        kSCDynamicStoreDomainSetup,
+                                                        kSCEntNetAppleTalk);
+       CFArrayAppendValue(keys, key);
+       CFRelease(key);
+
+       /* ...watch for (per-service) AppleTalk configuration changes */
+       pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
+                                                             kSCDynamicStoreDomainSetup,
+                                                             kSCCompAnyRegex,
+                                                             kSCEntNetAppleTalk);
+       CFArrayAppendValue(patterns, pattern);
+       CFRelease(pattern);
+
+       /* ...watch for network interface link status changes */
+       pattern = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL,
+                                                               kSCDynamicStoreDomainState,
+                                                               kSCCompAnyRegex,
+                                                               kSCEntNetLink);
+       CFArrayAppendValue(patterns, pattern);
+       CFRelease(pattern);
+
+       /* ...watch for (per-interface) AppleTalk configuration changes */
+       pattern = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL,
+                                                               kSCDynamicStoreDomainState,
+                                                               kSCCompAnyRegex,
+                                                               kSCEntNetAppleTalk);
+       CFArrayAppendValue(patterns, pattern);
+       CFRelease(pattern);
+
+       /* ...watch for computer name changes */
+       key = SCDynamicStoreKeyCreateComputerName(NULL);
+       CFArrayAppendValue(keys, key);
+       CFRelease(key);
+
+       /* register the keys/patterns */
+       if (!SCDynamicStoreSetNotificationKeys(store, keys, patterns)) {
+               SCLog(TRUE, LOG_ERR,
+                     CFSTR("SCDynamicStoreSetNotificationKeys() failed: %s"),
+                     SCErrorString(SCError()));
+               goto error;
+       }
+       CFRelease(keys);
+       CFRelease(patterns);
+
+       storeRls = SCDynamicStoreCreateRunLoopSource(NULL, store, 0);
+       if (!storeRls) {
+               SCLog(TRUE, LOG_ERR,
+                     CFSTR("SCDynamicStoreCreateRunLoopSource() failed: %s"),
+                     SCErrorString(SCError()));
+               goto error;
+       }
+       CFRunLoopAddSource(CFRunLoopGetCurrent(), storeRls, kCFRunLoopDefaultMode);
+
+       return;
+
+    error :
+
+       if (curGlobals)         CFRelease(curGlobals);
+       if (curConfigFile)      CFRelease(curConfigFile);
+       if (curDefaults)        CFRelease(curDefaults);
+       if (curStartup)         CFRelease(curStartup);
+       if (store)              CFRelease(store);
+       if (keys)               CFRelease(keys);
+       if (patterns)           CFRelease(patterns);
+       return;
+}
+
+
+#ifdef MAIN
+#include "cfManager.c"
+int
+main(int argc, char **argv)
+{
+       _sc_log     = FALSE;
+       _sc_verbose = (argc > 1) ? TRUE : FALSE;
+
+       load_ATconfig(CFBundleGetMainBundle(), (argc > 1) ? TRUE : FALSE);
+       CFRunLoopRun();
+       /* not reached */
+       exit(0);
+       return 0;
+}
+#endif