/*++

Copyright (c) 1998  Microsoft Corporation

Module Name:

    termutil.c

Abstract:

    Terminal server support functions and inifile syncing/merging code

Author:


Revision History:

--*/

#include "basedll.h"
#include "regapi.h"

#define TERMSRV_INIFILE_TIMES L"\\Registry\\Machine\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Terminal Server\\Install\\IniFile Times"



BOOL IsTerminalServerCompatible(VOID)
{

PIMAGE_NT_HEADERS NtHeader = RtlImageNtHeader( NtCurrentPeb()->ImageBaseAddress );

    if ((NtHeader) && (NtHeader->OptionalHeader.DllCharacteristics & IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE)) {
        return TRUE;
    } else {
        return FALSE;
    }
}

BOOL IsTSAppCompatEnabled(VOID)
{

   NTSTATUS NtStatus;
   OBJECT_ATTRIBUTES ObjectAttributes;
   UNICODE_STRING UniString;
   HKEY   hKey = 0;
   ULONG  ul, ulcbuf;
   PKEY_VALUE_PARTIAL_INFORMATION pKeyValInfo = NULL;

   BOOL retval = TRUE;


   RtlInitUnicodeString(&UniString,REG_NTAPI_CONTROL_TSERVER);



   // Determine the value info buffer size
   ulcbuf = sizeof(KEY_VALUE_FULL_INFORMATION) + MAX_PATH*sizeof(WCHAR) +
            sizeof(ULONG);

   pKeyValInfo = RtlAllocateHeap(RtlProcessHeap(),
                                 0,
                                 ulcbuf);

   // Did everything initialize OK?
   if (UniString.Buffer && pKeyValInfo) {

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

       NtStatus = NtOpenKey(&hKey, KEY_READ, &ObjectAttributes);

       if (NT_SUCCESS(NtStatus)) {

           RtlInitUnicodeString(&UniString,
                               L"TSAppCompat");
           NtStatus = NtQueryValueKey(hKey,
                                      &UniString,
                                      KeyValuePartialInformation,
                                      pKeyValInfo,
                                      ulcbuf,
                                      &ul);

           if (NT_SUCCESS(NtStatus) && (REG_DWORD == pKeyValInfo->Type)) {

               if ((*(PULONG)pKeyValInfo->Data) == 0) {
                  retval = FALSE;
               }

           }

           NtClose(hKey);
       }
   }

   // Free up the buffers we allocated
   // Need to zero out the buffers, because some apps (MS Internet Assistant)
   // won't install if the heap is not zero filled.
   if (pKeyValInfo) {
       memset(pKeyValInfo, 0, ulcbuf);
       RtlFreeHeap( RtlProcessHeap(), 0, pKeyValInfo );
   }

   return(retval);

}

BOOL IsSystemLUID(VOID)
{
    HANDLE      TokenHandle;
    UCHAR       TokenInformation[ sizeof( TOKEN_STATISTICS ) ];
    ULONG       ReturnLength;
    LUID        CurrentLUID = { 0, 0 };
    LUID        SystemLUID = SYSTEM_LUID;
    NTSTATUS Status;

    if ( CurrentLUID.LowPart == 0 && CurrentLUID.HighPart == 0 ) {

        Status = NtOpenProcessToken( NtCurrentProcess(),
                                     TOKEN_QUERY,
                                     &TokenHandle );
        if ( !NT_SUCCESS( Status ) )
            return(TRUE);

        NtQueryInformationToken( TokenHandle, TokenStatistics, &TokenInformation,
                                 sizeof(TokenInformation), &ReturnLength );
        NtClose( TokenHandle );

        RtlCopyLuid(&CurrentLUID,
                    &(((PTOKEN_STATISTICS)TokenInformation)->AuthenticationId));
    }

    if (RtlEqualLuid(&CurrentLUID, &SystemLUID)) {
        return(TRUE);
    } else {
        return(FALSE );
    }
}


void InitializeTermsrvFpns(void)
{

    HANDLE          dllHandle;

    if (IsTerminalServerCompatible() ||
       (IsSystemLUID()) ||
       (!IsTSAppCompatEnabled())) {
        return;
    }

    //
    // Load Terminal Server application compatibility dll
    //
    dllHandle = LoadLibraryW(L"tsappcmp.dll");

    if (dllHandle) {

        gpTermsrvFormatObjectName =
            (PTERMSRVFORMATOBJECTNAME)GetProcAddress(dllHandle,"TermsrvFormatObjectName");

        gpTermsrvGetComputerName =
            (PTERMSRVGETCOMPUTERNAME)GetProcAddress(dllHandle,"TermsrvGetComputerName");

        gpTermsrvAdjustPhyMemLimits =
                (PTERMSRVADJUSTPHYMEMLIMITS)GetProcAddress(dllHandle,"TermsrvAdjustPhyMemLimits");

        gpTermsrvGetWindowsDirectoryA =
                (PTERMSRVGETWINDOWSDIRECTORYA)GetProcAddress(dllHandle,"TermsrvGetWindowsDirectoryA");

        gpTermsrvGetWindowsDirectoryW =
                (PTERMSRVGETWINDOWSDIRECTORYW)GetProcAddress(dllHandle,"TermsrvGetWindowsDirectoryW");

        gpTermsrvConvertSysRootToUserDir =
                (PTERMSRVCONVERTSYSROOTTOUSERDIR)GetProcAddress(dllHandle,"TermsrvConvertSysRootToUserDir");

        gpTermsrvBuildIniFileName =
                (PTERMSRVBUILDINIFILENAME)GetProcAddress(dllHandle,"TermsrvBuildIniFileName");

        gpTermsrvCORIniFile =
                (PTERMSRVCORINIFILE)GetProcAddress(dllHandle,"TermsrvCORIniFile");

        gpGetTermsrCompatFlags =
                (PGETTERMSRCOMPATFLAGS)GetProcAddress(dllHandle,"GetTermsrCompatFlags");

        gpTermsrvBuildSysIniPath =
                (PTERMSRVBUILDSYSINIPATH)GetProcAddress(dllHandle,"TermsrvBuildSysIniPath");

        gpTermsrvCopyIniFile =
        (PTERMSRVCOPYINIFILE)GetProcAddress(dllHandle,"TermsrvCopyIniFile");

        gpTermsrvGetString =
        (PTERMSRVGETSTRING)GetProcAddress(dllHandle,"TermsrvGetString");

        gpTermsrvLogInstallIniFile =
        (PTERMSRVLOGINSTALLINIFILE)GetProcAddress(dllHandle,"TermsrvLogInstallIniFile");


    }
}


