MoreSCF/MoreSCFDigest.c

/*
    File:       MoreSCFDigest.c
 
    Contains:   Routines for working with SC entities.
 
    Written by: DTS
 
    Copyright:  Copyright (c) 2007 by Apple Inc., All Rights Reserved.
 
    Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple Inc.
                ("Apple") in consideration of your agreement to the following terms, and your
                use, installation, modification or redistribution of this Apple software
                constitutes acceptance of these terms.  If you do not agree with these terms,
                please do not use, install, modify or redistribute this Apple software.
 
                In consideration of your agreement to abide by the following terms, and subject
                to these terms, Apple grants you a personal, non-exclusive license, under Apple's
                copyrights in this original Apple software (the "Apple Software"), to use,
                reproduce, modify and redistribute the Apple Software, with or without
                modifications, in source and/or binary forms; provided that if you redistribute
                the Apple Software in its entirety and without modifications, you must retain
                this notice and the following text and disclaimers in all such redistributions of
                the Apple Software.  Neither the name, trademarks, service marks or logos of
                Apple Inc. may be used to endorse or promote products derived from the
                Apple Software without specific prior written permission from Apple.  Except as
                expressly stated in this notice, no other rights or licenses, express or implied,
                are granted by Apple herein, including but not limited to any patent rights that
                may be infringed by your derivative works or by other works in which the Apple
                Software may be incorporated.
 
                The Apple Software is provided by Apple on an "AS IS" basis.  APPLE MAKES NO
                WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
                WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
                PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
                COMBINATION WITH YOUR PRODUCTS.
 
                IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
                CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
                GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
                ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
                OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT
                (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN
                ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
    Change History (most recent first):
 
$Log: MoreSCFDigest.c,v $
Revision 1.13  2006/03/27 14:42:20  eskimo1
Eliminate high-bit set characters.
 
Revision 1.12  2006/03/24 15:44:26  eskimo1
Updated copyright.
 
Revision 1.11  2006/03/24 12:38:34  eskimo1
Eliminate "pascal" keyword.
 
Revision 1.10  2006/03/24 11:29:58  eskimo1
Eliminated "MoreSetup.h" to make it easier for folks to copy MIB source into their projects.
 
Revision 1.9  2003/04/14 15:51:21  eskimo1
Use CFQAllocate/Deallocate to prevent "malloc(0) returns NULL" problems.
 
Revision 1.8  2003/02/26 20:51:59  eskimo1
<rdar://problem/3183087> Added support for V.92 modem hold.
 
Revision 1.7  2003/02/26 12:14:28  eskimo1
In MoreSCCreateIPv4Entity, digest->configMethod can't be NULL, so let's assert that.
 
Revision 1.6  2002/11/25 16:49:41  eskimo1
Changes to build in Project Builder without warnings.
 
Revision 1.5  2002/11/14 20:16:31  eskimo1
Tidy up the debug checking in MoreSCCreateIPv4Entity and add a way to disable it for the benefit of MoreSCNewSet.
 
Revision 1.4  2002/11/09 00:01:55  eskimo1
Include our prototype early to flush out any missing dependencies. Convert nil to NULL. Convert MoreAssertQ to assert.
 
Revision 1.3  2002/08/14 16:33:50  eskimo1
Added support for v 1.3 interfaces changes.  Certain hardwired defaults now get different values on 10.2 and later.  Changed some asserts to fprintfs in MoreSCCreateIPv4Entity because changes for post 10.1 compatibility trigger them.
 
Revision 1.2  2002/01/22 06:19:42  eskimo1
Changes to support variant field in MoreSCInterfaceDigest. Also adapted to minor CFQ changes.
 
Revision 1.1  2002/01/16 22:52:28  eskimo1
First checked in.
 
 
*/
 
/////////////////////////////////////////////////////////////////
 
// Our prototypes
 
#include "MoreSCFDigest.h"
 
// System interfaces
 
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
 
// MIB Interfaces
 
#include "MoreCFQ.h"
#include "MoreSCFPortScanner.h"
#include "MoreSCF.h"
 
/////////////////////////////////////////////////////////////////
 
#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_4 && ! MORE_SCF_NO_DEPRECATION_WARNINGS
    #warning MoreSCF is deprecated if you are building for 10.4 or later.
#endif
 
/////////////////////////////////////////////////////////////////
#pragma mark ***** Utilities
 
extern OSStatus MoreSCFCreateStringWithMacAddress(const UInt8 *macAddr, CFStringRef *result)
    // See comment in header.
    //
    // This code was stolen from Robert's code (CreateMACFromData in "MoreSCFPortScanner.c".  
    // I should unify these eventually.
{
    OSStatus err;
    const UInt8 *p;
    char s [19];              // mac addresses are 6 bytes long, which translates to 18 chars.
    char *cp = &s[0];
    int len = 6;
    
    assert(macAddr != NULL);
    assert( result != NULL);
    assert(*result == NULL);
 
    // add digits in pairs with colon separators
 
    p = macAddr;
    while ( len-- )
        cp += sprintf( cp, "%2.2x:", *p++ );
 
    // terminate string
 
    s[17] = '\0';
 
    err = noErr;
    *result = CFStringCreateWithCString(NULL, s, kCFStringEncodingASCII);
    if (*result == NULL) {
        err = coreFoundationUnknownErr;
    }
 
    assert( (err == noErr) == (*result != NULL) );
 
    return err;
}
 
