OTTCPWillDial.c

/*
    File:       OTTCPWillDial.c
 
    Contains:   Library to determine whether open a TCP endpoint will
                dial the modem.
 
    Written by: Quinn "The Eskimo!"
 
    Copyright:  © 1998 by Apple Computer, Inc., all rights reserved.
 
    Change History (most recent first):
 
    You may incorporate this sample code into your applications without
    restriction, though the sample code has been provided "AS IS" and the
    responsibility for its operation is 100% yours.  However, what you are
    not permitted to do is to redistribute the source as "DSC Sample Code"
    after having made changes. If you're going to re-distribute the source,
    we require that you make it clear in the source that the code was
    descended from Apple Sample Code, but that you've made changes.
*/
 
/////////////////////////////////////////////////////////////////
 
#define qDebug 1
 
/////////////////////////////////////////////////////////////////
// Pick up lots of OT interfaces.
 
#import <OpenTransport.h>
#import <OpenTptInternet.h>
#import <OpenTptLinks.h>
#import <OTDebug.h>
 
/////////////////////////////////////////////////////////////////
// Pick up standard system interfaces.
 
#import <CodeFragments.h>
#import <Resources.h>
#import <Errors.h>
#import <Folders.h>
 
/////////////////////////////////////////////////////////////////
// Pick up OT configuration database stuff.
 
#import <NetworkSetup.h>
 
/////////////////////////////////////////////////////////////////
// Pick up our own header file.
 
#import "OTTCPWillDial.h"
 
/////////////////////////////////////////////////////////////////
// Default OTDebugStr prototype, because it's not in the interfaces.
 
extern void OTDebugStr(const char *message);
 
/////////////////////////////////////////////////////////////////
// Common code to parse 'iitf' preferences.
 
// The structure of an 'iitf' is the same regardless of whether it
// comes from a resource or from the configuration database.
 
// An 'iitf' preference consists of a UInt16 count followed
// by 0 or more interface specifications.  Each interface
// specification is a variable length data structure, with
// some fixed length and some variable length fields.
// This structure is used to represent an interface as 
// a fixed size data structure, much more suitable for
// C programming.
 
// In current versions of OT, only one interface is allowed.
 
struct TCPiitfPref {
    UInt8    fActive;
    InetHost fIPAddress;
    InetHost fSubnetMask;
    Str255   fAppleTalkZone;
    UInt8    fPath[36];         // Pascal string
    UInt8    fModuleName[31];   // Pascal string
    UInt32   fFramingFlags;
};
typedef struct TCPiitfPref TCPiitfPref;
 
static void UnpackIITF(Ptr *buffer, TCPiitfPref *unpackedIITF)
    // This routine unpacks an interface from an 'iitf' preference
    // into a TCPiitfPref.  *buffer must point to the beginning
    // of the interface, ie two bytes into the pref data if
    // if you're extracting the first interface.  *buffer
    // is updated to point to the byte after the last byte
    // parsed, so you can parse multiple interfaces by
    // repeatedly calling this routine.
{
    UInt8 *cursor;
    
    cursor = (UInt8 *) *buffer;
    
    unpackedIITF->fActive = *cursor;
    cursor += sizeof(UInt8);
    unpackedIITF->fIPAddress = *((InetHost *) cursor);
    cursor += sizeof(InetHost);
    unpackedIITF->fSubnetMask = *((InetHost *) cursor);
    cursor += sizeof(InetHost);
    BlockMoveData(cursor, unpackedIITF->fAppleTalkZone, *cursor + 1);
    cursor += (*cursor + 1);
    BlockMoveData(cursor, unpackedIITF->fPath, 36);
    cursor += 36;
    BlockMoveData(cursor, unpackedIITF->fModuleName, 32);
    cursor += 32;
    unpackedIITF->fFramingFlags = *((UInt32 *) cursor);
    cursor += sizeof(UInt32);
 
    *buffer = (Ptr) cursor;
}
 
