/*++

Copyright (c) 1998  Microsoft Corporation

Module Name:

    util.c

Abstract:

    This module implements all utility functions.

Author:

    Wesley Witt (wesw) 21-Oct-1998

Revision History:

--*/

#include "cmdcons.h"
#pragma hdrstop


#include "remboot.h" 

HANDLE NetUseHandles[26] = { NULL };
BOOLEAN RdrIsInKernelMode = FALSE;

RC_ALLOWED_DIRECTORY AllowedDirs[] = {
    { FALSE, L"$WIN_NT$.~BT" },
    { FALSE, L"$WIN_NT$.~LS" },
    { FALSE, L"CMDCONS" },
    { TRUE, L"SYSTEM VOLUME INFORMATION" }
};

BOOLEAN
RcIsPathNameAllowed(
    IN LPCWSTR FullPath,
    IN BOOLEAN RemovableMediaOk,
    IN BOOLEAN Mkdir
    )

/*++

Routine Description:

    This routine verifies that the specified path name is
    allowed based on the security context that the console
    user is logged into.

Arguments:

    FullPath - specifies the full path to be verified.

Return Value:

    FALSE if failure, indicating the path is not allowed.
    TRUE otherwise.

--*/

{
    WCHAR TempBuf[MAX_PATH*2];
    UNICODE_STRING UnicodeString;
    OBJECT_ATTRIBUTES Obja;
    HANDLE  Handle;
    IO_STATUS_BLOCK IoStatusBlock;
    NTSTATUS Status;
    BOOL isDirectory = TRUE;

    //
    // should we bypass security?
    //

    if (AllowAllPaths) {
        return TRUE;
    }

    //
    // some special processing for dos paths
    // we must make sure that only the root and %systemdir% are allowed.
    //

    if (FullPath[1] == L':' && FullPath[2] == L'\\' && FullPath[3] == 0) {
        //
        // root directory is ok.
        //
        return TRUE;
    }

    SpStringToUpper((PWSTR)FullPath);

    if (!RcGetNTFileName((PWSTR)FullPath,TempBuf))
        return FALSE;

    INIT_OBJA(&Obja,&UnicodeString,TempBuf);

    Status = ZwOpenFile(
        &Handle,
        FILE_READ_ATTRIBUTES,
        &Obja,
        &IoStatusBlock,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        FILE_DIRECTORY_FILE
        );
    if( !NT_SUCCESS(Status) ) {
        isDirectory = FALSE;

    } else {
        ZwClose( Handle );
    }

    if (isDirectory == FALSE && wcsrchr( FullPath, L'\\' ) == ( &FullPath[2] )) {
        //
        // if the cannonicalized path has only one slash the user is trying to do something
        // to the files in the root, which we allow.
        //
        // however we do not allow users to mess with directories at the root
        //
        if (Mkdir) {
            return FALSE;
        } else {
            return TRUE;
        }
    }

    ASSERT(SelectedInstall != NULL);

    if(SelectedInstall != NULL) {
        //
        // Get the length of the first element in the path
        //
        PCWSTR sz;
        DWORD Len;
        DWORD i;
        WCHAR SelectedInstallDrive;

        sz = wcschr(FullPath + 3, L'\\');

        if(NULL == sz) {
            sz = FullPath + wcslen(FullPath);
        }

        Len = (DWORD) ((sz - FullPath) - 3);
        SelectedInstallDrive = RcToUpper(SelectedInstall->DriveLetter);

        //
        // See if path begins with the install path
        //
        if(FullPath[0] == SelectedInstallDrive && 0 == _wcsnicmp(FullPath + 3, SelectedInstall->Path, Len)) {
            return TRUE;
        }

        //
        // See if the path begins with an allowed dir
        //
        for(i = 0; i < sizeof(AllowedDirs) / sizeof(AllowedDirs[0]); ++i) {
            if((!AllowedDirs[i].MustBeOnInstallDrive || FullPath[0] == SelectedInstallDrive) &&
                0 == _wcsnicmp(FullPath + 3, AllowedDirs[i].Directory, Len)) {
                return TRUE;
            }
        }
    }

    if (RcIsFileOnRemovableMedia(TempBuf) == STATUS_SUCCESS) {
        if (RemovableMediaOk) {
            return TRUE;
        }
    }

    if (RcIsNetworkDrive(TempBuf) == STATUS_SUCCESS) {
        //
        // Context that was used for connection will do appropriate security checking.
        //
        return TRUE;
    }

    return FALSE;
}