extern OSStatus MoreSCFStringToMacAddress(CFStringRef cfStr, UInt8 *macAddr)
    // See comment in header.
{
    OSStatus err;
    char buf[ (6 * 2) + 5 + 1 ];        // 6 hex bytes (2 chars each) + 5 separators + null
    int macAsInts[6];
    
    assert(cfStr   != NULL);
    assert(macAddr != NULL);
    
    err = noErr;
    if ( ! CFStringGetCString(cfStr, buf, sizeof(buf), kCFStringEncodingASCII) ) {
        err = coreFoundationUnknownErr;
    }
    if (err == noErr) {
        if ( sscanf(buf, "%02x:%02x:%02x:%02x:%02x:%02x", 
                            &macAsInts[0], &macAsInts[1], &macAsInts[2], 
                            &macAsInts[3], &macAsInts[4], &macAsInts[5]) != 6 ) {
            err = -1;
        }
    }
    if (err == noErr) {
        int i;
        
        for (i = 0; i < 6; i++) {
            assert((macAsInts[i] >= 0) && (macAsInts[i] < 256));
            macAddr[i] = (UInt8) macAsInts[i];
        }
    }
 
    return err;
}
 
extern OSStatus MoreSCFCreateStringWithInetAddress(in_addr addr, CFStringRef *result)
    // See comment in header.
{
    OSStatus    err;
    const char *addrStr;
 
    assert( result != NULL);
    assert(*result == NULL);
 
    err = noErr;    
    addrStr = inet_ntoa(addr);
    *result = CFStringCreateWithCString(NULL, addrStr, kCFStringEncodingASCII);
    if (*result == NULL) {
        err = coreFoundationUnknownErr;
    }
 
    assert( (err == noErr) == (*result != NULL) );
 
    return err;
}
 
extern OSStatus MoreSCFCreateArrayWithInetAddresses(ItemCount addrCount, const in_addr *addresses, CFArrayRef *result)
    // See comment in header.
{
    OSStatus            err;
    ItemCount           index;
    CFMutableArrayRef   addressesArray;
    
    assert( (addrCount == 0) || (addresses != NULL) );
    assert( result != NULL);
    assert(*result == NULL);
    
    addressesArray = NULL;
    err = CFQArrayCreateMutable(&addressesArray);
    if (err == noErr) {
        for (index = 0; index < addrCount; index++) {
            CFStringRef value;
            
            value = NULL;
            err = MoreSCFCreateStringWithInetAddress(addresses[index], &value);
            if (err == noErr) {
                CFArrayAppendValue(addressesArray, value);
            }
            CFQRelease(value);
            if (err != noErr) {
                break;
            }
        }
    }
 
    if (err != noErr) {
        CFQRelease(addressesArray);
        addressesArray = NULL;
    }
    *result = addressesArray;
 
    assert( (err == noErr) == (*result != NULL) );
    
    return err;
}
 
extern OSStatus MoreSCFCopyEncodedPPPPassword(CFStringRef password, CFDataRef *encodedPassword)
    // See comment in header.
{
    OSStatus err;
    CFIndex  numUniChars;
    UniChar *uniChars;
    
    assert(password != NULL);
    assert( encodedPassword != NULL);
    assert(*encodedPassword == NULL);
 
    uniChars = NULL;
    
    // Can't use CFStringCreateExternalRepresentation because, according to the 
    // documentation, it always includes a byte order mark (BOM) character.
    
    numUniChars = CFStringGetLength(password);
    err = CFQAllocate(numUniChars * sizeof(UniChar), (void **) &uniChars);
    if (err == noErr) {
        CFStringGetCharacters(password, CFRangeMake(0, numUniChars), uniChars);
        
        *encodedPassword = CFDataCreate(NULL, (UInt8 *) uniChars, numUniChars * (CFIndex) sizeof(UniChar));
        if (*encodedPassword == NULL) {
            err = coreFoundationUnknownErr;
        }
    }
    
    CFQDeallocate(uniChars);
    
    assert( (err == noErr) == (*encodedPassword != NULL) );
    return err;
}
 
/////////////////////////////////////////////////////////////////
#pragma mark ***** Global Entities
 
extern OSStatus MoreSCCreateIPv4GlobalEntity(const MoreSCIPv4GlobalDigest *digest, CFDictionaryRef *entity)
    // See comment in header.
{
    #pragma unused(digest)
    OSStatus err;
    CFMutableDictionaryRef result;
    
    assert( digest != NULL);
    assert( entity != NULL);
    assert(*entity == NULL);
    
    result = NULL;
    err = CFQDictionaryCreateMutable(&result);
    if ( (err == noErr) && (MoreSCGetSystemVersion() < 0x01050) ) {
        err = CFQDictionarySetNumber(result, kSCPropNetPPPOverridePrimary, 0);
    }
    if (err == noErr) {
        CFArrayRef serviceOrder;
        
        serviceOrder = CFArrayCreate(NULL, NULL, 0, &kCFTypeArrayCallBacks);
        if (serviceOrder == NULL) {
            err = coreFoundationUnknownErr;
        }
        CFDictionaryAddValue(result, kSCPropNetServiceOrder, serviceOrder);
        CFQRelease(serviceOrder);
    }
    
    if (err != noErr) {
        CFQRelease(result);
        result = NULL;
    }
    *entity = result;
    
    return err;
}
 
extern OSStatus MoreSCCreateNetInfoGlobalEntity(const MoreSCNetInfoGlobalDigest *digest, CFDictionaryRef *entity)
    // See comment in header.
{
    #pragma unused(digest)
    OSStatus err;
    CFMutableDictionaryRef result;
    
    assert( digest != NULL);
    assert( entity != NULL);
    assert(*entity == NULL);
    
    result = NULL;
    err = CFQDictionaryCreateMutable(&result);
    // No useful values in MoreSCNetInfoGlobalDigest, something 
    // that I should probably address in the future.
    
    *entity = result;
    
    return err;
}
 