/*****************************************************************************
 *
 *  TermsrvAppInstallMode
 *
 *   Returns whether the system is in Install mode or not
 *
 * ENTRY:
 *   Param1 (input/output)
 *     Comments
 *
 * EXIT:
 *   STATUS_SUCCESS - no error
 *
 ****************************************************************************/

WINBASEAPI
BOOL
WINAPI
TermsrvAppInstallMode( VOID )
{

    if ( BaseStaticServerData->fTermsrvAppInstallMode )
        return( TRUE );

    return( FALSE );
}


/*****************************************************************************
 *
 *  SetTermsrvAppInstallMode
 *
 *   Turns App install mode on or off. Default is off
 *
 * ENTRY:
 *   Param1 (input/output)
 *     Comments
 *
 * EXIT:
 *   STATUS_SUCCESS - no error
 *
 ****************************************************************************/

WINBASEAPI
BOOL
WINAPI
SetTermsrvAppInstallMode( BOOL bState )
{
    typedef BOOL ( APIENTRY  Func_CheckTokenMembership )( HANDLE , PSID , PBOOL);
    BOOL     rc;
    NTSTATUS Status;
    PSID pSid = NULL ;
    SID_IDENTIFIER_AUTHORITY SidAuthority = SECURITY_NT_AUTHORITY;

    HINSTANCE  dllHandle;
    
    if ( !( IsTerminalServer() && IsTSAppCompatEnabled() ) )
    {
        return FALSE;
    }


    dllHandle = LoadLibraryW(L"advapi32.dll");
    
    if (dllHandle)
    {
        Func_CheckTokenMembership     *fPtr;
        fPtr =  (Func_CheckTokenMembership * )GetProcAddress(dllHandle,"CheckTokenMembership");
        if (fPtr)
        {
            Status = RtlAllocateAndInitializeSid(
                    &SidAuthority,
                    2,
                    SECURITY_BUILTIN_DOMAIN_RID,
                    DOMAIN_ALIAS_RID_ADMINS,
                    0, 0, 0, 0, 0, 0,
                    &pSid
                    );

            if (NT_SUCCESS(Status))
            {
                BOOL    FoundAdmin;
                if ( fPtr (NULL, pSid , &FoundAdmin)) 
                {
                    if (FoundAdmin) 
                    {
                    // caller is admin, go ahead

                    #if defined(BUILD_WOW6432)
                        Status = CsrBasepSetTermsrvAppInstallMode(bState);
                    #else
                        BASE_API_MSG m;
                        PBASE_SET_TERMSRVAPPINSTALLMODE c= (PBASE_SET_TERMSRVAPPINSTALLMODE)&m.u.SetTermsrvAppInstallMode;

                        c->bState = bState;
                        Status = CsrClientCallServer((PCSR_API_MSG)&m, NULL,
                                                     CSR_MAKE_API_NUMBER(BASESRV_SERVERDLL_INDEX,
                                                     BasepSetTermsrvAppInstallMode),
                                                     sizeof( *c ));
                    #endif

                        if ( NT_SUCCESS( Status ) ) 
                        {
                            //
                            // Load tsappcmp.dll
                            //
                            if (gpTermsrvUpdateAllUserMenu == NULL) 
                            {
                                HANDLE          dllHandle;

                                //
                                // Load Terminal Server application compatibility dll
                                //
                                dllHandle = LoadLibraryW(L"tsappcmp.dll");

                                if (dllHandle) 
                                {
                                    gpTermsrvUpdateAllUserMenu =
                                            (PTERMSRVUPDATEALLUSERMENU)GetProcAddress(dllHandle,"TermsrvUpdateAllUserMenu");
                                }
                            }

                            if (gpTermsrvUpdateAllUserMenu) 
                            {
                                gpTermsrvUpdateAllUserMenu( bState == TRUE ? 0 : 1 );
                            }
                            rc = TRUE;
                        }
                        else
                        {
                            SetLastError( RtlNtStatusToDosError( Status ) );
                            rc = FALSE; 
                        }
                                   
                    }
                    else 
                    {
                        // user is not admin
                        SetLastError( ERROR_ACCESS_DENIED );
                        rc = FALSE;
                    }
                }
                else
                {
                    // call to CheckTokenMembership() failed, it set the last error
                    rc = FALSE;
                }
            }
            else
            {
                // attempt to allocate and init SID failed.
                SetLastError( RtlNtStatusToDosError( Status ) );
                rc = FALSE; 
            }
        }                       
        else
        {
            // function not found, GetProc() set the last error.
            rc = FALSE;
        }
        FreeLibrary( dllHandle );
    }
    else
    {
        // library not found, LoadLib() set the last error
        rc = FALSE;
    }

    if (pSid) 
    {
        RtlFreeSid( pSid );
    }

    return rc;
}


/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
/*
  Ini File syncing/merging code

*/
//////////////////////////////////////////////////////////////////////////////
/* External Functions */
NTSTATUS
BaseDllOpenIniFileOnDisk(
    PINIFILE_PARAMETERS a
    );