BOOLEAN
RcDoesPathHaveWildCards(
    IN LPCWSTR FullPath
    )

/*++

Routine Description:

    This routine verifies that the specified path name is
    allowed based on the security context that the console
    user is logged into.

Arguments:

    FullPath - specifies the full path to be verified.

Return Value:

    FALSE if failure, indicating the path is not allowed.
    TRUE otherwise.

--*/

{
    if (wcsrchr( FullPath, L'*' )) {
        return TRUE;
    }

    if (wcsrchr( FullPath, L'?' )) {
        return TRUE;
    }

    return FALSE;
}

NTSTATUS
RcIsNetworkDrive(
    IN PWSTR FileName
    )

/*++

Routine Description:

    This routine returns if the FileName given is a network path.

Arguments:

    FileName - specifies the full path to be checked.

Return Value:

    Any other than STATUS_SUCCESS if failure, indicating the path is not on the network, 
    STATUS_SUCCESS otherwise.

--*/

{
    NTSTATUS Status;
    FILE_FS_DEVICE_INFORMATION DeviceInfo;
    PWSTR BaseNtName;

    if (wcsncmp(FileName, L"\\DosDevice", wcslen(L"\\DosDevice")) == 0) {
        Status = GetDriveLetterLinkTarget( FileName, &BaseNtName );

        if (!NT_SUCCESS(Status)) {
            return Status;
        }
    } else {
        BaseNtName = FileName;
    }

    Status = pRcGetDeviceInfo( BaseNtName, &DeviceInfo );
    if(NT_SUCCESS(Status)) {
        if (DeviceInfo.DeviceType != FILE_DEVICE_NETWORK_FILE_SYSTEM) {
            Status = STATUS_NO_MEDIA;
        }
    }

    return Status;
}


NTSTATUS
RcDoNetUse(
    PWSTR Share, 
    PWSTR User, 
    PWSTR Password, 
    PWSTR Drive
    )

/*++

Routine Description:

    This routine attempts to make a connection using the redirector to the remote server.

Arguments:

    Share - A string of the form "\\server\share"
    
    User  - A string of the form "domain\user"
    
    Password - A string containing the password information.
    
    Drive - Filled in with a string of the form "X", where X is the drive letter the share 
        has been mapped to.

Return Value:

    STATUS_SUCCESS if successful, indicating Drive contains the mapped drive letter,
    otherwise the appropriate error code.

--*/