extern OSStatus MoreSCCreateGlobalEntity(CFStringRef protocol, const MoreSCGlobalDigest *digest, CFDictionaryRef *entity)
    // See comment in header.
{
    OSStatus err;
    
    assert(protocol != NULL);
    assert(digest != NULL);
    assert( entity != NULL);
    assert(*entity == NULL);
    
    // Call the appropriate protocol-specific routine based on the 
    // protocol type supplied.
 
    if (        CFEqual(protocol, kSCEntNetIPv4) ) {
        err = MoreSCCreateIPv4GlobalEntity(&digest->ipv4, entity);
    } else if ( CFEqual(protocol, kSCEntNetNetInfo) ) {
        err = MoreSCCreateNetInfoGlobalEntity(&digest->netInfo, entity);
    } else {
        assert(false);
        err = -1;
    }
    return err;
}
 
/////////////////////////////////////////////////////////////////
#pragma mark ***** Service Entities
 
extern OSStatus MoreSCCreateInterfaceEntity(const MoreSCInterfaceDigest *digest, CFDictionaryRef *entity)
    // See comment in header.
{
    OSStatus err;
    CFMutableDictionaryRef result;
    
    assert( digest != NULL);
    assert( entity != NULL);
    assert(*entity == NULL);
    
    result = NULL;
    err = CFQDictionaryCreateMutable(&result);
    if (err == noErr) {
        assert(digest->deviceName != NULL);
        assert(digest->userDefinedName != NULL);
        CFDictionaryAddValue(result, kSCPropNetInterfaceDeviceName, digest->deviceName);
        CFDictionaryAddValue(result, kSCPropUserDefinedName, digest->userDefinedName);
        assert(
               CFEqual(digest->hardware, kSCEntNetAirPort) 
            || CFEqual(digest->hardware, kSCEntNetEthernet) 
            || CFEqual(digest->hardware, kSCEntNetModem) 
            || CFEqual(digest->hardware, kSCEntNetFireWire) 
        );
        CFDictionaryAddValue(result, kSCPropNetInterfaceHardware, digest->hardware);
        if ( CFEqual(digest->hardware, kSCEntNetModem) ) {
            if (digest->supportsHold) {
                err = CFQDictionarySetNumber(result, kSCPropNetInterfaceSupportsModemOnHold, 1);
            }
        }
    }
    if (err == noErr) {
        assert( CFEqual(digest->type, kSCValNetInterfaceTypeEthernet) 
            ||  CFEqual(digest->type, kSCValNetInterfaceTypePPP) 
            ||  CFEqual(digest->type, kSCValNetInterfaceTypeFireWire) 
        );
        CFDictionaryAddValue(result, kSCPropNetInterfaceType, digest->type);
        if (digest->subType != NULL) {
            assert( CFEqual(digest->type, kSCValNetInterfaceTypePPP) );
            assert(     CFEqual(digest->subType, kSCValNetInterfaceSubTypePPPSerial) 
                        ||  CFEqual(digest->subType, kSCValNetInterfaceSubTypePPPoE) );
            CFDictionaryAddValue(result, kSCPropNetInterfaceSubType, digest->subType);
        }
        if (digest->variant != NULL) {
            assert( CFEqual(digest->hardware, kSCEntNetModem) );
            CFDictionaryAddValue(result, kMoreSCPropNetInterfaceHardwareVariant, digest->variant);
        }
    }
    
    if (err != noErr) {
        CFQRelease(result);
        result = NULL;
    }
    *entity = result;
    
    assert( (err == noErr) == (*entity != NULL) );
    
    return err;
}
 
static OSStatus AddProxy(CFMutableDictionaryRef dict, 
                            CFStringRef enableKey, CFStringRef proxyKey,   CFStringRef portKey, 
                            Boolean enableValue,   CFStringRef proxyValue, UInt16 portValue)
    // Adds a proxy entry to dict.  The entry consists of up to three 
    // key/value pairs.  The enable property is a number that 
    // indicates whether the proxy is to be used.  It is always 
    // added.  The other two properties, proxy and port, are optional 
    // (controlled by whether their values are NULL or 0).
{
    OSStatus err;
    
    assert(dict        != NULL);
    assert(enableKey   != NULL);
    
    // On 10.4 and later, we only set proxy keys if they're a non-default value.
    
    err = 0;
    if ( (MoreSCGetSystemVersion() < 0x01030) || enableValue || (proxyValue != NULL) || (portValue != 0) ) {
        err = CFQDictionarySetNumber(dict, enableKey, enableValue);
        if (err == noErr && proxyValue != NULL) {
            CFDictionaryAddValue(dict, proxyKey, proxyValue);
        }
        if (err == noErr && portValue != 0) {
            err = CFQDictionarySetNumber(dict, portKey, portValue);
        }
    }
    
    return err;
}
 
