mirror of https://github.com/tongzx/nt5src
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.
3133 lines
85 KiB
3133 lines
85 KiB
//+----------------------------------------------------------------------------
|
|
//
|
|
// Copyright (C) 1996, Microsoft Corporation
|
|
//
|
|
// File: srv.c
|
|
//
|
|
// Contents: Support for interacting with the SMB server.
|
|
//
|
|
// Classes: None
|
|
//
|
|
// Functions: DfsSrvFsctrl
|
|
// DfsFsctrlTranslatePath
|
|
// DfsFsctrlGetReferrals
|
|
// DfsFsctrlIsShareInDfs
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "dfsprocs.h"
|
|
#include "srv.h"
|
|
|
|
#include "smbtypes.h"
|
|
#include "smbtrans.h"
|
|
|
|
#include "know.h"
|
|
#include "ftdfs.h"
|
|
|
|
#include "sitesup.h"
|
|
#include "ipsup.h"
|
|
#include "spcsup.h"
|
|
#include "dfslpc.h"
|
|
#include "fsctrl.h"
|
|
#include "dfswml.h"
|
|
|
|
#include <ntlsa.h>
|
|
|
|
#include <netevent.h>
|
|
|
|
#define Dbg DEBUG_TRACE_REFERRALS
|
|
#define MAXIMUM_DFS_REFERRALS 4096
|
|
|
|
DFS_IPADDRESS ZeroIpAddress = {0};
|
|
|
|
NTSTATUS
|
|
DfspFormPrefix(
|
|
IN PDFS_LOCAL_VOL_ENTRY LvEntry,
|
|
IN PUNICODE_STRING ParentPath,
|
|
IN PUNICODE_STRING DfsPathName,
|
|
IN OUT PUNICODE_STRING Prefix);
|
|
|
|
ULONG
|
|
DfspGetV1ReferralSize(
|
|
IN PDFS_PKT_ENTRY PktEntry,
|
|
IN PUNICODE_STRING MachineName);
|
|
|
|
ULONG
|
|
DfspGetV2ReferralSize(
|
|
IN PDFS_PKT_ENTRY PktEntry,
|
|
IN PUNICODE_STRING MachineName);
|
|
|
|
ULONG
|
|
DfspGetV3ReferralSize(
|
|
IN PDFS_PKT_ENTRY PktEntry,
|
|
IN PUNICODE_STRING MachineName);
|
|
|
|
VOID
|
|
DfspGetV1Referral(
|
|
IN PDFS_PKT_ENTRY PktEntry,
|
|
IN PUNICODE_STRING MachineName,
|
|
OUT PRESP_GET_DFS_REFERRAL Ref);
|
|
|
|
NTSTATUS
|
|
DfspGetV2Referral(
|
|
IN PDFS_PKT_ENTRY PktEntry,
|
|
IN PUNICODE_STRING MachineName,
|
|
IN ULONG BufferSize,
|
|
OUT PRESP_GET_DFS_REFERRAL Ref,
|
|
OUT PULONG ReferralSize);
|
|
|
|
NTSTATUS
|
|
DfspGetV3Referral(
|
|
IN PDFS_PKT_ENTRY PktEntry,
|
|
IN PUNICODE_STRING MachineName,
|
|
IN PDFS_IP_INFO pIpInfo,
|
|
IN ULONG BufferSize,
|
|
OUT PRESP_GET_DFS_REFERRAL Ref,
|
|
OUT PULONG ReferralSize);
|
|
|
|
NTSTATUS
|
|
DfspGetOneV3SpecialReferral(
|
|
IN PDFS_SPECIAL_INFO pSpcInfo,
|
|
IN PUNICODE_STRING Name,
|
|
IN PDFS_IP_INFO pIpInfo,
|
|
OUT PRESP_GET_DFS_REFERRAL Ref,
|
|
IN ULONG MaximumSize,
|
|
PULONG ReturnedSize);
|
|
|
|
NTSTATUS
|
|
DfspGetAllV3SpecialReferral(
|
|
IN PDFS_IP_INFO pIpInfo,
|
|
OUT PRESP_GET_DFS_REFERRAL Ref,
|
|
IN ULONG MaximumSize,
|
|
PULONG ReturnedSize);
|
|
|
|
NTSTATUS
|
|
DfspGetV3FtDfsReferral(
|
|
IN PUNICODE_STRING DomainName,
|
|
IN PUNICODE_STRING ShareName,
|
|
IN PDFS_IP_INFO pIpInfo,
|
|
OUT PRESP_GET_DFS_REFERRAL Ref,
|
|
IN ULONG MaximumSize,
|
|
PULONG ReturnedSize);
|
|
|
|
NTSTATUS
|
|
DfsFsctrlTranslatePath(
|
|
IN PVOID InputBuffer,
|
|
IN ULONG InputBufferLength);
|
|
|
|
NTSTATUS
|
|
DfsFsctrlGetReferrals(
|
|
IN PVOID InputBuffer,
|
|
IN ULONG InputBufferLength,
|
|
OUT PVOID OutputBuffer,
|
|
IN ULONG OutputBufferLength,
|
|
OUT PULONG ReturnedSize);
|
|
|
|
NTSTATUS
|
|
DfsFsctrlIsShareInDfs(
|
|
IN PVOID InputBuffer,
|
|
IN ULONG InputBufferLength);
|
|
|
|
NTSTATUS
|
|
DfsFsctrlFindShare(
|
|
IN PVOID InputBuffer,
|
|
IN ULONG InputBufferLength);
|
|
|
|
VOID
|
|
SrvShuffle(
|
|
PDFS_REFERRAL_LIST pRefList,
|
|
LONG nStart,
|
|
LONG nEnd);
|
|
|
|
ULONG
|
|
DfsIpOrdering(
|
|
IN PDFS_IP_INFO pIpInfo,
|
|
IN ULONG RefCount,
|
|
IN PDFS_REFERRAL_LIST pRefList);
|
|
|
|
|
|
PDFS_SPECIAL_INFO
|
|
DfspLookupSpcEntry(
|
|
IN PUNICODE_STRING SpecialName);
|
|
|
|
BOOLEAN
|
|
DfspIsSpecialShare(
|
|
PUNICODE_STRING ShareName);
|
|
|
|
#define UNICODE_STRING_STRUCT(s) \
|
|
{sizeof(s) - sizeof(WCHAR), sizeof(s) - sizeof(WCHAR), (s)}
|
|
|
|
static UNICODE_STRING SpecialShares[] = {
|
|
UNICODE_STRING_STRUCT(L"SYSVOL"),
|
|
UNICODE_STRING_STRUCT(L"NETLOGON"),
|
|
UNICODE_STRING_STRUCT(L"DEBUG")
|
|
};
|
|
|
|
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text( PAGE, DfsSrvFsctrl )
|
|
#pragma alloc_text( PAGE, DfsFsctrlTranslatePath )
|
|
#pragma alloc_text( PAGE, DfsFsctrlGetReferrals )
|
|
#pragma alloc_text( PAGE, DfsFsctrlIsShareInDfs )
|
|
#pragma alloc_text( PAGE, DfsFsctrlFindShare )
|
|
#pragma alloc_text( PAGE, DfsIpOrdering )
|
|
|
|
#pragma alloc_text( PAGE, DfspFormPrefix )
|
|
#pragma alloc_text( PAGE, DfspGetV1ReferralSize )
|
|
#pragma alloc_text( PAGE, DfspGetV2ReferralSize )
|
|
#pragma alloc_text( PAGE, DfspGetV3ReferralSize )
|
|
#pragma alloc_text( PAGE, DfspGetV1Referral )
|
|
#pragma alloc_text( PAGE, DfspGetV2Referral )
|
|
#pragma alloc_text( PAGE, DfspGetV3Referral )
|
|
#pragma alloc_text( PAGE, DfspGetAllV3SpecialReferral )
|
|
#pragma alloc_text( PAGE, DfspGetOneV3SpecialReferral )
|
|
#pragma alloc_text( PAGE, DfspLookupSpcEntry )
|
|
|
|
#endif // ALLOC_PRAGMA
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: DfsSrvFsctrl
|
|
//
|
|
// Synopsis: Process fsctrls used by the server to communicate with Dfs.
|
|
//
|
|
// Arguments: [IoControlCode] -- The fsctrl code.
|
|
// [InputBuffer] -- Input buffer for fsctrl.
|
|
// [InputBufferLength] -- Length in bytes of InputBuffer
|
|
// [OutputBuffer] -- Output buffer for fsctrl.
|
|
// [OutputBufferLength] -- Length in bytes of OutputBuffer
|
|
// [IoStatus] -- place to return status of fsctrl.
|
|
//
|
|
// Returns: [STATUS_SUCCESS] -- Fsctrl handled successfully.
|
|
//
|
|
// [STATUS_INVALID_DEVICE_REQUEST] -- Unrecognized fsctrl code.
|
|
//
|
|
// Status returned by the appropriate fsctrl handler.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
VOID DfsSrvFsctrl(
|
|
IN ULONG IoControlCode,
|
|
IN PVOID InputBuffer,
|
|
IN ULONG InputBufferLength,
|
|
OUT PVOID OutputBuffer,
|
|
IN ULONG OutputBufferLength,
|
|
OUT PIO_STATUS_BLOCK IoStatus)
|
|
{
|
|
|
|
KPROCESSOR_MODE PreviousMode;
|
|
ULONG returnedSize = 0;
|
|
|
|
if (IoControlCode == FSCTL_DFS_IS_ROOT) {
|
|
if (DfsData.MachineState == DFS_ROOT_SERVER) {
|
|
IoStatus->Status = STATUS_SUCCESS;
|
|
} else {
|
|
IoStatus->Status = STATUS_PATH_NOT_COVERED;
|
|
DFS_TRACE_HIGH(ERROR, DfsSrvFsctrl_Error1, LOGSTATUS(IoStatus->Status));
|
|
}
|
|
return;
|
|
} else if (IoControlCode == FSCTL_DFS_IS_SHARE_IN_DFS) {
|
|
IoStatus->Status = DfsFsctrlIsShareInDfs(
|
|
InputBuffer,
|
|
InputBufferLength);
|
|
return;
|
|
}
|
|
|
|
PreviousMode = ExGetPreviousMode();
|
|
if (PreviousMode != KernelMode) {
|
|
IoStatus->Status = STATUS_INVALID_PARAMETER;
|
|
DFS_TRACE_HIGH(ERROR, DfsSrvFsctrl_Error2, LOGSTATUS(IoStatus->Status));
|
|
return;
|
|
}
|
|
|
|
switch (IoControlCode) {
|
|
case FSCTL_DFS_TRANSLATE_PATH:
|
|
IoStatus->Status = DfsFsctrlTranslatePath(
|
|
InputBuffer,
|
|
InputBufferLength);
|
|
break;
|
|
|
|
case FSCTL_DFS_GET_REFERRALS:
|
|
IoStatus->Status = DfsFsctrlGetReferrals(
|
|
InputBuffer,
|
|
InputBufferLength,
|
|
OutputBuffer,
|
|
OutputBufferLength,
|
|
&returnedSize);
|
|
|
|
IoStatus->Information = returnedSize;
|
|
|
|
break;
|
|
|
|
case FSCTL_DFS_REPORT_INCONSISTENCY:
|
|
IoStatus->Status = STATUS_SUCCESS;
|
|
break;
|
|
|
|
case FSCTL_DFS_FIND_SHARE:
|
|
IoStatus->Status = DfsFsctrlFindShare(
|
|
InputBuffer,
|
|
InputBufferLength);
|
|
break;
|
|
|
|
default:
|
|
IoStatus->Status = STATUS_INVALID_DEVICE_REQUEST;
|
|
DFS_TRACE_HIGH(ERROR, DfsSrvFsctrl_Error3, LOGSTATUS(IoStatus->Status));
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: DfsFsctrlTranslatePath
|
|
//
|
|
// Synopsis: Given a share path of the form \??\X:\foo\bar
|
|
// and a full-blown Dfs prefix of the form \Root\Dfs\frum\bump,
|
|
// this routine will determine the path relative to
|
|
// \??\X:\foo\bar that maps to \Root\Dfs\frum\bump.
|
|
//
|
|
// In the above example, if \??\X:\foo\bar maps to
|
|
// \Root\Dfs\frum, then this routine will return \bump.
|
|
//
|
|
// Since this fsctrl is intended for use by the server, the
|
|
// input parameter \Root\Dfs\frum\bump will itself be truncated
|
|
// to indicate the relative path.
|
|
//
|
|
// Arguments: [InputBuffer] -- Pointer to DFS_TRANSLATE_PATH_ARG.
|
|
//
|
|
// [InputBufferLength] -- Length in bytes of InputBuffer
|
|
//
|
|
// Returns: [STATUS_SUCCESS] -- Input path successfully translated.
|
|
//
|
|
// [STATUS_PATH_NOT_COVERED] -- The input path crossed a junction
|
|
// point, or is not in the dfs at all.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NTSTATUS
|
|
DfsFsctrlTranslatePath(
|
|
IN PVOID InputBuffer,
|
|
IN ULONG InputBufferLength)
|
|
{
|
|
PDFS_TRANSLATE_PATH_ARG arg = (PDFS_TRANSLATE_PATH_ARG) InputBuffer;
|
|
PDFS_PKT pkt;
|
|
PDFS_PKT_ENTRY pktEntry, shortPktEntry;
|
|
PUNICODE_PREFIX_TABLE_ENTRY lvPrefix;
|
|
PDFS_LOCAL_VOL_ENTRY lvEntry;
|
|
UNICODE_STRING lvName;
|
|
WCHAR nameBuffer[MAX_PATH];
|
|
UNICODE_STRING prefix;
|
|
UNICODE_STRING lastComponent;
|
|
UNICODE_STRING remPath, shortRemPath;
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
|
|
DebugTrace(+1, Dbg, "DfsFsctrlTranslatePath entered\n", 0);
|
|
|
|
DFS_TRACE_NORM(EVENT, DfsFsctrlTranslatePath_Start,
|
|
LOGSTATUS(status)
|
|
LOGUSTR(arg->SubDirectory)
|
|
LOGUSTR(arg->DfsPathName)
|
|
LOGUSTR(arg->ParentPathName));
|
|
|
|
lvName = arg->SubDirectory;
|
|
|
|
DebugTrace( 0, Dbg, "lvName=%wZ\n", &lvName);
|
|
|
|
RtlZeroMemory( &prefix, sizeof(prefix) );
|
|
|
|
//
|
|
// Lookup the localVol in the local volume prefix table.
|
|
//
|
|
|
|
pkt = _GetPkt();
|
|
|
|
PktAcquireExclusive( pkt, TRUE );
|
|
|
|
lvPrefix = DfsFindUnicodePrefix(
|
|
&pkt->LocalVolTable,
|
|
&lvName,
|
|
&remPath);
|
|
|
|
if (lvPrefix != NULL) {
|
|
|
|
DebugTrace( 0, Dbg, "found lvPrefix=0x%x\n", lvPrefix);
|
|
|
|
lvEntry = CONTAINING_RECORD(
|
|
lvPrefix,
|
|
DFS_LOCAL_VOL_ENTRY,
|
|
PrefixTableEntry);
|
|
|
|
if (lvEntry->LocalPath.Length == lvName.Length) {
|
|
|
|
DebugTrace( 0, Dbg, "found path for a local vol\n", 0);
|
|
DebugTrace( 0, Dbg, "lvEntry->LocalPath=%wZ\n", &lvEntry->LocalPath);
|
|
|
|
|
|
//
|
|
// Found an exact match for a local volume. Next, we check
|
|
// whether the Dfs path crosses an exit point.
|
|
//
|
|
|
|
prefix.Length = 0;
|
|
prefix.MaximumLength = sizeof( nameBuffer );
|
|
prefix.Buffer = nameBuffer;
|
|
|
|
status = DfspFormPrefix(
|
|
lvEntry,
|
|
&arg->ParentPathName,
|
|
&arg->DfsPathName,
|
|
&prefix);
|
|
|
|
if (arg->Flags & DFS_TRANSLATE_STRIP_LAST_COMPONENT) {
|
|
|
|
DebugTrace( 0, Dbg, "prefix=%wZ\n", &prefix);
|
|
|
|
lastComponent = prefix;
|
|
|
|
RemoveLastComponent( &lastComponent, &prefix );
|
|
|
|
lastComponent.Length -= prefix.Length;
|
|
lastComponent.MaximumLength -= prefix.Length;
|
|
lastComponent.Buffer += (prefix.Length / sizeof(WCHAR));
|
|
|
|
DebugTrace( 0, Dbg, "lastComponent=%wZ\n", &lastComponent);
|
|
|
|
}
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
|
|
pktEntry = PktLookupEntryByPrefix(pkt, &prefix, &remPath);
|
|
|
|
shortPktEntry =
|
|
PktLookupEntryByShortPrefix(pkt, &prefix, &shortRemPath);
|
|
|
|
if (shortPktEntry != NULL) {
|
|
|
|
if (pktEntry == NULL ||
|
|
shortPktEntry->Id.Prefix.Length >
|
|
pktEntry->Id.Prefix.Length) {
|
|
|
|
pktEntry = shortPktEntry;
|
|
|
|
remPath = shortRemPath;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
DebugTrace( 0, Dbg, "pktEntry=0x%x\n", pktEntry);
|
|
|
|
if (pktEntry != NULL) {
|
|
|
|
//
|
|
// Found a pkt entry that matches. Is it the one belonging
|
|
// to the local volume that matched?
|
|
//
|
|
|
|
if (pktEntry == lvEntry->PktEntry) {
|
|
|
|
DebugTrace( 0, Dbg, "pktEntry===lvEntryPktEntry\n", 0);
|
|
|
|
ASSERT( pktEntry->LocalService != NULL );
|
|
|
|
//
|
|
// For DfsPathName which is relative to some parent
|
|
// path, we don't need to adjust the DfsPathName;
|
|
// simply checking to see if we landed up with the
|
|
// same Pkt Entry is all that is needed.
|
|
//
|
|
|
|
if (arg->ParentPathName.Length == 0) {
|
|
|
|
if (arg->Flags & DFS_TRANSLATE_STRIP_LAST_COMPONENT) {
|
|
DebugTrace( 0, Dbg, "STRIP_LAST on \n", 0);
|
|
remPath.Length += lastComponent.Length;
|
|
}
|
|
|
|
arg->DfsPathName.Length = remPath.Length;
|
|
|
|
RtlMoveMemory(
|
|
arg->DfsPathName.Buffer,
|
|
remPath.Buffer,
|
|
remPath.Length);
|
|
|
|
arg->DfsPathName.Buffer[
|
|
remPath.Length/sizeof(WCHAR)] = UNICODE_NULL;
|
|
|
|
DebugTrace( 0, Dbg, "arg->DfsPathName=%wZ\n", &arg->DfsPathName);
|
|
|
|
}
|
|
|
|
status = STATUS_SUCCESS;
|
|
|
|
} else {
|
|
|
|
//
|
|
// Must have crossed a junction point (to another local
|
|
// volume!)
|
|
//
|
|
|
|
status = STATUS_PATH_NOT_COVERED;
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// We don't know anything about this Dfs path. The client
|
|
// must be guessing...
|
|
//
|
|
|
|
status = STATUS_PATH_NOT_COVERED;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// Didn't find an exact match for the given local volume name.
|
|
//
|
|
|
|
status = STATUS_PATH_NOT_COVERED;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// Didn't find a match for the given local volume at all!
|
|
//
|
|
|
|
status = STATUS_PATH_NOT_COVERED;
|
|
|
|
}
|
|
|
|
PktRelease( pkt );
|
|
|
|
if (prefix.Buffer != NULL && prefix.Buffer != nameBuffer) {
|
|
ExFreePool( prefix.Buffer );
|
|
}
|
|
|
|
DebugTrace( 0, Dbg, "DfsFsctrlTranslatePath exit 0x%x\n", ULongToPtr( status ));
|
|
DebugTrace( 0, Dbg, "-->arg->SubDirectory=%wZ\n", &arg->SubDirectory);
|
|
DebugTrace( 0, Dbg, "-->arg->ParentPathName=%wZ\n", &arg->ParentPathName);
|
|
DebugTrace(-1, Dbg, "-->arg->DfsPathName=%wZ\n", &arg->DfsPathName);
|
|
|
|
|
|
DFS_TRACE_ERROR_HIGH(status, ALL_ERROR, DfsFsctrlTranslatePath_Error1,
|
|
LOGSTATUS(status)
|
|
LOGUSTR(arg->SubDirectory)
|
|
LOGUSTR(arg->ParentPathName)
|
|
LOGUSTR(arg->DfsPathName));
|
|
|
|
DFS_TRACE_NORM(EVENT, DfsFsctrlTranslatePath_End,
|
|
LOGSTATUS(status)
|
|
LOGUSTR(arg->SubDirectory)
|
|
LOGUSTR(arg->DfsPathName)
|
|
LOGUSTR(arg->ParentPathName));
|
|
|
|
return( status );
|
|
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: DfspFormPrefix
|
|
//
|
|
// Synopsis: Helper routine that forms the arguments to
|
|
// DfsFsctrlTranslatePath into a prefix appropriate for looking
|
|
// up in the Pkt.
|
|
//
|
|
// Arguments: [LvEntry] -- The local volume entry on which the ParentPath
|
|
// was opened.
|
|
// [ParentPath] -- The path of the parent object, relative to
|
|
// the dfs volume referenced by LvEntry. (equivalently,
|
|
// it is also relative to the Share Path). If this is a 0
|
|
// length string, then this is not a relative open.
|
|
// [DfsPathName] -- The name of the file that is being translated.
|
|
// [Prefix] -- The fully formed prefix is returned here. On
|
|
// entry, this should be initialized to have a buffer of
|
|
// some reasonable size; on return, the buffer might be
|
|
// replaced with a bigger one if needed, in which case
|
|
// the buffer should be freed using ExFreePool.
|
|
//
|
|
// Returns: [STATUS_SUCCESS] -- Successfully formed prefix.
|
|
//
|
|
// [STATUS_INSUFFICIENT_RESOURCES] -- Unable to form prefix.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NTSTATUS
|
|
DfspFormPrefix(
|
|
IN PDFS_LOCAL_VOL_ENTRY LvEntry,
|
|
IN PUNICODE_STRING ParentPath,
|
|
IN PUNICODE_STRING DfsPathName,
|
|
IN OUT PUNICODE_STRING Prefix)
|
|
{
|
|
NTSTATUS status;
|
|
ULONG sizeRequired;
|
|
|
|
DebugTrace(+1, Dbg, "DfspFormPrefix(LvEntry=0x%x)\n", LvEntry);
|
|
DebugTrace( 0, Dbg, " ParentPath=%wZ\n", ParentPath);
|
|
DebugTrace( 0, Dbg, " DfsPathName=%wZ\n", DfsPathName);
|
|
|
|
ASSERT(Prefix->Buffer != NULL);
|
|
|
|
sizeRequired = sizeof(UNICODE_PATH_SEP) +
|
|
ParentPath->Length +
|
|
sizeof(UNICODE_PATH_SEP) +
|
|
DfsPathName->Length;
|
|
|
|
if (ParentPath->Length > 0) {
|
|
|
|
sizeRequired += LvEntry->PktEntry->Id.Prefix.Length;
|
|
|
|
}
|
|
|
|
if (sizeRequired > MAXUSHORT) {
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
DFS_TRACE_HIGH(ERROR, DfspFormPrefix_Error1,
|
|
LOGSTATUS(status)
|
|
LOGUSTR(*ParentPath)
|
|
LOGUSTR(*DfsPathName));
|
|
return status;
|
|
|
|
}
|
|
|
|
if (sizeRequired > Prefix->MaximumLength) {
|
|
|
|
Prefix->MaximumLength = (USHORT)sizeRequired;
|
|
|
|
Prefix->Buffer = ExAllocatePoolWithTag(PagedPool, sizeRequired, ' sfD');
|
|
|
|
}
|
|
|
|
if (Prefix->Buffer != NULL) {
|
|
|
|
if (ParentPath->Length > 0) {
|
|
|
|
RtlAppendUnicodeStringToString(
|
|
Prefix,
|
|
&LvEntry->PktEntry->Id.Prefix);
|
|
|
|
DfsConcatenateFilePath(
|
|
Prefix,
|
|
ParentPath->Buffer,
|
|
(USHORT) (ParentPath->Length));
|
|
|
|
} else {
|
|
|
|
RtlAppendUnicodeToString(
|
|
Prefix,
|
|
UNICODE_PATH_SEP_STR);
|
|
|
|
}
|
|
|
|
DfsConcatenateFilePath(
|
|
Prefix,
|
|
DfsPathName->Buffer,
|
|
DfsPathName->Length);
|
|
|
|
status = STATUS_SUCCESS;
|
|
|
|
} else {
|
|
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
|
|
}
|
|
|
|
DebugTrace( 0, Dbg, "on exit, Prefix=%wZ\n", Prefix);
|
|
DebugTrace(-1, Dbg, "DfspFormPrefix exit 0x%x\n", ULongToPtr( status ));
|
|
|
|
DFS_TRACE_ERROR_HIGH(status, ALL_ERROR, DfspFormPrefix_Error2,
|
|
LOGSTATUS(status)
|
|
LOGUSTR(*ParentPath)
|
|
LOGUSTR(*DfsPathName));
|
|
return( status );
|
|
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: DfsFsctrlGetReferrals
|
|
//
|
|
// Synopsis: Returns a referral buffer for the given prefix.
|
|
//
|
|
// Arguments: [InputBuffer] -- Pointer to DFS_GET_REFERRALS_INPUT_ARG
|
|
// [InputBufferLength] -- Length of InputBuffer.
|
|
// [OutputBuffer] -- On successful return, contains a
|
|
// DFS_GET_REFERRALS_OUTPUT_BUFFER.
|
|
// [OutputBufferLength] -- Length in bytes of OutputBuffer.
|
|
// [ReturnedSize] -- On successful return, size of the returned
|
|
// referral. If returning STATUS_BUFFER_OVERFLOW, this
|
|
// variable contains the size of the buffer required to
|
|
// fit the entire referral.
|
|
//
|
|
// Returns: [STATUS_SUCCESS] -- DFS_GET_REFERRALS_OUTPUT_BUFFER
|
|
// successfully returned.
|
|
//
|
|
// [STATUS_BUFFER_OVERFLOW] -- Couldn't fit all the referrals
|
|
// in the buffer; the required buffer length is returned
|
|
// in ReturnedSize.
|
|
//
|
|
// [STATUS_NO_SUCH_DEVICE] -- Unable to find the Dfs volume in
|
|
// the local pkt.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NTSTATUS
|
|
DfsFsctrlGetReferrals(
|
|
IN PVOID InputBuffer,
|
|
IN ULONG InputBufferLength,
|
|
OUT PVOID OutputBuffer,
|
|
IN ULONG OutputBufferLength,
|
|
OUT PULONG ReturnedSize)
|
|
{
|
|
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
PDFS_GET_REFERRALS_INPUT_ARG arg =
|
|
(PDFS_GET_REFERRALS_INPUT_ARG) InputBuffer;
|
|
PRESP_GET_DFS_REFERRAL ref =
|
|
(PRESP_GET_DFS_REFERRAL) OutputBuffer;
|
|
PDFS_PKT pkt;
|
|
PDFS_PKT_ENTRY pktEntry, shortPfxEntry;
|
|
PDFS_IPADDRESS pIpAddress = NULL;
|
|
UNICODE_STRING prefix;
|
|
UNICODE_STRING PrefixTail;
|
|
UNICODE_STRING remPath;
|
|
UNICODE_STRING DomainName;
|
|
UNICODE_STRING ShareName;
|
|
ULONG i, size, maxLevel;
|
|
LPWSTR AltList;
|
|
PDFS_IP_INFO pIpInfo = NULL;
|
|
PDFS_SPECIAL_INFO pSpcInfo = NULL;
|
|
PSPECIAL_HASH_TABLE pHashTable = DfsData.SpcHashTable;
|
|
|
|
DFS_TRACE_NORM(EVENT, DfsFsctrlGetReferrals_Start,
|
|
LOGSTATUS(status)
|
|
LOGUSTR(arg->DfsPathName));
|
|
|
|
//
|
|
// If we're not started, then say so
|
|
//
|
|
|
|
if (DfsData.OperationalState != DFS_STATE_STARTED && DfsData.IsDC == FALSE) {
|
|
status = STATUS_DFS_UNAVAILABLE;
|
|
DFS_TRACE_HIGH(ERROR, DfsFsctrlGetReferrals_Error1, LOGSTATUS(status));
|
|
//return( status );
|
|
goto cleanup;
|
|
}
|
|
|
|
if (InputBufferLength == sizeof(DFS_GET_REFERRALS_INPUT_ARG)
|
|
&&
|
|
RtlCompareMemory(
|
|
&ZeroIpAddress,
|
|
&arg->IpAddress,
|
|
sizeof(DFS_IPADDRESS)) != sizeof(DFS_IPADDRESS)) {
|
|
|
|
pIpAddress = &arg->IpAddress;
|
|
|
|
}
|
|
|
|
prefix = arg->DfsPathName;
|
|
|
|
if ((maxLevel = arg->MaxReferralLevel) > 3)
|
|
maxLevel = 3;
|
|
|
|
DebugTrace(0, Dbg, "DfsFsctrlGetReferrals(maxLevel=%d)\n", ULongToPtr( maxLevel ));
|
|
|
|
// Create domain, sharename, remainder from prefix
|
|
|
|
remPath.Length = remPath.MaximumLength = 0;
|
|
|
|
DfspParsePath(&prefix, &DomainName, &ShareName, &remPath);
|
|
|
|
// See if this is a special name referral
|
|
|
|
if (DfsData.IsDC == TRUE) {
|
|
|
|
if (maxLevel >= 3 &&
|
|
(DomainName.Length == 0 ||
|
|
(DomainName.Length > 0 && ShareName.Length == 0))) {
|
|
|
|
DebugTrace(0, Dbg, "DfsFsctrlGetReferrals: case 1\n", 0);
|
|
|
|
ref->PathConsumed = 0;
|
|
|
|
if (DomainName.Length > 0) {
|
|
|
|
pSpcInfo = DfspLookupSpcEntry(&DomainName);
|
|
|
|
if (pSpcInfo != NULL && pSpcInfo->NameCount > 1) {
|
|
|
|
pIpInfo = DfsLookupSiteByIpaddress(pIpAddress, TRUE);
|
|
|
|
}
|
|
|
|
status = DfspGetOneV3SpecialReferral(
|
|
pSpcInfo,
|
|
&DomainName,
|
|
pIpInfo,
|
|
ref,
|
|
OutputBufferLength,
|
|
ReturnedSize);
|
|
|
|
if (pSpcInfo != NULL) {
|
|
|
|
DfsReleaseSpcInfo(pHashTable, pSpcInfo);
|
|
pSpcInfo = NULL;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
status = DfspGetAllV3SpecialReferral(
|
|
pIpInfo,
|
|
ref,
|
|
OutputBufferLength,
|
|
ReturnedSize);
|
|
|
|
}
|
|
|
|
DfsReleaseIpInfo(pIpInfo);
|
|
if (DfsEventLog > 1)
|
|
LogWriteMessage(DFS_REFERRAL_REQUEST, status, 1, &prefix);
|
|
//return( status );
|
|
goto cleanup;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// If we are going to hand out regular referrals,
|
|
// check to see that we are a root server, and that the
|
|
// referral request looks good.
|
|
//
|
|
|
|
pSpcInfo = DfspLookupSpcEntry(&DomainName);
|
|
|
|
if (DfsData.MachineState == DFS_ROOT_SERVER
|
|
&&
|
|
DfsData.OperationalState == DFS_STATE_STARTED
|
|
&&
|
|
DfsData.LvState == LV_INITIALIZED
|
|
&&
|
|
DomainName.Length > 0
|
|
&&
|
|
ShareName.Length > 0
|
|
&&
|
|
(pSpcInfo == NULL || remPath.Length > 0)
|
|
) {
|
|
|
|
DebugTrace(0, Dbg, "DfsFsctrlGetReferrals: case 2\n", 0);
|
|
|
|
pkt = _GetPkt();
|
|
|
|
if (maxLevel >= 3) {
|
|
|
|
PktAcquireShared( pkt, TRUE );
|
|
pktEntry = PktLookupEntryByPrefix( pkt, &prefix, &remPath);
|
|
PktRelease( pkt );
|
|
|
|
if (pktEntry != NULL && pktEntry->Info.ServiceCount > 1) {
|
|
|
|
pIpInfo = DfsLookupSiteByIpaddress(pIpAddress, TRUE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
PktAcquireShared( pkt, TRUE );
|
|
|
|
//
|
|
// Look up the name the client wants a referral for
|
|
//
|
|
if ((pktEntry = PktLookupEntryByPrefix( pkt, &prefix, &remPath)) != NULL) {
|
|
//
|
|
// See if there is a better match with an 8.3 prefix
|
|
//
|
|
shortPfxEntry = PktLookupEntryByShortPrefix( pkt, &prefix, &remPath );
|
|
if ((maxLevel == 2 || maxLevel == 3) &&
|
|
shortPfxEntry != NULL &&
|
|
(shortPfxEntry->Id.Prefix.Length >
|
|
pktEntry->Id.Prefix.Length)) {
|
|
|
|
pktEntry = shortPfxEntry;
|
|
RemoveFirstComponent(&pktEntry->Id.ShortPrefix, &PrefixTail);
|
|
} else {
|
|
RemoveFirstComponent(&pktEntry->Id.Prefix, &PrefixTail);
|
|
}
|
|
ref->PathConsumed = sizeof(UNICODE_PATH_SEP) +
|
|
DomainName.Length +
|
|
PrefixTail.Length;
|
|
//
|
|
// Found an entry for the requested path. First, size the referral,
|
|
// then, if it will fit in the output buffer, marshal it in.
|
|
//
|
|
switch (maxLevel) {
|
|
case 1:
|
|
size = DfspGetV1ReferralSize(pktEntry, &DomainName); break;
|
|
case 2:
|
|
size = DfspGetV2ReferralSize(pktEntry, &DomainName); break;
|
|
case 3:
|
|
size = DfspGetV3ReferralSize(pktEntry, &DomainName); break;
|
|
default:
|
|
ASSERT(FALSE && "Invalid MaxLevel");
|
|
}
|
|
|
|
status = STATUS_SUCCESS;
|
|
//
|
|
// For level 1 referral, we fail if buffer is not big enough to
|
|
// fit entire referral. For level 2 and 3, we try to fit as many
|
|
// entries as possible into the refferal.
|
|
|
|
switch (maxLevel) {
|
|
case 1:
|
|
if (size <= OutputBufferLength) {
|
|
DfspGetV1Referral(pktEntry, &DomainName, ref);
|
|
} else {
|
|
status = STATUS_BUFFER_OVERFLOW;
|
|
DFS_TRACE_HIGH(ERROR, DfsFsctrlGetReferals_Error1, LOGSTATUS(status));
|
|
}
|
|
break;
|
|
case 2:
|
|
status = DfspGetV2Referral(pktEntry, &DomainName, OutputBufferLength, ref, &size); break;
|
|
case 3:
|
|
status = DfspGetV3Referral(pktEntry, &DomainName, pIpInfo, OutputBufferLength, ref, &size); break;
|
|
default:
|
|
ASSERT(FALSE && "Invalid MaxLevel");
|
|
}
|
|
if (status == STATUS_SUCCESS) {
|
|
*ReturnedSize = size;
|
|
}
|
|
|
|
PktRelease( pkt );
|
|
DfsReleaseIpInfo(pIpInfo);
|
|
if (DfsEventLog > 1)
|
|
LogWriteMessage(DFS_REFERRAL_REQUEST, status, 1, &prefix);
|
|
//return( status );
|
|
goto cleanup;
|
|
}
|
|
|
|
PktRelease( pkt );
|
|
}
|
|
|
|
if (pSpcInfo != NULL) {
|
|
|
|
DfsReleaseSpcInfo(pHashTable, pSpcInfo);
|
|
|
|
}
|
|
|
|
if (DfsData.IsDC == TRUE) {
|
|
|
|
//
|
|
// See if this is an FtDFS referral
|
|
//
|
|
|
|
if (maxLevel >= 3
|
|
&&
|
|
DomainName.Length > 0
|
|
&&
|
|
ShareName.Length > 0
|
|
&&
|
|
remPath.Length == 0
|
|
) {
|
|
|
|
DebugTrace( 0, Dbg, "DfsFsctrlGetReferrals: case 3\n", 0);
|
|
DebugTrace( 0, Dbg, " DomainName=%wZ\n", &DomainName);
|
|
DebugTrace( 0, Dbg, " ShareName=%wZ\n", &ShareName);
|
|
|
|
pIpInfo = DfsLookupSiteByIpaddress(pIpAddress, TRUE);
|
|
|
|
status = DfspGetV3FtDfsReferral(
|
|
&DomainName,
|
|
&ShareName,
|
|
pIpInfo,
|
|
ref,
|
|
OutputBufferLength,
|
|
ReturnedSize);
|
|
|
|
DebugTrace(-1, Dbg, "DfsFsctrlGetReferrals: exit->0x%x\n", ULongToPtr( status ));
|
|
|
|
DfsReleaseIpInfo(pIpInfo);
|
|
if (DfsEventLog > 1)
|
|
LogWriteMessage(DFS_REFERRAL_REQUEST, status, 1, &prefix);
|
|
//return( status );
|
|
goto cleanup;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Nobody claimed this referral
|
|
//
|
|
|
|
DfsReleaseIpInfo(pIpInfo);
|
|
|
|
if (DfsEventLog > 1)
|
|
LogWriteMessage(DFS_REFERRAL_REQUEST, STATUS_NO_SUCH_DEVICE, 1, &prefix);
|
|
DebugTrace(-1, Dbg, "DfsFsctrlGetReferrals: exit STATUS_NO_SUCH_DEVICE\n", 0);
|
|
status = STATUS_NO_SUCH_DEVICE;
|
|
DFS_TRACE_HIGH(ERROR, DfsFsctrlGetReferals_Error2, LOGSTATUS(status));
|
|
|
|
cleanup:
|
|
DFS_TRACE_NORM(EVENT, DfsFsctrlGetReferrals_End,
|
|
LOGSTATUS(status)
|
|
LOGUSTR(arg->DfsPathName));
|
|
|
|
return(status);
|
|
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: DfspGetV1ReferralSize, private
|
|
//
|
|
// Synopsis: Sizes a V1 referral given a PktEntry
|
|
//
|
|
// Arguments: [PktEntry] -- The pkt entry to be returned in the referral
|
|
//
|
|
// Returns: Size in bytes of a buffer required to hold the V1 referral
|
|
// for this Pkt Entry
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
ULONG
|
|
DfspGetV1ReferralSize(
|
|
IN PDFS_PKT_ENTRY PktEntry,
|
|
IN PUNICODE_STRING MachineName)
|
|
{
|
|
ULONG i, size;
|
|
|
|
size = sizeof( RESP_GET_DFS_REFERRAL );
|
|
|
|
for (i = 0; i < PktEntry->Info.ServiceCount; i++) {
|
|
|
|
size += sizeof(DFS_REFERRAL_V1) +
|
|
PktEntry->Info.ServiceList[i].Address.Length +
|
|
sizeof(UNICODE_NULL);
|
|
|
|
}
|
|
|
|
return( size );
|
|
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: DfspGetV2ReferralSize, private
|
|
//
|
|
// Synopsis: Sizes a V2 referral given a PktEntry
|
|
//
|
|
// Arguments: [PktEntry] -- The pkt entry to be returned in the referral
|
|
//
|
|
// Returns: Size in bytes of a buffer required to hold the V2 referral
|
|
// for this Pkt Entry
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
ULONG
|
|
DfspGetV2ReferralSize(
|
|
IN PDFS_PKT_ENTRY PktEntry,
|
|
IN PUNICODE_STRING MachineName)
|
|
{
|
|
UNICODE_STRING PrefixTail;
|
|
ULONG i, size;
|
|
|
|
DebugTrace(+1, Dbg, "DfsPGetV2ReferralSize()\n", 0);
|
|
|
|
size = sizeof( RESP_GET_DFS_REFERRAL );
|
|
|
|
for (i = 0; i < PktEntry->Info.ServiceCount; i++) {
|
|
|
|
size += sizeof(DFS_REFERRAL_V2) +
|
|
PktEntry->Info.ServiceList[i].Address.Length +
|
|
sizeof(UNICODE_NULL);
|
|
|
|
}
|
|
|
|
RemoveFirstComponent(&PktEntry->Id.Prefix, &PrefixTail);
|
|
|
|
size += sizeof(UNICODE_PATH_SEP) +
|
|
MachineName->Length +
|
|
PrefixTail.Length +
|
|
sizeof(UNICODE_NULL);
|
|
|
|
RemoveFirstComponent(&PktEntry->Id.ShortPrefix, &PrefixTail);
|
|
|
|
size += sizeof(UNICODE_PATH_SEP) +
|
|
MachineName->Length +
|
|
PrefixTail.Length +
|
|
sizeof(UNICODE_NULL);
|
|
|
|
DebugTrace(-1, Dbg, "DfspGetV2ReferralSize() -- returning 0x%x\n", ULongToPtr( size ));
|
|
|
|
return( size );
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: DfspGetV3ReferralSize, private
|
|
//
|
|
// Synopsis: Sizes a V3 referral given a PktEntry
|
|
//
|
|
// Arguments: [PktEntry] -- The pkt entry to be returned in the referral
|
|
//
|
|
// Returns: Size in bytes of a buffer required to hold the V3 referral
|
|
// for this Pkt Entry
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
ULONG
|
|
DfspGetV3ReferralSize(
|
|
IN PDFS_PKT_ENTRY PktEntry,
|
|
IN PUNICODE_STRING MachineName)
|
|
{
|
|
UNICODE_STRING PrefixTail;
|
|
ULONG i, size;
|
|
|
|
DebugTrace(+1, Dbg, "DfsPGetV3ReferralSize()\n", 0);
|
|
|
|
size = sizeof( RESP_GET_DFS_REFERRAL );
|
|
|
|
for (i = 0; i < PktEntry->Info.ServiceCount; i++) {
|
|
|
|
size += sizeof(DFS_REFERRAL_V3) +
|
|
PktEntry->Info.ServiceList[i].Address.Length +
|
|
sizeof(UNICODE_NULL);
|
|
|
|
}
|
|
|
|
RemoveFirstComponent(&PktEntry->Id.Prefix, &PrefixTail);
|
|
|
|
size += sizeof(UNICODE_PATH_SEP) +
|
|
MachineName->Length +
|
|
PrefixTail.Length +
|
|
sizeof(UNICODE_NULL);
|
|
|
|
RemoveFirstComponent(&PktEntry->Id.ShortPrefix, &PrefixTail);
|
|
|
|
size += sizeof(UNICODE_PATH_SEP) +
|
|
MachineName->Length +
|
|
PrefixTail.Length +
|
|
sizeof(UNICODE_NULL);
|
|
|
|
DebugTrace(-1, Dbg, "DfspGetV3ReferralSize() -- returning 0x%x\n", ULongToPtr( size ));
|
|
|
|
return( size );
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: DfspGetV1Referral, private
|
|
//
|
|
// Synopsis: Marshals a pkt entry into a RESP_GET_DFS_REFERRAL buffer with
|
|
// V1 referrals
|
|
//
|
|
// Arguments:
|
|
//
|
|
// Returns:
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
VOID
|
|
DfspGetV1Referral(
|
|
IN PDFS_PKT_ENTRY PktEntry,
|
|
IN PUNICODE_STRING MachineName,
|
|
OUT PRESP_GET_DFS_REFERRAL Ref)
|
|
{
|
|
PDFS_REFERRAL_V1 pv1;
|
|
ULONG i;
|
|
|
|
Ref->NumberOfReferrals = (USHORT) PktEntry->Info.ServiceCount;
|
|
|
|
Ref->ReferralServers =
|
|
(PktEntry->Type & PKT_ENTRY_TYPE_REFERRAL_SVC ? 1 : 0);
|
|
|
|
Ref->StorageServers = 1;
|
|
|
|
for (i = 0, pv1 = &Ref->Referrals[0].v1;
|
|
i < PktEntry->Info.ServiceCount;
|
|
i++) {
|
|
|
|
PDFS_SERVICE pSvc;
|
|
|
|
pSvc = &PktEntry->Info.ServiceList[i];
|
|
|
|
pv1->VersionNumber = 1;
|
|
|
|
pv1->Size = sizeof(DFS_REFERRAL_V1) +
|
|
pSvc->Address.Length +
|
|
sizeof(UNICODE_NULL);
|
|
|
|
pv1->ServerType =
|
|
pSvc->Type & DFS_SERVICE_TYPE_DOWN_LEVEL ? 0 : 1;
|
|
|
|
RtlCopyMemory(
|
|
pv1->ShareName,
|
|
pSvc->Address.Buffer,
|
|
pSvc->Address.Length);
|
|
|
|
pv1->ShareName[ pSvc->Address.Length / sizeof(WCHAR) ] =
|
|
UNICODE_NULL;
|
|
|
|
pv1 = (PDFS_REFERRAL_V1) ( ((PCHAR) pv1) + pv1->Size );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: DfspGetV2Referral, private
|
|
//
|
|
// Synopsis: Marshals a pkt entry into a RESP_GET_DFS_REFERRAL buffer with
|
|
// V2 referrals
|
|
//
|
|
// Arguments:
|
|
//
|
|
// Returns:
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NTSTATUS
|
|
DfspGetV2Referral(
|
|
IN PDFS_PKT_ENTRY PktEntry,
|
|
IN PUNICODE_STRING MachineName,
|
|
IN ULONG BufferSize,
|
|
OUT PRESP_GET_DFS_REFERRAL Ref,
|
|
OUT PULONG ReferralSize)
|
|
{
|
|
PDFS_REFERRAL_V2 pv2;
|
|
ULONG i;
|
|
ULONG j;
|
|
LPWSTR dfsPath, alternatePath, nextAddress;
|
|
UNICODE_STRING PrefixTail;
|
|
ULONG CumulativeSize, CurrentSize;
|
|
ULONG optimalReferralCount;
|
|
USHORT totalReferrals = 0;
|
|
NTSTATUS status;
|
|
|
|
// Setup the maximum number of referrals that we intend to return.
|
|
|
|
if (DfsData.Pkt.MaxReferrals != 0) {
|
|
optimalReferralCount = DfsData.Pkt.MaxReferrals;
|
|
}
|
|
else {
|
|
optimalReferralCount = MAXIMUM_DFS_REFERRALS;
|
|
}
|
|
|
|
DebugTrace(+1, Dbg, "DfspGetV2Referral()\n", 0);
|
|
|
|
// Calculate the size of the referral, and make sure our size does not
|
|
// exceed the passed in buffer len.
|
|
CumulativeSize = sizeof (RESP_GET_DFS_REFERRAL) +
|
|
MachineName->Length + PktEntry->Id.Prefix.Length +
|
|
sizeof(UNICODE_PATH_SEP) + sizeof(UNICODE_NULL) +
|
|
MachineName->Length + PktEntry->Id.ShortPrefix.Length +
|
|
sizeof(UNICODE_PATH_SEP) + sizeof(UNICODE_NULL);
|
|
|
|
if (BufferSize < CumulativeSize) {
|
|
status = STATUS_BUFFER_OVERFLOW;
|
|
DFS_TRACE_HIGH(ERROR, DfspGetV2Referral_Error1,
|
|
LOGSTATUS(status)
|
|
LOGUSTR(*MachineName));
|
|
return status;
|
|
}
|
|
Ref->NumberOfReferrals = (USHORT) PktEntry->Info.ServiceCount;
|
|
|
|
Ref->ReferralServers =
|
|
(PktEntry->Type & PKT_ENTRY_TYPE_REFERRAL_SVC ? 1 : 0);
|
|
|
|
Ref->StorageServers =
|
|
(PktEntry->Type & PKT_ENTRY_TYPE_OUTSIDE_MY_DOM ? 0 : 1);
|
|
|
|
pv2 = &Ref->Referrals[0].v2;
|
|
|
|
for (i = 0; i < Ref->NumberOfReferrals; i++) {
|
|
PDFS_SERVICE pSvc;
|
|
|
|
pSvc = &PktEntry->Info.ServiceList[i];
|
|
if ((pSvc->Type & DFS_SERVICE_TYPE_OFFLINE) == 0) {
|
|
totalReferrals++;
|
|
}
|
|
}
|
|
|
|
for (i = j = 0; i < Ref->NumberOfReferrals; i++) {
|
|
PDFS_SERVICE pSvc;
|
|
|
|
pSvc = &PktEntry->Info.ServiceList[i];
|
|
if ((pSvc->Type & DFS_SERVICE_TYPE_OFFLINE) == 0) {
|
|
CurrentSize = sizeof(DFS_REFERRAL_V2) + pSvc->Address.Length + sizeof(UNICODE_NULL);
|
|
if (((CumulativeSize + CurrentSize) >= BufferSize) || (j >= optimalReferralCount))
|
|
break;
|
|
j++;
|
|
CumulativeSize += CurrentSize;
|
|
}
|
|
}
|
|
|
|
// Adjust the number of referrals accordingly.
|
|
Ref->NumberOfReferrals = (USHORT)i;
|
|
|
|
//
|
|
// Copy the volume prefix into the response buffer, just past the end
|
|
// of all the V2 referrals
|
|
//
|
|
|
|
nextAddress = dfsPath = (LPWSTR) &pv2[j];
|
|
|
|
*nextAddress++ = UNICODE_PATH_SEP;
|
|
|
|
RtlCopyMemory(
|
|
nextAddress,
|
|
MachineName->Buffer,
|
|
MachineName->Length);
|
|
|
|
nextAddress += MachineName->Length/sizeof(WCHAR);
|
|
|
|
RemoveFirstComponent(&PktEntry->Id.Prefix, &PrefixTail);
|
|
|
|
RtlCopyMemory(
|
|
nextAddress,
|
|
PrefixTail.Buffer,
|
|
PrefixTail.Length);
|
|
|
|
nextAddress += PrefixTail.Length/sizeof(WCHAR);
|
|
|
|
*nextAddress++ = UNICODE_NULL;
|
|
|
|
//
|
|
// Copy the 8.3 volume prefix into the response buffer after the
|
|
// dfsPath
|
|
//
|
|
|
|
alternatePath = nextAddress;
|
|
|
|
*nextAddress++ = UNICODE_PATH_SEP;
|
|
|
|
RtlCopyMemory(
|
|
nextAddress,
|
|
MachineName->Buffer,
|
|
MachineName->Length);
|
|
|
|
nextAddress += MachineName->Length/sizeof(WCHAR);
|
|
|
|
RemoveFirstComponent(&PktEntry->Id.ShortPrefix, &PrefixTail);
|
|
|
|
RtlCopyMemory(
|
|
nextAddress,
|
|
PrefixTail.Buffer,
|
|
PrefixTail.Length);
|
|
|
|
nextAddress += PrefixTail.Length/sizeof(WCHAR);
|
|
|
|
*nextAddress++ = UNICODE_NULL;
|
|
|
|
//
|
|
// nextAddress is pointer into buffer where the individual service addresses
|
|
// will go.
|
|
//
|
|
|
|
for (i = j = 0; i < Ref->NumberOfReferrals; i++) {
|
|
PDFS_SERVICE pSvc;
|
|
|
|
//
|
|
// Only take online services
|
|
//
|
|
pSvc = &PktEntry->Info.ServiceList[i];
|
|
if ((pSvc->Type & DFS_SERVICE_TYPE_OFFLINE) == 0) {
|
|
pv2->VersionNumber = 2;
|
|
pv2->Size = sizeof(DFS_REFERRAL_V2);
|
|
pv2->ServerType =
|
|
pSvc->Type & DFS_SERVICE_TYPE_DOWN_LEVEL ? 0 : 1;
|
|
pv2->Proximity = 0;
|
|
pv2->TimeToLive = PktEntry->Info.Timeout;
|
|
pv2->DfsPathOffset = (USHORT) (((PCHAR) dfsPath) - ((PCHAR) pv2));
|
|
pv2->DfsAlternatePathOffset =
|
|
(USHORT) (((PCHAR) alternatePath) - ((PCHAR) pv2));
|
|
pv2->NetworkAddressOffset =
|
|
(USHORT) (((PCHAR) nextAddress) - ((PCHAR) pv2));
|
|
RtlCopyMemory(
|
|
nextAddress,
|
|
pSvc->Address.Buffer,
|
|
pSvc->Address.Length);
|
|
nextAddress[ pSvc->Address.Length/sizeof(WCHAR) ] = UNICODE_NULL;
|
|
nextAddress += pSvc->Address.Length/sizeof(WCHAR) + 1;
|
|
pv2++;
|
|
j++;
|
|
}
|
|
}
|
|
|
|
Ref->NumberOfReferrals = (USHORT) j;
|
|
|
|
//
|
|
// we have more than one service, but cannot fit any into the buffer
|
|
// return buffer overflow.
|
|
//
|
|
if ((totalReferrals > 0) && (Ref->NumberOfReferrals == 0)) {
|
|
status = STATUS_BUFFER_OVERFLOW;
|
|
DFS_TRACE_HIGH(ERROR, DfspGetV2Referral_Error2,
|
|
LOGSTATUS(status)
|
|
LOGUSTR(*MachineName));
|
|
return status;
|
|
}
|
|
|
|
*ReferralSize = (ULONG)((PUCHAR)nextAddress - (PUCHAR)Ref);
|
|
|
|
DebugTrace(-1, Dbg, "DfspGetV2Referral() -- exit\n", 0);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: DfspGetV3Referral, private
|
|
//
|
|
// Synopsis: Marshals a pkt entry into a RESP_GET_DFS_REFERRAL buffer with
|
|
// V3 referrals
|
|
//
|
|
// Arguments:
|
|
//
|
|
// Returns:
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NTSTATUS
|
|
DfspGetV3Referral(
|
|
IN PDFS_PKT_ENTRY PktEntry,
|
|
IN PUNICODE_STRING MachineName,
|
|
IN PDFS_IP_INFO pIpInfo,
|
|
IN ULONG BufferSize,
|
|
OUT PRESP_GET_DFS_REFERRAL Ref,
|
|
OUT PULONG ReferralSize)
|
|
{
|
|
PDFS_REFERRAL_V3 pv3;
|
|
ULONG i;
|
|
ULONG j;
|
|
LPWSTR dfsPath, alternatePath, nextAddress;
|
|
UNICODE_STRING PrefixTail;
|
|
PDFS_REFERRAL_LIST pRefList = NULL;
|
|
ULONG CumulativeSize, Ndx, CurrentSize;
|
|
USHORT totalReferrals;
|
|
ULONG InSite;
|
|
ULONG optimalReferralCount;
|
|
NTSTATUS status;
|
|
|
|
// Setup the maximum number of referrals that we intend to return.
|
|
if (DfsData.Pkt.MaxReferrals != 0) {
|
|
optimalReferralCount = DfsData.Pkt.MaxReferrals;
|
|
}
|
|
else {
|
|
optimalReferralCount = MAXIMUM_DFS_REFERRALS;
|
|
}
|
|
|
|
DebugTrace(+1, Dbg, "DfspGetV3Referral()\n", 0);
|
|
|
|
// Calculate the size of the referral, and make sure our size does not
|
|
// exceed the passed in buffer len.
|
|
CumulativeSize =
|
|
sizeof (RESP_GET_DFS_REFERRAL) +
|
|
MachineName->Length + PktEntry->Id.Prefix.Length +
|
|
sizeof(UNICODE_PATH_SEP) + sizeof(UNICODE_NULL) +
|
|
MachineName->Length + PktEntry->Id.ShortPrefix.Length +
|
|
sizeof(UNICODE_PATH_SEP) + sizeof(UNICODE_NULL);
|
|
|
|
if (BufferSize < CumulativeSize) {
|
|
status = STATUS_BUFFER_OVERFLOW;
|
|
DFS_TRACE_HIGH(ERROR, DfspGetV3Referral_Error1,
|
|
LOGSTATUS(status)
|
|
LOGUSTR(*MachineName));
|
|
return status;
|
|
}
|
|
|
|
Ref->NumberOfReferrals = (USHORT) PktEntry->Info.ServiceCount;
|
|
totalReferrals = Ref->NumberOfReferrals;
|
|
|
|
//
|
|
// Alloc and init a DFS_REFERRAL_LIST
|
|
//
|
|
|
|
if (Ref->NumberOfReferrals) {
|
|
pRefList = ExAllocatePoolWithTag(
|
|
PagedPool,
|
|
sizeof(DFS_REFERRAL_LIST) * Ref->NumberOfReferrals,
|
|
' sfD');
|
|
|
|
if (pRefList == NULL) {
|
|
DebugTrace(-1,
|
|
Dbg,
|
|
"DfspGetV3Referral() exit STATUS_INSUFFICIENT_RESOURCES\n",
|
|
0);
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
DFS_TRACE_HIGH(ERROR, DfspGetV3Referral_Error2,
|
|
LOGSTATUS(status)
|
|
LOGUSTR(*MachineName));
|
|
return status;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Initialize it
|
|
//
|
|
|
|
for (i = j = 0; i < Ref->NumberOfReferrals; i++) {
|
|
//
|
|
// Skip offline entries
|
|
//
|
|
if ((PktEntry->Info.ServiceList[i].Type & DFS_SERVICE_TYPE_OFFLINE) == 0) {
|
|
pRefList[j].pName = PktEntry->Info.ServiceList[i].Name;
|
|
pRefList[j].pAddress = PktEntry->Info.ServiceList[i].Address;
|
|
pRefList[j].Type = PktEntry->Info.ServiceList[i].Type;
|
|
j++;
|
|
}
|
|
}
|
|
|
|
Ref->NumberOfReferrals = (USHORT)j;
|
|
|
|
if (Ref->NumberOfReferrals) {
|
|
//
|
|
// Shuffle the list in case we don't have site info
|
|
//
|
|
|
|
SrvShuffle(pRefList, 0, Ref->NumberOfReferrals - 1);
|
|
|
|
//
|
|
// Determine client's site based on the IP address srv gave us.
|
|
//
|
|
|
|
if (pIpInfo != NULL) {
|
|
|
|
//
|
|
// Reorder by site
|
|
//
|
|
|
|
InSite = DfsIpOrdering(pIpInfo, Ref->NumberOfReferrals, pRefList);
|
|
if (PktEntry->Type & PKT_ENTRY_TYPE_INSITE_ONLY) {
|
|
Ref->NumberOfReferrals = (USHORT)InSite;
|
|
}
|
|
}
|
|
}
|
|
|
|
Ref->ReferralServers =
|
|
(PktEntry->Type & PKT_ENTRY_TYPE_REFERRAL_SVC ? 1 : 0);
|
|
|
|
Ref->StorageServers =
|
|
(PktEntry->Type & PKT_ENTRY_TYPE_OUTSIDE_MY_DOM ? 0 : 1);
|
|
|
|
totalReferrals = Ref->NumberOfReferrals;
|
|
|
|
pv3 = &Ref->Referrals[0].v3;
|
|
|
|
//
|
|
// Now, we have a shuffled list of referrals. see how many we can give back
|
|
// to the client in the buffer allocated.
|
|
// We pick the first entry, followed by the last entry, then the second
|
|
// entry followed by the last but one, and so on. The purpose of this
|
|
// logic is to ensure that we split the returned referrals between
|
|
// the "insite" referrals that are on the top of the list and the
|
|
// "outsite" referrals that are at the bottom
|
|
//
|
|
|
|
for (i = 0; i < Ref->NumberOfReferrals; i++) {
|
|
Ndx = i / 2;
|
|
if (i & 1) {
|
|
Ndx = Ref->NumberOfReferrals - Ndx - 1;
|
|
}
|
|
|
|
CurrentSize = sizeof(DFS_REFERRAL_V3) +
|
|
pRefList[Ndx].pAddress.Length + sizeof(UNICODE_NULL);
|
|
if (((CumulativeSize + CurrentSize) >= BufferSize) || (i >= optimalReferralCount))
|
|
break;
|
|
CumulativeSize += CurrentSize;
|
|
}
|
|
|
|
// Adjust the number of referrals accordingly.
|
|
Ref->NumberOfReferrals = (USHORT)i;
|
|
|
|
//
|
|
// Copy the volume prefix into the response buffer, just past the end
|
|
// of all the V3 referrals
|
|
//
|
|
|
|
nextAddress = dfsPath = (LPWSTR) &pv3[ Ref->NumberOfReferrals ];
|
|
|
|
*nextAddress++ = UNICODE_PATH_SEP;
|
|
|
|
RtlCopyMemory(
|
|
nextAddress,
|
|
MachineName->Buffer,
|
|
MachineName->Length);
|
|
|
|
nextAddress += MachineName->Length/sizeof(WCHAR);
|
|
|
|
RemoveFirstComponent(&PktEntry->Id.Prefix, &PrefixTail);
|
|
|
|
RtlCopyMemory(
|
|
nextAddress,
|
|
PrefixTail.Buffer,
|
|
PrefixTail.Length);
|
|
|
|
nextAddress += PrefixTail.Length/sizeof(WCHAR);
|
|
|
|
*nextAddress++ = UNICODE_NULL;
|
|
|
|
//
|
|
// Copy the 8.3 volume prefix into the response buffer after the
|
|
// dfsPath
|
|
//
|
|
|
|
alternatePath = nextAddress;
|
|
|
|
*nextAddress++ = UNICODE_PATH_SEP;
|
|
|
|
RtlCopyMemory(
|
|
nextAddress,
|
|
MachineName->Buffer,
|
|
MachineName->Length);
|
|
|
|
nextAddress += MachineName->Length/sizeof(WCHAR);
|
|
|
|
RemoveFirstComponent(&PktEntry->Id.ShortPrefix, &PrefixTail);
|
|
|
|
RtlCopyMemory(
|
|
nextAddress,
|
|
PrefixTail.Buffer,
|
|
PrefixTail.Length);
|
|
|
|
nextAddress += PrefixTail.Length/sizeof(WCHAR);
|
|
|
|
*nextAddress++ = UNICODE_NULL;
|
|
|
|
//
|
|
// nextAddress is pointer into buffer where the individual service addresses
|
|
// will go.
|
|
//
|
|
|
|
for (i = 0; i < Ref->NumberOfReferrals; i++) {
|
|
if (i < ((ULONG)Ref->NumberOfReferrals + 1) / 2) {
|
|
Ndx = i;
|
|
}
|
|
else {
|
|
Ndx = totalReferrals - Ref->NumberOfReferrals + i;
|
|
}
|
|
pv3->VersionNumber = 3;
|
|
pv3->Size = sizeof(DFS_REFERRAL_V3);
|
|
pv3->ServerType = pRefList[Ndx].Type & DFS_SERVICE_TYPE_DOWN_LEVEL ? 0 : 1;
|
|
pv3->StripPath = 0; // for now
|
|
pv3->NameListReferral = 0;
|
|
pv3->TimeToLive = PktEntry->Info.Timeout;
|
|
pv3->DfsPathOffset = (USHORT) (((PCHAR) dfsPath) - ((PCHAR) pv3));
|
|
|
|
pv3->DfsAlternatePathOffset =
|
|
(USHORT) (((PCHAR) alternatePath) - ((PCHAR) pv3));
|
|
|
|
pv3->NetworkAddressOffset =
|
|
(USHORT) (((PCHAR) nextAddress) - ((PCHAR) pv3));
|
|
|
|
RtlZeroMemory(
|
|
&pv3->ServiceSiteGuid,
|
|
sizeof (GUID));
|
|
|
|
RtlCopyMemory(
|
|
nextAddress,
|
|
pRefList[Ndx].pAddress.Buffer,
|
|
pRefList[Ndx].pAddress.Length);
|
|
|
|
nextAddress[ pRefList[Ndx].pAddress.Length/sizeof(WCHAR) ] = UNICODE_NULL;
|
|
nextAddress += pRefList[Ndx].pAddress.Length/sizeof(WCHAR) + 1;
|
|
|
|
pv3++;
|
|
}
|
|
|
|
if (pRefList) {
|
|
ExFreePool(pRefList);
|
|
}
|
|
|
|
//
|
|
// we have more than one service, but cannot fit any into the buffer
|
|
// return buffer overflow.
|
|
//
|
|
if ((totalReferrals > 0) && (Ref->NumberOfReferrals == 0)) {
|
|
return STATUS_BUFFER_OVERFLOW;
|
|
}
|
|
|
|
*ReferralSize = (ULONG)((PUCHAR)nextAddress - (PUCHAR)Ref);
|
|
|
|
DebugTrace(-1, Dbg, "DfspGetV3Referral() -- returning STATUS_SUCCESS\n", 0);
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: DfspGetOneV3SpecialReferral, private
|
|
//
|
|
// Synopsis: Marshals a special referral
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NTSTATUS
|
|
DfspGetOneV3SpecialReferral(
|
|
IN PDFS_SPECIAL_INFO pSpcInfo,
|
|
IN PUNICODE_STRING Name,
|
|
IN PDFS_IP_INFO pIpInfo,
|
|
OUT PRESP_GET_DFS_REFERRAL Ref,
|
|
IN ULONG MaximumSize,
|
|
OUT PULONG ReturnedSize)
|
|
{
|
|
PDFS_REFERRAL_V3 pv3;
|
|
PDFS_REFERRAL_LIST pRefList;
|
|
LPWSTR pwName;
|
|
LONG i, j;
|
|
ULONG size;
|
|
NTSTATUS status;
|
|
DFS_REFERRAL_LIST MustKeep;
|
|
BOOLEAN FoundMustKeep;
|
|
|
|
ULONG CumulativeSize;
|
|
ULONG CurrentSize;
|
|
ULONG NameCount;
|
|
|
|
// ASSERT(Name->Length > 0);
|
|
|
|
DebugTrace(+1, Dbg, "DfspGetOneV3SpecialReferral(%ws)\n", Name->Buffer);
|
|
|
|
//
|
|
// Calculate the size
|
|
//
|
|
|
|
if (pSpcInfo == NULL) {
|
|
status = STATUS_NO_SUCH_DEVICE;
|
|
DebugTrace(-1, Dbg, "DfspGetOneV3SpecialReferral() -- exit STATUS_NO_SUCH_DEVICE\n", 0);
|
|
DFS_TRACE_HIGH(ERROR, DfspGetOneV3SpecialReferral_Error1,
|
|
LOGSTATUS(status)
|
|
LOGUSTR(*Name));
|
|
return status;
|
|
|
|
}
|
|
|
|
CumulativeSize = sizeof( RESP_GET_DFS_REFERRAL ) +
|
|
sizeof(DFS_REFERRAL_V3) +
|
|
pSpcInfo->SpecialName.Length +
|
|
sizeof(UNICODE_NULL) +
|
|
sizeof(UNICODE_NULL);
|
|
|
|
|
|
if (MaximumSize < CumulativeSize) {
|
|
*((PULONG) Ref) = CumulativeSize + sizeof(ULONG);
|
|
return STATUS_BUFFER_OVERFLOW;
|
|
}
|
|
|
|
|
|
//
|
|
// Alloc and init a DFS_REFERRAL_LIST
|
|
//
|
|
|
|
pRefList = ExAllocatePoolWithTag(
|
|
PagedPool,
|
|
sizeof(DFS_REFERRAL_LIST) * pSpcInfo->NameCount,
|
|
' sfD');
|
|
|
|
if (pRefList == NULL) {
|
|
|
|
DebugTrace(-1,
|
|
Dbg,
|
|
"DfspGetOneV3SpecialReferral() exit STATUS_INSUFFICIENT_RESOURCES\n",
|
|
0);
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
DFS_TRACE_HIGH(ERROR, DfspGetOneV3SpecialRefferal_Error2,
|
|
LOGSTATUS(status)
|
|
LOGUSTR(*Name));
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
//
|
|
// Initialize it
|
|
//
|
|
|
|
for (j = 0; j < pSpcInfo->NameCount; j++) {
|
|
pRefList[j].pName = pSpcInfo->Name[j];
|
|
pRefList[j].pAddress = pSpcInfo->Name[j];
|
|
pRefList[j].Type = 0;
|
|
|
|
}
|
|
|
|
//
|
|
// Shuffle the list in case we don't have site info
|
|
//
|
|
|
|
SrvShuffle(pRefList, 1, pSpcInfo->NameCount - 1);
|
|
|
|
NameCount = 0;
|
|
|
|
if (pSpcInfo->NameCount > 0) {
|
|
MustKeep = pRefList[0];
|
|
|
|
CurrentSize = sizeof(UNICODE_PATH_SEP);
|
|
CurrentSize += MustKeep.pName.Length;
|
|
CurrentSize += sizeof(UNICODE_NULL);
|
|
|
|
CumulativeSize += CurrentSize;
|
|
}
|
|
|
|
//
|
|
// Determine client's site based on the IP address srv gave us.
|
|
//
|
|
|
|
if (pIpInfo != NULL) {
|
|
|
|
//
|
|
// Reorder by site
|
|
//
|
|
|
|
DfsIpOrdering(pIpInfo, pSpcInfo->NameCount, pRefList);
|
|
}
|
|
|
|
FoundMustKeep = FALSE;
|
|
|
|
for (j = 0; j < pSpcInfo->NameCount; j++) {
|
|
if ((FoundMustKeep == FALSE) &&
|
|
(MustKeep.pName.Buffer == pRefList[j].pName.Buffer)) {
|
|
|
|
FoundMustKeep = TRUE;
|
|
if (CumulativeSize >= MaximumSize) {
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
CurrentSize = sizeof(UNICODE_PATH_SEP);
|
|
CurrentSize += pRefList[j].pName.Length;
|
|
CurrentSize += sizeof(UNICODE_NULL);
|
|
|
|
if (CumulativeSize + CurrentSize >= MaximumSize) {
|
|
break;
|
|
}
|
|
CumulativeSize += CurrentSize;
|
|
}
|
|
}
|
|
|
|
if ((pSpcInfo->NameCount > 0) && (FoundMustKeep == FALSE)) {
|
|
if (CumulativeSize < MaximumSize) {
|
|
NameCount++;
|
|
pRefList[j] = MustKeep;
|
|
}
|
|
}
|
|
|
|
NameCount += j;
|
|
|
|
if ((NameCount == 0) && (pSpcInfo->NameCount > 0)) {
|
|
*ReturnedSize = CumulativeSize + CurrentSize;
|
|
ExFreePool(pRefList);
|
|
return STATUS_BUFFER_OVERFLOW;
|
|
}
|
|
|
|
//
|
|
// Fill in the referral
|
|
//
|
|
|
|
DebugTrace( 0, Dbg, "DfspGetOneV3SpecialReferral pSpcInfo = 0x%x\n", pSpcInfo);
|
|
|
|
Ref->NumberOfReferrals = 1;
|
|
Ref->StorageServers = Ref->ReferralServers = 0;
|
|
Ref->PathConsumed = 0;
|
|
pv3 = &Ref->Referrals[0].v3;
|
|
pv3->NumberOfExpandedNames = (USHORT) NameCount;
|
|
//
|
|
// Copy the Special Names right after the V3 referral
|
|
//
|
|
|
|
pwName = (LPWSTR) &pv3[1];
|
|
pv3->SpecialNameOffset = (USHORT) (((PCHAR) pwName) - ((PCHAR) pv3));
|
|
pv3->VersionNumber = 3;
|
|
pv3->Size = sizeof(DFS_REFERRAL_V3);
|
|
pv3->ServerType = 0;
|
|
pv3->StripPath = 0; // for now
|
|
pv3->NameListReferral = 1;
|
|
pv3->TimeToLive = DfsData.SpcHashTable->SpcTimeout;
|
|
|
|
*pwName++ = UNICODE_PATH_SEP;
|
|
|
|
RtlCopyMemory(
|
|
pwName,
|
|
pSpcInfo->SpecialName.Buffer,
|
|
pSpcInfo->SpecialName.Length);
|
|
|
|
pwName[ pSpcInfo->SpecialName.Length/sizeof(WCHAR) ] = UNICODE_NULL;
|
|
pwName += pSpcInfo->SpecialName.Length/sizeof(WCHAR) + 1;
|
|
|
|
pv3->ExpandedNameOffset = (USHORT) (((PCHAR) pwName) - ((PCHAR) pv3));
|
|
|
|
for (j = 0; j < pv3->NumberOfExpandedNames; j++) {
|
|
|
|
*pwName++ = UNICODE_PATH_SEP;
|
|
|
|
RtlCopyMemory(
|
|
pwName,
|
|
pRefList[j].pName.Buffer,
|
|
pRefList[j].pName.Length);
|
|
|
|
pwName[ pRefList[j].pName.Length/sizeof(WCHAR) ] = UNICODE_NULL;
|
|
pwName += pRefList[j].pName.Length/sizeof(WCHAR) + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Double UNICODE_NULL at end
|
|
|
|
*pwName++ = UNICODE_NULL;
|
|
*ReturnedSize = CumulativeSize;
|
|
ExFreePool(pRefList);
|
|
|
|
DebugTrace(-1, Dbg, "DfspGetOneV3SpecialReferral() -- exit\n", 0);
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: DfspGetAllV3SpecialReferral, private
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NTSTATUS
|
|
DfspGetAllV3SpecialReferral(
|
|
IN PDFS_IP_INFO pIpInfo,
|
|
OUT PRESP_GET_DFS_REFERRAL Ref,
|
|
IN ULONG MaximumSize,
|
|
OUT PULONG ReturnedSize)
|
|
{
|
|
PDFS_REFERRAL_V3 pv3;
|
|
PDFS_SPECIAL_INFO pSpcInfo;
|
|
PSPECIAL_HASH_TABLE pHashTable = DfsData.SpcHashTable;
|
|
PDFS_REFERRAL_LIST pRefList;
|
|
LPWSTR pwName;
|
|
LONG i, j;
|
|
ULONG size;
|
|
ULONG SpcCount;
|
|
NTSTATUS status;
|
|
|
|
DebugTrace(+1, Dbg, "DfspGetAllV3SpecialReferral()\n", 0);
|
|
|
|
//
|
|
// return all special names, with DFS_SPECIAL_INFO_PRIMARY's expanded
|
|
//
|
|
|
|
size = sizeof( RESP_GET_DFS_REFERRAL );
|
|
|
|
DfsSpcInfoFindOpen(pHashTable);
|
|
|
|
for (SpcCount = 0, pSpcInfo = DfsSpcInfoFindFirst(pHashTable);
|
|
pSpcInfo != NULL;
|
|
pSpcInfo = DfsSpcInfoFindNext(pHashTable,pSpcInfo)) {
|
|
|
|
//
|
|
// Skip downlevel trusts - they can't have a SYSVOL or an FtDfs
|
|
// in them.
|
|
//
|
|
|
|
if (pSpcInfo->TrustType != TRUST_TYPE_UPLEVEL)
|
|
continue;
|
|
|
|
size += sizeof(DFS_REFERRAL_V3) +
|
|
sizeof(UNICODE_PATH_SEP) +
|
|
pSpcInfo->SpecialName.Length +
|
|
sizeof(UNICODE_NULL);
|
|
|
|
if ((pSpcInfo->TypeFlags & DFS_SPECIAL_INFO_PRIMARY) != 0) {
|
|
|
|
for (j = 0; j < pSpcInfo->NameCount; j++) {
|
|
|
|
size += sizeof(UNICODE_PATH_SEP) +
|
|
pSpcInfo->Name[j].Length +
|
|
sizeof(UNICODE_NULL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Double UNICODE_NULL at end
|
|
//
|
|
|
|
size += sizeof(WCHAR);
|
|
|
|
SpcCount++;
|
|
|
|
}
|
|
|
|
//
|
|
// If no entries to return say so
|
|
//
|
|
|
|
if (SpcCount == 0) {
|
|
|
|
DfsSpcInfoFindClose(pHashTable);
|
|
if (pIpInfo != NULL) {
|
|
DfsReleaseIpInfo(pIpInfo);
|
|
}
|
|
DebugTrace(-1, Dbg, "DfspGetAllV3SpecialReferral() -- exit STATUS_NO_SUCH_DEVICE\n", 0);
|
|
|
|
status = STATUS_NO_SUCH_DEVICE;
|
|
DFS_TRACE_HIGH(ERROR, DfspGetAllV3SpecialReferral_Error1, LOGSTATUS(status));
|
|
return status;
|
|
|
|
}
|
|
|
|
//
|
|
// See if it will fit - respond with needed size if it doesn't
|
|
//
|
|
|
|
if (size > MaximumSize) {
|
|
|
|
*((PULONG) Ref) = size;
|
|
*ReturnedSize = sizeof(ULONG);
|
|
DfsSpcInfoFindClose(pHashTable);
|
|
if (pIpInfo != NULL) {
|
|
DfsReleaseIpInfo(pIpInfo);
|
|
}
|
|
DebugTrace(-1, Dbg, "DfspGetAllV3SpecialReferral() -- exit STATUS_BUFFER_OVERFLOW\n", 0);
|
|
status = STATUS_BUFFER_OVERFLOW;
|
|
DFS_TRACE_HIGH(ERROR, DfspGetAllV3SpecialReferral_Error2, LOGSTATUS(status));
|
|
return STATUS_BUFFER_OVERFLOW;
|
|
|
|
}
|
|
|
|
*ReturnedSize = size;
|
|
|
|
Ref->NumberOfReferrals = (USHORT) SpcCount;
|
|
|
|
Ref->StorageServers = Ref->ReferralServers = 0;
|
|
|
|
Ref->PathConsumed = 0;
|
|
|
|
pv3 = &Ref->Referrals[0].v3;
|
|
|
|
//
|
|
// Copy the Special Names right after the V3 referrals
|
|
//
|
|
|
|
pwName = (LPWSTR) &pv3[SpcCount];
|
|
|
|
for (i = 0, pSpcInfo = DfsSpcInfoFindFirst(pHashTable);
|
|
pSpcInfo != NULL;
|
|
pSpcInfo = DfsSpcInfoFindNext(pHashTable,pSpcInfo)) {
|
|
|
|
|
|
//
|
|
// Skip downlevel trusts - they can't have a SYSVOL or an FtDfs
|
|
// in them.
|
|
//
|
|
|
|
if (pSpcInfo->TrustType != TRUST_TYPE_UPLEVEL)
|
|
continue;
|
|
|
|
pv3[i].SpecialNameOffset = (USHORT) (((PCHAR) pwName) - ((PCHAR) &pv3[i]));
|
|
pv3[i].VersionNumber = 3;
|
|
pv3[i].Size = sizeof(DFS_REFERRAL_V3);
|
|
pv3[i].ServerType = 0;
|
|
pv3[i].StripPath = 0; // for now
|
|
pv3[i].NameListReferral = 1;
|
|
pv3[i].TimeToLive = pHashTable->SpcTimeout;
|
|
|
|
*pwName++ = UNICODE_PATH_SEP;
|
|
|
|
RtlCopyMemory(
|
|
pwName,
|
|
pSpcInfo->SpecialName.Buffer,
|
|
pSpcInfo->SpecialName.Length);
|
|
|
|
pwName[ pSpcInfo->SpecialName.Length/sizeof(WCHAR) ] = UNICODE_NULL;
|
|
|
|
pwName += pSpcInfo->SpecialName.Length/sizeof(WCHAR) + 1;
|
|
|
|
if ((pSpcInfo->TypeFlags & DFS_SPECIAL_INFO_PRIMARY) != 0) {
|
|
|
|
//
|
|
// Alloc and init a DFS_REFERRAL_LIST
|
|
//
|
|
|
|
pRefList = ExAllocatePoolWithTag(
|
|
PagedPool,
|
|
sizeof(DFS_REFERRAL_LIST) * pSpcInfo->NameCount,
|
|
' sfD');
|
|
|
|
if (pRefList == NULL) {
|
|
|
|
DfsSpcInfoFindClose(pHashTable);
|
|
if (pIpInfo != NULL) {
|
|
DfsReleaseIpInfo(pIpInfo);
|
|
}
|
|
|
|
DebugTrace(-1,
|
|
Dbg,
|
|
"DfspGetAllV3SpecialReferral() exit STATUS_INSUFFICIENT_RESOURCES\n",
|
|
0);
|
|
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
DFS_TRACE_HIGH(ERROR, DfspGetAllV3SpecialReferral_Error3, LOGSTATUS(status));
|
|
return status;
|
|
|
|
}
|
|
|
|
//
|
|
// Initialize it
|
|
//
|
|
|
|
for (j = 0; j < pSpcInfo->NameCount; j++) {
|
|
|
|
pRefList[j].pName = pSpcInfo->Name[j];
|
|
pRefList[j].pAddress = pSpcInfo->Name[j];
|
|
pRefList[j].Type = 0;
|
|
|
|
}
|
|
|
|
//
|
|
// Shuffle the list in case we don't have site info
|
|
//
|
|
|
|
SrvShuffle(pRefList, 0, pSpcInfo->NameCount - 1);
|
|
|
|
//
|
|
// Reorder by site
|
|
//
|
|
|
|
//
|
|
// Determine client's site based on the IP address srv gave us.
|
|
//
|
|
|
|
if (pSpcInfo->NameCount > 1 && pIpInfo != NULL) {
|
|
|
|
//
|
|
// Reorder by site
|
|
//
|
|
|
|
DfsIpOrdering(pIpInfo, pSpcInfo->NameCount, pRefList);
|
|
|
|
}
|
|
|
|
//
|
|
// Load the referrals
|
|
//
|
|
|
|
pv3[i].NumberOfExpandedNames = (USHORT) pSpcInfo->NameCount;
|
|
pv3[i].ExpandedNameOffset = (USHORT) (((PCHAR) pwName) - ((PCHAR) &pv3[i]));
|
|
|
|
for (j = 0; j < pSpcInfo->NameCount; j++) {
|
|
|
|
*pwName++ = UNICODE_PATH_SEP;
|
|
|
|
RtlCopyMemory(
|
|
pwName,
|
|
pRefList[j].pName.Buffer,
|
|
pRefList[j].pName.Length);
|
|
|
|
pwName[ pRefList[j].pName.Length/sizeof(WCHAR) ] = UNICODE_NULL;
|
|
pwName += pRefList[j].pName.Length/sizeof(WCHAR) + 1;
|
|
|
|
}
|
|
|
|
|
|
ExFreePool(pRefList);
|
|
|
|
// Double UNICODE_NULL at end
|
|
|
|
*pwName++ = UNICODE_NULL;
|
|
|
|
} else {
|
|
|
|
pv3[i].NumberOfExpandedNames = 0;
|
|
pv3[i].ExpandedNameOffset = 0;
|
|
|
|
}
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
DfsSpcInfoFindClose(pHashTable);
|
|
|
|
DebugTrace(-1, Dbg, "DfspGetAllV3SpecialReferral() -- exit\n", 0);
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: DfspGetV3FtDfsReferral, private
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NTSTATUS
|
|
DfspGetV3FtDfsReferral(
|
|
IN PUNICODE_STRING DomainName,
|
|
IN PUNICODE_STRING ShareName,
|
|
IN PDFS_IP_INFO pIpInfo,
|
|
OUT PRESP_GET_DFS_REFERRAL Ref,
|
|
IN ULONG MaximumSize,
|
|
PULONG ReturnedSize)
|
|
{
|
|
|
|
NTSTATUS status;
|
|
PDFS_REFERRAL_V3 pv3;
|
|
PSPECIAL_HASH_TABLE pFtHashTable = DfsData.FtDfsHashTable;
|
|
PDFS_REFERRAL_LIST pRefList;
|
|
PDFS_SPECIAL_INFO pFtInfo = NULL;
|
|
LPWSTR dfsPath, alternatePath, ustrp;
|
|
LONG i, j;
|
|
ULONG k;
|
|
ULONG size;
|
|
UNICODE_STRING MachineName;
|
|
UNICODE_STRING TempName;
|
|
LARGE_INTEGER now;
|
|
PDFS_SPECIAL_INFO pSpcInfo = NULL;
|
|
PSPECIAL_HASH_TABLE pHashTable = DfsData.SpcHashTable;
|
|
BOOLEAN fSysvol = FALSE;
|
|
|
|
DFS_REFERRAL_LIST MustKeep;
|
|
BOOLEAN SpecialInfoCreated = FALSE;
|
|
BOOLEAN FoundMustKeep;
|
|
|
|
ULONG CumulativeSize;
|
|
ULONG CurrentSize;
|
|
ULONG TotalCount;
|
|
|
|
|
|
DebugTrace(+1, Dbg, "DfspGetV3FtDfsReferral()\n", 0);
|
|
|
|
if (DfspIsSpecialShare(ShareName) == TRUE) {
|
|
|
|
pSpcInfo = DfspLookupSpcEntry(DomainName);
|
|
|
|
if (pSpcInfo == NULL || pSpcInfo->NameCount == 0) {
|
|
status = STATUS_NO_SUCH_DEVICE;
|
|
goto ErrorOnSpecialShare;
|
|
}
|
|
|
|
size = sizeof(DFS_SPECIAL_INFO) + DomainName->Length;
|
|
|
|
if (pSpcInfo->NameCount > 1)
|
|
size += sizeof(UNICODE_STRING) * (pSpcInfo->NameCount - 1);
|
|
|
|
for (i = 0; i < pSpcInfo->NameCount; i++) {
|
|
size += sizeof(UNICODE_PATH_SEP) +
|
|
pSpcInfo->Name[i].Length +
|
|
sizeof(UNICODE_PATH_SEP) +
|
|
ShareName->Length;
|
|
}
|
|
|
|
pFtInfo = ExAllocatePoolWithTag(
|
|
PagedPool,
|
|
size,
|
|
' sfD');
|
|
|
|
if (pFtInfo == NULL) {
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto ErrorOnSpecialShare;
|
|
}
|
|
|
|
RtlZeroMemory(pFtInfo, size);
|
|
|
|
ustrp = (LPWSTR) &pFtInfo->Name[pSpcInfo->NameCount];
|
|
|
|
pFtInfo->SpecialName.Length = DomainName->Length;
|
|
pFtInfo->SpecialName.MaximumLength = DomainName->MaximumLength;
|
|
pFtInfo->SpecialName.Buffer = ustrp;
|
|
|
|
RtlCopyMemory(
|
|
ustrp,
|
|
DomainName->Buffer,
|
|
DomainName->Length);
|
|
|
|
ustrp += DomainName->Length / sizeof(WCHAR);
|
|
|
|
//
|
|
// NOTE:
|
|
// By setting UseCount to 1 and SPECIAL_INFO_DELETE_PENDING, the
|
|
// call to DfsReleaseSpcInfo() will free up this chunk of memory.
|
|
//
|
|
|
|
InitializeListHead(&pFtInfo->HashChain);
|
|
pFtInfo->NodeTypeCode = DFS_NTC_SPECIAL_INFO;
|
|
pFtInfo->NodeByteSize = (USHORT) size;
|
|
pFtInfo->UseCount = 1;
|
|
pFtInfo->Flags = SPECIAL_INFO_DELETE_PENDING;
|
|
pFtInfo->NameCount = pSpcInfo->NameCount;
|
|
|
|
SpecialInfoCreated = TRUE;
|
|
|
|
for (i = 0; i < pSpcInfo->NameCount; i++) {
|
|
|
|
pFtInfo->Name[i].Length = sizeof(UNICODE_PATH_SEP) +
|
|
pSpcInfo->Name[i].Length +
|
|
sizeof(UNICODE_PATH_SEP) +
|
|
ShareName->Length;
|
|
|
|
pFtInfo->Name[i].MaximumLength = pFtInfo->Name[i].Length;
|
|
pFtInfo->Name[i].Buffer = ustrp;
|
|
|
|
*ustrp++ = UNICODE_PATH_SEP;
|
|
RtlCopyMemory(
|
|
ustrp,
|
|
pSpcInfo->Name[i].Buffer,
|
|
pSpcInfo->Name[i].Length);
|
|
ustrp += pSpcInfo->Name[i].Length / sizeof(WCHAR);
|
|
*ustrp++ = UNICODE_PATH_SEP;
|
|
RtlCopyMemory(
|
|
ustrp,
|
|
ShareName->Buffer,
|
|
ShareName->Length);
|
|
ustrp += ShareName->Length / sizeof(WCHAR);
|
|
|
|
}
|
|
|
|
if (pSpcInfo != NULL) {
|
|
DfsReleaseSpcInfo(pHashTable, pSpcInfo);
|
|
pSpcInfo = NULL;
|
|
}
|
|
|
|
fSysvol = TRUE;
|
|
|
|
goto CreateReferral;
|
|
|
|
ErrorOnSpecialShare:
|
|
|
|
if (pSpcInfo != NULL) {
|
|
DfsReleaseSpcInfo(pHashTable, pSpcInfo);
|
|
pSpcInfo = NULL;
|
|
}
|
|
|
|
DebugTrace(-1, Dbg,
|
|
"DfspGetV3FtDfsReferral(Syvol or NetLogon) -- exit STATUS_NO_SUCH_DEVICE\n", 0);
|
|
status = STATUS_NO_SUCH_DEVICE;
|
|
DFS_TRACE_HIGH(ERROR, DfspGetV3FtDfsReferral_Error1,
|
|
LOGSTATUS(status)
|
|
LOGUSTR(*DomainName)
|
|
LOGUSTR(*ShareName));
|
|
return status;
|
|
|
|
}
|
|
|
|
//
|
|
// Check the FtDfs cache
|
|
//
|
|
pFtInfo = DfsLookupSpcInfo(
|
|
pFtHashTable,
|
|
ShareName);
|
|
|
|
//
|
|
// If the entry is old try to refresh it.
|
|
//
|
|
|
|
KeQuerySystemTime(&now);
|
|
|
|
if (pFtInfo != NULL && now.QuadPart > pFtInfo->ExpireTime.QuadPart) {
|
|
|
|
ExAcquireFastMutex( &pFtHashTable->HashListMutex );
|
|
pFtInfo->ExpireTime.QuadPart = now.QuadPart + UInt32x32To64(
|
|
10 * 60,
|
|
10 * 1000 * 1000);
|
|
ExReleaseFastMutex( &pFtHashTable->HashListMutex );
|
|
DfsReleaseSpcInfo(pFtHashTable, pFtInfo);
|
|
pFtInfo = NULL;
|
|
|
|
}
|
|
|
|
if (pFtInfo == NULL) {
|
|
|
|
//
|
|
// Try to get it into the cache
|
|
//
|
|
|
|
DfsLpcDomRequest(ShareName);
|
|
|
|
//
|
|
// And try again
|
|
//
|
|
|
|
pFtInfo = DfsLookupSpcInfo(
|
|
pFtHashTable,
|
|
ShareName);
|
|
|
|
if (pFtInfo == NULL) {
|
|
|
|
//
|
|
// Not in the cache, and we couldn't get it
|
|
//
|
|
|
|
DebugTrace(-1, Dbg, "DfspGetV3FtDfsReferral(1) -- exit STATUS_NO_SUCH_DEVICE\n", 0);
|
|
status = STATUS_NO_SUCH_DEVICE;
|
|
DFS_TRACE_HIGH(ERROR, DfspGetV3FtDfsReferral_Error2,
|
|
LOGSTATUS(status)
|
|
LOGUSTR(*DomainName)
|
|
LOGUSTR(*ShareName));
|
|
return status;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (pFtInfo->NameCount == 0) {
|
|
|
|
DfsReleaseSpcInfo(pFtHashTable, pFtInfo);
|
|
DebugTrace(-1, Dbg, "DfspGetOneV3SpecialReferral(2) -- exit STATUS_NO_SUCH_DEVICE\n", 0);
|
|
status = STATUS_NO_SUCH_DEVICE;
|
|
DFS_TRACE_HIGH(ERROR, DfspGetV3FtDfsReferral_Error3,
|
|
LOGSTATUS(status)
|
|
LOGUSTR(*DomainName)
|
|
LOGUSTR(*ShareName));
|
|
return STATUS_NO_SUCH_DEVICE;
|
|
|
|
}
|
|
|
|
CreateReferral:
|
|
|
|
|
|
CumulativeSize = sizeof( RESP_GET_DFS_REFERRAL );
|
|
|
|
// Longname
|
|
|
|
CumulativeSize += sizeof(UNICODE_PATH_SEP) +
|
|
DomainName->Length +
|
|
sizeof(UNICODE_PATH_SEP) +
|
|
ShareName->Length +
|
|
sizeof(UNICODE_NULL);
|
|
|
|
// Shortname
|
|
|
|
CumulativeSize += sizeof(UNICODE_PATH_SEP) +
|
|
DomainName->Length +
|
|
sizeof(UNICODE_PATH_SEP) +
|
|
ShareName->Length +
|
|
sizeof(UNICODE_NULL);
|
|
|
|
if (CumulativeSize > MaximumSize) {
|
|
*((PULONG) Ref) = (CumulativeSize + sizeof(ULONG));
|
|
*ReturnedSize = sizeof(ULONG);
|
|
DfsReleaseSpcInfo(pFtHashTable, pFtInfo);
|
|
return STATUS_BUFFER_OVERFLOW;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Alloc and init a DFS_REFERRAL_LIST
|
|
//
|
|
|
|
pRefList = ExAllocatePoolWithTag(
|
|
PagedPool,
|
|
sizeof(DFS_REFERRAL_LIST) * pFtInfo->NameCount,
|
|
' sfD');
|
|
|
|
if (pRefList == NULL) {
|
|
|
|
DfsReleaseSpcInfo(pFtHashTable, pFtInfo);
|
|
|
|
DebugTrace(-1,
|
|
Dbg,
|
|
"DfspGetV3FtDfsReferral exit STATUS_INSUFFICIENT_RESOURCES\n",
|
|
0);
|
|
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
DFS_TRACE_HIGH(ERROR, DfspGetV3FtDfsReferral_Error4,
|
|
LOGSTATUS(status)
|
|
LOGUSTR(*DomainName)
|
|
LOGUSTR(*ShareName));
|
|
return status;
|
|
|
|
}
|
|
|
|
//
|
|
// Initialize it
|
|
//
|
|
|
|
for (i = 0; i < pFtInfo->NameCount; i++) {
|
|
|
|
//
|
|
// We need to extract the servername from the address
|
|
//
|
|
|
|
pRefList[i].pName = pFtInfo->Name[i];
|
|
|
|
pRefList[i].pName.Buffer++;
|
|
pRefList[i].pName.Length -= sizeof(WCHAR);
|
|
|
|
for (k = 0;
|
|
k < pRefList[i].pName.Length/sizeof(WCHAR)
|
|
&&
|
|
pRefList[i].pName.Buffer[k] != L'\\';
|
|
k++) {
|
|
|
|
/* NOTHING */;
|
|
|
|
}
|
|
|
|
if (k < pRefList[i].pName.Length/sizeof(WCHAR)) {
|
|
|
|
pRefList[i].pName.Length = (USHORT) (k * sizeof(WCHAR));
|
|
|
|
}
|
|
|
|
pRefList[i].pAddress = pFtInfo->Name[i];
|
|
pRefList[i].Type = 0;
|
|
|
|
|
|
}
|
|
|
|
//
|
|
// Shuffle the list in case we don't have site info
|
|
//
|
|
|
|
SrvShuffle(pRefList, (SpecialInfoCreated == TRUE) ? 1 : 0, pFtInfo->NameCount - 1);
|
|
|
|
TotalCount = 0;
|
|
if ((SpecialInfoCreated == TRUE) && (pFtInfo->NameCount > 0)) {
|
|
MustKeep = pRefList[0];
|
|
|
|
CurrentSize = sizeof(UNICODE_PATH_SEP);
|
|
CurrentSize += MustKeep.pAddress.Length;
|
|
CurrentSize += sizeof(UNICODE_NULL);
|
|
|
|
CumulativeSize += CurrentSize;
|
|
}
|
|
|
|
//
|
|
// Determine client's site based on the IP address srv gave us.
|
|
//
|
|
|
|
if (pIpInfo != NULL) {
|
|
|
|
//
|
|
// Reorder by site
|
|
//
|
|
|
|
DfsIpOrdering(pIpInfo, pFtInfo->NameCount, pRefList);
|
|
|
|
}
|
|
|
|
FoundMustKeep = FALSE;
|
|
for (i = 0; i < pFtInfo->NameCount; i++) {
|
|
if ((SpecialInfoCreated == TRUE) &&
|
|
(FoundMustKeep == FALSE) &&
|
|
(MustKeep.pAddress.Buffer == pRefList[i].pAddress.Buffer)) {
|
|
|
|
FoundMustKeep = TRUE;
|
|
if (CumulativeSize >= MaximumSize) {
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
CurrentSize = sizeof(DFS_REFERRAL_V3) +
|
|
pRefList[i].pAddress.Length +
|
|
sizeof(UNICODE_NULL);
|
|
if ((CumulativeSize + CurrentSize) >= MaximumSize) {
|
|
break;
|
|
}
|
|
CumulativeSize += CurrentSize;
|
|
}
|
|
}
|
|
if ((pFtInfo->NameCount > 0) && (SpecialInfoCreated == TRUE) && (FoundMustKeep == FALSE)) {
|
|
if (CumulativeSize < MaximumSize) {
|
|
TotalCount++;
|
|
pRefList[i] = MustKeep;
|
|
}
|
|
}
|
|
TotalCount += i;
|
|
if ((TotalCount == 0) && (pFtInfo->NameCount > 0)) {
|
|
*ReturnedSize = CumulativeSize + CurrentSize;
|
|
DfsReleaseSpcInfo(pFtHashTable, pFtInfo);
|
|
ExFreePool(pRefList);
|
|
return STATUS_BUFFER_OVERFLOW;
|
|
}
|
|
|
|
//
|
|
// Fill in the referral
|
|
//
|
|
|
|
Ref->NumberOfReferrals = (USHORT) TotalCount;
|
|
|
|
Ref->ReferralServers = 1; // Means the FtDFS roots can handle referrals (should be true)
|
|
Ref->StorageServers = 1; // Means the FtDFS roots contain data/storage (should be true)
|
|
|
|
pv3 = &Ref->Referrals[0].v3;
|
|
|
|
//
|
|
// Copy the FtDfs prefix into the response buffer, just past the end
|
|
// of all the V3 referrals
|
|
//
|
|
|
|
dfsPath = ustrp = (LPWSTR) &pv3[ TotalCount ];
|
|
|
|
*ustrp++ = UNICODE_PATH_SEP;
|
|
|
|
RtlCopyMemory(
|
|
ustrp,
|
|
DomainName->Buffer,
|
|
DomainName->Length);
|
|
|
|
ustrp += DomainName->Length / sizeof(WCHAR);
|
|
*ustrp++ = UNICODE_PATH_SEP;
|
|
|
|
RtlCopyMemory(
|
|
ustrp,
|
|
ShareName->Buffer,
|
|
ShareName->Length);
|
|
|
|
ustrp += ShareName->Length / sizeof(WCHAR);
|
|
*ustrp++ = UNICODE_NULL;
|
|
|
|
//
|
|
// Copy the 8.3 volume prefix into the response buffer after the
|
|
// dfsPath
|
|
//
|
|
|
|
alternatePath = ustrp;
|
|
|
|
*ustrp++ = UNICODE_PATH_SEP;
|
|
|
|
RtlCopyMemory(
|
|
ustrp,
|
|
DomainName->Buffer,
|
|
DomainName->Length);
|
|
|
|
ustrp += DomainName->Length / sizeof(WCHAR);
|
|
*ustrp++ = UNICODE_PATH_SEP;
|
|
|
|
RtlCopyMemory(
|
|
ustrp,
|
|
ShareName->Buffer,
|
|
ShareName->Length);
|
|
|
|
ustrp += ShareName->Length / sizeof(WCHAR);
|
|
*ustrp++ = UNICODE_NULL;
|
|
|
|
//
|
|
// Initialize pointer into buffer where the individual service addresses
|
|
// will go.
|
|
//
|
|
|
|
for (i = 0; i < Ref->NumberOfReferrals; i++) {
|
|
|
|
pv3->VersionNumber = 3;
|
|
pv3->Size = sizeof(DFS_REFERRAL_V3);
|
|
pv3->ServerType = (fSysvol == TRUE) ? 0 : 1;
|
|
pv3->StripPath = 0; // for now
|
|
pv3->NameListReferral = 0;
|
|
pv3->TimeToLive = pFtHashTable->SpcTimeout;
|
|
pv3->DfsPathOffset = (USHORT) (((PCHAR) dfsPath) - ((PCHAR) pv3));
|
|
pv3->DfsAlternatePathOffset =
|
|
(USHORT) (((PCHAR) alternatePath) - ((PCHAR) pv3));
|
|
|
|
pv3->NetworkAddressOffset =
|
|
(USHORT) (((PCHAR) ustrp) - ((PCHAR) pv3));
|
|
|
|
RtlZeroMemory(
|
|
&pv3->ServiceSiteGuid,
|
|
sizeof (GUID));
|
|
|
|
RtlCopyMemory(
|
|
ustrp,
|
|
pRefList[i].pAddress.Buffer,
|
|
pRefList[i].pAddress.Length);
|
|
|
|
ustrp += pRefList[i].pAddress.Length / sizeof(WCHAR);
|
|
*ustrp++ = UNICODE_NULL;
|
|
|
|
pv3++;
|
|
|
|
}
|
|
|
|
DfsReleaseSpcInfo(pFtHashTable, pFtInfo);
|
|
ExFreePool(pRefList);
|
|
*ReturnedSize = CumulativeSize;
|
|
return STATUS_SUCCESS;
|
|
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: DfsFsctrlIsShareInDfs
|
|
//
|
|
// Synopsis: Determines whether a share path is also in the Dfs.
|
|
//
|
|
// Arguments: [InputBuffer] -- Pointer to DFS_IS_SHARE_IN_DFS_ARG.
|
|
// [InputBufferLength] -- Length in bytes of InputBuffer.
|
|
//
|
|
// Returns: [STATUS_SUCCESS] -- Share path is in Dfs
|
|
//
|
|
// [STATUS_NO_SUCH_DEVICE] -- Share path is not in Dfs.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NTSTATUS
|
|
DfsFsctrlIsShareInDfs(
|
|
IN PVOID InputBuffer,
|
|
IN ULONG InputBufferLength)
|
|
{
|
|
PDFS_IS_SHARE_IN_DFS_ARG arg = (PDFS_IS_SHARE_IN_DFS_ARG) InputBuffer;
|
|
PDFS_PKT pkt;
|
|
PUNICODE_PREFIX_TABLE_ENTRY lvPrefix;
|
|
PDFS_LOCAL_VOL_ENTRY lvEntry;
|
|
UNICODE_STRING lvName, remPath;
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
|
|
//
|
|
// Verify the buffer is at least of size DFS_IS_SHARE_IN_DFS_ARG
|
|
// And that this is the system process.
|
|
//
|
|
|
|
if (InputBufferLength < sizeof(DFS_IS_SHARE_IN_DFS_ARG)
|
|
||
|
|
PsGetCurrentProcess() != DfsData.OurProcess
|
|
) {
|
|
status = STATUS_INVALID_PARAMETER;
|
|
DFS_TRACE_HIGH(ERROR, DfsFsctrlIsShareInDfs_Error1, LOGSTATUS(status));
|
|
return status;
|
|
}
|
|
|
|
lvName = arg->SharePath;
|
|
|
|
DebugTrace(+1, Dbg, "DfsFsctrlIsShareInDfs(%wZ)\n", &lvName);
|
|
|
|
//
|
|
// Lookup the localVol in the local volume prefix table.
|
|
//
|
|
|
|
pkt = _GetPkt();
|
|
|
|
PktAcquireShared( pkt, TRUE );
|
|
|
|
lvPrefix = DfsFindUnicodePrefix(
|
|
&pkt->LocalVolTable,
|
|
&lvName,
|
|
&remPath);
|
|
|
|
DebugTrace( 0, Dbg, " lvPrefix=0x%x\n", lvPrefix);
|
|
|
|
if (lvPrefix != NULL) {
|
|
|
|
lvEntry = CONTAINING_RECORD(
|
|
lvPrefix,
|
|
DFS_LOCAL_VOL_ENTRY,
|
|
PrefixTableEntry);
|
|
|
|
DebugTrace( 0, Dbg, " lvEntry=0x%x\n", lvEntry);
|
|
DebugTrace( 0, Dbg, " lvEntry->LocalPath=[%wZ]\n", &lvEntry->LocalPath);
|
|
|
|
if (lvEntry->LocalPath.Length == lvName.Length) {
|
|
|
|
if (RtlEqualUnicodeString(
|
|
&lvEntry->ShareName, &arg->ShareName, TRUE)) {
|
|
|
|
arg->ShareType = DFS_SHARE_TYPE_DFS_VOLUME;
|
|
|
|
if (lvEntry->PktEntry->Type & PKT_ENTRY_TYPE_REFERRAL_SVC)
|
|
arg->ShareType |= DFS_SHARE_TYPE_ROOT;
|
|
|
|
status = STATUS_SUCCESS;
|
|
|
|
} else {
|
|
|
|
DebugTrace( 0, Dbg, " NO_SUCH_DEVICE(1)\n", 0);
|
|
status = STATUS_NO_SUCH_DEVICE;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
DebugTrace( 0, Dbg, " NO_SUCH_DEVICE(2)\n", 0);
|
|
status = STATUS_NO_SUCH_DEVICE;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
DebugTrace( 0, Dbg, " NO_SUCH_DEVICE(3)\n", 0);
|
|
status = STATUS_NO_SUCH_DEVICE;
|
|
|
|
}
|
|
|
|
PktRelease( pkt );
|
|
|
|
DebugTrace(-1, Dbg, "DfsFsctrlIsShareInDfs exit 0x%x\n", ULongToPtr( status ));
|
|
DFS_TRACE_ERROR_HIGH(status, ALL_ERROR, DfsFsctrlIsShareInDfs_Error2, LOGSTATUS(status));
|
|
|
|
return( status );
|
|
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: DfsFsctrlFindShare
|
|
//
|
|
// Synopsis: Determines whether a share path is an FtDFS root
|
|
//
|
|
// Arguments: [InputBuffer] -- Pointer to DFS_FIND_SHARE_ARG
|
|
// [InputBufferLength] -- Length in bytes of InputBuffer.
|
|
//
|
|
// Returns: [STATUS_PATH_NOT_COVERED] -- Share path is an FtDFS root
|
|
// [STATUS_BAD_NETWORK_NAME] -- Share path is NOT an FtDFS root
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NTSTATUS
|
|
DfsFsctrlFindShare(
|
|
IN PVOID InputBuffer,
|
|
IN ULONG InputBufferLength)
|
|
{
|
|
PSPECIAL_HASH_TABLE pFtHashTable = DfsData.FtDfsHashTable;
|
|
PDFS_SPECIAL_INFO pFtInfo;
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
|
|
|
|
PDFS_FIND_SHARE_ARG arg = (PDFS_FIND_SHARE_ARG) InputBuffer;
|
|
|
|
DFS_TRACE_NORM(EVENT, DfsFsctrlFindShare_Start,
|
|
LOGSTATUS(status)
|
|
LOGUSTR(arg->ShareName));
|
|
|
|
DebugTrace(+1, Dbg, "DfsFsctrlFindShare(%ws)\n", arg->ShareName.Buffer);
|
|
|
|
//
|
|
// See if we have the FtDfs info cached
|
|
//
|
|
pFtInfo = DfsLookupSpcInfo(
|
|
pFtHashTable,
|
|
&arg->ShareName);
|
|
|
|
if (pFtInfo != NULL) {
|
|
|
|
if (pFtInfo->NameCount > 0) {
|
|
|
|
status = STATUS_PATH_NOT_COVERED;
|
|
|
|
} else {
|
|
|
|
status = STATUS_BAD_NETWORK_NAME;
|
|
|
|
}
|
|
|
|
DfsReleaseSpcInfo(
|
|
pFtHashTable,
|
|
pFtInfo);
|
|
|
|
DebugTrace(-1, Dbg, "DfsFsctrlFindShare() -- returning 0x%x\n", ULongToPtr( status ));
|
|
DFS_TRACE_ERROR_HIGH(status, ALL_ERROR, DfsFsctrlFindShare_Error1, LOGSTATUS(status));
|
|
//return status;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Not in cache -- call up pipe to dfssvc.exe
|
|
//
|
|
|
|
status = DfsLpcDomRequest(&arg->ShareName);
|
|
|
|
//
|
|
// If success, return STATUS_PATH_NOT_COVERED
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
|
|
status = STATUS_PATH_NOT_COVERED;
|
|
|
|
} else {
|
|
|
|
status = STATUS_BAD_NETWORK_NAME;
|
|
|
|
}
|
|
|
|
DebugTrace(-1, Dbg, "DfsFsctrlFindShare() -- returning 0x%x\n", ULongToPtr( status ));
|
|
|
|
DFS_TRACE_ERROR_HIGH(status, ALL_ERROR, DfsFsctrlFindShare_Error2, LOGSTATUS(status));
|
|
cleanup:
|
|
DFS_TRACE_NORM(EVENT, DfsFsctrlFindShare_End,
|
|
LOGSTATUS(status)
|
|
LOGUSTR(arg->ShareName));
|
|
|
|
return( status );
|
|
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: SrvShuffle
|
|
//
|
|
// Synopsis: Shuffles a cost equivalent group of (pointers to) services around
|
|
// for load balancing. Uses the classic card shuffling algorithm - for
|
|
// each card in the deck, exchange it with a random card in the
|
|
// deck.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
VOID
|
|
SrvShuffle(
|
|
PDFS_REFERRAL_LIST pRefList,
|
|
LONG nStart,
|
|
LONG nEnd)
|
|
{
|
|
LONG i;
|
|
LARGE_INTEGER seed;
|
|
|
|
//
|
|
// We allow caller to have nEnd to be before nStart, so check for this
|
|
//
|
|
if (nStart >= nEnd)
|
|
return;
|
|
|
|
KeQuerySystemTime( &seed );
|
|
|
|
for (i = nStart; i <= nEnd; i++) {
|
|
|
|
DFS_REFERRAL_LIST pTempEntry;
|
|
ULONG j;
|
|
|
|
j = (RtlRandom( &seed.LowPart ) % (nEnd - nStart + 1)) + nStart;
|
|
|
|
pTempEntry = pRefList[i];
|
|
|
|
pRefList[i] = pRefList[j];
|
|
|
|
pRefList[j] = pTempEntry;
|
|
|
|
}
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: DfsIpOrdering
|
|
//
|
|
// Synopsis: Reorders a list of names based upon the passed-in client ip address.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
ULONG
|
|
DfsIpOrdering(
|
|
IN PDFS_IP_INFO pIpInfo,
|
|
IN ULONG RefCount,
|
|
IN PDFS_REFERRAL_LIST pRefList)
|
|
{
|
|
PDFS_REFERRAL_LIST pOrdRefList;
|
|
PDFS_SITE_INFO pSiteInfo;
|
|
BOOLEAN ToFront;
|
|
LONG Front = RefCount;
|
|
LONG Back;
|
|
ULONG i, j;
|
|
|
|
DebugTrace(+1, Dbg, "DfsIpOrdering()\n", 0);
|
|
|
|
//
|
|
// Create an ordered list of addresses based on Sites
|
|
//
|
|
|
|
pOrdRefList = ExAllocatePoolWithTag(
|
|
PagedPool,
|
|
sizeof(DFS_REFERRAL_LIST) * RefCount,
|
|
' sfD');
|
|
|
|
if (pOrdRefList != NULL) {
|
|
|
|
Front = 0;
|
|
Back = RefCount - 1;
|
|
|
|
for (i = 0; i < RefCount; i++) {
|
|
|
|
//
|
|
// Scan the list of alternates, placing each alternate either toward the
|
|
// front of the ordered list (if site matches) or toward the rear of the
|
|
// ordered list (if sites don't match).
|
|
//
|
|
|
|
ToFront = FALSE;
|
|
|
|
pSiteInfo = DfsLookupSiteInfo(&pRefList[i].pName);
|
|
|
|
|
|
if (pSiteInfo != NULL) {
|
|
|
|
for (j = 0; ToFront == FALSE && j < pSiteInfo->SiteCount; j++) {
|
|
|
|
if (RtlCompareUnicodeString(
|
|
&pIpInfo->SiteName,
|
|
&pSiteInfo->SiteName[j],
|
|
TRUE) == 0) {
|
|
|
|
ToFront = TRUE;
|
|
}
|
|
}
|
|
|
|
DfsReleaseSiteInfo(pSiteInfo);
|
|
}
|
|
|
|
if (ToFront == TRUE) {
|
|
|
|
pOrdRefList[Front++] = pRefList[i];
|
|
|
|
} else {
|
|
|
|
pOrdRefList[Back--] = pRefList[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Replace the unordered list with the ordered one
|
|
//
|
|
|
|
for (i = 0; i < RefCount; i++) {
|
|
|
|
pRefList[i] = pOrdRefList[i];
|
|
|
|
}
|
|
|
|
ExFreePool(pOrdRefList);
|
|
|
|
}
|
|
|
|
DebugTrace(-1, Dbg, "DfsIpOrdering()--exit\n", 0);
|
|
|
|
return Front;
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: DfspLookupSpcEntry
|
|
//
|
|
// Synopsis: Returns a special list info entry, expanded if necessary
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
PDFS_SPECIAL_INFO
|
|
DfspLookupSpcEntry(
|
|
IN PUNICODE_STRING SpecialName)
|
|
{
|
|
PSPECIAL_HASH_TABLE pHashTable = DfsData.SpcHashTable;
|
|
PDFS_SPECIAL_INFO pSpcInfo = NULL;
|
|
LARGE_INTEGER now;
|
|
ULONG TypeFlags;
|
|
|
|
DebugTrace(+1, Dbg, "DfspLookupSpcEntry(%wZ)\n", SpecialName);
|
|
|
|
//
|
|
// Check the SpcName cache
|
|
//
|
|
pSpcInfo = DfsLookupSpcInfo(
|
|
pHashTable,
|
|
SpecialName);
|
|
|
|
if (pSpcInfo != NULL) {
|
|
|
|
//
|
|
// If the entry is old or unexpanded, try to expand it
|
|
//
|
|
|
|
KeQuerySystemTime(&now);
|
|
|
|
if ((now.QuadPart > pSpcInfo->ExpireTime.QuadPart ||
|
|
pSpcInfo->NameCount == -1)
|
|
) {
|
|
|
|
ExAcquireFastMutex( &pHashTable->HashListMutex );
|
|
if (now.QuadPart > pSpcInfo->ExpireTime.QuadPart) {
|
|
|
|
pSpcInfo->ExpireTime.QuadPart = now.QuadPart + UInt32x32To64(
|
|
60 * 60,
|
|
10 * 1000 * 1000);
|
|
TypeFlags = pSpcInfo->TypeFlags;
|
|
ExReleaseFastMutex( &pHashTable->HashListMutex );
|
|
DfsReleaseSpcInfo(pHashTable, pSpcInfo);
|
|
|
|
//
|
|
// Try to refresh the cache
|
|
//
|
|
|
|
if (pSpcInfo->Flags & SPECIAL_INFO_IS_LONG_NAME) {
|
|
pSpcInfo->Flags |= SPECIAL_INFO_NEEDS_REFRESH;
|
|
}
|
|
|
|
DfsLpcSpcRequest(SpecialName, TypeFlags);
|
|
|
|
//
|
|
// And try again
|
|
//
|
|
|
|
pSpcInfo = DfsLookupSpcInfo(
|
|
pHashTable,
|
|
SpecialName);
|
|
|
|
} else {
|
|
|
|
ExReleaseFastMutex( &pHashTable->HashListMutex );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (pSpcInfo != NULL) {
|
|
|
|
if (pSpcInfo->NameCount == -1) {
|
|
|
|
DfsReleaseSpcInfo(pHashTable, pSpcInfo);
|
|
pSpcInfo = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
DebugTrace(-1, Dbg, "DfspLookupSpcEntry returning 0x%x\n", pSpcInfo);
|
|
|
|
return pSpcInfo;
|
|
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: DfspIsSpecialShare, local
|
|
//
|
|
// Synopsis: Sees if a share name is a special share.
|
|
//
|
|
// Arguments: [ShareName] -- Name of share to test.
|
|
//
|
|
// Returns: TRUE if special, FALSE otherwise.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
BOOLEAN
|
|
DfspIsSpecialShare(
|
|
PUNICODE_STRING ShareName)
|
|
{
|
|
ULONG i;
|
|
BOOLEAN fSpecial = FALSE;
|
|
|
|
for (i = 0;
|
|
(i < (sizeof(SpecialShares) / sizeof(SpecialShares[0]))) &&
|
|
!fSpecial;
|
|
i++) {
|
|
|
|
if (SpecialShares[i].Length == ShareName->Length) {
|
|
|
|
if (RtlCompareUnicodeString(&SpecialShares[i],ShareName,TRUE) == 0) {
|
|
|
|
fSpecial = TRUE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return( fSpecial );
|
|
|
|
}
|