bed1361040efaafc016abfe511493a50fb9738e9
[reactos.git] / reactos / dll / appcompat / apphelp / hsdb.c
1 /*
2 * Copyright 2011 André Hentschel
3 * Copyright 2013 Mislav Blažević
4 * Copyright 2015-2017 Mark Jansen
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 */
20
21 #define WIN32_NO_STATUS
22 #include "windows.h"
23 #include "ntndk.h"
24 #include "strsafe.h"
25 #include "apphelp.h"
26
27 #include "wine/unicode.h"
28
29 #define MAX_LAYER_LENGTH 256
30 #define GPLK_USER 1
31 #define GPLK_MACHINE 2
32
33 static BOOL WINAPI SdbpFileExists(LPCWSTR path)
34 {
35 DWORD attr = GetFileAttributesW(path);
36 return (attr != INVALID_FILE_ATTRIBUTES && !(attr & FILE_ATTRIBUTE_DIRECTORY));
37 }
38
39 static BOOL WINAPI SdbpMatchExe(PDB db, TAGID exe, WCHAR* dir)
40 {
41 static const WCHAR fmt[] = {'%','s','%','s',0};
42 WCHAR buffer[256]; /* FIXME: rewrite using a buffer that can grow if needed, f.e. RtlInitBuffer stuff! */
43 TAGID matching_file;
44
45 /* TODO: check size/checksum from the main exe as well as from the extra files */
46 for (matching_file = SdbFindFirstTag(db, exe, TAG_MATCHING_FILE);
47 matching_file != TAGID_NULL; matching_file = SdbFindNextTag(db, exe, matching_file))
48 {
49 TAGID tagName = SdbFindFirstTag(db, matching_file, TAG_NAME);
50 LPWSTR name = SdbGetStringTagPtr(db, tagName);
51
52 if (!wcscmp(name, L"*"))
53 {
54 // if attributes dont match main file, return FALSE!
55 continue;
56 }
57
58 snprintfW(buffer, _countof(buffer), fmt, dir, name);
59 if (!SdbpFileExists(buffer))
60 return FALSE;
61 }
62
63 return TRUE;
64 }
65
66 static void SdbpAddDatabaseGuid(PDB db, PSDBQUERYRESULT result)
67 {
68 size_t n;
69
70 for (n = 0; n < _countof(result->rgGuidDB); ++n)
71 {
72 if (!memcmp(&result->rgGuidDB[n], &db->database_id, sizeof(db->database_id)))
73 return;
74
75 if (result->dwCustomSDBMap & (1<<n))
76 continue;
77
78 memcpy(&result->rgGuidDB[n], &db->database_id, sizeof(result->rgGuidDB[n]));
79 result->dwCustomSDBMap |= (1<<n);
80 return;
81 }
82 }
83
84 static BOOL SdbpAddSingleLayerMatch(TAGREF layer, PSDBQUERYRESULT result)
85 {
86 size_t n;
87
88 for (n = 0; n < result->dwLayerCount; ++n)
89 {
90 if (result->atrLayers[n] == layer)
91 return FALSE;
92 }
93
94 if (n >= _countof(result->atrLayers))
95 return FALSE;
96
97 result->atrLayers[n] = layer;
98 result->dwLayerCount++;
99
100 return TRUE;
101 }
102
103
104 static BOOL SdbpAddNamedLayerMatch(HSDB hsdb, PCWSTR layerName, PSDBQUERYRESULT result)
105 {
106 TAGID database, layer;
107 TAGREF tr;
108 PDB db = hsdb->db;
109
110 database = SdbFindFirstTag(db, TAGID_ROOT, TAG_DATABASE);
111 if (database == TAGID_NULL)
112 return FALSE;
113
114 layer = SdbFindFirstNamedTag(db, database, TAG_LAYER, TAG_NAME, layerName);
115 if (layer == TAGID_NULL)
116 return FALSE;
117
118 if (!SdbTagIDToTagRef(hsdb, db, layer, &tr))
119 return FALSE;
120
121 if (!SdbpAddSingleLayerMatch(tr, result))
122 return FALSE;
123
124 SdbpAddDatabaseGuid(db, result);
125 return TRUE;
126 }
127
128 static void SdbpAddExeLayers(HSDB hsdb, PDB db, TAGID tagExe, PSDBQUERYRESULT result)
129 {
130 TAGID layer = SdbFindFirstTag(db, tagExe, TAG_LAYER);
131
132 while (layer != TAGID_NULL)
133 {
134 TAGREF tr;
135 TAGID layerIdTag = SdbFindFirstTag(db, layer, TAG_LAYER_TAGID);
136 DWORD tagId = SdbReadDWORDTag(db, layerIdTag, TAGID_NULL);
137
138 if (layerIdTag != TAGID_NULL &&
139 tagId != TAGID_NULL &&
140 SdbTagIDToTagRef(hsdb, db, tagId, &tr))
141 {
142 SdbpAddSingleLayerMatch(tr, result);
143 }
144 else
145 {
146 /* Try a name lookup */
147 TAGID layerTag = SdbFindFirstTag(db, layer, TAG_NAME);
148 if (layerTag != TAGID_NULL)
149 {
150 LPCWSTR layerName = SdbGetStringTagPtr(db, layerTag);
151 if (layerName)
152 {
153 SdbpAddNamedLayerMatch(hsdb, layerName, result);
154 }
155 }
156 }
157
158 layer = SdbFindNextTag(db, tagExe, layer);
159 }
160 }
161
162 static void SdbpAddExeMatch(HSDB hsdb, PDB db, TAGID tagExe, PSDBQUERYRESULT result)
163 {
164 size_t n;
165 TAGREF tr;
166
167 if (!SdbTagIDToTagRef(hsdb, db, tagExe, &tr))
168 return;
169
170 for (n = 0; n < result->dwExeCount; ++n)
171 {
172 if (result->atrExes[n] == tr)
173 return;
174 }
175
176 if (n >= _countof(result->atrExes))
177 return;
178
179 result->atrExes[n] = tr;
180 result->dwExeCount++;
181
182 SdbpAddExeLayers(hsdb, db, tagExe, result);
183
184 SdbpAddDatabaseGuid(db, result);
185 }
186
187 static ULONG SdbpAddLayerMatches(HSDB hsdb, PWSTR pwszLayers, DWORD pdwBytes, PSDBQUERYRESULT result)
188 {
189 PWSTR start = pwszLayers, p;
190 ULONG Added = 0;
191
192 const PWSTR end = pwszLayers + (pdwBytes / sizeof(WCHAR));
193 while (start < end && (*start == L'!' || *start == L'#' || *start == L' ' || *start == L'\t'))
194 start++;
195
196 if (start == end)
197 return 0;
198
199 do
200 {
201 while (*start == L' ' || *start == L'\t')
202 ++start;
203
204 if (*start == UNICODE_NULL)
205 break;
206 p = wcspbrk(start, L" \t");
207
208 if (p)
209 *p = UNICODE_NULL;
210
211 if (SdbpAddNamedLayerMatch(hsdb, start, result))
212 Added++;
213
214 start = p + 1;
215 } while (start < end && p);
216
217 return Added;
218 }
219
220 static BOOL SdbpPropagateEnvLayers(HSDB hsdb, LPWSTR Environment, PSDBQUERYRESULT Result)
221 {
222 static const UNICODE_STRING EnvKey = RTL_CONSTANT_STRING(L"__COMPAT_LAYER");
223 UNICODE_STRING EnvValue;
224 NTSTATUS Status;
225 WCHAR Buffer[MAX_LAYER_LENGTH];
226
227 RtlInitEmptyUnicodeString(&EnvValue, Buffer, sizeof(Buffer));
228
229 Status = RtlQueryEnvironmentVariable_U(Environment, &EnvKey, &EnvValue);
230
231 if (!NT_SUCCESS(Status))
232 return FALSE;
233
234 return SdbpAddLayerMatches(hsdb, Buffer, EnvValue.Length, Result) > 0;
235 }
236
237
238
239 /**
240 * Opens specified shim database file Handle returned by this function may only be used by
241 * functions which take HSDB param thus differing it from SdbOpenDatabase.
242 *
243 * @param [in] flags Specifies type of path or predefined database.
244 * @param [in] path Path to the shim database file.
245 *
246 * @return Success: Handle to the opened shim database, NULL otherwise.
247 */
248 HSDB WINAPI SdbInitDatabase(DWORD flags, LPCWSTR path)
249 {
250 static const WCHAR shim[] = {'\\','s','y','s','m','a','i','n','.','s','d','b',0};
251 static const WCHAR msi[] = {'\\','m','s','i','m','a','i','n','.','s','d','b',0};
252 static const WCHAR drivers[] = {'\\','d','r','v','m','a','i','n','.','s','d','b',0};
253 LPCWSTR name;
254 WCHAR buffer[128];
255 HSDB hsdb;
256
257 hsdb = SdbAlloc(sizeof(SDB));
258 if (!hsdb)
259 return NULL;
260 hsdb->auto_loaded = 0;
261
262 /* Check for predefined databases */
263 if ((flags & HID_DATABASE_TYPE_MASK) && path == NULL)
264 {
265 switch (flags & HID_DATABASE_TYPE_MASK)
266 {
267 case SDB_DATABASE_MAIN_SHIM: name = shim; break;
268 case SDB_DATABASE_MAIN_MSI: name = msi; break;
269 case SDB_DATABASE_MAIN_DRIVERS: name = drivers; break;
270 default:
271 SdbReleaseDatabase(hsdb);
272 return NULL;
273 }
274 SdbGetAppPatchDir(NULL, buffer, 128);
275 memcpy(buffer + lstrlenW(buffer), name, SdbpStrsize(name));
276 flags = HID_DOS_PATHS;
277 }
278
279 hsdb->db = SdbOpenDatabase(path ? path : buffer, (flags & 0xF) - 1);
280
281 /* If database could not be loaded, a handle doesn't make sense either */
282 if (!hsdb->db)
283 {
284 SdbReleaseDatabase(hsdb);
285 return NULL;
286 }
287
288 return hsdb;
289 }
290
291 /**
292 * Closes shim database opened by SdbInitDatabase.
293 *
294 * @param [in] hsdb Handle to the shim database.
295 */
296 void WINAPI SdbReleaseDatabase(HSDB hsdb)
297 {
298 SdbCloseDatabase(hsdb->db);
299 SdbFree(hsdb);
300 }
301
302 /**
303 * Queries database for a specified exe If hsdb is NULL default database shall be loaded and
304 * searched.
305 *
306 * @param [in] hsdb Handle to the shim database.
307 * @param [in] path Path to executable for which we query database.
308 * @param [in] module_name Unused.
309 * @param [in] env The environment block to use
310 * @param [in] flags 0 or SDBGMEF_IGNORE_ENVIRONMENT.
311 * @param [out] result Pointer to structure in which query result shall be stored.
312 *
313 * @return TRUE if it succeeds, FALSE if it fails.
314 */
315 BOOL WINAPI SdbGetMatchingExe(HSDB hsdb, LPCWSTR path, LPCWSTR module_name,
316 LPCWSTR env, DWORD flags, PSDBQUERYRESULT result)
317 {
318 BOOL ret = FALSE;
319 TAGID database, iter, name;
320 PATTRINFO attribs = NULL;
321 /*DWORD attr_count;*/
322 RTL_UNICODE_STRING_BUFFER DosApplicationName = { { 0 } };
323 WCHAR DosPathBuffer[MAX_PATH];
324 ULONG PathType = 0;
325 LPWSTR file_name;
326 WCHAR wszLayers[MAX_LAYER_LENGTH];
327 DWORD dwSize;
328 PDB db;
329
330 /* Load default database if one is not specified */
331 if (!hsdb)
332 {
333 /* To reproduce windows behaviour HID_DOS_PATHS needs
334 * to be specified when loading default database */
335 hsdb = SdbInitDatabase(HID_DOS_PATHS | SDB_DATABASE_MAIN_SHIM, NULL);
336 if (hsdb)
337 hsdb->auto_loaded = TRUE;
338 }
339
340 ZeroMemory(result, sizeof(*result));
341
342 /* No database could be loaded */
343 if (!hsdb || !path)
344 return FALSE;
345
346 /* We do not support multiple db's yet! */
347 db = hsdb->db;
348
349 RtlInitUnicodeString(&DosApplicationName.String, path);
350 RtlInitBuffer(&DosApplicationName.ByteBuffer, (PUCHAR)DosPathBuffer, sizeof(DosPathBuffer));
351 if (!NT_SUCCESS(RtlEnsureBufferSize(RTL_SKIP_BUFFER_COPY, &DosApplicationName.ByteBuffer, DosApplicationName.String.MaximumLength)))
352 {
353 SHIM_ERR("Failed to convert allocate buffer.");
354 goto Cleanup;
355 }
356 /* Update the internal buffer to contain the string */
357 memcpy(DosApplicationName.ByteBuffer.Buffer, path, DosApplicationName.String.MaximumLength);
358 /* Make sure the string uses our internal buffer (we want to modify the buffer,
359 and RtlNtPathNameToDosPathName does not always modify the String to point to the Buffer)! */
360 DosApplicationName.String.Buffer = (PWSTR)DosApplicationName.ByteBuffer.Buffer;
361
362 if (!NT_SUCCESS(RtlNtPathNameToDosPathName(0, &DosApplicationName, &PathType, NULL)))
363 {
364 SHIM_ERR("Failed to convert %S to DOS Path.", path);
365 goto Cleanup;
366 }
367
368
369 /* Extract file name */
370 file_name = strrchrW(DosApplicationName.String.Buffer, '\\');
371 if (!file_name)
372 {
373 SHIM_ERR("Failed to find Exe name in %wZ.", &DosApplicationName.String);
374 goto Cleanup;
375 }
376
377 /* We will use the buffer for exe name and directory. */
378 *(file_name++) = UNICODE_NULL;
379
380 /* Get information about executable required to match it with database entry */
381 /*if (!SdbGetFileAttributes(path, &attribs, &attr_count))
382 return FALSE;*/
383
384 /* DATABASE is list TAG which contains all executables */
385 database = SdbFindFirstTag(db, TAGID_ROOT, TAG_DATABASE);
386 if (database == TAGID_NULL)
387 {
388 goto Cleanup;
389 }
390
391 /* EXE is list TAG which contains data required to match executable */
392 iter = SdbFindFirstTag(db, database, TAG_EXE);
393
394 /* Search for entry in database, we should look into indexing tags! */
395 while (iter != TAGID_NULL)
396 {
397 LPWSTR foundName;
398 /* Check if exe name matches */
399 name = SdbFindFirstTag(db, iter, TAG_NAME);
400 /* If this is a malformed DB, (no TAG_NAME), we should not crash. */
401 foundName = SdbGetStringTagPtr(db, name);
402 if (foundName && !lstrcmpiW(foundName, file_name))
403 {
404 /* We have a null terminator before the application name, so DosApplicationName only contains the path. */
405 if (SdbpMatchExe(db, iter, DosApplicationName.String.Buffer))
406 {
407 ret = TRUE;
408 SdbpAddExeMatch(hsdb, db, iter, result);
409 }
410 }
411
412 /* Continue iterating */
413 iter = SdbFindNextTag(db, database, iter);
414 }
415
416 /* Restore the full path. */
417 *(--file_name) = L'\\';
418
419 dwSize = sizeof(wszLayers);
420 if (SdbGetPermLayerKeys(DosApplicationName.String.Buffer, wszLayers, &dwSize, GPLK_MACHINE | GPLK_USER))
421 {
422 SdbpAddLayerMatches(hsdb, wszLayers, dwSize, result);
423 ret = TRUE;
424 }
425
426 if (!(flags & SDBGMEF_IGNORE_ENVIRONMENT))
427 {
428 if (SdbpPropagateEnvLayers(hsdb, (LPWSTR)env, result))
429 {
430 ret = TRUE;
431 result->dwFlags |= SHIMREG_HAS_ENVIRONMENT;
432 }
433 }
434
435 Cleanup:
436 RtlFreeBuffer(&DosApplicationName.ByteBuffer);
437 if (attribs)
438 SdbFreeFileAttributes(attribs);
439 if (hsdb->auto_loaded)
440 SdbReleaseDatabase(hsdb);
441 return ret;
442 }
443
444 /**
445 * Retrieves AppPatch directory.
446 *
447 * @param [in] db Handle to the shim database.
448 * @param [out] path Pointer to memory in which path shall be written.
449 * @param [in] size Size of the buffer in characters.
450 */
451 BOOL WINAPI SdbGetAppPatchDir(HSDB db, LPWSTR path, DWORD size)
452 {
453 static WCHAR* default_dir = NULL;
454 static CONST WCHAR szAppPatch[] = {'\\','A','p','p','P','a','t','c','h',0};
455
456 /* In case function fails, path holds empty string */
457 if (size > 0)
458 *path = 0;
459
460 if (!default_dir)
461 {
462 WCHAR* tmp = NULL;
463 UINT len = GetSystemWindowsDirectoryW(NULL, 0) + lstrlenW(szAppPatch);
464 tmp = SdbAlloc((len + 1)* sizeof(WCHAR));
465 if (tmp)
466 {
467 UINT r = GetSystemWindowsDirectoryW(tmp, len+1);
468 if (r && r < len)
469 {
470 if (SUCCEEDED(StringCchCatW(tmp, len+1, szAppPatch)))
471 {
472 if (InterlockedCompareExchangePointer((void**)&default_dir, tmp, NULL) == NULL)
473 tmp = NULL;
474 }
475 }
476 if (tmp)
477 SdbFree(tmp);
478 }
479 if (!default_dir)
480 {
481 SHIM_ERR("Unable to obtain default AppPatch directory\n");
482 return FALSE;
483 }
484 }
485
486 if (!db)
487 {
488 return SUCCEEDED(StringCchCopyW(path, size, default_dir));
489 }
490 else
491 {
492 SHIM_ERR("Unimplemented for db != NULL\n");
493 return FALSE;
494 }
495 }
496
497
498 /**
499 * Translates the given trWhich to a specific database / tagid
500 *
501 * @param [in] hsdb Handle to the database.
502 * @param [in] trWhich Tagref to find
503 * @param [out,opt] ppdb The Shim database that trWhich belongs to.
504 * @param [out,opt] ptiWhich The tagid that trWhich corresponds to.
505 *
506 * @return TRUE if it succeeds, FALSE if it fails.
507 */
508 BOOL WINAPI SdbTagRefToTagID(HSDB hsdb, TAGREF trWhich, PDB* ppdb, TAGID* ptiWhich)
509 {
510 if (trWhich & 0xf0000000)
511 {
512 SHIM_ERR("Multiple shim databases not yet implemented!\n");
513 if (ppdb)
514 *ppdb = NULL;
515 if (ptiWhich)
516 *ptiWhich = TAG_NULL;
517 return FALSE;
518 }
519
520 /* There seems to be no range checking on trWhich.. */
521 if (ppdb)
522 *ppdb = hsdb->db;
523 if (ptiWhich)
524 *ptiWhich = trWhich & 0x0fffffff;
525
526 return TRUE;
527 }
528
529 /**
530 * Translates the given trWhich to a specific database / tagid
531 *
532 * @param [in] hsdb Handle to the database.
533 * @param [in] pdb The Shim database that tiWhich belongs to.
534 * @param [in] tiWhich Path to executable for which we query database.
535 * @param [out,opt] ptrWhich The tagid that tiWhich corresponds to.
536 *
537 * @return TRUE if it succeeds, FALSE if it fails.
538 */
539 BOOL WINAPI SdbTagIDToTagRef(HSDB hsdb, PDB pdb, TAGID tiWhich, TAGREF* ptrWhich)
540 {
541 if (pdb != hsdb->db)
542 {
543 SHIM_ERR("Multiple shim databases not yet implemented!\n");
544 if (ptrWhich)
545 *ptrWhich = TAGREF_NULL;
546 return FALSE;
547 }
548
549 if (ptrWhich)
550 *ptrWhich = tiWhich & 0x0fffffff;
551
552 return TRUE;
553 }
554
555
556