extern OSStatus MoreSCCreateProxiesEntity(const MoreSCProxiesDigest *digest, CFDictionaryRef *entity)
    // See comment in header.
{
    OSStatus err;
    CFMutableDictionaryRef result;
    
    assert( digest != NULL);
    assert( entity != NULL);
    assert(*entity == NULL);
 
    result = NULL;
    err = CFQDictionaryCreateMutable(&result);
    
    // Add the "ExceptionsList" property, if supplied.
    
    if (err == noErr) {
        if (digest->exceptionsList != NULL) {
            CFDictionaryAddValue(result, kSCPropNetProxiesExceptionsList, digest->exceptionsList);
        }
        err = CFQDictionarySetNumber(result, kSCPropNetProxiesFTPPassive, digest->ftpPassive);
    }
    
    // Add properties for each of the 6 possible proxies, if supplied.
    
    if (err == noErr) {
        err = AddProxy(result, kSCPropNetProxiesFTPEnable, kSCPropNetProxiesFTPProxy, kSCPropNetProxiesFTPPort, 
                        digest->ftpEnable, digest->ftpProxy, digest->ftpPort);
    }
    if (err == noErr) {
        err = AddProxy(result, kSCPropNetProxiesGopherEnable, kSCPropNetProxiesGopherProxy, kSCPropNetProxiesGopherPort, 
                        digest->gopherEnable, digest->gopherProxy, digest->gopherPort);
    }
    if (err == noErr) {
        err = AddProxy(result, kSCPropNetProxiesHTTPEnable, kSCPropNetProxiesHTTPProxy, kSCPropNetProxiesHTTPPort, 
                        digest->httpEnable, digest->httpProxy, digest->httpPort);
    }
    if (err == noErr) {
        err = AddProxy(result, kSCPropNetProxiesHTTPSEnable, kSCPropNetProxiesHTTPSProxy, kSCPropNetProxiesHTTPSPort, 
                        digest->httpsEnable, digest->httpsProxy, digest->httpsPort);
    }
    if (err == noErr) {
        err = AddProxy(result, kSCPropNetProxiesRTSPEnable, kSCPropNetProxiesRTSPProxy, kSCPropNetProxiesRTSPPort, 
                        digest->rtspEnable, digest->rtspProxy, digest->rtspPort);
    }
    if (err == noErr) {
        err = AddProxy(result, kSCPropNetProxiesSOCKSEnable, kSCPropNetProxiesSOCKSProxy, kSCPropNetProxiesSOCKSPort, 
                        digest->socksEnable, digest->socksProxy, digest->socksPort);
    }
    
    // Misc stuff
    
    if ( (err == noErr) && (MoreSCGetSystemVersion() >= 0x01040) ) {
        err = CFQDictionarySetNumber(result, kSCPropNetProxiesExcludeSimpleHostnames, digest->excludeSimpleHostnames);
        if (err == noErr) {
            err = CFQDictionarySetNumber(result, kSCPropNetProxiesProxyAutoDiscoveryEnable, digest->autoDiscoveryEnable);
        }
    }
    
    if (err != noErr) {
        CFQRelease(result);
        result = NULL;
    }
    *entity = result;
    
    return err;
}
 
extern OSStatus MoreSCCreateAppleTalkEntity(const MoreSCAppleTalkDigest *digest, CFDictionaryRef *entity)
    // See comment in header.
{
    OSStatus err;
    CFMutableDictionaryRef result;
    
    assert( digest != NULL);
    assert( entity != NULL);
    assert(*entity == NULL);
 
    result    = NULL;
    
    err = CFQDictionaryCreateMutable(&result);
    if (err == noErr) {
        assert( CFEqual(digest->configMethod, kSCValNetAppleTalkConfigMethodNode) );
        CFDictionaryAddValue(result, kSCPropNetAppleTalkConfigMethod, digest->configMethod);
 
        if (digest->defaultZone != NULL) {
            CFDictionaryAddValue(result, kSCPropNetAppleTalkDefaultZone, digest->defaultZone);
        }
 
        if (digest->manual) {
            err = CFQDictionarySetNumber(result, kSCPropNetAppleTalkNetworkID, digest->networkID);
            if (err == noErr) {
                err = CFQDictionarySetNumber(result, kSCPropNetAppleTalkNodeID, digest->nodeID);
            }
        }
    }
    if (err == noErr && ! digest->active ) {
        err = CFQDictionarySetNumber(result, kSCResvInactive, 1);
    }
    
    if (err != noErr) {
        CFQRelease(result);
        result = NULL;
    }
    *entity = result;
    
    return err;
}
 
extern OSStatus MoreSCCreateDNSEntity(const MoreSCDNSDigest *digest, CFDictionaryRef *entity)
    // See comment in header.
{
    OSStatus err;
    CFMutableDictionaryRef result;
    ItemCount index;
    
    assert( digest != NULL);
    assert( entity != NULL);
    assert(*entity == NULL);
 
    result = NULL;
    
    err = CFQDictionaryCreateMutable(&result);
    
    // If the caller supplied a DNS server addresses, add them 
    // to the entity.
    
    if (err == noErr && digest->serverAddressesCount > 0) {
        CFArrayRef          addresses;
        
        assert(digest->serverAddresses != NULL);
 
        addresses = NULL;
        err = MoreSCFCreateArrayWithInetAddresses(digest->serverAddressesCount, 
                                                  digest->serverAddresses, 
                                                  &addresses);
        CFDictionaryAddValue(result, kSCPropNetDNSServerAddresses, addresses);
 
        CFQRelease(addresses);  
    }
    
    // If the caller support a DNS search domains, add each to the entity.
    
    if (err == noErr && digest->searchDomainsCount > 0) {
        CFMutableArrayRef   domains;
        
        assert(digest->searchDomains != NULL);
 
        domains = NULL;
        
        // I could have used CFArrayCreate here but I wanted to loop through 
        // each element so that I can check for NULL.
        
        err = CFQArrayCreateMutable(&domains);
        if (err == noErr) {
            for (index = 0; index < digest->searchDomainsCount; index++) {
                assert(digest->searchDomains[index] != NULL);
                
                CFArrayAppendValue(domains, digest->searchDomains[index]);
            }
        }
        CFDictionaryAddValue(result, kSCPropNetDNSSearchDomains, domains);
 
        CFQRelease(domains);    
    }
 
    if (err != noErr) {
        CFQRelease(result);
        result = NULL;
    }
    *entity = result;
    
    return err;
}
 