NTSTATUS
BaseDllWriteKeywordValue(
    IN PINIFILE_PARAMETERS a,
    IN PUNICODE_STRING VariableName OPTIONAL
    );

NTSTATUS
BaseDllCloseIniFileOnDisk(
    IN PINIFILE_PARAMETERS a
    );

NTSTATUS
BaseDllFindSection(
    IN PINIFILE_PARAMETERS a
    );

NTSTATUS
BaseDllFindKeyword(
    IN PINIFILE_PARAMETERS a
    );

NTSTATUS
TermsrvIniSyncLoop( HANDLE SrcHandle,
                PINIFILE_PARAMETERS a,
                PBOOLEAN pfIniUpdated
              );
NTSTATUS
TermsrvGetSyncTime( PUNICODE_STRING pSysIniPath,
                PUNICODE_STRING pUserBasePath,
                PLARGE_INTEGER  pLastSyncTime
              );

NTSTATUS
TermsrvPutSyncTime( PUNICODE_STRING pSysIniPath,
                PUNICODE_STRING pUserBasePath,
                PLARGE_INTEGER  pLastSyncTime
              );


/*****************************************************************************
 *
 *  TermsrvGetSyncTime
 *
 *  This routine will get the time of the system ini file that the user ini
 *  file was last sync'd with.
 *
 * ENTRY:
 *   PUNICODE_STRING pSysIniPath (In) - NT fully qualified system ini path
 *   PUNICODE_STRING pUserBasePath (In) - NT fully qualified user directory path
 *   PLARGE_INTEGER pLastSyncTime (OUT) - ptr to return last sync time
 *
 * EXIT:
 *   STATUS_SUCCESS - successfully retrieved the last sync time from infile.upd
 *
 ****************************************************************************/

NTSTATUS
TermsrvGetSyncTime(
    PUNICODE_STRING pSysIniPath,
    PUNICODE_STRING pUserBasePath,
    PLARGE_INTEGER  pLastSyncTime)
{
    NTSTATUS Status;
    HANDLE   hUpdate = NULL;
    OBJECT_ATTRIBUTES ObjAUpd;
    IO_STATUS_BLOCK   Iosb;
    FILE_STANDARD_INFORMATION StandardInfo;
    WCHAR             wcUpdateFile[MAX_PATH+1];
    UNICODE_STRING    UniUpdateName = {0,
                                       sizeof(wcUpdateFile),
                                       wcUpdateFile};
    PCHAR             pBuff = NULL, pBuffEnd;
    PWCH              pwch;
    SIZE_T            ulBuffSize;
    LONG              lresult;

    if (!pSysIniPath) {
        return STATUS_INVALID_PARAMETER_1;
    }
    if (!pUserBasePath) {
        return STATUS_INVALID_PARAMETER_2;
    }
    if (!pLastSyncTime) {
        return STATUS_INVALID_PARAMETER_3;
    }

    Status = RtlAppendUnicodeStringToString(&UniUpdateName,
                                            pUserBasePath);
    if (NT_SUCCESS(Status)) {
      Status = RtlAppendUnicodeToString(&UniUpdateName,
                                        L"\\inifile.upd");
    }

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

    pLastSyncTime->LowPart = 0;
    pLastSyncTime->HighPart = 0;

    InitializeObjectAttributes( &ObjAUpd,
                                &UniUpdateName,
                                OBJ_CASE_INSENSITIVE,
                                NULL,
                                NULL
                              );

    // Open the update log
    Iosb.Status = STATUS_SUCCESS;
    Status = NtOpenFile( &hUpdate,
                         FILE_GENERIC_READ,
                         &ObjAUpd,
                         &Iosb,
                         FILE_SHARE_READ|FILE_SHARE_WRITE,
                         FILE_SYNCHRONOUS_IO_NONALERT    // OpenOptions
                       );

    // Get the size of the file
    if (NT_SUCCESS( Status )) {
        Status = NtQueryInformationFile( hUpdate,
                                         &Iosb,
                                         &StandardInfo,
                                         sizeof(StandardInfo),
                                         FileStandardInformation
                                       );
        if (Status == STATUS_BUFFER_OVERFLOW) {
            Status = STATUS_SUCCESS;
        }
#if DBG
        else if (!NT_SUCCESS( Status )) {
            DbgPrint( "TermsrvGetSyncTime: Unable to QueryInformation for %wZ - Status == %x\n", &UniUpdateName, Status );
        }
#endif
    }

    if (NT_SUCCESS( Status )) {
        ulBuffSize = StandardInfo.EndOfFile.LowPart + 4 * sizeof(WCHAR);
        Status = NtAllocateVirtualMemory( NtCurrentProcess(),
                                          &pBuff,
                                          0,
                                          &ulBuffSize,
                                          MEM_RESERVE,
                                          PAGE_READWRITE
                                        );
    }

    if (NT_SUCCESS( Status )) {
        Status = NtAllocateVirtualMemory( NtCurrentProcess(),
                                          &pBuff,
                                          0,
                                          &ulBuffSize,
                                          MEM_COMMIT,
                                          PAGE_READWRITE
                                        );
    }

    if (NT_SUCCESS( Status )) {
        Status = NtReadFile( hUpdate,
                             NULL,
                             NULL,
                             NULL,
                             &Iosb,
                             pBuff,
                             StandardInfo.EndOfFile.LowPart,
                             NULL,
                             NULL
                           );

        if ( Status == STATUS_PENDING ) {
            Status = NtWaitForSingleObject( hUpdate, FALSE, NULL );
        }

        if ( NT_SUCCESS(Status) ) {
            // Get final I/O status
            Status = Iosb.Status;
        }
    }

    // Look for this ini file in the list
    if (NT_SUCCESS(Status)) {

        pwch = (PWCHAR)pBuff;
        pBuffEnd = pBuff + StandardInfo.EndOfFile.LowPart;

        // Look for the file in the sorted list
        while ((pwch < (PWCHAR)pBuffEnd) &&
               ((lresult = _wcsicmp(pwch, pSysIniPath->Buffer)) < 0)) {
            pwch += wcslen(pwch) + sizeof(LARGE_INTEGER)/sizeof(WCHAR) + 1;
        }

        if ((pwch < (PWCHAR)pBuffEnd) && (lresult == 0)) {
            pwch += wcslen(pwch) + 1;
            pLastSyncTime->LowPart = ((PLARGE_INTEGER)pwch)->LowPart;
            pLastSyncTime->HighPart = ((PLARGE_INTEGER)pwch)->HighPart;
        }
    }

    if (NT_SUCCESS(Status) ) {
        // Get final I/O status
        Status = Iosb.Status;
    }

    if (pBuff) {
        NtFreeVirtualMemory( NtCurrentProcess(),
                             &pBuff,
                             &ulBuffSize,
                             MEM_RELEASE
                           );
    }

    if (hUpdate) {
        Status = NtClose( hUpdate );
    }
    return(Status);
}


