22003f4e5dd449abf29f2a43f51c687712e60899
[exim.git] / src / src / lookups / dsearch.c
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4
5 /* Copyright (c) The Exim Maintainers 2020 - 2022 */
6 /* Copyright (c) University of Cambridge 1995 - 2015 */
7 /* See the file NOTICE for conditions of use and distribution. */
8 /* SPDX-License-Identifier: GPL-2.0-or-later */
9
10 /* The idea for this code came from Matthew Byng-Maddick, but his original has
11 been heavily reworked a lot for Exim 4 (and it now uses stat() (more precisely:
12 lstat()) rather than a directory scan). */
13
14
15 #include "../exim.h"
16 #include "lf_functions.h"
17
18
19
20 /*************************************************
21 *              Open entry point                  *
22 *************************************************/
23
24 /* See local README for interface description. We open the directory to test
25 whether it exists and whether it is searchable. However, we don't need to keep
26 it open, because the "search" can be done by a call to lstat() rather than
27 actually scanning through the list of files. */
28
29 static void *
30 dsearch_open(const uschar * dirname, uschar ** errmsg)
31 {
32 DIR * dp = exim_opendir(dirname);
33 if (!dp)
34   {
35   *errmsg = string_open_failed("%s for directory search", dirname);
36   return NULL;
37   }
38 closedir(dp);
39 return (void *)(-1);
40 }
41
42
43 /*************************************************
44 *             Check entry point                  *
45 *************************************************/
46
47 /* The handle will always be (void *)(-1), but don't try casting it to an
48 integer as this gives warnings on 64-bit systems. */
49
50 static BOOL
51 dsearch_check(void * handle, const uschar * filename, int modemask,
52   uid_t * owners, gid_t * owngroups, uschar ** errmsg)
53 {
54 handle = handle;
55 if (*filename == '/')
56   return lf_check_file(-1, filename, S_IFDIR, modemask, owners, owngroups,
57     "dsearch", errmsg) == 0;
58 *errmsg = string_sprintf("dirname '%s' for dsearch is not absolute", filename);
59 return FALSE;
60 }
61
62
63 /*************************************************
64 *              Find entry point                  *
65 *************************************************/
66
67 #define RET_FULL        BIT(0)
68 #define FILTER_TYPE     BIT(1)
69 #define FILTER_ALL      BIT(1)
70 #define FILTER_FILE     BIT(2)
71 #define FILTER_DIR      BIT(3)
72 #define FILTER_SUBDIR   BIT(4)
73 #define ALLOW_PATH      BIT(5)
74
75 /* See local README for interface description. We use lstat() instead of
76 scanning the directory, as it is hopefully faster to let the OS do the scanning
77 for us. */
78
79 static int
80 dsearch_find(void * handle, const uschar * dirname, const uschar * keystring,
81   int length, uschar ** result, uschar ** errmsg, uint * do_cache,
82   const uschar * opts)
83 {
84 struct stat statbuf;
85 int save_errno;
86 uschar * filename;
87 unsigned flags = 0;
88
89 if (opts)
90   {
91   int sep = ',';
92   uschar * ele;
93
94   while ((ele = string_nextinlist(&opts, &sep, NULL, 0)))
95     if (Ustrcmp(ele, "ret=full") == 0)
96       flags |= RET_FULL;
97     else if (Ustrncmp(ele, "filter=", 7) == 0)
98       {
99       ele += 7;
100       if (Ustrcmp(ele, "file") == 0)
101         flags |= FILTER_TYPE | FILTER_FILE;
102       else if (Ustrcmp(ele, "dir") == 0)
103         flags |= FILTER_TYPE | FILTER_DIR;
104       else if (Ustrcmp(ele, "subdir") == 0)
105         flags |= FILTER_TYPE | FILTER_SUBDIR;   /* like dir but not "." or ".." */
106       }
107     else if (Ustrcmp(ele, "key=path") == 0)
108       flags |= ALLOW_PATH;
109   }
110
111 if (flags & ALLOW_PATH)
112   {
113   if (Ustrstr(keystring, "/../") != NULL || Ustrstr(keystring, "/./"))
114     {
115     *errmsg = string_sprintf(
116       "key for dsearch lookup contains bad component: %s", keystring);
117     return DEFER;
118     }
119   }
120 else if (Ustrchr(keystring, '/') != NULL)
121   {
122   *errmsg = string_sprintf("key for dsearch lookup contains a slash: %s",
123     keystring);
124   return DEFER;
125   }
126
127 filename = string_sprintf("%s/%s", dirname, keystring);
128 if (  Ulstat(filename, &statbuf) >= 0
129    && (  !(flags & FILTER_TYPE)
130       || (flags & FILTER_FILE && S_ISREG(statbuf.st_mode))
131       || (  flags & (FILTER_DIR | FILTER_SUBDIR)
132          && S_ISDIR(statbuf.st_mode)
133          && (  flags & FILTER_DIR
134             || keystring[0] != '.'
135             || keystring[1] && keystring[1] != '.'
136    )  )  )  )
137   {
138   /* Since the filename exists in the filesystem, we can return a
139   non-tainted result. */
140   *result = string_copy_taint(flags & RET_FULL ? filename : keystring, GET_UNTAINTED);
141   return OK;
142   }
143
144 if (errno == ENOENT || errno == 0) return FAIL;
145
146 save_errno = errno;
147 *errmsg = string_sprintf("%s: lstat: %s", filename, strerror(errno));
148 errno = save_errno;
149 return DEFER;
150 }
151
152
153 /*************************************************
154 *              Close entry point                 *
155 *************************************************/
156
157 /* See local README for interface description */
158
159 void
160 static dsearch_close(void *handle)
161 {
162 handle = handle;   /* Avoid compiler warning */
163 }
164
165
166 /*************************************************
167 *         Version reporting entry point          *
168 *************************************************/
169
170 /* See local README for interface description. */
171
172 #include "../version.h"
173
174 gstring *
175 dsearch_version_report(gstring * g)
176 {
177 #ifdef DYNLOOKUP
178 g = string_fmt_append(g, "Library version: dsearch: Exim version %s\n", EXIM_VERSION_STR);
179 #endif
180 return g;
181 }
182
183
184 static lookup_info _lookup_info = {
185   .name = US"dsearch",                  /* lookup name */
186   .type = lookup_absfile,               /* uses absolute file name */
187   .open = dsearch_open,                 /* open function */
188   .check = dsearch_check,               /* check function */
189   .find = dsearch_find,                 /* find function */
190   .close = dsearch_close,               /* close function */
191   .tidy = NULL,                         /* no tidy function */
192   .quote = NULL,                        /* no quoting function */
193   .version_report = dsearch_version_report         /* version reporting */
194 };
195
196 #ifdef DYNLOOKUP
197 #define dsearch_lookup_module_info _lookup_module_info
198 #endif
199
200 static lookup_info *_lookup_list[] = { &_lookup_info };
201 lookup_module_info dsearch_lookup_module_info = { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 1 };
202
203 /* End of lookups/dsearch.c */
204 /* vi: aw ai sw=2
205 */