Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1008 lines
26 KiB

//+-----------------------------------------------------------------------
//
// Microsoft Windows
//
// Copyright (c) Microsoft Corporation 1992 - 1995
//
// File: dgutil.cxx
//
// Contents: Server support routines for datagram sockets
//
//
// History: 10-July-1996 MikeSw Created
//
//------------------------------------------------------------------------
#include "kdcsvr.hxx"
#include "sockutil.h"
extern "C"
{
#include <atq.h>
#include <nlrepl.h>
}
#include <issched.hxx>
#include "fileno.h"
#define FILENO FILENO_DGUTIL
#define KDC_KEY "System\\CurrentControlSet\\Services\\kdc"
#define KDC_PARAMETERS_KEY KDC_KEY "\\parameters"
#define KDC_MAX_ACCEPT_BUFFER 5000
#define KDC_MAX_ACCEPT_OUTSTANDING 5
#define KDC_ACCEPT_TIMEOUT 100
#define KDC_LISTEN_BACKLOG 10
#define KDC_CONTEXT_TIMEOUT 50
extern BOOLEAN KdcSocketsInitialized;
typedef struct _KDC_DATAGRAM_ENDPOINT {
SOCKADDR LocalAddress;
PKDC_GET_TICKET_ROUTINE EndpointFunction;
PVOID Endpoint;
} KDC_DATAGRAM_ENDPOINT, *PKDC_DATAGRAM_ENDPOINT;
PKDC_DATAGRAM_ENDPOINT DatagramEndpoints = NULL;
ULONG DatagramEndpointCount = 0;
RTL_RESOURCE DatagramEndpointLock;
BOOL DatagramEndpointLockInitialized = FALSE;
SOCKET KdcWinsockPnpSocket = INVALID_SOCKET;
HANDLE KdcWinsockPnpEvent = NULL;
HANDLE KdcPnpEventChangeHandle = NULL;
//+-------------------------------------------------------------------------
//
// Function: KdcAtqDgIoCompletion
//
// Synopsis:
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
VOID
KdcAtqDgIoCompletion(
IN PVOID Context,
IN DWORD BytesWritten,
IN DWORD CompletionStatus,
IN OVERLAPPED * lpo
)
{
PVOID Buffer;
SOCKADDR * RemoteAddress = NULL;
INT AddressSize;
PATQ_CONTEXT AtqContext = (PATQ_CONTEXT) Context;
SOCKET NewSocket = INVALID_SOCKET;
KERB_MESSAGE_BUFFER InputMessage;
KERB_MESSAGE_BUFFER OutputMessage;
ULONG_PTR KdcContext;
WSABUF SocketBuffer;
PKDC_DATAGRAM_ENDPOINT Endpoint;
TRACE(KDC,KdcAtqDgIoCompletion, DEB_FUNCTION);
if (Context == NULL)
{
return;
}
KdcContext = AtqContextGetInfo(
AtqContext,
ATQ_INFO_COMPLETION_CONTEXT
);
//
// If the context is 1, then this is the completion from a write, so close
// this down.
//
if (KdcContext == 1)
{
lpo = NULL;
}
//
// If a client connects and then disconnects gracefully ,we will get a
// completion with zero bytes and success status.
//
if ((BytesWritten == 0) && (CompletionStatus == NO_ERROR))
{
CompletionStatus = WSAECONNABORTED;
}
if ((CompletionStatus != NO_ERROR) || (lpo == NULL) || !KdcSocketsInitialized)
{
D_DebugLog((DEB_T_SOCK,"IoCompletion: CompletionStatus = 0x%x\n",CompletionStatus));
D_DebugLog((DEB_T_SOCK,"IoCompletion: lpo = %p\n",lpo));
D_DebugLog((DEB_T_SOCK, "Freeing context %p\n",AtqContext));
if (CompletionStatus == ERROR_OPERATION_ABORTED)
{
AtqCloseSocket( AtqContext, TRUE );
AtqFreeContext( (PATQ_CONTEXT) AtqContext, FALSE );
return;
}
else if ( CompletionStatus != ERROR_MORE_DATA )
{
AtqFreeContext( (PATQ_CONTEXT) AtqContext, TRUE );
return;
}
}
//
// Obtain shared access to the global endpoint list
//
RtlAcquireResourceShared( &DatagramEndpointLock , TRUE );
//
// Can not proceed unless there are endpoints registered
//
if ( DatagramEndpointCount == 0 )
{
AtqFreeContext( (PATQ_CONTEXT) AtqContext, TRUE );
goto Cleanup;
}
AtqGetDatagramAddrs(
AtqContext,
&NewSocket,
&Buffer,
(PVOID *) &Endpoint,
&RemoteAddress,
&AddressSize
);
//
// If the remote address is port 88, don't respond, as we don't
// want to be vulnerable to a loopback attack.
//
if ((AddressSize >= sizeof(SOCKADDR_IN) &&
((((SOCKADDR_IN *) RemoteAddress)->sin_port == KERB_KDC_PORT) ||
(((SOCKADDR_IN *) RemoteAddress)->sin_port == KERB_KPASSWD_PORT))))
{
//
// Just free up the context so it can be reused.
//
AtqFreeContext( (PATQ_CONTEXT) AtqContext, TRUE );
goto Cleanup;
}
//fester
D_DebugLog((DEB_T_SOCK, "Bytes written - %x\n", BytesWritten));
//
// There is a buffer, so use it to do the KDC thang.
//
InputMessage.BufferSize = BytesWritten;
InputMessage.Buffer = (PUCHAR) Buffer;
OutputMessage.Buffer = NULL;
if ( CompletionStatus != ERROR_MORE_DATA )
{
Endpoint->EndpointFunction(
NULL, // no atq context for retries
RemoteAddress,
&Endpoint->LocalAddress,
&InputMessage,
&OutputMessage
);
}
else
{
//
// For now, if client sends us more data over UDP than we can swallow
// (indicated by a completion status of ERROR_MORE_DATA), just send
// a response of "response too big" back to them, which will cause
// the Windows client (and hopefully a future MIT client) to switch
// over to TCP
//
// In a future release, ATQ should be less timid and give us all the
// UDP data that was received, in which case this logic could be pulled
//
KERB_EXT_ERROR ExtendedError = {0,0};
KerbBuildErrorMessageEx(
KRB_ERR_RESPONSE_TOO_BIG,
&ExtendedError,
SecData.KdcDnsRealmName(),
SecData.KdcInternalName(),
NULL,
NULL,
0,
&OutputMessage.BufferSize,
&OutputMessage.Buffer
);
}
//
// If there is a response, write it back to the sender.
//
if (OutputMessage.Buffer != NULL)
{
DsysAssert(OutputMessage.BufferSize < max( KdcGlobalMaxDatagramReplySize, KDC_MAX_ACCEPT_BUFFER));
RtlCopyMemory(
Buffer,
OutputMessage.Buffer,
OutputMessage.BufferSize
);
KdcFreeEncodedData(OutputMessage.Buffer);
SocketBuffer.buf = (char *) Buffer;
SocketBuffer.len = OutputMessage.BufferSize;
AtqContextSetInfo(
AtqContext,
ATQ_INFO_COMPLETION_CONTEXT,
1
);
if (!AtqWriteDatagramSocket(
(PATQ_CONTEXT) AtqContext,
&SocketBuffer,
1, // 1 buffer
NULL // no OVERLAPPED
))
{
DebugLog((DEB_ERROR,"Datagram write failed for %d bytes: 0x%x\n",OutputMessage.BufferSize,GetLastError()));
AtqCloseSocket( AtqContext, TRUE );
AtqFreeContext( (PATQ_CONTEXT) AtqContext, TRUE );
}
}
else
{
//
// Just free up the context so it can be reused.
//
AtqFreeContext( (PATQ_CONTEXT) AtqContext, TRUE );
}
Cleanup:
RtlReleaseResource( &DatagramEndpointLock );
}
//+-------------------------------------------------------------------------
//
// Function: KdcCreateDgAtqEndpoint
//
// Synopsis: Sets up a datagram endpoint
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KdcCreateDgAtqEndpoint(
IN USHORT Port,
IN PVOID EndpointContext,
IN ULONG IpAddress,
OUT PVOID * Endpoint
)
{
NTSTATUS Status = STATUS_SUCCESS;
ATQ_ENDPOINT_CONFIGURATION EndpointConfig;
SOCKET EndpointSocket = INVALID_SOCKET;
int RecvBufSize;
//
// Create the endpoint config
//
EndpointConfig.ListenPort = Port;
EndpointConfig.IpAddress = IpAddress;
EndpointConfig.cbAcceptExRecvBuffer = max( KdcGlobalMaxDatagramReplySize, KDC_MAX_ACCEPT_BUFFER);
EndpointConfig.nAcceptExOutstanding = KDC_MAX_ACCEPT_OUTSTANDING;
EndpointConfig.AcceptExTimeout = KDC_ACCEPT_TIMEOUT;
EndpointConfig.pfnConnect = NULL;
EndpointConfig.pfnConnectEx = KdcAtqDgIoCompletion;
EndpointConfig.pfnIoCompletion = KdcAtqDgIoCompletion;
EndpointConfig.fDatagram = TRUE;
EndpointConfig.fLockDownPort = TRUE;
EndpointConfig.fReverseQueuing = FALSE;
EndpointConfig.cbDatagramWSBufSize = 0; // means use the default.
*Endpoint = AtqCreateEndpoint(
&EndpointConfig,
EndpointContext
);
if (*Endpoint == NULL)
{
DebugLog((DEB_ERROR,"Failed to create ATQ endpoint\n"));
Status = STATUS_UNSUCCESSFUL;
goto Cleanup;
}
//
// Get the socket so we can change the recieve buffer size
//
EndpointSocket = (SOCKET) AtqEndpointGetInfo(
*Endpoint,
EndpointInfoListenSocket
);
RecvBufSize = 0x8000; // 32 k buffers
if (setsockopt(
EndpointSocket,
SOL_SOCKET,
SO_RCVBUF,
(const char *) &RecvBufSize,
sizeof(int)
))
{
DebugLog((DEB_ERROR,"Failed to set recv buf size to 32k: 0x%x, %d\n",
WSAGetLastError(),WSAGetLastError()));
}
//
// Start the endpoint
//
if (!AtqStartEndpoint(*Endpoint))
{
DebugLog((DEB_ERROR, "Failed to add ATQ endpoint\n"));
Status = STATUS_UNSUCCESSFUL;
goto Cleanup;
}
Cleanup:
return(Status);
}
//+-------------------------------------------------------------------------
//
// Function: KdcGetAddressListFromWinsock
//
// Synopsis: gets the list of addresses from a winsock ioctl
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KdcGetAddressListFromWinsock(
OUT LPSOCKET_ADDRESS_LIST * SocketAddressList
)
{
ULONG BytesReturned = 150;
LPSOCKET_ADDRESS_LIST AddressList = NULL;
INT i,j;
ULONG NetStatus = STATUS_SUCCESS;
for (;;) {
//
// Allocate a buffer that should be big enough.
//
if ( AddressList != NULL ) {
MIDL_user_free( AddressList );
}
AddressList = (LPSOCKET_ADDRESS_LIST) MIDL_user_allocate( BytesReturned );
if ( AddressList == NULL ) {
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
//
// Get the list of IP addresses
//
NetStatus = WSAIoctl( KdcWinsockPnpSocket,
SIO_ADDRESS_LIST_QUERY,
NULL, // No input buffer
0, // No input buffer
(PVOID) AddressList,
BytesReturned,
&BytesReturned,
NULL, // No overlapped,
NULL ); // Not async
if ( NetStatus != 0 ) {
NetStatus = WSAGetLastError();
//
// If the buffer isn't big enough, try again.
//
if ( NetStatus == WSAEFAULT ) {
continue;
}
DebugLog((DEB_ERROR,"LdapUdpPnpBind: Cannot WSAIoctl SIO_ADDRESS_LIST_QUERY %ld %ld\n",
NetStatus, BytesReturned));
goto Cleanup;
}
break;
}
//
// Weed out any zero IP addresses and other invalid addresses
//
for ( i = 0, j = 0; i < AddressList->iAddressCount; i++ ) {
PSOCKET_ADDRESS SocketAddress;
//
// Copy this address to the front of the list.
//
AddressList->Address[j] = AddressList->Address[i];
//
// If the address isn't valid,
// skip it.
//
SocketAddress = &AddressList->Address[j];
if ( SocketAddress->iSockaddrLength == 0 ||
SocketAddress->lpSockaddr == NULL ||
SocketAddress->lpSockaddr->sa_family != AF_INET ||
((PSOCKADDR_IN)(SocketAddress->lpSockaddr))->sin_addr.s_addr == 0 ) {
} else {
//
// Otherwise keep it.
//
j++;
}
}
AddressList->iAddressCount = j;
*SocketAddressList = AddressList;
AddressList = NULL;
Cleanup:
if (AddressList != NULL)
{
MIDL_user_free(AddressList);
}
if (NetStatus != ERROR_SUCCESS)
{
return(STATUS_UNSUCCESSFUL);
}
else
{
return(STATUS_SUCCESS);
}
}
//+-------------------------------------------------------------------------
//
// Function: KdcUpdateAddressesWorker
//
// Synopsis: Updates the IP addresses used for datagram sockest by
// stopping the endpoints and then starting them with the
// new addresses
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
ULONG
KdcUpdateAddressesWorker(
IN PVOID IgnoredParameter
)
{
ULONG Index;
INT IntIndex;
NTSTATUS Status = STATUS_SUCCESS;
LPSOCKET_ADDRESS_LIST AddressList = NULL;
DWORD NetStatus;
DWORD BytesReturned ;
BOOL DatagramEndpointsLocked = FALSE;
D_DebugLog(( DEB_TRACE, "KdcUpdateAddressesWorker\n" ));
//
// Tell winsock we want address list changes on this socket:
//
if ( KdcWinsockPnpSocket != INVALID_SOCKET )
{
NetStatus = WSAIoctl(
KdcWinsockPnpSocket,
SIO_ADDRESS_LIST_CHANGE,
NULL,
0,
NULL,
0,
&BytesReturned,
NULL,
NULL );
if ( NetStatus != 0 )
{
NetStatus = WSAGetLastError();
if ( NetStatus != WSAEWOULDBLOCK )
{
DebugLog((DEB_ERROR,"WSASocket failed with %ld\n", NetStatus ));
Status = STATUS_UNSUCCESSFUL;
goto Cleanup;
}
}
}
//
// Get the list of socket addresses
//
Status = KdcGetAddressListFromWinsock(
&AddressList
);
if ( !NT_SUCCESS( Status ))
{
goto Cleanup;
}
//
// Cleanup any old endpoints
//
if (DatagramEndpoints != NULL)
{
for (Index = 0; Index < DatagramEndpointCount ; Index++ )
{
if (DatagramEndpoints[Index].Endpoint != NULL)
{
//
// Stopping the endpoint prevents future completions
//
while ( FALSE == AtqStopEndpoint( DatagramEndpoints[Index].Endpoint ))
{
Sleep( 5000 );
}
//
// The endpoint won't close until all outstanding operations on it
// have completed
//
while ( FALSE == AtqCloseEndpoint( DatagramEndpoints[Index].Endpoint ))
{
Sleep( 5000 );
}
}
}
}
//
// Must acquire the lock now since we're manipulating global variables
// that this lock protects.
//
RtlAcquireResourceExclusive( &DatagramEndpointLock, TRUE );
DatagramEndpointsLocked = TRUE;
MIDL_user_free(DatagramEndpoints);
DatagramEndpoints = NULL;
DatagramEndpointCount = 0;
//
// Create new endpoints
//
DatagramEndpoints = (PKDC_DATAGRAM_ENDPOINT) MIDL_user_allocate(
sizeof(KDC_DATAGRAM_ENDPOINT) * AddressList->iAddressCount * 2
);
if (DatagramEndpoints == NULL)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
RtlZeroMemory(
DatagramEndpoints,
sizeof(KDC_DATAGRAM_ENDPOINT) * AddressList->iAddressCount * 2
);
//
// Create an endpoint for the KDC and for KPASSWD for each transport
//
for (IntIndex = 0; IntIndex < AddressList->iAddressCount ; IntIndex++ )
{
RtlCopyMemory(
&DatagramEndpoints[DatagramEndpointCount].LocalAddress,
AddressList->Address[IntIndex].lpSockaddr,
sizeof(SOCKADDR_IN)
);
DatagramEndpoints[DatagramEndpointCount].EndpointFunction = KdcGetTicket;
Status = KdcCreateDgAtqEndpoint(
KERB_KDC_PORT,
&DatagramEndpoints[DatagramEndpointCount],
((PSOCKADDR_IN) &DatagramEndpoints[DatagramEndpointCount].LocalAddress)->sin_addr.s_addr,
&DatagramEndpoints[DatagramEndpointCount].Endpoint
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
DatagramEndpointCount++;
//
// Create the KPASSWD endpoint
//
RtlCopyMemory(
&DatagramEndpoints[DatagramEndpointCount].LocalAddress,
AddressList->Address[IntIndex].lpSockaddr,
sizeof(SOCKADDR_IN)
);
DatagramEndpoints[DatagramEndpointCount].EndpointFunction = KdcChangePassword;
Status = KdcCreateDgAtqEndpoint(
KERB_KPASSWD_PORT,
&DatagramEndpoints[DatagramEndpointCount],
((PSOCKADDR_IN) &DatagramEndpoints[DatagramEndpointCount].LocalAddress)->sin_addr.s_addr,
&DatagramEndpoints[DatagramEndpointCount].Endpoint
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
DatagramEndpointCount++;
}
Cleanup:
if (!NT_SUCCESS(Status))
{
if (DatagramEndpoints != NULL)
{
//
// Allow completion routines to fire while the endpoints are being closed
// by releasing the lock for the duration of the 'stop' operation
//
if ( DatagramEndpointsLocked )
{
RtlReleaseResource( &DatagramEndpointLock );
DatagramEndpointsLocked = FALSE;
}
for (Index = 0; Index < DatagramEndpointCount ; Index++ )
{
if (DatagramEndpoints[Index].Endpoint != NULL)
{
while ( FALSE == AtqStopEndpoint( DatagramEndpoints[Index].Endpoint ))
{
Sleep( 5000 );
}
while ( FALSE == AtqCloseEndpoint( DatagramEndpoints[Index].Endpoint ))
{
Sleep( 5000 );
}
}
}
//
// Must re-acquire the lock now since we're manipulating global variables
// that this lock protects.
//
RtlAcquireResourceExclusive( &DatagramEndpointLock, TRUE );
DatagramEndpointsLocked = TRUE;
MIDL_user_free(DatagramEndpoints);
DatagramEndpoints = NULL;
DatagramEndpointCount = 0;
}
}
if ( DatagramEndpointsLocked )
{
RtlReleaseResource( &DatagramEndpointLock );
}
MIDL_user_free(AddressList);
return((ULONG) Status);
}
//+-------------------------------------------------------------------------
//
// Function: KdcInitializeSockets
//
// Synopsis: Initializes the KDCs socket handling code
//
// Effects:
//
// Arguments: none
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KdcInitializeDatagramSockets(
VOID
)
{
NTSTATUS Status = STATUS_SUCCESS;
DWORD NetStatus;
__try
{
RtlInitializeResource(&DatagramEndpointLock);
}
__except( EXCEPTION_EXECUTE_HANDLER )
{
Status = GetExceptionCode();
}
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
else
{
DatagramEndpointLockInitialized = TRUE;
}
//
// Initialize the asynchronous thread queue.
//
if (!AtqInitialize(0))
{
DebugLog((DEB_ERROR,"Failed to initialize ATQ\n"));
Status = STATUS_UNSUCCESSFUL;
goto Cleanup;
}
//
// Open a socket to get winsock PNP notifications on.
//
KdcWinsockPnpSocket = WSASocket( AF_INET,
SOCK_DGRAM,
0, // PF_INET,
NULL,
0,
0 );
if ( KdcWinsockPnpSocket == INVALID_SOCKET ) {
NetStatus = WSAGetLastError();
DebugLog((DEB_ERROR,"WSASocket failed with %ld\n", NetStatus ));
Status = STATUS_UNSUCCESSFUL;
goto Cleanup;
}
//
// Open an event to wait on.
//
KdcWinsockPnpEvent = CreateEvent(
NULL, // No security ettibutes
FALSE, // Auto reset
FALSE, // Initially not signaled
NULL); // No Name
if ( KdcWinsockPnpEvent == NULL ) {
NetStatus = GetLastError();
DebugLog((DEB_ERROR,"Cannot create Winsock PNP event %ld\n", NetStatus ));
Status = STATUS_UNSUCCESSFUL;
goto Cleanup;
}
//
// Associate the event with new addresses becoming available on the socket.
//
NetStatus = WSAEventSelect( KdcWinsockPnpSocket, KdcWinsockPnpEvent, FD_ADDRESS_LIST_CHANGE );
if ( NetStatus != 0 ) {
NetStatus = WSAGetLastError();
DebugLog((DEB_ERROR,"Can't WSAEventSelect %ld\n", NetStatus ));
Status = STATUS_UNSUCCESSFUL;
goto Cleanup;
}
Status = (NTSTATUS) KdcUpdateAddressesWorker( NULL );
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR,"Failed to udpate datagram addresses\n"));
goto Cleanup;
}
D_DebugLog((DEB_TRACE, "Successfully started ATQ listening\n"));
if ( KdcPnpEventChangeHandle == NULL ) {
KdcPnpEventChangeHandle = LsaIRegisterNotification(
KdcUpdateAddressesWorker,
NULL, // no parameter,
NOTIFIER_TYPE_HANDLE_WAIT,
0, // no class
0, // no flags
0, // no interval
KdcWinsockPnpEvent
);
if (KdcPnpEventChangeHandle == NULL)
{
DebugLog((DEB_ERROR,"Failed to register KDC pnp event change handle.\n"));
}
}
Cleanup:
if (!NT_SUCCESS(Status))
{
KdcShutdownSockets();
}
return(Status);
}
//+-------------------------------------------------------------------------
//
// Function: KdcShutdownDatagramSockets
//
// Synopsis: Shuts down the KDC socket handling code
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KdcShutdownDatagramSockets(
VOID
)
{
ULONG Index;
TRACE(KDC,KdcShutdownSockets, DEB_FUNCTION);
if ( KdcPnpEventChangeHandle != NULL )
{
LsaICancelNotification(KdcPnpEventChangeHandle);
KdcPnpEventChangeHandle = NULL;
}
if ( KdcWinsockPnpEvent != NULL )
{
CloseHandle(KdcWinsockPnpEvent);
KdcWinsockPnpEvent = NULL;
}
if ( KdcWinsockPnpSocket != INVALID_SOCKET ) {
closesocket(KdcWinsockPnpSocket);
KdcWinsockPnpSocket = INVALID_SOCKET;
}
//
// Go through the list of contexts and close them all.
//
if (DatagramEndpoints != NULL)
{
for (Index = 0; Index < DatagramEndpointCount ; Index++ )
{
if (DatagramEndpoints[Index].Endpoint != NULL)
{
while ( FALSE == AtqStopEndpoint( DatagramEndpoints[Index].Endpoint ))
{
Sleep( 5000 );
}
while ( FALSE == AtqCloseEndpoint( DatagramEndpoints[Index].Endpoint ))
{
Sleep( 5000 );
}
}
}
RtlAcquireResourceExclusive( &DatagramEndpointLock, TRUE );
MIDL_user_free(DatagramEndpoints);
DatagramEndpoints = NULL;
DatagramEndpointCount = 0;
RtlReleaseResource( &DatagramEndpointLock );
}
if (!AtqTerminate())
{
DebugLog((DEB_ERROR, "Failed to terminate ATQ!!!\n"));
}
if ( DatagramEndpointLockInitialized )
{
RtlDeleteResource(&DatagramEndpointLock);
DatagramEndpointLockInitialized = FALSE;
}
return(STATUS_SUCCESS);
}