/*****************************************************************************
 *
 *  TermsrvPutSyncTime
 *
 *  This routine will write the time of the system ini file that the user ini
 *  file was last sync'd with.
 *
 * ENTRY:
 *   PUNICODE_STRING pSysIniPath (In) - NT fully qualified system ini path
 *   PUNICODE_STRING pUserBasePath (In) - NT fully qualified user directory path
 *   PLARGE_INTEGER pLastSyncTime (OUT) - ptr to return last sync time
 *
 * EXIT:
 *   STATUS_SUCCESS - successfully stored the last sync time in infile.upd
 *
 ****************************************************************************/

NTSTATUS
TermsrvPutSyncTime(
    PUNICODE_STRING pSysIniPath,
    PUNICODE_STRING pUserBasePath,
    PLARGE_INTEGER  pLastSyncTime)
{
    NTSTATUS Status;
    HANDLE   hUpdate = NULL;
    OBJECT_ATTRIBUTES ObjAUpd;
    IO_STATUS_BLOCK   Iosb;
    FILE_STANDARD_INFORMATION StandardInfo;
    WCHAR             wcUpdateFile[MAX_PATH+1];
    UNICODE_STRING    UniUpdateName = {0,
                                       sizeof(wcUpdateFile),
                                       wcUpdateFile};
    PCHAR             pBuff = NULL, pBuffEnd;
    PWCH              pwch;
    SIZE_T            ulBuffSize;
    ULONG             ulLength;
    SIZE_T            ulRegionSize;
    LONG              lresult;
    LARGE_INTEGER     FileLength;
    FILE_POSITION_INFORMATION CurrentPos;

    if (!pSysIniPath) {
        return STATUS_INVALID_PARAMETER_1;
    }
    if (!pUserBasePath) {
        return STATUS_INVALID_PARAMETER_2;
    }
    if (!pLastSyncTime) {
        return STATUS_INVALID_PARAMETER_3;
    }

    Status = RtlAppendUnicodeStringToString(&UniUpdateName,
                                            pUserBasePath);
    if (NT_SUCCESS(Status)) {
      Status = RtlAppendUnicodeToString(&UniUpdateName,
                                        L"\\inifile.upd");
    }

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

    InitializeObjectAttributes( &ObjAUpd,
                                &UniUpdateName,
                                OBJ_CASE_INSENSITIVE,
                                NULL,
                                NULL
                              );

    // Open the update log
    Iosb.Status = STATUS_SUCCESS;
    Status = NtCreateFile( &hUpdate,
                             FILE_READ_DATA | FILE_WRITE_DATA |
                               FILE_READ_ATTRIBUTES | SYNCHRONIZE,
                             &ObjAUpd,
                             &Iosb,
                           NULL,                  // Allocation size
                           FILE_ATTRIBUTE_NORMAL, // dwFlagsAndAttributes
                             FILE_SHARE_WRITE,      // dwShareMode
                           FILE_OPEN_IF,          // CreateDisposition
                             FILE_SYNCHRONOUS_IO_NONALERT |
                               FILE_NON_DIRECTORY_FILE, // CreateFlags
                           NULL, // EaBuffer
                           0     // EaLength
                           );

    if (NT_SUCCESS( Status )) {
        Status = NtQueryInformationFile( hUpdate,
                                         &Iosb,
                                         &StandardInfo,
                                         sizeof(StandardInfo),
                                         FileStandardInformation
                                       );
        if (Status == STATUS_BUFFER_OVERFLOW) {
            Status = STATUS_SUCCESS;
        }
#if DBG
        else if (!NT_SUCCESS( Status )) {
            DbgPrint( "TermsrvPutLastSyncTime: Unable to QueryInformation for %wZ - Status == %x\n", &UniUpdateName, Status );
        }
#endif
    }

    if (NT_SUCCESS( Status )) {
        ulBuffSize = StandardInfo.EndOfFile.LowPart + 4 * sizeof(WCHAR);
        ulRegionSize = ulBuffSize + 0x1000; // Room for 4K of growth
        Status = NtAllocateVirtualMemory( NtCurrentProcess(),
                                          &pBuff,
                                          0,
                                          &ulRegionSize,
                                          MEM_RESERVE,
                                          PAGE_READWRITE
                                        );
    }

    if (NT_SUCCESS( Status )) {
        Status = NtAllocateVirtualMemory( NtCurrentProcess(),
                                          &pBuff,
                                          0,
                                          &ulBuffSize,
                                          MEM_COMMIT,
                                          PAGE_READWRITE
                                        );
    }

    if (NT_SUCCESS( Status ) && StandardInfo.EndOfFile.LowPart) {
        Status = NtReadFile( hUpdate,
                             NULL,
                             NULL,
                             NULL,
                             &Iosb,
                             pBuff,
                             StandardInfo.EndOfFile.LowPart,
                             NULL,
                             NULL
                           );

        if ( Status == STATUS_PENDING ) {
            Status = NtWaitForSingleObject( hUpdate, FALSE, NULL );
        }

        if ( NT_SUCCESS(Status) ) {
            // Get final I/O status
            Status = Iosb.Status;
        }
    }

    // Look for this ini file in the list
    if (NT_SUCCESS(Status)) {

        pwch = (PWCHAR)pBuff;
        pBuffEnd = pBuff + StandardInfo.EndOfFile.LowPart;

        // Look for the file in the list
        while ((pwch < (PWCHAR)pBuffEnd) &&
               ((lresult = _wcsicmp(pwch, pSysIniPath->Buffer)) < 0)) {
            pwch += wcslen(pwch) + (sizeof(LARGE_INTEGER)/sizeof(WCHAR)) + 1;
        }

        // If the ini file is already in the file, just update the time
        if ((pwch < (PWCHAR)pBuffEnd) && (lresult == 0)) {
            pwch += wcslen(pwch) + 1;
            ((PLARGE_INTEGER)pwch)->LowPart = pLastSyncTime->LowPart;
            ((PLARGE_INTEGER)pwch)->HighPart = pLastSyncTime->HighPart;

        } else {                    // Ini file not in list

            // Figure out the size to grow the file
            ulLength = (pSysIniPath->Length + 2) + sizeof(LARGE_INTEGER);
            ulBuffSize += ulLength;

            // Grow the memory region
            Status = NtAllocateVirtualMemory( NtCurrentProcess(),
                                              &pBuff,
                                              0,
                                              &ulBuffSize,
                                              MEM_COMMIT,
                                              PAGE_READWRITE
                                            );

            if (NT_SUCCESS(Status)) {
                // figure out where the entry goes in the file
                if (pwch < (PWCHAR)pBuffEnd) {
                    RtlMoveMemory( pwch+(ulLength/sizeof(WCHAR)),
                                   pwch,
                                   pBuffEnd - (PCHAR)pwch
                                 );
                }

                pBuffEnd += ulLength;
                wcscpy(pwch, pSysIniPath->Buffer);
                pwch += (pSysIniPath->Length + 2)/sizeof(WCHAR);
                ((PLARGE_INTEGER)pwch)->LowPart = pLastSyncTime->LowPart;
                ((PLARGE_INTEGER)pwch)->HighPart = pLastSyncTime->HighPart;
            }
        }
    }

    if (NT_SUCCESS(Status)) {
        CurrentPos.CurrentByteOffset.LowPart = 0;
        CurrentPos.CurrentByteOffset.HighPart = 0;
        Status = NtSetInformationFile( hUpdate,
                                       &Iosb,
                                       &CurrentPos,
                                       sizeof(CurrentPos),
                                       FilePositionInformation
                                     );

        Status = NtWriteFile( hUpdate,
                              NULL,
                              NULL,
                              NULL,
                              &Iosb,
                              pBuff,
                              (ULONG)(pBuffEnd - pBuff + 1),
                              NULL,
                              NULL
                            );

        if( Status == STATUS_PENDING ) {
            Status = NtWaitForSingleObject( hUpdate, FALSE, NULL );
        }

        if( NT_SUCCESS(Status) ) {
            // Get final I/O status
            Status = Iosb.Status;
        }
    }

    if (NT_SUCCESS( Status )) {
        FileLength.LowPart = (ULONG)(pBuffEnd - pBuff);
        FileLength.HighPart = 0;
        Status = NtSetInformationFile( hUpdate,
                                       &Iosb,
                                       &FileLength,
                                       sizeof( FileLength ),
                                       FileEndOfFileInformation
                                     );
    }

    if (pBuff) {
        NtFreeVirtualMemory( NtCurrentProcess(),
                             &pBuff,
                             &ulRegionSize,
                             MEM_RELEASE
                           );
    }

    if (hUpdate) {
        Status = NtClose( hUpdate );
    }

    return(Status);
}


