smbd: Protect ea-reading on symlinks
[Samba.git] / source3 / lib / util_matching.c
blob4a321f2ca441acc2c737bd176018995152833d69
1 /*
2 Unix SMB/CIFS implementation.
3 Samba utility functions
4 Copyright (C) Stefan Metzmacher 2021
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.
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.
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/>.
20 #include "includes.h"
21 #include "lib/util_matching.h"
22 #include "lib/util/string_wrappers.h"
24 struct samba_path_matching_entry {
25 const char *name;
26 bool is_wild;
27 regex_t re;
30 struct samba_path_matching_result {
31 ssize_t replace_start;
32 ssize_t replace_end;
33 bool match;
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;
46 static NTSTATUS samba_path_matching_split(TALLOC_CTX *mem_ctx,
47 const char *namelist_in,
48 struct samba_path_matching **ppm)
50 TALLOC_CTX *frame = talloc_stackframe();
51 char *name_end = NULL;
52 char *namelist = NULL;
53 char *namelist_end = NULL;
54 char *nameptr = NULL;
55 struct samba_path_matching *pm = NULL;
56 size_t num_entries = 0;
57 struct samba_path_matching_entry *entries = NULL;
59 *ppm = NULL;
61 pm = talloc_zero(mem_ctx, struct samba_path_matching);
62 if (pm == NULL) {
63 TALLOC_FREE(frame);
64 return NT_STATUS_NO_MEMORY;
66 talloc_reparent(mem_ctx, frame, pm);
68 namelist = talloc_strdup(frame, namelist_in);
69 if (namelist == NULL) {
70 TALLOC_FREE(frame);
71 return NT_STATUS_NO_MEMORY;
73 nameptr = namelist;
75 namelist_end = &namelist[strlen(namelist)];
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.
82 * The 1st time entries is NULL.
83 * the 2nd time entries is allocated.
85 again:
86 while (nameptr <= namelist_end) {
87 /* anything left? */
88 if (*nameptr == '\0') {
89 break;
92 if (*nameptr == '/') {
93 /* cope with multiple (useless) /s) */
94 nameptr++;
95 continue;
98 /* find the next '/' or consume remaining */
99 name_end = strchr_m(nameptr, '/');
100 if (entries != NULL) {
101 if (name_end != NULL) {
102 *name_end = '\0';
104 entries[num_entries].name = talloc_strdup(entries,
105 nameptr);
106 if (entries[num_entries].name == NULL) {
107 TALLOC_FREE(frame);
108 return NT_STATUS_NO_MEMORY;
111 num_entries++;
112 if (name_end != NULL) {
113 /* next segment please */
114 nameptr = name_end + 1;
115 continue;
118 /* no entries remaining */
119 break;
122 if (num_entries == 0) {
124 * No entries in the first round => we're done
126 goto done;
129 if (entries != NULL) {
131 * We finished the 2nd round => we're done
133 goto done;
137 * Now allocate the array and loop again
138 * in order to split the names.
140 entries = talloc_zero_array(pm,
141 struct samba_path_matching_entry,
142 num_entries);
143 if (entries == NULL) {
144 TALLOC_FREE(frame);
145 return NT_STATUS_NO_MEMORY;
147 num_entries = 0;
148 nameptr = namelist;
149 goto again;
151 done:
152 pm->num_entries = num_entries;
153 pm->entries = entries;
154 *ppm = talloc_move(mem_ctx, &pm);
155 TALLOC_FREE(frame);
156 return NT_STATUS_OK;
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)
164 bool match = false;
166 if (e->is_wild) {
167 match = mask_match(namecomponent, e->name, pm->case_sensitive);
168 } else if (pm->case_sensitive) {
169 match = (strcmp(namecomponent, e->name) == 0);
170 } else {
171 match = (strcasecmp_m(namecomponent, e->name) == 0);
174 *result = (struct samba_path_matching_result) {
175 .match = match,
176 .replace_start = -1,
177 .replace_end = -1,
180 return NT_STATUS_OK;
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)
188 NTSTATUS status;
189 TALLOC_CTX *frame = talloc_stackframe();
190 struct samba_path_matching *pm = NULL;
191 size_t i;
193 *ppm = NULL;
195 status = samba_path_matching_split(mem_ctx, namelist_in, &pm);
196 if (!NT_STATUS_IS_OK(status)) {
197 TALLOC_FREE(frame);
198 return status;
200 talloc_reparent(mem_ctx, frame, pm);
202 for (i = 0; i < pm->num_entries; i++) {
203 struct samba_path_matching_entry *e = &pm->entries[i];
205 e->is_wild = ms_has_wild(e->name);
208 pm->case_sensitive = case_sensitive;
209 pm->matching_fn = samba_path_create_mswild_fn;
210 *ppm = talloc_move(mem_ctx, &pm);
211 TALLOC_FREE(frame);
212 return NT_STATUS_OK;
215 static int samba_path_matching_regex_sub1_destructor(struct samba_path_matching *pm)
217 ssize_t i;
219 for (i = 0; i < pm->num_entries; i++) {
220 struct samba_path_matching_entry *e = &pm->entries[i];
222 regfree(&e->re);
225 pm->num_entries = 0;
227 return 0;
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)
235 if (e->re.re_nsub == 1) {
236 regmatch_t matches[2] = { };
237 int ret;
239 ret = regexec(&e->re, namecomponent, 2, matches, 0);
240 if (ret == 0) {
241 *result = (struct samba_path_matching_result) {
242 .match = true,
243 .replace_start = matches[1].rm_so,
244 .replace_end = matches[1].rm_eo,
247 return NT_STATUS_OK;
251 *result = (struct samba_path_matching_result) {
252 .match = false,
253 .replace_start = -1,
254 .replace_end = -1,
257 return NT_STATUS_OK;
260 NTSTATUS samba_path_matching_regex_sub1_create(TALLOC_CTX *mem_ctx,
261 const char *namelist_in,
262 struct samba_path_matching **ppm)
264 NTSTATUS status;
265 TALLOC_CTX *frame = talloc_stackframe();
266 struct samba_path_matching *pm = NULL;
267 ssize_t i;
269 *ppm = NULL;
271 status = samba_path_matching_split(mem_ctx, namelist_in, &pm);
272 if (!NT_STATUS_IS_OK(status)) {
273 TALLOC_FREE(frame);
274 return status;
276 talloc_reparent(mem_ctx, frame, pm);
278 for (i = 0; i < pm->num_entries; i++) {
279 struct samba_path_matching_entry *e = &pm->entries[i];
280 int ret;
282 ret = regcomp(&e->re, e->name, 0);
283 if (ret != 0) {
284 fstring buf = { 0,};
286 regerror(ret, &e->re, buf, sizeof(buf));
288 DBG_ERR("idx[%zu] regcomp: /%s/ - %d - %s\n",
289 i, e->name, ret, buf);
291 status = NT_STATUS_INVALID_PARAMETER;
292 i--;
293 goto cleanup;
296 if (e->re.re_nsub != 1) {
297 DBG_ERR("idx[%zu] regcomp: /%s/ - re_nsub[%zu] != 1\n",
298 i, e->name, e->re.re_nsub);
299 status = NT_STATUS_INVALID_PARAMETER;
300 goto cleanup;
304 talloc_set_destructor(pm, samba_path_matching_regex_sub1_destructor);
306 pm->case_sensitive = true;
307 pm->matching_fn = samba_path_create_regex_sub1_fn;
308 *ppm = talloc_move(mem_ctx, &pm);
309 TALLOC_FREE(frame);
310 return NT_STATUS_OK;
312 cleanup:
313 for (; i >= 0; i--) {
314 struct samba_path_matching_entry *e = &pm->entries[i];
316 regfree(&e->re);
319 TALLOC_FREE(frame);
320 return status;
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)
329 struct samba_path_matching_result result = {
330 .match = false,
331 .replace_start = -1,
332 .replace_end = -1,
334 ssize_t match_idx = -1;
335 NTSTATUS status = NT_STATUS_OK;
336 const char *last_component = NULL;
337 size_t i;
339 if (pm->num_entries == 0) {
340 goto finish;
343 /* Get the last component of the unix name. */
344 last_component = strrchr_m(name, '/');
345 if (last_component == NULL) {
346 last_component = name;
347 } else {
348 last_component++; /* Go past '/' */
351 for (i = 0; i < pm->num_entries; i++) {
352 struct samba_path_matching_entry *e = &pm->entries[i];
354 status = pm->matching_fn(pm, e, last_component, &result);
355 if (!NT_STATUS_IS_OK(status)) {
356 result = (struct samba_path_matching_result) {
357 .match = false,
358 .replace_start = -1,
359 .replace_end = -1,
361 goto finish;
364 if (result.match) {
365 match_idx = i;
366 goto finish;
370 finish:
371 *p_match_idx = match_idx;
372 if (p_replace_start != NULL) {
373 size_t last_ofs = 0;
375 if (result.replace_start >= 0) {
376 last_ofs = PTR_DIFF(last_component, name);
379 *p_replace_start = last_ofs + result.replace_start;
381 if (p_replace_end != NULL) {
382 size_t last_ofs = 0;
384 if (result.replace_end >= 0) {
385 last_ofs = PTR_DIFF(last_component, name);
388 *p_replace_end = last_ofs + result.replace_end;
390 return status;