static OSStatus GetPortNameFromIITF(Ptr buffer, SInt32 prefSize, char *portName)
    // This routine takes the address and size of an 'iitf' preference
    // and extracts the port name from the first interface.
{
    OSStatus err;
    UInt16 interfaceCount;
    Ptr cursor;
    TCPiitfPref firstInterface;
    UInt8 portNameLength;
    
    // Get the count of interfaces, checking for possibly bogus
    // preference data.
    
    err = noErr;
    if (prefSize < sizeof(UInt16)) {
        err = -1;
    }
    if (err == noErr) {
        interfaceCount = *((UInt16 *)buffer);
        if (interfaceCount < 1) {
            err = -1;
        }
    }
    
    // Unpack the first interface out of the 'iitf'.
    
    if (err == noErr) {
        cursor = buffer + sizeof(UInt16);
        UnpackIITF(&cursor, &firstInterface);
 
        OTAssert("GetPortNameFromIITF: Did not consume correct number of bytes",
                    interfaceCount > 1 || (cursor == buffer + prefSize) );
    }
    
    // Copy the port name out of the unpacked interface.
    
    if (err == noErr) {
        portNameLength = firstInterface.fPath[0];
        if ( portNameLength > kMaxProviderNameLength) {
            err = -1;
        } else {
 
            // Poor Man's C2PString avoids me having to figure
            // out which wacky library CodeWarrior wants me to link with
            // today!
            
            BlockMoveData(firstInterface.fPath + 1, portName, portNameLength);
            portName[ portNameLength ] = 0;
        }
    }
 
    return err;
}
 
/////////////////////////////////////////////////////////////////
// OT configuration database implementation.
 
static OSStatus GetFixedSizePref(CfgDatabaseRef ref, const CfgEntityRef *entityID, OSType prefType,
                        void *buffer, ByteCount prefSize)
    // This routine gets a fixed size preference out of
    // the configuration database described by ref.  entityID
    // is the entity containing the preference.  prefType is the
    // type of preference within the entity.  buffer is the address
    // where the preference data should be put.  prefSize is the size
    // of the buffer, and the routine validates that the preference
    // is exactly that size.
{
    OSStatus err;
    OSStatus err2;
    CfgEntityAccessID prefsRefNum;
    ByteCount actualPrefSize;
    
    OTAssert("GetFixedSizePref: paramErr", buffer != nil);
 
    // Open the entity, read out the preference, and then
    // close it down.
    
    err = OTCfgOpenPrefs(ref, entityID, false, &prefsRefNum);
    if (err == noErr) {
        err = OTCfgGetPrefsSize(prefsRefNum, prefType, &actualPrefSize);
        if (err == noErr && actualPrefSize != prefSize) {
            err = -1;
        }
        if (err == noErr) {
            err = OTCfgGetPrefs(prefsRefNum, prefType, buffer, prefSize);
        }
    
        err2 = OTCfgClosePrefs(prefsRefNum);
        if (err == noErr) {
            err = err2;
        }
    }
    
    return err;
}
 
static OSStatus GetPref(CfgDatabaseRef ref, const CfgEntityRef *entityID, OSType prefType,
                        void **buffer, ByteCount *prefSize)
    // This routine gets a variable size preference out of
    // the configuration database described by ref.  entityID
    // is the entity containing the preference.  prefType is the
    // type of preference within the entity.  buffer is the address
    // a pointer where the address of the newly allocated preference
    // buffer should be put.  prefSize is the address of a variable
    // where the size of the newly allocated preference should be.
    // 
    // The caller is responsible for disposing of the preference buffer
    // using OTFreeMem.  If the routine fails, no preference buffer is 
    // returned.
{
    OSStatus err;
    OSStatus err2;
    CfgEntityAccessID prefsRefNum;
    
    OTAssert("GetPref: paramErr", buffer != nil);
 
    // Open the entity, read out the preference, and then
    // close it down.
    
    *buffer = nil;  
    err = OTCfgOpenPrefs(ref, entityID, false, &prefsRefNum);
    if (err == noErr) {
        err = OTCfgGetPrefsSize(prefsRefNum, prefType, prefSize);
 
        if (err == noErr) {
            *buffer = OTAllocMem(*prefSize);
            if (*buffer == nil) {
                err = kOTOutOfMemoryErr;
            }
        }
        if (err == noErr) {
            err = OTCfgGetPrefs(prefsRefNum, prefType, *buffer, *prefSize);
        }
    
        err2 = OTCfgClosePrefs(prefsRefNum);
        if (err == noErr) {
            err = err2;
        }
    }
    
    // Clean up.
    
    if (err != noErr && *buffer != nil) {
        OTFreeMem(*buffer);
        *buffer = nil;
    }
    return err;
}
 