/*****************************************************************************
 *
 *  TermsrvCheckIniSync
 *
 *  This routine will get the time of the system ini file that the user ini
 *  file was last sync'd with.
 *
 * ENTRY:
 *   PUNICODE_STRING pSysIniPath (In) - NT fully qualified system ini path
 *   PUNICODE_STRING pUserBasePath (In) - NT fully qualified user directory path
 *   BOOLEAN fGet (In) - TRUE means to get last sync time, FALSE means to write it
 *   PLARGE_INTEGER pLastSyncTime (OUT) - ptr to return last sync time
 *
 * EXIT:
 *   TRUE  - User ini file should be sync'd
 *   FALSE - User ini file should be sync'd
 *
 ****************************************************************************/

BOOLEAN
TermsrvCheckIniSync(
    PUNICODE_STRING pSysIniPath,
    PUNICODE_STRING pUserBasePath)
{
    LARGE_INTEGER          LastSyncTime;
    OBJECT_ATTRIBUTES      objaIni;
    FILE_NETWORK_OPEN_INFORMATION BasicInfo;
    NTSTATUS               Status;

    // Get the last sync time of the ini file from the inifile.upd file
    TermsrvGetSyncTime(pSysIniPath, pUserBasePath, &LastSyncTime);

    // Get the last write time of the system ini file
    InitializeObjectAttributes(
        &objaIni,
        pSysIniPath,
        OBJ_CASE_INSENSITIVE,
        NULL,
        NULL
        );

    // Now query it
    Status = NtQueryFullAttributesFile( &objaIni, &BasicInfo );

    // If we couldn't get the time or the system ini file has been updated
    // since we last sync'd, return TRUE
    if (!NT_SUCCESS(Status) ||
        ((BasicInfo.LastWriteTime.HighPart > LastSyncTime.HighPart) ||
         ((BasicInfo.LastWriteTime.HighPart == LastSyncTime.HighPart) &&
         (BasicInfo.LastWriteTime.LowPart > LastSyncTime.LowPart)))) {
        return(TRUE);
    }
    return(FALSE);
}
/*****************************************************************************
 *
 *  TermsrvDoesFileExist
 *
 *   Returns whether the file exists or not.
 *
 *   Must use NT, not WIN32 pathnames.
 *
 * ENTRY:
 *   Param1 (input/output)
 *     Comments
 *
 * EXIT:
 *   STATUS_SUCCESS - no error
 *
 ****************************************************************************/

