From 48d6f3b6c68a33a4ff11650f9f0de858cb5fc256 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Sun, 26 May 2024 17:17:50 +0100 Subject: [PATCH] Lookups: sub-path for dsearch --- doc/doc-docbook/spec.xfpt | 16 ++++++++++++++-- doc/doc-txt/NewStuff | 2 ++ src/src/lookups/dsearch.c | 28 +++++++++++++++++++++------- test/scripts/2500-dsearch/2500 | 2 ++ test/stdout/2500 | 2 ++ 5 files changed, 41 insertions(+), 9 deletions(-) diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt index 8f7abac89..6ee615145 100644 --- a/doc/doc-docbook/spec.xfpt +++ b/doc/doc-docbook/spec.xfpt @@ -6846,7 +6846,10 @@ by default, but has an option to omit them (see section &<>&). .cindex "dsearch lookup type" The given file must be an absolute directory path; this is searched for an entry whose name is the key by calling the &[lstat()]& function. -The key may not contain any forward slash characters. +.new +Unless the options (below) permit a path, +.wen +the key may not contain any forward slash characters. If &[lstat()]& succeeds then so does the lookup. .cindex "tainted data" "dsearch result" The result is regarded as untainted. @@ -6855,7 +6858,7 @@ Options for the lookup can be given by appending them after the word "dsearch", separated by a comma. Options, if present, are a comma-separated list having each element starting with a tag name and an equals. -Two options are supported, for the return value and for filtering match +Three options are supported, for the return value and for filtering match candidates. The "ret" option requests an alternate result value of the entire path for the entry. Example: @@ -6863,6 +6866,7 @@ the entire path for the entry. Example: ${lookup {passwd} dsearch,ret=full {/etc}} .endd The default result is just the requested entry. + The "filter" option requests that only directory entries of a given type are matched. The match value is one of "file", "dir" or "subdir" (the latter not matching "." or ".."). Example: @@ -6872,6 +6876,14 @@ ${lookup {passwd} dsearch,filter=file {/etc}} The default matching is for any entry type, including directories and symlinks. +The "key" option relaxes the restriction that only a simple path component can +be searched for, to permit a sequence of path components. Example: +.code +${lookup {foo/bar} dsearch,key=path {/etc}} +.endd +If this option is used, a ".." component in the key is specifically disallowed. +The default operation is that the key may only be a single path component. + An example of how this lookup can be used to support virtual domains is given in section &<>&. diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff index 1781e05b1..b0702eea2 100644 --- a/doc/doc-txt/NewStuff +++ b/doc/doc-txt/NewStuff @@ -22,6 +22,8 @@ Version 4.98 6. A dns:fail event. + 7. The dsearch lookup supports search for a sub-path. + Version 4.97 ------------ diff --git a/src/src/lookups/dsearch.c b/src/src/lookups/dsearch.c index 74439bfc8..22003f4e5 100644 --- a/src/src/lookups/dsearch.c +++ b/src/src/lookups/dsearch.c @@ -70,6 +70,7 @@ return FALSE; #define FILTER_FILE BIT(2) #define FILTER_DIR BIT(3) #define FILTER_SUBDIR BIT(4) +#define ALLOW_PATH BIT(5) /* See local README for interface description. We use lstat() instead of scanning the directory, as it is hopefully faster to let the OS do the scanning @@ -85,13 +86,6 @@ int save_errno; uschar * filename; unsigned flags = 0; -if (Ustrchr(keystring, '/') != 0) - { - *errmsg = string_sprintf("key for dsearch lookup contains a slash: %s", - keystring); - return DEFER; - } - if (opts) { int sep = ','; @@ -110,6 +104,24 @@ if (opts) else if (Ustrcmp(ele, "subdir") == 0) flags |= FILTER_TYPE | FILTER_SUBDIR; /* like dir but not "." or ".." */ } + else if (Ustrcmp(ele, "key=path") == 0) + flags |= ALLOW_PATH; + } + +if (flags & ALLOW_PATH) + { + if (Ustrstr(keystring, "/../") != NULL || Ustrstr(keystring, "/./")) + { + *errmsg = string_sprintf( + "key for dsearch lookup contains bad component: %s", keystring); + return DEFER; + } + } +else if (Ustrchr(keystring, '/') != NULL) + { + *errmsg = string_sprintf("key for dsearch lookup contains a slash: %s", + keystring); + return DEFER; } filename = string_sprintf("%s/%s", dirname, keystring); @@ -189,3 +201,5 @@ static lookup_info *_lookup_list[] = { &_lookup_info }; lookup_module_info dsearch_lookup_module_info = { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 1 }; /* End of lookups/dsearch.c */ +/* vi: aw ai sw=2 +*/ diff --git a/test/scripts/2500-dsearch/2500 b/test/scripts/2500-dsearch/2500 index f3937ec91..1690256da 100644 --- a/test/scripts/2500-dsearch/2500 +++ b/test/scripts/2500-dsearch/2500 @@ -18,6 +18,8 @@ ok,subdir: ${lookup{TESTNUM.dir} dsearch,filter=subdir {DIR/aux-fixed}{$value}{ fail,subdir(..):${lookup{..} dsearch,filter=subdir {DIR/aux-fixed}{$value}{FAIL}} fail,subdir(.) :${lookup{.} dsearch,filter=subdir {DIR/aux-fixed}{$value}{FAIL}} fail,subdir(f) :${lookup{TESTNUM.tst} dsearch,filter=subdir {DIR/aux-fixed}{$value}{FAIL}} +fail.path: ${lookup{TESTNUM.dir/regfile} dsearch {DIR/aux-fixed}{$value}{FAIL}} +ok.path: ${lookup{TESTNUM.dir/regfile} dsearch,key=path {DIR/aux-fixed}{$value}{FAIL}} cachelayer tests fail: ${lookup{test-data} dsearch {DIR/} {$value}{FAIL}} diff --git a/test/stdout/2500 b/test/stdout/2500 index 6daaab658..065004bf2 100644 --- a/test/stdout/2500 +++ b/test/stdout/2500 @@ -15,6 +15,8 @@ > fail,subdir(..):FAIL > fail,subdir(.) :FAIL > fail,subdir(f) :FAIL +> Failed: lookup of "2500.dir/regfile" gave DEFER: key for dsearch lookup contains a slash: 2500.dir/regfile +> ok.path: 2500.dir/regfile > > cachelayer tests > fail: FAIL -- 2.30.2