static OSStatus GetEntityList(CfgDatabaseRef ref, CfgAreaID area,
                                OSType class, OSType type,
                                CfgEntityRef **entityIDs, ItemCount *entityCount)
    // This routine gets a list of all the entities that match
    // class and type in the specified area of the specified database.
    // It allocates a buffer (using OTAllocMem) to hold the CfgEntityRef's
    // for the result and sets *entityIDs to point to the buffer.  It sets
    // entityCount to the number of CfgEntityRef's in the buffer.
{
    OSStatus err;
    CfgEntityInfo *junkEntityInfos;
 
    OTAssert("GetEntityList: paramErr", entityIDs != nil);
    OTAssert("GetEntityList: paramErr", entityCount != nil);
    
    *entityIDs = nil;
    junkEntityInfos = nil;
    
    err = OTCfgGetEntitiesCount(ref, area, class, type, entityCount);
    if (err == noErr) {
        *entityIDs = OTAllocMem(*entityCount * sizeof(CfgEntityRef));
        junkEntityInfos = OTAllocMem(*entityCount * sizeof(CfgEntityInfo));
        if (*entityIDs == nil || junkEntityInfos == nil) {
            err = kOTOutOfMemoryErr;
        }
    }
    if (err == noErr) {
    
        // I'm not sure whether you can pass nil to the entityInfos parameter
        // of OTCfgGetEntitiesList, so for the moment I'm passing in a valid
        // buffer.  I'll fix this up pending confirmation from engineering
        // that nil is OK.
        
        err = OTCfgGetEntitiesList(ref, area, 
                    class, type, 
                    entityCount, *entityIDs, junkEntityInfos);
    }
    
    // Clean up.
    
    if (junkEntityInfos != nil) {
        OTFreeMem(junkEntityInfos);
    }
    
    if (err != noErr) {
        if (*entityIDs != nil) {
            OTFreeMem(*entityIDs);
            *entityIDs = nil;
        }
    }
    return err;
}
 
static OSStatus GetInfoForTCPEntity(CfgDatabaseRef ref, const CfgEntityRef *entityID,
                                    Boolean *enabled, char *portName)
    // This routine returns the enabled status and port name
    // for the TCP/IP preferences entity described by entityID
    // in the ref database.
{   
    OSStatus err;
    SInt16 enabledInt;
    Ptr buffer;
    ByteCount prefSize;
 
    buffer = nil;
 
    // First return enabled using the simple API.
    
    err = GetFixedSizePref(ref, entityID, 'unld', &enabledInt, sizeof(SInt16));
    if (err == noErr) {
        *enabled = (enabledInt != 3);
    }
    
    // Now return the port name.  Now call the variable sized
    // API to get the 'iitf' resource and then extract the port name 
    // from the preference buffer.
    
    if (err == noErr) {
        err = GetPref(ref, entityID, 'iitf', &buffer, &prefSize);
    }
    if (err == noErr) {
        err = GetPortNameFromIITF(buffer, prefSize, portName);
    }
    
    // Clean up.
    
    if (buffer != nil) {
        OTFreeMem(buffer);
    }
    return err;
}
 