BOOL
TermsrvDoesFileExist(
    PUNICODE_STRING pFileName
    )
{
    NTSTATUS Status;
    FILE_BASIC_INFORMATION BasicInfo;
    OBJECT_ATTRIBUTES Obja;

    InitializeObjectAttributes(
        &Obja,
        pFileName,
        OBJ_CASE_INSENSITIVE,
        NULL,
        NULL
        );

    /*
     * Now query it
     */
    Status = NtQueryAttributesFile( &Obja, &BasicInfo );

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

    return( FALSE );
}



/*****************************************************************************
 *
 *  TermsrvSyncUserIniFile
 *
 *   This routine will check that the user's ini file is "sync'd" with the
 *   system version of the ini file.  This means that it walks through the
 *   system ini file and checks that there is a corresponding entry in the
 *   user's ini file.
 *
 * ENTRY:
 *   IN PINIFILE_PARAMETERS a - ptr to inifile structure
 *
 * EXIT:
 *   True  - Ini file updated
 *   False - User Ini file was unchanged
 *
 ****************************************************************************/
BOOL TermsrvSyncUserIniFile(PINIFILE_PARAMETERS a)
{
    WCHAR          wcIniPath[MAX_PATH+1];
    UNICODE_STRING IniFilePath = {MAX_PATH,
                                  MAX_PATH+1,
                                  wcIniPath};
    PWCH           pwch, pwcIniName;
    UNICODE_STRING UniSysPath;
    UNICODE_STRING UserBasePath;
    NTSTATUS       Status;
    HANDLE         SrcHandle;
    ULONG          ulCompatFlags;
    OBJECT_ATTRIBUTES SrcObja;
    IO_STATUS_BLOCK   SrcIosb;
    INIFILE_OPERATION OrigOperation;
    BOOLEAN           OrigWrite, OrigMultiValue, OrigUnicode,
                      OrigWriteOperation, fIniUpdated = FALSE;
    ANSI_STRING       OrigAppName, OrigVarName;
    ULONG             OrigResultChars, OrigResultMaxChars;
    LPSTR             OrigResultBuffer;
    OBJECT_ATTRIBUTES      objaIni;
    FILE_NETWORK_OPEN_INFORMATION BasicInfo;

    // If INI file mapping is not on, return
    if (IsSystemLUID() || TermsrvAppInstallMode()) {
        return(FALSE);
    }

    // Build full system path to the Ini file, and get BasePath to user dir
    if ((gpTermsrvBuildSysIniPath == NULL) || !(gpTermsrvBuildSysIniPath(&a->NtFileName, &UniSysPath, &UserBasePath))) {
        #if DBG
        //DbgPrint("TermsrvSyncUserIniFile: Error building Sys Ini Path!\n");
        #endif
        return(FALSE);
    }

    // Get the ini file name
    pwch = wcsrchr(a->NtFileName.Buffer, L'\\') ;
    if (pwch == NULL) {
        return FALSE;
    } else{
        pwch++;
    }

    pwcIniName = RtlAllocateHeap( RtlProcessHeap(),
                                  0,
                                  (wcslen(pwch) + 1)*sizeof(WCHAR));
    if (pwcIniName == NULL) {
        return FALSE;
    }

    wcscpy(pwcIniName, pwch);
    pwch = wcsrchr(pwcIniName, L'.');
    if (pwch) {
        *pwch = L'\0';
    }

    if (gpGetTermsrCompatFlags) {
        gpGetTermsrCompatFlags(pwcIniName, &ulCompatFlags, CompatibilityIniFile);
    } else {
        return FALSE;
    }

    // If the INISYNC compatibility flag is set in the registry and the
    // system version of the ini file exists, sync up the user version
    if (((ulCompatFlags & (TERMSRV_COMPAT_INISYNC | TERMSRV_COMPAT_WIN16)) ==
         (TERMSRV_COMPAT_INISYNC | TERMSRV_COMPAT_WIN16)) &&
        TermsrvDoesFileExist(&UniSysPath) &&
        TermsrvCheckIniSync(&UniSysPath, &UserBasePath)) {

        // Create a backup copy of the original file (inifile.ctx)
        wcscpy(wcIniPath, UserBasePath.Buffer);
        if (UserBasePath.Buffer[UserBasePath.Length/sizeof(WCHAR) - 1] != L'\\')
            wcscat(wcIniPath, L"\\");
        wcscat(wcIniPath, pwcIniName);
        wcscat(wcIniPath, L".ctx");
        IniFilePath.Length = wcslen(wcIniPath)*sizeof(WCHAR);

        if (gpTermsrvCopyIniFile) {
            Status = gpTermsrvCopyIniFile(&a->NtFileName, NULL, &IniFilePath);
    #if DBG
            if (!NT_SUCCESS(Status)) {
                DbgPrint("TermsrvSyncUserIniFile: Error 0x%x creating backup ini file %ws\n",
                         Status,
                         wcIniPath);
            }
    #endif
        } else {
            return FALSE;
            }

        // Check that each entry in the system version is in the user's version
        InitializeObjectAttributes(&SrcObja,
                                   &UniSysPath,
                                   OBJ_CASE_INSENSITIVE,
                                   NULL,
                                   NULL);

        // Open the src
        SrcIosb.Status = STATUS_SUCCESS;
        Status = NtOpenFile(&SrcHandle,
                             FILE_GENERIC_READ,
                            &SrcObja,
                            &SrcIosb,
                            FILE_SHARE_READ|FILE_SHARE_WRITE,
                            FILE_SYNCHRONOUS_IO_NONALERT);

        if( NT_SUCCESS(Status) ) {
            // Get final I/O status
                  Status = SrcIosb.Status;
        }

        if( !NT_SUCCESS(Status) ) {
#if DBG
            DbgPrint("TermsrvSyncUserIniFile: Error 0x%x opening SrcFile %ws\n",
                     Status,
                     &UniSysPath.Buffer);
#endif
            goto Cleanup;
        }

        // Save the original values
        OrigOperation = a->Operation;
        OrigMultiValue = a->MultiValueStrings;
        OrigAppName = a->ApplicationName;
        OrigVarName = a->VariableName;
        OrigResultChars = a->ResultChars;
        OrigResultMaxChars = a->ResultMaxChars;
        OrigResultBuffer = a->ResultBuffer;
        OrigUnicode = a->Unicode;
        OrigWriteOperation = a->WriteOperation;

        // Set up the open for writes
        a->WriteOperation = TRUE;
        a->Operation = WriteKeyValue;
        a->MultiValueStrings = FALSE;
        a->Unicode = FALSE;

        Status = BaseDllOpenIniFileOnDisk( a );

        if( !NT_SUCCESS(Status) ) {
#if DBG
            DbgPrint("TermsrvSyncUserIniFile: Error 0x%x opening DestFile %ws\n",
                     Status,
                     &a->NtFileName.Buffer);
#endif
            NtClose( SrcHandle );
                goto Cleanup;
        }

        // set the data up for writing
        a->TextEnd = (PCHAR)a->IniFile->BaseAddress +
                            a->IniFile->EndOfFile;
        a->TextCurrent = a->IniFile->BaseAddress;

        // Make sure entries in system ini file are in user ini file
        Status = TermsrvIniSyncLoop( SrcHandle, a, &fIniUpdated );
#if DBG
        if( !NT_SUCCESS(Status) ) {
            DbgPrint("TermsrvSyncUserIniFile: Error 0x%x Doing sync loop\n",Status);
        }
#endif

        // Close the file handles
        NtClose( SrcHandle );
        BaseDllCloseIniFileOnDisk( a );

        // Restore the variables in the ini file structure
        a->Operation = OrigOperation;
        a->MultiValueStrings = OrigMultiValue;
        a->ApplicationName = OrigAppName;
        a->VariableName = OrigVarName;
        a->ResultChars = OrigResultChars;
        a->ResultMaxChars = OrigResultMaxChars;
        a->ResultBuffer = OrigResultBuffer;
        a->WriteOperation = FALSE;
        a->Unicode = OrigUnicode;
        a->WriteOperation = OrigWriteOperation;

        // Get the last write time of the system ini file
        InitializeObjectAttributes( &objaIni,
                                    &UniSysPath,
                                    OBJ_CASE_INSENSITIVE,
                                    NULL,
                                    NULL
                                  );

        // Now query it
        Status = NtQueryFullAttributesFile( &objaIni, &BasicInfo );

        // Update the sync time in the inisync file
        if (NT_SUCCESS(Status)) {
            TermsrvPutSyncTime( &UniSysPath,
                            &UserBasePath,
                            &BasicInfo.LastWriteTime
                          );
        }
    }

Cleanup:
    // Free the unicode buffers
    RtlFreeHeap( RtlProcessHeap(), 0, UniSysPath.Buffer );
    RtlFreeHeap( RtlProcessHeap(), 0, UserBasePath.Buffer );
    RtlFreeHeap( RtlProcessHeap(), 0, pwcIniName);

    return(fIniUpdated);
}


