/*++

Copyright (c) 1999  Microsoft Corporation

Module Name:

    apicli.c

Abstract:

    Windows File Protection client side APIs.

Author:

    Wesley Witt (wesw) 27-May-1999

Revision History:
    
    Andrew Ritz (andrewr) 5-Jul-1999 : added comments

--*/

#include "sfcp.h"
#pragma hdrstop

//
// global RPC binding handle because some client API's don't require you to
// specify an RPC handle
//
HANDLE _pRpcHandle;

//
// global boolean variable that tracks how the global RPC binding handle was
// established (via explicit or implicit call to SfcConnectToServer), where
// TRUE indicates that the connection was established implicitly
static BOOL InternalClient;

//
// these macros are used by each client side api to
// ensure that we have a valid rpc handle.  if the
// calling application chooses to not call SfcConnectToServer
// they connect to the local server and save the handle
// in a global for future use.
//
#define EnsureGoodConnectionHandleStatus(_h)\
    if (_h == NULL) {\
        if (_pRpcHandle == NULL) {\
            BOOL ic = InternalClient;\
            _pRpcHandle = SfcConnectToServer( NULL );\
            if (_pRpcHandle == NULL) {\
                return RPC_S_SERVER_UNAVAILABLE;\
            }\
            InternalClient = ic;\
        }\
        _h = _pRpcHandle;\
    }

#define EnsureGoodConnectionHandleBool(_h)\
    if (_h == NULL) {\
        if (_pRpcHandle == NULL) {\
            BOOL ic = InternalClient;\
            _pRpcHandle = SfcConnectToServer( NULL );\
            if (_pRpcHandle == NULL) {\
                SetLastError(RPC_S_SERVER_UNAVAILABLE);\
				return FALSE;\
            }\
            InternalClient = ic;\
        }\
        _h = _pRpcHandle;\
    }


void
ClientApiInit(
    void
    )
{
#ifndef _WIN64
    SfcInitPathTranslator();
#endif  // _WIN64
}

void
ClientApiCleanup(
    void
    )
/*++

Routine Description:

    RPC cleanup wrapper routine called by client side when done with server side
    connection that was previously established with SfcConnectToServer().
    
Arguments:
    
    None

Return Value:

    none.
    
--*/
{
    if (_pRpcHandle) {
        SfcClose( _pRpcHandle );
    }

#ifndef _WIN64
    SfcCleanupPathTranslator(TRUE);
#endif  // _WIN64
}


HANDLE
WINAPI
SfcConnectToServer(
    IN PCWSTR ServerName
    )
/*++

Routine Description:

    RPC attachment routine.
    
Arguments:
    
    ServerName - NULL terminated unicode string specifying server to connect to

Return Value:

    an RPC binding handle on success, else NULL.
    
--*/
{
    RPC_BINDING_HANDLE RpcHandle;
    NTSTATUS Status;

#ifndef SFC_REMOTE_CLIENT_SUPPORT
    //
    // Note:   We don't want to support remote calls for now.
    //         This call should really allow the local computer name or synonyms
    //         thereof, but since this is a private interface, we don't worry
    //         about that.
    if (ServerName) {
        SetLastError( ERROR_CALL_NOT_IMPLEMENTED );
        return(NULL);
    }
#endif

    //
    // connect to the RPC server
    //
    Status = RpcpBindRpc(
        ServerName ? (PWSTR) ServerName : L".",
        L"SfcApi",
        0,
        &RpcHandle
        );
    if (!NT_SUCCESS(Status)) {
        return NULL;
    }

    //
    // record this as an internal client connection
    //
    InternalClient = TRUE;
    return RpcHandle;
}


VOID
SfcClose(
    IN HANDLE RpcHandle
    )

/*++

Routine Description:

    RPC cleanup routine.
    
Arguments:
    
    RpcHandle - RPC binding handle to the SFC server

Return Value:

    None.
    
--*/
{
    RpcpUnbindRpc( RpcHandle );
    InternalClient = FALSE;
    _pRpcHandle = NULL;
}


DWORD
WINAPI
SfcFileException(
    IN HANDLE RpcHandle,
    IN PCWSTR FileName,
    IN DWORD ExpectedChangeType
    )