static OSStatus FindActiveSet(CfgDatabaseRef ref, CfgAreaID area, CfgEntityRef *activeSet)
    // This routine finds the entity ref of the active set entity
    // in the database.  It works by finding all the set entities
    // (there is generally only one in the current OT implementation)
    // and checks each one for the active bit set in its flags.
    // It returns the first set that claims to be active.
{
    OSStatus err;
    ItemCount setCount;
    CfgEntityRef *setEntities;
    Boolean found;
    ItemCount thisSetIndex;
    CfgSetsStruct thisStruct;
 
    setEntities = nil;
 
    err = GetEntityList(ref, area, kOTSetOfSettingsClass, kOTSetOfSettingsType, &setEntities, &setCount);
    if (err == noErr) {
        thisSetIndex = 0;
        found = false;
        while (err == noErr && thisSetIndex < setCount && ! found) {
            err = GetFixedSizePref(ref, &setEntities[thisSetIndex], kPrefsTypeStruct,
                            &thisStruct, sizeof(thisStruct));
            if (err == noErr) {
                found = ((thisStruct.fFlags & (1 << kSetsIndexActive)) != 0);
                if ( ! found ) {
                    thisSetIndex += 1;
                }
            }
        }
        if (err == noErr && ! found) {
            err = -1;
        }
    }
    if (err == noErr) {
        *activeSet = setEntities[thisSetIndex];
    }
 
    // Clean up.
    
    if (setEntities != nil) {
        OTFreeMem(setEntities);
    }
    
    return err;
}
 
static OSStatus FindCurrentTCPEntity(CfgDatabaseRef ref, CfgAreaID area, CfgEntityRef *currentTCPEntity)
    // This routine finds the current active TCP/IP connection entity.
    // It does this by first looking up the active set, then getting
    // the list of entities out of the active set, then searching
    // through that list of entities for the first TCP/IP connection
    // entity.
{
    OSStatus err;
    CfgEntityRef activeSet;
    CfgSetsVector *vectorPrefData;
    ByteCount vectorPrefSize;
    Boolean found;
    ItemCount thisElementIndex;
    CfgEntityInfo thisEntityInfo;
    
    vectorPrefData = nil;
 
    err = FindActiveSet(ref, area, &activeSet);
    if (err == noErr) {
        err = GetPref(ref, &activeSet, kPrefsTypeVector,
                        &vectorPrefData, &vectorPrefSize);
    }
    if (err == noErr) {
 
        // The kOTSetOfSettingsClass/kOTSetOfSettingsType preference
        // data is a count of elements followed by an array of that
        // many elements.  We walk index through the array looking
        // for the first TCP/IP connection entity.
        
        thisElementIndex = 0;
        found = false;
        while ( thisElementIndex < vectorPrefData->fCount && ! found ) {
            thisEntityInfo = vectorPrefData->fElements[thisElementIndex].fEntityInfo;
            found = (thisEntityInfo.fClass == kOTNetworkConnectionClass) 
                        && (thisEntityInfo.fType == kOTTCPv4NetworkConnection);
            if (found) {
                *currentTCPEntity = vectorPrefData->fElements[thisElementIndex].fEntityRef;
                
                // A weird misfeature of kOTSetOfSettingsClass/kOTSetOfSettingsType 
                // preference is that the CfgEntityRef's it holds have their area
                // (ie fLoc) set to a bogus area ID.  [It's actually the area ID
                // of the temporary area generated when the person who wrote the
                // set called OTCfgBeginAreaModifications.]  So we have to reset
                // this to the current area before returning it to our caller.
                
                currentTCPEntity->fLoc = area;
            } else {
                thisElementIndex += 1;
            }
        }
        if ( err == noErr && ! found ) {
            err = -3;
        }
    }
    
    // Clean up.
    
    if (vectorPrefData != nil) {
        OTFreeMem(vectorPrefData);
    }
    return err;
}
 
static OSStatus GetTCPInfoUsingAPI(Boolean *enabled, char *portName)
    // The high-level entry point into the configuration database
    // implementation.  We open the database, find the current
    // TCP entity and read the info we need out of that entity.
{
    OSStatus err;
    OSStatus err2;
    CfgDatabaseRef ref;
    CfgAreaID currentArea;
    CfgEntityRef currentTCPEntity;
    
    err = OTCfgOpenDatabase(&ref);
    if (err == noErr) {
        err = OTCfgGetCurrentArea(ref, &currentArea);
        if (err == noErr) {
            err = OTCfgOpenArea(ref, currentArea);
            if (err == noErr) {
                err = FindCurrentTCPEntity(ref, currentArea, &currentTCPEntity);
                if (err == noErr) {
                    err = GetInfoForTCPEntity(ref, &currentTCPEntity, enabled, portName);
                }
                
                err2 = OTCfgCloseArea(ref, currentArea);
                if (err == noErr) {
                    err = err2;
                }
            }
        }
    
        err2 = OTCfgCloseDatabase(&ref);
        if (err == noErr) {
            err = err2;
        }
    }
    return err;
}
 