#if !defined(NDEBUG)
 
    Boolean gMoreSCCreateIPv4EntityDontCheck;
 
#endif
 
extern OSStatus MoreSCCreateIPv4Entity(const MoreSCIPv4Digest *digest, CFDictionaryRef *entity)
    // See comment in header.
{
    OSStatus err;
    CFMutableDictionaryRef result;
    
    assert( digest != NULL);
    assert( digest->configMethod != NULL);
    assert( entity != NULL);
    assert(*entity == NULL);
 
    result = NULL;
    err = CFQDictionaryCreateMutable(&result);
    if (err == noErr) {
    
        // If we're debugging, make sure that the configMethod is one of 
        // the supported options, and that other fields match the 
        // config method (for example, you can't supply a client ID if 
        // you're not using DHCP).
        
        #if !defined(NDEBUG)
            if ( ! gMoreSCCreateIPv4EntityDontCheck ) {
                OSType  method;
                Boolean printed;
                
                printed = false;
                
                if (CFEqual(digest->configMethod, kSCValNetIPv4ConfigMethodBOOTP)) {
                    method = 'boot';
                } else if (CFEqual(digest->configMethod, kSCValNetIPv4ConfigMethodDHCP)) {
                    method = 'dhcp';
                } else if (CFEqual(digest->configMethod, kSCValNetIPv4ConfigMethodINFORM)) {
                    method = 'info';
                } else if (CFEqual(digest->configMethod, kSCValNetIPv4ConfigMethodManual)) {
                    method = 'manu';
                } else if (CFEqual(digest->configMethod, kSCValNetIPv4ConfigMethodPPP)) {
                    method = 'ppp ';
                } else {
                    method = 'bad!';
                }
                assert(method != 'bad!');
 
                // These used to be asserts but they are now triggered by 
                // some of our test cases on Mac OS X 10.2, where the default 
                // setup for an Ethernet port is Manual with IP address 0.
                
                if ( (digest->address.s_addr == 0) !=
                              (    (method == 'boot')
                                || (method == 'dhcp')
                                || (method == 'ppp ')  ) ) {
                    fprintf(stderr, "MoreSCCreateIPv4Entity: IP address zero makes sense for BOOTP, DHCP, and PPP config methods only\n");
                    printed = true;
                }
                if ( ( digest->subnetMask.s_addr != 0) != (method == 'manu') ) {
                    fprintf(stderr, "MoreSCCreateIPv4Entity: Subnet mask non-zero makes sense for Manual config method only\n");
                    printed = true;
                }
                if ( ( digest->router.s_addr != 0) != (method == 'manu') ) {
                    fprintf(stderr, "MoreSCCreateIPv4Entity: Router address non-zero makes sense for Manual config method only\n");
                    printed = true;
                }
                if (printed) {
                    fprintf(stderr, "  address      = %08x\n", digest->address.s_addr);
                    fprintf(stderr, "  subnetMask   = %08x\n", digest->subnetMask.s_addr);
                    fprintf(stderr, "  router       = %08x\n", digest->router.s_addr);
                    fprintf(stderr, "  method       = %4.4s\n", (char *) &method);
                    fprintf(stderr, "  configMethod = ");
                    CFShow(digest->configMethod);
                }
                assert( (digest->clientID == NULL) || (method == 'dhcp') );
            }
        #endif
        
        // Add the config method and client ID (if supplied).
        
        CFDictionaryAddValue(result, kSCPropNetIPv4ConfigMethod, digest->configMethod);
        if (digest->clientID != NULL) {
            CFDictionaryAddValue(result, kSCPropNetIPv4DHCPClientID, digest->clientID);
        }
 
        // If the caller supplied an address, add it.
        
        if (digest->address.s_addr != 0) {
            CFArrayRef addresses;
            
            addresses = NULL;
            err = MoreSCFCreateArrayWithInetAddresses(1, &digest->address, &addresses);
            
            CFDictionaryAddValue(result, kSCPropNetIPv4Addresses, addresses);
            CFQRelease(addresses);
        }
        
        // If the caller supplied a subnet mask, add it.
        
        if (err == noErr && digest->subnetMask.s_addr != 0) {
            CFArrayRef subnetMasks;
            
            subnetMasks = NULL;
            err = MoreSCFCreateArrayWithInetAddresses(1, &digest->subnetMask, &subnetMasks);
            
            CFDictionaryAddValue(result, kSCPropNetIPv4SubnetMasks, subnetMasks);
            CFQRelease(subnetMasks);
        }
 
        // If the caller supplied a router mask, add it.
        
        if (err == noErr && digest->router.s_addr != 0) {
            CFStringRef router;
            
            router = NULL;
            err = MoreSCFCreateStringWithInetAddress(digest->router, &router);
            CFDictionaryAddValue(result, kSCPropNetIPv4Router, router);
            CFQRelease(router);
        }
    }
    
    if (err != noErr) {
        CFQRelease(result);
        result = NULL;
    }
    *entity = result;
    
    return err;
}
 
// This structure is the default options for PPP if the caller doesn't supply 
// an options structure.
 
const MoreSCPPPOptions kMoreSCPPPOptionsDefaultTenOne =
{
    false,      // dialOnDemand
    false,      // idleReminder
    1800,       // idleReminderTimer
    true,       // disconnectOnIdle
    900,        // disconnectOnIdleTimer
    true,       // disconnectOnLogout
    true,       // commRedialEnabled
    1,          // commRedialCount
    30,         // commRedialInterval
    true,       // lcpEchoEnabled
    true,       // ipcpCompressionVJ
    false,      // commDisplayTerminalWindow
    false       // verboseLogging
};
 