/*****************************************************************************
 *
 *  TermsrvIniSyncLoop
 *
 *  This routine will verify that there's a corresponding entry in the user's
 *  ini file for each entry in the system ini file.
 *
 * ENTRY:
 *   HANDLE SrcHandle (INPUT)  - Handle to system ini file
 *   PINIFILE_PARAMETERS a (INPUT) - pointer to current ini file structure
 *   PBOOLEAN pfIniUpdated (OUTPUT) - Returns TRUE if user ini file is modified
 *
 * EXIT:
 *   STATUS_SUCCESS - no error
 *
 ****************************************************************************/

NTSTATUS
TermsrvIniSyncLoop(HANDLE SrcHandle,
               PINIFILE_PARAMETERS a,
               PBOOLEAN pfIniUpdated)
{
    PCHAR pStr;
    NTSTATUS Status;
    ULONG StringSize;
    CHAR  IOBuf[512];
    ULONG IOBufSize = 512;
    ULONG IOBufIndex = 0;
    ULONG IOBufFillSize = 0;
    ANSI_STRING AnsiSection;
    PCH pch;
    PVOID pSection, origbase;

    AnsiSection.Buffer = NULL;
    *pfIniUpdated = FALSE;

    while( 1 ) {

        pStr = NULL;
        StringSize = 0;

        if (gpTermsrvGetString == NULL) {
            return STATUS_UNSUCCESSFUL;
        }

        // Get a string from the source ini file
        Status = gpTermsrvGetString(SrcHandle,
                               &pStr,
                               &StringSize,
                               IOBuf,
                               IOBufSize,
                              &IOBufIndex,
                               &IOBufFillSize);

        if( !NT_SUCCESS(Status) ) {

            ASSERT( pStr == NULL );

            if( Status == STATUS_END_OF_FILE ) {
                Status = STATUS_SUCCESS;
            }
            if (AnsiSection.Buffer) {
                RtlFreeHeap( RtlProcessHeap(), 0, AnsiSection.Buffer );
            }

            a->IniFile->UpdateEndOffset = a->IniFile->EndOfFile;
            return( Status );
        }

        // Make sure we got some actual data
        ASSERT( pStr != NULL );

        // Is this a section name?
        if (*pStr == '[') {
            if (AnsiSection.Buffer) {
                RtlFreeHeap( RtlProcessHeap(), 0, AnsiSection.Buffer );
                AnsiSection.Buffer = NULL;
            }
            pch = strrchr(pStr, ']');
            if (pch) {
                AnsiSection.MaximumLength = (USHORT)(pch - pStr);
                *pch = '\0';
            } else {
                AnsiSection.Length = (USHORT)strlen(pStr);
            }
            AnsiSection.Length = AnsiSection.MaximumLength - 1;
            AnsiSection.Buffer = RtlAllocateHeap(RtlProcessHeap(),
                                                 0,
                                                 AnsiSection.MaximumLength);
            if (!AnsiSection.Buffer) {
                return STATUS_INSUFFICIENT_RESOURCES;
            }
            strcpy(AnsiSection.Buffer, pStr+1);
            a->ApplicationName = AnsiSection;

            a->TextCurrent = a->IniFile->BaseAddress;   // reset file pointer

            // See if the section already exists, if so save the start of it
            Status = BaseDllFindSection( a );
            if (NT_SUCCESS(Status)) {
                pSection = a->TextCurrent;
            } else {
                pSection = NULL;
            }

        // If it's not a comment, see if the entry is in the user's ini file
        } else if (*pStr != ';') {

            pch = strchr(pStr, '=');
            if (pch) {
                a->VariableName.Length = a->VariableName.MaximumLength =
                    (USHORT)(pch - pStr);
                a->VariableName.Buffer = pStr;
                a->ValueBuffer = (++pch);
                a->ValueLength = 0;
                while (*pch && (*pch != 0xa) && (*pch != 0xd)) {
                    pch++;
                    a->ValueLength++;
                }


                // If the section exists, check for the keyword in user's ini
                if (pSection) {
                    a->TextCurrent = pSection;
                    Status = BaseDllFindKeyword( a );
                }

                // If variable isn't found, write it out
                if (!pSection || !NT_SUCCESS( Status )) {

                    origbase = a->TextCurrent = a->IniFile->BaseAddress;
                    Status = BaseDllWriteKeywordValue( a, NULL );
                    a->TextEnd = (PCHAR)a->IniFile->BaseAddress +
                                        a->IniFile->EndOfFile;
                    if (!NT_SUCCESS(Status)) {
                              #if DBG
                              DbgPrint("TermsrvIniSyncLoop: Error 0x%x write Key Value\n",
                                  Status);
                              #endif
                        a->IniFile->UpdateEndOffset = a->IniFile->EndOfFile;
                        RtlFreeHeap( RtlProcessHeap(), 0, pStr );
                        if (AnsiSection.Buffer) {
                            RtlFreeHeap(RtlProcessHeap(),
                                        0,
                                        AnsiSection.Buffer);
                        }
                        return(Status);
                    }
                    *pfIniUpdated = TRUE;
                    if (origbase != a->IniFile->BaseAddress) {
                        a->TextCurrent = a->IniFile->BaseAddress;
                        Status = BaseDllFindSection( a );
                        if (NT_SUCCESS(Status)) {
                            pSection = a->TextCurrent;
                        } else {
                            pSection = NULL;
                        }
                    }
                }
            }
        }


    } // end while(1)
}

