/************************************************************************* * * inimap.c * * Handle Copy-On-Reference Ini File Mapping * * copyright notice: Copyright 1998 Micrsoft * * *************************************************************************/ #include "precomp.h" #pragma hdrstop #define LOCAL #include "regmap.h" //#include "basedll.h" #if DBG ULONG DbgPrint( PCH Format, ... ); #define DBGPRINT(x) DbgPrint x #if DBGTRACE #define TRACE0(x) DbgPrint x #define TRACE1(x) DbgPrint x #else #define TRACE0(x) #define TRACE1(x) #endif #else #define DBGPRINT(x) #define TRACE0(x) #define TRACE1(x) #endif #define IS_NEWLINE_CHAR( c ) ((c == 0x0D) || (c == 0x0A)) /* * INI_BUF_SIZE defines the maximum number of characters that can * be on a single INI file line. If a line contains more than this * number of characters, the additional characters will be lost. */ #define INI_BUF_SIZE 1024 /* Internal Functions */ BOOL TermsrvDoesFileExist( PUNICODE_STRING pFileName ); BOOL TermsrvBuildSysIniPath( PUNICODE_STRING pIniPath, PUNICODE_STRING pSysPath, PUNICODE_STRING pUserPath ); BOOL TermsrvCopyIniFile( PUNICODE_STRING pSysPath, PUNICODE_STRING pUserPath, PUNICODE_STRING pFileName ); BOOL TermsrvGetUnicodeRemainder( PUNICODE_STRING pFullPath, PUNICODE_STRING pPrefix, PUNICODE_STRING pRemainder ); NTSTATUS TermsrvIniCopyLoop( HANDLE SrcHandle, HANDLE DestHandle ); NTSTATUS TermsrvPutString( HANDLE DestHandle, PCHAR pStr, ULONG StringSize ); NTSTATUS TermsrvProcessBuffer( PCHAR *ppStr, PULONG pStrSize, PULONG pStrBufSize, PBOOL pSawNL, PCHAR pIOBuf, PULONG pIOBufIndex, PULONG pIOBufFillSize ); NTSTATUS TermsrvGetString( HANDLE SrcHandle, PCHAR *ppStringPtr, PULONG pStringSize, PCHAR pIOBuf, ULONG IOBufSize, PULONG pIOBufIndex, PULONG pIOBufFillSize ); NTSTATUS TermsrvIniCopyAndChangeLoop( HANDLE SrcHandle, HANDLE DestHandle, PUNICODE_STRING pUserFullPath, PUNICODE_STRING pSysFullPath ); BOOL TermsrvReallocateBuf( PCHAR *ppStr, PULONG pStrBufSize, ULONG NewSize ); PCHAR Ctxstristr( PCHAR pstring1, PCHAR pstring2 ); NTSTATUS TermsrvCheckKeys(HANDLE hKeySysRoot, HANDLE hKeyUsrRoot, PKEY_BASIC_INFORMATION pKeySysInfo, PKEY_FULL_INFORMATION pKeyUsrInfo, ULONG ulcsys, ULONG ulcusr, DWORD indentLevel); NTSTATUS TermsrvCloneKey(HANDLE hKeySys, HANDLE hKeyUsr, PKEY_FULL_INFORMATION pDefKeyInfo, BOOL fCreateSubKeys); VOID InitUnicodeStringWithLen( OUT PUNICODE_STRING DestinationString, IN PCWSTR SourceString, IN USHORT StringLength ); void TermsrvCheckNewRegEntries(IN LPCWSTR wszBaseKeyName); BOOL TermsrvGetUserSyncTime(PULONG pultime); BOOL TermsrvSetUserSyncTime(void); NTSTATUS TermsrvCheckNewIniFilesInternal(IN LPCWSTR wszBaseKeyName); NTSTATUS GetFullKeyPath( IN HANDLE hKeyParent, IN LPCWSTR wszKey, OUT LPWSTR *pwszKeyPath); PWINSTATIONQUERYINFORMATIONW pWinStationQueryInformationW; DWORD g_debugIniMap=FALSE; DWORD IsDebugIniMapEnabled() { HKEY hKey; DWORD rc; DWORD res=0; DWORD size; rc = RegOpenKeyEx( HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Terminal Server\\Install", 0, KEY_READ, &hKey ); size = sizeof(DWORD); if (rc == ERROR_SUCCESS ) { rc = RegQueryValueEx( hKey, L"debug", NULL , NULL , (LPBYTE ) &res, & size ) ; if (rc != ERROR_SUCCESS ) { res = FALSE; } RegCloseKey( hKey ); } return res; } void Indent( ULONG indent) { ULONG i; for ( i = 1; i Buffer, pS->Length ); s[pS->Length + 1 ] = L'\0'; Indent( indent ); DbgPrint("L: %4d, %10ws: %ws \n", line, where, s ); } } // last param two params, one is the wchar str and the last one is the length. Boy I miss c++ and func overloading... void Debug2( DWORD indent, DWORD line, WCHAR *where, WCHAR *pS , DWORD length) { WCHAR s[1024]; if (g_debugIniMap) { wcsncpy( s, pS, length ); s[length + 1 ] = L'\0'; Indent( indent ); DbgPrint("L: %4d, %10ws: %ws \n", line, where, s ); } } void DebugTime( DWORD indent, DWORD line, WCHAR *comment, LARGE_INTEGER li ) { if (g_debugIniMap) { Indent( indent ); DbgPrint("L: %4d, %5ws : %I64x \n", line , comment , li.QuadPart ); } } /***************************************************************************** * * TermsrvGetUserSyncTime * * This routine will get the last time we sync'd up this user's .ini files * and registry values with the system versions. * * ENTRY: * PULONG pultime: pointer to receive last sync time (in seconds since 1970) * * EXIT: * SUCCESS: * returns TRUE * FAILURE: * returns FALSE * ****************************************************************************/ BOOL TermsrvGetUserSyncTime(PULONG pultime) { ULONG ullen, ultmp; NTSTATUS Status; HANDLE hKey, hKeyRoot; OBJECT_ATTRIBUTES ObjectAttr; PKEY_VALUE_PARTIAL_INFORMATION pKeyValInfo; UNICODE_STRING UniString, UserSID; PWCHAR pwch; // Allocate a buffer for the key value name and time info ullen = sizeof(KEY_VALUE_PARTIAL_INFORMATION) + sizeof(ULONG); pKeyValInfo = RtlAllocateHeap(RtlProcessHeap(), 0, ullen); // If we didn't get the buffer, return if (!pKeyValInfo) { return(FALSE); } Status = RtlOpenCurrentUser(KEY_READ, &hKeyRoot); if (NT_SUCCESS(Status)) { // Now open up the Citrix key for this user RtlInitUnicodeString(&UniString, USER_SOFTWARE_TERMSRV); InitializeObjectAttributes(&ObjectAttr, &UniString, OBJ_CASE_INSENSITIVE, hKeyRoot, NULL); Status = NtCreateKey(&hKey, KEY_READ, &ObjectAttr, 0, NULL, REG_OPTION_NON_VOLATILE, &ultmp); NtClose(hKeyRoot); } // If we opened the key, and it was already there, get the value if (NT_SUCCESS(Status) && (ultmp == REG_OPENED_EXISTING_KEY)) { RtlInitUnicodeString(&UniString, TERMSRV_USER_SYNCTIME); Status = NtQueryValueKey(hKey, &UniString, KeyValuePartialInformation, pKeyValInfo, ullen, &ultmp); NtClose(hKey); if (NT_SUCCESS(Status)) { *pultime = *(PULONG)pKeyValInfo->Data; } } else { Status = STATUS_OBJECT_NAME_NOT_FOUND; } RtlFreeHeap( RtlProcessHeap(), 0, pKeyValInfo ); return(NT_SUCCESS(Status)); } /***************************************************************************** * * TermsrvSetUserSyncTime * * This routine will set the current time as this user's last .ini file * sync time. * * ENTRY: * * EXIT: * SUCCESS: * returns TRUE * FAILURE: * returns FALSE * ****************************************************************************/ BOOL TermsrvSetUserSyncTime(void) { ULONG ultmp; NTSTATUS Status; HANDLE hKey, hKeyRoot; OBJECT_ATTRIBUTES ObjectAttr; UNICODE_STRING UniString; PWCHAR pwch; FILETIME FileTime; Status = RtlOpenCurrentUser(KEY_WRITE, &hKeyRoot); if (NT_SUCCESS(Status)) { // Now open up the Citrix key for this user RtlInitUnicodeString(&UniString, USER_SOFTWARE_TERMSRV); InitializeObjectAttributes(&ObjectAttr, &UniString, OBJ_CASE_INSENSITIVE, hKeyRoot, NULL); Status = NtCreateKey(&hKey, KEY_WRITE, &ObjectAttr, 0, NULL, REG_OPTION_NON_VOLATILE, &ultmp); NtClose(hKeyRoot); } // If we opened the key, and set the sync time value if (NT_SUCCESS(Status)) { // Get the system time, convert to local time, and convert to seconds GetSystemTimeAsFileTime(&FileTime); RtlTimeToSecondsSince1970((PLARGE_INTEGER)&FileTime, &ultmp); RtlInitUnicodeString(&UniString, TERMSRV_USER_SYNCTIME); // Now store it under the citrix key in the registry Status = NtSetValueKey(hKey, &UniString, 0, REG_DWORD, &ultmp, sizeof(ultmp)); NtClose(hKey); } else { Status = STATUS_OBJECT_NAME_NOT_FOUND; } return(NT_SUCCESS(Status)); } /***************************************************************************** * * TermsrvCORIniFile * * Copy On Reference an Ini file * * This function is called to copy an ini file from the system * directory to a users local ini file directory. * * The path supplied is the fully translated TERMSRV INI file path, * whichs points to a users directory. * * This string is used to find the system ini file, and copy it to the * users directory. * * All paths are NT paths, NOT WIN32 paths. * * Example: * * \DosDevices\U:\users\default\windows\win.ini is the path given * * %SystemRoot%\win.ini is the "default" location with ini mapping off. * * If \DosDevices\U:\users\default\windows\win.ini does not exist, test to see if * %SystemRoot%\win.ini exists, and if does, copy the system version * to the users directory. * * NOTE: If the path is to the normal unmapped system directory, just * return since there is no mapping occuring. * * ENTRY: * pUserFullPath (input) * Translated TERMSRV INI path name * * EXIT: * ****************************************************************************/ VOID TermsrvCORIniFile( PUNICODE_STRING pUserFullPath ) { DWORD Result; BOOL rc; UNICODE_STRING SysFullPath; UNICODE_STRING UserBasePath; /* * If in install mode, just return to make * everything behave as stock NT. */ if ( IsSystemLUID() || TermsrvAppInstallMode() ) { TRACE0(("TermsrvCORIniFile: INI file mapping is OFF\n")); return; } if (!TermsrvPerUserWinDirMapping()) { return; } /* * If a NULL file name, just return */ if( (pUserFullPath == NULL) || (pUserFullPath->Buffer == NULL) ) { TRACE0(("TermsrvCORIniFile: NULL File INI file name\n")); return; } /* * Test if user file exists */ if( TermsrvDoesFileExist( pUserFullPath ) ) { TRACE0(("TermsrvCORIniFile: File %ws Exists\n",pUserFullPath->Buffer)); // // Nothing to do if the user already has a copy // return; } else { TRACE0(("TermsrvCORIniFile: File %ws DOES NOT Exist!\n",pUserFullPath->Buffer)); } /* * The requested ini file does not exist in the users local * directory. We must change the path name to point to the system * directory, and test if the ini file exists there. */ /* * Build full system path to the Ini file. * * This also parses out the users base path and returns that as well. */ if( !TermsrvBuildSysIniPath( pUserFullPath, &SysFullPath, &UserBasePath ) ) { TRACE0(("TermsrvCORIniFile: Error building Sys Ini Path!\n")); return; } /* * Test if system version exists */ if( !TermsrvDoesFileExist( &SysFullPath ) ) { // // It does not exist in the system directory either, // so we just return. // TRACE0(("TermsrvCORIniFile: Path %ws does not exist in system dir, Length %d\n",SysFullPath.Buffer,SysFullPath.Length)); TRACE0(("TermsrvCORIniFile: UserPath %ws\n",pUserFullPath->Buffer)); RtlFreeHeap( RtlProcessHeap(), 0, SysFullPath.Buffer ); RtlFreeHeap( RtlProcessHeap(), 0, UserBasePath.Buffer ); return; } /* * Now Copy it. * * The copy routine could also translate any paths internal to the * ini file that point to the system directory, to point to the user * directory in the base path. */ rc = TermsrvCopyIniFile( &SysFullPath, &UserBasePath, pUserFullPath); #if DBG if( !rc ) { DBGPRINT(("TermsrvCORIniFile: Could not copy file %ws to %ws\n",SysFullPath.Buffer,pUserFullPath->Buffer)); } #endif RtlFreeHeap( RtlProcessHeap(), 0, SysFullPath.Buffer ); RtlFreeHeap( RtlProcessHeap(), 0, UserBasePath.Buffer ); return; } /***************************************************************************** * * 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 ); } /***************************************************************************** * * TermsrvBuildSysIniPath * * Builds the full ini path to pointing to the system directory * from the users private ini path. * * Also returns the users base ini path directory to be used by * the ini file path conversion when copying. * * ENTRY: * Param1 (input/output) * Comments * * EXIT: * STATUS_SUCCESS - no error * ****************************************************************************/ BOOL TermsrvBuildSysIniPath( PUNICODE_STRING pUserFullPath, PUNICODE_STRING pSysFullPath, PUNICODE_STRING pUserBasePath ) { BOOL rc = FALSE; NTSTATUS Status; UNICODE_STRING SysBasePath; UNICODE_STRING IniPathTail; UNICODE_STRING UniSysDir; WCHAR CtxWindowsPath[MAX_PATH+1]; UNICODE_STRING CtxWindowsDir = { sizeof(CtxWindowsPath), sizeof(CtxWindowsPath), CtxWindowsPath }; OBJECT_ATTRIBUTES ObjectAttr; HKEY hKey = 0; ULONG ul; PKEY_VALUE_FULL_INFORMATION pKeyValInfo; WCHAR SystemWindowsDirectory[MAX_PATH+1]; if (!TermsrvPerUserWinDirMapping()) { return FALSE; } SysBasePath.Buffer = NULL; pKeyValInfo = RtlAllocateHeap(RtlProcessHeap(), 0, sizeof(KEY_VALUE_FULL_INFORMATION) + MAX_PATH ); if (pKeyValInfo) { RtlInitUnicodeString(&UniSysDir, TERMSRV_COMPAT ); InitializeObjectAttributes(&ObjectAttr, &UniSysDir, OBJ_CASE_INSENSITIVE, NULL, NULL ); Status = NtOpenKey(&hKey, KEY_READ, &ObjectAttr); if (NT_SUCCESS(Status)) { RtlInitUnicodeString(&UniSysDir, L"SYSDIR"); Status = NtQueryValueKey(hKey, &UniSysDir, KeyValueFullInformation, pKeyValInfo, sizeof(KEY_VALUE_FULL_INFORMATION) + MAX_PATH, &ul ); if (NT_SUCCESS(Status)) { NtClose(hKey); if (ul = wcslen((PWCHAR)((PCHAR)pKeyValInfo + pKeyValInfo->DataOffset))) { RtlInitUnicodeString(&UniSysDir, (PWCHAR)((PCHAR)pKeyValInfo + pKeyValInfo->DataOffset) ); // Convert to an NT path rc = RtlDosPathNameToNtPathName_U( UniSysDir.Buffer, &SysBasePath, NULL, NULL ); // Was this a valid path? If not, use actual system directory. if (rc && !TermsrvDoesFileExist(&SysBasePath)) { RtlFreeHeap( RtlProcessHeap(), 0, SysBasePath.Buffer ); SysBasePath.Buffer = NULL; rc = FALSE; } // if the path is the root, get rid of last backslash if (ul == 3 && SysBasePath.Buffer) { SysBasePath.Buffer[SysBasePath.Length/sizeof(WCHAR)] = L'\0'; SysBasePath.Length -= 2; } } } } } GetSystemWindowsDirectory(SystemWindowsDirectory,(MAX_PATH * sizeof(WCHAR))); if (!rc) { /* * We must convert the SystemWindowsDirectory from a WIN32 path to * an NT path. */ rc = RtlDosPathNameToNtPathName_U( SystemWindowsDirectory, &SysBasePath, NULL, NULL ); } if (pKeyValInfo) { RtlFreeHeap( RtlProcessHeap(), 0, pKeyValInfo ); } TRACE0(("BaseWindowsDirectory is %ws\n",SystemWindowsDirectory)); if( !rc ) { DBGPRINT(("BuildSysIniPath: Error translating system path to NT path %ws\n",SystemWindowsDirectory)); return( FALSE ); } TRACE0(("BuildSysIniPath: NT SYS path is %ws\n",SysBasePath.Buffer)); /* * Get the users windows path prefix */ Status = GetPerUserWindowsDirectory( &CtxWindowsDir ); if( !NT_SUCCESS( Status ) ) { DBGPRINT(("BuildSysIniPath: Could not get TermsrvWindowsDir 0x%x\n",Status)); RtlFreeHeap( RtlProcessHeap(), 0, SysBasePath.Buffer ); return( FALSE ); } /* * Now convert it into an NT path */ rc = RtlDosPathNameToNtPathName_U( CtxWindowsDir.Buffer, pUserBasePath, NULL, NULL ); if( !rc ) { DBGPRINT(("BuildSysIniPath: Could not convert TermsrvWindowsDir %d\n",rc)); RtlFreeHeap( RtlProcessHeap(), 0, SysBasePath.Buffer ); return( FALSE ); } TRACE0(("BuildSysIniPath: Users Ini PathBase is %ws\n",pUserBasePath->Buffer)); // // Here we have: // // SysBasePath, UserBasePath // // UserFullPath, must now build SysFullPath // rc = TermsrvGetUnicodeRemainder( pUserFullPath, pUserBasePath, &IniPathTail ); if( !rc ) { WCHAR szShortPath[MAX_PATH]; WCHAR szPath[MAX_PATH]; UNICODE_STRING ShortPath; // // GetShortPathName doesn't take NT Path. Strip out "\??\" // if (!wcsncmp(pUserBasePath->Buffer,L"\\??\\",4)) { wcsncpy(szPath,&(pUserBasePath->Buffer[4]),(pUserBasePath->Length - 4)); } else { wcsncpy(szPath,pUserBasePath->Buffer,pUserBasePath->Length); } if (GetShortPathNameW(szPath,szShortPath,MAX_PATH)) { if (!wcsncmp(pUserBasePath->Buffer,L"\\??\\",4)) { wcscpy(szPath,L"\\??\\"); wcscat(szPath,szShortPath); } else { wcscpy(szPath,szShortPath); } RtlInitUnicodeString(&ShortPath,szPath); rc = TermsrvGetUnicodeRemainder( pUserFullPath, &ShortPath, &IniPathTail ); } if (!rc) { RtlFreeHeap( RtlProcessHeap(), 0, SysBasePath.Buffer ); RtlFreeHeap( RtlProcessHeap(), 0, pUserBasePath->Buffer ); return( FALSE ); } } pSysFullPath->Length = 0; pSysFullPath->MaximumLength = (MAX_PATH+1)*sizeof(WCHAR); pSysFullPath->Buffer = RtlAllocateHeap( RtlProcessHeap(), 0, pSysFullPath->MaximumLength ); if( pSysFullPath->Buffer == NULL ) { DBGPRINT(("BuildSysPath: Error in memory allocate\n")); RtlFreeHeap( RtlProcessHeap(), 0, SysBasePath.Buffer ); RtlFreeHeap( RtlProcessHeap(), 0, IniPathTail.Buffer ); RtlFreeHeap( RtlProcessHeap(), 0, pUserBasePath->Buffer ); return( FALSE ); } TRACE0(("BuildSysPath: IniPathTail :%ws:, Length %d\n",IniPathTail.Buffer,IniPathTail.Length)); RtlCopyUnicodeString( pSysFullPath, &SysBasePath ); if ((pSysFullPath->Buffer[pSysFullPath->Length/sizeof(WCHAR) -1 ] != L'\\') && (IniPathTail.Buffer[0] != L'\\')) { // check whether need "\\" Status = RtlAppendUnicodeToString(pSysFullPath, L"\\"); if ( !NT_SUCCESS( Status) ) { DBGPRINT(("BuildSysPath: Error appending UnicodeStirng\n",Status)); return( FALSE ); } } Status = RtlAppendUnicodeStringToString( pSysFullPath, &IniPathTail ); if( !NT_SUCCESS( Status ) ) { DBGPRINT(("BuildSysPath: Error 0x%x appending UnicodeString\n",Status)); RtlFreeHeap( RtlProcessHeap(), 0, SysBasePath.Buffer ); RtlFreeHeap( RtlProcessHeap(), 0, IniPathTail.Buffer ); RtlFreeHeap( RtlProcessHeap(), 0, pUserBasePath->Buffer ); RtlFreeHeap( RtlProcessHeap(), 0, pSysFullPath->Buffer ); return( FALSE ); } TRACE0(("BuildSysPath: SysFullPath :%ws:, Length %d\n",pSysFullPath->Buffer,pSysFullPath->Length)); /* * Free the local resources allocated */ RtlFreeHeap( RtlProcessHeap(), 0, SysBasePath.Buffer ); RtlFreeHeap( RtlProcessHeap(), 0, IniPathTail.Buffer ); return( TRUE ); } /***************************************************************************** * * TermsrvGetUnicodeRemainder * * Given the full path, and a prefix, return the remainder of * the UNICODE_STRING in newly allocated buffer space. * * ENTRY: * Param1 (input/output) * Comments * * EXIT: * TRUE - no error * FALSE - error * ****************************************************************************/ BOOL TermsrvGetUnicodeRemainder( PUNICODE_STRING pFullPath, PUNICODE_STRING pPrefix, PUNICODE_STRING pRemainder ) { WCHAR c1, c2; USHORT Index, RemIndex; USHORT PathLen, PrefixLen, RemLen; PathLen = pFullPath->Length / sizeof(WCHAR); PrefixLen = pPrefix->Length / sizeof(WCHAR); if( (PathLen == 0) || (PrefixLen == 0) ) { TRACE1(("TermsrvGetUnicodeRemainder: 0 PathLength Full %d, Prefix %d\n",PathLen,PrefixLen)); return( FALSE ); } Index = 0; while( PathLen && PrefixLen ) { c1 = pFullPath->Buffer[Index]; c2 = pPrefix->Buffer[Index]; // Do a fast case insensitive compare if( (c1 != c2) && (towupper(c1) != towupper(c2)) ) { TRACE1(("TermsrvGetUnicodeRemainder: Non matching character Index %d\n",Index)); return( FALSE ); } PathLen--; PrefixLen--; Index++; } // If prefix is longer, its an error if( PrefixLen ) { TRACE1(("TermsrvGetUnicodeRemainder: Prefix is longer\n")); return(FALSE); } // If PathLen is 0, there is no remainder. if( PathLen == 0 ) { RemLen = 0; } else { RemLen = PathLen; } // Allocate memory for remainder, including a UNICODE_NULL pRemainder->Length = RemLen*sizeof(WCHAR); pRemainder->MaximumLength = (RemLen+1)*sizeof(WCHAR); pRemainder->Buffer = RtlAllocateHeap( RtlProcessHeap(), 0, pRemainder->MaximumLength ); if( pRemainder->Buffer == NULL ) { TRACE1(("TermsrvGetUnicodeRemainder: Memory allocation error\n")); return( FALSE ); } RemIndex = 0; while( RemLen ) { pRemainder->Buffer[RemIndex] = pFullPath->Buffer[Index]; Index++; RemIndex++; RemLen--; } // Now include the UNICODE_NULL pRemainder->Buffer[RemIndex] = UNICODE_NULL; TRACE0(("TermsrvGetUnicodeRemainder: Remainder %ws\n",pRemainder->Buffer)); return( TRUE ); } /***************************************************************************** * * TermsrvCopyIniFile * * Copies the INI file from the system directory to the * users directory. * * Any paths inside the INI file that match pUserBasePath and do not point * to a shareable application resource will be translated. * * ENTRY: * PUNICODE_STRING pSysFullPath (In) - Path of ini file in system dir (source) * PUNICODE_STRING pUserBasePath (In) - Optional, User's windows home dir * PUNICODE_STRING pUserFullPath (In) - Path of ini file in user's home dir (dest) * * Notes: * If pUserBasePath is NULL, no path substitution is done as the ini file is * copied from the system directory to the user's home directory. * * EXIT: * STATUS_SUCCESS - no error * ****************************************************************************/ BOOL TermsrvCopyIniFile( PUNICODE_STRING pSysFullPath, PUNICODE_STRING pUserBasePath, PUNICODE_STRING pUserFullPath ) { NTSTATUS Status; HANDLE SrcHandle, DestHandle; OBJECT_ATTRIBUTES SrcObja; OBJECT_ATTRIBUTES DestObja; IO_STATUS_BLOCK SrcIosb; IO_STATUS_BLOCK DestIosb; PWCHAR pwch, pwcIniName; ULONG ulCompatFlags; TRACE0(("TermsrvCopyIniFile: From %ws, TO -> %ws\n",pSysFullPath->Buffer,pUserFullPath->Buffer)); TRACE0(("UserBasePath %ws\n",pUserBasePath->Buffer)); /* * This must all be done at the NT level */ InitializeObjectAttributes( &SrcObja, pSysFullPath, OBJ_CASE_INSENSITIVE, NULL, NULL ); InitializeObjectAttributes( &DestObja, pUserFullPath, 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 // OpenOptions ); if( NT_SUCCESS(Status) ) { // Get final I/O status Status = SrcIosb.Status; } if( !NT_SUCCESS(Status) ) { DBGPRINT(("TermsrvCopyIniFile: Error 0x%x opening SrcFile %ws\n",Status,pSysFullPath->Buffer)); return( FALSE ); } // Create the destination file DestIosb.Status = STATUS_SUCCESS; Status = NtCreateFile( &DestHandle, FILE_READ_DATA | FILE_WRITE_DATA | FILE_READ_ATTRIBUTES | SYNCHRONIZE, &DestObja, &DestIosb, NULL, // Allocation size FILE_ATTRIBUTE_NORMAL, // dwFlagsAndAttributes FILE_SHARE_WRITE, // dwShareMode FILE_OVERWRITE_IF, // CreateDisposition FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE, // CreateFlags NULL, // EaBuffer 0 // EaLength ); if( NT_SUCCESS(Status) ) { // Get final I/O status Status = DestIosb.Status; } if( !NT_SUCCESS(Status) ) { DBGPRINT(("TermsrvCopyIniFile: Error 0x%x Creating DestFile %ws\n",Status,pUserFullPath->Buffer)); NtClose( SrcHandle ); return( FALSE ); } TRACE0(("TermsrvCopyFile: Create Disposition 0x%x\n",DestIosb.Information)); // Get the ini file name pwch = wcsrchr(pSysFullPath->Buffer, L'\\') + 1; pwcIniName = RtlAllocateHeap( RtlProcessHeap(), 0, (wcslen(pwch) + 1)*sizeof(WCHAR)); if(!pwcIniName) { DBGPRINT(("TermsrvCopyIniFile: Error Allocating pwcIniName\n")); NtClose( SrcHandle ); NtClose( DestHandle ); return( FALSE ); } wcscpy(pwcIniName, pwch); pwch = wcsrchr(pwcIniName, L'.'); if (pwch) { *pwch = L'\0'; } GetTermsrCompatFlags(pwcIniName, &ulCompatFlags, CompatibilityIniFile); RtlFreeHeap( RtlProcessHeap(), 0, pwcIniName ); /* * Now do the copy loop */ if (pUserBasePath && !(ulCompatFlags & TERMSRV_COMPAT_ININOSUB)) { Status = TermsrvIniCopyAndChangeLoop( SrcHandle, DestHandle, pUserBasePath, pSysFullPath ); } else { Status = TermsrvIniCopyLoop( SrcHandle, DestHandle ); } if( !NT_SUCCESS(Status) ) { DBGPRINT(("TermsrvCopyIniFile: Error 0x%x Doing copy loop\n",Status)); NtClose( SrcHandle ); NtClose( DestHandle ); return( FALSE ); } /* * Close the file handles */ NtClose( SrcHandle ); NtClose( DestHandle ); return( TRUE ); } /***************************************************************************** * * TermsrvIniCopyLoop * * Actual copy loop. This copies the src ini file to the destination * ini file. * * ENTRY: * Param1 (input/output) * Comments * * EXIT: * STATUS_SUCCESS - no error * ****************************************************************************/ NTSTATUS TermsrvIniCopyLoop( HANDLE SrcHandle, HANDLE DestHandle ) { NTSTATUS Status; PCHAR pBuf = NULL; IO_STATUS_BLOCK Iosb; pBuf = LocalAlloc( LPTR, INI_BUF_SIZE ); if ( !pBuf ) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; } while( 1 ) { Iosb.Status = STATUS_SUCCESS; Status = NtReadFile( SrcHandle, NULL, // Event NULL, // APC routine NULL, // APC context &Iosb, pBuf, INI_BUF_SIZE, NULL, // ByteOffset (not used since in synchronous I/O) NULL // Key ); if( Status == STATUS_PENDING ) { Status = NtWaitForSingleObject( SrcHandle, FALSE, NULL ); } if( NT_SUCCESS(Status) ) { // Get final I/O status Status = Iosb.Status; } if( !NT_SUCCESS(Status) ) { if( Status == STATUS_END_OF_FILE ) { Status = STATUS_SUCCESS; goto Cleanup; } DBGPRINT(("TermsrvIniCopyLoop: Error 0x%x doing NtReadFile\n",Status)); goto Cleanup; } Iosb.Status = STATUS_SUCCESS; Status = NtWriteFile( DestHandle, NULL, // Event NULL, // APC routine NULL, // APC context &Iosb, pBuf, (ULONG)Iosb.Information, // Actual amount read NULL, // ByteOffset (not used since in synchronous I/O) NULL // Key ); if( Status == STATUS_PENDING ) { Status = NtWaitForSingleObject( DestHandle, FALSE, NULL ); } if( NT_SUCCESS(Status) ) { // Get final I/O status Status = Iosb.Status; } if( !NT_SUCCESS(Status) ) { DBGPRINT(("TermsrvIniCopyLoop: Error 0x%x doing NtWriteFile\n",Status)); goto Cleanup; } } // end while(1) Cleanup: if ( pBuf ) { LocalFree( pBuf ); } return( Status ); } /***************************************************************************** * * TermsrvIniCopyAndChangeLoop * * Actual copy loop. This copies the src ini file to the destination * ini file. It also handles any path translations. * * ENTRY: * HANDLE SrcHandle (In) - Source file handle * HANDLE DestHandle (In) - Destination file handle * PUNICODE_STRING pUserFullPath (In) - Ptr to Uni string with user's home * windows dir * * EXIT: * STATUS_SUCCESS - no error * ****************************************************************************/ NTSTATUS TermsrvIniCopyAndChangeLoop( HANDLE SrcHandle, HANDLE DestHandle, PUNICODE_STRING pUserFullPath, PUNICODE_STRING pSysFullPath ) { PCHAR pStr, pch, ptemp, pnext; PWCHAR pwch; NTSTATUS Status; ULONG StringSize; CHAR IOBuf[512]; ULONG IOBufSize = 512; ULONG IOBufIndex = 0; ULONG IOBufFillSize = 0; ANSI_STRING AnsiUserDir, AnsiSysDir; UNICODE_STRING UniString; // Get the DOS filename from the NT file name if (pwch = wcschr(pUserFullPath->Buffer, L':')) { pwch--; } else { pwch = pUserFullPath->Buffer; } RtlInitUnicodeString( &UniString, pwch ); Status = RtlUnicodeStringToAnsiString( &AnsiUserDir, &UniString, TRUE ); if (!NT_SUCCESS(Status)) { DBGPRINT(("TermsrvIniCopyAndChangeLoop: Error 0x%x converting user dir\n", Status)); return(Status); } // Get the system directory from the fully qualified system path if (pwch = wcschr(pSysFullPath->Buffer, L':')) { pwch--; } else { pwch = pUserFullPath->Buffer; } RtlInitUnicodeString( &UniString, pwch ); Status = RtlUnicodeStringToAnsiString( &AnsiSysDir, &UniString, TRUE ); if (!NT_SUCCESS(Status)) { DBGPRINT(("TermsrvIniCopyAndChangeLoop: Error 0x%x converting system dir\n", Status)); RtlFreeAnsiString( &AnsiUserDir ); return(Status); } pch = strrchr(AnsiSysDir.Buffer, '\\'); // unless something has gone wrong, we should always have a pch since a full-path always // has at least "\" in it, and actually in our case, we have atleast two slashes inside, // since we are dealing with a string such as "\A\file.ini", where 'A' is a folder // name that has at least one letter in it if (pch) { if ((pch - AnsiSysDir.Buffer) > 2) { *pch = '\0'; } else { *(pch+1) = '\0'; } AnsiSysDir.Length = (USHORT) strlen(AnsiSysDir.Buffer); while( 1 ) { pStr = NULL; StringSize = 0; /* * Get a string from the source ini file */ Status = TermsrvGetString( SrcHandle, &pStr, &StringSize, IOBuf, IOBufSize, &IOBufIndex, &IOBufFillSize ); if( !NT_SUCCESS(Status) ) { ASSERT( pStr == NULL ); RtlFreeAnsiString( &AnsiUserDir ); RtlFreeAnsiString( &AnsiSysDir ); if( Status == STATUS_END_OF_FILE ) { return( STATUS_SUCCESS ); } return( Status ); } /* * Process the string for any ini path translations */ ASSERT( pStr != NULL ); // Go through the string looking for anything that contains the system // directory. if (pch = Ctxstristr(pStr, AnsiSysDir.Buffer)) { // See if this entry might point to an ini file if ((ptemp = strchr(pch, '.')) && !(_strnicmp(ptemp, ".ini", 4))) { // Check to make sure this is the right string to replace pnext = pch + AnsiSysDir.Length + 1; while (pch && (pnext < ptemp)) { // Check for another entry if (*pnext == ',') { pch = Ctxstristr(pnext, AnsiSysDir.Buffer); if (pch) { pnext = pch + AnsiSysDir.Length + 1; } } pnext++; } // Check that this .ini is in the system directory pnext = pch + AnsiSysDir.Length + 1; while (pch && (pnext < ptemp)) { if (*pnext == '\\') { pch = NULL; } pnext++; } if (pch && (pch < ptemp)) { ptemp = RtlAllocateHeap( RtlProcessHeap(), 0, StringSize + AnsiUserDir.Length ); strncpy(ptemp, pStr, (size_t)(pch - pStr)); // copy up to sys dir ptemp[pch - pStr] = '\0'; strcat(ptemp, AnsiUserDir.Buffer); // subst user dir if (AnsiSysDir.Length == 3) { strcat(ptemp, "\\"); } strcat(ptemp, pch + AnsiSysDir.Length); // append rest of line RtlFreeHeap( RtlProcessHeap(), 0, pStr ); StringSize = strlen(ptemp); pStr = ptemp; } } } /* * Write out the translated string */ Status = TermsrvPutString( DestHandle, pStr, StringSize ); RtlFreeHeap( RtlProcessHeap(), 0, pStr ); if( !NT_SUCCESS(Status) ) { DBGPRINT(("TermsrvIniCopyLoop: Error 0x%x doing NtWriteFile\n",Status)); RtlFreeAnsiString( &AnsiUserDir ); RtlFreeAnsiString( &AnsiSysDir ); return( Status ); } } // end while(1) } else { return STATUS_UNSUCCESSFUL; } } /***************************************************************************** * * TermsrvGetString * * This function gets a "string" from an ini file and returns it to the * caller. Since processing must be done in memory on the strings, they * are returned NULL terminated, but this NULL is NOT included in the * returned string size. Of course, buffer size calculations take this * NULL into account. Strings retain any characters and are * not stripped out like the C runtime. * * The I/O buffer used is passed in by the caller. If the IoBufIndex is * not 0, this is an indication that there is still data left in the buffer * from a previous operation. This data is used before reading additional * data from the file handle. This handles the case where string breaks * do not occur at buffer boundries. * * Strings are returned in newly allocated memory on the process heap. * The caller is reponsible for freeing them when done. * * ENTRY: * Param1 (input/output) * Comments * * EXIT: * STATUS_SUCCESS - no error * ****************************************************************************/ NTSTATUS TermsrvGetString( HANDLE SrcHandle, PCHAR *ppStringPtr, PULONG pStringSize, PCHAR pIOBuf, ULONG IOBufSize, PULONG pIOBufIndex, PULONG pIOBufFillSize ) { NTSTATUS Status; IO_STATUS_BLOCK Iosb; BOOL SawNL = FALSE; ULONG StrSize = 0; ULONG StrBufSize = 512; PCHAR pStr = NULL; /* * first process any left over data in the current I/O buffer */ if( *pIOBufIndex < *pIOBufFillSize ) { Status = TermsrvProcessBuffer( &pStr, &StrSize, &StrBufSize, &SawNL, pIOBuf, pIOBufIndex, pIOBufFillSize ); if( Status == STATUS_SUCCESS ) { *ppStringPtr = pStr; *pStringSize = StrSize; return( STATUS_SUCCESS ); } else if (Status == STATUS_MORE_PROCESSING_REQUIRED) { /* * emptied the buffer */ *pIOBufIndex = 0; *pIOBufFillSize = 0; // fall through to read more data } else { // Error if( pStr ) { RtlFreeHeap( RtlProcessHeap(), 0, pStr ); } *ppStringPtr = NULL; *pStringSize = 0; return( Status ); } } while( 1 ) { ASSERT( *pIOBufIndex == 0 ); ASSERT( *pIOBufFillSize == 0 ); Iosb.Status = STATUS_SUCCESS; Status = NtReadFile( SrcHandle, NULL, // Event NULL, // APC routine NULL, // APC context &Iosb, pIOBuf, IOBufSize, NULL, // ByteOffset (not used since in synchronous I/O) NULL // Key ); if( Status == STATUS_PENDING ) { Status = NtWaitForSingleObject( SrcHandle, FALSE, NULL ); } if( NT_SUCCESS(Status) ) { // Get final I/O status Status = Iosb.Status; } if( !NT_SUCCESS(Status) ) { if( (Status == STATUS_END_OF_FILE) && (StrSize != 0) ) { // Force the string finished pStr[StrSize] = (CHAR)NULL; *pStringSize = StrSize; *ppStringPtr = pStr; return( STATUS_SUCCESS ); } // Free the buffer if( pStr ) { RtlFreeHeap( RtlProcessHeap(), 0, pStr ); } *ppStringPtr = NULL; *pStringSize = 0; if (Status != STATUS_END_OF_FILE) DBGPRINT(("TermsrvIniCopyLoop: Error 0x%x doing NtReadFile\n",Status)); return( Status ); } // Fill in the count *pIOBufFillSize = (ULONG)Iosb.Information; /* * Now process this buffer of data */ Status = TermsrvProcessBuffer( &pStr, &StrSize, &StrBufSize, &SawNL, pIOBuf, pIOBufIndex, pIOBufFillSize ); if( Status == STATUS_SUCCESS ) { *ppStringPtr = pStr; *pStringSize = StrSize; return( STATUS_SUCCESS ); } else if (Status == STATUS_MORE_PROCESSING_REQUIRED) { /* * emptied the buffer */ *pIOBufIndex = 0; *pIOBufFillSize = 0; // fall through to read more data } else { // Error if( pStr ) { RtlFreeHeap( RtlProcessHeap(), 0, pStr ); } *ppStringPtr = NULL; *pStringSize = 0; return( Status ); } } // end while(1) } /***************************************************************************** * * TermsrvProcessBuffer * * Process a buffer of data. * * This uses state passed in by the caller since the string can be * partially built, and the buffer may not be fully processed when * a string completes. * * Can return if it completes a string with data still in buffer. * * ENTRY: * Param1 (input/output) * Comments * * EXIT: * STATUS_SUCCESS - no error * ****************************************************************************/ NTSTATUS TermsrvProcessBuffer( PCHAR *ppStr, PULONG pStrSize, PULONG pStrBufSize, PBOOL pSawNL, PCHAR pIOBuf, PULONG pIOBufIndex, PULONG pIOBufFillSize ) { PCHAR pStr; ULONG Index; BOOL SawNL; /* * See if we are starting a new string */ if( *ppStr == NULL ) { pStr = RtlAllocateHeap( RtlProcessHeap(), 0, *pStrBufSize ); if( pStr == NULL ) { DBGPRINT(("TermsrvProcessBuf: Memory allocation failure\n")); return( STATUS_NO_MEMORY ); } // Set it to our caller *ppStr = pStr; } /* * Get passed in state to local variables */ pStr = *ppStr; Index = *pStrSize; SawNL = *pSawNL; while ( *pIOBufIndex < *pIOBufFillSize ) { pStr[Index] = pIOBuf[*pIOBufIndex]; if( IS_NEWLINE_CHAR( pStr[Index] ) ) { /* * Mark the we saw an end of string character. * We will keep putting them into the buffer until a * non-NL character is encountered. This handles the * variations for , alone, or * if its been mangled by a buggy editor. */ SawNL = TRUE; } else { /* * If we saw a previous NL character, and this character * is not one, we do not take this one, but put a NULL in * its place and return. NOTE: Do not bump the count, since * the count does not include the NULL. */ if( SawNL ) { pStr[Index] = (CHAR)NULL; *pStrSize = Index; return( STATUS_SUCCESS ); } } Index++; (*pIOBufIndex)++; if( Index >= *pStrBufSize ) { // Grow the string buffer if( !TermsrvReallocateBuf( &pStr, pStrBufSize, (*pStrBufSize) * 2 ) ) { if( pStr ) { RtlFreeHeap( RtlProcessHeap(), 0, pStr ); } *ppStr = NULL; DBGPRINT(("TermsrvIniCopyLoop: Memory re-allocation failure\n")); return( STATUS_NO_MEMORY ); } // Memory buffer has been re-allocated *ppStr = pStr; *pStrBufSize = (*pStrBufSize) * 2; } } *pStrSize = Index; *pSawNL = SawNL; /* * emptied the buffer without building a whole string */ return( STATUS_MORE_PROCESSING_REQUIRED ); } /***************************************************************************** * * TermsrvReallocateBuf * * Grow the buffer, copy data to new buffer. * * ENTRY: * Param1 (input/output) * Comments * * EXIT: * STATUS_SUCCESS - no error * ****************************************************************************/ BOOL TermsrvReallocateBuf( PCHAR *ppStr, PULONG pStrBufSize, ULONG NewSize ) { PCHAR ptr; ULONG CopyCount; CopyCount = *pStrBufSize; ptr = RtlAllocateHeap( RtlProcessHeap(), 0, NewSize ); if( ptr == NULL ) { return( FALSE ); } RtlMoveMemory( ptr, *ppStr, CopyCount ); RtlFreeHeap( RtlProcessHeap(), 0, *ppStr ); *ppStr = ptr; return( TRUE ); } /***************************************************************************** * * TermsrvPutString * * Write out the current string to the destination file handle * * ENTRY: * Param1 (input/output) * Comments * * EXIT: * STATUS_SUCCESS - no error * ****************************************************************************/ NTSTATUS TermsrvPutString( HANDLE DestHandle, PCHAR pStr, ULONG StringSize ) { NTSTATUS Status; IO_STATUS_BLOCK Iosb; Iosb.Status = STATUS_SUCCESS; Status = NtWriteFile( DestHandle, NULL, // Event NULL, // APC routine NULL, // APC context &Iosb, pStr, StringSize, NULL, // ByteOffset (not used since in synchronous I/O) NULL // Key ); if( Status == STATUS_PENDING ) { Status = NtWaitForSingleObject( DestHandle, FALSE, NULL ); } if( NT_SUCCESS(Status) ) { // Get final I/O status Status = Iosb.Status; } return( Status ); } /***************************************************************************** * * TermsrvCheckNewIniFiles * * This routine will check the timestamps of the .ini files installed by the * system administrator, and see if any of the user's .ini file are out of * date, and if so, they will be renamed. * * ENTRY: * * EXIT: * No return value. * ****************************************************************************/ void TermsrvCheckNewIniFiles(void) { NTSTATUS Status; #if defined (_WIN64) Status = TermsrvCheckNewIniFilesInternal(REG_NTAPI_SOFTWARE_WOW6432_TSERVER); if (!NT_SUCCESS(Status)) { return; } #endif // defined(_WIN64) Status = TermsrvCheckNewIniFilesInternal(REG_NTAPI_SOFTWARE_TSERVER); if (!NT_SUCCESS(Status)) { return; } // Update the user's sync time in the registry TermsrvSetUserSyncTime(); } /***************************************************************************** * * TermsrvCheckNewIniFilesInternal * * This routine will check the timestamps of the .ini files installed by the * system administrator, and see if any of the user's .ini file are out of * date, and if so, they will be renamed. * * ENTRY: * LPCWSTR wszBaseKeyName * * EXIT: * No return value. * ****************************************************************************/ NTSTATUS TermsrvCheckNewIniFilesInternal( IN LPCWSTR wszBaseKeyName) { PWCHAR pwch; UNICODE_STRING UniString, UniUserDir, UniNTDir = {0,0,NULL}; OBJECT_ATTRIBUTES ObjectAttr; FILE_NETWORK_OPEN_INFORMATION BasicInfo; HANDLE hKey = NULL, hWinDir = NULL; NTSTATUS Status; ULONG ulcnt, ullen, ultmp; WCHAR wcWinDir[MAX_PATH], wcbuff[MAX_PATH]; PKEY_VALUE_FULL_INFORMATION pKeyValInfo; PKEY_BASIC_INFORMATION pKeyInfo; IO_STATUS_BLOCK IOStatus; g_debugIniMap = IsDebugIniMapEnabled(); // Allocate a buffer for the key value name and time info ullen = sizeof(KEY_VALUE_FULL_INFORMATION) + MAX_PATH*sizeof(WCHAR) + sizeof(ULONG); pKeyValInfo = RtlAllocateHeap(RtlProcessHeap(), 0, ullen); // If we didn't get the buffer, return if (!pKeyValInfo) { return STATUS_NO_MEMORY; } // Open up the registry key to get the last sync time for this user wcscpy(wcbuff,wszBaseKeyName); wcscat(wcbuff,TERMSRV_INIFILE_TIMES_SHORT); RtlInitUnicodeString(&UniString, wcbuff); InitializeObjectAttributes(&ObjectAttr, &UniString, OBJ_CASE_INSENSITIVE, NULL, NULL); Status = NtOpenKey(&hKey, KEY_READ, &ObjectAttr); // If we successfully opened the key, check if there are any new entries if (NT_SUCCESS(Status)) { // Since we already allocated a hunk of memory, use the value buffer // for the key info query pKeyInfo = (PKEY_BASIC_INFORMATION)pKeyValInfo; // Get the last time anyone wrote to "IniFile Times" key Status = NtQueryKey(hKey, KeyBasicInformation, pKeyInfo, ullen, &ultmp); // We got the last write time OK, now get the last time we sync'd if (NT_SUCCESS(Status) && TermsrvGetUserSyncTime(&ultmp)) { // Convert the time value to seconds since 1970 RtlTimeToSecondsSince1970 (&pKeyInfo->LastWriteTime, &ulcnt); // If no .ini files or reg entries have been updated since the last // time we sync'd this user, just return if (ultmp >= ulcnt) { NtClose(hKey); RtlFreeHeap(RtlProcessHeap(), 0, pKeyValInfo); return STATUS_SUCCESS; } } TermsrvCheckNewRegEntries(wszBaseKeyName); // Set up UniUserDir to point at wcbuff UniUserDir.Buffer = wcWinDir; UniUserDir.Length = 0; UniUserDir.MaximumLength = sizeof(wcbuff); Status = GetPerUserWindowsDirectory(&UniUserDir); if (NT_SUCCESS(Status)) { // Convert to an NT path if (RtlDosPathNameToNtPathName_U(UniUserDir.Buffer, &UniNTDir, NULL, NULL)) { InitializeObjectAttributes(&ObjectAttr, &UniNTDir, OBJ_CASE_INSENSITIVE, NULL, NULL); // Open the user's windows directory IOStatus.Status = STATUS_SUCCESS; Status = NtOpenFile(&hWinDir, FILE_GENERIC_READ, &ObjectAttr, &IOStatus, FILE_SHARE_READ|FILE_SHARE_WRITE, FILE_SYNCHRONOUS_IO_NONALERT); } else { Status = STATUS_NO_SUCH_FILE; } } // Go through each of the keys, checking if it's newer than the user's // version of the file, and if so rename it ulcnt = 0; wcscat(wcWinDir, L"\\"); UniUserDir.Length += 2; // add in length of \ seperator while (NT_SUCCESS(Status)) { Status = NtEnumerateValueKey(hKey, ulcnt++, KeyValueFullInformation, pKeyValInfo, ullen, &ultmp); if (NT_SUCCESS(Status)) { RtlMoveMemory(wcbuff, pKeyValInfo->Name, pKeyValInfo->NameLength); wcbuff[pKeyValInfo->NameLength/sizeof(WCHAR)] = L'\0'; // Get rid of the .ini extension if (pwch = wcschr(wcbuff, L'.')) { *pwch = L'\0'; } // Get the compatibility flags for this .ini file GetTermsrCompatFlags(wcbuff, &ultmp, CompatibilityIniFile); // If we removed the extension, put it back if (pwch) { *pwch = '.'; } // If the INISYNC compat bit is set, don't rename the file if ((ultmp & (TERMSRV_COMPAT_INISYNC | TERMSRV_COMPAT_WIN16)) != (TERMSRV_COMPAT_INISYNC | TERMSRV_COMPAT_WIN16)) { RtlInitUnicodeString(&UniString, wcbuff); // Query the last write time of the .ini file InitializeObjectAttributes(&ObjectAttr, &UniString, OBJ_CASE_INSENSITIVE, hWinDir, NULL); // Get the last write time if (NT_SUCCESS(NtQueryFullAttributesFile( &ObjectAttr, &BasicInfo ))) { // Convert the last write time to seconds RtlTimeToSecondsSince1970(&BasicInfo.LastWriteTime, &ultmp); // Check if the system version is newer than the user's // version if (*(PULONG)((PCHAR)pKeyValInfo + pKeyValInfo->DataOffset) > ultmp) { // Concatenate the .ini name onto the user's path wcscpy(wcWinDir + (UniUserDir.Length/sizeof(WCHAR)), wcbuff); // Create the target name to rename the file // (inifile.ctx) wcscpy(wcbuff, wcWinDir); pwch = wcsrchr(wcbuff, L'.'); if (pwch) { wcscpy(pwch, L".ctx"); } else { wcscat(pwch, L".ctx"); } // Rename the .ini file MoveFileExW(wcWinDir, wcbuff, MOVEFILE_REPLACE_EXISTING); } } } } } // Close the handles, if they were opened if (hKey) { NtClose(hKey); } if (hWinDir) { NtClose(hWinDir); } } // Free the memory we allocated RtlFreeHeap( RtlProcessHeap(), 0, pKeyValInfo ); if (UniNTDir.Buffer) { RtlFreeHeap(RtlProcessHeap(), 0, UniNTDir.Buffer); } return STATUS_SUCCESS; } /***************************************************************************** * * TermsrvCheckKeys * * This recursive routine will check for any subkeys under the system root * key passed in. It will delete the corresponding key in the user's * software registry if the user's key is older than the system key. If the * INISYNC bit is set for this registry key or the key is still has subkeys, * it won't be deleted. If the key doesn't exist in the user's registry, * it will be added. * * ENTRY: * HANDLE hKeySysRoot: handle to key in system section of registry * HANDLE hKeyUsrRoot: handle to key in user section of registry * PKEY_BASIC_INFORMATION pKeySysInfo: Ptr to buffer for key basic info struc * PKEY_FULL_INFORMATION pKeyUsrInfo: Ptr to buffer for full key info struc * ULONG ulcsys: Size of SysInfo buffer * ULONG ulcusr: Size of UsrInfo buffer * * EXIT: * SUCCESS: * STATUS_SUCCESS * FAILURE: * NTSTATUS Return Code * ****************************************************************************/ NTSTATUS TermsrvCheckKeys(HANDLE hKeySysRoot, HANDLE hKeyUsrRoot, PKEY_BASIC_INFORMATION pKeySysInfo, PKEY_FULL_INFORMATION pKeyUsrInfo, ULONG ulcsys, ULONG ulcusr, DWORD indentLevel ) { NTSTATUS Status = STATUS_SUCCESS, Status2; ULONG ultemp, ulcnt = 0; UNICODE_STRING UniPath, UniString; OBJECT_ATTRIBUTES ObjAttr; HANDLE hKeyUsr = NULL, hKeySys = NULL; LPWSTR wcbuff = NULL; ULONG aulbuf[4]; PKEY_FULL_INFORMATION pKeyUsrFullInfoSaved = NULL ; ULONG sizeFullInfo; PKEY_VALUE_PARTIAL_INFORMATION pValKeyInfo = (PKEY_VALUE_PARTIAL_INFORMATION)aulbuf; ++indentLevel; Status = GetFullKeyPath(hKeyUsrRoot,NULL,&wcbuff); if(!NT_SUCCESS(Status)) { return Status; } // Get the compatibility flags for this entry GetTermsrCompatFlags(wcbuff, &ultemp, CompatibilityRegEntry); LocalFree(wcbuff); // If the INISYNC or NOREGMAP bits are set for this entry, // return, since there's nothing to do if ((ultemp & TERMSRV_COMPAT_WIN32) && (ultemp & (TERMSRV_COMPAT_NOREGMAP | TERMSRV_COMPAT_INISYNC))) { return(STATUS_NO_MORE_ENTRIES); } // Save the current info for the current user key // @@@ if (!hKeyUsrRoot) { DBGPRINT(("ERROR : LINE : %4d, why is this null? \n", __LINE__ )); return(STATUS_NO_MORE_ENTRIES ); } // use a zero length query to get the actual length Status = NtQueryKey(hKeyUsrRoot, KeyFullInformation, pKeyUsrFullInfoSaved, 0, &ultemp) ; if ( !NT_SUCCESS( Status ) ) { if (Status == STATUS_BUFFER_TOO_SMALL ) { sizeFullInfo = ultemp; pKeyUsrFullInfoSaved = RtlAllocateHeap(RtlProcessHeap(), 0, sizeFullInfo ); if ( ! pKeyUsrFullInfoSaved ) { return STATUS_NO_MEMORY; } Status = NtQueryKey(hKeyUsrRoot, KeyFullInformation, pKeyUsrFullInfoSaved, sizeFullInfo, &ultemp); if( !NT_SUCCESS(Status ) ) { DBGPRINT(("ERROR : LINE : %4d, Status =0x%lx , ultemp=%d\n", __LINE__ , Status, ultemp)); RtlFreeHeap( RtlProcessHeap(), 0, pKeyUsrFullInfoSaved); return( Status ); } } else { DBGPRINT(("ERROR : LINE : %4d, Status =0x%lx \n", __LINE__ , Status )); return Status; } } // Go through each of the subkeys, checking for user keys that are older // than the system version of the keys while (NT_SUCCESS(Status)) { Status = NtEnumerateKey(hKeySysRoot, ulcnt++, KeyBasicInformation, pKeySysInfo, ulcsys, &ultemp); // See if there are any user keys under this key that are out of date if (NT_SUCCESS(Status)) { // Null terminate the key name pKeySysInfo->Name[pKeySysInfo->NameLength/sizeof(WCHAR)] = L'\0'; // Create a unicode string for the key name RtlInitUnicodeString(&UniPath, pKeySysInfo->Name); InitializeObjectAttributes(&ObjAttr, &UniPath, OBJ_CASE_INSENSITIVE, hKeySysRoot, NULL); Debug1( indentLevel, __LINE__, L"system", &UniPath ); // Open up the system key Status2 = NtOpenKey(&hKeySys, KEY_READ, &ObjAttr); // We opened up the system key, now open the user key if (NT_SUCCESS(Status2)) { // Setup the object attr struc for the user key InitializeObjectAttributes(&ObjAttr, &UniPath, OBJ_CASE_INSENSITIVE, hKeyUsrRoot, NULL); // Open up the user key Status2 = NtOpenKey(&hKeyUsr, MAXIMUM_ALLOWED, &ObjAttr); // Check if there are any subkeys under this key if (NT_SUCCESS(Status2)) { Debug1(indentLevel, __LINE__, L"user", &UniPath ); TermsrvCheckKeys(hKeySys, hKeyUsr, pKeySysInfo, pKeyUsrInfo, ulcsys, ulcusr, indentLevel); NtClose(hKeyUsr); } // key doesn't exist, clone system key to user else { Status2 = GetFullKeyPath(hKeyUsrRoot,pKeySysInfo->Name,&wcbuff); if(NT_SUCCESS(Status2)) { // don't clone if mapping off for this registry entry GetTermsrCompatFlags(wcbuff, &ultemp, CompatibilityRegEntry); LocalFree(wcbuff); if (((ultemp & (TERMSRV_COMPAT_WIN32 | TERMSRV_COMPAT_NOREGMAP)) != (TERMSRV_COMPAT_WIN32 | TERMSRV_COMPAT_NOREGMAP) )) { Status2 = NtQueryKey(hKeySys, KeyFullInformation, pKeyUsrInfo, ulcusr, &ultemp); if (NT_SUCCESS(Status2)) { // don't clone if key previously deleted RtlInitUnicodeString(&UniString, TERMSRV_COPYONCEFLAG); Status2 = NtQueryValueKey(hKeySys, &UniString, KeyValuePartialInformation, pValKeyInfo, sizeof(aulbuf), &ultemp); if (!(NT_SUCCESS(Status2) && (pValKeyInfo->Data))) { // Setup the unicode string for the class InitUnicodeStringWithLen(&UniString, pKeyUsrInfo->ClassLength ? pKeyUsrInfo->Class : NULL, (USHORT)pKeyUsrInfo->ClassLength); Debug1(indentLevel, __LINE__, L"creating user key", ObjAttr.ObjectName ); Status2 = NtCreateKey(&hKeyUsr, MAXIMUM_ALLOWED, &ObjAttr, 0, &UniString, REG_OPTION_NON_VOLATILE, &ultemp); if (NT_SUCCESS(Status2)) { Debug1(indentLevel, __LINE__, L"cloning key", ObjAttr.ObjectName ); TermsrvCloneKey(hKeySys, hKeyUsr, pKeyUsrInfo, TRUE); } } } } } } NtClose(hKeySys); } } } // Get the info for the user key if (NtQueryKey(hKeyUsrRoot, KeyFullInformation, pKeyUsrInfo, ulcusr, &ultemp) == STATUS_SUCCESS) { // Now get the info for the system key (again) if (NtQueryKey(hKeySysRoot, KeyBasicInformation, pKeySysInfo, ulcsys, &ultemp) == STATUS_SUCCESS) { // Get the compatibility flags for this registry entry pKeySysInfo->Name[pKeySysInfo->NameLength/sizeof(WCHAR)] = L'\0'; GetTermsrCompatFlags(pKeySysInfo->Name, &ultemp, CompatibilityRegEntry); //check if it's older than the system version if( pKeyUsrFullInfoSaved->LastWriteTime.QuadPart < pKeySysInfo->LastWriteTime.QuadPart) { DebugTime(indentLevel, __LINE__, L"User key time", pKeyUsrFullInfoSaved->LastWriteTime ); DebugTime(indentLevel, __LINE__, L"Sys key time", pKeySysInfo->LastWriteTime); Debug2( indentLevel, __LINE__, L"Key Old, values being cloned", pKeySysInfo->Name, pKeySysInfo->NameLength ); if(NtQueryKey(hKeySysRoot, KeyFullInformation, pKeyUsrInfo, ulcusr, &ultemp) == STATUS_SUCCESS) { TermsrvCloneKey(hKeySysRoot, hKeyUsrRoot, pKeyUsrInfo,//actually it is system key information FALSE); } } } } RtlFreeHeap( RtlProcessHeap(), 0, pKeyUsrFullInfoSaved); return(Status); } /***************************************************************************** * * TermsrvCheckNewRegEntries * * This routine will check the user's registry keys, and see if any of them * are older than the system versions. If so, the old key will be removed. * * ENTRY: * IN LPCWSTR wszBaseKeyName * * EXIT: * No return value. * ****************************************************************************/ void TermsrvCheckNewRegEntries( IN LPCWSTR wszBaseKeyName) { NTSTATUS Status; ULONG ulcsys, ulcusr; UNICODE_STRING UniPath; OBJECT_ATTRIBUTES ObjAttr; PKEY_BASIC_INFORMATION pKeySysInfo = NULL; PKEY_FULL_INFORMATION pKeyUserInfo = NULL; HANDLE hKeyUser, hKeySys = NULL; WCHAR wcuser[MAX_PATH], wcsys[MAX_PATH]; DWORD indentLevel = 0; // Oct 15, 1999 // This is BAD ! The Status bit was not initiazted to zero, which causes intermitent // execution by this function. The problem is that even if the status bit is init'd // to zero, then we can get the wrong behavior which will cause Office97 installation to // go wrong. // See BUG ID 412419 // Here is what woudl happen: Install any app ( say TsClient). This would cause // an update to a Key in HKDU called Explorer\ShellFolders, just a refresh (no real change). // Then, if Admin did an logout and login, the call from UserInit.EXE into this // function (assuming by chance Status=0 was on the stack) would cause deletion of // that key (ShellFolder). But once Explorer starts, it writes to the ShellFolder with // a subset of original 19 values. // The problem is that if then, you decide to install Office97, setup.exe would look // in the same key for a value called "template" which is now missing. Explorer // would then create it, but it would point to some other than default location. // When all was done, our office97 compat script would be looking to where the // "template" value used to point (the default location), not where it is pointing now. // // we decide to disable this func by calling return right here, and instead, rely // on the TS mechanism to fault in keys. // Oct 31, 1999 // I have decided to initialize the status var and let this func run, in addition to // marking the Explorer\ShellFolders as a do-not propagate key tree. // // It was discovered that after installing Office2000, when a user clicks on the // start-menu-> Open Office Docs link, MSI starts to run since user's hive is missing some // keys. // // The reason MSI does not see the keys in HKCU is because MSI has the TS-aware bit set, // which means that there are no faulting-in for any keys. The same is true for the Explorer. // On the other hand, when an app such as Office runs, since it is not ts-aware, we // fault in the keys that office touches. I verified this to work as expected. // The problem is that when you click on "Open Office Documents", you do so from the // explorer and when Explorer opens keys in the registry, since explorer is TS-aware, // those keys are not faulted in. I have verified that if you mark explorer as a non-TS-aware // app, the problem goes away. // We made a recent change in TS code (B-bug 412419, bld 2156+) to fix a different // problem which has now uncovered the reliance of the Explorer to get keys // faulted in during login. // Specifically, TS used to fault in all keys at login time, // regardless of the need. Post 2156, TS faults in only keys upon // access by non-ts-aware apps. This was to fix a bug based on // what we considered to be our most informed and well tested opinion. That // has turned out to be wrong. // It is too risky to mark explorer non-ts-aware this late, so we must change the fix for 412419. // This should also make Bruno Amice very happy, since the fix will be as it was // advocated by him. Status = STATUS_SUCCESS; // Get a buffer for the system key info ulcsys = sizeof(KEY_BASIC_INFORMATION) + MAX_PATH*sizeof(WCHAR); pKeySysInfo = RtlAllocateHeap(RtlProcessHeap(), 0, ulcsys); if (!pKeySysInfo) { Status = STATUS_NO_MEMORY; } // Get a buffer for the user key info if (NT_SUCCESS(Status)) { ulcusr = sizeof(KEY_FULL_INFORMATION) + MAX_PATH*sizeof(WCHAR); pKeyUserInfo = RtlAllocateHeap(RtlProcessHeap(), 0, ulcusr); if (!pKeyUserInfo) { Status = STATUS_NO_MEMORY; } } // We have the necessary buffers, start checking the keys if (NT_SUCCESS(Status)) { // Build a string that points to Citrix\Install\Software wcscpy(wcsys, wszBaseKeyName); wcscat(wcsys, TERMSRV_INSTALL_SOFTWARE_SHORT); // Build up a string for this user's software section Status = RtlFormatCurrentUserKeyPath( &UniPath ); if (NT_SUCCESS(Status)) { wcscpy(wcuser, UniPath.Buffer); wcscat(wcuser, L"\\Software"); // Free the original user path RtlFreeHeap( RtlProcessHeap(), 0, UniPath.Buffer ); } if (NT_SUCCESS(Status)) { // Create a unicode string for the system key path RtlInitUnicodeString(&UniPath, wcsys); InitializeObjectAttributes(&ObjAttr, &UniPath, OBJ_CASE_INSENSITIVE, NULL, NULL); Debug1(indentLevel, __LINE__, L"system", &UniPath ); Status = NtOpenKey(&hKeySys, KEY_READ, &ObjAttr); } if (NT_SUCCESS(Status)) { // Create a unicode string for the user key path RtlInitUnicodeString(&UniPath, wcuser); InitializeObjectAttributes(&ObjAttr, &UniPath, OBJ_CASE_INSENSITIVE, NULL, NULL); Debug1(indentLevel, __LINE__, L"user", &UniPath ); Status = NtOpenKey(&hKeyUser, KEY_READ | DELETE, &ObjAttr); } // Go through each of the keys, checking if the system version is // newer than the user version if (NT_SUCCESS(Status)) { TermsrvCheckKeys(hKeySys, hKeyUser, pKeySysInfo, pKeyUserInfo, ulcsys, ulcusr, indentLevel ); // Close the user key NtClose(hKeyUser); } // If we allocated the system key, close it if (hKeySys) { NtClose(hKeySys); } } // Free up any memory we allocated if (pKeySysInfo) { RtlFreeHeap( RtlProcessHeap(), 0, pKeySysInfo); } if (pKeyUserInfo) { RtlFreeHeap( RtlProcessHeap(), 0, pKeyUserInfo); } } /***************************************************************************** * * Ctxstristr * * This is a case insensitive version of strstr. * * ENTRY: * PCHAR pstring1 (In) - String to search in * PCHAR pstring2 (In) - String to search for * * EXIT: * TRUE - User ini file should be sync'd * FALSE - User ini file should be sync'd * ****************************************************************************/ PCHAR Ctxstristr( PCHAR pstring1, PCHAR pstring2) { PCHAR pch, ps1, ps2; pch = pstring1; while (*pch) { ps1 = pch; ps2 = pstring2; while (*ps1 && *ps2 && !(toupper(*ps1) - toupper(*ps2))) { ps1++; ps2++; } if (!*ps2) { return(pch); } pch++; } return(NULL); } /***************************************************************************** * * TermsrvLogInstallIniFile * * This routine will write the time the .ini file was last updated into the * Terminal Server\install section of the registry. * * ENTRY: * * EXIT: * TRUE - Success * FALSE - Failure * ****************************************************************************/ BOOL TermsrvLogInstallIniFile(PUNICODE_STRING NtFileName) { PWCHAR pwch; UNICODE_STRING UniString; OBJECT_ATTRIBUTES ObjectAttr; FILE_NETWORK_OPEN_INFORMATION BasicInfo; HANDLE hKey; NTSTATUS Status; ULONG ultmp; if (!TermsrvPerUserWinDirMapping()) { return FALSE; } // Open up the registry key to store the last write time of the file RtlInitUnicodeString(&UniString, TERMSRV_INSTALL); InitializeObjectAttributes(&ObjectAttr, &UniString, OBJ_CASE_INSENSITIVE, NULL, NULL); // Open or create the Terminal Server\Install path Status = NtCreateKey(&hKey, KEY_WRITE, &ObjectAttr, 0, NULL, REG_OPTION_NON_VOLATILE, &ultmp); // Now open or create the IniFile Times key if (NT_SUCCESS(Status)) { NtClose(hKey); RtlInitUnicodeString(&UniString, TERMSRV_INIFILE_TIMES); InitializeObjectAttributes(&ObjectAttr, &UniString, OBJ_CASE_INSENSITIVE, NULL, NULL); Status = NtCreateKey(&hKey, KEY_WRITE, &ObjectAttr, 0, NULL, REG_OPTION_NON_VOLATILE, &ultmp); } // Opened up the registry key, now get the last write time of the file if (NT_SUCCESS(Status)) { // Query the last write time of the .ini file InitializeObjectAttributes(&ObjectAttr, NtFileName, OBJ_CASE_INSENSITIVE, NULL, NULL); Status = NtQueryFullAttributesFile( &ObjectAttr, &BasicInfo ); // Got the last write time, convert to seconds and write it out if (NT_SUCCESS(Status)) { // Just save the .ini filename, get rid of the path pwch = wcsrchr(NtFileName->Buffer, L'\\') + 1; if (!pwch) { pwch = NtFileName->Buffer; } // Convert to seconds (so it fits in a DWORD) RtlTimeToSecondsSince1970 (&BasicInfo.LastWriteTime, &ultmp); RtlInitUnicodeString(&UniString, pwch); // Write it out the .ini file name and the last write time Status = NtSetValueKey(hKey, &UniString, 0, REG_DWORD, &ultmp, sizeof(ultmp)); } // Close the registry key NtClose(hKey); } return(NT_SUCCESS(Status)); } /********************************************************************** * * BOOL TermsrvLogInstallIniFileEx( WCHAR *pDosFileName ) * * This wraps TermsrvLogInstallIniFile() for dos name files instead of * NT-file-objects * * The file name must have the full path since func will try to get * access time for that file. * * EXIT: * TRUE - Success * FALSE - Failure **********************************************************************/ BOOL TermsrvLogInstallIniFileEx( WCHAR *pDosFileName ) { UNICODE_STRING uniString; BOOL rc= FALSE; if ( RtlDosPathNameToNtPathName_U( pDosFileName, &uniString, 0, 0 ) ) { if ( rc = TermsrvLogInstallIniFile( & uniString ) ) { RtlFreeHeap( RtlProcessHeap(), 0, uniString.Buffer ); } } return rc; } /********************************************************************** * * GetFullKeyPath() * * PURPOSE: * Creates full key path given the key handle and subkey name. * * PARAMETERS: * IN HANDLE hKeyParent - key handle * IN LPCWSTR wszKey - subkey name (may be NULL) * OUT LPWSTR *pwszKeyPath - on return contains a full key path * (caller must freee allocated memory with LocalFree()). * * EXIT: NTSTATUS * **********************************************************************/ NTSTATUS GetFullKeyPath( IN HANDLE hKeyParent, IN LPCWSTR wszKey, OUT LPWSTR *pwszKeyPath) { NTSTATUS Status = STATUS_NO_MEMORY; PKEY_NAME_INFORMATION pNameInfo; ULONG cbSize = 0; *pwszKeyPath = NULL; cbSize = sizeof(KEY_NAME_INFORMATION) + MAX_PATH*sizeof(WCHAR); pNameInfo = (PKEY_NAME_INFORMATION) LocalAlloc(LPTR, cbSize); if(pNameInfo) { Status = NtQueryKey( hKeyParent, KeyNameInformation, pNameInfo, cbSize, &cbSize); if(Status == STATUS_BUFFER_OVERFLOW) { LocalFree(pNameInfo); pNameInfo = (PKEY_NAME_INFORMATION) LocalAlloc(LPTR, cbSize); if(pNameInfo) { Status = NtQueryKey( hKeyParent, KeyNameInformation, pNameInfo, cbSize, &cbSize); } else { return STATUS_NO_MEMORY; } } if(NT_SUCCESS(Status)) { cbSize = pNameInfo->NameLength + sizeof(WCHAR); if(wszKey) { cbSize += wcslen(wszKey)*sizeof(WCHAR) + sizeof(L'\\'); } *pwszKeyPath = (LPWSTR) LocalAlloc(LPTR, cbSize); if(*pwszKeyPath) { memcpy(*pwszKeyPath,pNameInfo->Name,pNameInfo->NameLength); (*pwszKeyPath)[pNameInfo->NameLength/sizeof(WCHAR)] = 0; if(wszKey) { wcscat(*pwszKeyPath,L"\\"); wcscat(*pwszKeyPath,wszKey); } } else { Status = STATUS_NO_MEMORY; } } LocalFree(pNameInfo); } return Status; } VOID InitUnicodeStringWithLen( OUT PUNICODE_STRING DestinationString, IN PCWSTR SourceString, IN USHORT StringLength ) { DestinationString->Buffer = (PWSTR)SourceString; DestinationString->Length = StringLength; if ( StringLength ) { DestinationString->MaximumLength = StringLength + (USHORT)sizeof(UNICODE_NULL); } else { DestinationString->MaximumLength = 0; } }