const MoreSCPPPOptions kMoreSCPPPOptionsDefaultTenTwoAndLater =
{
    false,      // dialOnDemand
    false,      // idleReminder
    1800,       // idleReminderTimer
    true,       // disconnectOnIdle
    600,        // disconnectOnIdleTimer
    true,       // disconnectOnLogout
    true,       // commRedialEnabled
    1,          // commRedialCount
    5,          // commRedialInterval
    true,       // lcpEchoEnabled
    true,       // ipcpCompressionVJ
    false,      // commDisplayTerminalWindow
    false       // verboseLogging
};
 
const MoreSCPPPOptions kMoreSCPPPoEOptionsDefaultTenTwoAndLater =
{
    false,      // dialOnDemand
    false,      // idleReminder
    1800,       // idleReminderTimer
    false,      // disconnectOnIdle
    1800,       // disconnectOnIdleTimer
    true,       // disconnectOnLogout
    true,       // commRedialEnabled
    1,          // commRedialCount
    5,          // commRedialInterval
    true,       // lcpEchoEnabled
    true,       // ipcpCompressionVJ
    false,      // commDisplayTerminalWindow
    false       // verboseLogging
};
 
static Boolean gTenTwoOrLater;
static Boolean gTenThreeOrLater;
static Boolean gTenThreeFiveOrLater;
static Boolean gTenFourOrLater;
 
extern const MoreSCPPPOptions *MoreSCGetDefaultPPPOptions(Boolean isPPPoE)
{
    const MoreSCPPPOptions *result;
    
    gTenTwoOrLater       = (MoreSCGetSystemVersion() >= 0x01020);
    gTenThreeOrLater     = (MoreSCGetSystemVersion() >= 0x01030);
    gTenThreeFiveOrLater = (MoreSCGetSystemVersion() >= 0x01035);
    gTenFourOrLater      = (MoreSCGetSystemVersion() >= 0x01040);
    if (gTenTwoOrLater) {
        if (isPPPoE) {
            result = &kMoreSCPPPoEOptionsDefaultTenTwoAndLater;
        } else {
            result = &kMoreSCPPPOptionsDefaultTenTwoAndLater;
        }
    } else {
        result = &kMoreSCPPPOptionsDefaultTenOne;
    }
    return result;
}
 
// This trickery allows us to build with either 10.1 or 10.2 headers.
 
#if !defined(kSCPropNetPPPCommUseTerminalScript)
    #define kSCPropNetPPPCommUseTerminalScript       SCSTR("CommUseTerminalScript")           /* CFNumber (0 or 1) */
#endif
 
