/*++ Copyright (C) 1999 Microsoft Coporation Module Name: main.c Abstract: main module --*/ #include #include BOOL GlobalIsNT4, GlobalIsNT5; WCHAR CurrentDir[MAX_PATH*2]; CRITICAL_SECTION DhcpGlobalMemoryCritSect; CRITICAL_SECTION DhcpGlobalInProgressCritSect; CHAR DhcpEximOemDatabaseName[2048]; CHAR DhcpEximOemDatabasePath[2048]; HANDLE hLog; BOOL IsNT4( VOID ) { return GlobalIsNT4; } BOOL IsNT5( VOID ) { return GlobalIsNT5; } #define MAX_PRINTF_LEN 4096 char OutputBuf[MAX_PRINTF_LEN]; VOID StartDebugLog( VOID ) { CHAR Buffer[MAX_PATH*2]; if( NULL != hLog ) return; if( 0 == GetWindowsDirectoryA( Buffer, MAX_PATH )) { ZeroMemory(Buffer, sizeof(Buffer)); } if( Buffer[strlen(Buffer)-1] != '\\' ) { strcat(Buffer, "\\"); } strcat(Buffer, "dhcpexim.log"); hLog = CreateFileA( Buffer, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL ); if( hLog == INVALID_HANDLE_VALUE) { hLog = NULL; } if(GetLastError() == ERROR_ALREADY_EXISTS) { // // Appending to existing file // SetFilePointer(hLog,0,NULL,FILE_END); } } VOID CloseDebugLog( VOID ) { if( hLog ) { CloseHandle( hLog ); hLog = NULL; } } DWORD Tr( IN LPSTR Format, ... ) { va_list ArgList; ULONG Length; strcpy(OutputBuf, "[DHCP] "); Length = strlen(OutputBuf); va_start(ArgList, Format); Length = vsprintf(&OutputBuf[Length], Format, ArgList ); va_end(ArgList); #if DBG DbgPrint( (PCH)OutputBuf ); #endif if( hLog ) { DWORD Size = strlen(OutputBuf); WriteFile( hLog, OutputBuf, Size, &Size, NULL ); } return NO_ERROR; } BOOL IsPostW2k( VOID ) { HKEY hKey; DWORD Error, Type, Value, Size; Error = RegOpenKeyEx( HKEY_LOCAL_MACHINE, TEXT("SYSTEM\\CurrentControlSet\\Services\\DHCPServer\\Parameters"), 0, KEY_READ, &hKey ); if( NO_ERROR != Error ) return FALSE; Type = REG_DWORD; Value = 0; Size = sizeof(Value); Error = RegQueryValueEx( hKey, TEXT("Version"), NULL, &Type, (PVOID)&Value, &Size ); RegCloseKey( hKey ); // // if this value is not present, then upgrade is needed // return (Error == NO_ERROR ); } DWORD ReconcileLocalService( IN PULONG Subnets, IN DWORD nSubnets OPTIONAL ) /*++ Routine Description: This routine reconciles the specified scopes. This is needed after importing as the import doesnt actually get the bitmask, but only the database entries. --*/ { DWORD Error, FinalError, nRead, nTotal, i; DHCP_RESUME_HANDLE Resume = 0; LPDHCP_IP_ARRAY IpArray; if( 0 == nSubnets ) { IpArray = NULL; Error = DhcpEnumSubnets( L"127.0.0.1", &Resume, (ULONG)(-1), &IpArray, &nRead, &nTotal ); if( NO_ERROR != Error ) { Tr("DhcpEnumSubnets: %ld\n", Error); return Error; } if( 0 == nRead || 0 == nTotal || IpArray->NumElements == 0 ) { Tr("DhcpEnumSubnets returned none." ); return NO_ERROR; } Error = ReconcileLocalService( IpArray->Elements, IpArray->NumElements ); DhcpRpcFreeMemory( IpArray ); return Error; } // // Reconcile each of the specified scopes // FinalError = NO_ERROR; for( i = 0; i < nSubnets ; i ++ ) { LPDHCP_SCAN_LIST ScanList = NULL; Error = DhcpScanDatabase( L"127.0.0.1", Subnets[i], TRUE, &ScanList); if( NULL != ScanList ) DhcpRpcFreeMemory( ScanList ); if( NO_ERROR != Error ) { Tr("DhcpScanDatabase(0x%lx): %ld\n", Subnets[i], Error); FinalError = Error; } } return FinalError; } DWORD InitializeAndGetServiceConfig( OUT PM_SERVER *pServer ) /*++ Routine Description: This routine initializes all the modules and obtains the configuration for the service --*/ { DWORD Error; OSVERSIONINFO Ver; extern DWORD ClassIdRunningCount; // defined in mm\classdefl.c // // Initialize globals // GlobalIsNT4 = FALSE; GlobalIsNT5 = FALSE; try { InitializeCriticalSection( &DhcpGlobalMemoryCritSect ); InitializeCriticalSection( &DhcpGlobalInProgressCritSect ); }except ( EXCEPTION_EXECUTE_HANDLER ) { Error = GetLastError( ); return Error; } ClassIdRunningCount = 0x1000; // // Other initialization // Error = NO_ERROR; Ver.dwOSVersionInfoSize = sizeof(Ver); if( FALSE == GetVersionEx(&Ver) ) return GetLastError(); if( Ver.dwMajorVersion == 4 ) GlobalIsNT4 = TRUE; else if( Ver.dwMajorVersion == 5 ) GlobalIsNT5 = TRUE; else if( Ver.dwMajorVersion < 4 ) return ERROR_NOT_SUPPORTED; if( GlobalIsNT5 && IsPostW2k() ) GlobalIsNT5 = FALSE; #if DBG DbgPrint("Is NT4: %s, Is NT5: %s\n", GlobalIsNT4 ? "yes" : "no", GlobalIsNT5 ? "yes" : "no" ); StartDebugLog(); #endif // // // SaveBufSize = 0; SaveBuf = LocalAlloc( LPTR, SAVE_BUF_SIZE ); if( NULL == SaveBuf ) { return GetLastError(); } Error = GetCurrentDirectoryW( sizeof(CurrentDir)/sizeof(WCHAR), CurrentDir ); if( 0 != Error ) { Error = NO_ERROR; } else { Error = GetLastError(); Tr("GetCurrentDirectoryW: %ld\n", Error ); return Error; } // // Set right permissions on the db directory etc.. // Error = InitializeDatabaseParameters(); if( NO_ERROR != Error ) { Tr("InitializeDatabaseParameters: %ld\n", Error ); return Error; } // // Now obtain the configuration // if( !GlobalIsNT4 && !GlobalIsNT5 ) { // // Whistler configuration should be read from the // database.. // Error = DhcpeximReadDatabaseConfiguration(pServer); if( NO_ERROR != Error ) { Tr("DhcpeximReadDatabaseConfiguration: %ld\n", Error ); } } else { // // NT4 or W2K configuration should be read from registry.. // Error = DhcpeximReadRegistryConfiguration(pServer); if( NO_ERROR != Error ) { Tr("DhcpeximReadRegistryConfiguration: %ld\n", Error ); } } return Error; } DWORD CleanupServiceConfig( IN OUT PM_SERVER Server ) { DWORD Error; if( NULL != SaveBuf ) LocalFree(SaveBuf); SaveBuf = NULL; SaveBufSize = 0; if( NULL != Server ) MemServerFree(Server); Error = CleanupDatabaseParameters(); if( NO_ERROR != Error ) { Tr("CleanupServiceConfig: %ld\n", Error ); } if( FALSE == SetCurrentDirectoryW(CurrentDir) ) { if( NO_ERROR == Error ) Error = GetLastError(); Tr("SetCurrentDirectoryW: %ld\n", GetLastError()); } CloseDebugLog(); DeleteCriticalSection( &DhcpGlobalMemoryCritSect ); DeleteCriticalSection( &DhcpGlobalInProgressCritSect ); return Error; } DWORD ExportConfiguration( IN OUT PM_SERVER SvcConfig, IN ULONG *Subnets, IN ULONG nSubnets, IN HANDLE hFile ) /*++ Routine Description: This routine attempts to save the service configuration to a file after selecting the required subnets. --*/ { DWORD Error; // // First select the required subnets and get this // configuration alone. // Error = SelectConfiguration( SvcConfig, Subnets, nSubnets ); if( NO_ERROR != Error ) return Error; // // Save the configuration to the specified file handle // hTextFile = hFile; Error = SaveConfigurationToFile(SvcConfig); if( NO_ERROR != Error ) return Error; // // Now try to save the database entries to file // Error = SaveDatabaseEntriesToFile(Subnets, nSubnets); if( NO_ERROR != Error ) return Error; Tr("ExportConfiguration succeeded\n"); return NO_ERROR; } DWORD ImportConfiguration( IN OUT PM_SERVER SvcConfig, IN ULONG *Subnets, IN ULONG nSubnets, IN LPBYTE Mem, // import file : shared mem IN ULONG MemSize // shared mem size ) { DWORD Error; PM_SERVER Server; // // First obtain the configuration from the file // Error = ReadDbEntries( &Mem, &MemSize, &Server ); if( NO_ERROR != Error ) { Tr("ReadDbEntries: %ld\n", Error ); return Error; } // // Select the configuration required // Error = SelectConfiguration( Server, Subnets, nSubnets ); if( NO_ERROR != Error ) return Error; // // Merge the configuration along with the svc configuration // Error = MergeConfigurations( SvcConfig, Server ); if( NO_ERROR != Error ) { Tr("MergeConfigurations: %ld\n", Error ); } MemServerFree( Server ); if( NO_ERROR != Error ) return Error; // // Now save the new configuration to registry/disk // if( !GlobalIsNT5 && !GlobalIsNT4 ) { // // Whistler has config in database // Error = DhcpeximWriteDatabaseConfiguration(SvcConfig); if( NO_ERROR != Error ) { Tr("DhcpeximWriteDatabaseConfiguration: %ld\n", Error ); } } else { Error = DhcpeximWriteRegistryConfiguration(SvcConfig); if( NO_ERROR != Error ) { Tr("DhcpeximWriteRegistryConfiguration: %ld\n", Error ); } } if( NO_ERROR != Error ) return Error; // // Now read the database entries from file and stash them // into the db. // Error = SaveFileEntriesToDatabase( Mem, MemSize, Subnets, nSubnets ); if( NO_ERROR != Error ) { Tr("SaveFileEntriesToDatabase: %ld\n", Error ); } return Error; } VOID IpAddressToStringW( IN DWORD IpAddress, IN LPWSTR String // must have enough space preallocated ) { PUCHAR pAddress; ULONG Size; pAddress = (PUCHAR)&IpAddress; wsprintf(String, L"%ld.%ld.%ld.%ld", pAddress[3], pAddress[2], pAddress[1], pAddress[0] ); } DWORD StringToIpAddressW( LPWSTR pwszString ) { DWORD dwStrlen = 0; DWORD dwLen = 0; DWORD dwRes = 0; LPSTR pszString = NULL; if( pwszString == NULL ) return dwRes; pszString = DhcpUnicodeToOem(pwszString, NULL); if( pszString ) { dwRes = DhcpDottedStringToIpAddress(pszString); } return dwRes; } DWORD CmdLineDoImport( IN LPWSTR *Args, IN ULONG nArgs ) { // // Syntax: Import // LPWSTR FileName; ULONG Subnets[1024],*pSubnets, nSubnets, MemSize, Error; HANDLE hFile; LPBYTE Mem; PM_SERVER SvcConfig, FileConfig; if( nArgs == 1 ) return ERROR_BAD_ARGUMENTS; FileName = Args[0]; Args ++ ; nArgs --; // // First open the file // Error = OpenTextFile( FileName, TRUE, &hFile, &Mem, &MemSize ); if( NO_ERROR != Error ) { Tr("OpenTextFileForRead: %ld\n", Error ); return Error; } // // Now try to parse the rest of the arguments to see if they // are all ok // if( _wcsicmp(Args[0],L"ALL") == 0 ) { nSubnets = 0; pSubnets = NULL; } else { pSubnets = Subnets; nSubnets = 0; while( nArgs -- ) { pSubnets[nSubnets++] = StringToIpAddressW(*Args++); if( pSubnets[nSubnets-1] == INADDR_ANY || pSubnets[nSubnets-1] == INADDR_NONE ) { Error = ERROR_BAD_ARGUMENTS; goto Cleanup; } } } // // Initialize parameters // Error = InitializeAndGetServiceConfig( &SvcConfig ); if( NO_ERROR != Error ) { Tr("InitializeAndGetServiceConfig: %ld\n", Error ); goto Cleanup; } Error = ImportConfiguration( SvcConfig, pSubnets, nSubnets, Mem, MemSize ); if( NO_ERROR != Error ) { Tr("ImportConfiguration: %ld\n", Error ); } // // Finally cleanup // CleanupServiceConfig(SvcConfig); // // Also reconcile local server // ReconcileLocalService(pSubnets, nSubnets); Cleanup: CloseTextFile( hFile, Mem ); return Error; } DWORD CmdLineDoExport( IN LPWSTR *Args, IN ULONG nArgs ) { // // Syntax: Import // LPWSTR FileName; ULONG Subnets[1024],*pSubnets, nSubnets, MemSize, Error; HANDLE hFile; LPBYTE Mem; PM_SERVER SvcConfig, FileConfig; if( nArgs == 1 ) return ERROR_BAD_ARGUMENTS; FileName = Args[0]; Args ++ ; nArgs --; // // First open the file // Error = OpenTextFile( FileName, FALSE, &hFile, &Mem, &MemSize ); if( NO_ERROR != Error ) { Tr("OpenTextFileForRead: %ld\n", Error ); return Error; } // // Now try to parse the rest of the arguments to see if they // are all ok // if( _wcsicmp(Args[0],L"ALL") == 0 ) { nSubnets = 0; pSubnets = NULL; } else { pSubnets = Subnets; nSubnets = 0; while( nArgs -- ) { pSubnets[nSubnets++] = StringToIpAddressW(*Args++); if( pSubnets[nSubnets-1] == INADDR_ANY || pSubnets[nSubnets-1] == INADDR_NONE ) { Error = ERROR_BAD_ARGUMENTS; goto Cleanup; } } } // // Initialize parameters // Error = InitializeAndGetServiceConfig( &SvcConfig ); if( NO_ERROR != Error ) { Tr("InitializeAndGetServiceConfig: %ld\n", Error ); goto Cleanup; } // // Export configuration // Error = ExportConfiguration( SvcConfig, pSubnets, nSubnets, hFile ); if( NO_ERROR != Error ) { Tr("ExportConfiguration: %ld\n", Error ); } // // Finally cleanup // CleanupServiceConfig(SvcConfig); Cleanup: CloseTextFile( hFile, Mem ); return Error; } PM_SERVER DhcpGetCurrentServer( VOID ) { ASSERT( FALSE ); // // This is there only to let teh compiler compile without // having to include dhcpssvc.lib. This routine should never // be called at all // return NULL; } BOOL SubnetMatches( IN DWORD IpAddress, IN ULONG *Subnets, IN ULONG nSubnets ) { if( 0 == nSubnets || NULL == Subnets ) return TRUE; while( nSubnets -- ) { if( IpAddress == *Subnets ++) return TRUE; } return FALSE; } VOID DisableLocalScopes( IN ULONG *Subnets, IN ULONG nSubnets ) { DWORD Error; PM_SERVER SvcConfig; ARRAY_LOCATION Loc; PM_SUBNET Subnet; Error = InitializeAndGetServiceConfig(&SvcConfig); if( NO_ERROR != Error ) { Tr("DisableLocalScopes: Init: %ld\n", Error ); return; } Error = MemArrayInitLoc(&SvcConfig->Subnets, &Loc); while( NO_ERROR == Error ) { Error = MemArrayGetElement( &SvcConfig->Subnets, &Loc, &Subnet); ASSERT( NO_ERROR == Error && NULL != Subnet ); // // Disable the subnet // if( SubnetMatches(Subnet->Address, Subnets, nSubnets ) ) { Subnet->State = DISABLED(Subnet->State); } Error = MemArrayNextLoc( &SvcConfig->Subnets, &Loc); } // // Now save the new configuration to registry/disk // if( !GlobalIsNT5 && !GlobalIsNT4 ) { // // Whistler has config in database // Error = DhcpeximWriteDatabaseConfiguration(SvcConfig); if( NO_ERROR != Error ) { Tr("DhcpeximWriteDatabaseConfiguration: %ld\n", Error ); } } else { Error = DhcpeximWriteRegistryConfiguration(SvcConfig); if( NO_ERROR != Error ) { Tr("DhcpeximWriteRegistryConfiguration: %ld\n", Error ); } } CleanupServiceConfig(SvcConfig); } LPWSTR MakeName( IN DWORD IpAddress, IN LPWSTR Name ) { static WCHAR Buffer[40]; PUCHAR pAddress; LPWSTR RetVal; ULONG Size; pAddress = (PUCHAR)&IpAddress; wsprintf(Buffer, L"[%d.%d.%d.%d] ", pAddress[3], pAddress[2], pAddress[1], pAddress[0] ); Size = wcslen(Buffer)+1; if( NULL != Name ) Size += wcslen(Name); RetVal = LocalAlloc( LPTR, Size * sizeof(WCHAR)); if( NULL == RetVal ) return NULL; wcscpy(RetVal, Buffer); if( NULL != Name ) wcscat(RetVal, Name ); return RetVal; } DWORD InitializeCtxt( IN OUT PDHCPEXIM_CONTEXT Ctxt, IN PM_SERVER Server ) { DWORD Error,i, Size; ARRAY_LOCATION Loc; PM_SUBNET Subnet; // // First find the # of subnets and allocate array // Ctxt->nScopes = i = MemArraySize(&Server->Subnets); Ctxt->Scopes = LocalAlloc(LPTR, i * sizeof(Ctxt->Scopes[0]) ); if( NULL == Ctxt->Scopes ) { Ctxt->nScopes = 0; return GetLastError(); } // // Walk through the array and setup each element // i = 0; Error = MemArrayInitLoc( &Server->Subnets, &Loc ); while( NO_ERROR == Error ) { Error = MemArrayGetElement(&Server->Subnets, &Loc, &Subnet ); ASSERT(NO_ERROR == Error ); Ctxt->Scopes[i].SubnetAddress = Subnet->Address; Ctxt->Scopes[i].SubnetName = MakeName(Subnet->Address, Subnet->Name); if( NULL == Ctxt->Scopes[i].SubnetName ) return GetLastError(); i ++; Error = MemArrayNextLoc(&Server->Subnets, &Loc ); } return NO_ERROR; } DWORD DhcpEximInitializeContext( IN OUT PDHCPEXIM_CONTEXT Ctxt, IN LPWSTR FileName, IN BOOL fExport ) { DWORD Error; LPVOID Mem; ZeroMemory(Ctxt, sizeof(*Ctxt)); // // First set the FileName and fExport fields // Ctxt->FileName = FileName; Ctxt->fExport = fExport; // // Next open the file. // Error = OpenTextFile( FileName, !fExport, &Ctxt->hFile, &Ctxt->Mem, &Ctxt->MemSize ); if( NO_ERROR != Error ) { Tr("OpenTextFileForRead:%ld\n", Error ); return Error; } // // Initialize parameters and obtain config // Error = InitializeAndGetServiceConfig( (PM_SERVER*)&Ctxt->SvcConfig); if( NO_ERROR != Error ) { Tr("InitializeAndGetServiceConfig: %ld\n", Error ); CloseTextFile(Ctxt->hFile, Ctxt->Mem); return Error; } do { // // If this is an import, the configuration from the file // should also be read. // if( !fExport ) { Error = ReadDbEntries( &Ctxt->Mem, &Ctxt->MemSize, (PM_SERVER*)&Ctxt->FileConfig ); if( NO_ERROR != Error ) { Tr("ReadDbEntries: %ld\n", Error ); break; } } // // Allocate and initialize the Ctxt data structures with the // service scopes info in case of EXPORT // Error = InitializeCtxt( Ctxt, fExport ? Ctxt->SvcConfig : Ctxt->FileConfig ); if( NO_ERROR != Error ) { Tr("InitializeCtxt: %ld\n", Error ); break; } } while( 0 ); if( NO_ERROR != Error ) { CleanupServiceConfig( Ctxt->SvcConfig ); if( NULL != Ctxt->FileConfig ) { MemServerFree( (PM_SERVER)Ctxt->FileConfig ); } CloseTextFile( Ctxt->hFile, Ctxt->Mem ); } return Error; } DWORD CalculateSubnets( IN PDHCPEXIM_CONTEXT Ctxt, OUT PULONG *Subnets, OUT ULONG *nSubnets ) { DWORD Error, i; PULONG pSubnets; // // First check if there is atleast one unselected subnet // (*nSubnets) = 0; for( i = 0; i < Ctxt->nScopes; i ++ ) { if( Ctxt->Scopes[i].fSelected ) (*nSubnets) ++; } // // Special case if all subnets are selected // if( *nSubnets == Ctxt->nScopes ) { *nSubnets = 0; *Subnets = NULL; return NO_ERROR; } // // Allocate memory // *Subnets = LocalAlloc( LPTR, sizeof(DWORD)* (*nSubnets)); if( NULL == *Subnets ) return GetLastError(); // // Copy the subnets // (*nSubnets) = 0; for( i = 0; i < Ctxt->nScopes; i ++ ) { if( Ctxt->Scopes[i].fSelected ) { (*Subnets)[(*nSubnets)++] = Ctxt->Scopes[i].SubnetAddress; } } return NO_ERROR; } DWORD DhcpEximCleanupContext( IN OUT PDHCPEXIM_CONTEXT Ctxt, IN BOOL fAbort ) { DWORD Error, i; DWORD *Subnets, nSubnets; Error = NO_ERROR; Subnets = NULL; nSubnets = 0; // // If not aborting, attempt to execute the operation // if( !fAbort ) do { Error = CalculateSubnets( Ctxt, &Subnets, &nSubnets ); if( NO_ERROR != Error ) { Tr("CalculateSubnets: %ld\n", Error ); break; } if( Ctxt->fExport ) { // // Export the specified subnets out // Error = SelectConfiguration( Ctxt->SvcConfig, Subnets, nSubnets ); if( NO_ERROR != Error ) { Tr("SelectConfiguration: %ld\n", Error ); break; } Error = SaveConfigurationToFile( Ctxt->SvcConfig); if( NO_ERROR != Error ) { Tr("SaveConfigurationToFile: %ld\n", Error ); break; } // // Now try to save the database entries to file // Error = SaveDatabaseEntriesToFile(Subnets, nSubnets); if( NO_ERROR != Error ) { Tr("SaveDatabaseEntriesToFile: %ld\n", Error ); } break; } // // Import the specified subnets in // Error = SelectConfiguration( Ctxt->FileConfig, Subnets, nSubnets ); if( NO_ERROR != Error ) { Tr("SelectConfiguration: %ld\n", Error ); break; } Error = MergeConfigurations( Ctxt->SvcConfig, Ctxt->FileConfig ); if( NO_ERROR != Error ) { Tr("MergeConfigurations: %ld\n", Error ); break; } // // Now save the new configuration to registry/disk // if( !GlobalIsNT5 && !GlobalIsNT4 ) { // // Whistler has config in database // Error = DhcpeximWriteDatabaseConfiguration(Ctxt->SvcConfig); if( NO_ERROR != Error ) { Tr("DhcpeximWriteDatabaseConfiguration: %ld\n", Error ); } } else { Error = DhcpeximWriteRegistryConfiguration(Ctxt->SvcConfig); if( NO_ERROR != Error ) { Tr("DhcpeximWriteRegistryConfiguration: %ld\n", Error ); } } if( NO_ERROR != Error ) break; // // Now read the database entries from file and stash them // into the db. // Error = SaveFileEntriesToDatabase( Ctxt->Mem, Ctxt->MemSize, Subnets, nSubnets ); if( NO_ERROR != Error ) { Tr("SaveFileEntriesToDatabase: %ld\n", Error ); } } while( 0 ); // // Cleanup // if( NULL != Ctxt->SvcConfig ) { CleanupServiceConfig( Ctxt->SvcConfig ); } if( NULL != Ctxt->FileConfig ) { MemServerFree( (PM_SERVER)Ctxt->FileConfig ); } if( !fAbort && Ctxt->fExport == FALSE ) { // // Also reconcile local server // ReconcileLocalService( Subnets, nSubnets ); } CloseTextFile( Ctxt->hFile, Ctxt->Mem ); // // Walk through the array and free up pointers // for( i = 0 ; i < Ctxt->nScopes ; i ++ ) { if( Ctxt->Scopes[i].SubnetName ) { LocalFree( Ctxt->Scopes[i].SubnetName ); } } if( Ctxt->Scopes ) LocalFree( Ctxt->Scopes ); Ctxt->Scopes = NULL; Ctxt->nScopes = 0; if( !fAbort && Ctxt->fExport && Ctxt->fDisableExportedScopes ) { // // Fix the local scopes to all be disabled // DisableLocalScopes(Subnets, nSubnets); } if( NULL != Subnets && 0 != nSubnets ) { LocalFree( Subnets ); } return Error; }