Coverage Report

Created: 2026-06-07 07:07

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/samba/source3/lib/util_matching.c
Line
Count
Source
1
/*
2
   Unix SMB/CIFS implementation.
3
   Samba utility functions
4
   Copyright (C) Stefan Metzmacher 2021
5
6
   This program is free software; you can redistribute it and/or modify
7
   it under the terms of the GNU General Public License as published by
8
   the Free Software Foundation; either version 3 of the License, or
9
   (at your option) any later version.
10
11
   This program 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
14
   GNU General Public License for more details.
15
16
   You should have received a copy of the GNU General Public License
17
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
*/
19
20
#include "includes.h"
21
#include "lib/util_matching.h"
22
#include "lib/util/string_wrappers.h"
23
24
struct samba_path_matching_entry {
25
  const char *name;
26
  bool is_wild;
27
  regex_t re;
28
};
29
30
struct samba_path_matching_result {
31
  ssize_t replace_start;
32
  ssize_t replace_end;
33
  bool match;
34
};
35
36
struct samba_path_matching {
37
  bool case_sensitive;
38
  NTSTATUS (*matching_fn)(const struct samba_path_matching *pm,
39
        const struct samba_path_matching_entry *e,
40
        const char *namecomponent,
41
        struct samba_path_matching_result *result);
42
  size_t num_entries;
43
  struct samba_path_matching_entry *entries;
44
};
45
46
static NTSTATUS samba_path_matching_split(TALLOC_CTX *mem_ctx,
47
            const char *namelist_in,
48
            struct samba_path_matching **ppm)
49
0
{
50
0
  TALLOC_CTX *frame = talloc_stackframe();
51
0
  char *name_end = NULL;
52
0
  char *namelist = NULL;
53
0
  char *namelist_end = NULL;
54
0
  char *nameptr = NULL;
55
0
  struct samba_path_matching *pm = NULL;
56
0
  size_t num_entries = 0;
57
0
  struct samba_path_matching_entry *entries = NULL;
58
59
0
  *ppm = NULL;
60
61
0
  pm = talloc_zero(mem_ctx, struct samba_path_matching);
62
0
  if (pm == NULL) {
63
0
    TALLOC_FREE(frame);
64
0
    return NT_STATUS_NO_MEMORY;
65
0
  }
66
0
  talloc_reparent(mem_ctx, frame, pm);
67
68
0
  namelist = talloc_strdup(frame, namelist_in);
69
0
  if (namelist == NULL) {
70
0
    TALLOC_FREE(frame);
71
0
    return NT_STATUS_NO_MEMORY;
72
0
  }
73
0
  nameptr = namelist;
74
75
0
  namelist_end = &namelist[strlen(namelist)];
76
77
  /*
78
   * We need to make two passes over the string. The
79
   * first to count the number of elements, the second
80
   * to split it.
81
   *
82
   * The 1st time entries is NULL.
83
   * the 2nd time entries is allocated.
84
   */
85
0
again:
86
0
  while (nameptr <= namelist_end) {
87
    /* anything left? */
88
0
    if (*nameptr == '\0') {
89
0
      break;
90
0
    }
91
92
0
    if (*nameptr == '/') {
93
      /* cope with multiple (useless) /s) */
94
0
      nameptr++;
95
0
      continue;
96
0
    }
97
98
    /* find the next '/' or consume remaining */
99
0
    name_end = strchr_m(nameptr, '/');
100
0
    if (entries != NULL) {
101
0
      if (name_end != NULL) {
102
0
        *name_end = '\0';
103
0
      }
104
0
      entries[num_entries].name = talloc_strdup(entries,
105
0
                  nameptr);
106
0
      if (entries[num_entries].name == NULL) {
107
0
        TALLOC_FREE(frame);
108
0
        return NT_STATUS_NO_MEMORY;
109
0
      }
110
0
    }
111
0
    num_entries++;
112
0
    if (name_end != NULL) {
113
      /* next segment please */
114
0
      nameptr = name_end + 1;
115
0
      continue;
116
0
    }
117
118
    /* no entries remaining */
119
0
    break;
120
0
  }
121
122
0
  if (num_entries == 0) {
123
    /*
124
     * No entries in the first round => we're done
125
     */
126
0
    goto done;
127
0
  }
128
129
0
  if (entries != NULL) {
130
    /*
131
     * We finished the 2nd round => we're done
132
     */
133
0
    goto done;
134
0
  }
135
136
  /*
137
   * Now allocate the array and loop again
138
   * in order to split the names.
139
   */
140
0
  entries = talloc_zero_array(pm,
141
0
            struct samba_path_matching_entry,
142
0
            num_entries);
143
0
  if (entries == NULL) {
144
0
    TALLOC_FREE(frame);
145
0
    return NT_STATUS_NO_MEMORY;
146
0
  }
147
0
  num_entries = 0;
148
0
  nameptr = namelist;
149
0
  goto again;
150
151
0
done:
152
0
  pm->num_entries = num_entries;
153
0
  pm->entries = entries;
154
0
  *ppm = talloc_move(mem_ctx, &pm);
155
0
  TALLOC_FREE(frame);
156
0
  return NT_STATUS_OK;
157
0
};
158
159
static NTSTATUS samba_path_create_mswild_fn(const struct samba_path_matching *pm,
160
              const struct samba_path_matching_entry *e,
161
              const char *namecomponent,
162
              struct samba_path_matching_result *result)
