1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
6 * Exim - CDB database lookup module
7 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
9 * Copyright (c) The Exim Maintainers 2020 - 2022
10 * Copyright (c) 1998 Nigel Metheringham, Planet Online Ltd
11 * SPDX-License-Identifier: GPL-2.0-or-later
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
18 * --------------------------------------------------------------
19 * Modified by PH for Exim 4:
20 * Changed over to using unsigned chars
21 * Makes use of lf_check_file() for file checking
22 * --------------------------------------------------------------
23 * Modified by The Exim Maintainers 2015:
25 * --------------------------------------------------------------
27 * This program is distributed in the hope that it will be useful,
28 * but WITHOUT ANY WARRANTY; without even the implied warranty of
29 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30 * GNU General Public License for more details.
32 * You should have received a copy of the GNU General Public License
33 * along with this program; if not, write to the Free Software
34 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
38 * This code implements Dan Bernstein's Constant DataBase (cdb) spec.
39 * Information, the spec and sample code for cdb can be obtained from
40 * http://www.pobox.com/~djb/cdb.html
42 * This implementation borrows some code from Dan Bernstein's
43 * implementation (which has no license restrictions applied to it).
44 * This (read-only) implementation is completely contained within
45 * cdb.[ch] it does *not* link against an external cdb library.
48 * There are 2 variants included within this code. One uses MMAP and
49 * should give better performance especially for multiple lookups on a
50 * modern machine. The other is the default implementation which is
51 * used in the case where the MMAP fails or if MMAP was not compiled
52 * in. this implementation is the same as the original reference cdb
53 * implementation. The MMAP version is compiled in if the HAVE_MMAP
54 * preprocessor define is defined - this should be set in the system
61 #include "lf_functions.h"
64 # include <sys/mman.h>
65 /* Not all implementations declare MAP_FAILED */
67 # define MAP_FAILED ((void *) -1)
68 # endif /* MAP_FAILED */
69 #endif /* HAVE_MMAP */
72 #define CDB_HASH_SPLIT 256 /* num pieces the hash table is split into */
73 #define CDB_HASH_MASK 255 /* mask to and off split value */
74 #define CDB_HASH_ENTRY 8 /* how big each offset it */
75 #define CDB_HASH_TABLE (CDB_HASH_SPLIT * CDB_HASH_ENTRY)
77 /* State information for cdb databases that are open NB while the db
78 * is open its contents will not change (cdb dbs are normally updated
79 * atomically by renaming). However the lifetime of one of these
80 * state structures should be limited - ie a long running daemon
81 * that opens one may hit problems....
91 /* 32 bit unsigned type - this is an int on all modern machines */
92 typedef unsigned int uint32;
96 * Internal function to make hash value */
99 cdb_hash(const uschar *buf, unsigned int len)
107 h ^= (uint32) *buf++;
114 * Internal function to read len bytes from disk, coping with oddities */
124 r = Uread(fd,buf,len);
125 while ((r == -1) && (errno == EINTR));
126 if (r == -1) return -1;
127 if (r == 0) { errno = EIO; return -1; }
136 * Internal function to parse 4 byte number (endian independent) */
139 cdb_unpack(uschar *buf)
142 num = buf[3]; num <<= 8;
143 num += buf[2]; num <<= 8;
144 num += buf[1]; num <<= 8;
149 static void cdb_close(void *handle);
152 cdb_open(const uschar * filename, uschar ** errmsg)
155 struct cdb_state *cdbp;
159 if ((fileno = Uopen(filename, O_RDONLY, 0)) < 0)
161 *errmsg = string_open_failed("%s for cdb lookup", filename);
165 if (fstat(fileno, &statbuf) != 0)
167 *errmsg = string_open_failed("fstat(%s) failed - cannot do cdb lookup",
172 /* If this is a valid file, then it *must* be at least
173 CDB_HASH_TABLE bytes long */
175 if (statbuf.st_size < CDB_HASH_TABLE)
177 *errmsg = string_open_failed("%s too short for cdb lookup", filename);
181 /* Having got a file open we need the structure to put things in */
182 cdbp = store_get(sizeof(struct cdb_state), GET_UNTAINTED);
183 /* store_get() does not return if memory was not available... */
184 /* preload the structure.... */
185 cdbp->fileno = fileno;
186 cdbp->filelen = statbuf.st_size;
187 cdbp->cdb_map = NULL;
188 cdbp->cdb_offsets = NULL;
190 /* if we are allowed to we use mmap here.... */
192 if ((mapbuf = mmap(NULL, statbuf.st_size, PROT_READ, MAP_SHARED, fileno, 0))
195 /* We have an mmap-ed section. Now we can just use it */
196 cdbp->cdb_map = mapbuf;
197 /* The offsets can be set to the same value since they should
198 * effectively be cached as well
200 cdbp->cdb_offsets = mapbuf;
202 /* Now return the state struct */
206 /* If we got here the map failed. Basically we can ignore this since we fall
207 back to slower methods.... However lets debug log it... */
209 DEBUG(D_lookup) debug_printf_indent("cdb mmap failed - %d\n", errno);
210 #endif /* HAVE_MMAP */
212 /* In this case we have either not got MMAP allowed, or it failed */
214 /* get a buffer to stash the basic offsets in - this should speed
215 things up a lot - especially on multiple lookups */
217 cdbp->cdb_offsets = store_get(CDB_HASH_TABLE, GET_UNTAINTED);
219 /* now fill the buffer up... */
221 if (cdb_bread(fileno, cdbp->cdb_offsets, CDB_HASH_TABLE) == -1)
223 /* read of hash table failed, oh dear, oh..... time to give up I think....
224 call the close routine (deallocs the memory), and return NULL */
226 *errmsg = string_open_failed("cannot read header from %s for cdb lookup",
232 /* Everything else done - return the cache structure */
238 /*************************************************
239 * Check entry point *
240 *************************************************/
243 cdb_check(void * handle, const uschar * filename, int modemask,
244 uid_t * owners, gid_t * owngroups, uschar ** errmsg)
246 struct cdb_state * cdbp = handle;
247 return lf_check_file(cdbp->fileno, filename, S_IFREG, modemask,
248 owners, owngroups, "cdb", errmsg) == 0;
253 /*************************************************
255 *************************************************/
258 cdb_find(void * handle, const uschar * filename, const uschar * keystring,
259 int key_len, uschar ** result, uschar ** errmsg, uint * do_cache,
262 struct cdb_state * cdbp = handle;
275 key_hash = cdb_hash(keystring, key_len);
277 hash_offset_entry = CDB_HASH_ENTRY * (key_hash & CDB_HASH_MASK);
278 hash_offset = cdb_unpack(cdbp->cdb_offsets + hash_offset_entry);
279 hash_offlen = cdb_unpack(cdbp->cdb_offsets + hash_offset_entry + 4);
281 /* If the offset length is zero this key cannot be in the file */
283 if (hash_offlen == 0)
286 hash_slotnm = (key_hash >> 8) % hash_offlen;
288 /* check to ensure that the file is not corrupt
289 * if the hash_offset + (hash_offlen * CDB_HASH_ENTRY) is longer
290 * than the file, then we have problems.... */
292 if ((hash_offset + (hash_offlen * CDB_HASH_ENTRY)) > cdbp->filelen)
294 *errmsg = string_sprintf("cdb: corrupt cdb file %s (too short)",
296 DEBUG(D_lookup) debug_printf_indent("%s\n", *errmsg);
300 cur_offset = hash_offset + (hash_slotnm * CDB_HASH_ENTRY);
301 end_offset = hash_offset + (hash_offlen * CDB_HASH_ENTRY);
303 /* if we are allowed to we use mmap here.... */
306 /* make sure the mmap was OK */
307 if (cdbp->cdb_map != NULL)
309 uschar * cur_pos = cur_offset + cdbp->cdb_map;
310 uschar * end_pos = end_offset + cdbp->cdb_map;
312 for (int loop = 0; (loop < hash_offlen); ++loop)
314 item_hash = cdb_unpack(cur_pos);
316 item_posn = cdb_unpack(cur_pos);
319 /* if the position is zero then we have a definite miss */
324 if (item_hash == key_hash)
325 { /* matching hash value */
326 uschar * item_ptr = cdbp->cdb_map + item_posn;
328 item_key_len = cdb_unpack(item_ptr);
330 item_dat_len = cdb_unpack(item_ptr);
333 /* check key length matches */
335 if (item_key_len == key_len)
337 /* finally check if key matches */
338 if (Ustrncmp(keystring, item_ptr, key_len) == 0)
340 /* we have a match.... * make item_ptr point to data */
342 item_ptr += item_key_len;
344 /* ... and the returned result. Assume it is not
345 tainted, lacking any way of telling. */
347 *result = store_get(item_dat_len + 1, GET_UNTAINTED);
348 memcpy(*result, item_ptr, item_dat_len);
349 (*result)[item_dat_len] = 0;
354 /* handle warp round of table */
355 if (cur_pos == end_pos)
356 cur_pos = cdbp->cdb_map + hash_offset;
358 /* looks like we failed... */
362 #endif /* HAVE_MMAP */
364 for (int loop = 0; (loop < hash_offlen); ++loop)
368 if (lseek(cdbp->fileno, (off_t) cur_offset, SEEK_SET) == -1) return DEFER;
369 if (cdb_bread(cdbp->fileno, packbuf, 8) == -1) return DEFER;
371 item_hash = cdb_unpack(packbuf);
372 item_posn = cdb_unpack(packbuf + 4);
374 /* if the position is zero then we have a definite miss */
379 if (item_hash == key_hash)
380 { /* matching hash value */
381 if (lseek(cdbp->fileno, (off_t) item_posn, SEEK_SET) == -1) return DEFER;
382 if (cdb_bread(cdbp->fileno, packbuf, 8) == -1) return DEFER;
384 item_key_len = cdb_unpack(packbuf);
386 /* check key length matches */
388 if (item_key_len == key_len)
389 { /* finally check if key matches */
390 rmark reset_point = store_mark();
391 uschar * item_key = store_get(key_len, GET_TAINTED); /* keys liable to be tainted */
393 if (cdb_bread(cdbp->fileno, item_key, key_len) == -1) return DEFER;
394 if (Ustrncmp(keystring, item_key, key_len) == 0)
396 /* Reclaim some store */
397 store_reset(reset_point);
399 /* matches - get data length */
400 item_dat_len = cdb_unpack(packbuf + 4);
402 /* then we build a new result string. We know we have enough
403 memory so disable Coverity errors about the tainted item_dat_ken */
405 *result = store_get(item_dat_len + 1, GET_UNTAINTED);
406 /* coverity[tainted_data] */
407 if (cdb_bread(cdbp->fileno, *result, item_dat_len) == -1)
410 /* coverity[tainted_data] */
411 (*result)[item_dat_len] = 0;
414 /* Reclaim some store */
415 store_reset(reset_point);
420 /* handle warp round of table */
421 if (cur_offset == end_offset)
422 cur_offset = hash_offset;
429 /*************************************************
430 * Close entry point *
431 *************************************************/
433 /* See local README for interface description */
436 cdb_close(void *handle)
438 struct cdb_state * cdbp = handle;
443 munmap(CS cdbp->cdb_map, cdbp->filelen);
444 if (cdbp->cdb_map == cdbp->cdb_offsets)
445 cdbp->cdb_offsets = NULL;
447 #endif /* HAVE_MMAP */
449 (void)close(cdbp->fileno);
454 /*************************************************
455 * Version reporting entry point *
456 *************************************************/
458 /* See local README for interface description. */
460 #include "../version.h"
463 cdb_version_report(gstring * g)
466 g = string_fmt_append(g, "Library version: CDB: Exim version %s\n", EXIM_VERSION_STR);
472 lookup_info cdb_lookup_info = {
473 .name = US"cdb", /* lookup name */
474 .type = lookup_absfile, /* absolute file name */
475 .open = cdb_open, /* open function */
476 .check = cdb_check, /* check function */
477 .find = cdb_find, /* find function */
478 .close = cdb_close, /* close function */
479 .tidy = NULL, /* no tidy function */
480 .quote = NULL, /* no quoting function */
481 .version_report = cdb_version_report /* version reporting */
485 #define cdb_lookup_module_info _lookup_module_info
488 static lookup_info *_lookup_list[] = { &cdb_lookup_info };
489 lookup_module_info cdb_lookup_module_info = { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 1 };
491 /* End of lookups/cdb.c */