/*++

Routine Description:

    Routine to exempt a given file from the specified file change.  This 
    routine is used by certain clients to allow files to be deleted from
    the system, etc.
    
Arguments:
    
    RpcHandle - RPC binding handle to the SFC server
    FileName  - NULL terminated unicode string specifying full filename of the
                file to be exempted
    ExpectedChangeType - SFC_ACTION_* mask listing the file changes to exempt

Return Value:

    Win32 error code indicating outcome.
    
--*/
{
#ifndef _WIN64

    DWORD dwError = ERROR_SUCCESS;
    UNICODE_STRING Path = { 0 };
    NTSTATUS Status;

    EnsureGoodConnectionHandleStatus( RpcHandle );
    Status = SfcRedirectPath(FileName, &Path);

    if(!NT_SUCCESS(Status))
    {
        dwError = RtlNtStatusToDosError(Status);
        goto exit;
    }

    ASSERT(Path.Buffer != NULL);
    dwError = SfcCli_FileException( RpcHandle, Path.Buffer, ExpectedChangeType );

exit:
    MemFree(Path.Buffer);
    return dwError;

#else  // _WIN64

    EnsureGoodConnectionHandleStatus( RpcHandle );
    return SfcCli_FileException( RpcHandle, FileName, ExpectedChangeType );

#endif  // _WIN64
}


DWORD
WINAPI
SfcInitiateScan(
    IN HANDLE RpcHandle,
    IN DWORD ScanWhen
    )
/*++

Routine Description:

    Routine to start some sort scan on the system.
    
Arguments:
    
    RpcHandle - RPC binding handle to the SFC server
    ScanWhen  - flag indicating when to scan.  This parameter is currently
                unused.
    
Return Value:

    Win32 error code indicating outcome.
    
--*/
{
    UNREFERENCED_PARAMETER(ScanWhen);

    EnsureGoodConnectionHandleStatus( RpcHandle );
    return SfcCli_InitiateScan( RpcHandle, ScanWhen );
}


BOOL
WINAPI
SfcInstallProtectedFiles(
    IN HANDLE RpcHandle,
    IN PCWSTR FileNames,
    IN BOOL AllowUI,
    IN PCWSTR ClassName,
    IN PCWSTR WindowName,
    IN PSFCNOTIFICATIONCALLBACK SfcNotificationCallback,
    IN DWORD_PTR Context OPTIONAL
    )
