From 845a59919e528d083b1ab025844c41155ac93e43 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Wed, 9 Jun 2021 14:44:39 +0200 Subject: [PATCH] s3:lib: add a new samba_path_matching* infrastructure This aims to replace the current is_in_path() code in the long run. For now it implements samba_path_matching_mswild_create() in order to replace is_in_path() in the long run. But there will be other "backends" using regexec() too. Signed-off-by: Stefan Metzmacher Reviewed-by: Ralph Boehme --- source3/lib/util_matching.c | 282 ++++++++++++++++++++++++++++++++ source3/lib/util_matching.h | 36 ++++ source3/torture/test_matching.c | 52 +++++- source3/wscript_build | 1 + 4 files changed, 367 insertions(+), 4 deletions(-) create mode 100644 source3/lib/util_matching.c create mode 100644 source3/lib/util_matching.h diff --git a/source3/lib/util_matching.c b/source3/lib/util_matching.c new file mode 100644 index 00000000000..a11ff494c74 --- /dev/null +++ b/source3/lib/util_matching.c @@ -0,0 +1,282 @@ +/* + Unix SMB/CIFS implementation. + Samba utility functions + Copyright (C) Stefan Metzmacher 2021 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "includes.h" +#include "lib/util_matching.h" +#include "lib/util/string_wrappers.h" + +struct samba_path_matching_entry { + const char *name; + bool is_wild; +}; + +struct samba_path_matching_result { + ssize_t replace_start; + ssize_t replace_end; + bool match; +}; + +struct samba_path_matching { + bool case_sensitive; + NTSTATUS (*matching_fn)(const struct samba_path_matching *pm, + const struct samba_path_matching_entry *e, + const char *namecomponent, + struct samba_path_matching_result *result); + size_t num_entries; + struct samba_path_matching_entry *entries; +}; + +static NTSTATUS samba_path_matching_split(TALLOC_CTX *mem_ctx, + const char *namelist_in, + struct samba_path_matching **ppm) +{ + TALLOC_CTX *frame = talloc_stackframe(); + char *name_end = NULL; + char *namelist = NULL; + char *namelist_end = NULL; + char *nameptr = NULL; + struct samba_path_matching *pm = NULL; + size_t num_entries = 0; + struct samba_path_matching_entry *entries = NULL; + + *ppm = NULL; + + pm = talloc_zero(mem_ctx, struct samba_path_matching); + if (pm == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + talloc_reparent(mem_ctx, frame, pm); + + namelist = talloc_strdup(frame, namelist_in); + if (namelist == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + nameptr = namelist; + + namelist_end = &namelist[strlen(namelist)]; + + /* + * We need to make two passes over the string. The + * first to count the number of elements, the second + * to split it. + * + * The 1st time entries is NULL. + * the 2nd time entries is allocated. + */ +again: + while (nameptr <= namelist_end) { + /* anything left? */ + if (*nameptr == '\0') { + break; + } + + if (*nameptr == '/') { + /* cope with multiple (useless) /s) */ + nameptr++; + continue; + } + + /* find the next '/' or consume remaining */ + name_end = strchr_m(nameptr, '/'); + if (entries != NULL) { + if (name_end != NULL) { + *name_end = '\0'; + } + entries[num_entries].name = talloc_strdup(entries, + nameptr); + if (entries[num_entries].name == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + } + num_entries++; + if (name_end != NULL) { + /* next segment please */ + nameptr = name_end + 1; + continue; + } + + /* no entries remaining */ + break; + } + + if (num_entries == 0) { + /* + * No entries in the first round => we're done + */ + goto done; + } + + if (entries != NULL) { + /* + * We finished the 2nd round => we're done + */ + goto done; + } + + /* + * Now allocate the array and loop again + * in order to split the names. + */ + entries = talloc_zero_array(pm, + struct samba_path_matching_entry, + num_entries); + if (entries == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + num_entries = 0; + nameptr = namelist; + goto again; + +done: + pm->num_entries = num_entries; + pm->entries = entries; + *ppm = talloc_move(mem_ctx, &pm); + TALLOC_FREE(frame); + return NT_STATUS_OK; +}; + +static NTSTATUS samba_path_create_mswild_fn(const struct samba_path_matching *pm, + const struct samba_path_matching_entry *e, + const char *namecomponent, + struct samba_path_matching_result *result) +{ + bool match = false; + + if (e->is_wild) { + match = mask_match(namecomponent, e->name, pm->case_sensitive); + } else if (pm->case_sensitive) { + match = (strcmp(namecomponent, e->name) == 0); + } else { + match = (strcasecmp_m(namecomponent, e->name) == 0); + } + + *result = (struct samba_path_matching_result) { + .match = match, + .replace_start = -1, + .replace_end = -1, + }; + + return NT_STATUS_OK; +} + +NTSTATUS samba_path_matching_mswild_create(TALLOC_CTX *mem_ctx, + bool case_sensitive, + const char *namelist_in, + struct samba_path_matching **ppm) +{ + NTSTATUS status; + TALLOC_CTX *frame = talloc_stackframe(); + struct samba_path_matching *pm = NULL; + size_t i; + + *ppm = NULL; + + status = samba_path_matching_split(mem_ctx, namelist_in, &pm); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + talloc_reparent(mem_ctx, frame, pm); + + for (i = 0; i < pm->num_entries; i++) { + struct samba_path_matching_entry *e = &pm->entries[i]; + + e->is_wild = ms_has_wild(e->name); + } + + pm->case_sensitive = case_sensitive; + pm->matching_fn = samba_path_create_mswild_fn; + *ppm = talloc_move(mem_ctx, &pm); + TALLOC_FREE(frame); + return NT_STATUS_OK; +}; + +NTSTATUS samba_path_matching_check_last_component(struct samba_path_matching *pm, + const char *name, + ssize_t *p_match_idx, + ssize_t *p_replace_start, + ssize_t *p_replace_end) +{ + struct samba_path_matching_result result = { + .match = false, + .replace_start = -1, + .replace_end = -1, + }; + ssize_t match_idx = -1; + NTSTATUS status = NT_STATUS_OK; + const char *last_component = NULL; + size_t i; + + if (pm->num_entries == 0) { + goto finish; + } + + /* Get the last component of the unix name. */ + last_component = strrchr_m(name, '/'); + if (last_component == NULL) { + last_component = name; + } else { + last_component++; /* Go past '/' */ + } + + for (i = 0; i < pm->num_entries; i++) { + struct samba_path_matching_entry *e = &pm->entries[i]; + + status = pm->matching_fn(pm, e, last_component, &result); + if (!NT_STATUS_IS_OK(status)) { + result = (struct samba_path_matching_result) { + .match = false, + .replace_start = -1, + .replace_end = -1, + }; + goto finish; + } + + if (result.match) { + match_idx = i; + goto finish; + } + } + +finish: + *p_match_idx = match_idx; + if (p_replace_start != NULL) { + size_t last_ofs = 0; + + if (result.replace_start >= 0) { + last_ofs = PTR_DIFF(last_component, name); + } + + *p_replace_start = last_ofs + result.replace_start; + } + if (p_replace_end != NULL) { + size_t last_ofs = 0; + + if (result.replace_end >= 0) { + last_ofs = PTR_DIFF(last_component, name); + } + + *p_replace_end = last_ofs + result.replace_end; + } + return status; +} diff --git a/source3/lib/util_matching.h b/source3/lib/util_matching.h new file mode 100644 index 00000000000..71b8600c51b --- /dev/null +++ b/source3/lib/util_matching.h @@ -0,0 +1,36 @@ +/* + Unix SMB/CIFS implementation. + Samba utility functions + Copyright (C) Stefan Metzmacher 2021 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef _SAMBA_LIB_UTIL_MATCHING_H_ +#define _SAMBA_LIB_UTIL_MATCHING_H_ + +struct samba_path_matching; + +NTSTATUS samba_path_matching_mswild_create(TALLOC_CTX *mem_ctx, + bool case_sensitive, + const char *namelist_in, + struct samba_path_matching **ppm); + +NTSTATUS samba_path_matching_check_last_component(struct samba_path_matching *pm, + const char *name, + ssize_t *p_match_idx, + ssize_t *p_replace_start, + ssize_t *p_replace_end); + +#endif /* _SAMBA_LIB_UTIL_MATCHING_H_ */ diff --git a/source3/torture/test_matching.c b/source3/torture/test_matching.c index 4764c64f296..f94fa9a3dd0 100644 --- a/source3/torture/test_matching.c +++ b/source3/torture/test_matching.c @@ -18,13 +18,16 @@ */ #include "includes.h" +#include "lib/util_matching.h" #include "proto.h" bool run_str_match_mswild(int dummy) { const char *namelist = "/abc*.txt/xyz*.dat/a0123456789Z/"; name_compare_entry *name_entries = NULL; - const struct test_name { + struct samba_path_matching *pmcs = NULL; + struct samba_path_matching *pmci = NULL; + const struct str_match_mswild_name { const char *name; ssize_t case_sensitive_idx; ssize_t case_insensitive_idx; @@ -57,6 +60,7 @@ bool run_str_match_mswild(int dummy) .case_sensitive_idx = -1, .case_insensitive_idx = 2, }}; + NTSTATUS status; size_t i; bool ret = true; @@ -65,10 +69,26 @@ bool run_str_match_mswild(int dummy) set_namearray(&name_entries, namelist); SMB_ASSERT(name_entries != NULL); + status = samba_path_matching_mswild_create(talloc_tos(), + true, /* case_sensitive */ + namelist, + &pmcs); + SMB_ASSERT(NT_STATUS_IS_OK(status)); + status = samba_path_matching_mswild_create(talloc_tos(), + false, /* case_sensitive */ + namelist, + &pmci); + SMB_ASSERT(NT_STATUS_IS_OK(status)); + + for (i = 0; i < ARRAY_SIZE(names); i++) { - const struct test_name *n = &names[i]; + const struct str_match_mswild_name *n = &names[i]; bool case_sensitive_match; bool case_insensitive_match; + ssize_t cs_match_idx = -1; + ssize_t ci_match_idx = -1; + ssize_t replace_start = -1; + ssize_t replace_end = -1; bool ok = true; case_sensitive_match = is_in_path(n->name, @@ -79,6 +99,17 @@ bool run_str_match_mswild(int dummy) } else { ok &= !case_sensitive_match; } + status = samba_path_matching_check_last_component(pmcs, + n->name, + &cs_match_idx, + &replace_start, + &replace_end); + SMB_ASSERT(NT_STATUS_IS_OK(status)); + SMB_ASSERT(replace_start == -1); + SMB_ASSERT(replace_end == -1); + if (n->case_sensitive_idx != cs_match_idx) { + ok = false; + } case_insensitive_match = is_in_path(n->name, name_entries, false); @@ -87,16 +118,29 @@ bool run_str_match_mswild(int dummy) } else { ok &= !case_insensitive_match; } + status = samba_path_matching_check_last_component(pmci, + n->name, + &ci_match_idx, + &replace_start, + &replace_end); + SMB_ASSERT(NT_STATUS_IS_OK(status)); + SMB_ASSERT(replace_start == -1); + SMB_ASSERT(replace_end == -1); + if (n->case_insensitive_idx != ci_match_idx) { + ok = false; + } d_fprintf(stderr, "name[%s] " - "case_sensitive[TIDX=%zd;MATCH=%u] " - "case_insensitive[TIDX=%zd;MATCH=%u] " + "case_sensitive[TIDX=%zd;MATCH=%u;MIDX=%zd] " + "case_insensitive[TIDX=%zd;MATCH=%u;MIDX=%zd] " "%s\n", n->name, n->case_sensitive_idx, case_sensitive_match, + cs_match_idx, n->case_insensitive_idx, case_insensitive_match, + ci_match_idx, ok ? "OK" : "FAIL"); ret &= ok; diff --git a/source3/wscript_build b/source3/wscript_build index 6130d1e3e99..d231584a850 100644 --- a/source3/wscript_build +++ b/source3/wscript_build @@ -287,6 +287,7 @@ bld.SAMBA3_SUBSYSTEM('samba3util', lib/util_file.c lib/util.c lib/util_path.c + lib/util_matching.c lib/util_procid.c lib/util_sock.c lib/util_tsock.c