/******************************************************************************
 *
 *  GetPerUserWindowsDirectory
 *
 *
 *
 *****************************************************************************/
ULONG
GetPerUserWindowsDirectory(PWCHAR TermSrvWindowsPath, ULONG len)
{
    WCHAR Buf[MAX_PATH+1];
    UNICODE_STRING BaseHomePathVariableName, BaseHomeDriveVariableName;
    NTSTATUS Status;
    UNICODE_STRING Path;


    if( !IsTSAppCompatEnabled() || IsSystemLUID() || (len < MAX_PATH) ) {
        return 0;
    }

    /*
     * Check for HOMEDRIVE and HOMEPATH
     */
    RtlInitUnicodeString(&BaseHomeDriveVariableName,L"HOMEDRIVE");
    RtlInitUnicodeString(&BaseHomePathVariableName,L"HOMEPATH");


    Path.Buffer = Buf;
    Path.Length = 0;
    Path.MaximumLength = (MAX_PATH * sizeof(WCHAR)) - (9 * sizeof(WCHAR)); //MAX_PATH - wcslen(L"\\WINDOWS") + 1

    if (NT_SUCCESS(Status = RtlQueryEnvironmentVariable_U( NULL, &BaseHomeDriveVariableName,
                                        &Path))) {
      Path.Buffer[Path.Length] = UNICODE_NULL;
      wcscpy(TermSrvWindowsPath,Path.Buffer);

      Path.MaximumLength -= Path.Length;

      if (NT_SUCCESS(Status = RtlQueryEnvironmentVariable_U( NULL, &BaseHomePathVariableName,
                                          &Path))) {
         Path.Buffer[Path.Length] = UNICODE_NULL;
         wcscat(TermSrvWindowsPath,Path.Buffer);
      }

    }

    if (!NT_SUCCESS(Status)) {
#if DBG
       DbgPrint("GetPerUserWindowsDirectory Failed with Status %lx\n", Status);
#endif
       return 0;
    }

    /*
    * Add a trailing backslash if one's not already there
    */

    if (TermSrvWindowsPath[wcslen(TermSrvWindowsPath) -1 ] != L'\\') {
      wcscat(TermSrvWindowsPath,L"\\");
    }

    wcscat(TermSrvWindowsPath,L"WINDOWS");

    return( wcslen(TermSrvWindowsPath) );
}