/*++

Routine Description:

    Routine to install one or more protected system files onto the system at 
    the protected location.  A client can use this API to request that WFP
    install the specified operating system files as appropriate (instead of the
    client redistributing the operating system files!)  The caller specifies a
    callback routine and a context structure that is called once per file.
    
Arguments:
    
    RpcHandle  - RPC binding handle to the SFC server
    FileNames  - a list of NULL seperated unicode strings, terminated by two 
                 NULL characters
    AllowUI    - a BOOL indicating whether UI is allowed or not.  If this value
                 is TRUE, then any prompts for UI cause the API call to fail.
    ClassName  - NULL terminated unicode string indicating the window classname
                 for the parent window
    WindowName - NULL terminated unicode string indicating the window name for
                 the parent window for any UI that may be displayed
    SfcNotificationCallback - pointer to a callback routine that is called once
                 per file.
    Context    - opaque pointer to caller defined context structure that is 
                 passed through to the callback routine.
    
Return Value:

    TRUE for success, FALSE for error.  last error code contains a Win32 error 
    code on failure.
    
--*/
{
    DWORD rVal = ERROR_SUCCESS;
    PCWSTR fname;
    ULONG cnt = 0, cntold = 0;
    ULONG sz = 0;
    PFILEINSTALL_STATUS cs = NULL;
    DWORD StatusSize = 0;
    UNICODE_STRING Path = { 0 };

#ifndef _WIN64
    //
    // must translate the paths
    //
    PWSTR szTranslatedFiles = NULL;
#endif
    
    //
    // parameter validation
    //
    if((SfcNotificationCallback == NULL) ||
       (FileNames == NULL)) {
        rVal = ERROR_INVALID_PARAMETER;
        goto exit;
    }

    //
    // 1. if a windowname is specified, a classname should be specified
    // 2. if a classname is specified, a windowname should be specified
    // 3. if we don't allow UI, then windowname and classname should both be
    //    NULL.
    //
    if ((WindowName && !ClassName) 
        || (ClassName && !WindowName)
        || (!AllowUI && (ClassName || WindowName))) {
        rVal = ERROR_INVALID_PARAMETER;
        goto exit;
    }

    //
    // validate RPC handle
    //
    EnsureGoodConnectionHandleBool( RpcHandle );

    //
    // check out how large of a buffer to send over
    //

    try {
#ifdef _WIN64

        for(fname = FileNames; *fname; ++cntold) {

            DWORD StringLength;
            StringLength = wcslen(fname) + 1;
            sz += StringLength * sizeof(WCHAR);
            fname += StringLength;
        }

#else
        //
        // must translate paths before calling the server
        //
        PWSTR szNewBuf = NULL;

        for(fname = FileNames; *fname; fname += wcslen(fname) + 1, ++cntold) {
            NTSTATUS Status;

            Status = SfcRedirectPath(fname, &Path);

            if(!NT_SUCCESS(Status))
            {
                rVal = RtlNtStatusToDosError(Status);
                goto exit;
            }

            if(NULL == szTranslatedFiles)
            {
                szNewBuf = (PWSTR) MemAlloc(Path.Length + 2 * sizeof(WCHAR));
            }
            else
            {
                szNewBuf = (PWSTR) MemReAlloc(sz + Path.Length + 2 * sizeof(WCHAR), szTranslatedFiles);
            }

            if(szNewBuf != NULL)
            {
                szTranslatedFiles = szNewBuf;
                RtlCopyMemory((PCHAR) szTranslatedFiles + sz, Path.Buffer, Path.Length + sizeof(WCHAR));
                sz += Path.Length + sizeof(WCHAR);
            }

            MemFree(Path.Buffer);
            RtlZeroMemory(&Path, sizeof(Path));

            if(NULL == szNewBuf)
            {
                rVal = ERROR_NOT_ENOUGH_MEMORY;
                goto exit;
            }
        }

        //
        //set the last null
        //
        if(szTranslatedFiles != NULL)
        {
            szTranslatedFiles[sz / sizeof(WCHAR)] = L'\0';
        }

#endif
    } except (EXCEPTION_EXECUTE_HANDLER) {
        rVal = RtlNtStatusToDosError(GetExceptionCode());
        goto exit;
    }

    if(0 == cntold)
    {
        //
        // not files to install
        //
        rVal = ERROR_INVALID_PARAMETER;
        goto exit;
    }

    //
    // for terminating NULL
    //
    sz+=sizeof(WCHAR);

    //
    // make the RPC call to install the files
    //
    rVal = SfcCli_InstallProtectedFiles(
        RpcHandle,
#ifdef _WIN64
        (LPBYTE)FileNames,
#else
        (LPBYTE)szTranslatedFiles,
#endif
        sz,
        (LPBYTE*)&cs,
        &StatusSize,
        &cnt,
        AllowUI,
        ClassName,
        WindowName
        );

    if (rVal != ERROR_SUCCESS) {
        goto exit;
    }
    
    //
    // we should have gotten back the same amount of status information as the 
    // number of files that we passed in
    // 
    ASSERT(cnt == cntold);

    //
    // call the callback function once for each file, now that we've completed
    // copying the files in the list.  We pass the caller a structure which 
    // indicates the success of copying each individual file in the list.
    //
    for (fname = FileNames, sz=0; sz<cnt; sz++, fname += wcslen(fname) + 1) {
        LPEXCEPTION_POINTERS ExceptionPointers = NULL;
        try {
            NTSTATUS Status;
            BOOL b;
            //
            // don't use the (possibly reditected) file names returned from the server
            //
            Status = SfcAllocUnicodeStringFromPath(fname, &Path);

            if(!NT_SUCCESS(Status))
            {
                rVal = RtlNtStatusToDosError(Status);
                goto exit;
            }

            cs[sz].FileName = Path.Buffer;
            b = SfcNotificationCallback( &cs[sz], Context );
            MemFree(Path.Buffer);
            RtlZeroMemory(&Path, sizeof(Path));

            if (!b) {
                //
                // return FALSE if the callback fails for any reason
                //
                rVal = ERROR_CANCELLED;
                goto exit;
            }
        } except (ExceptionPointers = GetExceptionInformation(),
                  EXCEPTION_EXECUTE_HANDLER) {
            //
            // we hit an exception calling the callback...return exception code
            //            
            DebugPrint3( LVL_VERBOSE, 
                         L"SIPF hit exception %x while calling callback routine %x at address %x\n",
                         ExceptionPointers->ExceptionRecord->ExceptionCode,
                         SfcNotificationCallback,
                         ExceptionPointers->ExceptionRecord->ExceptionAddress
                       );
            rVal = RtlNtStatusToDosError(ExceptionPointers->ExceptionRecord->ExceptionCode);
            goto exit;
        }
    }

exit:
    MemFree(Path.Buffer);

    if(cs != NULL)
    {
        midl_user_free( cs );    
    }

#ifndef _WIN64
    MemFree(szTranslatedFiles);
#endif

    SetLastError(rVal);
    return rVal == ERROR_SUCCESS;
}

BOOL
WINAPI
SfcGetNextProtectedFile(
    IN HANDLE RpcHandle,
    IN PPROTECTED_FILE_DATA ProtFileData
    )