{
    NTSTATUS Status;
    PWSTR NtDeviceName;
    ULONG ShareLength;
    WCHAR DriveLetter;
    WCHAR temporaryBuffer[128];
    PWCHAR Temp, Temp2;
    HANDLE Handle;
    ULONG EaBufferLength;
    PWSTR UserName; 
    PWSTR DomainName; 
    PVOID EaBuffer;
    UNICODE_STRING UnicodeString;
    UNICODE_STRING UnicodeString2;
    OBJECT_ATTRIBUTES ObjectAttributes;
    IO_STATUS_BLOCK IoStatusBlock;
    PFILE_FULL_EA_INFORMATION FullEaInfo;

    //
    // Switch the redirector to kernel-mode security if it is not.
    //
    if (!RdrIsInKernelMode) {
        Status = PutRdrInKernelMode();

        if (!NT_SUCCESS(Status)) {
            return Status;
        }

        RdrIsInKernelMode = TRUE;
    }

    //
    // Search for an open drive letter, starting at D: and working up.
    //
    wcscpy(temporaryBuffer, L"\\DosDevices\\D:");
    Temp = wcsstr(temporaryBuffer, L"D:");

    for (DriveLetter = L'D'; (Temp && (DriveLetter <= L'Z')); DriveLetter++) {
        *Temp = DriveLetter;
        
        Status = GetDriveLetterLinkTarget( temporaryBuffer, &Temp2 );

        if (!NT_SUCCESS(Status)) {
            break;
        }
    }

    if (DriveLetter > L'Z') {
        return STATUS_OBJECT_NAME_INVALID;
    }

    //
    // Build the NT device name.
    //
    ShareLength = wcslen(Share);
    NtDeviceName = SpMemAlloc(ShareLength * sizeof(WCHAR) + sizeof(L"\\Device\\LanmanRedirector\\;X:0"));   
    if (NtDeviceName == NULL) {
        return STATUS_NO_MEMORY;
    }
    wcscpy(NtDeviceName, L"\\Device\\LanmanRedirector\\;");
    temporaryBuffer[0] = DriveLetter;
    temporaryBuffer[1] = UNICODE_NULL;
    wcscat(NtDeviceName, temporaryBuffer);
    wcscat(NtDeviceName, L":0");
    wcscat(NtDeviceName, Share + 1);

    //
    // Chop the username and domainname into individual values.
    //
    wcscpy(temporaryBuffer, User);
    DomainName = temporaryBuffer;
    UserName = wcsstr(temporaryBuffer, L"\\");

    if (UserName == NULL) {
        SpMemFree(NtDeviceName);
        return STATUS_OBJECT_NAME_INVALID;
    }
    *UserName = UNICODE_NULL;
    UserName++;

    //
    // Create buffer with user credentials
    //

    EaBufferLength = FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName[0]);
    EaBufferLength += sizeof(EA_NAME_DOMAIN);
    EaBufferLength += (wcslen(DomainName) * sizeof(WCHAR));
    if (EaBufferLength & (sizeof(ULONG) - 1)) {
        //
        // Long align the next entry
        //
        EaBufferLength += (sizeof(ULONG) - (EaBufferLength & (sizeof(ULONG) - 1)));
    }

    EaBufferLength += FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName[0]);
    EaBufferLength += sizeof(EA_NAME_USERNAME);
    EaBufferLength += (wcslen(UserName) * sizeof(WCHAR));
    if (EaBufferLength & (sizeof(ULONG) - 1)) {
        //
        // Long align the next entry
        //
        EaBufferLength += (sizeof(ULONG) - (EaBufferLength & (sizeof(ULONG) - 1)));
    }

    EaBufferLength += FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName[0]);
    EaBufferLength += sizeof(EA_NAME_PASSWORD);
    EaBufferLength += (wcslen(Password) * sizeof(WCHAR));

    EaBuffer = SpMemAlloc(EaBufferLength);
    if (EaBuffer == NULL) {
        SpMemFree(NtDeviceName);
        return STATUS_NO_MEMORY;
    }

    FullEaInfo = (PFILE_FULL_EA_INFORMATION)EaBuffer;

    FullEaInfo->Flags = 0;
    FullEaInfo->EaNameLength = sizeof(EA_NAME_DOMAIN) - 1;
    FullEaInfo->EaValueLength = (wcslen(DomainName)) * sizeof(WCHAR);
    strcpy(&(FullEaInfo->EaName[0]), EA_NAME_DOMAIN);
    memcpy(&(FullEaInfo->EaName[FullEaInfo->EaNameLength + 1]), DomainName, FullEaInfo->EaValueLength);
    FullEaInfo->NextEntryOffset = FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName[0]) +
                                  FullEaInfo->EaNameLength + 1 +
                                  FullEaInfo->EaValueLength;
    if (FullEaInfo->NextEntryOffset & (sizeof(ULONG) - 1)) {
        FullEaInfo->NextEntryOffset += (sizeof(ULONG) - 
                                         (FullEaInfo->NextEntryOffset & 
                                          (sizeof(ULONG) - 1)));
    }


    FullEaInfo = (PFILE_FULL_EA_INFORMATION)(((char *)FullEaInfo) + FullEaInfo->NextEntryOffset);

    FullEaInfo->Flags = 0;
    FullEaInfo->EaNameLength = sizeof(EA_NAME_USERNAME) - 1;
    FullEaInfo->EaValueLength = (wcslen(UserName)) * sizeof(WCHAR);
    strcpy(&(FullEaInfo->EaName[0]), EA_NAME_USERNAME);
    memcpy(&(FullEaInfo->EaName[FullEaInfo->EaNameLength + 1]), UserName, FullEaInfo->EaValueLength);
    FullEaInfo->NextEntryOffset = FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName[0]) +
                                  FullEaInfo->EaNameLength + 1 +
                                  FullEaInfo->EaValueLength;
    if (FullEaInfo->NextEntryOffset & (sizeof(ULONG) - 1)) {
        FullEaInfo->NextEntryOffset += (sizeof(ULONG) - 
                                         (FullEaInfo->NextEntryOffset & 
                                          (sizeof(ULONG) - 1)));
    }


    FullEaInfo = (PFILE_FULL_EA_INFORMATION)(((char *)FullEaInfo) + FullEaInfo->NextEntryOffset);

    FullEaInfo->Flags = 0;
    FullEaInfo->EaNameLength = sizeof(EA_NAME_PASSWORD) - 1;
    FullEaInfo->EaValueLength = (wcslen(Password)) * sizeof(WCHAR);
    strcpy(&(FullEaInfo->EaName[0]), EA_NAME_PASSWORD);
    memcpy(&(FullEaInfo->EaName[FullEaInfo->EaNameLength + 1]), Password, FullEaInfo->EaValueLength);
    FullEaInfo->NextEntryOffset = 0;

    //
    // Now make the connection
    //
    RtlInitUnicodeString(&UnicodeString, NtDeviceName);
    InitializeObjectAttributes(&ObjectAttributes,
                               &UnicodeString,
                               OBJ_CASE_INSENSITIVE,
                               NULL,
                               NULL
                              );

    Status = ZwCreateFile(&Handle,
                          SYNCHRONIZE,
                          &ObjectAttributes,
                          &IoStatusBlock,
                          NULL,
                          FILE_ATTRIBUTE_NORMAL,
                          FILE_SHARE_READ,
                          FILE_OPEN_IF,
                          (FILE_CREATE_TREE_CONNECTION | FILE_SYNCHRONOUS_IO_NONALERT),
                          EaBuffer,
                          EaBufferLength
                         );

    if (NT_SUCCESS(Status) && NT_SUCCESS(IoStatusBlock.Status)) {
        //
        // Save off the handle so we can close it later if need be
        //
        NetUseHandles[DriveLetter - L'A'] = Handle;
        Drive[0] = DriveLetter;
        Drive[1] = L':';
        Drive[2] = UNICODE_NULL;

        //
        // Now create a symbolic link from the dos drive letter to the redirector
        //
        wcscpy(temporaryBuffer, L"\\DosDevices\\");
        wcscat(temporaryBuffer, Drive);
        RtlInitUnicodeString(&UnicodeString2, temporaryBuffer);

        Status = IoCreateSymbolicLink(&UnicodeString2, &UnicodeString);
        if (!NT_SUCCESS(Status)) {
            ZwClose(Handle);
            NetUseHandles[DriveLetter - L'A'] = NULL;
        } else {
            RcAddDrive(DriveLetter);
        }

    }

    SpMemFree(NtDeviceName);
    return Status;
}
        