/////////////////////////////////////////////////////////////////
// Implementation that reads the TCP/IP Preferences file directly.
 
// You have to search for the preferences file by type and creator
// because the name will be different on localised systems.
 
enum {
    kOTTCPPrefFileType = 'pref',
    kOTTCPPrefFileCreator = 'ztcp'
};
 
static OSStatus FindTCPPrefFile(FSSpec *fss)
    // This routine scans the Preferences folder looking
    // for the "TCP/IP Preferences" file by type and creator.
{
    OSStatus err;
    Boolean found;
    CInfoPBRec cpb;
    SInt16 index;
    
    err = FindFolder(kOnSystemDisk, kPreferencesFolderType, kCreateFolder, &fss->vRefNum, &fss->parID);
    if (err == noErr) {
        found = false;
        index = 1;
        do {
            cpb.hFileInfo.ioVRefNum = fss->vRefNum;
            cpb.hFileInfo.ioDirID = fss->parID;
            cpb.hFileInfo.ioNamePtr = fss->name;
            cpb.hFileInfo.ioFDirIndex = index;
            err = PBGetCatInfoSync(&cpb);
            if (err == noErr) {
                found = (   cpb.hFileInfo.ioFlFndrInfo.fdType == kOTTCPPrefFileType &&
                            cpb.hFileInfo.ioFlFndrInfo.fdCreator == kOTTCPPrefFileCreator );
            }
            index += 1;
        } while (err == noErr & ! found);
    }
    return err;
}
 
static OSStatus CheckResError(void *testH)
    // A trivial wrapper routine for ResError,
    // which is too lame to report an error code
    // in all cases when GetResource fails.
{
    OSStatus err;
 
    err = ResError();
    if (err == noErr && testH == nil) {
        err = resNotFound;
    }
    return err;
}
 
static OSStatus GetTCPInfoFromFile(Boolean *enabled, char *portName)
    // This is the high-level entry point into the direct file
    // access implementation.  It simply finds the preferences
    // file and reads the preferences out directly.
{
    OSStatus err;
    FSSpec fss;
    SInt16 oldResFile;
    SInt16 prefResFile;
    Handle currentConfigResourceH;
    Handle unldResource;
    Handle iitfResource;
    SInt8  s;
    
    oldResFile = CurResFile();
    
    err = FindTCPPrefFile(&fss);
    if (err == noErr) {
        prefResFile = FSpOpenResFile(&fss, fsRdPerm);
        err = ResError();
    }
    if (err == noErr) {
 
        currentConfigResourceH = Get1Resource('ccfg', 1);
        err = CheckResError(currentConfigResourceH);
 
        if (err == noErr && GetHandleSize(currentConfigResourceH) != sizeof(SInt16) ) {
            OTDebugBreak("GetTCPInfoFromFile: 'ccfg' is of the wrong size");
            err = -1;
        }
 
        if (err == noErr) {
            unldResource = Get1Resource('unld', **( (SInt16 **) currentConfigResourceH));
            err = CheckResError(unldResource);
        }
        if (err == noErr) {
            *enabled = ( **((SInt16 **) unldResource) != 3);
        }
 
        if (err == noErr) {
            iitfResource = Get1Resource('iitf', **( (SInt16 **) currentConfigResourceH));
            err = CheckResError(iitfResource);
        }
 
        if (err == noErr) {
            s = HGetState(iitfResource);
            HLock(iitfResource);
            err = GetPortNameFromIITF(*iitfResource, GetHandleSize(iitfResource), portName);
            HSetState(iitfResource, s);
        }
        
        CloseResFile(prefResFile);
        OTAssert("GetTCPInfoFromFile: Failed to close prefResFile", ResError() == noErr);
    }
    
    UseResFile(oldResFile);
    OTAssert("GetTCPInfoFromFile: Could not re-establish CurResFile", ResError() == noErr);
    
    return err;
}
 
