/src/FreeRDP/winpr/libwinpr/registry/registry_reg.c
Line | Count | Source (jump to first uncovered line) |
1 | | /** |
2 | | * WinPR: Windows Portable Runtime |
3 | | * Windows Registry (.reg file format) |
4 | | * |
5 | | * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> |
6 | | * |
7 | | * Licensed under the Apache License, Version 2.0 (the "License"); |
8 | | * you may not use this file except in compliance with the License. |
9 | | * You may obtain a copy of the License at |
10 | | * |
11 | | * http://www.apache.org/licenses/LICENSE-2.0 |
12 | | * |
13 | | * Unless required by applicable law or agreed to in writing, software |
14 | | * distributed under the License is distributed on an "AS IS" BASIS, |
15 | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
16 | | * See the License for the specific language governing permissions and |
17 | | * limitations under the License. |
18 | | */ |
19 | | |
20 | | #include <winpr/config.h> |
21 | | #include <winpr/path.h> |
22 | | |
23 | | #include <errno.h> |
24 | | #include <stdio.h> |
25 | | #include <stdlib.h> |
26 | | #include <string.h> |
27 | | |
28 | | #include <winpr/wtypes.h> |
29 | | #include <winpr/string.h> |
30 | | #include <winpr/assert.h> |
31 | | #include <winpr/crt.h> |
32 | | #include <winpr/file.h> |
33 | | |
34 | | #include "registry_reg.h" |
35 | | |
36 | | #include "../log.h" |
37 | | #define TAG WINPR_TAG("registry") |
38 | | |
39 | | struct reg_data_type |
40 | | { |
41 | | char* tag; |
42 | | size_t length; |
43 | | DWORD type; |
44 | | }; |
45 | | |
46 | | static struct reg_data_type REG_DATA_TYPE_TABLE[] = { { "\"", 1, REG_SZ }, |
47 | | { "dword:", 6, REG_DWORD }, |
48 | | { "str:\"", 5, REG_SZ }, |
49 | | { "str(2):\"", 8, REG_EXPAND_SZ }, |
50 | | { "str(7):\"", 8, REG_MULTI_SZ }, |
51 | | { "hex:", 4, REG_BINARY }, |
52 | | { "hex(2):\"", 8, REG_EXPAND_SZ }, |
53 | | { "hex(7):\"", 8, REG_MULTI_SZ }, |
54 | | { "hex(b):\"", 8, REG_QWORD } }; |
55 | | |
56 | | static char* reg_data_type_string(DWORD type) |
57 | 0 | { |
58 | 0 | switch (type) |
59 | 0 | { |
60 | 0 | case REG_NONE: |
61 | 0 | return "REG_NONE"; |
62 | 0 | case REG_SZ: |
63 | 0 | return "REG_SZ"; |
64 | 0 | case REG_EXPAND_SZ: |
65 | 0 | return "REG_EXPAND_SZ"; |
66 | 0 | case REG_BINARY: |
67 | 0 | return "REG_BINARY"; |
68 | 0 | case REG_DWORD: |
69 | 0 | return "REG_DWORD"; |
70 | 0 | case REG_DWORD_BIG_ENDIAN: |
71 | 0 | return "REG_DWORD_BIG_ENDIAN"; |
72 | 0 | case REG_LINK: |
73 | 0 | return "REG_LINK"; |
74 | 0 | case REG_MULTI_SZ: |
75 | 0 | return "REG_MULTI_SZ"; |
76 | 0 | case REG_RESOURCE_LIST: |
77 | 0 | return "REG_RESOURCE_LIST"; |
78 | 0 | case REG_FULL_RESOURCE_DESCRIPTOR: |
79 | 0 | return "REG_FULL_RESOURCE_DESCRIPTOR"; |
80 | 0 | case REG_RESOURCE_REQUIREMENTS_LIST: |
81 | 0 | return "REG_RESOURCE_REQUIREMENTS_LIST"; |
82 | 0 | case REG_QWORD: |
83 | 0 | return "REG_QWORD"; |
84 | 0 | default: |
85 | 0 | return "REG_UNKNOWN"; |
86 | 0 | } |
87 | 0 | } |
88 | | |
89 | | static BOOL reg_load_start(Reg* reg) |
90 | 0 | { |
91 | 0 | char* buffer = NULL; |
92 | 0 | INT64 file_size = 0; |
93 | |
|
94 | 0 | WINPR_ASSERT(reg); |
95 | 0 | WINPR_ASSERT(reg->fp); |
96 | | |
97 | 0 | if (_fseeki64(reg->fp, 0, SEEK_END) != 0) |
98 | 0 | return FALSE; |
99 | 0 | file_size = _ftelli64(reg->fp); |
100 | 0 | if (_fseeki64(reg->fp, 0, SEEK_SET) != 0) |
101 | 0 | return FALSE; |
102 | 0 | reg->line = NULL; |
103 | 0 | reg->next_line = NULL; |
104 | |
|
105 | 0 | if (file_size < 1) |
106 | 0 | return FALSE; |
107 | | |
108 | 0 | buffer = (char*)realloc(reg->buffer, (size_t)file_size + 2); |
109 | |
|
110 | 0 | if (!buffer) |
111 | 0 | return FALSE; |
112 | 0 | reg->buffer = buffer; |
113 | |
|
114 | 0 | if (fread(reg->buffer, (size_t)file_size, 1, reg->fp) != 1) |
115 | 0 | return FALSE; |
116 | | |
117 | 0 | reg->buffer[file_size] = '\n'; |
118 | 0 | reg->buffer[file_size + 1] = '\0'; |
119 | 0 | reg->next_line = strtok_s(reg->buffer, "\n", ®->saveptr); |
120 | 0 | return TRUE; |
121 | 0 | } |
122 | | |
123 | | static void reg_load_finish(Reg* reg) |
124 | 0 | { |
125 | 0 | if (!reg) |
126 | 0 | return; |
127 | | |
128 | 0 | if (reg->buffer) |
129 | 0 | { |
130 | 0 | free(reg->buffer); |
131 | 0 | reg->buffer = NULL; |
132 | 0 | } |
133 | 0 | } |
134 | | |
135 | | static RegVal* reg_load_value(const Reg* reg, RegKey* key) |
136 | 0 | { |
137 | 0 | const char* p[5] = { 0 }; |
138 | 0 | size_t length = 0; |
139 | 0 | char* name = NULL; |
140 | 0 | const char* type = NULL; |
141 | 0 | const char* data = NULL; |
142 | 0 | RegVal* value = NULL; |
143 | |
|
144 | 0 | WINPR_ASSERT(reg); |
145 | 0 | WINPR_ASSERT(key); |
146 | 0 | WINPR_ASSERT(reg->line); |
147 | | |
148 | 0 | p[0] = reg->line + 1; |
149 | 0 | p[1] = strstr(p[0], "\"="); |
150 | 0 | if (!p[1]) |
151 | 0 | return NULL; |
152 | | |
153 | 0 | p[2] = p[1] + 2; |
154 | 0 | type = p[2]; |
155 | |
|
156 | 0 | if (p[2][0] == '"') |
157 | 0 | p[3] = p[2]; |
158 | 0 | else |
159 | 0 | p[3] = strchr(p[2], ':'); |
160 | |
|
161 | 0 | if (!p[3]) |
162 | 0 | return NULL; |
163 | | |
164 | 0 | data = p[3] + 1; |
165 | 0 | length = (size_t)(p[1] - p[0]); |
166 | 0 | if (length < 1) |
167 | 0 | goto fail; |
168 | | |
169 | 0 | name = (char*)calloc(length + 1, sizeof(char)); |
170 | |
|
171 | 0 | if (!name) |
172 | 0 | goto fail; |
173 | | |
174 | 0 | memcpy(name, p[0], length); |
175 | 0 | value = (RegVal*)calloc(1, sizeof(RegVal)); |
176 | |
|
177 | 0 | if (!value) |
178 | 0 | goto fail; |
179 | | |
180 | 0 | value->name = name; |
181 | 0 | value->type = REG_NONE; |
182 | |
|
183 | 0 | for (size_t index = 0; index < ARRAYSIZE(REG_DATA_TYPE_TABLE); index++) |
184 | 0 | { |
185 | 0 | const struct reg_data_type* current = ®_DATA_TYPE_TABLE[index]; |
186 | 0 | WINPR_ASSERT(current->tag); |
187 | 0 | WINPR_ASSERT(current->length > 0); |
188 | 0 | WINPR_ASSERT(current->type != REG_NONE); |
189 | | |
190 | 0 | if (strncmp(type, current->tag, current->length) == 0) |
191 | 0 | { |
192 | 0 | value->type = current->type; |
193 | 0 | break; |
194 | 0 | } |
195 | 0 | } |
196 | | |
197 | 0 | switch (value->type) |
198 | 0 | { |
199 | 0 | case REG_DWORD: |
200 | 0 | { |
201 | 0 | unsigned long val = 0; |
202 | 0 | errno = 0; |
203 | 0 | val = strtoul(data, NULL, 0); |
204 | |
|
205 | 0 | if ((errno != 0) || (val > UINT32_MAX)) |
206 | 0 | { |
207 | 0 | WLog_WARN(TAG, "%s::%s value %s invalid", key->name, value->name, data); |
208 | 0 | goto fail; |
209 | 0 | } |
210 | 0 | value->data.dword = (DWORD)val; |
211 | 0 | } |
212 | 0 | break; |
213 | 0 | case REG_QWORD: |
214 | 0 | { |
215 | 0 | unsigned long long val = 0; |
216 | 0 | errno = 0; |
217 | 0 | val = strtoull(data, NULL, 0); |
218 | |
|
219 | 0 | if ((errno != 0) || (val > UINT64_MAX)) |
220 | 0 | { |
221 | 0 | WLog_WARN(TAG, "%s::%s value %s invalid", key->name, value->name, data); |
222 | 0 | goto fail; |
223 | 0 | } |
224 | | |
225 | 0 | value->data.qword = (UINT64)val; |
226 | 0 | } |
227 | 0 | break; |
228 | 0 | case REG_SZ: |
229 | 0 | { |
230 | 0 | char* start = strchr(data, '"'); |
231 | 0 | if (!start) |
232 | 0 | goto fail; |
233 | | |
234 | | /* Check for terminating quote, check it is the last symbol */ |
235 | 0 | const size_t len = strlen(start); |
236 | 0 | char* end = strchr(start + 1, '"'); |
237 | 0 | if (!end) |
238 | 0 | goto fail; |
239 | 0 | const intptr_t cmp = end - start + 1; |
240 | 0 | if ((cmp < 0) || (len != WINPR_ASSERTING_INT_CAST(size_t, cmp))) |
241 | 0 | goto fail; |
242 | 0 | if (start[0] == '"') |
243 | 0 | start++; |
244 | 0 | if (end[0] == '"') |
245 | 0 | end[0] = '\0'; |
246 | 0 | value->data.string = _strdup(start); |
247 | |
|
248 | 0 | if (!value->data.string) |
249 | 0 | goto fail; |
250 | 0 | } |
251 | 0 | break; |
252 | 0 | default: |
253 | 0 | WLog_ERR(TAG, "[%s] %s unimplemented format: %s", key->name, value->name, |
254 | 0 | reg_data_type_string(value->type)); |
255 | 0 | break; |
256 | 0 | } |
257 | | |
258 | 0 | if (!key->values) |
259 | 0 | { |
260 | 0 | key->values = value; |
261 | 0 | } |
262 | 0 | else |
263 | 0 | { |
264 | 0 | RegVal* pValue = key->values; |
265 | |
|
266 | 0 | while (pValue->next != NULL) |
267 | 0 | { |
268 | 0 | pValue = pValue->next; |
269 | 0 | } |
270 | |
|
271 | 0 | pValue->next = value; |
272 | 0 | value->prev = pValue; |
273 | 0 | } |
274 | |
|
275 | 0 | return value; |
276 | | |
277 | 0 | fail: |
278 | 0 | free(value); |
279 | 0 | free(name); |
280 | 0 | return NULL; |
281 | 0 | } |
282 | | |
283 | | static BOOL reg_load_has_next_line(Reg* reg) |
284 | 0 | { |
285 | 0 | if (!reg) |
286 | 0 | return 0; |
287 | | |
288 | 0 | return (reg->next_line != NULL) ? 1 : 0; |
289 | 0 | } |
290 | | |
291 | | static char* reg_load_get_next_line(Reg* reg) |
292 | 0 | { |
293 | 0 | if (!reg) |
294 | 0 | return NULL; |
295 | | |
296 | 0 | reg->line = reg->next_line; |
297 | 0 | reg->next_line = strtok_s(NULL, "\n", ®->saveptr); |
298 | 0 | reg->line_length = strlen(reg->line); |
299 | 0 | return reg->line; |
300 | 0 | } |
301 | | |
302 | | static char* reg_load_peek_next_line(Reg* reg) |
303 | 0 | { |
304 | 0 | WINPR_ASSERT(reg); |
305 | 0 | return reg->next_line; |
306 | 0 | } |
307 | | |
308 | | static void reg_insert_key(WINPR_ATTR_UNUSED Reg* reg, RegKey* key, RegKey* subkey) |
309 | 0 | { |
310 | 0 | char* name = NULL; |
311 | 0 | char* path = NULL; |
312 | 0 | char* save = NULL; |
313 | |
|
314 | 0 | WINPR_ASSERT(reg); |
315 | 0 | WINPR_ASSERT(key); |
316 | 0 | WINPR_ASSERT(subkey); |
317 | 0 | WINPR_ASSERT(subkey->name); |
318 | | |
319 | 0 | path = _strdup(subkey->name); |
320 | |
|
321 | 0 | if (!path) |
322 | 0 | return; |
323 | | |
324 | 0 | name = strtok_s(path, "\\", &save); |
325 | |
|
326 | 0 | while (name != NULL) |
327 | 0 | { |
328 | 0 | if (strcmp(key->name, name) == 0) |
329 | 0 | { |
330 | 0 | if (save) |
331 | 0 | subkey->subname = _strdup(save); |
332 | | |
333 | | /* TODO: free allocated memory in error case */ |
334 | 0 | if (!subkey->subname) |
335 | 0 | { |
336 | 0 | free(path); |
337 | 0 | return; |
338 | 0 | } |
339 | 0 | } |
340 | | |
341 | 0 | name = strtok_s(NULL, "\\", &save); |
342 | 0 | } |
343 | | |
344 | 0 | free(path); |
345 | 0 | } |
346 | | |
347 | | static RegKey* reg_load_key(Reg* reg, RegKey* key) |
348 | 0 | { |
349 | 0 | char* p[2]; |
350 | 0 | size_t length = 0; |
351 | 0 | RegKey* subkey = NULL; |
352 | |
|
353 | 0 | WINPR_ASSERT(reg); |
354 | 0 | WINPR_ASSERT(key); |
355 | | |
356 | 0 | WINPR_ASSERT(reg->line); |
357 | 0 | p[0] = reg->line + 1; |
358 | 0 | p[1] = strrchr(p[0], ']'); |
359 | 0 | if (!p[1]) |
360 | 0 | return NULL; |
361 | | |
362 | 0 | subkey = (RegKey*)calloc(1, sizeof(RegKey)); |
363 | |
|
364 | 0 | if (!subkey) |
365 | 0 | return NULL; |
366 | | |
367 | 0 | length = (size_t)(p[1] - p[0]); |
368 | 0 | subkey->name = (char*)malloc(length + 1); |
369 | |
|
370 | 0 | if (!subkey->name) |
371 | 0 | { |
372 | 0 | free(subkey); |
373 | 0 | return NULL; |
374 | 0 | } |
375 | | |
376 | 0 | memcpy(subkey->name, p[0], length); |
377 | 0 | subkey->name[length] = '\0'; |
378 | |
|
379 | 0 | while (reg_load_has_next_line(reg)) |
380 | 0 | { |
381 | 0 | char* line = reg_load_peek_next_line(reg); |
382 | |
|
383 | 0 | if (line[0] == '[') |
384 | 0 | break; |
385 | | |
386 | 0 | reg_load_get_next_line(reg); |
387 | |
|
388 | 0 | if (reg->line[0] == '"') |
389 | 0 | { |
390 | 0 | reg_load_value(reg, subkey); |
391 | 0 | } |
392 | 0 | } |
393 | |
|
394 | 0 | reg_insert_key(reg, key, subkey); |
395 | |
|
396 | 0 | if (!key->subkeys) |
397 | 0 | { |
398 | 0 | key->subkeys = subkey; |
399 | 0 | } |
400 | 0 | else |
401 | 0 | { |
402 | 0 | RegKey* pKey = key->subkeys; |
403 | |
|
404 | 0 | while (pKey->next != NULL) |
405 | 0 | { |
406 | 0 | pKey = pKey->next; |
407 | 0 | } |
408 | |
|
409 | 0 | pKey->next = subkey; |
410 | 0 | subkey->prev = pKey; |
411 | 0 | } |
412 | |
|
413 | 0 | return subkey; |
414 | 0 | } |
415 | | |
416 | | static void reg_load(Reg* reg) |
417 | 0 | { |
418 | 0 | reg_load_start(reg); |
419 | |
|
420 | 0 | while (reg_load_has_next_line(reg)) |
421 | 0 | { |
422 | 0 | reg_load_get_next_line(reg); |
423 | |
|
424 | 0 | if (reg->line[0] == '[') |
425 | 0 | { |
426 | 0 | reg_load_key(reg, reg->root_key); |
427 | 0 | } |
428 | 0 | } |
429 | |
|
430 | 0 | reg_load_finish(reg); |
431 | 0 | } |
432 | | |
433 | | static void reg_unload_value(WINPR_ATTR_UNUSED Reg* reg, RegVal* value) |
434 | 0 | { |
435 | 0 | WINPR_ASSERT(reg); |
436 | 0 | WINPR_ASSERT(value); |
437 | | |
438 | 0 | switch (value->type) |
439 | 0 | { |
440 | 0 | case REG_SZ: |
441 | 0 | free(value->data.string); |
442 | 0 | break; |
443 | 0 | default: |
444 | 0 | break; |
445 | 0 | } |
446 | | |
447 | 0 | free(value); |
448 | 0 | } |
449 | | |
450 | | static void reg_unload_key(Reg* reg, RegKey* key) |
451 | 0 | { |
452 | 0 | RegVal* pValue = NULL; |
453 | |
|
454 | 0 | WINPR_ASSERT(reg); |
455 | 0 | WINPR_ASSERT(key); |
456 | | |
457 | 0 | pValue = key->values; |
458 | |
|
459 | 0 | while (pValue != NULL) |
460 | 0 | { |
461 | 0 | RegVal* pValueNext = pValue->next; |
462 | 0 | reg_unload_value(reg, pValue); |
463 | 0 | pValue = pValueNext; |
464 | 0 | } |
465 | |
|
466 | 0 | free(key->name); |
467 | 0 | free(key); |
468 | 0 | } |
469 | | |
470 | | static void reg_unload(Reg* reg) |
471 | 0 | { |
472 | 0 | WINPR_ASSERT(reg); |
473 | 0 | if (reg->root_key) |
474 | 0 | { |
475 | 0 | RegKey* pKey = reg->root_key->subkeys; |
476 | |
|
477 | 0 | while (pKey != NULL) |
478 | 0 | { |
479 | 0 | RegKey* pKeyNext = pKey->next; |
480 | 0 | reg_unload_key(reg, pKey); |
481 | 0 | pKey = pKeyNext; |
482 | 0 | } |
483 | |
|
484 | 0 | free(reg->root_key); |
485 | 0 | } |
486 | 0 | } |
487 | | |
488 | | Reg* reg_open(BOOL read_only) |
489 | 0 | { |
490 | 0 | Reg* reg = (Reg*)calloc(1, sizeof(Reg)); |
491 | |
|
492 | 0 | if (!reg) |
493 | 0 | return NULL; |
494 | | |
495 | 0 | reg->read_only = read_only; |
496 | 0 | reg->filename = winpr_GetConfigFilePath(TRUE, "HKLM.reg"); |
497 | 0 | if (!reg->filename) |
498 | 0 | goto fail; |
499 | | |
500 | 0 | if (reg->read_only) |
501 | 0 | reg->fp = winpr_fopen(reg->filename, "r"); |
502 | 0 | else |
503 | 0 | { |
504 | 0 | reg->fp = winpr_fopen(reg->filename, "r+"); |
505 | |
|
506 | 0 | if (!reg->fp) |
507 | 0 | reg->fp = winpr_fopen(reg->filename, "w+"); |
508 | 0 | } |
509 | |
|
510 | 0 | if (!reg->fp) |
511 | 0 | goto fail; |
512 | | |
513 | 0 | reg->root_key = (RegKey*)calloc(1, sizeof(RegKey)); |
514 | |
|
515 | 0 | if (!reg->root_key) |
516 | 0 | goto fail; |
517 | | |
518 | 0 | reg->root_key->values = NULL; |
519 | 0 | reg->root_key->subkeys = NULL; |
520 | 0 | reg->root_key->name = "HKEY_LOCAL_MACHINE"; |
521 | 0 | reg_load(reg); |
522 | 0 | return reg; |
523 | | |
524 | 0 | fail: |
525 | 0 | reg_close(reg); |
526 | 0 | return NULL; |
527 | 0 | } |
528 | | |
529 | | void reg_close(Reg* reg) |
530 | 0 | { |
531 | 0 | if (reg) |
532 | 0 | { |
533 | 0 | reg_unload(reg); |
534 | 0 | if (reg->fp) |
535 | 0 | (void)fclose(reg->fp); |
536 | 0 | free(reg->filename); |
537 | 0 | free(reg); |
538 | 0 | } |
539 | 0 | } |
540 | | |
541 | | const char* reg_type_string(DWORD type) |
542 | 0 | { |
543 | 0 | switch (type) |
544 | 0 | { |
545 | 0 | case REG_NONE: |
546 | 0 | return "REG_NONE"; |
547 | 0 | case REG_SZ: |
548 | 0 | return "REG_SZ"; |
549 | 0 | case REG_EXPAND_SZ: |
550 | 0 | return "REG_EXPAND_SZ"; |
551 | 0 | case REG_BINARY: |
552 | 0 | return "REG_BINARY"; |
553 | 0 | case REG_DWORD: |
554 | 0 | return "REG_DWORD"; |
555 | 0 | case REG_DWORD_BIG_ENDIAN: |
556 | 0 | return "REG_DWORD_BIG_ENDIAN"; |
557 | 0 | case REG_LINK: |
558 | 0 | return "REG_LINK"; |
559 | 0 | case REG_MULTI_SZ: |
560 | 0 | return "REG_MULTI_SZ"; |
561 | 0 | case REG_RESOURCE_LIST: |
562 | 0 | return "REG_RESOURCE_LIST"; |
563 | 0 | case REG_FULL_RESOURCE_DESCRIPTOR: |
564 | 0 | return "REG_FULL_RESOURCE_DESCRIPTOR"; |
565 | 0 | case REG_RESOURCE_REQUIREMENTS_LIST: |
566 | 0 | return "REG_RESOURCE_REQUIREMENTS_LIST"; |
567 | 0 | case REG_QWORD: |
568 | 0 | return "REG_QWORD"; |
569 | 0 | default: |
570 | 0 | return "REG_UNKNOWN"; |
571 | 0 | } |
572 | 0 | } |