/* * 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 * - startup/shutdown AT networking without Kicker's help and * publish the state information after the configuration is * active. * * April 29, 2002 Allan Nathanson * - add global state information (primary service, interface) * * June 24, 2001 Allan Nathanson * - update to public SystemConfiguration.framework APIs * * July 7, 2000 Allan Nathanson * - initial revision */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 #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