/////////////////////////////////////////////////////////////////
// Code that's common to both implementations.
 
static OSStatus GetTCPInfo(Boolean *enabled, char *portName)
    // A dispatcher.  If the config database is available,
    // we call it, otherwise we fall back to reading the
    // preferences file directly.
{
    OSStatus err;
    
    if ( (void *) OTCfgOpenDatabase == (void *) kUnresolvedCFragSymbolAddress) {
        err = GetTCPInfoFromFile(enabled, portName);
    } else {
        err = GetTCPInfoUsingAPI(enabled, portName);
    }
    return err;
}
 
// If you set kUseInetInterfaceInfo to false, OTTCPWillDial will not
// use the heuristic of "if the TCP/IP stack is loaded, it's safe
// to open an endpoint".  This is especially useful when debugging.
 
const Boolean kUseInetInterfaceInfo = true;
 
extern OSStatus OTTCPWillDial(UInt32 *willDial)
    // The main entry point.  We call our core
    // implementation and then generate the result
    // based on the returned information.
{
    OSStatus err;
    InetInterfaceInfo info;
    Boolean enabled;
    char currentPortName[kMaxProviderNameSize];
    OTPortRecord portRecord;
    
    OTAssert("OTTCPWillDial: paramErr", willDial != nil);
    
    *willDial = kOTTCPDialUnknown;
    
    err = noErr;
    if ( kUseInetInterfaceInfo && OTInetGetInterfaceInfo(&info, kDefaultInetInterface) == noErr) {
    
        // The TCP/IP stack is already loaded.  With the current
        // way TCP/IP is organised, the stack being loaded implies
        // that we're already dialled in.
        
        *willDial = kOTTCPDialNo;
        
    } else {
        err = GetTCPInfo(&enabled, currentPortName);
        if (err == noErr) {
            if (enabled) {
                if ( OTStrEqual(currentPortName, "ddp") ) { 
 
                    // A special case for MacIP, because "ddp" does
                    // not have an active port if AppleTalk is disabled.
                    
                    *willDial = kOTTCPDialNo;
                    
                } else if ( OTFindPort(&portRecord, currentPortName) ) {
                
                    // We know the port.  Look at the device type
                    // to decide whether we might dial.
                
                    switch ( OTGetDeviceTypeFromPortRef(portRecord.fRef) ) {
                        case kOTADEVDevice:
                        case kOTIRTalkDevice:
                        case kOTSMDSDevice:
                            OTDebugBreak("OTTCPWillDial: TCP shouldn't be using this link type");
                            *willDial = kOTTCPDialNo;
                            break;
                            
                        case kOTISDNDevice:
                        case kOTATMDevice:
                        case kOTSerialDevice:
                        case kOTModemDevice:
                            OTDebugBreak("OTTCPWillDial: TCP shouldn't be using this link type");
                            *willDial = kOTTCPDialYes;
                            break;
 
                        case kOTLocalTalkDevice:
                        case kOTTokenRingDevice:
                        case kOTEthernetDevice:
                        case kOTFastEthernetDevice:
                        case kOTFDDIDevice:
                        case kOTIrDADevice:
                        case kOTATMSNAPDevice:
                        case kOTFibreChannelDevice:
                        case kOTFireWireDevice:
                            *willDial = kOTTCPDialNo;
                            break;
 
                        case kOTMDEVDevice:
                        case kOTSLIPDevice:
                        case kOTPPPDevice:
                            *willDial = kOTTCPDialYes;
                            break;
 
                        default:
                            OTAssert("OTTCPWillDial", *willDial == kOTTCPDialUnknown);
                            break;
                    }
                } else {
                    err = -1;
                }
            } else {
                *willDial = kOTTCPDialTCPDisabled;
            }
        }
    }
    
    return err;
}