Coverage Report

Created: 2026-10-15 06:30

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/FreeRDP/winpr/libwinpr/utils/sam.c
Line
Count
Source
1
/**
2
 * WinPR: Windows Portable Runtime
3
 * Security Accounts Manager (SAM)
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 <stdio.h>
24
#include <stdlib.h>
25
#include <string.h>
26
27
#include <winpr/wtypes.h>
28
#include <winpr/crt.h>
29
#include <winpr/sam.h>
30
#include <winpr/cast.h>
31
#include <winpr/print.h>
32
#include <winpr/file.h>
33
34
#include "../log.h"
35
#include "../utils.h"
36
37
#ifdef WINPR_HAVE_UNISTD_H
38
#include <unistd.h>
39
#endif
40
41
#define TAG WINPR_TAG("utils")
42
43
struct winpr_sam
44
{
45
  FILE* fp;
46
  char* line;
47
  char* buffer;
48
  size_t bufferlen;
49
  char* context;
50
  BOOL readOnly;
51
};
52
53
WINPR_ATTR_MALLOC(SamFreeEntry, 2)
54
static WINPR_SAM_ENTRY* SamEntryFromDataA(LPCSTR User, DWORD UserLength, LPCSTR Domain,
55
                                          DWORD DomainLength)
56
0
{
57
0
  WINPR_SAM_ENTRY* entry = calloc(1, sizeof(WINPR_SAM_ENTRY));
58
0
  if (!entry)
59
0
    return nullptr;
60
0
  if (User && (UserLength > 0))
61
0
    entry->User = _strdup(User);
62
0
  entry->UserLength = UserLength;
63
0
  if (Domain && (DomainLength > 0))
64
0
    entry->Domain = _strdup(Domain);
65
0
  entry->DomainLength = DomainLength;
66
0
  return entry;
67
0
}
68
69
WINPR_ATTR_NODISCARD
70
static BOOL SamAreEntriesEqual(const WINPR_SAM_ENTRY* a, const WINPR_SAM_ENTRY* b)
71
0
{
72
0
  if (!a || !b)
73
0
    return FALSE;
74
0
  if (a->UserLength != b->UserLength)
75
0
    return FALSE;
76
0
  if (a->DomainLength != b->DomainLength)
77
0
    return FALSE;
78
0
  if (a->UserLength > 0)
79
0
  {
80
0
    if (!a->User || !b->User)
81
0
      return FALSE;
82
0
    if (strncmp(a->User, b->User, a->UserLength) != 0)
83
0
      return FALSE;
84
0
  }
85
0
  if (a->DomainLength > 0)
86
0
  {
87
0
    if (!a->Domain || !b->Domain)
88
0
      return FALSE;
89
0
    if (strncmp(a->Domain, b->Domain, a->DomainLength) != 0)
90
0
      return FALSE;
91
0
  }
92
0
  return TRUE;
93
0
}
94
95
WINPR_SAM* SamOpen(const char* filename, BOOL readOnly)
96
0
{
97
0
  FILE* fp = nullptr;
98
0
  WINPR_SAM* sam = nullptr;
99
0
  char* allocatedFileName = nullptr;
100
101
0
  if (!filename)
102
0
  {
103
0
    allocatedFileName = winpr_GetConfigFilePath(TRUE, "SAM");
104
0
    filename = allocatedFileName;
105
0
  }
106
107
0
  WLog_DBG(TAG, "Trying to open SAM file '%s'", filename);
108
0
  if (readOnly)
109
0
    fp = winpr_fopen(filename, "r");
110
0
  else
111
0
  {
112
0
    fp = winpr_fopen(filename, "r+");
113
114
0
    if (!fp)
115
0
      fp = winpr_fopen(filename, "w+");
116
0
  }
117
118
0
  if (!fp)
119
0
    goto fail;
120
121
0
  sam = (WINPR_SAM*)calloc(1, sizeof(WINPR_SAM));
122
0
  if (!sam)
123
0
    goto fail;
124
125
0
  sam->readOnly = readOnly;
126
0
  sam->fp = fp;
127
128
0
fail:
129
0
  if (!fp || !sam)
130
0
    WLog_DBG(TAG, "Could not open SAM file '%s'", filename);
131
132
0
  if (fp && !sam)
133
0
    (void)fclose(fp);
134
0
  winpr_zfree(allocatedFileName);
135
0
  return sam;
136
0
}
137
138
static void SamLookupFinish(WINPR_SAM* sam)
139
0
{
140
0
  winpr_znfree(sam->buffer, sam->bufferlen);
141
0
  sam->buffer = nullptr;
142
0
  sam->bufferlen = 0;
143
0
  sam->line = nullptr;
144
0
}
145
146
WINPR_ATTR_NODISCARD
147
static BOOL SamLookupStart(WINPR_SAM* sam)
148
0
{
149
0
  size_t readSize = 0;
150
0
  INT64 fileSize = 0;
151
152
0
  if (!sam || !sam->fp)
153
0
    return FALSE;
154
155
0
  if (_fseeki64(sam->fp, 0, SEEK_END) != 0)
156
0
    return FALSE;
157
0
  fileSize = _ftelli64(sam->fp);
158
0
  if (_fseeki64(sam->fp, 0, SEEK_SET) != 0)
159
0
    return FALSE;
160
161
0
  if (fileSize < 1)
162
0
    return FALSE;
163
164
0
  sam->context = nullptr;
165
0
  const size_t allocsize = WINPR_ASSERTING_INT_CAST(size_t, fileSize) + 2ull;
166
0
  sam->buffer = (char*)calloc(allocsize, 1);
167
168
0
  if (!sam->buffer)
169
0
    return FALSE;
170
0
  sam->bufferlen = allocsize;
171
172
0
  readSize = fread(sam->buffer, sam->bufferlen - 2ull, 1, sam->fp);
173
174
0
  if (!readSize)
175
0
  {
176
0
    if (!ferror(sam->fp))
177
0
      readSize = (size_t)fileSize;
178
0
  }
179
180
0
  if (readSize < 1)
181
0
  {
182
0
    SamLookupFinish(sam);
183
0
    return FALSE;
184
0
  }
185
186
0
  sam->buffer[fileSize] = '\n';
187
0
  sam->buffer[fileSize + 1] = '\0';
188
0
  sam->line = strtok_s(sam->buffer, "\n", &sam->context);
189
0
  return TRUE;
190
0
}
191
192
static void SamResetEntryUser(WINPR_SAM_ENTRY* entry)
193
0
{
194
0
  if (!entry)
195
0
    return;
196
197
0
  if (entry->UserLength > 0)
198
0
    winpr_znfree(entry->User, entry->UserLength);
199
0
  entry->User = nullptr;
200
0
  entry->UserLength = 0;
201
202
0
  if (entry->DomainLength > 0)
203
0
    winpr_znfree(entry->Domain, entry->DomainLength);
204
0
  entry->Domain = nullptr;
205
0
  entry->DomainLength = 0;
206
0
}
207
208
WINPR_ATTR_NODISCARD
209
static BOOL SamReadEntry(WINPR_SAM* sam, WINPR_SAM_ENTRY* entry)
210
0
{
211
0
  char* p[5] = WINPR_C_ARRAY_INIT;
212
0
  size_t count = 0;
213
214
0
  if (!sam || !entry || !sam->line)
215
0
    return FALSE;
216
217
0
  char* cur = sam->line;
218
219
0
  while ((cur = strchr(cur, ':')) != nullptr)
220
0
  {
221
0
    count++;
222
0
    cur++;
223
0
  }
224
225
0
  if (count < 4)
226
0
    goto fail;
227
228
0
  p[0] = sam->line;
229
0
  p[1] = strchr(p[0], ':') + 1;
230
0
  p[2] = strchr(p[1], ':') + 1;
231
0
  p[3] = strchr(p[2], ':') + 1;
232
0
  p[4] = strchr(p[3], ':') + 1;
233
0
  const size_t LmHashLength = WINPR_ASSERTING_INT_CAST(size_t, (p[3] - p[2] - 1));
234
0
  const size_t NtHashLength = WINPR_ASSERTING_INT_CAST(size_t, (p[4] - p[3] - 1));
235
236
0
  if ((LmHashLength != 0) && (LmHashLength != 32))
237
0
    goto fail;
238
239
0
  if ((NtHashLength != 0) && (NtHashLength != 32))
240
0
    goto fail;
241
242
0
  entry->UserLength = (UINT32)(p[1] - p[0] - 1);
243
0
  entry->User = (LPSTR)malloc(entry->UserLength + 1);
244
245
0
  if (!entry->User)
246
0
    goto fail;
247
248
0
  entry->User[entry->UserLength] = '\0';
249
0
  entry->DomainLength = (UINT32)(p[2] - p[1] - 1);
250
0
  memcpy(entry->User, p[0], entry->UserLength);
251
252
0
  if (entry->DomainLength > 0)
253
0
  {
254
0
    entry->Domain = (LPSTR)malloc(entry->DomainLength + 1);
255
256
0
    if (!entry->Domain)
257
0
      goto fail;
258
259
0
    memcpy(entry->Domain, p[1], entry->DomainLength);
260
0
    entry->Domain[entry->DomainLength] = '\0';
261
0
  }
262
0
  else
263
0
    entry->Domain = nullptr;
264
265
0
  if (LmHashLength == 32)
266
0
  {
267
0
    const size_t rc =
268
0
        winpr_HexStringToBinBuffer(p[2], LmHashLength, entry->LmHash, sizeof(entry->LmHash));
269
0
    if (rc != 16)
270
0
      goto fail;
271
0
  }
272
273
0
  if (NtHashLength == 32)
274
0
  {
275
0
    const size_t rc = winpr_HexStringToBinBuffer(p[3], NtHashLength, (BYTE*)entry->NtHash,
276
0
                                                 sizeof(entry->NtHash));
277
0
    if (rc != 16)
278
0
      goto fail;
279
0
  }
280
281
0
  return TRUE;
282
283
0
fail:
284
0
  SamResetEntryUser(entry);
285
0
  return FALSE;
286
0
}
287
288
void SamFreeEntry(WINPR_ATTR_UNUSED WINPR_SAM* sam, WINPR_SAM_ENTRY* entry)
289
0
{
290
0
  if (!entry)
291
0
    return;
292
0
  SamResetEntry(entry);
293
0
  winpr_znfree(entry, sizeof(WINPR_SAM_ENTRY));
294
0
}
295
296
void SamResetEntry(WINPR_SAM_ENTRY* entry)
297
0
{
298
0
  if (!entry)
299
0
    return;
300
301
0
  SamResetEntryUser(entry);
302
303
0
  ZeroMemory(entry->LmHash, sizeof(entry->LmHash));
304
0
  ZeroMemory(entry->NtHash, sizeof(entry->NtHash));
305
0
}
306
307
WINPR_SAM_ENTRY* SamLookupUserA(WINPR_SAM* sam, LPCSTR User, UINT32 UserLength, LPCSTR Domain,
308
                                UINT32 DomainLength)
309
0
{
310
0
  size_t length = 0;
311
0
  BOOL found = FALSE;
312
0
  WINPR_SAM_ENTRY* search = SamEntryFromDataA(User, UserLength, Domain, DomainLength);
313
0
  WINPR_SAM_ENTRY* entry = (WINPR_SAM_ENTRY*)calloc(1, sizeof(WINPR_SAM_ENTRY));
314
315
0
  if (!entry || !search)
316
0
    goto fail;
317
318
0
  if (!SamLookupStart(sam))
319
0
    goto fail;
320
321
0
  while (sam->line != nullptr)
322
0
  {
323
0
    length = strlen(sam->line);
324
325
0
    if (length > 1)
326
0
    {
327
0
      if (sam->line[0] != '#')
328
0
      {
329
0
        if (!SamReadEntry(sam, entry))
330
0
        {
331
0
          goto out_fail;
332
0
        }
333
334
0
        if (SamAreEntriesEqual(entry, search))
335
0
        {
336
0
          found = 1;
337
0
          break;
338
0
        }
339
0
      }
340
0
    }
341
342
0
    SamResetEntry(entry);
343
0
    sam->line = strtok_s(nullptr, "\n", &sam->context);
344
0
  }
345
346
0
out_fail:
347
0
  SamLookupFinish(sam);
348
0
fail:
349
0
  SamFreeEntry(sam, search);
350
351
0
  if (!found)
352
0
  {
353
0
    SamFreeEntry(sam, entry);
354
0
    return nullptr;
355
0
  }
356
357
0
  return entry;
358
0
}
359
360
WINPR_SAM_ENTRY* SamLookupUserW(WINPR_SAM* sam, LPCWSTR User, UINT32 UserLength, LPCWSTR Domain,
361
                                UINT32 DomainLength)
362
0
{
363
0
  WINPR_SAM_ENTRY* entry = nullptr;
364
0
  char* utfUser = nullptr;
365
0
  char* utfDomain = nullptr;
366
0
  size_t userCharLen = 0;
367
0
  size_t domainCharLen = 0;
368
369
0
  utfUser = ConvertWCharNToUtf8Alloc(User, UserLength / sizeof(WCHAR), &userCharLen);
370
0
  if (!utfUser)
371
0
    goto fail;
372
0
  if (DomainLength > 0)
373
0
  {
374
0
    utfDomain = ConvertWCharNToUtf8Alloc(Domain, DomainLength / sizeof(WCHAR), &domainCharLen);
375
0
    if (!utfDomain)
376
0
      goto fail;
377
0
  }
378
0
  entry = SamLookupUserA(sam, utfUser, (UINT32)userCharLen, utfDomain, (UINT32)domainCharLen);
379
0
  if (entry)
380
0
  {
381
0
    SamResetEntryUser(entry);
382
383
0
    if (User)
384
0
      entry->User = (char*)winpr_wcsndup(User, UserLength / sizeof(WCHAR));
385
0
    entry->UserLength = UserLength;
386
0
    if (Domain)
387
0
      entry->Domain = (char*)winpr_wcsndup(Domain, DomainLength / sizeof(WCHAR));
388
0
    entry->DomainLength = DomainLength;
389
0
  }
390
0
fail:
391
0
  winpr_znfree(utfUser, userCharLen);
392
0
  winpr_znfree(utfDomain, domainCharLen);
393
0
  return entry;
394
0
}
395
396
void SamClose(WINPR_SAM* sam)
397
0
{
398
0
  if (!sam)
399
0
    return;
400
401
0
  if (sam->fp)
402
0
    (void)fclose(sam->fp);
403
0
  winpr_znfree(sam, sizeof(WINPR_SAM));
404
0
}