extern OSStatus MoreSCCreatePPPEntity(const MoreSCPPPDigest *digest, CFDictionaryRef *entity)
    // See comment in header.
{
    OSStatus err;
    CFMutableDictionaryRef result;
    const MoreSCPPPOptions *options;
    
    assert( digest != NULL);
    assert( entity != NULL);
    assert(*entity == NULL);
 
    // If the caller supplied options, use them, otherwise use the defaults.
    // Note that we always call MoreSCGetDefaultPPPOptions to guarantee that 
    // gTenTwoOrLater has been set up, which we need below.
    
    options = MoreSCGetDefaultPPPOptions(false);
    if (digest->options != NULL) {
        options = digest->options;
    }
    result = NULL;
    err = CFQDictionaryCreateMutable(&result);
    
    if (err == noErr) {
    
        // Set the user-visible name, if supplied.
        
        if (digest->userDefinedName != NULL) {
            CFDictionaryAddValue(result, kSCPropUserDefinedName, digest->userDefinedName);
        }       
        
        // If the entity isn't active, record that.
        
        if (! digest->active ) {
            err = CFQDictionarySetNumber(result, kSCResvInactive, 1);
        }
    }
    
    // Identifiers listed in comments are included in "SCSchemaDefinitions.h"
    // but aren't created by the Network preferences panel by default (and 
    // not created by us (or it) by default).
    
    // PPP-level properties
    
    if (err == noErr) {
        err = CFQDictionarySetNumber(result, kSCPropNetPPPDialOnDemand, options->dialOnDemand);
    }
    if (err == noErr) {
        err = CFQDictionarySetNumber(result, kSCPropNetPPPDisconnectOnIdle, options->disconnectOnIdle);
    }
    if (err == noErr) {
        err = CFQDictionarySetNumber(result, kSCPropNetPPPDisconnectOnIdleTimer, options->disconnectOnIdleTimer);
    }
    if (err == noErr) {
        err = CFQDictionarySetNumber(result, kSCPropNetPPPDisconnectOnLogout, options->disconnectOnLogout);
    }
    if (err == noErr) {
        err = CFQDictionarySetNumber(result, kSCPropNetPPPIdleReminder, options->idleReminder);
    }
    if (err == noErr) {
        err = CFQDictionarySetNumber(result, kSCPropNetPPPIdleReminderTimer, options->idleReminderTimer);
    }
    if (err == noErr) {
        // For security reasons, the location of the PPP log file changed in 
        // Mac OS X 10.3.5 <rdar://problem/3740180>.
        if (gTenThreeFiveOrLater) {
            CFDictionaryAddValue(result, kSCPropNetPPPLogfile, CFSTR("/var/log/ppp.log"));
        } else {
            CFDictionaryAddValue(result, kSCPropNetPPPLogfile, CFSTR("/tmp/ppp.log"));
        }
    }
    // kSCPropNetPPPSessionTimer
    if (err == noErr) {
        err = CFQDictionarySetNumber(result, kSCPropNetPPPVerboseLogging, options->verboseLogging);
    }
 
    // Authentication properties
 
    if (err == noErr && digest->authName != NULL) {
        CFDictionaryAddValue(result, kSCPropNetPPPAuthName, digest->authName);
    }
    if (err == noErr && digest->authPassword != NULL) {
        CFDataRef encodedPassword;
        
        encodedPassword = NULL;
        err = MoreSCFCopyEncodedPPPPassword(digest->authPassword, &encodedPassword);
        if (err == noErr) {
            CFDictionaryAddValue(result, kSCPropNetPPPAuthPassword, encodedPassword);
        }
        
        CFQRelease(encodedPassword);
    }
    // kSCPropNetPPPAuthPasswordEncryption
    // kSCPropNetPPPAuthProtocol
        
    // Communications-level properties
    
    if (err == noErr && digest->commAlternateRemoteAddress != NULL) {
        CFDictionaryAddValue(result, kSCPropNetPPPCommAlternateRemoteAddress, digest->commAlternateRemoteAddress);
    }
    // kSCPropNetPPPCommConnectDelay
    if (err == noErr) {
        err = CFQDictionarySetNumber(result, kSCPropNetPPPCommDisplayTerminalWindow, options->commDisplayTerminalWindow);
    }
    if (err == noErr) {
        err = CFQDictionarySetNumber(result, kSCPropNetPPPCommRedialCount, options->commRedialCount);
    }
    if (err == noErr) {
        err = CFQDictionarySetNumber(result, kSCPropNetPPPCommRedialEnabled, options->commRedialEnabled);
    }
    if (err == noErr) {
        err = CFQDictionarySetNumber(result, kSCPropNetPPPCommRedialInterval, options->commRedialInterval);
    }
    if (err == noErr && digest->commRemoteAddress != NULL) {
        CFDictionaryAddValue(result, kSCPropNetPPPCommRemoteAddress, digest->commRemoteAddress);
    }
    // kSCPropNetPPPCommTerminalScript
    if (err == noErr) {
        if (gTenTwoOrLater) {
            err = CFQDictionarySetNumber(result, kSCPropNetPPPCommUseTerminalScript, 0);
        }
    }
 
    // IPCP-level properties
    
    if (err == noErr) {
        err = CFQDictionarySetNumber(result, kSCPropNetPPPIPCPCompressionVJ, options->ipcpCompressionVJ);
    }
 
    // LCP-level properties
    
    if (err == noErr) {
        err = CFQDictionarySetNumber(result, kSCPropNetPPPLCPEchoEnabled, options->lcpEchoEnabled);
    }
    if (err == noErr) {
        if (gTenTwoOrLater) {
            err = CFQDictionarySetNumber(result, kSCPropNetPPPLCPEchoFailure, 4);
        } else {
            err = CFQDictionarySetNumber(result, kSCPropNetPPPLCPEchoFailure, 3);
        }
    }
    if (err == noErr) {
        err = CFQDictionarySetNumber(result, kSCPropNetPPPLCPEchoInterval, 10);
    }
    // kSCPropNetPPPLCPCompressionACField
    // kSCPropNetPPPLCPCompressionPField
    // kSCPropNetPPPLCPMRU
    // kSCPropNetPPPLCPMTU
    // kSCPropNetPPPLCPReceiveACCM
    // kSCPropNetPPPLCPTransmitACCM
    
    // Stuff that doesn't fit neatly into the above categories.
    
    if ( (err == noErr) && gTenThreeOrLater ) {
        err = CFQDictionarySetNumber(result, kSCPropNetPPPACSPEnabled, 0);
    }
    if ( (err == noErr) && gTenTwoOrLater ) {
        err = CFQDictionarySetNumber(result, kSCPropNetPPPDisconnectOnSleep, 1);
    }
    if ( (err == noErr) && gTenFourOrLater ) {
        err = CFQDictionarySetNumber(result, kSCPropNetPPPDisconnectOnFastUserSwitch, 1);
    }
    
    if (err != noErr) {
        CFQRelease(result);
        result = NULL;
    }
    *entity = result;
    
    return err;
}
 
extern OSStatus MoreSCCreateAirPortEntity(const MoreSCAirPortDigest *digest, CFDictionaryRef *entity)
    // See comment in header.
{
    OSStatus err;
    CFMutableDictionaryRef result;
    
    assert( digest != NULL);
    assert( entity != NULL);
    assert(*entity == NULL);
 
    result = NULL;
    err = CFQDictionaryCreateMutable(&result);
    
    // 10.4 and later don't record the MAC address in the prefs.
    
    if ( (err == noErr) && (MoreSCGetSystemVersion() < 0x01040) ) {
        CFStringRef mac;
        
        mac = NULL;
        err = MoreSCFCreateStringWithMacAddress(digest->macAddress, &mac);
        CFDictionaryAddValue(result, kSCPropMACAddress, mac);
        CFQRelease(mac);
    }
    
    if (err == noErr && digest->preferredNetwork != NULL) {
        CFDictionaryAddValue(result, kSCPropNetAirPortPreferredNetwork, digest->preferredNetwork);
    }
    
    // *** "SCSchemaDefinitions.h" defines the following.  Not sure 
    // what to do with them right now.
    
    // kSCPropNetAirPortPowerEnabled
    // kSCPropNetAirPortAuthPassword
    // kSCPropNetAirPortAuthPasswordEncryption
    
    if (err != noErr) {
        CFQRelease(result);
        result = NULL;
    }
    *entity = result;
    
    return err;
}
 
