xref: /netbsd-src/external/ibm-public/postfix/dist/src/util/dict_cdb.c (revision 67b9b338a7386232ac596b5fd0cd5a9cc8a03c71)
1 /*	$NetBSD: dict_cdb.c,v 1.3 2022/10/08 16:12:50 christos Exp $	*/
2 
3 /*++
4 /* NAME
5 /*	dict_cdb 3
6 /* SUMMARY
7 /*	dictionary manager interface to CDB files
8 /* SYNOPSIS
9 /*	#include <dict_cdb.h>
10 /*
11 /*	DICT	*dict_cdb_open(path, open_flags, dict_flags)
12 /*	const char *path;
13 /*	int	open_flags;
14 /*	int	dict_flags;
15 /*
16 /* DESCRIPTION
17 /*	dict_cdb_open() opens the specified CDB database.  The result is
18 /*	a pointer to a structure that can be used to access the dictionary
19 /*	using the generic methods documented in dict_open(3).
20 /*
21 /*	Arguments:
22 /* .IP path
23 /*	The database pathname, not including the ".cdb" suffix.
24 /* .IP open_flags
25 /*	Flags passed to open(). Specify O_RDONLY or O_WRONLY|O_CREAT|O_TRUNC.
26 /* .IP dict_flags
27 /*	Flags used by the dictionary interface.
28 /* SEE ALSO
29 /*	dict(3) generic dictionary manager
30 /* DIAGNOSTICS
31 /*	Fatal errors: cannot open file, write error, out of memory.
32 /* LICENSE
33 /* .ad
34 /* .fi
35 /*	The Secure Mailer license must be distributed with this software.
36 /* AUTHOR(S)
37 /*	Michael Tokarev <mjt@tls.msk.ru> based on dict_db.c by
38 /*	Wietse Venema
39 /*	IBM T.J. Watson Research
40 /*	P.O. Box 704
41 /*	Yorktown Heights, NY 10598, USA
42 /*--*/
43 
44 #include "sys_defs.h"
45 
46 /* System library. */
47 
48 #include <sys/stat.h>
49 #include <limits.h>
50 #include <string.h>
51 #include <unistd.h>
52 #include <stdio.h>
53 
54 /* Utility library. */
55 
56 #include "msg.h"
57 #include "mymalloc.h"
58 #include "vstring.h"
59 #include "stringops.h"
60 #include "iostuff.h"
61 #include "myflock.h"
62 #include "stringops.h"
63 #include "dict.h"
64 #include "dict_cdb.h"
65 #include "warn_stat.h"
66 
67 #ifdef HAS_CDB
68 
69 #include <cdb.h>
70 #ifndef TINYCDB_VERSION
71 #include <cdb_make.h>
72 #endif
73 #ifndef cdb_fileno
74 #define cdb_fileno(c) ((c)->fd)
75 #endif
76 
77 #ifndef CDB_SUFFIX
78 #define CDB_SUFFIX ".cdb"
79 #endif
80 #ifndef CDB_TMP_SUFFIX
81 #define CDB_TMP_SUFFIX CDB_SUFFIX ".tmp"
82 #endif
83 
84 /* Application-specific. */
85 
86 typedef struct {
87     DICT    dict;			/* generic members */
88     struct cdb cdb;			/* cdb structure */
89 } DICT_CDBQ;				/* query interface */
90 
91 typedef struct {
92     DICT    dict;			/* generic members */
93     struct cdb_make cdbm;		/* cdb_make structure */
94     char   *cdb_path;			/* cdb pathname (.cdb) */
95     char   *tmp_path;			/* temporary pathname (.tmp) */
96 } DICT_CDBM;				/* rebuild interface */
97 
98 /* dict_cdbq_lookup - find database entry, query mode */
99 
dict_cdbq_lookup(DICT * dict,const char * name)100 static const char *dict_cdbq_lookup(DICT *dict, const char *name)
101 {
102     DICT_CDBQ *dict_cdbq = (DICT_CDBQ *) dict;
103     unsigned vlen;
104     int     status = 0;
105     static char *buf;
106     static unsigned len;
107     const char *result = 0;
108 
109     dict->error = 0;
110 
111     /* CDB is constant, so do not try to acquire a lock. */
112 
113     /*
114      * Optionally fold the key.
115      */
116     if (dict->flags & DICT_FLAG_FOLD_FIX) {
117 	if (dict->fold_buf == 0)
118 	    dict->fold_buf = vstring_alloc(10);
119 	vstring_strcpy(dict->fold_buf, name);
120 	name = lowercase(vstring_str(dict->fold_buf));
121     }
122 
123     /*
124      * See if this CDB file was written with one null byte appended to key
125      * and value.
126      */
127     if (dict->flags & DICT_FLAG_TRY1NULL) {
128 	status = cdb_find(&dict_cdbq->cdb, name, strlen(name) + 1);
129 	if (status > 0)
130 	    dict->flags &= ~DICT_FLAG_TRY0NULL;
131     }
132 
133     /*
134      * See if this CDB file was written with no null byte appended to key and
135      * value.
136      */
137     if (status == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) {
138 	status = cdb_find(&dict_cdbq->cdb, name, strlen(name));
139 	if (status > 0)
140 	    dict->flags &= ~DICT_FLAG_TRY1NULL;
141     }
142     if (status < 0)
143 	msg_fatal("error reading %s: %m", dict->name);
144 
145     if (status) {
146 	vlen = cdb_datalen(&dict_cdbq->cdb);
147 	if (len < vlen) {
148 	    if (buf == 0)
149 		buf = mymalloc(vlen + 1);
150 	    else
151 		buf = myrealloc(buf, vlen + 1);
152 	    len = vlen;
153 	}
154 	if (cdb_read(&dict_cdbq->cdb, buf, vlen,
155 		     cdb_datapos(&dict_cdbq->cdb)) < 0)
156 	    msg_fatal("error reading %s: %m", dict->name);
157 	buf[vlen] = '\0';
158 	result = buf;
159     }
160     /* No locking so not release the lock.  */
161 
162     return (result);
163 }
164 
165 /* dict_cdbq_close - close data base, query mode */
166 
dict_cdbq_close(DICT * dict)167 static void dict_cdbq_close(DICT *dict)
168 {
169     DICT_CDBQ *dict_cdbq = (DICT_CDBQ *) dict;
170 
171     cdb_free(&dict_cdbq->cdb);
172     close(dict->stat_fd);
173     if (dict->fold_buf)
174 	vstring_free(dict->fold_buf);
175     dict_free(dict);
176 }
177 
178 /* dict_cdbq_open - open data base, query mode */
179 
dict_cdbq_open(const char * path,int dict_flags)180 static DICT *dict_cdbq_open(const char *path, int dict_flags)
181 {
182     DICT_CDBQ *dict_cdbq;
183     struct stat st;
184     char   *cdb_path;
185     int     fd;
186 
187     /*
188      * Let the optimizer worry about eliminating redundant code.
189      */
190 #define DICT_CDBQ_OPEN_RETURN(d) do { \
191 	DICT *__d = (d); \
192 	myfree(cdb_path); \
193 	return (__d); \
194     } while (0)
195 
196     cdb_path = concatenate(path, CDB_SUFFIX, (char *) 0);
197 
198     if ((fd = open(cdb_path, O_RDONLY)) < 0)
199 	DICT_CDBQ_OPEN_RETURN(dict_surrogate(DICT_TYPE_CDB, path,
200 					     O_RDONLY, dict_flags,
201 					 "open database %s: %m", cdb_path));
202 
203     dict_cdbq = (DICT_CDBQ *) dict_alloc(DICT_TYPE_CDB,
204 					 cdb_path, sizeof(*dict_cdbq));
205 #if defined(TINYCDB_VERSION)
206     if (cdb_init(&(dict_cdbq->cdb), fd) != 0)
207 	msg_fatal("dict_cdbq_open: unable to init %s: %m", cdb_path);
208 #else
209     cdb_init(&(dict_cdbq->cdb), fd);
210 #endif
211     dict_cdbq->dict.lookup = dict_cdbq_lookup;
212     dict_cdbq->dict.close = dict_cdbq_close;
213     dict_cdbq->dict.stat_fd = fd;
214     if (fstat(fd, &st) < 0)
215 	msg_fatal("dict_dbq_open: fstat: %m");
216     dict_cdbq->dict.mtime = st.st_mtime;
217     dict_cdbq->dict.owner.uid = st.st_uid;
218     dict_cdbq->dict.owner.status = (st.st_uid != 0);
219     close_on_exec(fd, CLOSE_ON_EXEC);
220 
221     /*
222      * Warn if the source file is newer than the indexed file, except when
223      * the source file changed only seconds ago.
224      */
225     if (stat(path, &st) == 0
226 	&& st.st_mtime > dict_cdbq->dict.mtime
227 	&& st.st_mtime < time((time_t *) 0) - 100)
228 	msg_warn("database %s is older than source file %s", cdb_path, path);
229 
230     /*
231      * If undecided about appending a null byte to key and value, choose to
232      * try both in query mode.
233      */
234     if ((dict_flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
235 	dict_flags |= DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL;
236     dict_cdbq->dict.flags = dict_flags | DICT_FLAG_FIXED;
237     if (dict_flags & DICT_FLAG_FOLD_FIX)
238 	dict_cdbq->dict.fold_buf = vstring_alloc(10);
239 
240     DICT_CDBQ_OPEN_RETURN(DICT_DEBUG (&dict_cdbq->dict));
241 }
242 
243 /* dict_cdbm_update - add database entry, create mode */
244 
dict_cdbm_update(DICT * dict,const char * name,const char * value)245 static int dict_cdbm_update(DICT *dict, const char *name, const char *value)
246 {
247     DICT_CDBM *dict_cdbm = (DICT_CDBM *) dict;
248     unsigned ksize, vsize;
249     int     r;
250 
251     dict->error = 0;
252 
253     /*
254      * Optionally fold the key.
255      */
256     if (dict->flags & DICT_FLAG_FOLD_FIX) {
257 	if (dict->fold_buf == 0)
258 	    dict->fold_buf = vstring_alloc(10);
259 	vstring_strcpy(dict->fold_buf, name);
260 	name = lowercase(vstring_str(dict->fold_buf));
261     }
262     ksize = strlen(name);
263     vsize = strlen(value);
264 
265     /*
266      * Optionally append a null byte to key and value.
267      */
268     if (dict->flags & DICT_FLAG_TRY1NULL) {
269 	ksize++;
270 	vsize++;
271     }
272 
273     /*
274      * Do the add operation.  No locking is done.
275      */
276 #ifdef TINYCDB_VERSION
277 #ifndef CDB_PUT_ADD
278 #error please upgrate tinycdb to at least 0.5 version
279 #endif
280     if (dict->flags & DICT_FLAG_DUP_IGNORE)
281 	r = CDB_PUT_ADD;
282     else if (dict->flags & DICT_FLAG_DUP_REPLACE)
283 	r = CDB_PUT_REPLACE;
284     else
285 	r = CDB_PUT_INSERT;
286     r = cdb_make_put(&dict_cdbm->cdbm, name, ksize, value, vsize, r);
287     if (r < 0)
288 	msg_fatal("error writing %s: %m", dict_cdbm->tmp_path);
289     else if (r > 0) {
290 	if (dict->flags & (DICT_FLAG_DUP_IGNORE | DICT_FLAG_DUP_REPLACE))
291 	     /* void */ ;
292 	else if (dict->flags & DICT_FLAG_DUP_WARN)
293 	    msg_warn("%s: duplicate entry: \"%s\"",
294 		     dict_cdbm->dict.name, name);
295 	else
296 	    msg_fatal("%s: duplicate entry: \"%s\"",
297 		      dict_cdbm->dict.name, name);
298     }
299     return (r);
300 #else
301     if (cdb_make_add(&dict_cdbm->cdbm, name, ksize, value, vsize) < 0)
302 	msg_fatal("error writing %s: %m", dict_cdbm->tmp_path);
303     return (0);
304 #endif
305 }
306 
307 /* dict_cdbm_close - close data base and rename file.tmp to file.cdb */
308 
dict_cdbm_close(DICT * dict)309 static void dict_cdbm_close(DICT *dict)
310 {
311     DICT_CDBM *dict_cdbm = (DICT_CDBM *) dict;
312     int     fd = cdb_fileno(&dict_cdbm->cdbm);
313 
314     /*
315      * Note: if FCNTL locking is used, closing any file descriptor on a
316      * locked file cancels all locks that the process may have on that file.
317      * CDB is FCNTL locking safe, because it uses the same file descriptor
318      * for database I/O and locking.
319      */
320     if (cdb_make_finish(&dict_cdbm->cdbm) < 0)
321 	msg_fatal("finish database %s: %m", dict_cdbm->tmp_path);
322     if (rename(dict_cdbm->tmp_path, dict_cdbm->cdb_path) < 0)
323 	msg_fatal("rename database from %s to %s: %m",
324 		  dict_cdbm->tmp_path, dict_cdbm->cdb_path);
325     if (close(fd) < 0)				/* releases a lock */
326 	msg_fatal("close database %s: %m", dict_cdbm->cdb_path);
327     myfree(dict_cdbm->cdb_path);
328     myfree(dict_cdbm->tmp_path);
329     if (dict->fold_buf)
330 	vstring_free(dict->fold_buf);
331     dict_free(dict);
332 }
333 
334 /* dict_cdbm_open - create database as file.tmp */
335 
dict_cdbm_open(const char * path,int dict_flags)336 static DICT *dict_cdbm_open(const char *path, int dict_flags)
337 {
338     DICT_CDBM *dict_cdbm;
339     char   *cdb_path;
340     char   *tmp_path;
341     int     fd;
342     struct stat st0, st1;
343 
344     /*
345      * Let the optimizer worry about eliminating redundant code.
346      */
347 #define DICT_CDBM_OPEN_RETURN(d) do { \
348 	DICT *__d = (d); \
349 	if (cdb_path) \
350 	    myfree(cdb_path); \
351 	if (tmp_path) \
352 	    myfree(tmp_path); \
353 	return (__d); \
354     } while (0)
355 
356     cdb_path = concatenate(path, CDB_SUFFIX, (char *) 0);
357     tmp_path = concatenate(path, CDB_TMP_SUFFIX, (char *) 0);
358 
359     /*
360      * Repeat until we have opened *and* locked *existing* file. Since the
361      * new (tmp) file will be renamed to be .cdb file, locking here is
362      * somewhat funny to work around possible race conditions.  Note that we
363      * can't open a file with O_TRUNC as we can't know if another process
364      * isn't creating it at the same time.
365      */
366     for (;;) {
367 	if ((fd = open(tmp_path, O_RDWR | O_CREAT, 0644)) < 0)
368 	    DICT_CDBM_OPEN_RETURN(dict_surrogate(DICT_TYPE_CDB, path,
369 						 O_RDWR, dict_flags,
370 						 "open database %s: %m",
371 						 tmp_path));
372 	if (fstat(fd, &st0) < 0)
373 	    msg_fatal("fstat(%s): %m", tmp_path);
374 
375 	/*
376 	 * Get an exclusive lock - we're going to change the database so we
377 	 * can't have any spectators.
378 	 */
379 	if (myflock(fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
380 	    msg_fatal("lock %s: %m", tmp_path);
381 
382 	if (stat(tmp_path, &st1) < 0)
383 	    msg_fatal("stat(%s): %m", tmp_path);
384 
385 	/*
386 	 * Compare file's state before and after lock: should be the same,
387 	 * and nlinks should be >0, or else we opened non-existing file...
388 	 */
389 	if (st0.st_ino == st1.st_ino && st0.st_dev == st1.st_dev
390 	    && st0.st_rdev == st1.st_rdev && st0.st_nlink == st1.st_nlink
391 	    && st0.st_nlink > 0)
392 	    break;				/* successfully opened */
393 
394 	close(fd);
395 
396     }
397 
398 #ifndef NO_FTRUNCATE
399     if (st0.st_size)
400 	ftruncate(fd, 0);
401 #endif
402 
403     dict_cdbm = (DICT_CDBM *) dict_alloc(DICT_TYPE_CDB, path,
404 					 sizeof(*dict_cdbm));
405     if (cdb_make_start(&dict_cdbm->cdbm, fd) < 0)
406 	msg_fatal("initialize database %s: %m", tmp_path);
407     dict_cdbm->dict.close = dict_cdbm_close;
408     dict_cdbm->dict.update = dict_cdbm_update;
409     dict_cdbm->cdb_path = cdb_path;
410     dict_cdbm->tmp_path = tmp_path;
411     cdb_path = tmp_path = 0;			/* DICT_CDBM_OPEN_RETURN() */
412     dict_cdbm->dict.owner.uid = st1.st_uid;
413     dict_cdbm->dict.owner.status = (st1.st_uid != 0);
414     close_on_exec(fd, CLOSE_ON_EXEC);
415 
416     /*
417      * If undecided about appending a null byte to key and value, choose a
418      * default to not append a null byte when creating a cdb.
419      */
420     if ((dict_flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
421 	dict_flags |= DICT_FLAG_TRY0NULL;
422     else if ((dict_flags & DICT_FLAG_TRY1NULL)
423 	     && (dict_flags & DICT_FLAG_TRY0NULL))
424 	dict_flags &= ~DICT_FLAG_TRY0NULL;
425     dict_cdbm->dict.flags = dict_flags | DICT_FLAG_FIXED;
426     if (dict_flags & DICT_FLAG_FOLD_FIX)
427 	dict_cdbm->dict.fold_buf = vstring_alloc(10);
428 
429     DICT_CDBM_OPEN_RETURN(DICT_DEBUG (&dict_cdbm->dict));
430 }
431 
432 /* dict_cdb_open - open data base for query mode or create mode */
433 
dict_cdb_open(const char * path,int open_flags,int dict_flags)434 DICT   *dict_cdb_open(const char *path, int open_flags, int dict_flags)
435 {
436     switch (open_flags & (O_RDONLY | O_RDWR | O_WRONLY | O_CREAT | O_TRUNC)) {
437 	case O_RDONLY:			/* query mode */
438 	return dict_cdbq_open(path, dict_flags);
439     case O_WRONLY | O_CREAT | O_TRUNC:		/* create mode */
440     case O_RDWR | O_CREAT | O_TRUNC:		/* sloppiness */
441 	return dict_cdbm_open(path, dict_flags);
442     default:
443 	msg_fatal("dict_cdb_open: inappropriate open flags for cdb database"
444 		  " - specify O_RDONLY or O_WRONLY|O_CREAT|O_TRUNC");
445     }
446 }
447 
448 #endif					/* HAS_CDB */
449