[APPHELP]
[reactos.git] / reactos / dll / appcompat / apphelp / layer.c
1 /*
2 * Copyright 2015 Mark Jansen
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17 */
18
19 #define WIN32_NO_STATUS
20 #include "windef.h"
21 #include "winbase.h"
22 #include "strsafe.h"
23 #include <ntndk.h>
24 #include "apphelp.h"
25
26 #define GPLK_USER 1
27 #define GPLK_MACHINE 2
28 #define MAX_LAYER_LENGTH 256
29 #define LAYER_APPLY_TO_SYSTEM_EXES 1
30 #define LAYER_UNK_FLAG2 2
31
32 #ifndef REG_SZ
33 #define REG_SZ 1
34 #endif
35
36 #if defined(__GNUC__)
37 #define APPCOMPAT_LAYER_KEY (const WCHAR[]){'\\','S','o','f','t','w','a','r','e','\\','M','i','c','r','o','s','o','f','t','\\','W','i','n','d','o','w','s',' ','N','T','\\','C','u','r','r','e','n','t','V','e','r','s','i','o','n','\\','A','p','p','C','o','m','p','a','t','F','l','a','g','s','\\','L','a','y','e','r','s',0}
38 #define REGISTRY_MACHINE (const WCHAR[]){'\\','R','e','g','i','s','t','r','y','\\','M','a','c','h','i','n','e',0}
39 #define SPACE_ONLY (const WCHAR[]){' ',0}
40 #define DISALLOWED_LAYER_CHARS (const WCHAR[]){' ','#','!',0}
41 #define LAYER_SEPARATORS (const WCHAR[]){' ','\t',0}
42 #define SIGN_MEDIA_FMT (const WCHAR[]){'S','I','G','N','.','M','E','D','I','A','=','%','X',' ','%','s',0}
43
44 #else
45 #define APPCOMPAT_LAYER_KEY L"\\Software\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Layers"
46 #define REGISTRY_MACHINE L"\\Registry\\Machine"
47 #define SPACE_ONLY L" "
48 #define DISALLOWED_LAYER_CHARS L" #!"
49 #define LAYER_SEPARATORS L" \t"
50 #define SIGN_MEDIA_FMT L"SIGN.MEDIA=%X %s"
51 #endif
52
53
54 typedef struct SDB_TMP_STR
55 {
56 UNICODE_STRING Str;
57 WCHAR FixedBuffer[MAX_PATH];
58 } SDB_TMP_STR, *PSDB_TMP_STR;
59
60 void SdbpInitTempStr(PSDB_TMP_STR String)
61 {
62 String->Str.Buffer = String->FixedBuffer;
63 String->Str.Length = 0;
64 String->Str.MaximumLength = sizeof(String->FixedBuffer);
65 }
66
67 void SdbpFreeTempStr(PSDB_TMP_STR String)
68 {
69 if (String->Str.Buffer != String->FixedBuffer)
70 {
71 SdbFree(String->Str.Buffer);
72 }
73 }
74
75 void SdbpResizeTempStr(PSDB_TMP_STR String, WORD newLength)
76 {
77 if (newLength > String->Str.MaximumLength)
78 {
79 SdbpFreeTempStr(String);
80 String->Str.MaximumLength = newLength * sizeof(WCHAR);
81 String->Str.Buffer = SdbAlloc(String->Str.MaximumLength);
82 String->Str.Length = 0;
83 }
84 }
85
86 BOOL SdbpGetLongPathName(PCWSTR wszPath, PSDB_TMP_STR Result)
87 {
88 DWORD max = Result->Str.MaximumLength / 2;
89 DWORD ret = GetLongPathNameW(wszPath, Result->Str.Buffer, max);
90 if (ret)
91 {
92 if (ret >= max)
93 {
94 SdbpResizeTempStr(Result, ret);
95 max = Result->Str.MaximumLength / 2;
96 ret = GetLongPathNameW(wszPath, Result->Str.Buffer, max);
97 }
98 if (ret && ret < max)
99 {
100 Result->Str.Length = ret * 2;
101 return TRUE;
102 }
103 }
104 SHIM_ERR("Failed to convert short path to long path error 0x%lx\n", GetLastError());
105 return FALSE;
106 }
107
108 BOOL SdbpIsPathOnRemovableMedia(PCWSTR Path)
109 {
110 WCHAR tmp[] = { 'A',':','\\',0 };
111 ULONG type;
112 if (!Path)
113 {
114 SHIM_ERR("Invalid argument\n");
115 return FALSE;
116 }
117 switch (Path[1])
118 {
119 case L':':
120 break;
121 case L'\\':
122 SHIM_INFO("\"%S\" is a network path.\n", Path);
123 return FALSE;
124 default:
125 SHIM_INFO("\"%S\" not a full path we can operate on.\n", Path);
126 return FALSE;
127 }
128 tmp[0] = Path[0];
129 type = GetDriveTypeW(tmp);
130
131 return type == DRIVE_REMOVABLE || type == DRIVE_CDROM;
132 }
133
134 BOOL SdbpBuildSignMediaId(PSDB_TMP_STR LongPath)
135 {
136 SDB_TMP_STR Scratch;
137 PWCHAR Ptr;
138
139 SdbpInitTempStr(&Scratch);
140 SdbpResizeTempStr(&Scratch, LongPath->Str.Length / sizeof(WCHAR) + 30);
141 StringCbCopyNW(Scratch.Str.Buffer, Scratch.Str.MaximumLength, LongPath->Str.Buffer, LongPath->Str.Length);
142 Ptr = wcsrchr(LongPath->Str.Buffer, '\\');
143 if (Ptr)
144 {
145 HANDLE FindHandle;
146 WIN32_FIND_DATAW FindData;
147 Ptr[1] = '*';
148 Ptr[2] = '\0';
149 FindHandle = FindFirstFileW(LongPath->Str.Buffer, &FindData);
150 if (FindHandle != INVALID_HANDLE_VALUE)
151 {
152 DWORD SignMedia = 0;
153 do
154 {
155 if (!(FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && FindData.nFileSizeLow)
156 SignMedia = SignMedia << 1 ^ FindData.nFileSizeLow;
157 } while (FindNextFileW(FindHandle, &FindData));
158
159 FindClose(FindHandle);
160 SdbpResizeTempStr(LongPath, (LongPath->Str.Length >> 1) + 20);
161 StringCbPrintfW(LongPath->Str.Buffer, LongPath->Str.MaximumLength, SIGN_MEDIA_FMT, SignMedia, Scratch.Str.Buffer + 3);
162 LongPath->Str.Length = wcslen(LongPath->Str.Buffer) * sizeof(WCHAR);
163 SdbpFreeTempStr(&Scratch);
164 return TRUE;
165 }
166 }
167 SdbpFreeTempStr(&Scratch);
168 SdbpFreeTempStr(LongPath);
169 return FALSE;
170 }
171
172 BOOL SdbpResolvePath(PSDB_TMP_STR LongPath, PCWSTR wszPath)
173 {
174 SdbpInitTempStr(LongPath);
175 if (!SdbpGetLongPathName(wszPath, LongPath))
176 {
177 SdbpFreeTempStr(LongPath);
178 return FALSE;
179 }
180 if (SdbpIsPathOnRemovableMedia(LongPath->Str.Buffer))
181 {
182 return SdbpBuildSignMediaId(LongPath);
183 }
184 return TRUE;
185 }
186
187 static ACCESS_MASK g_QueryFlag = 0xffffffff;
188 ACCESS_MASK QueryFlag(void)
189 {
190 if (g_QueryFlag == 0xffffffff)
191 {
192 ULONG_PTR wow64_ptr = 0;
193 NTSTATUS Status = NtQueryInformationProcess(NtCurrentProcess(), ProcessWow64Information, &wow64_ptr, sizeof(wow64_ptr), NULL);
194 g_QueryFlag = (NT_SUCCESS(Status) && wow64_ptr != 0) ? KEY_WOW64_64KEY : 0;
195 }
196 return g_QueryFlag;
197 }
198
199 NTSTATUS SdbpOpenKey(PUNICODE_STRING FullPath, BOOL bMachine, ACCESS_MASK Access, PHANDLE KeyHandle)
200 {
201 UNICODE_STRING BasePath;
202 const WCHAR* LayersKey = APPCOMPAT_LAYER_KEY;
203 OBJECT_ATTRIBUTES ObjectLayer = RTL_INIT_OBJECT_ATTRIBUTES(FullPath, OBJ_CASE_INSENSITIVE);
204 NTSTATUS Status;
205 FullPath->Buffer = NULL;
206 FullPath->Length = FullPath->MaximumLength = 0;
207 if (bMachine)
208 {
209 RtlInitUnicodeString(&BasePath, REGISTRY_MACHINE);
210 }
211 else
212 {
213 Status = RtlFormatCurrentUserKeyPath(&BasePath);
214 if (!NT_SUCCESS(Status))
215 {
216 SHIM_ERR("Unable to aquire user registry key, Error: 0x%lx\n", Status);
217 return Status;
218 }
219 }
220 FullPath->MaximumLength = BasePath.Length + (wcslen(LayersKey) + 1) * sizeof(WCHAR);
221 FullPath->Buffer = SdbAlloc(FullPath->MaximumLength);
222 FullPath->Length = 0;
223 RtlAppendUnicodeStringToString(FullPath, &BasePath);
224 if (!bMachine)
225 RtlFreeUnicodeString(&BasePath);
226 RtlAppendUnicodeToString(FullPath, LayersKey);
227
228 Status = NtOpenKey(KeyHandle, Access | QueryFlag(), &ObjectLayer);
229 if (!NT_SUCCESS(Status))
230 {
231 SHIM_ERR("Unable to open Key \"%wZ\" Status 0x%lx\n", FullPath, Status);
232 SdbFree(FullPath->Buffer);
233 FullPath->Buffer = NULL;
234 }
235 return Status;
236 }
237
238
239 BOOL SdbpGetPermLayersInternal(PUNICODE_STRING FullPath, PWSTR pwszLayers, PDWORD pdwBytes, BOOL bMachine)
240 {
241 UNICODE_STRING FullKey;
242 ULONG ValueBuffer[(MAX_LAYER_LENGTH * sizeof(WCHAR) + sizeof(KEY_VALUE_PARTIAL_INFORMATION) + sizeof(ULONG) - 1) / sizeof(ULONG)];
243 PKEY_VALUE_PARTIAL_INFORMATION PartialInfo = (PVOID)ValueBuffer;
244 ULONG Length = 0;
245 HANDLE KeyHandle;
246 NTSTATUS Status;
247
248 Status = SdbpOpenKey(&FullKey, bMachine, KEY_QUERY_VALUE, &KeyHandle);
249 if (NT_SUCCESS(Status))
250 {
251 Status = NtQueryValueKey(KeyHandle, FullPath, KeyValuePartialInformation, PartialInfo, sizeof(ValueBuffer), &Length);
252 if (NT_SUCCESS(Status))
253 {
254 StringCbCopyNW(pwszLayers, *pdwBytes, (PCWSTR)PartialInfo->Data, PartialInfo->DataLength);
255 *pdwBytes = PartialInfo->DataLength;
256 }
257 else
258 {
259 SHIM_INFO("Failed to read value info from Key \"%wZ\" Status 0x%lx\n", &FullKey, Status);
260 }
261 NtClose(KeyHandle);
262 SdbFree(FullKey.Buffer);
263 }
264 return NT_SUCCESS(Status);
265 }
266
267 BOOL SdbDeletePermLayerKeys(PCWSTR wszPath, BOOL bMachine)
268 {
269 UNICODE_STRING FullKey;
270 SDB_TMP_STR LongPath;
271 HANDLE KeyHandle;
272 NTSTATUS Status;
273
274 if (!SdbpResolvePath(&LongPath, wszPath))
275 return FALSE;
276
277 Status = SdbpOpenKey(&FullKey, bMachine, KEY_SET_VALUE, &KeyHandle);
278 if (NT_SUCCESS(Status))
279 {
280 Status = NtDeleteValueKey(KeyHandle, &LongPath.Str);
281 if (!NT_SUCCESS(Status))
282 {
283 SHIM_INFO("Failed to delete value from Key \"%wZ\" Status 0x%lx\n", &FullKey, Status);
284 /* This is what we want, so if the key didnt exist, we should not fail :) */
285 if (Status == STATUS_OBJECT_NAME_NOT_FOUND)
286 Status = STATUS_SUCCESS;
287 }
288 NtClose(KeyHandle);
289 SdbFree(FullKey.Buffer);
290 }
291 SdbpFreeTempStr(&LongPath);
292 return NT_SUCCESS(Status);
293 }
294
295 BOOL SdbpMatchLayer(PCWSTR start, PCWSTR end, PCWSTR compare)
296 {
297 size_t len;
298 if (!end)
299 return !wcsicmp(start, compare);
300 len = end - start;
301 return wcslen(compare) == len && !_wcsnicmp(start, compare, len);
302 }
303
304 BOOL SdbpAppendLayer(PWSTR target, DWORD len, PCWSTR layer, PCWSTR end)
305 {
306 NTSTATUS Status = STATUS_SUCCESS;
307 if (target[0])
308 Status = StringCbCatW(target, len, SPACE_ONLY);
309
310 if (NT_SUCCESS(Status))
311 {
312 if (end)
313 Status = StringCbCatNW(target, len, layer, (end - layer) * sizeof(WCHAR));
314 else
315 Status = StringCbCatW(target, len, layer);
316 }
317
318 return NT_SUCCESS(Status);
319 }
320
321
322 /**
323 * Determine if we allow permission layers to apply on this file.
324 *
325 * @param [in] Path Full pathname of the file, only the drive part is used.
326 *
327 * @return TRUE if we allow permission layer, FALSE if not.
328 */
329 BOOL WINAPI AllowPermLayer(PCWSTR Path)
330 {
331 WCHAR tmp[] = { 'A',':','\\', 0 };
332 ULONG type;
333 if (!Path)
334 {
335 SHIM_ERR("Invalid argument\n");
336 return FALSE;
337 }
338 switch (Path[1])
339 {
340 case L':':
341 break;
342 case L'\\':
343 SHIM_INFO("\"%S\" is a network path.\n", Path);
344 return FALSE;
345 default:
346 SHIM_INFO("\"%S\" not a full path we can operate on.\n", Path);
347 return FALSE;
348 }
349 tmp[0] = Path[0];
350 type = GetDriveTypeW(tmp);
351 if (type == DRIVE_REMOTE)
352 {
353 /* The logging here indicates that it does not like a CDROM or removable media, but it only
354 seems to bail out on a media that reports it is remote...
355 I have included correct logging, I doubt anyone would parse the logging, so this shouldnt break anything. */
356 SHIM_INFO("\"%S\" is on a remote drive.\n", Path);
357 return FALSE;
358 }
359 return TRUE;
360 }
361
362 /**
363 * Read the layers specified for the application.
364 *
365 * @param [in] wszPath Full pathname of the file.
366 * @param [out] pwszLayers On return, the layers set on the file.
367 * @param pdwBytes The size of the pwszLayers buffer in bytes, and on return the size of
368 * the data written (in bytes)
369 * @param [in] dwFlags The flags, [GPLK_USER | GPLK_MACHINE].
370 *
371 * @return TRUE if it succeeds, FALSE if it fails.
372 */
373 BOOL WINAPI SdbGetPermLayerKeys(PCWSTR wszPath, PWSTR pwszLayers, PDWORD pdwBytes, DWORD dwFlags)
374 {
375 BOOL Result = FALSE;
376 SDB_TMP_STR LongPath;
377 DWORD dwBytes, dwTotal = 0;
378 if (!wszPath || !pdwBytes)
379 {
380 SHIM_ERR("NULL parameter passed for wszPath or pdwBytes.\n");
381 return FALSE;
382 }
383
384 if (!SdbpResolvePath(&LongPath, wszPath))
385 return FALSE;
386 dwBytes = *pdwBytes;
387 if (dwFlags & GPLK_MACHINE)
388 {
389 if (SdbpGetPermLayersInternal(&LongPath.Str, pwszLayers, &dwBytes, TRUE))
390 {
391 Result = TRUE;
392 dwTotal = dwBytes - sizeof(WCHAR); /* Compensate for the nullterm. */
393 pwszLayers += dwTotal / sizeof(WCHAR);
394 dwBytes = *pdwBytes - dwBytes;
395 if (dwFlags & GPLK_USER)
396 {
397 *(pwszLayers++) = L' ';
398 *pwszLayers = L'\0';
399 dwBytes -= sizeof(WCHAR);
400 dwTotal += sizeof(WCHAR);
401 }
402 }
403 }
404 if (dwFlags & GPLK_USER)
405 {
406 if (SdbpGetPermLayersInternal(&LongPath.Str, pwszLayers, &dwBytes, FALSE))
407 {
408 Result = TRUE;
409 dwTotal += dwBytes - sizeof(WCHAR); /* Compensate for the nullterm. */
410 }
411 else if (dwTotal > 0 && pwszLayers[-1] == L' ')
412 {
413 pwszLayers[-1] = '\0';
414 dwTotal -= sizeof(WCHAR);
415 }
416 }
417 if (dwTotal)
418 dwTotal += sizeof(WCHAR);
419 *pdwBytes = dwTotal;
420 SdbpFreeTempStr(&LongPath);
421 return Result;
422 }
423
424 /**
425 * Set or clear the Layer key.
426 *
427 * @param [in] wszPath Full pathname of the file.
428 * @param [in] wszLayers The layers to add (space separated), or an empty string / NULL to
429 * remove all layers.
430 * @param [in] bMachine TRUE to machine.
431 *
432 * @return TRUE if it succeeds, FALSE if it fails.
433 */
434 BOOL WINAPI SdbSetPermLayerKeys(PCWSTR wszPath, PCWSTR wszLayers, BOOL bMachine)
435 {
436 UNICODE_STRING FullKey;
437 SDB_TMP_STR LongPath;
438 HANDLE KeyHandle;
439 NTSTATUS Status;
440
441 if (!wszLayers || *wszLayers == '\0')
442 return SdbDeletePermLayerKeys(wszPath, bMachine);
443
444 if (!SdbpResolvePath(&LongPath, wszPath))
445 return FALSE;
446
447 Status = SdbpOpenKey(&FullKey, bMachine, KEY_SET_VALUE, &KeyHandle);
448 if (NT_SUCCESS(Status))
449 {
450 Status = NtSetValueKey(KeyHandle, &LongPath.Str, 0, REG_SZ, (PVOID)wszLayers, (wcslen(wszLayers)+1) * sizeof(WCHAR));
451 if (!NT_SUCCESS(Status))
452 {
453 SHIM_INFO("Failed to write a value to Key \"%wZ\" Status 0x%lx\n", &FullKey, Status);
454 if (Status == STATUS_OBJECT_NAME_NOT_FOUND)
455 Status = STATUS_SUCCESS;
456 }
457 NtClose(KeyHandle);
458 SdbFree(FullKey.Buffer);
459 }
460 SdbpFreeTempStr(&LongPath);
461 return NT_SUCCESS(Status);
462 }
463
464 /**
465 * Adds or removes a single layer entry.
466 *
467 * @param [in] wszPath Full pathname of the file.
468 * @param [in] wszLayer The layer to add or remove.
469 * @param [in] dwFlags Additional flags to add / remove [LAYER_APPLY_TO_SYSTEM_EXES | ???].
470 * @param [in] bMachine When TRUE, the setting applies to all users, when FALSE only applies
471 * to the current user.
472 * @param [in] bEnable TRUE to enable, FALSE to disable a layer / flag specified.
473 *
474 * @return TRUE if it succeeds, FALSE if it fails.
475 */
476 BOOL WINAPI SetPermLayerState(PCWSTR wszPath, PCWSTR wszLayer, DWORD dwFlags, BOOL bMachine, BOOL bEnable)
477 {
478 WCHAR fullLayer[MAX_LAYER_LENGTH] = { 0 };
479 WCHAR newLayer[MAX_LAYER_LENGTH] = { 0 };
480 DWORD dwBytes = sizeof(fullLayer), dwWriteFlags = 0;
481 PWSTR start, p;
482
483 if (!wszLayer)
484 {
485 SHIM_ERR("Invalid argument\n");
486 return FALSE;
487 }
488 if (dwFlags & ~(LAYER_APPLY_TO_SYSTEM_EXES | LAYER_UNK_FLAG2))
489 {
490 SHIM_ERR("Invalid flags\n");
491 return FALSE;
492 }
493 p = wcspbrk(wszLayer, DISALLOWED_LAYER_CHARS);
494 if (p)
495 {
496 switch (*p)
497 {
498 case ' ':
499 SHIM_ERR("Only one layer can be passed in at a time.\n");
500 return FALSE;
501 case '#':
502 case '!':
503 SHIM_ERR("Flags cannot be passed in with the layer name.\n");
504 return FALSE;
505 }
506 }
507 if (!SdbGetPermLayerKeys(wszPath, fullLayer, &dwBytes, bMachine ? GPLK_MACHINE : GPLK_USER))
508 {
509 fullLayer[0] = '\0';
510 dwBytes = sizeof(fullLayer);
511 }
512
513 start = fullLayer;
514 while (*start == '!' || *start == '#' || *start == ' ' || *start == '\t')
515 {
516 if (*start == '#')
517 dwWriteFlags |= LAYER_APPLY_TO_SYSTEM_EXES;
518 else if (*start == '!')
519 dwWriteFlags |= LAYER_UNK_FLAG2;
520 start++;
521 }
522 if (bEnable)
523 dwWriteFlags |= dwFlags;
524 else
525 dwWriteFlags &= ~dwFlags;
526
527 p = newLayer;
528 if (dwWriteFlags & LAYER_UNK_FLAG2)
529 *(p++) = '!';
530 if (dwWriteFlags & LAYER_APPLY_TO_SYSTEM_EXES)
531 *(p++) = '#';
532
533 do
534 {
535 while (*start == ' ' || *start == '\t')
536 ++start;
537
538 if (*start == '\0')
539 break;
540 p = wcspbrk(start, LAYER_SEPARATORS);
541 if (!SdbpMatchLayer(start, p, wszLayer))
542 {
543 SdbpAppendLayer(newLayer, sizeof(newLayer), start, p);
544 }
545 start = p + 1;
546 } while (p);
547
548 if (bEnable && wszLayer[0])
549 {
550 SdbpAppendLayer(newLayer, sizeof(newLayer), wszLayer, NULL);
551 }
552
553 return SdbSetPermLayerKeys(wszPath, newLayer, bMachine);
554 }