163
0
{
164
0
  bool match = false;
165
166
0
  if (e->is_wild) {
167
0
    match = mask_match(namecomponent, e->name, pm->case_sensitive);
168
0
  } else if (pm->case_sensitive) {
169
0
    match = (strcmp(namecomponent, e->name) == 0);
170
0
  } else {
171
0
    match = (strcasecmp_m(namecomponent, e->name) == 0);
172
0
  }
173
174
0
  *result = (struct samba_path_matching_result) {
175
0
    .match = match,
176
0
    .replace_start = -1,
177
0
    .replace_end = -1,
178
0
  };
179
180
0
  return NT_STATUS_OK;
181
0
}
182
183
NTSTATUS samba_path_matching_mswild_create(TALLOC_CTX *mem_ctx,
184
             bool case_sensitive,
185
             const char *namelist_in,
186
             struct samba_path_matching **ppm)
187
0
{
188
0
  NTSTATUS status;
189
0
  TALLOC_CTX *frame = talloc_stackframe();
190
0
  struct samba_path_matching *pm = NULL;
191
0
  size_t i;
192
193
0
  *ppm = NULL;
194
195
0
  status = samba_path_matching_split(mem_ctx, namelist_in, &pm);
196
0
  if (!NT_STATUS_IS_OK(status)) {
197
0
    TALLOC_FREE(frame);
198
0
    return status;
199
0
  }
200
0
  talloc_reparent(mem_ctx, frame, pm);
201
202
0
  for (i = 0; i < pm->num_entries; i++) {
203
0
    struct samba_path_matching_entry *e = &pm->entries[i];
204
205
0
    e->is_wild = ms_has_wild(e->name);
206
0
  }
207
208
0
  pm->case_sensitive = case_sensitive;
209
0
  pm->matching_fn = samba_path_create_mswild_fn;
210
0
  *ppm = talloc_move(mem_ctx, &pm);
211
0
  TALLOC_FREE(frame);
212
0
  return NT_STATUS_OK;
213
0
};
214
215
static int samba_path_matching_regex_sub1_destructor(struct samba_path_matching *pm)
216
0
{
217
0
  ssize_t i;
218
219
0
  for (i = 0; i < pm->num_entries; i++) {
220
0
    struct samba_path_matching_entry *e = &pm->entries[i];
221
222
0
    regfree(&e->re);
223
0
  }
224
225
0
  pm->num_entries = 0;
226
227
0
  return 0;
228
0
}
229
230
static NTSTATUS samba_path_create_regex_sub1_fn(const struct samba_path_matching *pm,
231
            const struct samba_path_matching_entry *e,
232
            const char *namecomponent,
233
            struct samba_path_matching_result *result)
234
0
{
235
0
  if (e->re.re_nsub == 1) {
236
0
    regmatch_t matches[2] = { };
237
0
    int ret;
238
239
0
    ret = regexec(&e->re, namecomponent, 2, matches, 0);
240
0
    if (ret == 0) {
241
0
      *result = (struct samba_path_matching_result) {
242
0
        .match = true,
243
0
        .replace_start = matches[1].rm_so,
244
0
        .replace_end = matches[1].rm_eo,
245
0
      };
246
247
0
      return NT_STATUS_OK;
248
0
    }
249
0
  }
250
251
0
  *result = (struct samba_path_matching_result) {
252
0
    .match = false,
253
0
    .replace_start = -1,
254
0
    .replace_end = -1,
255
0
  };
256
257
0
  return NT_STATUS_OK;
258
0
}
259
260
NTSTATUS samba_path_matching_regex_sub1_create(TALLOC_CTX *mem_ctx,
261
                 const char *namelist_in,
262
                 struct samba_path_matching **ppm)
