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.
657 lines
21 KiB
657 lines
21 KiB
/*--
|
|
|
|
|
|
Copyright (c) 1996-1997 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
nlldap.cxx
|
|
|
|
Abstract:
|
|
|
|
Routines the use the DS's ldap filter.
|
|
|
|
This is actually in a .cxx file by itself to work around a compiler bug
|
|
where .c files cannot include ldap.h
|
|
|
|
Author:
|
|
|
|
|
|
Environment:
|
|
|
|
User mode only.
|
|
Contains NT-specific code.
|
|
Requires ANSI C extensions: slash-slash comments, long external names.
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif /* __cplusplus */
|
|
#include <nt.h> // LARGE_INTEGER definition
|
|
#include <ntrtl.h> // LARGE_INTEGER definition
|
|
#include <nturtl.h> // LARGE_INTEGER definition
|
|
#include <ntlsa.h> // Needed by lsrvdata.h
|
|
#include <ntddbrow.h> // Needed by nlcommon.h
|
|
|
|
#define NOMINMAX // Avoid redefinition of min and max in stdlib.h
|
|
#include <rpc.h> // Needed by logon_s.h
|
|
#include <logon_s.h> // includes lmcons.h, lmaccess.h, netlogon.h, ssi.h, windef.h
|
|
//
|
|
#include <windows.h>
|
|
#include <winsock2.h> // Winsock support
|
|
#include <lmapibuf.h> // NetApiBufferFree
|
|
#include <logonp.h> // NetpLogon routines
|
|
#include <lsarpc.h> // Needed by lsrvdata.h and logonsrv.h
|
|
#include <lsaisrv.h> // Needed by changelg.h
|
|
#include <wincrypt.h> // CryptoAPI, needed by lsrvdata.h
|
|
#ifdef __cplusplus
|
|
} /* extern "C" */
|
|
#endif /* __cplusplus */
|
|
#include <netlib.h> // Needed by nlcommon.h
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif /* __cplusplus */
|
|
#include <samrpc.h> // Needed by lsrvdata.h and logonsrv.h
|
|
#include <ntdsa.h> // THSave/THRestore
|
|
#include <wmistr.h> // Needed by lsrvdata.h
|
|
#include <evntrace.h> // Needed by lsrvdata.h
|
|
#include <sspi.h> // Needed by ssiinit.h
|
|
|
|
//
|
|
// Netlogon specific header files.
|
|
//
|
|
|
|
#define _AVOID_REPL_API 1
|
|
#include <nlrepl.h> // I_Net*
|
|
#include "worker.h" // Worker routines
|
|
#include "nlcommon.h" // Routines shared with logonsrv\common
|
|
#include "domain.h" // Hosted domain definitions
|
|
#include "changelg.h" // Change Log support
|
|
#include "chutil.h" // Change Log utilities
|
|
#include "iniparm.h" // registry parameters
|
|
#include "windns.h" // needed by ssiinit.h
|
|
#include "ssiinit.h" // Misc global definitions
|
|
#include "nldebug.h" // Netlogon debugging
|
|
#include "lsrvdata.h" // Globals
|
|
|
|
// #include <string.h> // strnicmp ...
|
|
#include <ldap.h> // Filter for LDAP query
|
|
|
|
#define NL_MAXIMUM_USER_NAME_LENGTH 20 // The max SAM allows
|
|
|
|
BOOLEAN
|
|
IsFilterName(
|
|
IN Filter *CurrentFilter,
|
|
IN LPSTR NameToMatch
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
If the CurrentFilter is for NameToMatch, return TRUE
|
|
|
|
Arguments:
|
|
|
|
Filter - Pointer to an equalityMatch Filter element.
|
|
|
|
NameToMatch - Specifies the name to compare with the filter element.
|
|
|
|
Return Value:
|
|
|
|
TRUE name matches.
|
|
|
|
--*/
|
|
{
|
|
ULONG NameLength;
|
|
|
|
NameLength = strlen( NameToMatch );
|
|
if ( NameLength != CurrentFilter->u.equalityMatch.attributeDesc.length ) {
|
|
return FALSE;
|
|
}
|
|
if ( _strnicmp( NameToMatch,
|
|
(LPSTR)CurrentFilter->u.equalityMatch.attributeDesc.value,
|
|
NameLength ) != 0 ) {
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
NET_API_STATUS
|
|
FilterString(
|
|
IN Filter *CurrentFilter,
|
|
IN ULONG UnicodeStringBufferSize,
|
|
OUT LPWSTR UnicodeStringBuffer OPTIONAL,
|
|
OUT LPWSTR *UnicodeString
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Return a zero terminated unicode string containing the value of the
|
|
current equalityMatch filter element.
|
|
|
|
The value of the element is expected to be in UTF-8.
|
|
|
|
Arguments:
|
|
|
|
Filter - Pointer to an equalityMatch Filter element.
|
|
|
|
UnicodeStringBuffer -- Buffer to copy the covnverted string to.
|
|
If NULL, the function will allocate the needed memory and
|
|
return it in UnicodeString.
|
|
|
|
UnicodeStringSize - Size in wide charactes of UnicodeStringBuffer.
|
|
If this size is less than what's needed to store the resulting
|
|
NULL terminated unicode string, the function will allocate the
|
|
needed memory and return it in UnicodeString.
|
|
|
|
UnicodeString - Returns a pointer to the resulting string.
|
|
If the passed in buffer for the resulting unicode string isn't
|
|
large enough, the function will allocate the needed memory and
|
|
the pointer to the allocated memory will be returned in this
|
|
parameter. If NULL and the passed in buffer isn't large enough
|
|
to store the resulting NULL terminated string, the function
|
|
returns ERROR_INSUFFICIENT_BUFFER. On successful return, the
|
|
caller should check whether UnicodeString != UnicodeStringBuffer
|
|
and, if so, free the allocated buffer using NetApiBufferFree.
|
|
|
|
Return Value:
|
|
|
|
Error returned by NetpAllocWStrFromUtf8StrAsRequired.
|
|
|
|
--*/
|
|
{
|
|
NET_API_STATUS NetStatus;
|
|
LPWSTR AllocatedUnicodeString = NULL;
|
|
|
|
//
|
|
// Call the worker routine
|
|
//
|
|
|
|
NetStatus = NetpAllocWStrFromUtf8StrAsRequired(
|
|
(LPSTR)CurrentFilter->u.equalityMatch.assertionValue.value,
|
|
CurrentFilter->u.equalityMatch.assertionValue.length,
|
|
UnicodeStringBufferSize,
|
|
UnicodeStringBuffer,
|
|
&AllocatedUnicodeString );
|
|
|
|
//
|
|
// Set the return pointer to point to the appropriate buffer
|
|
//
|
|
|
|
if ( NetStatus == NO_ERROR ) {
|
|
if ( AllocatedUnicodeString != NULL ) {
|
|
NlAssert( AllocatedUnicodeString != UnicodeStringBuffer );
|
|
*UnicodeString = AllocatedUnicodeString;
|
|
} else {
|
|
*UnicodeString = UnicodeStringBuffer;
|
|
}
|
|
}
|
|
|
|
return NetStatus;
|
|
}
|
|
|
|
NET_API_STATUS
|
|
FilterBinary(
|
|
IN Filter *CurrentFilter,
|
|
IN ULONG BufferSize,
|
|
OUT PVOID Buffer,
|
|
OUT PULONG DataSize
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Returns a properly aligned pointer to the binary data.
|
|
|
|
Arguments:
|
|
|
|
Filter - Pointer to an equalityMatch Filter element.
|
|
|
|
Buffer - Buffer to copy the data into.
|
|
|
|
BufferSize - The size of the passed buffer. If the
|
|
buffer isn't large enough to store the data,
|
|
ERROR_INSUFFICIENT_BUFFER is returned.
|
|
|
|
DataSize - Size of the data copied
|
|
|
|
Return Value:
|
|
|
|
NO_ERROR - The data has been successfully coppied.
|
|
|
|
ERROR_INSUFFICIENT_BUFFER - The passed in buffer is too small.
|
|
|
|
--*/
|
|
{
|
|
if ( BufferSize < CurrentFilter->u.equalityMatch.assertionValue.length ) {
|
|
return ERROR_INSUFFICIENT_BUFFER;
|
|
}
|
|
|
|
RtlCopyMemory( Buffer,
|
|
CurrentFilter->u.equalityMatch.assertionValue.value,
|
|
CurrentFilter->u.equalityMatch.assertionValue.length );
|
|
|
|
*DataSize = CurrentFilter->u.equalityMatch.assertionValue.length;
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
I_NetLogonLdapLookupEx(
|
|
IN PVOID FilterParam,
|
|
IN PVOID ClientSockAddr,
|
|
OUT PVOID *Response,
|
|
OUT PULONG ResponseSize
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine builds a response to an LDAP ping of a DC. DsGetDcName does
|
|
such a ping to ensure the DC is functional and still meets the requirements
|
|
of the DsGetDcName. DsGetDcName does an LDAP lookup of the NULL DN asking
|
|
for attribute "Netlogon". The DS turns that into a call to this routine
|
|
passing in the filter parameter.
|
|
|
|
The DS is expected to save the DS thread state before calling. This routine
|
|
calls SAM which expects to have its own DS thread state.
|
|
|
|
Arguments:
|
|
|
|
Filter - Filter describing the query. The filter is built by the DsGetDcName
|
|
client, so we can limit the flexibility significantly.
|
|
|
|
SockAddr - Socket Address of the client this request came in on.
|
|
|
|
Response - Returns a pointer to an allocated buffer containing
|
|
the response to return to the caller. This response is a binary blob
|
|
which should be returned to the caller bit-for-bit intact.
|
|
The buffer should be freed be calling I_NetLogonFree.
|
|
|
|
ResponseSize - Size (in bytes) of the returned message.
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS -- The response was returned in the supplied buffer.
|
|
|
|
STATUS_INVALID_PARAMETER -- The filter was invalid.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
NET_API_STATUS NetStatus = NO_ERROR;
|
|
|
|
Filter *LdapFilter = (Filter *) FilterParam;
|
|
Filter *CurrentFilter;
|
|
struct _setof3_ *CurrentAnd;
|
|
|
|
LPSTR DnsDomainName = NULL;
|
|
GUID DomainGuid;
|
|
GUID *DomainGuidPtr = NULL;
|
|
// The domain SID buffer must be DWORD alligned. Avoid any buffer overflow problem
|
|
// due to truncation in case SECURITY_MAX_SID_SIZE isn't evenly divisible by 4
|
|
ULONG DomainSid[SECURITY_MAX_SID_SIZE/sizeof(ULONG)+1];
|
|
PSID DomainSidPtr = NULL;
|
|
WCHAR UnicodeComputerNameBuffer[MAX_COMPUTERNAME_LENGTH+1];
|
|
LPWSTR UnicodeComputerName = NULL;
|
|
WCHAR UnicodeUserNameBuffer[NL_MAXIMUM_USER_NAME_LENGTH+1];
|
|
LPWSTR UnicodeUserName = NULL;
|
|
ULONG AllowableAccountControlBits = 0;
|
|
DWORD NtVersion = LMNT_MESSAGE;
|
|
DWORD NtVersionFlags = NETLOGON_NT_VERSION_5;
|
|
ULONG Length = 0;
|
|
SOCKADDR_IN SockAddrIn;
|
|
|
|
//
|
|
// Initialization.
|
|
//
|
|
|
|
*Response = NULL;
|
|
*ResponseSize = 0;
|
|
|
|
//
|
|
// If caller is calling when the netlogon service isn't running,
|
|
// tell it so.
|
|
//
|
|
|
|
if ( !NlStartNetlogonCall() ) {
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Validate the client socket address.
|
|
//
|
|
|
|
if ( ClientSockAddr != NULL ) {
|
|
if ( ((PSOCKADDR)ClientSockAddr)->sa_family != AF_INET ) {
|
|
NlPrint((NL_CRITICAL,
|
|
"I_NetlogonLdapLookupEx: DS passed us a SOCKADDR that wasn't AF_INET (ignoring it)\n" ));
|
|
ClientSockAddr = NULL;
|
|
} else {
|
|
//
|
|
// Force the port number to zero to avoid confusion later.
|
|
SockAddrIn = *((PSOCKADDR_IN)ClientSockAddr);
|
|
SockAddrIn.sin_port = 0;
|
|
ClientSockAddr = &SockAddrIn;
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Parse the filter.
|
|
//
|
|
|
|
if ( LdapFilter != NULL ) {
|
|
|
|
//
|
|
// The first element of the filter must be an AND.
|
|
//
|
|
|
|
if ( LdapFilter->choice != and_chosen ) {
|
|
NlPrint((NL_CRITICAL,
|
|
"I_NetlogonLdapLookup: Filter doesn't have AND %ld\n",
|
|
LdapFilter->choice ));
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Loop through the AND'ed attributes.
|
|
//
|
|
//
|
|
|
|
for ( CurrentAnd = LdapFilter->u.and;
|
|
CurrentAnd != NULL;
|
|
CurrentAnd = CurrentAnd->next ) {
|
|
|
|
CurrentFilter = &CurrentAnd->value;
|
|
|
|
if ( CurrentFilter->choice != equalityMatch_chosen ) {
|
|
NlPrint((NL_CRITICAL,
|
|
"I_NetlogonLdapLookup: Filter doesn't have EqualityMatch %ld\n",
|
|
LdapFilter->choice ));
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Handle DnsDomainName parameter.
|
|
//
|
|
|
|
if ( IsFilterName( CurrentFilter, NL_FILTER_DNS_DOMAIN_NAME ) ) {
|
|
|
|
if ( CurrentFilter->u.equalityMatch.assertionValue.length == 0 ) {
|
|
NlPrint((NL_CRITICAL,
|
|
"I_NetlogonLdapLookup: Bad DnsDomainName\n" ));
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Avoid leaking memory - there must be only one domain name specified
|
|
//
|
|
|
|
if ( DnsDomainName != NULL ) {
|
|
NlPrint(( NL_CRITICAL,
|
|
"I_NetlogonLdapLookup: Bad ping from %ws: multiple DnsDomainName\n",
|
|
UnicodeComputerName ));
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto Cleanup;
|
|
}
|
|
|
|
NetStatus = NetApiBufferAllocate(
|
|
CurrentFilter->u.equalityMatch.assertionValue.length + sizeof(CHAR),
|
|
(LPVOID *) &DnsDomainName );
|
|
|
|
if ( NetStatus != NO_ERROR ) {
|
|
Status = STATUS_NO_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
RtlCopyMemory( DnsDomainName,
|
|
CurrentFilter->u.equalityMatch.assertionValue.value,
|
|
CurrentFilter->u.equalityMatch.assertionValue.length );
|
|
|
|
DnsDomainName[ CurrentFilter->u.equalityMatch.assertionValue.length ] = '\0';
|
|
|
|
//
|
|
// Handle Host parameter.
|
|
//
|
|
|
|
} else if ( IsFilterName( CurrentFilter, NL_FILTER_HOST_NAME ) ) {
|
|
|
|
//
|
|
// Avoid leaking memory - there must be only one host name specified
|
|
//
|
|
|
|
if ( UnicodeComputerName != NULL ) {
|
|
NlPrint(( NL_CRITICAL,
|
|
"I_NetlogonLdapLookup: Bad ping from %ws: multiple UnicodeComputerName\n",
|
|
UnicodeComputerName ));
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto Cleanup;
|
|
}
|
|
|
|
NetStatus = FilterString( CurrentFilter,
|
|
MAX_COMPUTERNAME_LENGTH+1,
|
|
UnicodeComputerNameBuffer,
|
|
&UnicodeComputerName );
|
|
|
|
if ( NetStatus != NO_ERROR ) {
|
|
NlPrint((NL_CRITICAL,
|
|
"I_NetlogonLdapLookup: Bad UnicodeComputerName 0x%lx\n",
|
|
NetStatus ));
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Handle User parameter.
|
|
//
|
|
|
|
} else if ( IsFilterName( CurrentFilter, NL_FILTER_USER_NAME ) ) {
|
|
|
|
//
|
|
// Avoid leaking memory - there must be only one user name specified
|
|
//
|
|
|
|
if ( UnicodeUserName != NULL ) {
|
|
NlPrint(( NL_CRITICAL,
|
|
"I_NetlogonLdapLookup: Bad ping from %ws: multiple UnicodeUserName\n",
|
|
UnicodeComputerName ));
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto Cleanup;
|
|
}
|
|
|
|
NetStatus = FilterString( CurrentFilter,
|
|
NL_MAXIMUM_USER_NAME_LENGTH+1,
|
|
UnicodeUserNameBuffer,
|
|
&UnicodeUserName );
|
|
|
|
if ( NetStatus != NO_ERROR ) {
|
|
NlPrint((NL_CRITICAL,
|
|
"I_NetlogonLdapLookup: Bad UnicodeUserName 0x%lx\n",
|
|
NetStatus ));
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Handle AccountControlBits parameter.
|
|
//
|
|
} else if ( IsFilterName( CurrentFilter, NL_FILTER_ALLOWABLE_ACCOUNT_CONTROL ) ) {
|
|
|
|
NetStatus = FilterBinary( CurrentFilter,
|
|
sizeof(AllowableAccountControlBits),
|
|
&AllowableAccountControlBits,
|
|
&Length );
|
|
|
|
if ( NetStatus != NO_ERROR || Length != sizeof(AllowableAccountControlBits) ) {
|
|
NlPrint((NL_CRITICAL,
|
|
"I_NetlogonLdapLookup: Bad AllowableAccountControl 0x%lx %lu\n",
|
|
NetStatus,
|
|
Length ));
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Handle DomainSid parameter.
|
|
//
|
|
} else if ( IsFilterName( CurrentFilter, NL_FILTER_DOMAIN_SID ) ) {
|
|
|
|
NetStatus = FilterBinary( CurrentFilter,
|
|
sizeof(DomainSid),
|
|
DomainSid,
|
|
&Length );
|
|
|
|
//
|
|
// Check the length
|
|
//
|
|
if ( NetStatus != NO_ERROR ||
|
|
!RtlValidSid(DomainSid) ||
|
|
RtlLengthSid(DomainSid) != Length ) {
|
|
|
|
NlPrint((NL_CRITICAL,
|
|
"I_NetlogonLdapLookup: Bad DomainSid 0x%lx %ld\n",
|
|
NetStatus,
|
|
Length ));
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto Cleanup;
|
|
}
|
|
|
|
DomainSidPtr = DomainSid;
|
|
|
|
//
|
|
// Handle DomainGuid parameter.
|
|
//
|
|
} else if ( IsFilterName( CurrentFilter, NL_FILTER_DOMAIN_GUID ) ) {
|
|
|
|
NetStatus = FilterBinary( CurrentFilter,
|
|
sizeof(DomainGuid),
|
|
&DomainGuid,
|
|
&Length );
|
|
|
|
//
|
|
// Check the length
|
|
//
|
|
if ( NetStatus != NO_ERROR ||
|
|
Length != sizeof(GUID) ) {
|
|
|
|
NlPrint((NL_CRITICAL,
|
|
"I_NetlogonLdapLookup: Bad DomainGuid 0x%lx %ld\n",
|
|
NetStatus,
|
|
Length ));
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto Cleanup;
|
|
}
|
|
|
|
DomainGuidPtr = &DomainGuid;
|
|
|
|
//
|
|
// Handle NtVersion parameter.
|
|
//
|
|
} else if ( IsFilterName( CurrentFilter, NL_FILTER_NT_VERSION ) ) {
|
|
|
|
NetStatus = FilterBinary( CurrentFilter,
|
|
sizeof(NtVersionFlags),
|
|
&NtVersionFlags,
|
|
&Length );
|
|
|
|
if ( NetStatus != NO_ERROR || Length != sizeof(NtVersionFlags) ) {
|
|
NlPrint((NL_CRITICAL,
|
|
"I_NetlogonLdapLookup: Bad NtVersionFlags 0x%lx %ld\n",
|
|
NetStatus,
|
|
Length ));
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Attributes we don't understand are ignored. That way clients
|
|
// that are newer than this version can pass additional information
|
|
// to newer DCs and not worry about breaking older DCs.
|
|
//
|
|
|
|
} else {
|
|
NlPrint((NL_CRITICAL,
|
|
"I_NetlogonLdapLookup: unrecognized parameter %.*s\n",
|
|
CurrentFilter->u.equalityMatch.attributeDesc.length,
|
|
CurrentFilter->u.equalityMatch.attributeDesc.value ));
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// Build the ping based on the queried information.
|
|
//
|
|
|
|
NetStatus = NlGetLocalPingResponse(
|
|
L"UDP LDAP",
|
|
TRUE, // LDAP ping
|
|
NULL, // No Netbios domain name
|
|
DnsDomainName,
|
|
DomainGuidPtr,
|
|
DomainSidPtr,
|
|
FALSE, // Not PDC only
|
|
UnicodeComputerName,
|
|
UnicodeUserName,
|
|
AllowableAccountControlBits,
|
|
NtVersion,
|
|
NtVersionFlags,
|
|
(PSOCKADDR)ClientSockAddr,
|
|
Response,
|
|
ResponseSize );
|
|
|
|
if ( NetStatus != NO_ERROR ) {
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto Cleanup;
|
|
}
|
|
|
|
#if NETLOGONDBG
|
|
NlpDumpBuffer( NL_MAILSLOT_TEXT, *Response, *ResponseSize );
|
|
#endif // NETLOGONDBG
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
Cleanup:
|
|
if ( DnsDomainName != NULL ) {
|
|
NetApiBufferFree( DnsDomainName );
|
|
}
|
|
if ( UnicodeComputerName != NULL &&
|
|
UnicodeComputerName != UnicodeComputerNameBuffer ) {
|
|
NetApiBufferFree( UnicodeComputerName );
|
|
}
|
|
if ( UnicodeUserName != NULL &&
|
|
UnicodeUserName != UnicodeUserNameBuffer ) {
|
|
NetApiBufferFree( UnicodeUserName );
|
|
}
|
|
|
|
// Let netlogon service exit.
|
|
NlEndNetlogonCall();
|
|
return Status;
|
|
|
|
|
|
}
|
|
#ifdef __cplusplus
|
|
} /* extern "C" */
|
|
#endif /* __cplusplus */
|