windows-nt/Source/XPSP1/NT/net/tcpip/services/rip/util.c

724 lines
24 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
//****************************************************************************
//
// Microsoft Windows NT RIP
//
// Copyright 1995-96
//
//
// Revision History
//
//
// 3/12/95 Gurdeep Singh Pall Created
//
//
// Description: General utility functions:
//
//****************************************************************************
#include "pchrip.h"
#pragma hdrstop
DWORD LoadAddressSockets();
DWORD OpenTcp();
DWORD TCPSetInformationEx(LPVOID lpvInBuffer, LPDWORD lpdwInSize,
LPVOID lpvOutBuffer, LPDWORD lpdwOutSize);
DWORD TCPQueryInformationEx(LPVOID lpvInBuffer, LPDWORD lpdwInSize,
LPVOID lpvOutBuffer, LPDWORD lpdwOutSize);
//-------------------------------------------------------------------
// Function: LogEntry
// Parameters:
// WORD wEventType type of event (ERROR, WARNING, etc)
// DWORD dwMsgID ID of message string
// WORD wNumStrings number of strings in lplpStrings
// LPSTR *lplpStrings array of strings
// DWORD dwErr error code
//-------------------------------------------------------------------
void LogEntry(WORD wEventType, DWORD dwMsgID, WORD wNumStrings,
LPSTR *lplpStrings, DWORD dwErr) {
DWORD dwSize;
LPVOID lpvData;
HANDLE hLog;
PSID pSidUser = NULL;
hLog = RegisterEventSource(NULL, RIP_SERVICE);
dwSize = (dwErr == NO_ERROR) ? 0 : sizeof(dwErr);
lpvData = (dwErr == NO_ERROR) ? NULL : (LPVOID)&dwErr;
ReportEvent(hLog, wEventType, 0, dwMsgID, pSidUser,
wNumStrings, dwSize, lplpStrings, lpvData);
DeregisterEventSource(hLog);
}
//-------------------------------------------------------------------
// Function: RipLogError
// Parameters:
// see LogEntry for parameter description
//-------------------------------------------------------------------
void RipLogError(DWORD dwMsgID, WORD wNumStrings,
LPSTR *lplpStrings, DWORD dwErr) {
DWORD dwLevel;
dwLevel = g_params.dwLoggingLevel;
if (dwLevel < LOGLEVEL_ERROR) { return; }
LogEntry(EVENTLOG_ERROR_TYPE, dwMsgID, wNumStrings, lplpStrings, dwErr);
}
//-------------------------------------------------------------------
// Function: LogWarning
// Parameters:
// see LogEntry for parameter description
//-------------------------------------------------------------------
void RipLogWarning(DWORD dwMsgID, WORD wNumStrings,
LPSTR *lplpStrings, DWORD dwErr) {
DWORD dwLevel;
dwLevel = g_params.dwLoggingLevel;
if (dwLevel < LOGLEVEL_WARNING) { return; }
LogEntry(EVENTLOG_WARNING_TYPE, dwMsgID, wNumStrings, lplpStrings, dwErr);
}
//-------------------------------------------------------------------
// Function: LogInformation
// Parameters:
// see LogEntry for parameter description
//-------------------------------------------------------------------
void RipLogInformation(DWORD dwMsgID, WORD wNumStrings,
LPSTR *lplpStrings, DWORD dwErr) {
DWORD dwLevel;
dwLevel = g_params.dwLoggingLevel;
if (dwLevel < LOGLEVEL_INFORMATION) { return; }
LogEntry(EVENTLOG_INFORMATION_TYPE, dwMsgID,
wNumStrings, lplpStrings, dwErr);
}
//-------------------------------------------------------------------
// Function: Audit
//-------------------------------------------------------------------
VOID Audit(IN WORD wEventType, IN DWORD dwMessageId,
IN WORD cNumberOfSubStrings, IN LPSTR *plpwsSubStrings) {
HANDLE hLog;
PSID pSidUser = NULL;
// Audit enabled
hLog = RegisterEventSourceA(NULL, RIP_SERVICE);
ReportEventA(hLog, wEventType, 0, dwMessageId, pSidUser,
cNumberOfSubStrings, 0, plpwsSubStrings, (PVOID)NULL);
DeregisterEventSource( hLog );
}
//-------------------------------------------------------------------
// Function: dbgprintf
//-------------------------------------------------------------------
VOID dbgprintf(LPSTR lpszFormat, ...) {
va_list arglist;
va_start(arglist, lpszFormat);
TraceVprintf(g_dwTraceID, lpszFormat, arglist);
va_end(arglist);
}
//-------------------------------------------------------------------
// Function: InitializeAddressTable
//
// Assumes the address table is locked.
//-------------------------------------------------------------------
DWORD InitializeAddressTable(BOOL bFirstTime) {
LPRIP_ADDRESS lpaddr, lpaddrend;
LPRIP_ADDRESS_STATISTICS lpstats;
DWORD dwErr, dwCount, *lpdw, *lpdwend;
PMIB_IPADDRROW lpTable, lpiae, lpiaeend;
// first close old sockets, if necessary
if (!bFirstTime) {
lpaddrend = g_ripcfg.lpAddrTable + g_ripcfg.dwAddrCount;
for (lpaddr = g_ripcfg.lpAddrTable; lpaddr < lpaddrend; lpaddr++) {
if (lpaddr->sock != INVALID_SOCKET) {
closesocket(lpaddr->sock);
lpaddr->sock = INVALID_SOCKET;
}
}
}
dwErr = GetIPAddressTable(&lpTable, &dwCount);
if (dwErr != 0) { return dwErr; }
if (dwCount > MAX_ADDRESS_COUNT) { dwCount = MAX_ADDRESS_COUNT; }
lpaddr = g_ripcfg.lpAddrTable;
lpstats = g_ripcfg.lpStatsTable->lpAddrStats;
lpiaeend = lpTable + dwCount;
g_ripcfg.dwAddrCount = dwCount;
for (lpiae = lpTable; lpiae < lpiaeend; lpiae++) {
if (!lpiae->dwAddr || IP_LOOPBACK_ADDR(lpiae->dwAddr)) {
--g_ripcfg.dwAddrCount; continue;
}
lpaddr->dwIndex = lpiae->dwIndex;
lpaddr->dwAddress = lpiae->dwAddr;
lpaddr->dwNetmask = lpiae->dwMask;
lpaddr->dwFlag = 0;
lpdwend = (LPDWORD)(lpstats + 1);
for (lpdw = (LPDWORD)lpstats; lpdw < lpdwend; lpdw++) {
InterlockedExchange(lpdw, 0);
}
InterlockedExchange(&lpstats->dwAddress, lpaddr->dwAddress);
lpaddr->lpstats = lpstats;
lpstats++;
lpaddr++;
}
FreeIPAddressTable(lpTable);
// also update the stats address count
InterlockedExchange(&g_ripcfg.lpStatsTable->dwAddrCount,
g_ripcfg.dwAddrCount);
// if no addresses, bail out now
if (g_ripcfg.dwAddrCount == 0) {
dbgprintf("no IP addresses available for routing");
return NO_ERROR;
}
// open sockets for each interface we have, and set options on the sockets
dwErr = LoadAddressSockets();
return dwErr;
}
//-------------------------------------------------------------------
// Function: InitializeStatsTable
//
// Creates a mapping of our statistics table in shareable memory
// so interested processes can examine RIP's behavior.
//-------------------------------------------------------------------
DWORD InitializeStatsTable() {
DWORD dwErr;
g_ripcfg.lpStatsTable = NULL;
// set up a pointer to the memory
g_ripcfg.lpStatsTable = HeapAlloc(GetProcessHeap(), 0,
sizeof(RIP_STATISTICS));
if (g_ripcfg.lpStatsTable == NULL) {
dwErr = ERROR_NOT_ENOUGH_MEMORY;
dbgprintf( "InitializeStatsTable failed with error %x\n", dwErr );
RipLogError( RIPLOG_ADDR_ALLOC_FAILED, 0, NULL, dwErr );
return dwErr;
}
ZeroMemory(g_ripcfg.lpStatsTable, sizeof(RIP_STATISTICS));
return 0;
}
VOID CleanupStatsTable() {
if (g_ripcfg.lpStatsTable != NULL) {
InterlockedExchange(&g_ripcfg.lpStatsTable->dwAddrCount, 0);
HeapFree(GetProcessHeap(), 0, g_ripcfg.lpStatsTable);
g_ripcfg.lpStatsTable = NULL;
}
}
//--------------------------------------------------------------------------
// Function: LoadAddressSockets
//
// Opens, configures, and binds sockets for each address in the table
//--------------------------------------------------------------------------
DWORD LoadAddressSockets() {
IN_ADDR addr;
CHAR szAddress[24] = {0};
CHAR *ppszArgs[] = { szAddress };
CHAR *pszTemp;
SOCKADDR_IN sinsock;
DWORD dwOption, dwErr;
LPRIP_ADDRESS lpaddr, lpend;
struct ip_mreq imOption;
lpend = g_ripcfg.lpAddrTable + g_ripcfg.dwAddrCount;
for (lpaddr = g_ripcfg.lpAddrTable; lpaddr < lpend; lpaddr++) {
if ((lpaddr->dwFlag & ADDRFLAG_DISABLED) != 0) {
continue;
}
addr.s_addr = lpaddr->dwAddress;
pszTemp = inet_ntoa(addr);
if (pszTemp != NULL) {
strcpy(szAddress, pszTemp);
}
lpaddr->sock = socket(AF_INET, SOCK_DGRAM, 0);
if (lpaddr->sock == INVALID_SOCKET) {
dwErr = WSAGetLastError();
dbgprintf("error %d creating socket for address %s",
dwErr, szAddress);
RipLogError(RIPLOG_CREATESOCK_FAILED, 1, ppszArgs, dwErr);
continue;
}
dwOption = 1;
dwErr = setsockopt(lpaddr->sock, SOL_SOCKET, SO_BROADCAST,
(LPBYTE)&dwOption, sizeof(dwOption));
if (dwErr == SOCKET_ERROR) {
dwErr = WSAGetLastError();
dbgprintf("error %d enabling broadcast for address %s",
dwErr, szAddress);
RipLogError(RIPLOG_SET_BCAST_FAILED, 1, ppszArgs, dwErr);
// this socket is useless if we can't broadcast on it
closesocket(lpaddr->sock);
lpaddr->sock = INVALID_SOCKET;
continue;
}
dwOption = 1;
dwErr = setsockopt(lpaddr->sock, SOL_SOCKET, SO_REUSEADDR,
(LPBYTE)&dwOption, sizeof(dwOption));
if (dwErr == SOCKET_ERROR) {
dwErr = WSAGetLastError();
dbgprintf("error %d enabling reuse of address %s",
dwErr, szAddress);
RipLogError(RIPLOG_SET_REUSE_FAILED, 1, ppszArgs, dwErr);
}
sinsock.sin_family = AF_INET;
sinsock.sin_port = htons(RIP_PORT);
sinsock.sin_addr.s_addr = lpaddr->dwAddress;
dwErr = bind(lpaddr->sock, (LPSOCKADDR)&sinsock, sizeof(SOCKADDR_IN));
if (dwErr == SOCKET_ERROR) {
dwErr = WSAGetLastError();
dbgprintf("error %d binding address %s to RIP port",
dwErr, szAddress);
RipLogError(RIPLOG_BINDSOCK_FAILED, 1, ppszArgs, dwErr);
closesocket(lpaddr->sock);
lpaddr->sock = INVALID_SOCKET;
continue;
}
#if DBG
dbgprintf( "socket %d bound to %s\n\n", lpaddr-> sock, inet_ntoa( *( (struct in_addr *) &(lpaddr-> dwAddress) ) ) );
#endif
//
// enable multicasting also
//
sinsock.sin_addr.s_addr = lpaddr->dwAddress;
dwErr = setsockopt(lpaddr->sock, IPPROTO_IP, IP_MULTICAST_IF,
(PBYTE)&sinsock.sin_addr, sizeof(IN_ADDR));
if (dwErr == SOCKET_ERROR) {
dwErr = WSAGetLastError();
dbgprintf("error %d setting interface %d (%s) as multicast",
dwErr, lpaddr->dwIndex, szAddress);
RipLogError(RIPLOG_SET_MCAST_IF_FAILED, 1, ppszArgs, dwErr);
closesocket(lpaddr->sock);
lpaddr->sock = INVALID_SOCKET;
continue;
}
//
// join the IPRIP multicast group
//
imOption.imr_multiaddr.s_addr = RIP_MULTIADDR;
imOption.imr_interface.s_addr = lpaddr->dwAddress;
dwErr = setsockopt(lpaddr->sock, IPPROTO_IP, IP_ADD_MEMBERSHIP,
(PBYTE)&imOption, sizeof(imOption));
if (dwErr == SOCKET_ERROR) {
dwErr = WSAGetLastError();
dbgprintf("error %d enabling multicast on interface %d (%s)",
dwErr, lpaddr->dwIndex, szAddress);
RipLogError(RIPLOG_JOIN_GROUP_FAILED, 1, ppszArgs, dwErr);
closesocket(lpaddr->sock);
lpaddr->sock = INVALID_SOCKET;
continue;
}
}
return 0;
}
//--------------------------------------------------------------------------
// Function: LoadRouteTable
//
// Get the transports routing table. This is called with firsttime set
// to TRUE when RIP loads. After that it is called with firsttime set
// to FALSE. Assumes the route table is locked.
//--------------------------------------------------------------------------
int LoadRouteTable(BOOL bFirstTime) {
IN_ADDR addr;
LPHASH_TABLE_ENTRY rt_entry;
CHAR szDest[32] = {0};
CHAR szNexthop[32] = {0};
CHAR *pszTemp;
DWORD dwRouteTimeout, dwErr, dwRouteCount;
LPIPROUTE_ENTRY lpRouteEntryTable, lpentry, lpentend;
dwErr = GetRouteTable(&lpRouteEntryTable, &dwRouteCount);
if (dwErr != 0) {
return dwErr;
}
dwRouteTimeout = g_params.dwRouteTimeout;
// now prune unwanted entries, and add the others to our hash table.
// we only load RIP, static, and SNMP routes, and for non-RIP routes
// we set the timeout to 90 seconds,
//
lpentend = lpRouteEntryTable + dwRouteCount;
for (lpentry = lpRouteEntryTable; lpentry < lpentend; lpentry++) {
if (lpentry->ire_metric1 < METRIC_INFINITE &&
(lpentry->ire_proto == IRE_PROTO_RIP ||
lpentry->ire_proto == IRE_PROTO_LOCAL ||
lpentry->ire_proto == IRE_PROTO_NETMGMT) &&
!IP_LOOPBACK_ADDR(lpentry->ire_dest) &&
!IP_LOOPBACK_ADDR(lpentry->ire_nexthop) &&
!CLASSD_ADDR(lpentry->ire_dest) &&
!CLASSE_ADDR(lpentry->ire_dest) &&
!IsBroadcastAddress(lpentry->ire_dest) &&
!IsDisabledLocalAddress(lpentry->ire_nexthop)) {
rt_entry = GetRouteTableEntry(lpentry->ire_index,
lpentry->ire_dest,
lpentry->ire_mask);
// If we hit low memory conditions, get out of the loop.
//
if (rt_entry == NULL) {
dwErr = ERROR_OUTOFMEMORY;
break;
}
// only update the route with information from
// the system table if it is a route that was learnt
// from the system table; this is so if it is a new route
// or if it is an old static or SNMP-added route
//
if ((rt_entry->dwFlag & NEW_ENTRY) ||
rt_entry->dwProtocol == IRE_PROTO_LOCAL ||
rt_entry->dwProtocol == IRE_PROTO_NETMGMT) {
// if the route is new and this isn't the first time
// we have loaded the system routing table, set change flag
//
if (!bFirstTime && (rt_entry->dwFlag & NEW_ENTRY)) {
rt_entry->dwFlag |= ROUTE_CHANGE;
addr.s_addr = lpentry->ire_dest;
pszTemp = inet_ntoa(addr);
if (pszTemp != NULL) {
strcpy(szDest, pszTemp);
}
addr.s_addr = lpentry->ire_nexthop;
pszTemp = inet_ntoa(addr);
if (pszTemp != NULL) {
strcpy(szNexthop, pszTemp);
}
dbgprintf("new entry: dest=%s, nexthop=%s, "
"metric=%d, protocol=%d", szDest, szNexthop,
lpentry->ire_metric1, lpentry->ire_proto);
}
rt_entry->dwFlag &= ~NEW_ENTRY;
// we need to reset all these parameters
// because any of them may have changed since
// the last time the system route table was loaded.
//
rt_entry->dwIndex = lpentry->ire_index;
rt_entry->dwProtocol = lpentry->ire_proto;
rt_entry->dwDestaddr = lpentry->ire_dest;
rt_entry->dwNetmask = lpentry->ire_mask;
rt_entry->dwNexthop = lpentry->ire_nexthop;
rt_entry->dwMetric = lpentry->ire_metric1;
if (rt_entry->dwProtocol == IRE_PROTO_RIP) {
rt_entry->lTimeout = (LONG)dwRouteTimeout;
}
else {
rt_entry->lTimeout = DEF_LOCALROUTETIMEOUT;
}
rt_entry->dwFlag &= ~GARBAGE_TIMER;
rt_entry->dwFlag |= TIMEOUT_TIMER;
// if our estimate is that this is a host route
// and its mask tells us it is a host route
// then we mark it as being a host route
//
if (IsHostAddress(rt_entry->dwDestaddr) &&
rt_entry->dwNetmask == HOSTADDR_MASK) {
rt_entry->dwFlag |= ROUTE_HOST;
}
}
}
}
FreeRouteTable(lpRouteEntryTable);
return dwErr;
}
//--------------------------------------------------------------------------
// Function: UpdateSystemRouteTable
//
// Parameters:
// LPHASH_TABLE_ENTRY rt_entry the entry to update
// BOOL bAdd if true, the entry is added
// otherwise, the entry is deleted
//
// Returns: DWORD:
//
//
// Add a new route to the route table. Note: due to MIB's use of
// the destination address as an instance number, and also due to
// TCP/IP stack allowing multiple entries for a single destination,
// an ambiguity can exist. If there is already an entry for this
// destination. This will have the effect of changing the existing
// entry, rather than creating a new one.
// This function assumes the address table is locked.
//--------------------------------------------------------------------------
DWORD UpdateSystemRouteTable(LPHASH_TABLE_ENTRY rt_entry, BOOL bAdd) {
IN_ADDR addr;
DWORD dwErr, dwRouteType;
// never delete or update a route which was not created by RIP
if (rt_entry->dwProtocol != IRE_PROTO_RIP) {
return 0;
}
if (bAdd) {
dwRouteType = (IsLocalAddr(rt_entry->dwNexthop) ? IRE_TYPE_DIRECT
: IRE_TYPE_INDIRECT);
#if 0
DbgPrintf(
"AddRoute : Protocol %x, Index %x, dest addr %x, dest mask %x\n",
rt_entry->dwProtocol, rt_entry->dwIndex, rt_entry->dwDestaddr, rt_entry->dwNetmask
);
DbgPrintf(
"Next Hop %x, Metric %x\n\n", rt_entry->dwNexthop, rt_entry->dwMetric
);
#endif
dwErr = AddRoute(rt_entry->dwProtocol, dwRouteType, rt_entry->dwIndex,
rt_entry->dwDestaddr, rt_entry->dwNetmask,
rt_entry->dwNexthop, rt_entry->dwMetric);
}
else {
dwErr = DeleteRoute(rt_entry->dwIndex, rt_entry->dwDestaddr,
rt_entry->dwNetmask, rt_entry->dwNexthop);
}
if (dwErr == STATUS_SUCCESS) {
if (bAdd) {
InterlockedIncrement(
&g_ripcfg.lpStatsTable->dwRoutesAddedToSystemTable);
}
else {
InterlockedIncrement(
&g_ripcfg.lpStatsTable->dwRoutesDeletedFromSystemTable);
}
}
else {
if (bAdd) {
dbgprintf("error %X adding route to system table", dwErr);
RipLogError(RIPLOG_ADD_ROUTE_FAILED, 0, NULL, dwErr);
InterlockedIncrement(
&g_ripcfg.lpStatsTable->dwSystemAddRouteFailures);
}
else {
dbgprintf("error %X deleting route from system table", dwErr);
RipLogError(RIPLOG_DELETE_ROUTE_FAILED, 0, NULL, dwErr);
InterlockedIncrement(
&g_ripcfg.lpStatsTable->dwSystemDeleteRouteFailures);
}
}
return dwErr;
}
#ifndef CHICAGO
//------------------------------------------------------------------
// Function: OpenTcp
//
// Parameters:
// none.
//
// Opens the handle to the Tcpip driver.
//------------------------------------------------------------------
DWORD OpenTcp() {
NTSTATUS status;
UNICODE_STRING nameString;
IO_STATUS_BLOCK ioStatusBlock;
OBJECT_ATTRIBUTES objectAttributes;
// Open the ip stack for setting routes and parps later.
//
// Open a Handle to the TCP driver.
//
RtlInitUnicodeString(&nameString, DD_TCP_DEVICE_NAME);
InitializeObjectAttributes(&objectAttributes, &nameString,
OBJ_CASE_INSENSITIVE, NULL, NULL);
status = NtCreateFile(&g_ripcfg.hTCPDriver,
SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA,
&objectAttributes, &ioStatusBlock, NULL,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ | FILE_SHARE_WRITE,
FILE_OPEN_IF, 0, NULL, 0);
return (status == STATUS_SUCCESS ? 0 : ERROR_OPEN_FAILED);
}
//---------------------------------------------------------------------
// Function: TCPQueryInformationEx
//
// Parameters:
// TDIObjectID *ID The TDI Object ID to query
// void *Buffer buffer to contain the query results
// LPDWORD *BufferSize pointer to the size of the buffer
// filled in with the amount of data.
// UCHAR *Context context value for the query. should
// be zeroed for a new query. It will be
// filled with context information for
// linked enumeration queries.
//
// Returns:
// An NTSTATUS value.
//
// This routine provides the interface to the TDI QueryInformationEx
// facility of the TCP/IP stack on NT.
//---------------------------------------------------------------------
DWORD TCPQueryInformationEx(LPVOID lpvInBuffer, LPDWORD lpdwInSize,
LPVOID lpvOutBuffer, LPDWORD lpdwOutSize) {
NTSTATUS status;
IO_STATUS_BLOCK isbStatusBlock;
if (g_ripcfg.hTCPDriver == NULL) {
OpenTcp();
}
status = NtDeviceIoControlFile(g_ripcfg.hTCPDriver, // Driver handle
NULL, // Event
NULL, // APC Routine
NULL, // APC context
&isbStatusBlock, // Status block
IOCTL_TCP_QUERY_INFORMATION_EX, // Control
lpvInBuffer, // Input buffer
*lpdwInSize, // Input buffer size
lpvOutBuffer, // Output buffer
*lpdwOutSize); // Output buffer size
if (status == STATUS_PENDING) {
status = NtWaitForSingleObject(g_ripcfg.hTCPDriver, TRUE, NULL);
status = isbStatusBlock.Status;
}
if (status != STATUS_SUCCESS) {
*lpdwOutSize = 0;
}
else {
*lpdwOutSize = (ULONG)isbStatusBlock.Information;
}
return status;
}
//---------------------------------------------------------------------------
// Function: TCPSetInformationEx
//
// Parameters:
//
// TDIObjectID *ID the TDI Object ID to set
// void *lpvBuffer data buffer containing the information
// to be set
// DWORD dwBufferSize the size of the data buffer.
//
// This routine provides the interface to the TDI SetInformationEx
// facility of the TCP/IP stack on NT.
//---------------------------------------------------------------------------
DWORD TCPSetInformationEx(LPVOID lpvInBuffer, LPDWORD lpdwInSize,
LPVOID lpvOutBuffer, LPDWORD lpdwOutSize) {
NTSTATUS status;
IO_STATUS_BLOCK isbStatusBlock;
if (g_ripcfg.hTCPDriver == NULL) {
OpenTcp();
}
status = NtDeviceIoControlFile(g_ripcfg.hTCPDriver, // Driver handle
NULL, // Event
NULL, // APC Routine
NULL, // APC context
&isbStatusBlock, // Status block
IOCTL_TCP_SET_INFORMATION_EX, // Control
lpvInBuffer, // Input buffer
*lpdwInSize, // Input buffer size
lpvOutBuffer, // Output buffer
*lpdwOutSize); // Output buffer size
if (status == STATUS_PENDING) {
status = NtWaitForSingleObject(g_ripcfg.hTCPDriver, TRUE, NULL);
status = isbStatusBlock.Status;
}
if (status != STATUS_SUCCESS) {
*lpdwOutSize = 0;
}
else {
*lpdwOutSize = (ULONG)isbStatusBlock.Information;
}
return status;
}
#endif