extern OSStatus MoreSCCreateEthernetEntity(const MoreSCEthernetDigest *digest, CFDictionaryRef *entity)
    // See comment in header.
{
    OSStatus err;
    CFMutableDictionaryRef result;
    
    assert( digest != NULL);
    assert( entity != NULL);
    assert(*entity == NULL);
 
    result = NULL;
    err = CFQDictionaryCreateMutable(&result);
    if (err == noErr) {
        CFStringRef mac;
        
        mac = NULL;
        err = MoreSCFCreateStringWithMacAddress(digest->macAddress, &mac);
        CFDictionaryAddValue(result, kSCPropMACAddress, mac);
        CFQRelease(mac);
    }
    
    if (err != noErr) {
        CFQRelease(result);
        result = NULL;
    }
    *entity = result;
    
    return err;
}
 
// This structure contains the default options for modem.  It's 
// exported to clients but isn't referenced by this module itself.
 
const MoreSCModemDigest kMoreSCModemDigestDefault =
{
    NULL,           // connectionScript
    true,           // dataCompressionErrorCorrection
    true,           // speaker
    false,          // pulseDial
    true,           // waitForDialTone
    false,          // supportsHold
};
 
extern OSStatus MoreSCCreateModemEntity(const MoreSCModemDigest *digest, CFDictionaryRef *entity)
    // See comment in header.
    //
    // Note that the dataCompressionErrorCorrection digest field controls 
    // both kSCPropNetModemConnectionScript and kSCPropNetModemErrorCorrection.  
    // This is inline with the current user interface, which has a single 
    // checkbox that controls both of these parameters.
{
    OSStatus err;
    CFMutableDictionaryRef result;
    
    assert( digest != NULL);
    assert( entity != NULL);
    assert(*entity == NULL);
 
    result = NULL;
    err = CFQDictionaryCreateMutable(&result);
    if (err == noErr) {
        assert(digest->connectionScript != NULL);
        
        CFDictionaryAddValue(result, kSCPropNetModemConnectionScript, digest->connectionScript);
    }
    if (err == noErr) {
        err = CFQDictionarySetNumber(result, kSCPropNetModemDataCompression, digest->dataCompressionErrorCorrection);
    }
    if (err == noErr) {
        if (digest->waitForDialTone) {
            CFDictionaryAddValue(result, kSCPropNetModemDialMode, kSCValNetModemDialModeWaitForDialTone);
        } else {
            CFDictionaryAddValue(result, kSCPropNetModemDialMode, kSCValNetModemDialModeIgnoreDialTone);
        }
    }
    if (err == noErr) {
        err = CFQDictionarySetNumber(result, kSCPropNetModemErrorCorrection, digest->dataCompressionErrorCorrection);
    }
    if (err == noErr) {
        err = CFQDictionarySetNumber(result, kSCPropNetModemPulseDial, digest->pulseDial);
    }
    if (err == noErr) {
        err = CFQDictionarySetNumber(result, kSCPropNetModemSpeaker, digest->speaker);
    }
 
    // kSCPropNetModemSpeed is listed in "SCSchemaDefinitions.h" but I don't 
    // think it's ever stored in preferences; it more likely to be found in 
    // the dynamic store.
 
    if (err == noErr && digest->supportsHold) {
        err = CFQDictionarySetNumber(result, kSCPropNetModemHoldCallWaitingAudibleAlert, 1);
        if (err == noErr) {
            err = CFQDictionarySetNumber(result, kSCPropNetModemHoldDisconnectOnAnswer, 0);
        }
        if (err == noErr) {
            err = CFQDictionarySetNumber(result, kSCPropNetModemHoldEnabled, 0);
        }
        if (err == noErr) {
            err = CFQDictionarySetNumber(result, kSCPropNetModemHoldReminder, 1);
        }
        if (err == noErr) {
            err = CFQDictionarySetNumber(result, kSCPropNetModemHoldReminderTime, 10);
        }
    }
    
    if (err != noErr) {
        CFQRelease(result);
        result = NULL;
    }
    *entity = result;
    
    return err;
}
 
extern OSStatus MoreSCCreateEntity(CFStringRef protocol, const MoreSCDigest *digest, CFDictionaryRef *entity)
    // See comment in header.
{
    OSStatus err;
    
    assert(protocol != NULL);
    assert(digest != NULL);
    assert( entity != NULL);
    assert(*entity == NULL);
    
    // Call the appropriate protocol-specific routine based on the 
    // protocol type supplied.
    
    if (        CFEqual(protocol, kSCEntNetInterface) ) {
        err = MoreSCCreateInterfaceEntity(&digest->interface, entity);
    } else if ( CFEqual(protocol, kSCEntNetProxies) ) {
        err = MoreSCCreateProxiesEntity(&digest->proxies, entity);
    } else if ( CFEqual(protocol, kSCEntNetAppleTalk) ) {
        err = MoreSCCreateAppleTalkEntity(&digest->appleTalk, entity);
    } else if ( CFEqual(protocol, kSCEntNetDNS) ) {
        err = MoreSCCreateDNSEntity(&digest->dns, entity);
    } else if ( CFEqual(protocol, kSCEntNetIPv4) ) {
        err = MoreSCCreateIPv4Entity(&digest->ipv4, entity);
    } else if ( CFEqual(protocol, kSCEntNetPPP) ) {
        err = MoreSCCreatePPPEntity(&digest->ppp, entity);
    } else if ( CFEqual(protocol, kSCEntNetAirPort) ) {
        err = MoreSCCreateAirPortEntity(&digest->airPort, entity);
    } else if ( CFEqual(protocol, kSCEntNetEthernet) ) {
        err = MoreSCCreateEthernetEntity(&digest->ethernet, entity);
    } else if ( CFEqual(protocol, kSCEntNetModem) ) {
        err = MoreSCCreateModemEntity(&digest->modem, entity);
    } else {
        assert(false);
        err = -1;
    }
    return err;
}