/*++

Routine Description:

    Routine to retrieve the next protected file in the list.
    
Arguments:
    
    RpcHandle    - RPC binding handle to the SFC server
    ProtFileData - pointer to a PROTECTED_FILE_DATA structure to be filled
                   in by function.
    
Return Value:

    TRUE for success, FALSE for failure. If there are no more files, the last
    error code will be set to ERROR_NO_MORE_FILES.    
    
--*/
{
    DWORD rVal;
    LPWSTR FileName = NULL;
    DWORD FileNameSize = 0;
    BOOL bReturn = FALSE;
    DWORD FileNumber;

    //
    // validate parameters
    //
    if (ProtFileData == NULL) {
        SetLastError(ERROR_INVALID_PARAMETER);
        return(FALSE);
    }

    try {
        FileNumber = ProtFileData->FileNumber;        
    } except (EXCEPTION_EXECUTE_HANDLER) {
        SetLastError(ERROR_INVALID_DATA);
        return(FALSE);
    }
    
    //
    // If this is not an internal client, then RpcHandle must be NULL.
    //
    if (InternalClient == FALSE) {
        if (RpcHandle != NULL) {
            SetLastError(ERROR_INVALID_HANDLE);
            return(FALSE);
        }
    }
    EnsureGoodConnectionHandleBool( RpcHandle );

    //
    // call the server API
    //
    rVal = SfcCli_GetNextProtectedFile(
        RpcHandle,
        FileNumber,
        (LPBYTE*)&FileName,
        &FileNameSize
        );
    if (rVal != ERROR_SUCCESS) {
        SetLastError(rVal);
        goto exit;        
    }

    bReturn = TRUE;

    //
    // copy into the caller supplied buffer
    //
    try {
        wcscpy( ProtFileData->FileName, FileName );
        ProtFileData->FileNumber += 1;        
    } except (EXCEPTION_EXECUTE_HANDLER) {
        SetLastError(RtlNtStatusToDosError(GetExceptionCode()));
        bReturn = FALSE;
    }

    midl_user_free( FileName );

exit:    
    return(bReturn);
}


BOOL
WINAPI
SfcIsFileProtected(
    IN HANDLE RpcHandle,
    IN LPCWSTR ProtFileName
    )
/*++

Routine Description:

    Routine to determine if the specified file is protected.
    
Arguments:
    
    RpcHandle    - RPC binding handle to the SFC server
    ProtFileName - NULL terminated unicode string indicating fully qualified
                   filename to query
    
Return Value:

    TRUE if file is protected, FALSE if it isn't.  last error code contains a
    Win32 error code on failure.
    
--*/
{
    DWORD rVal;
    DWORD dwAttributes, dwSize;
    WCHAR Buffer[MAX_PATH];

    //
    // parameter validation
    //
    if (ProtFileName == NULL) {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

    //
    // if this is not an internal client, then RpcHandle must be NULL.
    //
    if (InternalClient == FALSE) {
        if (RpcHandle != NULL) {
            SetLastError(ERROR_INVALID_HANDLE);
            return FALSE;
        }
    }
    EnsureGoodConnectionHandleBool( RpcHandle );

    //
    // check whether this file is sxs-wfp first, which could be done on client-side only
    //

    // 
    // check whether it begins with "%SystemRoot%\\WinSxS\\"
    //
    dwSize = ExpandEnvironmentStrings( L"%SystemRoot%\\WinSxS\\", Buffer, UnicodeChars(Buffer));
    if(0 == dwSize)
    {        
        DebugPrint1( LVL_MINIMAL, L"SFC : ExpandEnvironmentStrings failed with lastError = 0x%x", GetLastError());        
        return FALSE;
    }

    --dwSize;

    try {
        if ((wcslen(ProtFileName) > dwSize) &&
            (_wcsnicmp(Buffer, ProtFileName, dwSize) == 0))  // if they're equal, this could be a protected file
        {
            dwAttributes = GetFileAttributesW(ProtFileName);        
            if (dwAttributes == 0xFFFFFFFF)
                return FALSE;

            if (dwAttributes & FILE_ATTRIBUTE_DIRECTORY) {
                SetLastError(ERROR_INVALID_PARAMETER);
                return FALSE;
            }

            return TRUE;
        }
    } except(EXCEPTION_EXECUTE_HANDLER) {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

    //
    // call server to determine if file is protected
    //
    rVal = SfcCli_IsFileProtected( RpcHandle, (PWSTR)ProtFileName );
    if (rVal != ERROR_SUCCESS) {
        SetLastError(rVal);
        return FALSE;
    }
    return TRUE;
}