b78e858c34e0849c9b620a522ac84c631d1d9c2a
[users/heiko/exim.git] / src / src / lookups / lsearch.c
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4
5 /* Copyright (c) University of Cambridge 1995 - 2018 */
6 /* Copyright (c) The Exim Maintainers 2020 */
7 /* See the file NOTICE for conditions of use and distribution. */
8
9 #include "../exim.h"
10 #include "lf_functions.h"
11
12 /* Codes for the different kinds of lsearch that are supported */
13
14 enum {
15   LSEARCH_PLAIN,        /* Literal keys */
16   LSEARCH_WILD,         /* Wild card keys, expanded */
17   LSEARCH_NWILD,        /* Wild card keys, not expanded */
18   LSEARCH_IP            /* IP addresses and networks */
19 };
20
21
22
23 /*************************************************
24 *              Open entry point                  *
25 *************************************************/
26
27 /* See local README for interface description */
28
29 static void *
30 lsearch_open(const uschar * filename, uschar ** errmsg)
31 {
32 FILE *f = Ufopen(filename, "rb");
33 if (f == NULL)
34   {
35   int save_errno = errno;
36   *errmsg = string_open_failed(errno, "%s for linear search", filename);
37   errno = save_errno;
38   return NULL;
39   }
40 return f;
41 }
42
43
44
45 /*************************************************
46 *             Check entry point                  *
47 *************************************************/
48
49 static BOOL
50 lsearch_check(void *handle, const uschar *filename, int modemask, uid_t *owners,
51   gid_t *owngroups, uschar **errmsg)
52 {
53 return lf_check_file(fileno((FILE *)handle), filename, S_IFREG, modemask,
54   owners, owngroups, "lsearch", errmsg) == 0;
55 }
56
57
58
59 /*************************************************
60 *  Internal function for the various lsearches   *
61 *************************************************/
62
63 /* See local README for interface description, plus:
64
65 Extra argument:
66
67   type     one of the values LSEARCH_PLAIN, LSEARCH_WILD, LSEARCH_NWILD, or
68            LSEARCH_IP
69
70 There is some messy logic in here to cope with very long data lines that do not
71 fit into the fixed sized buffer. Most of the time this will never be exercised,
72 but people do occasionally do weird things. */
73
74 static int
75 internal_lsearch_find(void * handle, const uschar * filename,
76   const uschar * keystring, int length, uschar ** result, uschar ** errmsg,
77  int type)
78 {
79 FILE *f = (FILE *)handle;
80 BOOL last_was_eol = TRUE;
81 BOOL this_is_eol = TRUE;
82 int old_pool = store_pool;
83 rmark reset_point = NULL;
84 uschar buffer[4096];
85
86 /* Wildcard searches may use up some store, because of expansions. We don't
87 want them to fill up our search store. What we do is set the pool to the main
88 pool and get a point to reset to later. Wildcard searches could also issue
89 lookups, but internal_search_find will take care of that, and the cache will be
90 safely stored in the search pool again. */
91
92 if(type == LSEARCH_WILD || type == LSEARCH_NWILD)
93   {
94   store_pool = POOL_MAIN;
95   reset_point = store_mark();
96   }
97
98 rewind(f);
99 for (last_was_eol = TRUE;
100      Ufgets(buffer, sizeof(buffer), f) != NULL;
101      last_was_eol = this_is_eol)
102   {
103   int p = Ustrlen(buffer);
104   int linekeylength;
105   BOOL this_is_comment;
106   gstring * yield;
107   uschar *s = buffer;
108
109   /* Check whether this the final segment of a line. If it follows an
110   incomplete part-line, skip it. */
111
112   this_is_eol = p > 0 && buffer[p-1] == '\n';
113   if (!last_was_eol) continue;
114
115   /* We now have the start of a physical line. If this is a final line segment,
116   remove trailing white space. */
117
118   if (this_is_eol)
119     {
120     while (p > 0 && isspace((uschar)buffer[p-1])) p--;
121     buffer[p] = 0;
122     }
123
124   /* If the buffer is empty it might be (a) a complete empty line, or (b) the
125   start of a line that begins with so much white space that it doesn't all fit
126   in the buffer. In both cases we want to skip the entire physical line.
127
128   If the buffer begins with # it is a comment line; if it begins with white
129   space it is a logical continuation; again, we want to skip the entire
130   physical line. */
131
132   if (buffer[0] == 0 || buffer[0] == '#' || isspace(buffer[0])) continue;
133
134   /* We assume that they key will fit in the buffer. If the key starts with ",
135   read it as a quoted string. We don't use string_dequote() because that uses
136   new store for the result, and we may be doing this many times in a long file.
137   We know that the dequoted string must be shorter than the original, because
138   we are removing the quotes, and also any escape sequences always turn two or
139   more characters into one character. Therefore, we can store the new string in
140   the same buffer. */
141
142   if (*s == '\"')
143     {
144     uschar *t = s++;
145     while (*s != 0 && *s != '\"')
146       {
147       if (*s == '\\') *t++ = string_interpret_escape(CUSS &s);
148         else *t++ = *s;
149       s++;
150       }
151     if (*s != 0) s++;               /* Past terminating " */
152     linekeylength = t - buffer;
153     }
154
155   /* Otherwise it is terminated by a colon or white space */
156
157   else
158     {
159     while (*s != 0 && *s != ':' && !isspace(*s)) s++;
160     linekeylength = s - buffer;
161     }
162
163   /* The matching test depends on which kind of lsearch we are doing */
164
165   switch(type)
166     {
167     /* A plain lsearch treats each key as a literal */
168
169     case LSEARCH_PLAIN:
170     if (linekeylength != length || strncmpic(buffer, keystring, length) != 0)
171       continue;
172     break;      /* Key matched */
173
174     /* A wild lsearch treats each key as a possible wildcarded string; no
175     expansion is done for nwildlsearch. */
176
177     case LSEARCH_WILD:
178     case LSEARCH_NWILD:
179       {
180       int rc;
181       int save = buffer[linekeylength];
182       const uschar *list = buffer;
183       buffer[linekeylength] = 0;
184       rc = match_isinlist(keystring,
185         &list,
186         UCHAR_MAX+1,              /* Single-item list */
187         NULL,                     /* No anchor */
188         NULL,                     /* No caching */
189         MCL_STRING + ((type == LSEARCH_WILD)? 0:MCL_NOEXPAND),
190         TRUE,                     /* Caseless */
191         NULL);
192       buffer[linekeylength] = save;
193       if (rc == FAIL) continue;
194       if (rc == DEFER) return DEFER;
195       }
196
197     /* The key has matched. If the search involved a regular expression, it
198     might have caused numerical variables to be set. However, their values will
199     be in the wrong storage pool for external use. Copying them to the standard
200     pool is not feasible because of the caching of lookup results - a repeated
201     lookup will not match the regular expression again. Therefore, we flatten
202     all numeric variables at this point. */
203
204     expand_nmax = -1;
205     break;
206
207     /* Compare an ip address against a list of network/ip addresses. We have to
208     allow for the "*" case specially. */
209
210     case LSEARCH_IP:
211     if (linekeylength == 1 && buffer[0] == '*')
212       {
213       if (length != 1 || keystring[0] != '*') continue;
214       }
215     else if (length == 1 && keystring[0] == '*') continue;
216     else
217       {
218       int maskoffset;
219       int save = buffer[linekeylength];
220       buffer[linekeylength] = 0;
221       if (string_is_ip_address(buffer, &maskoffset) == 0 ||
222           !host_is_in_net(keystring, buffer, maskoffset)) continue;
223       buffer[linekeylength] = save;
224       }
225     break;      /* Key matched */
226     }
227
228   /* The key has matched. Skip spaces after the key, and allow an optional
229   colon after the spaces. This is an odd specification, but it's for
230   compatibility. */
231
232   while (isspace((uschar)*s)) s++;
233   if (*s == ':')
234     {
235     s++;
236     while (isspace((uschar)*s)) s++;
237     }
238
239   /* Reset dynamic store, if we need to, and revert to the search pool */
240
241   if (reset_point)
242     {
243     reset_point = store_reset(reset_point);
244     store_pool = old_pool;
245     }
246
247   /* Now we want to build the result string to contain the data. There can be
248   two kinds of continuation: (a) the physical line may not all have fitted into
249   the buffer, and (b) there may be logical continuation lines, for which we
250   must convert all leading white space into a single blank.
251
252   Initialize, and copy the first segment of data. */
253
254   this_is_comment = FALSE;
255   yield = string_get(100);
256   if (*s != 0)
257     yield = string_cat(yield, s);
258
259   /* Now handle continuations */
260
261   for (last_was_eol = this_is_eol;
262        Ufgets(buffer, sizeof(buffer), f) != NULL;
263        last_was_eol = this_is_eol)
264     {
265     s = buffer;
266     p = Ustrlen(buffer);
267     this_is_eol = p > 0 && buffer[p-1] == '\n';
268
269     /* Remove trailing white space from a physical line end */
270
271     if (this_is_eol)
272       {
273       while (p > 0 && isspace((uschar)buffer[p-1])) p--;
274       buffer[p] = 0;
275       }
276
277     /* If this is not a physical line continuation, skip it entirely if it's
278     empty or starts with #. Otherwise, break the loop if it doesn't start with
279     white space. Otherwise, replace leading white space with a single blank. */
280
281     if (last_was_eol)
282       {
283       this_is_comment = (this_is_comment || (buffer[0] == 0 || buffer[0] == '#'));
284       if (this_is_comment) continue;
285       if (!isspace((uschar)buffer[0])) break;
286       while (isspace((uschar)*s)) s++;
287       *(--s) = ' ';
288       }
289     if (this_is_comment) continue;
290
291     /* Join a physical or logical line continuation onto the result string. */
292
293     yield = string_cat(yield, s);
294     }
295
296   gstring_release_unused(yield);
297   *result = string_from_gstring(yield);
298   return OK;
299   }
300
301 /* Reset dynamic store, if we need to */
302
303 if (reset_point)
304   {
305   store_reset(reset_point);
306   store_pool = old_pool;
307   }
308
309 return FAIL;
310 }
311
312
313 /*************************************************
314 *         Find entry point for lsearch           *
315 *************************************************/
316
317 /* See local README for interface description */
318
319 static int
320 lsearch_find(void * handle, const uschar * filename, const uschar * keystring,
321   int length, uschar ** result, uschar ** errmsg, uint * do_cache,
322   const uschar * opts)
323 {
324 return internal_lsearch_find(handle, filename, keystring, length, result,
325   errmsg, LSEARCH_PLAIN);
326 }
327
328
329
330 /*************************************************
331 *      Find entry point for wildlsearch          *
332 *************************************************/
333
334 /* See local README for interface description */
335
336 static int
337 wildlsearch_find(void * handle, const uschar * filename, const uschar * keystring,
338   int length, uschar ** result, uschar ** errmsg, uint * do_cache,
339   const uschar * opts)
340 {
341 return internal_lsearch_find(handle, filename, keystring, length, result,
342   errmsg, LSEARCH_WILD);
343 }
344
345
346
347 /*************************************************
348 *      Find entry point for nwildlsearch         *
349 *************************************************/
350
351 /* See local README for interface description */
352
353 static int
354 nwildlsearch_find(void * handle, const uschar * filename, const uschar * keystring,
355   int length, uschar ** result, uschar ** errmsg, uint * do_cache,
356   const uschar * opts)
357 {
358 return internal_lsearch_find(handle, filename, keystring, length, result,
359   errmsg, LSEARCH_NWILD);
360 }
361
362
363
364
365 /*************************************************
366 *      Find entry point for iplsearch            *
367 *************************************************/
368
369 /* See local README for interface description */
370
371 static int
372 iplsearch_find(void * handle, uschar const * filename, const uschar * keystring,
373   int length, uschar ** result, uschar ** errmsg, uint * do_cache,
374   const uschar * opts)
375 {
376 if ((length == 1 && keystring[0] == '*') ||
377     string_is_ip_address(keystring, NULL) != 0)
378   return internal_lsearch_find(handle, filename, keystring, length, result,
379     errmsg, LSEARCH_IP);
380
381 *errmsg = string_sprintf("\"%s\" is not a valid iplsearch key (an IP "
382 "address, with optional CIDR mask, is wanted): "
383 "in a host list, use net-iplsearch as the search type", keystring);
384 return DEFER;
385 }
386
387
388
389
390 /*************************************************
391 *              Close entry point                 *
392 *************************************************/
393
394 /* See local README for interface description */
395
396 static void
397 lsearch_close(void *handle)
398 {
399 (void)fclose((FILE *)handle);
400 }
401
402
403
404 /*************************************************
405 *         Version reporting entry point          *
406 *************************************************/
407
408 /* See local README for interface description. */
409
410 #include "../version.h"
411
412 void
413 lsearch_version_report(FILE *f)
414 {
415 #ifdef DYNLOOKUP
416 fprintf(f, "Library version: lsearch: Exim version %s\n", EXIM_VERSION_STR);
417 #endif
418 }
419
420
421 static lookup_info iplsearch_lookup_info = {
422   .name = US"iplsearch",                /* lookup name */
423   .type = lookup_absfile,               /* uses absolute file name */
424   .open = lsearch_open,                 /* open function */
425   .check = lsearch_check,               /* check function */
426   .find = iplsearch_find,               /* find function */
427   .close = lsearch_close,               /* close function */
428   .tidy = NULL,                         /* no tidy function */
429   .quote = NULL,                        /* no quoting function */
430   .version_report = NULL                           /* no version reporting (redundant) */
431 };
432
433 static lookup_info lsearch_lookup_info = {
434   .name = US"lsearch",                  /* lookup name */
435   .type = lookup_absfile,               /* uses absolute file name */
436   .open = lsearch_open,                 /* open function */
437   .check = lsearch_check,               /* check function */
438   .find = lsearch_find,                 /* find function */
439   .close = lsearch_close,               /* close function */
440   .tidy = NULL,                         /* no tidy function */
441   .quote = NULL,                        /* no quoting function */
442   .version_report = lsearch_version_report         /* version reporting */
443 };
444
445 static lookup_info nwildlsearch_lookup_info = {
446   .name = US"nwildlsearch",             /* lookup name */
447   .type = lookup_absfile,               /* uses absolute file name */
448   .open = lsearch_open,                 /* open function */
449   .check = lsearch_check,               /* check function */
450   .find = nwildlsearch_find,            /* find function */
451   .close = lsearch_close,               /* close function */
452   .tidy = NULL,                         /* no tidy function */
453   .quote = NULL,                        /* no quoting function */
454   .version_report = NULL                           /* no version reporting (redundant) */
455 };
456
457 static lookup_info wildlsearch_lookup_info = {
458   .name = US"wildlsearch",              /* lookup name */
459   .type = lookup_absfile,               /* uses absolute file name */
460   .open = lsearch_open,                 /* open function */
461   .check = lsearch_check,               /* check function */
462   .find = wildlsearch_find,             /* find function */
463   .close = lsearch_close,               /* close function */
464   .tidy = NULL,                         /* no tidy function */
465   .quote = NULL,                        /* no quoting function */
466   .version_report = NULL                           /* no version reporting (redundant) */
467 };
468
469 #ifdef DYNLOOKUP
470 #define lsearch_lookup_module_info _lookup_module_info
471 #endif
472
473 static lookup_info *_lookup_list[] = { &iplsearch_lookup_info,
474                                        &lsearch_lookup_info,
475                                        &nwildlsearch_lookup_info,
476                                        &wildlsearch_lookup_info };
477 lookup_module_info lsearch_lookup_module_info = { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 4 };
478
479 /* End of lookups/lsearch.c */