1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4 
5 /*
6  * Exim - CDB database lookup module
7  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
8  *
9  * Copyright (c) 1998 Nigel Metheringham, Planet Online Ltd
10  * Copyright (c) The Exim Maintainers 2020
11  *
12  * This program is free software; you can redistribute it and/or
13  * modify it under the terms of the GNU General Public License
14  * as published by the Free Software Foundation; either version 2
15  * of the License, or (at your option) any later version.
16  *
17  * --------------------------------------------------------------
18  * Modified by PH for Exim 4:
19  *   Changed over to using unsigned chars
20  *   Makes use of lf_check_file() for file checking
21  * --------------------------------------------------------------
22  * Modified by The Exim Maintainers 2015:
23  *   const propagation
24  * --------------------------------------------------------------
25  *
26  * This program is distributed in the hope that it will be useful,
27  * but WITHOUT ANY WARRANTY; without even the implied warranty of
28  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
29  * GNU General Public License for more details.
30  *
31  * You should have received a copy of the GNU General Public License
32  * along with this program; if not, write to the Free Software
33  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
34  * 02111-1307, USA.
35  *
36  *
37  * This code implements Dan Bernstein's Constant DataBase (cdb) spec.
38  * Information, the spec and sample code for cdb can be obtained from
39  *      http://www.pobox.com/~djb/cdb.html
40  *
41  * This implementation borrows some code from Dan Bernstein's
42  * implementation (which has no license restrictions applied to it).
43  * This (read-only) implementation is completely contained within
44  * cdb.[ch] it does *not* link against an external cdb library.
45  *
46  *
47  * There are 2 variants included within this code.  One uses MMAP and
48  * should give better performance especially for multiple lookups on a
49  * modern machine.  The other is the default implementation which is
50  * used in the case where the MMAP fails or if MMAP was not compiled
51  * in.  this implementation is the same as the original reference cdb
52  * implementation.  The MMAP version is compiled in if the HAVE_MMAP
53  * preprocessor define is defined - this should be set in the system
54  * specific os.h file.
55  *
56  */
57 
58 
59 #include "../exim.h"
60 #include "lf_functions.h"
61 
62 #ifdef HAVE_MMAP
63 #  include <sys/mman.h>
64 /* Not all implementations declare MAP_FAILED */
65 #  ifndef MAP_FAILED
66 #    define MAP_FAILED ((void *) -1)
67 #  endif /* MAP_FAILED */
68 #endif /* HAVE_MMAP */
69 
70 
71 #define CDB_HASH_SPLIT 256     /* num pieces the hash table is split into */
72 #define CDB_HASH_MASK  255     /* mask to and off split value */
73 #define CDB_HASH_ENTRY 8       /* how big each offset it */
74 #define CDB_HASH_TABLE (CDB_HASH_SPLIT * CDB_HASH_ENTRY)
75 
76 /* State information for cdb databases that are open NB while the db
77  * is open its contents will not change (cdb dbs are normally updated
78  * atomically by renaming).  However the lifetime of one of these
79  * state structures should be limited - ie a long running daemon
80  * that opens one may hit problems....
81  */
82 
83 struct cdb_state {
84   int     fileno;
85   off_t   filelen;
86   uschar *cdb_map;
87   uschar *cdb_offsets;
88 };
89 
90 /* 32 bit unsigned type - this is an int on all modern machines */
91 typedef unsigned int uint32;
92 
93 /*
94  * cdb_hash()
95  * Internal function to make hash value */
96 
97 static uint32
cdb_hash(const uschar * buf,unsigned int len)98 cdb_hash(const uschar *buf, unsigned int len)
99 {
100   uint32 h;
101 
102   h = 5381;
103   while (len) {
104     --len;
105     h += (h << 5);
106     h ^= (uint32) *buf++;
107   }
108   return h;
109 }
110 
111 /*
112  * cdb_bread()
113  * Internal function to read len bytes from disk, coping with oddities */
114 
115 static int
cdb_bread(int fd,uschar * buf,int len)116 cdb_bread(int fd,
117          uschar *buf,
118          int len)
119 {
120   int r;
121   while (len > 0) {
122     do
123       r = Uread(fd,buf,len);
124     while ((r == -1) && (errno == EINTR));
125     if (r == -1) return -1;
126     if (r == 0) { errno = EIO; return -1; }
127     buf += r;
128     len -= r;
129   }
130   return 0;
131 }
132 
133 /*
134  * cdb_bread()
135  * Internal function to parse 4 byte number (endian independent) */
136 
137 static uint32
cdb_unpack(uschar * buf)138 cdb_unpack(uschar *buf)
139 {
140 uint32 num;
141 num =  buf[3]; num <<= 8;
142 num += buf[2]; num <<= 8;
143 num += buf[1]; num <<= 8;
144 num += buf[0];
145 return num;
146 }
147 
148 static void cdb_close(void *handle);
149 
150 static void *
cdb_open(const uschar * filename,uschar ** errmsg)151 cdb_open(const uschar * filename, uschar ** errmsg)
152 {
153 int fileno;
154 struct cdb_state *cdbp;
155 struct stat statbuf;
156 void * mapbuf;
157 
158 if ((fileno = Uopen(filename, O_RDONLY, 0)) < 0)
159   {
160   *errmsg = string_open_failed("%s for cdb lookup", filename);
161   return NULL;
162   }
163 
164 if (fstat(fileno, &statbuf) != 0)
165   {
166   *errmsg = string_open_failed("fstat(%s) failed - cannot do cdb lookup",
167 			      filename);
168   return NULL;
169   }
170 
171 /* If this is a valid file, then it *must* be at least
172 CDB_HASH_TABLE bytes long */
173 
174 if (statbuf.st_size < CDB_HASH_TABLE)
175   {
176   *errmsg = string_open_failed("%s too short for cdb lookup", filename);
177   return NULL;
178   }
179 
180 /* Having got a file open we need the structure to put things in */
181 cdbp = store_get(sizeof(struct cdb_state), FALSE);
182 /* store_get() does not return if memory was not available... */
183 /* preload the structure.... */
184 cdbp->fileno = fileno;
185 cdbp->filelen = statbuf.st_size;
186 cdbp->cdb_map = NULL;
187 cdbp->cdb_offsets = NULL;
188 
189 /* if we are allowed to we use mmap here.... */
190 #ifdef HAVE_MMAP
191 if ((mapbuf = mmap(NULL, statbuf.st_size, PROT_READ, MAP_SHARED, fileno, 0))
192     != MAP_FAILED)
193   {
194   /* We have an mmap-ed section.  Now we can just use it */
195   cdbp->cdb_map = mapbuf;
196   /* The offsets can be set to the same value since they should
197    * effectively be cached as well
198    */
199   cdbp->cdb_offsets = mapbuf;
200 
201   /* Now return the state struct */
202   return(cdbp);
203   }
204 
205 /* If we got here the map failed.  Basically we can ignore this since we fall
206 back to slower methods....  However lets debug log it...  */
207 
208 DEBUG(D_lookup) debug_printf_indent("cdb mmap failed - %d\n", errno);
209 #endif /* HAVE_MMAP */
210 
211 /* In this case we have either not got MMAP allowed, or it failed */
212 
213 /* get a buffer to stash the basic offsets in - this should speed
214 things up a lot - especially on multiple lookups */
215 
216 cdbp->cdb_offsets = store_get(CDB_HASH_TABLE, FALSE);
217 
218 /* now fill the buffer up... */
219 
220 if (cdb_bread(fileno, cdbp->cdb_offsets, CDB_HASH_TABLE) == -1)
221   {
222   /* read of hash table failed, oh dear, oh.....  time to give up I think....
223   call the close routine (deallocs the memory), and return NULL */
224 
225   *errmsg = string_open_failed("cannot read header from %s for cdb lookup",
226 			      filename);
227   cdb_close(cdbp);
228   return NULL;
229   }
230 
231 /* Everything else done - return the cache structure */
232 return cdbp;
233 }
234 
235 
236 
237 /*************************************************
238 *             Check entry point                  *
239 *************************************************/
240 
241 static BOOL
cdb_check(void * handle,const uschar * filename,int modemask,uid_t * owners,gid_t * owngroups,uschar ** errmsg)242 cdb_check(void * handle, const uschar * filename, int modemask,
243   uid_t * owners, gid_t * owngroups, uschar ** errmsg)
244 {
245 struct cdb_state * cdbp = handle;
246 return lf_check_file(cdbp->fileno, filename, S_IFREG, modemask,
247 		     owners, owngroups, "cdb", errmsg) == 0;
248 }
249 
250 
251 
252 /*************************************************
253 *              Find entry point                  *
254 *************************************************/
255 
256 static int
cdb_find(void * handle,const uschar * filename,const uschar * keystring,int key_len,uschar ** result,uschar ** errmsg,uint * do_cache,const uschar * opts)257 cdb_find(void * handle, const uschar * filename, const uschar * keystring,
258   int key_len, uschar ** result, uschar ** errmsg, uint * do_cache,
259   const uschar * opts)
260 {
261 struct cdb_state * cdbp = handle;
262 uint32 item_key_len,
263 item_dat_len,
264 key_hash,
265 item_hash,
266 item_posn,
267 cur_offset,
268 end_offset,
269 hash_offset_entry,
270 hash_offset,
271 hash_offlen,
272 hash_slotnm;
273 
274 key_hash = cdb_hash(keystring, key_len);
275 
276 hash_offset_entry = CDB_HASH_ENTRY * (key_hash & CDB_HASH_MASK);
277 hash_offset = cdb_unpack(cdbp->cdb_offsets + hash_offset_entry);
278 hash_offlen = cdb_unpack(cdbp->cdb_offsets + hash_offset_entry + 4);
279 
280 /* If the offset length is zero this key cannot be in the file */
281 
282 if (hash_offlen == 0)
283   return FAIL;
284 
285 hash_slotnm = (key_hash >> 8) % hash_offlen;
286 
287 /* check to ensure that the file is not corrupt
288  * if the hash_offset + (hash_offlen * CDB_HASH_ENTRY) is longer
289  * than the file, then we have problems.... */
290 
291 if ((hash_offset + (hash_offlen * CDB_HASH_ENTRY)) > cdbp->filelen)
292   {
293   *errmsg = string_sprintf("cdb: corrupt cdb file %s (too short)",
294 		      filename);
295   DEBUG(D_lookup) debug_printf_indent("%s\n", *errmsg);
296   return DEFER;
297   }
298 
299 cur_offset = hash_offset + (hash_slotnm * CDB_HASH_ENTRY);
300 end_offset = hash_offset + (hash_offlen * CDB_HASH_ENTRY);
301 
302 /* if we are allowed to we use mmap here.... */
303 
304 #ifdef HAVE_MMAP
305 /* make sure the mmap was OK */
306 if (cdbp->cdb_map != NULL)
307   {
308   uschar * cur_pos = cur_offset + cdbp->cdb_map;
309   uschar * end_pos = end_offset + cdbp->cdb_map;
310 
311   for (int loop = 0; (loop < hash_offlen); ++loop)
312     {
313     item_hash = cdb_unpack(cur_pos);
314     cur_pos += 4;
315     item_posn = cdb_unpack(cur_pos);
316     cur_pos += 4;
317 
318     /* if the position is zero then we have a definite miss */
319 
320     if (item_posn == 0)
321       return FAIL;
322 
323     if (item_hash == key_hash)
324       {					/* matching hash value */
325       uschar * item_ptr = cdbp->cdb_map + item_posn;
326 
327       item_key_len = cdb_unpack(item_ptr);
328       item_ptr += 4;
329       item_dat_len = cdb_unpack(item_ptr);
330       item_ptr += 4;
331 
332       /* check key length matches */
333 
334       if (item_key_len == key_len)
335 	{
336 	 /* finally check if key matches */
337 	 if (Ustrncmp(keystring, item_ptr, key_len) == 0)
338 	   {
339 	   /* we have a match....  * make item_ptr point to data */
340 
341 	   item_ptr += item_key_len;
342 
343 	   /* ... and the returned result.  Assume it is not
344 	   tainted, lacking any way of telling.  */
345 
346 	   *result = store_get(item_dat_len + 1, FALSE);
347 	   memcpy(*result, item_ptr, item_dat_len);
348 	   (*result)[item_dat_len] = 0;
349 	   return OK;
350 	   }
351 	}
352       }
353     /* handle warp round of table */
354     if (cur_pos == end_pos)
355     cur_pos = cdbp->cdb_map + hash_offset;
356     }
357   /* looks like we failed... */
358   return FAIL;
359   }
360 
361 #endif /* HAVE_MMAP */
362 
363 for (int loop = 0; (loop < hash_offlen); ++loop)
364   {
365   uschar packbuf[8];
366 
367   if (lseek(cdbp->fileno, (off_t) cur_offset, SEEK_SET) == -1) return DEFER;
368   if (cdb_bread(cdbp->fileno, packbuf, 8) == -1) return DEFER;
369 
370   item_hash = cdb_unpack(packbuf);
371   item_posn = cdb_unpack(packbuf + 4);
372 
373   /* if the position is zero then we have a definite miss */
374 
375   if (item_posn == 0)
376     return FAIL;
377 
378   if (item_hash == key_hash)
379     {						/* matching hash value */
380     if (lseek(cdbp->fileno, (off_t) item_posn, SEEK_SET) == -1) return DEFER;
381     if (cdb_bread(cdbp->fileno, packbuf, 8) == -1) return DEFER;
382 
383     item_key_len = cdb_unpack(packbuf);
384 
385     /* check key length matches */
386 
387     if (item_key_len == key_len)
388       {					/* finally check if key matches */
389       rmark reset_point = store_mark();
390       uschar * item_key = store_get(key_len, TRUE); /* keys liable to be tainted */
391 
392       if (cdb_bread(cdbp->fileno, item_key, key_len) == -1) return DEFER;
393       if (Ustrncmp(keystring, item_key, key_len) == 0)
394         {
395         /* Reclaim some store */
396         store_reset(reset_point);
397 
398         /* matches - get data length */
399         item_dat_len = cdb_unpack(packbuf + 4);
400 
401         /* then we build a new result string.  We know we have enough
402         memory so disable Coverity errors about the tainted item_dat_ken */
403 
404         *result = store_get(item_dat_len + 1, FALSE);
405         /* coverity[tainted_data] */
406         if (cdb_bread(cdbp->fileno, *result, item_dat_len) == -1)
407 	  return DEFER;
408 
409         /* coverity[tainted_data] */
410         (*result)[item_dat_len] = 0;
411         return OK;
412         }
413       /* Reclaim some store */
414       store_reset(reset_point);
415       }
416     }
417   cur_offset += 8;
418 
419   /* handle warp round of table */
420   if (cur_offset == end_offset)
421   cur_offset = hash_offset;
422   }
423 return FAIL;
424 }
425 
426 
427 
428 /*************************************************
429 *              Close entry point                 *
430 *************************************************/
431 
432 /* See local README for interface description */
433 
434 static void
cdb_close(void * handle)435 cdb_close(void *handle)
436 {
437 struct cdb_state * cdbp = handle;
438 
439 #ifdef HAVE_MMAP
440 if (cdbp->cdb_map)
441   {
442   munmap(CS cdbp->cdb_map, cdbp->filelen);
443   if (cdbp->cdb_map == cdbp->cdb_offsets)
444      cdbp->cdb_offsets = NULL;
445   }
446 #endif /* HAVE_MMAP */
447 
448 (void)close(cdbp->fileno);
449 }
450 
451 
452 
453 /*************************************************
454 *         Version reporting entry point          *
455 *************************************************/
456 
457 /* See local README for interface description. */
458 
459 #include "../version.h"
460 
461 void
cdb_version_report(FILE * f)462 cdb_version_report(FILE *f)
463 {
464 #ifdef DYNLOOKUP
465 fprintf(f, "Library version: CDB: Exim version %s\n", EXIM_VERSION_STR);
466 #endif
467 }
468 
469 
470 lookup_info cdb_lookup_info = {
471   .name = US"cdb",			/* lookup name */
472   .type = lookup_absfile,		/* absolute file name */
473   .open = cdb_open,			/* open function */
474   .check = cdb_check,			/* check function */
475   .find = cdb_find,			/* find function */
476   .close = cdb_close,			/* close function */
477   .tidy = NULL,				/* no tidy function */
478   .quote = NULL,			/* no quoting function */
479   .version_report = cdb_version_report             /* version reporting */
480 };
481 
482 #ifdef DYNLOOKUP
483 #define cdb_lookup_module_info _lookup_module_info
484 #endif
485 
486 static lookup_info *_lookup_list[] = { &cdb_lookup_info };
487 lookup_module_info cdb_lookup_module_info = { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 1 };
488 
489 /* End of lookups/cdb.c */
490