NTSTATUS
RcNetUnuse(
    PWSTR Drive
    )

/*++

Routine Description:

    This routine closes a network connection.

Arguments:

    Drive - A string of the form "X:", where X is the drive letter returned by a previous call to 
        NetDoNetUse().

Return Value:

    STATUS_SUCCESS if successful, indicating the drive letter has been unmapped,
    otherwise the appropriate error code.

--*/

{
    NTSTATUS Status;
    WCHAR DriveLetter;
    WCHAR temporaryBuffer[128];
    UNICODE_STRING UnicodeString;

    DriveLetter = *Drive;
    if ((DriveLetter >= L'a') && (DriveLetter <= L'z')) {
        DriveLetter = L'A' + (DriveLetter - L'a');
    }

    if ((DriveLetter < L'A') | (DriveLetter > L'Z')) {
        return STATUS_OBJECT_NAME_INVALID;
    }

    if (NetUseHandles[DriveLetter - L'A'] == NULL) {
        return STATUS_OBJECT_NAME_INVALID;
    }

    if (RcGetCurrentDriveLetter() == DriveLetter) {
        return STATUS_CONNECTION_IN_USE;
    }

    wcscpy(temporaryBuffer, L"\\DosDevices\\");
    wcscat(temporaryBuffer, Drive);
    RtlInitUnicodeString(&UnicodeString, temporaryBuffer);

    Status = IoDeleteSymbolicLink(&UnicodeString);

    if (NT_SUCCESS(Status)) {
        ZwClose(NetUseHandles[DriveLetter - L'A']);
        NetUseHandles[DriveLetter - L'A'] = NULL;
        RcRemoveDrive(DriveLetter);
    }

    return Status;
}



