Coverage Report

Created: 2025-07-01 06:46

/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", &reg->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 = &REG_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", &reg->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
}