263
0
{
264
0
  NTSTATUS status;
265
0
  TALLOC_CTX *frame = talloc_stackframe();
266
0
  struct samba_path_matching *pm = NULL;
267
0
  ssize_t i;
268
269
0
  *ppm = NULL;
270
271
0
  status = samba_path_matching_split(mem_ctx, namelist_in, &pm);
272
0
  if (!NT_STATUS_IS_OK(status)) {
273
0
    TALLOC_FREE(frame);
274
0
    return status;
275
0
  }
276
0
  talloc_reparent(mem_ctx, frame, pm);
277
278
0
  for (i = 0; i < pm->num_entries; i++) {
279
0
    struct samba_path_matching_entry *e = &pm->entries[i];
280
0
    int ret;
281
282
0
    ret = regcomp(&e->re, e->name, 0);
283
0
    if (ret != 0) {
284
0
      fstring buf = { 0,};
285
286
0
      regerror(ret, &e->re, buf, sizeof(buf));
287
288
0
      DBG_ERR("idx[%zu] regcomp: /%s/ - %d - %s\n",
289
0
        i, e->name, ret, buf);
290
291
0
      status = NT_STATUS_INVALID_PARAMETER;
292
0
      i--;
293
0
      goto cleanup;
294
0
    }
295
296
0
    if (e->re.re_nsub != 1) {
297
0
      DBG_ERR("idx[%zu] regcomp: /%s/ - re_nsub[%zu] != 1\n",
298
0
        i, e->name, e->re.re_nsub);
299
0
      status = NT_STATUS_INVALID_PARAMETER;
300
0
      goto cleanup;
301
0
    }
302
0
  }
303
304
0
  talloc_set_destructor(pm, samba_path_matching_regex_sub1_destructor);
305
306
0
  pm->case_sensitive = true;
307
0
  pm->matching_fn = samba_path_create_regex_sub1_fn;
308
0
  *ppm = talloc_move(mem_ctx, &pm);
309
0
  TALLOC_FREE(frame);
310
0
  return NT_STATUS_OK;
311
312
0
cleanup:
313
0
  for (; i >= 0; i--) {
314
0
    struct samba_path_matching_entry *e = &pm->entries[i];
315
316
0
    regfree(&e->re);
317
0
  }
318
319
0
  TALLOC_FREE(frame);
320
0
  return status;
321
0
};
322
323
NTSTATUS samba_path_matching_check_last_component(struct samba_path_matching *pm,
324
              const char *name,
325
              ssize_t *p_match_idx,
326
              ssize_t *p_replace_start,
327
              ssize_t *p_replace_end)
328
0
{
329
0
  struct samba_path_matching_result result = {
330
0
    .match = false,
331
0
    .replace_start = -1,
332
0
    .replace_end = -1,
333
0
  };
334
0
  ssize_t match_idx = -1;
335
0
  NTSTATUS status = NT_STATUS_OK;
336
0
  const char *last_component = NULL;
337
0
  size_t i;
338
339
0
  if (pm->num_entries == 0) {
340
0
    goto finish;
341
0
  }
342
343
  /* Get the last component of the unix name. */
344
0
  last_component = strrchr_m(name, '/');
345
0
  if (last_component == NULL) {
346
0
    last_component = name;
347
0
  } else {
348
0
    last_component++; /* Go past '/' */
349
0
  }
350
351
0
  for (i = 0; i < pm->num_entries; i++) {
352
0
    struct samba_path_matching_entry *e = &pm->entries[i];
353
354
0
    status = pm->matching_fn(pm, e, last_component, &result);
355
0
    if (!NT_STATUS_IS_OK(status)) {
356
0
      result = (struct samba_path_matching_result) {
357
0
        .match = false,
358
0
        .replace_start = -1,
359
0
        .replace_end = -1,
360
0
      };
361
0
      goto finish;
362
0
    }
363
364
0
    if (result.match) {
365
0
      match_idx = i;
366
0
      goto finish;
367
0
    }
368
0
  }
369
370
0
finish:
371
0
  *p_match_idx = match_idx;
372
0
  if (p_replace_start != NULL) {
373
0
    size_t last_ofs = 0;
374
375
0
    if (result.replace_start >= 0) {
376
0
      last_ofs = PTR_DIFF(last_component, name);
377
0
    }
378
379
0
    *p_replace_start = last_ofs + result.replace_start;
380
0
  }
381
0
  if (p_replace_end != NULL) {
382
0
    size_t last_ofs = 0;
383
384
0
    if (result.replace_end >= 0) {
385
0
      last_ofs = PTR_DIFF(last_component, name);
386
0
    }
387
388
0
    *p_replace_end = last_ofs + result.replace_end;
389
0
  }
390
0
  return status;
391
0
}