diff options
author | Adam <you@example.com> | 2020-05-17 05:51:50 +0200 |
---|---|---|
committer | Adam <you@example.com> | 2020-05-17 05:51:50 +0200 |
commit | e611b132f9b8abe35b362e5870b74bce94a1e58e (patch) | |
tree | a5781d2ec0e085eeca33cf350cf878f2efea6fe5 /private/nw/svcdlls/nwwks/server/device.c | |
download | NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar.gz NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar.bz2 NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar.lz NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar.xz NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar.zst NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.zip |
Diffstat (limited to '')
-rw-r--r-- | private/nw/svcdlls/nwwks/server/device.c | 2772 |
1 files changed, 2772 insertions, 0 deletions
diff --git a/private/nw/svcdlls/nwwks/server/device.c b/private/nw/svcdlls/nwwks/server/device.c new file mode 100644 index 000000000..a280f3374 --- /dev/null +++ b/private/nw/svcdlls/nwwks/server/device.c @@ -0,0 +1,2772 @@ + +/*++ + +Copyright (c) 1991-1993 Microsoft Corporation + +Module Name: + + device.c + +Abstract: + + This module contains the support routines for the APIs that call + into the NetWare redirector + +Author: + + Rita Wong (ritaw) 20-Feb-1991 + Colin Watson (colinw) 30-Dec-1992 + +Revision History: + +--*/ + +#include <nw.h> +#include <nwcons.h> +#include <nwxchg.h> +#include <nwapi32.h> +#include <nwstatus.h> +#include <nwmisc.h> +#include <nwcons.h> +#include <nds.h> +#include <svcguid.h> +#include <tdi.h> + +#define NW_LINKAGE_REGISTRY_PATH L"NWCWorkstation\\Linkage" +#define NW_BIND_VALUENAME L"Bind" + +#define TWO_KB 2048 +#define EIGHT_KB 8192 +#define EXTRA_BYTES 256 + +#define TREECHAR L'*' +#define BUFFSIZE 1024 + +//-------------------------------------------------------------------// +// // +// Local Function Prototypes // +// // +//-------------------------------------------------------------------// + + +STATIC +NTSTATUS +BindToEachTransport( + IN PWSTR ValueName, + IN ULONG ValueType, + IN PVOID ValueData, + IN ULONG ValueLength, + IN PVOID Context, + IN PVOID EntryContext + ); + +DWORD +NwBindTransport( + IN LPWSTR TransportName, + IN DWORD QualityOfService + ); + +DWORD +GetConnectedBinderyServers( + OUT LPNW_ENUM_CONTEXT ContextHandle + ); + +DWORD +GetTreeEntriesFromBindery( + OUT LPNW_ENUM_CONTEXT ContextHandle + ); + +DWORD +NwGetConnectionStatus( + IN LPWSTR pszServerName, + IN OUT PDWORD ResumeKey, + OUT LPBYTE *Buffer, + OUT PDWORD EntriesRead + ); + +VOID +GetNearestDirServer( + IN LPWSTR TreeName, + OUT LPDWORD lpdwReplicaAddressSize, + OUT LPBYTE lpReplicaAddress + ); + +BOOL +NwpCompareTreeNames( + LPWSTR lpServiceInstanceName, + LPWSTR lpTreeName + ); + +//-------------------------------------------------------------------// +// // +// Global variables // +// // +//-------------------------------------------------------------------// + +// +// Handle to the Redirector FSD +// +STATIC HANDLE RedirDeviceHandle = NULL; + +// +// Redirector name in NT string format +// +STATIC UNICODE_STRING RedirDeviceName; + + +DWORD +NwInitializeRedirector( + VOID + ) +/*++ + +Routine Description: + + This routine initializes the NetWare redirector FSD. + +Arguments: + + None. + +Return Value: + + NO_ERROR or reason for failure. + +--*/ +{ + DWORD error; + NWR_REQUEST_PACKET Rrp; + + + // + // Initialize global handles + // + RedirDeviceHandle = NULL; + + // + // Initialize the global NT-style redirector device name string. + // + RtlInitUnicodeString(&RedirDeviceName, DD_NWFS_DEVICE_NAME_U); + + // + // Load driver + // + error = NwLoadOrUnloadDriver(TRUE); + + if (error != NO_ERROR && error != ERROR_SERVICE_ALREADY_RUNNING) { + return error; + } + + if ((error = NwOpenRedirector()) != NO_ERROR) { + + // + // Unload the redirector driver + // + (void) NwLoadOrUnloadDriver(FALSE); + return error; + } + + // + // Send the start FSCTL to the redirector + // + Rrp.Version = REQUEST_PACKET_VERSION; + + return NwRedirFsControl( + RedirDeviceHandle, + FSCTL_NWR_START, + &Rrp, + sizeof(NWR_REQUEST_PACKET), + NULL, + 0, + NULL + ); +} + + + +DWORD +NwOpenRedirector( + VOID + ) +/*++ + +Routine Description: + + This routine opens the NT NetWare redirector FSD. + +Arguments: + + None. + +Return Value: + + NO_ERROR or reason for failure. + +--*/ +{ + return RtlNtStatusToDosError( + NwOpenHandle(&RedirDeviceName, FALSE, &RedirDeviceHandle) + ); +} + + + +DWORD +NwShutdownRedirector( + VOID + ) +/*++ + +Routine Description: + + This routine stops the NetWare Redirector FSD and unloads it if + possible. + +Arguments: + + None. + +Return Value: + + NO_ERROR or ERROR_REDIRECTOR_HAS_OPEN_HANDLES + +--*/ +{ + NWR_REQUEST_PACKET Rrp; + DWORD error; + + + Rrp.Version = REQUEST_PACKET_VERSION; + + error = NwRedirFsControl( + RedirDeviceHandle, + FSCTL_NWR_STOP, + &Rrp, + sizeof(NWR_REQUEST_PACKET), + NULL, + 0, + NULL + ); + + (void) NtClose(RedirDeviceHandle); + + RedirDeviceHandle = NULL; + + if (error != ERROR_REDIRECTOR_HAS_OPEN_HANDLES) { + + // + // Unload the redirector only if all its open handles are closed. + // + (void) NwLoadOrUnloadDriver(FALSE); + } + + return error; +} + + +DWORD +NwLoadOrUnloadDriver( + BOOL Load + ) +/*++ + +Routine Description: + + This routine loads or unloads the NetWare redirector driver. + +Arguments: + + Load - Supplies the flag which if TRUE load the driver; otherwise + unloads the driver. + +Return Value: + + NO_ERROR or reason for failure. + +--*/ +{ + + LPWSTR DriverRegistryName; + UNICODE_STRING DriverRegistryString; + NTSTATUS ntstatus; + BOOLEAN WasEnabled; + + + DriverRegistryName = (LPWSTR) LocalAlloc( + LMEM_FIXED, + (UINT) (sizeof(SERVICE_REGISTRY_KEY) + + (wcslen(NW_DRIVER_NAME) * + sizeof(WCHAR))) + ); + + if (DriverRegistryName == NULL) { + return ERROR_NOT_ENOUGH_MEMORY; + } + + ntstatus = RtlAdjustPrivilege( + SE_LOAD_DRIVER_PRIVILEGE, + TRUE, + FALSE, + &WasEnabled + ); + + if (! NT_SUCCESS(ntstatus)) { + (void) LocalFree(DriverRegistryName); + return RtlNtStatusToDosError(ntstatus); + } + + wcscpy(DriverRegistryName, SERVICE_REGISTRY_KEY); + wcscat(DriverRegistryName, NW_DRIVER_NAME); + + RtlInitUnicodeString(&DriverRegistryString, DriverRegistryName); + + if (Load) { + ntstatus = NtLoadDriver(&DriverRegistryString); + } + else { + ntstatus = NtUnloadDriver(&DriverRegistryString); + } + + (void) RtlAdjustPrivilege( + SE_LOAD_DRIVER_PRIVILEGE, + WasEnabled, + FALSE, + &WasEnabled + ); + + (void) LocalFree(DriverRegistryName); + + if (Load) { + if (ntstatus != STATUS_SUCCESS && ntstatus != STATUS_IMAGE_ALREADY_LOADED) { + LPWSTR SubString[1]; + + KdPrint(("NWWORKSTATION: NtLoadDriver returned %08lx\n", ntstatus)); + + SubString[0] = NW_DRIVER_NAME; + + NwLogEvent( + EVENT_NWWKSTA_CANT_CREATE_REDIRECTOR, + 1, + SubString, + ntstatus + ); + } + } + + if (ntstatus == STATUS_OBJECT_NAME_NOT_FOUND) { + return ERROR_FILE_NOT_FOUND; + } + + return NwMapStatus(ntstatus); +} + + +DWORD +NwRedirFsControl( + IN HANDLE FileHandle, + IN ULONG RedirControlCode, + IN PNWR_REQUEST_PACKET Rrp, + IN ULONG RrpLength, + IN PVOID SecondBuffer OPTIONAL, + IN ULONG SecondBufferLength, + OUT PULONG Information OPTIONAL + ) +/*++ + +Routine Description: + +Arguments: + + FileHandle - Supplies a handle to the file or device on which the service + is being performed. + + RedirControlCode - Supplies the NtFsControlFile function code given to + the redirector. + + Rrp - Supplies the redirector request packet. + + RrpLength - Supplies the length of the redirector request packet. + + SecondBuffer - Supplies the second buffer in call to NtFsControlFile. + + SecondBufferLength - Supplies the length of the second buffer. + + Information - Returns the information field of the I/O status block. + +Return Value: + + NO_ERROR or reason for failure. + +--*/ + +{ + NTSTATUS ntstatus; + IO_STATUS_BLOCK IoStatusBlock; + + + // + // Send the request to the Redirector FSD. + // + ntstatus = NtFsControlFile( + FileHandle, + NULL, + NULL, + NULL, + &IoStatusBlock, + RedirControlCode, + (PVOID) Rrp, + RrpLength, + SecondBuffer, + SecondBufferLength + ); + + if (ntstatus == STATUS_SUCCESS) { + ntstatus = IoStatusBlock.Status; + } + + if (ARGUMENT_PRESENT(Information)) { + *Information = IoStatusBlock.Information; + } + +#if DBG + if (ntstatus != STATUS_SUCCESS) { + IF_DEBUG(DEVICE) { + KdPrint(("NWWORKSTATION: fsctl to redir returns %08lx\n", ntstatus)); + } + } +#endif + + return NwMapStatus(ntstatus); +} + + +DWORD +NwBindToTransports( + VOID + ) + +/*++ + +Routine Description: + + This routine binds to every transport specified under the linkage + key of the NetWare Workstation service. + +Arguments: + + None. + +Return Value: + + NET_API_STATUS - success/failure of the operation. + +--*/ + +{ + NTSTATUS ntstatus; + PRTL_QUERY_REGISTRY_TABLE QueryTable; + ULONG NumberOfBindings = 0; + + + // + // Ask the RTL to call us back for each subvalue in the MULTI_SZ + // value \NWCWorkstation\Linkage\Bind. + // + + if ((QueryTable = (PVOID) LocalAlloc( + LMEM_ZEROINIT, + sizeof(RTL_QUERY_REGISTRY_TABLE) * 2 + )) == NULL) { + return ERROR_NOT_ENOUGH_MEMORY; + } + + QueryTable[0].QueryRoutine = (PRTL_QUERY_REGISTRY_ROUTINE) BindToEachTransport; + QueryTable[0].Flags = 0; + QueryTable[0].Name = NW_BIND_VALUENAME; + QueryTable[0].EntryContext = NULL; + QueryTable[0].DefaultType = REG_NONE; + QueryTable[0].DefaultData = NULL; + QueryTable[0].DefaultLength = 0; + + QueryTable[1].QueryRoutine = NULL; + QueryTable[1].Flags = 0; + QueryTable[1].Name = NULL; + + ntstatus = RtlQueryRegistryValues( + RTL_REGISTRY_SERVICES, + NW_LINKAGE_REGISTRY_PATH, + QueryTable, + &NumberOfBindings, + NULL + ); + + (void) LocalFree((HLOCAL) QueryTable); + + // + // If failed to bind to any transports, the workstation will + // not start. + // + + if (! NT_SUCCESS(ntstatus)) { +#if DBG + IF_DEBUG(INIT) { + KdPrint(("NwBindToTransports: RtlQueryRegistryValues failed: " + "%lx\n", ntstatus)); + } +#endif + return RtlNtStatusToDosError(ntstatus); + } + + if (NumberOfBindings == 0) { + + NwLogEvent( + EVENT_NWWKSTA_NO_TRANSPORTS, + 0, + NULL, + NO_ERROR + ); + + KdPrint(("NWWORKSTATION: NwBindToTransports: could not bind " + "to any transport\n")); + + return ERROR_INVALID_PARAMETER; // BUGBUG: Bad return code. + // Create a NetWare specific set? + } + + return NO_ERROR; +} + + +STATIC +NTSTATUS +BindToEachTransport( + IN PWSTR ValueName, + IN ULONG ValueType, + IN PVOID ValueData, + IN ULONG ValueLength, + IN PVOID Context, + IN PVOID EntryContext + ) +{ + DWORD error; + LPDWORD NumberOfBindings = Context; + LPWSTR SubStrings[2]; + static DWORD QualityOfService = 65536; + + + UNREFERENCED_PARAMETER(ValueName); + UNREFERENCED_PARAMETER(ValueLength); + UNREFERENCED_PARAMETER(EntryContext); + + // + // The value type must be REG_SZ (translated from REG_MULTI_SZ by + // the RTL). + // + if (ValueType != REG_SZ) { + + SubStrings[0] = ValueName; + SubStrings[1] = NW_LINKAGE_REGISTRY_PATH; + + NwLogEvent( + EVENT_NWWKSTA_INVALID_REGISTRY_VALUE, + 2, + SubStrings, + NO_ERROR + ); + + KdPrint(("NWWORKSTATION: Skipping invalid value %ws\n", ValueName)); + + return STATUS_SUCCESS; + } + + // + // The value data is the name of the transport device object. + // + + // + // Bind to the transport. + // + +#if DBG + IF_DEBUG(INIT) { + KdPrint(("NWWORKSTATION: Binding to transport %ws with QOS %lu\n", + ValueData, QualityOfService)); + } +#endif + + error = NwBindTransport(ValueData, QualityOfService--); + + if (error != NO_ERROR) { + + // + // If failed to bind to one transport, don't fail starting yet. + // Try other transports. + // + SubStrings[0] = ValueData; + + NwLogEvent( + EVENT_NWWKSTA_CANT_BIND_TO_TRANSPORT, + 1, + SubStrings, + error + ); + } + else { + (*NumberOfBindings)++; + } + + return STATUS_SUCCESS; +} + + +DWORD +NwBindTransport( + IN LPWSTR TransportName, + IN DWORD QualityOfService + ) +/*++ + +Routine Description: + + This function binds the specified transport to the redirector + and the datagram receiver. + + NOTE: The transport name length pass to the redirector and + datagram receiver is the number of bytes. + +Arguments: + + TransportName - Supplies the name of the transport to bind to. + + QualityOfService - Supplies a value which specifies the search + order of the transport with respect to other transports. The + highest value is searched first. + +Return Value: + + NO_ERROR or reason for failure. + +--*/ +{ + DWORD status; + DWORD RequestPacketSize; + DWORD TransportNameSize = wcslen(TransportName) * sizeof(WCHAR); + + PNWR_REQUEST_PACKET Rrp; + + + // + // Size of request packet buffer + // + RequestPacketSize = TransportNameSize + sizeof(NWR_REQUEST_PACKET); + + // + // Allocate memory for redirector/datagram receiver request packet + // + if ((Rrp = (PVOID) LocalAlloc(LMEM_ZEROINIT, (UINT) RequestPacketSize)) == NULL) { + return ERROR_NOT_ENOUGH_MEMORY; + } + + // + // Get redirector to bind to transport + // + Rrp->Version = REQUEST_PACKET_VERSION; + Rrp->Parameters.Bind.QualityOfService = QualityOfService; + + Rrp->Parameters.Bind.TransportNameLength = TransportNameSize; + wcscpy((LPWSTR) Rrp->Parameters.Bind.TransportName, TransportName); + + if ((status = NwRedirFsControl( + RedirDeviceHandle, + FSCTL_NWR_BIND_TO_TRANSPORT, + Rrp, + RequestPacketSize, + NULL, + 0, + NULL + )) != NO_ERROR) { + + KdPrint(("NWWORKSTATION: NwBindTransport fsctl to bind to transport %ws failed\n", + TransportName)); + } + + (void) LocalFree((HLOCAL) Rrp); + return status; +} + + +DWORD +NwCreateTreeConnectName( + IN LPWSTR UncName, + IN LPWSTR LocalName OPTIONAL, + OUT PUNICODE_STRING TreeConnectStr + ) +/*++ + +Routine Description: + + This function replaces \\ with \Device\NwRdr\LocalName:\ in the + UncName to form the NT-style tree connection name. LocalName:\ is part + of the tree connection name only if LocalName is specified. A buffer + is allocated by this function and returned as the output string. + +Arguments: + + UncName - Supplies the UNC name of the shared resource. + + LocalName - Supplies the local device name for the redirection. + + TreeConnectStr - Returns a string with a newly allocated buffer that + contains the NT-style tree connection name. + +Return Value: + + NO_ERROR - the operation was successful. + + ERROR_NOT_ENOUGH_MEMORY - Could not allocate output buffer. + +--*/ +{ + DWORD UncNameLength = wcslen(UncName); + + + // + // Initialize tree connect string maximum length to hold + // \Device\NwRdr\LocalName:\Server\Volume\Path + // + TreeConnectStr->MaximumLength = RedirDeviceName.Length + + sizeof(WCHAR) + // For '\' + (ARGUMENT_PRESENT(LocalName) ? (wcslen(LocalName) * sizeof(WCHAR)) : 0) + + (USHORT) (UncNameLength * sizeof(WCHAR)); // Includes '\' and + // term char + + + if ((TreeConnectStr->Buffer = (PWSTR) LocalAlloc( + LMEM_ZEROINIT, + (UINT) TreeConnectStr->MaximumLength + )) == NULL) { + KdPrint(("NWWORKSTATION: NwCreateTreeConnectName LocalAlloc failed %lu\n", + GetLastError())); + return ERROR_NOT_ENOUGH_MEMORY; + } + + // + // Copy \Device\NwRdr + // + RtlCopyUnicodeString(TreeConnectStr, &RedirDeviceName); + + // + // Concatenate \LocalName: + // + if (ARGUMENT_PRESENT(LocalName)) { + + wcscat(TreeConnectStr->Buffer, L"\\"); + TreeConnectStr->Length += sizeof(WCHAR); + + wcscat(TreeConnectStr->Buffer, LocalName); + + TreeConnectStr->Length += (USHORT) (wcslen(LocalName) * sizeof(WCHAR)); + } + + // + // Concatenate \Server\Volume\Path + // + wcscat(TreeConnectStr->Buffer, &UncName[1]); + TreeConnectStr->Length += (USHORT) ((UncNameLength - 1) * sizeof(WCHAR)); + +#if DBG + IF_DEBUG(CONNECT) { + KdPrint(("NWWORKSTATION: NwCreateTreeConnectName %ws, maxlength %u, length %u\n", + TreeConnectStr->Buffer, TreeConnectStr->MaximumLength, + TreeConnectStr->Length)); + } +#endif + + return NO_ERROR; +} + + + +DWORD +NwOpenCreateConnection( + IN PUNICODE_STRING TreeConnectionName, + IN LPWSTR UserName OPTIONAL, + IN LPWSTR Password OPTIONAL, + IN LPWSTR UncName, + IN ACCESS_MASK DesiredAccess, + IN ULONG CreateDisposition, + IN ULONG CreateOptions, + IN ULONG ConnectionType, + OUT PHANDLE TreeConnectionHandle, + OUT PULONG Information OPTIONAL + ) +/*++ + +Routine Description: + + This function asks the redirector to either open an existing tree + connection (CreateDisposition == FILE_OPEN), or create a new tree + connection if one does not exist (CreateDisposition == FILE_CREATE). + + The password and user name passed to the redirector via the EA buffer + in the NtCreateFile call. The EA buffer is NULL if neither password + or user name is specified. + + The redirector expects the EA descriptor strings to be in ANSI + but the password and username themselves are in Unicode. + +Arguments: + + TreeConnectionName - Supplies the name of the tree connection in NT-style + file name format: \Device\NwRdr\Server\Volume\Directory + + UserName - Supplies the user name to create the tree connection with. + + Password - Supplies the password to create the tree connection with. + + DesiredAccess - Supplies the access need on the connection handle. + + CreateDisposition - Supplies the create disposition value to either + open or create the tree connection. + + CreateOptions - Supplies the options used when creating or opening + the tree connection. + + ConnectionType - Supplies the type of the connection (DISK, PRINT, + or ANY). + + TreeConnectionHandle - Returns the handle to the tree connection + created/opened by the redirector. + + Information - Returns the information field of the I/O status block. + +Return Value: + + NO_ERROR or reason for failure. + +--*/ +{ + DWORD status; + NTSTATUS ntstatus; + + OBJECT_ATTRIBUTES UncNameAttributes; + IO_STATUS_BLOCK IoStatusBlock; + + PFILE_FULL_EA_INFORMATION EaBuffer = NULL; + PFILE_FULL_EA_INFORMATION Ea; + ULONG EaBufferSize = 0; + + UCHAR EaNamePasswordSize = (UCHAR) (ROUND_UP_COUNT( + strlen(EA_NAME_PASSWORD) + sizeof(CHAR), + ALIGN_WCHAR + ) - sizeof(CHAR)); + UCHAR EaNameUserNameSize = (UCHAR) (ROUND_UP_COUNT( + strlen(EA_NAME_USERNAME) + sizeof(CHAR), + ALIGN_WCHAR + ) - sizeof(CHAR)); + + UCHAR EaNameTypeSize = (UCHAR) (ROUND_UP_COUNT( + strlen(EA_NAME_TYPE) + sizeof(CHAR), + ALIGN_DWORD + ) - sizeof(CHAR)); + + USHORT PasswordSize = 0; + USHORT UserNameSize = 0; + USHORT TypeSize = sizeof(ULONG); + + + + InitializeObjectAttributes( + &UncNameAttributes, + TreeConnectionName, + OBJ_CASE_INSENSITIVE, + NULL, + NULL + ); + + // + // Calculate the number of bytes needed for the EA buffer to put the + // password or user name. + // + if (ARGUMENT_PRESENT(Password)) { + +#if DBG + IF_DEBUG(CONNECT) { + KdPrint(("NWWORKSTATION: NwOpenCreateConnection password is %ws\n", + Password)); + } +#endif + + PasswordSize = (USHORT) (wcslen(Password) * sizeof(WCHAR)); + + EaBufferSize = ROUND_UP_COUNT( + FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName[0]) + + EaNamePasswordSize + sizeof(CHAR) + + PasswordSize, + ALIGN_DWORD + ); + } + + if (ARGUMENT_PRESENT(UserName)) { + +#if DBG + IF_DEBUG(CONNECT) { + KdPrint(("NWWORKSTATION: NwOpenCreateConnection username is %ws\n", + UserName)); + } +#endif + + UserNameSize = (USHORT) (wcslen(UserName) * sizeof(WCHAR)); + + EaBufferSize += ROUND_UP_COUNT( + FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName[0]) + + EaNameUserNameSize + sizeof(CHAR) + + UserNameSize, + ALIGN_DWORD + ); + } + + EaBufferSize += FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName[0]) + + EaNameTypeSize + sizeof(CHAR) + + TypeSize; + + // + // Allocate the EA buffer + // + if ((EaBuffer = (PFILE_FULL_EA_INFORMATION) LocalAlloc( + LMEM_ZEROINIT, + (UINT) EaBufferSize + )) == NULL) { + status = GetLastError(); + goto FreeMemory; + } + + Ea = EaBuffer; + + if (ARGUMENT_PRESENT(Password)) { + + // + // Copy the EA name into EA buffer. EA name length does not + // include the zero terminator. + // + strcpy((LPSTR) Ea->EaName, EA_NAME_PASSWORD); + Ea->EaNameLength = EaNamePasswordSize; + + // + // Copy the EA value into EA buffer. EA value length does not + // include the zero terminator. + // + wcscpy( + (LPWSTR) &(Ea->EaName[EaNamePasswordSize + sizeof(CHAR)]), + Password + ); + + Ea->EaValueLength = PasswordSize; + + Ea->NextEntryOffset = ROUND_UP_COUNT( + FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName[0]) + + EaNamePasswordSize + sizeof(CHAR) + + PasswordSize, + ALIGN_DWORD + ); + + Ea->Flags = 0; + + (ULONG) Ea += Ea->NextEntryOffset; + } + + if (ARGUMENT_PRESENT(UserName)) { + + // + // Copy the EA name into EA buffer. EA name length does not + // include the zero terminator. + // + strcpy((LPSTR) Ea->EaName, EA_NAME_USERNAME); + Ea->EaNameLength = EaNameUserNameSize; + + // + // Copy the EA value into EA buffer. EA value length does not + // include the zero terminator. + // + wcscpy( + (LPWSTR) &(Ea->EaName[EaNameUserNameSize + sizeof(CHAR)]), + UserName + ); + + Ea->EaValueLength = UserNameSize; + + Ea->NextEntryOffset = ROUND_UP_COUNT( + FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName[0]) + + EaNameUserNameSize + sizeof(CHAR) + + UserNameSize, + ALIGN_DWORD + ); + Ea->Flags = 0; + + (ULONG) Ea += Ea->NextEntryOffset; + + } + + // + // Copy the connection type name into EA buffer. EA name length + // does not include the zero terminator. + // + strcpy((LPSTR) Ea->EaName, EA_NAME_TYPE); + Ea->EaNameLength = EaNameTypeSize; + + *((PULONG) &(Ea->EaName[EaNameTypeSize + sizeof(CHAR)])) = ConnectionType; + + Ea->EaValueLength = TypeSize; + + // + // Terminate the EA. + // + Ea->NextEntryOffset = 0; + Ea->Flags = 0; + + // + // Create or open a tree connection + // + ntstatus = NtCreateFile( + TreeConnectionHandle, + DesiredAccess, + &UncNameAttributes, + &IoStatusBlock, + NULL, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_VALID_FLAGS, + CreateDisposition, + CreateOptions, + (PVOID) EaBuffer, + EaBufferSize + ); + + if (ntstatus == NWRDR_PASSWORD_HAS_EXPIRED) { + // + // wait till other thread is not using the popup data struct. + // if we timeout, then we just lose the popup. + // + switch (WaitForSingleObject(NwPopupDoneEvent, 3000)) + { + case WAIT_OBJECT_0: + { + LPWSTR lpServerStart, lpServerEnd ; + WCHAR UserNameW[NW_MAX_USERNAME_LEN+1] ; + DWORD dwUserNameWSize = sizeof(UserNameW)/sizeof(UserNameW[0]) ; + DWORD dwServerLength, dwGraceLogins ; + DWORD dwMessageId = NW_PASSWORD_HAS_EXPIRED ; + + // + // get the current username + // + if (UserName) + { + wcscpy(UserNameW, UserName) ; + } + else + { + if (!GetUserNameW(UserNameW, &dwUserNameWSize)) + { + SetEvent(NwPopupDoneEvent) ; + break ; + } + } + + // + // allocate string and fill in the username + // + if (!(PopupData.InsertStrings[0] = + (LPWSTR)LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT, + sizeof(WCHAR) * (wcslen(UserNameW)+1)))) + { + SetEvent(NwPopupDoneEvent) ; + break ; + } + wcscpy(PopupData.InsertStrings[0], UserNameW) ; + + // + // find the server name from unc name + // + lpServerStart = (*UncName == L'\\') ? UncName+2 : UncName ; + lpServerEnd = wcschr(lpServerStart,L'\\') ; + dwServerLength = lpServerEnd ? (lpServerEnd-lpServerStart) : + wcslen(lpServerStart) ; + + // + // allocate string and fill in the server insert string + // + if (!(PopupData.InsertStrings[1] = + (LPWSTR)LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT, + sizeof(WCHAR) * (dwServerLength+1)))) + { + (void) LocalFree((HLOCAL) PopupData.InsertStrings[0]); + SetEvent(NwPopupDoneEvent) ; + break ; + } + wcsncpy(PopupData.InsertStrings[1], + lpServerStart, + dwServerLength) ; + + // + // now call the NCP. if an error occurs while getting + // the grace login count, dont use it. + // + if (NwGetGraceLoginCount( + PopupData.InsertStrings[1], + UserNameW, + &dwGraceLogins) != NO_ERROR) + { + dwMessageId = NW_PASSWORD_HAS_EXPIRED1 ; + dwGraceLogins = 0 ; + } + + // + // stick the number of grace logins in second insert string. + // + if (!(PopupData.InsertStrings[2] = + (LPWSTR)LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT, + sizeof(WCHAR) * 16))) + { + (void) LocalFree((HLOCAL) PopupData.InsertStrings[0]); + (void) LocalFree((HLOCAL) PopupData.InsertStrings[1]); + SetEvent(NwPopupDoneEvent) ; + break ; + } + + wsprintfW(PopupData.InsertStrings[2], L"%d", dwGraceLogins); + PopupData.InsertCount = 3 ; + PopupData.MessageId = dwMessageId ; + + // + // all done at last, trigger the other thread do the popup + // + SetEvent(NwPopupEvent) ; + break ; + + } + + default: + break ; // dont bother if we cannot + } + } + + if (NT_SUCCESS(ntstatus)) { + ntstatus = IoStatusBlock.Status; + } + + if (ntstatus == NWRDR_PASSWORD_HAS_EXPIRED) { + ntstatus = STATUS_SUCCESS ; + } + + if (ARGUMENT_PRESENT(Information)) { + *Information = IoStatusBlock.Information; + } + +#if DBG + IF_DEBUG(CONNECT) { + KdPrint(("NWWORKSTATION: NtCreateFile returns %lx\n", ntstatus)); + } +#endif + + status = NwMapStatus(ntstatus); + +FreeMemory: + if (EaBuffer != NULL) { + RtlZeroMemory( EaBuffer, EaBufferSize ); // Clear the password + (void) LocalFree((HLOCAL) EaBuffer); + } + + return status; +} + + +DWORD +NwNukeConnection( + IN HANDLE TreeConnection, + IN DWORD UseForce + ) +/*++ + +Routine Description: + + This function asks the redirector to delete an existing tree + connection. + +Arguments: + + TreeConnection - Supplies the handle to an existing tree connection. + + UseForce - Supplies the force flag to delete the tree connection. + +Return Value: + + NO_ERROR or reason for failure. + +--*/ +{ + DWORD status; + NWR_REQUEST_PACKET Rrp; // Redirector request packet + + + // + // Tell the redirector to delete the tree connection + // + Rrp.Version = REQUEST_PACKET_VERSION; + Rrp.Parameters.DeleteConn.UseForce = (BOOLEAN) UseForce; + + status = NwRedirFsControl( + TreeConnection, + FSCTL_NWR_DELETE_CONNECTION, + &Rrp, + sizeof(NWR_REQUEST_PACKET), + NULL, + 0, + NULL + ); + + return status; +} + + +DWORD +NwGetServerResource( + IN LPWSTR LocalName, + IN DWORD LocalNameLength, + OUT LPWSTR RemoteName, + IN DWORD RemoteNameLen, + OUT LPDWORD CharsRequired + ) +/*++ + +Routine Description: + + This function + +Arguments: + + +Return Value: + + +--*/ +{ + DWORD status = NO_ERROR; + + BYTE Buffer[sizeof(NWR_REQUEST_PACKET) + 2 * sizeof(WCHAR)]; + PNWR_REQUEST_PACKET Rrp = (PNWR_REQUEST_PACKET) Buffer; + + + // + // local device name should not be longer than 4 characters e.g. LPTx, X: + // + if ( LocalNameLength > 4 ) + return ERROR_INVALID_PARAMETER; + + Rrp->Version = REQUEST_PACKET_VERSION; + + wcsncpy(Rrp->Parameters.GetConn.DeviceName, LocalName, LocalNameLength); + Rrp->Parameters.GetConn.DeviceNameLength = LocalNameLength * sizeof(WCHAR); + + status = NwRedirFsControl( + RedirDeviceHandle, + FSCTL_NWR_GET_CONNECTION, + Rrp, + sizeof(NWR_REQUEST_PACKET) + + Rrp->Parameters.GetConn.DeviceNameLength, + RemoteName, + RemoteNameLen * sizeof(WCHAR), + NULL + ); + + if (status == ERROR_INSUFFICIENT_BUFFER) { + *CharsRequired = Rrp->Parameters.GetConn.BytesNeeded / sizeof(WCHAR); + } + else if (status == ERROR_FILE_NOT_FOUND) { + + // + // Redirector could not find the specified LocalName + // + status = WN_NOT_CONNECTED; + } + + return status; + +} + + +DWORD +NwEnumerateConnections( + IN OUT LPDWORD ResumeId, + IN DWORD EntriesRequested, + IN LPBYTE Buffer, + IN DWORD BufferSize, + OUT LPDWORD BytesNeeded, + OUT LPDWORD EntriesRead, + IN DWORD ConnectionType + ) +/*++ + +Routine Description: + + This function asks the redirector to enumerate all existing + connections. + +Arguments: + + ResumeId - On input, supplies the resume ID of the next entry + to begin the enumeration. This ID is an integer value that + is either the smaller or the same value as the ID of the + next entry to return. On output, this ID indicates the next + entry to start resuming from for the subsequent call. + + EntriesRequested - Supplies the number of entries to return. If + this value is 0xffffffff, return all available entries. + + Buffer - Receives the entries we are listing. + + BufferSize - Supplies the size of the output buffer. + + BytesNeeded - Receives the number of bytes required to get the + first entry. This value is returned iff ERROR_MORE_DATA is + the return code, and Buffer is too small to even fit one + entry. + + EntriesRead - Receives the number of entries returned in Buffer. + This value is only returned iff NO_ERROR is the return code. + NO_ERROR is returned as long as at least one entry was written + into Buffer but does not necessarily mean that it's the number + of EntriesRequested. + + ConnectionType - The type of connected resource wanted ( DISK, PRINT, ...) + +Return Value: + + NO_ERROR or reason for failure. + +--*/ +{ + DWORD status; + NWR_REQUEST_PACKET Rrp; // Redirector request packet + + + // + // Tell the redirector to enumerate all connections. + // + Rrp.Version = REQUEST_PACKET_VERSION; + + Rrp.Parameters.EnumConn.ResumeKey = *ResumeId; + Rrp.Parameters.EnumConn.EntriesRequested = EntriesRequested; + Rrp.Parameters.EnumConn.ConnectionType = ConnectionType; + + status = NwRedirFsControl( + RedirDeviceHandle, + FSCTL_NWR_ENUMERATE_CONNECTIONS, + &Rrp, + sizeof(NWR_REQUEST_PACKET), + Buffer, // User output buffer + BufferSize, + NULL + ); + + *EntriesRead = Rrp.Parameters.EnumConn.EntriesReturned; + + if (status == WN_MORE_DATA) { + *BytesNeeded = Rrp.Parameters.EnumConn.BytesNeeded; + + // + // NP specs expect WN_SUCCESS in this case. + // + if (*EntriesRead) + status = WN_SUCCESS ; + } + + *ResumeId = Rrp.Parameters.EnumConn.ResumeKey; + + return status; +} + + +DWORD +NwGetNextServerEntry( + IN HANDLE PreferredServer, + IN OUT LPDWORD LastObjectId, + OUT LPSTR ServerName + ) +/*++ + +Routine Description: + + This function uses an opened handle to the preferred server to + scan it bindery for all file server objects. + +Arguments: + + PreferredServer - Supplies the handle to the preferred server on + which to scan the bindery. + + LastObjectId - On input, supplies the object ID to the last file + server object returned, which is the resume handle to get the + next file server object. On output, receives the object ID + of the file server object returned. + + ServerName - Receives the name of the returned file server object. + +Return Value: + + NO_ERROR - Successfully gotten a file server name. + + WN_NO_MORE_ENTRIES - No other file server object past the one + specified by LastObjectId. + +--*/ +{ + NTSTATUS ntstatus; + WORD ObjectType; + + +#if DBG + IF_DEBUG(ENUM) { + KdPrint(("NWWORKSTATION: NwGetNextServerEntry LastObjectId %lu\n", + *LastObjectId)); + } +#endif + + ntstatus = NwlibMakeNcp( + PreferredServer, + FSCTL_NWR_NCP_E3H, // Bindery function + 58, // Max request packet size + 59, // Max response packet size + "bdwp|dwc", // Format string + 0x37, // Scan bindery object + *LastObjectId, // Previous ID + 0x4, // File server object + "*", // Wildcard to match all + LastObjectId, // Current ID + &ObjectType, // Ignore + ServerName // Currently returned server + ); + +#if DBG + if (ntstatus == STATUS_SUCCESS) { + IF_DEBUG(ENUM) { + KdPrint(("NWWORKSTATION: NwGetNextServerEntry NewObjectId %08lx, ServerName %s\n", + *LastObjectId, ServerName)); + } + } +#endif + + return NwMapBinderyCompletionCode(ntstatus); +} + + +DWORD +GetConnectedBinderyServers( + OUT LPNW_ENUM_CONTEXT ContextHandle + ) +/*++ + +Routine Description: + + This function is a helper routine for the function + NwGetNextServerConnection. It allocates a buffer to cache + bindery server names returned from calls to the redirector. Since the + redirector may return duplicate bindery server names, this + function checks to see if the server name already exist in the buffer + before adding it. + +Arguments: + + ContextHandle - Used to track cached bindery information and the + current server name pointer in the cache buffer. + +Return Value: + + NO_ERROR - Successfully returned a server name and cache buffer. + + WN_NO_MORE_ENTRIES - No other server object past the one + specified by CH->ResumeId. + + ERROR_NOT_ENOUGH_MEMORY - Function was unable to allocate a buffer. + +++*/ +{ + DWORD ResumeKey = 0; + LPBYTE pBuffer = NULL; + DWORD EntriesRead = 0; + BYTE tokenIter; + LPWSTR tokenPtr; + BOOL fAddToList; + DWORD status = NwGetConnectionStatus( NULL, + &ResumeKey, + &pBuffer, + &EntriesRead ); + + if ( status == NO_ERROR && EntriesRead > 0 ) + { + DWORD i; + PCONN_STATUS pConnStatus = (PCONN_STATUS) pBuffer; + + ContextHandle->ResumeId = 0; + ContextHandle->NdsRawDataCount = 0; + ContextHandle->NdsRawDataSize = (NW_MAX_SERVER_LEN + 2) * EntriesRead; + ContextHandle->NdsRawDataBuffer = + (DWORD) LocalAlloc( LMEM_ZEROINIT, + ContextHandle->NdsRawDataSize ); + + if ( ContextHandle->NdsRawDataBuffer == 0 ) + { + KdPrint(("NWWORKSTATION: GetConnectedBinderyServers LocalAlloc failed %lu\n", + GetLastError())); + + ContextHandle->NdsRawDataSize = 0; + + return ERROR_NOT_ENOUGH_MEMORY; + } + + for ( i = 0; i < EntriesRead ; i++ ) + { + fAddToList = FALSE; + + if ( pConnStatus->fNds == 0 && + ( pConnStatus->dwConnType == NW_CONN_BINDERY_LOGIN || + pConnStatus->dwConnType == NW_CONN_NDS_AUTHENTICATED_NO_LICENSE || + pConnStatus->dwConnType == NW_CONN_NDS_AUTHENTICATED_LICENSED || + pConnStatus->dwConnType == NW_CONN_DISCONNECTED ) ) + { + fAddToList = TRUE; + tokenPtr = (LPWSTR) ContextHandle->NdsRawDataBuffer; + tokenIter = 0; + + // + // Walk through buffer to see if the tree name already exists. + // + while ( tokenIter < ContextHandle->NdsRawDataCount ) + { + if ( !wcscmp( tokenPtr, pConnStatus->pszServerName ) ) + { + fAddToList = FALSE; + } + + tokenPtr = tokenPtr + wcslen( tokenPtr ) + 1; + tokenIter++; + } + } + + // + // Add the new tree name to end of buffer if needed. + // + if ( fAddToList ) + { + wcscpy( tokenPtr, pConnStatus->pszServerName ); + _wcsupr( tokenPtr ); + ContextHandle->NdsRawDataCount += 1; + } + + pConnStatus = (PCONN_STATUS) ( (DWORD) pConnStatus + + pConnStatus->dwTotalLength ); + } + + if ( pBuffer != NULL ) + { + LocalFree( pBuffer ); + pBuffer = NULL; + } + + if ( ContextHandle->NdsRawDataCount > 0 ) + { + // + // Set ResumeId to point to the first entry in buffer + // and have NdsRawDataCount set to the number + // of tree entries left in buffer (ie. substract 1) + // + ContextHandle->ResumeId = ContextHandle->NdsRawDataBuffer; + ContextHandle->NdsRawDataCount -= 1; + } + + return NO_ERROR; + } + + return WN_NO_MORE_ENTRIES; +} + + +DWORD +NwGetNextServerConnection( + OUT LPNW_ENUM_CONTEXT ContextHandle + ) +/*++ + +Routine Description: + + This function queries the redirector for bindery server connections + +Arguments: + + ContextHandle - Receives the name of the returned bindery server. + +Return Value: + + NO_ERROR - Successfully returned a server name. + + WN_NO_MORE_ENTRIES - No other server objects past the one + specified by CH->ResumeId exist. + +--*/ +{ +#if DBG + IF_DEBUG(ENUM) { + KdPrint(("NWWORKSTATION: NwGetNextServerConnection ResumeId %lu\n", + ContextHandle->ResumeId)); + } +#endif + + if ( ContextHandle->ResumeId == 0xFFFFFFFF && + ContextHandle->NdsRawDataBuffer == 0 && + ContextHandle->NdsRawDataCount == 0 ) + { + // + // Fill the buffer and point ResumeId to the last + // server entry name in it. NdsRawDataCount will be + // set to one less than the number of server names in buffer. + // + return GetConnectedBinderyServers( ContextHandle ); + } + + if ( ContextHandle->NdsRawDataBuffer != 0 && + ContextHandle->NdsRawDataCount > 0 ) + { + // + // Move ResumeId to point to the next entry in the buffer + // and decrement the NdsRawDataCount by one. Watch for case + // where we backed up to 0xFFFFFFFF. + // + if (ContextHandle->ResumeId == 0xFFFFFFFF) { + + // + // Reset to start of buffer. + // + ContextHandle->ResumeId = ContextHandle->NdsRawDataBuffer; + } + else { + + // + // Treat as pointer and advance as need. + // + ContextHandle->ResumeId = + ContextHandle->ResumeId + + ( ( wcslen( (LPWSTR) ContextHandle->ResumeId ) + 1 ) * + sizeof(WCHAR) ); + } + ContextHandle->NdsRawDataCount -= 1; + + return NO_ERROR; + } + + if ( ContextHandle->NdsRawDataBuffer != 0 && + ContextHandle->NdsRawDataCount == 0 ) + { + // + // We already have a buffer and processed all server names + // in it, and there is no more data to get. + // So free the memory used for the buffer and return + // WN_NO_MORE_ENTRIES to tell WinFile that we are done. + // + (void) LocalFree( (HLOCAL) ContextHandle->NdsRawDataBuffer ); + + ContextHandle->NdsRawDataBuffer = 0; + ContextHandle->NdsRawDataSize = 0; + + return WN_NO_MORE_ENTRIES; + } + + // + // Were done + // + return WN_NO_MORE_ENTRIES; +} + + +DWORD +GetTreeEntriesFromBindery( + OUT LPNW_ENUM_CONTEXT ContextHandle + ) +/*++ + +Routine Description: + + This function is a helper routine for the function NwGetNextNdsTreeEntry. + It allocates a buffer (if needed) to cache NDS tree names returned from + calls to the bindery. Since the bindery often returns duplicates of a + NDS tree name, this function checks to see if the tree name already + exist in the buffer before adding it to it if not present. + +Arguments: + + ContextHandle - Used to track cached bindery information and the + current tree name pointer in the cache buffer. + +Return Value: + + NO_ERROR - Successfully returned a NDS tree name and cache buffer. + + WN_NO_MORE_ENTRIES - No other NDS tree object past the one + specified by CH->ResumeId. + + ERROR_NOT_ENOUGH_MEMORY - Function was unable to allocate a buffer. + +++*/ +{ + NTSTATUS ntstatus = STATUS_SUCCESS; + SERVERNAME TreeName; + LPWSTR UTreeName = NULL; //Unicode tree name + DWORD tempDataId; + WORD ObjectType; + BYTE iter; + BYTE tokenIter; + LPWSTR tokenPtr; + BOOL fAddToList; + + // + // Check to see if we need to allocate a buffer for use + // + if ( ContextHandle->NdsRawDataBuffer == 0x00000000 ) + { + ContextHandle->NdsRawDataId = ContextHandle->ResumeId; + ContextHandle->NdsRawDataSize = EIGHT_KB; + ContextHandle->NdsRawDataBuffer = + (DWORD) LocalAlloc( LMEM_FIXED | LMEM_ZEROINIT, + ContextHandle->NdsRawDataSize ); + + if ( ContextHandle->NdsRawDataBuffer == 0 ) + { + KdPrint(("NWWORKSTATION: GetTreeEntriesFromBindery LocalAlloc failed %lu\n", + GetLastError())); + + ContextHandle->NdsRawDataSize = 0; + ContextHandle->NdsRawDataId = 0xFFFFFFFF; + + return ERROR_NOT_ENOUGH_MEMORY; + } + } + + // + // Repeatedly call bindery to fill buffer with NDS tree names until + // buffer is full. + // + while ( ntstatus == STATUS_SUCCESS ) + { + RtlZeroMemory( TreeName, sizeof( TreeName ) ); + + tempDataId = ContextHandle->NdsRawDataId; + + ntstatus = NwlibMakeNcp( + ContextHandle->TreeConnectionHandle, + FSCTL_NWR_NCP_E3H, // Bindery function + 58, // Max request packet size + 59, // Max response packet size + "bdwp|dwc", // Format string + 0x37, // Scan bindery object + ContextHandle->NdsRawDataId, // Previous ID + 0x278, // Directory server object + "*", // Wildcard to match all + &ContextHandle->NdsRawDataId, // Current ID + &ObjectType, // Ignore + TreeName // Currently returned NDS tree + ); + + // + // We got a tree name, clean it up (i.e. get rid of underscores ), + // and add it to buffer if unique. + // + if ( ntstatus == STATUS_SUCCESS ) + { + iter = 31; + + while ( TreeName[iter] == '_' && iter > 0 ) + { + iter--; + } + + TreeName[iter + 1] = '\0'; + + // + // Convert tree name to a UNICODE string and proccess it, + // else just skip it and move on to the next tree name. + // + if ( NwConvertToUnicode( &UTreeName, TreeName ) ) + { + tokenPtr = (LPWSTR) ContextHandle->NdsRawDataBuffer; + tokenIter = 0; + fAddToList = TRUE; + + // + // Walk through buffer to see if the tree name already exists. + // + while ( tokenIter < ContextHandle->NdsRawDataCount ) + { + if ( !wcscmp( tokenPtr, UTreeName ) ) + { + fAddToList = FALSE; + } + + tokenPtr = tokenPtr + wcslen( tokenPtr ) + 1; + tokenIter++; + } + + // + // Add the new tree name to end of buffer if needed. + // + if ( fAddToList ) + { + DWORD BytesNeededToAddTreeName = (wcslen(UTreeName)+1) * sizeof(WCHAR); + DWORD NumberOfBytesAvailable = ContextHandle->NdsRawDataBuffer + + ContextHandle->NdsRawDataSize - + (DWORD) tokenPtr; + + if ( BytesNeededToAddTreeName < NumberOfBytesAvailable ) + { + wcscpy( tokenPtr, UTreeName ); + ContextHandle->NdsRawDataCount += 1; + } + else + { + ContextHandle->NdsRawDataId = tempDataId; + ntstatus = ERROR_NOT_ENOUGH_MEMORY; + } + } + + (void) LocalFree((HLOCAL) UTreeName); + } + } + } + + // + // We are done filling buffer, and there are no more tree names + // to request. Set id to indicate last value. + // + if ( ntstatus == STATUS_NO_MORE_ENTRIES ) + { + ContextHandle->NdsRawDataId = 0xFFFFFFFF; + ntstatus = STATUS_SUCCESS; + } + + // + // We are done because the buffer is full. So we return NO_ERROR to + // indicate completion, and leave ContextHandle->NdsRawDataId as is + // to indicate where we left off. + // + if ( ntstatus == ERROR_NOT_ENOUGH_MEMORY ) + { + ntstatus = STATUS_SUCCESS; + } + + if ( ContextHandle->NdsRawDataCount == 0 ) + { + if ( ContextHandle->NdsRawDataBuffer ) + (void) LocalFree( (HLOCAL) ContextHandle->NdsRawDataBuffer ); + + ContextHandle->NdsRawDataBuffer = 0; + ContextHandle->NdsRawDataSize = 0; + ContextHandle->NdsRawDataId = 0xFFFFFFFF; + + return WN_NO_MORE_ENTRIES; + } + + if ( ContextHandle->NdsRawDataCount > 0 ) + { + // + // Set ResumeId to point to the first entry in buffer + // and have NdsRawDataCount set to the number + // of tree entries left in buffer (ie. substract 1) + // + ContextHandle->ResumeId = ContextHandle->NdsRawDataBuffer; + ContextHandle->NdsRawDataCount -= 1; + + return NO_ERROR; + } + + if ( ContextHandle->NdsRawDataBuffer ) + (void) LocalFree( (HLOCAL) ContextHandle->NdsRawDataBuffer ); + + ContextHandle->NdsRawDataBuffer = 0; + ContextHandle->NdsRawDataSize = 0; + ContextHandle->NdsRawDataId = 0xFFFFFFFF; + + return NwMapStatus( ntstatus ); +} + + +DWORD +NwGetNextNdsTreeEntry( + OUT LPNW_ENUM_CONTEXT ContextHandle + ) +/*++ + +Routine Description: + + This function uses an opened handle to the preferred server to + scan it bindery for all NDS tree objects. + +Arguments: + + ContextHandle - Receives the name of the returned NDS tree object + given the current preferred server connection and CH->ResumeId. + +Return Value: + + NO_ERROR - Successfully returned a NDS tree name. + + WN_NO_MORE_ENTRIES - No other NDS tree objects past the one + specified by CH->ResumeId exist. + +--*/ +{ +#if DBG + IF_DEBUG(ENUM) { + KdPrint(("NWWORKSTATION: NwGetNextNdsTreeEntry ResumeId %lu\n", + ContextHandle->ResumeId)); + } +#endif + + if ( ContextHandle->ResumeId == 0xFFFFFFFF && + ContextHandle->NdsRawDataBuffer == 0 && + ContextHandle->NdsRawDataCount == 0 ) + { + // + // Fill the buffer and point ResumeId to the last + // tree entry name in it. NdsRawDataCount will be + // set to one less than the number of tree names in buffer. + // + return GetTreeEntriesFromBindery( ContextHandle ); + } + + if ( ContextHandle->NdsRawDataBuffer != 0 && + ContextHandle->NdsRawDataCount > 0 ) + { + // + // Move ResumeId to point to the next entry in the buffer + // and decrement the NdsRawDataCount by one. Watch for case + // where we backed up to 0xFFFFFFFF. + // + if (ContextHandle->ResumeId == 0xFFFFFFFF) { + + // + // Reset to start of buffer. + // + ContextHandle->ResumeId = ContextHandle->NdsRawDataBuffer; + } + else { + + // + // Move ResumeId to point to the next entry in the buffer + // and decrement the NdsRawDataCount by one + // + ContextHandle->ResumeId = + ContextHandle->ResumeId + + ( ( wcslen( (LPWSTR) ContextHandle->ResumeId ) + 1 ) * + sizeof(WCHAR) ); + } + + ContextHandle->NdsRawDataCount -= 1; + + return NO_ERROR; + } + + if ( ContextHandle->NdsRawDataBuffer != 0 && + ContextHandle->NdsRawDataCount == 0 && + ContextHandle->NdsRawDataId != 0xFFFFFFFF ) + { + // + // We already have a buffer and processed all tree names + // in it, and there is more data in the bindery to get. + // So go get it and point ResumeId to the last tree + // entry name in the buffer and set NdsRawDataCount to + // one less than the number of tree names in buffer. + // + return GetTreeEntriesFromBindery( ContextHandle ); + } + + if ( ContextHandle->NdsRawDataBuffer != 0 && + ContextHandle->NdsRawDataCount == 0 && + ContextHandle->NdsRawDataId == 0xFFFFFFFF ) + { + // + // We already have a buffer and processed all tree names + // in it, and there is no more data in the bindery to get. + // So free the memory used for the buffer and return + // WN_NO_MORE_ENTRIES to tell WinFile that we are done. + // + (void) LocalFree( (HLOCAL) ContextHandle->NdsRawDataBuffer ); + + ContextHandle->NdsRawDataBuffer = 0; + ContextHandle->NdsRawDataSize = 0; + + return WN_NO_MORE_ENTRIES; + } + + // + // We should never hit this area! + // + return WN_NO_MORE_ENTRIES; +} + + +DWORD +NwGetNextVolumeEntry( + IN HANDLE ServerConnection, + IN DWORD NextVolumeNumber, + OUT LPSTR VolumeName + ) +/*++ + +Routine Description: + + This function lists the volumes on the server specified by + an opened tree connection handle to the server. + +Arguments: + + ServerConnection - Supplies the tree connection handle to the + server to enumerate volumes from. + + NextVolumeNumber - Supplies the volume number which to look + up the name. + + VolumeName - Receives the name of the volume associated with + NextVolumeNumber. + +Return Value: + + NO_ERROR - Successfully gotten the volume name. + + WN_NO_MORE_ENTRIES - No other volume name associated with the + specified volume number. + +--*/ +{ + NTSTATUS ntstatus; + +#if DBG + IF_DEBUG(ENUM) { + KdPrint(("NWWORKSTATION: NwGetNextVolumeEntry volume number %lu\n", + NextVolumeNumber)); + } +#endif + + ntstatus = NwlibMakeNcp( + ServerConnection, + FSCTL_NWR_NCP_E2H, // Directory function + 4, // Max request packet size + 19, // Max response packet size + "bb|p", // Format string + 0x6, // Get volume name + (BYTE) NextVolumeNumber, // Previous ID + VolumeName // Currently returned server + ); + + return NwMapStatus(ntstatus); +} + + +DWORD +NwRdrLogonUser( + IN PLUID LogonId, + IN LPWSTR UserName, + IN DWORD UserNameSize, + IN LPWSTR Password OPTIONAL, + IN DWORD PasswordSize, + IN LPWSTR PreferredServer OPTIONAL, + IN DWORD PreferredServerSize + ) +/*++ + +Routine Description: + + This function tells the redirector the user logon credential. + +Arguments: + + UserName - Supplies the user name. + + UserNameSize - Supplies the size in bytes of the user name string without + the NULL terminator. + + Password - Supplies the password. + + PasswordSize - Supplies the size in bytes of the password string without + the NULL terminator. + + PreferredServer - Supplies the preferred server name. + + PreferredServerSize - Supplies the size in bytes of the preferred server + string without the NULL terminator. + +Return Value: + + NO_ERROR or reason for failure. + +--*/ +{ + DWORD status; + + PNWR_REQUEST_PACKET Rrp; // Redirector request packet + + DWORD RrpSize = sizeof(NWR_REQUEST_PACKET) + + UserNameSize + + PasswordSize + + PreferredServerSize; + LPBYTE Dest; + BYTE lpReplicaAddress[sizeof(TDI_ADDRESS_IPX)]; + DWORD ReplicaAddressSize = 0; + + +#if DBG + IF_DEBUG(LOGON) { + BYTE PW[128]; + + + RtlZeroMemory(PW, sizeof(PW)); + + if (PasswordSize > (sizeof(PW) - 1)) { + memcpy(PW, Password, sizeof(PW) - 1); + } + else { + memcpy(PW, Password, PasswordSize); + } + + KdPrint(("NWWORKSTATION: NwRdrLogonUser: UserName %ws\n", UserName)); + KdPrint((" Password %ws\n", PW)); + if ( PreferredServer ) + KdPrint((" Server %ws\n", PreferredServer )); + } +#endif + + if ( PreferredServer && PreferredServer[0] == TREECHAR ) + { + WCHAR TreeName[MAX_NDS_NAME_CHARS + 1]; + LPWSTR lpTemp; + + // + // Find the nearest dir server for the tree that the user wants to + // connect to. + // + + wcscpy( TreeName, PreferredServer + 1 ); + lpTemp = wcschr( TreeName, L'\\' ); + lpTemp[0] = L'\0'; + + GetNearestDirServer( TreeName, + &ReplicaAddressSize, + lpReplicaAddress ); + + RrpSize += ReplicaAddressSize; + } + + // + // Allocate the request packet + // + if ((Rrp = (PVOID) LocalAlloc( + LMEM_ZEROINIT, + RrpSize + )) == NULL) { + + KdPrint(("NWWORKSTATION: NwRdrLogonUser LocalAlloc failed %lu\n", + GetLastError())); + return ERROR_NOT_ENOUGH_MEMORY; + } + + // + // Tell the redirector the user logon credential. + // + Rrp->Version = REQUEST_PACKET_VERSION; + + RtlCopyLuid(&(Rrp->Parameters.Logon.LogonId), LogonId); + +#if DBG + IF_DEBUG(LOGON) { + KdPrint(("NWWORKSTATION: NwRdrLogonUser passing to Rdr logon ID %lu %lu\n", + *LogonId, *((PULONG) ((DWORD) LogonId + sizeof(ULONG))))); + } +#endif + + Rrp->Parameters.Logon.UserNameLength = UserNameSize; + Rrp->Parameters.Logon.PasswordLength = PasswordSize; + Rrp->Parameters.Logon.ServerNameLength = PreferredServerSize; + Rrp->Parameters.Logon.ReplicaAddrLength = ReplicaAddressSize; + + memcpy(Rrp->Parameters.Logon.UserName, UserName, UserNameSize); + Dest = (LPBYTE) ((DWORD) Rrp->Parameters.Logon.UserName + UserNameSize); + + if (PasswordSize > 0) + { + memcpy(Dest, Password, PasswordSize); + Dest = (LPBYTE) ((DWORD) Dest + PasswordSize); + } + + if (PreferredServerSize > 0) + { + memcpy(Dest, PreferredServer, PreferredServerSize); + + if (ReplicaAddressSize > 0) + { + Dest = (LPBYTE) ((DWORD) Dest + PreferredServerSize); + memcpy(Dest, lpReplicaAddress, ReplicaAddressSize); + } + } + + status = NwRedirFsControl( + RedirDeviceHandle, + FSCTL_NWR_LOGON, + Rrp, + RrpSize, + NULL, // No logon script in this release + 0, + NULL + ); + + RtlZeroMemory(Rrp, RrpSize); // Clear the password + (void) LocalFree((HLOCAL) Rrp); + + return status; +} + + +VOID +NwRdrChangePassword( + IN PNWR_REQUEST_PACKET Rrp + ) +/*++ + +Routine Description: + + This function tells the redirector the new password for a user on + a particular server. + +Arguments: + + Rrp - Supplies the username, new password and servername. + + RrpSize - Supplies the size of the request packet. + +Return Value: + + None. + +--*/ +{ + + // + // Tell the redirector the user new password. + // + Rrp->Version = REQUEST_PACKET_VERSION; + + (void) NwRedirFsControl( + RedirDeviceHandle, + FSCTL_NWR_CHANGE_PASS, + Rrp, + sizeof(NWR_REQUEST_PACKET) + + Rrp->Parameters.ChangePass.UserNameLength + + Rrp->Parameters.ChangePass.PasswordLength + + Rrp->Parameters.ChangePass.ServerNameLength, + NULL, + 0, + NULL + ); + +} + + +DWORD +NwRdrSetInfo( + IN DWORD PrintOption, + IN DWORD PacketBurstSize, + IN LPWSTR PreferredServer OPTIONAL, + IN DWORD PreferredServerSize, + IN LPWSTR ProviderName OPTIONAL, + IN DWORD ProviderNameSize + ) +/*++ + +Routine Description: + + This function passes some workstation configuration and current user's + preference to the redirector. This includes the network provider name, the + packet burst size, the user's selected preferred server and print option. + +Arguments: + + PrintOption - The current user's print option + + PacketBurstSize - The packet burst size stored in the registry + + PreferredServer - The preferred server the current user selected + PreferredServerSize - Supplies the size in bytes of the preferred server + string without the NULL terminator. + + ProviderName - Supplies the provider name. + ProviderNameSize - Supplies the size in bytes of the provider name + string without the NULL terminator. + + +Return Value: + + NO_ERROR or reason for failure. + +--*/ +{ + DWORD status; + + PNWR_REQUEST_PACKET Rrp; // Redirector request packet + + DWORD RrpSize = sizeof(NWR_REQUEST_PACKET) + + PreferredServerSize + + ProviderNameSize; + + LPBYTE Dest; + + // + // Allocate the request packet + // + if ((Rrp = (PVOID) LocalAlloc( + LMEM_ZEROINIT, + RrpSize + )) == NULL) { + + KdPrint(("NWWORKSTATION: NwRdrSetInfo LocalAlloc failed %lu\n", + GetLastError())); + return ERROR_NOT_ENOUGH_MEMORY; + } + + Rrp->Version = REQUEST_PACKET_VERSION; + + Rrp->Parameters.SetInfo.PrintOption = PrintOption; + Rrp->Parameters.SetInfo.MaximumBurstSize = PacketBurstSize; + + Rrp->Parameters.SetInfo.PreferredServerLength = PreferredServerSize; + Rrp->Parameters.SetInfo.ProviderNameLength = ProviderNameSize; + + if (ProviderNameSize > 0) { + memcpy( Rrp->Parameters.SetInfo.PreferredServer, + PreferredServer, PreferredServerSize); + } + + Dest = (LPBYTE) ((DWORD) Rrp->Parameters.SetInfo.PreferredServer + + PreferredServerSize); + + if (ProviderNameSize > 0) { + memcpy(Dest, ProviderName, ProviderNameSize); + } + + status = NwRedirFsControl( + RedirDeviceHandle, + FSCTL_NWR_SET_INFO, + Rrp, + RrpSize, + NULL, + 0, + NULL + ); + + (void) LocalFree((HLOCAL) Rrp); + + + if ( status != NO_ERROR ) + { + KdPrint(("NwRedirFsControl: FSCTL_NWR_SET_INFO failed with %d\n", + status )); + } + + return status; +} + + +DWORD +NwRdrLogoffUser( + IN PLUID LogonId + ) +/*++ + +Routine Description: + + This function asks the redirector to log off the interactive user. + +Arguments: + + None. + +Return Value: + + NO_ERROR or reason for failure. + +--*/ +{ + DWORD status; + NWR_REQUEST_PACKET Rrp; // Redirector request packet + + + // + // Tell the redirector to logoff user. + // + Rrp.Version = REQUEST_PACKET_VERSION; + + RtlCopyLuid(&Rrp.Parameters.Logoff.LogonId, LogonId); + + status = NwRedirFsControl( + RedirDeviceHandle, + FSCTL_NWR_LOGOFF, + &Rrp, + sizeof(NWR_REQUEST_PACKET), + NULL, + 0, + NULL + ); + + return status; +} + + +DWORD +NwConnectToServer( + IN LPWSTR ServerName + ) +/*++ + +Routine Description: + + This function opens a handle to \Device\Nwrdr\ServerName, given + ServerName, and then closes the handle if the open was successful. + It is to validate that the current user credential can access + the server. + +Arguments: + + ServerName - Supplies the name of the server to validate the + user credential. + +Return Value: + + NO_ERROR or reason for failure. + +--*/ +{ + DWORD status; + UNICODE_STRING ServerStr; + HANDLE ServerHandle; + + + + ServerStr.MaximumLength = (wcslen(ServerName) + 2) * + sizeof(WCHAR) + // \ServerName0 + RedirDeviceName.Length; // \Device\Nwrdr + + if ((ServerStr.Buffer = (PWSTR) LocalAlloc( + LMEM_ZEROINIT, + (UINT) ServerStr.MaximumLength + )) == NULL) { + return ERROR_NOT_ENOUGH_MEMORY; + } + + // + // Copy \Device\NwRdr + // + RtlCopyUnicodeString(&ServerStr, &RedirDeviceName); + + // + // Concatenate \ServerName + // + wcscat(ServerStr.Buffer, L"\\"); + ServerStr.Length += sizeof(WCHAR); + + wcscat(ServerStr.Buffer, ServerName); + ServerStr.Length += (USHORT) (wcslen(ServerName) * sizeof(WCHAR)); + + + status = NwOpenCreateConnection( + &ServerStr, + NULL, + NULL, + ServerName, + SYNCHRONIZE | FILE_WRITE_DATA, + FILE_OPEN, + FILE_SYNCHRONOUS_IO_NONALERT, + RESOURCETYPE_DISK, + &ServerHandle, + NULL + ); + + if (status == ERROR_FILE_NOT_FOUND) { + status = ERROR_BAD_NETPATH; + } + + (void) LocalFree((HLOCAL) ServerStr.Buffer); + + if (status == NO_ERROR || status == NW_PASSWORD_HAS_EXPIRED) { + (void) NtClose(ServerHandle); + } + + return status; +} + +DWORD +NWPGetConnectionStatus( + IN LPWSTR pszRemoteName, + IN OUT PDWORD ResumeKey, + OUT LPBYTE Buffer, + IN DWORD BufferSize, + OUT PDWORD BytesNeeded, + OUT PDWORD EntriesRead +) +{ + NTSTATUS ntstatus = STATUS_SUCCESS; + HANDLE handleRdr = NULL; + OBJECT_ATTRIBUTES ObjectAttributes; + IO_STATUS_BLOCK IoStatusBlock; + UNICODE_STRING uRdrName; + WCHAR RdrPrefix[] = L"\\Device\\NwRdr\\*"; + + PNWR_REQUEST_PACKET RequestPacket = NULL; + DWORD RequestPacketSize = 0; + DWORD dwRemoteNameLen = 0; + + // + // Set up the object attributes. + // + + RtlInitUnicodeString( &uRdrName, RdrPrefix ); + + InitializeObjectAttributes( &ObjectAttributes, + &uRdrName, + OBJ_CASE_INSENSITIVE, + NULL, + NULL ); + + ntstatus = NtOpenFile( &handleRdr, + SYNCHRONIZE | FILE_LIST_DIRECTORY, + &ObjectAttributes, + &IoStatusBlock, + FILE_SHARE_VALID_FLAGS, + FILE_SYNCHRONOUS_IO_NONALERT ); + + if ( !NT_SUCCESS(ntstatus) ) + goto CleanExit; + + dwRemoteNameLen = pszRemoteName? wcslen(pszRemoteName)*sizeof(WCHAR) : 0; + + RequestPacketSize = sizeof( NWR_REQUEST_PACKET ) + dwRemoteNameLen; + + RequestPacket = (PNWR_REQUEST_PACKET) LocalAlloc( LMEM_ZEROINIT, + RequestPacketSize ); + + if ( RequestPacket == NULL ) + { + ntstatus = STATUS_NO_MEMORY; + goto CleanExit; + } + + // + // Fill out the request packet for FSCTL_NWR_GET_CONN_STATUS. + // + + RequestPacket->Parameters.GetConnStatus.ResumeKey = *ResumeKey; + + RequestPacket->Version = REQUEST_PACKET_VERSION; + RequestPacket->Parameters.GetConnStatus.ConnectionNameLength = dwRemoteNameLen; + + RtlCopyMemory( &(RequestPacket->Parameters.GetConnStatus.ConnectionName[0]), + pszRemoteName, + dwRemoteNameLen ); + + ntstatus = NtFsControlFile( handleRdr, + NULL, + NULL, + NULL, + &IoStatusBlock, + FSCTL_NWR_GET_CONN_STATUS, + (PVOID) RequestPacket, + RequestPacketSize, + (PVOID) Buffer, + BufferSize ); + + if ( NT_SUCCESS( ntstatus )) + ntstatus = IoStatusBlock.Status; + + *EntriesRead = RequestPacket->Parameters.GetConnStatus.EntriesReturned; + *ResumeKey = RequestPacket->Parameters.GetConnStatus.ResumeKey; + *BytesNeeded = RequestPacket->Parameters.GetConnStatus.BytesNeeded; + +CleanExit: + + if ( handleRdr != NULL ) + NtClose( handleRdr ); + + if ( RequestPacket != NULL ) + LocalFree( RequestPacket ); + + return RtlNtStatusToDosError( ntstatus ); +} + +DWORD +NwGetConnectionStatus( + IN LPWSTR pszRemoteName, + OUT PDWORD ResumeKey, + OUT LPBYTE *Buffer, + OUT PDWORD EntriesRead +) +{ + DWORD err = NO_ERROR; + DWORD dwBytesNeeded = 0; + DWORD dwBufferSize = TWO_KB; + + *Buffer = NULL; + *EntriesRead = 0; + + do { + + *Buffer = (LPBYTE) LocalAlloc( LMEM_ZEROINIT, dwBufferSize ); + + if ( *Buffer == NULL ) + return ERROR_NOT_ENOUGH_MEMORY; + + err = NWPGetConnectionStatus( pszRemoteName, + ResumeKey, + *Buffer, + dwBufferSize, + &dwBytesNeeded, + EntriesRead ); + + if ( err == ERROR_INSUFFICIENT_BUFFER ) + { + dwBufferSize = dwBytesNeeded + EXTRA_BYTES; + LocalFree( *Buffer ); + *Buffer = NULL; + } + + } while ( err == ERROR_INSUFFICIENT_BUFFER ); + + if ( err == ERROR_INVALID_PARAMETER ) // not attached + { + err = NO_ERROR; + *EntriesRead = 0; + } + + return err; +} + +VOID +GetNearestDirServer( + IN LPWSTR TreeName, + OUT LPDWORD lpdwReplicaAddressSize, + OUT LPBYTE lpReplicaAddress + ) +{ + WCHAR Buffer[BUFFSIZE]; + PWSAQUERYSETW Query = (PWSAQUERYSETW)Buffer; + HANDLE hRnr; + DWORD dwQuerySize = BUFFSIZE; + GUID gdService = SVCID_NETWARE(0x278); + WSADATA wsaData; + WCHAR ServiceInstanceName[] = L"*"; + + WSAStartup(MAKEWORD(1, 1), &wsaData); + + memset(Query, 0, sizeof(*Query)); + + // + // putting a "*" in the lpszServiceInstanceName causes + // the query to look for all server instances. Putting a + // specific name in here will search only for instance of + // that name. If you have a specific name to look for, + // put a pointer to the name here. + // + Query->lpszServiceInstanceName = ServiceInstanceName; + Query->dwNameSpace = NS_SAP; + Query->dwSize = sizeof(*Query); + Query->lpServiceClassId = &gdService; + + // + // Find the servers. The flags indicate: + // LUP_NEAREST: look for nearest servers + // LUP_DEEP : if none are found on the local segement look + // for server using a general query + // LUP_RETURN_NAME: return the name + // LUP_RETURN_ADDR: return the server address + // + // if only servers on the local segment are acceptable, omit + // setting LUP_DEEP + // + if( WSALookupServiceBegin( Query, + LUP_NEAREST | + LUP_DEEP | + LUP_RETURN_NAME | + LUP_RETURN_ADDR, + &hRnr ) == SOCKET_ERROR ) + { + // + // Something went wrong, return no address. The redirector will + // have to come up with a dir server on its own. + // + *lpdwReplicaAddressSize = 0; + return ; + } + else + { + // + // Ready to actually look for one of the suckers ... + // + Query->dwSize = BUFFSIZE; + + while( WSALookupServiceNext( hRnr, + 0, + &dwQuerySize, + Query ) == NO_ERROR ) + { + // + // Found a dir server, now see if it is a server for the NDS tree + // TreeName. + // + if ( NwpCompareTreeNames( Query->lpszServiceInstanceName, + TreeName ) ) + { + *lpdwReplicaAddressSize = sizeof(TDI_ADDRESS_IPX); + memcpy( lpReplicaAddress, + Query->lpcsaBuffer->RemoteAddr.lpSockaddr->sa_data, + sizeof(TDI_ADDRESS_IPX) ); + + WSALookupServiceEnd(hRnr); + return ; + } + } + + // + // Could not find a dir server, return no address. The redirector will + // have to come up with a dir server on its own. + // + *lpdwReplicaAddressSize = 0; + WSALookupServiceEnd(hRnr); + } +} + +BOOL +NwpCompareTreeNames( + LPWSTR lpServiceInstanceName, + LPWSTR lpTreeName + ) +{ + DWORD iter = 31; + + while ( lpServiceInstanceName[iter] == '_' && iter > 0 ) + { + iter--; + } + + lpServiceInstanceName[iter + 1] = '\0'; + + if ( !_wcsicmp( lpServiceInstanceName, lpTreeName ) ) + { + return TRUE; + } + + return FALSE; +} + + |