NTSTATUS
PutRdrInKernelMode(
    VOID
    )

/*++

Routine Description:

    This routine IOCTLs down to the rdr to force it to use kernel-mode security.

Arguments:

    None.

Return Value:

    STATUS_SUCCESS if successful, otherwise the appropriate error code.

--*/

{
    OBJECT_ATTRIBUTES ObjectAttributes;
    UNICODE_STRING UnicodeString;
    IO_STATUS_BLOCK IoStatusBlock;
    NTSTATUS Status;
    HANDLE Handle;

    RtlInitUnicodeString(&UnicodeString, DD_NFS_DEVICE_NAME_U);

    InitializeObjectAttributes(
        &ObjectAttributes,
        &UnicodeString,
        OBJ_CASE_INSENSITIVE,
        NULL,
        NULL
        );

    Status = ZwCreateFile(
                &Handle,
                GENERIC_READ | GENERIC_WRITE,
                &ObjectAttributes,
                &IoStatusBlock,
                NULL,
                0,
                FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                FILE_OPEN,
                FILE_SYNCHRONOUS_IO_NONALERT,
                NULL,
                0
                );

    if (!NT_SUCCESS(Status)) {
        KdPrint(("SPCMDCON: Unable to open redirector. %x\n", Status));
        return Status;
    }

    Status = ZwDeviceIoControlFile(Handle,
                                   NULL,
                                   NULL,
                                   NULL,
                                   &IoStatusBlock,
                                   IOCTL_LMMR_USEKERNELSEC,
                                   NULL,
                                   0,
                                   NULL,
                                   0
                                  );

    if (NT_SUCCESS(Status)) {
        Status = IoStatusBlock.Status;
    }

    ZwClose(Handle);

    return Status;
}

BOOLEAN
RcIsArc(
    VOID
    )

/*++

Routine Description:

    Run time check to determine if this is an Arc system. We attempt to read an
    Arc variable using the Hal. This will fail for Bios based systems.

Arguments:

    None

Return Value:

    True = This is an Arc system.

--*/

{
#ifdef _X86_
    ARC_STATUS ArcStatus = EBADF;
    //
    // Get the env var into the temp buffer.
    //
    UCHAR   wbuff[130];
    //
    // Get the env var into the temp buffer.
    //
    ArcStatus = HalGetEnvironmentVariable(
                    "OsLoader",
                    sizeof(wbuff),
                    wbuff
                    );

    return((ArcStatus == ESUCCESS) ? TRUE: FALSE);
#else
    return TRUE;
#endif
}