Check return from asprintf()
[exim.git] / src / src / exim_dbutil.c
CommitLineData
059ec3d9
PH
1/*************************************************
2* Exim - an Internet mail transport agent *
3*************************************************/
4
f9ba5e22 5/* Copyright (c) University of Cambridge 1995 - 2018 */
059ec3d9
PH
6/* See the file NOTICE for conditions of use and distribution. */
7
8
9/* This single source file is used to compile three utility programs for
10maintaining Exim hints databases.
11
12 exim_dumpdb dumps out the contents
13 exim_fixdb patches the database (really for Exim maintenance/testing)
14 exim_tidydb removed obsolete data
15
16In all cases, the first argument is the name of the spool directory. The second
17argument is the name of the database file. The available names are:
18
19 retry: retry delivery information
20 misc: miscellaneous hints data
21 wait-<t>: message waiting information; <t> is a transport name
22 callout: callout verification cache
23
24There are a number of common subroutines, followed by three main programs,
25whose inclusion is controlled by -D on the compilation command. */
26
27
c99ce5c9 28#include "exim.h"
059ec3d9
PH
29
30
31/* Identifiers for the different database types. */
32
870f6ba8
TF
33#define type_retry 1
34#define type_wait 2
35#define type_misc 3
36#define type_callout 4
37#define type_ratelimit 5
059ec3d9
PH
38
39
c99ce5c9
TF
40/* This is used by our cut-down dbfn_open(). */
41
42uschar *spool_directory;
43
059ec3d9
PH
44
45
46/*************************************************
47* Berkeley DB error callback *
48*************************************************/
49
50/* For Berkeley DB >= 2, we can define a function to be called in case of DB
51errors. This should help with debugging strange DB problems, e.g. getting "File
1f922db1 52exists" when you try to open a db file. The API changed at release 4.3. */
059ec3d9
PH
53
54#if defined(USE_DB) && defined(DB_VERSION_STRING)
55void
1f922db1
PH
56#if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
57dbfn_bdb_error_callback(const DB_ENV *dbenv, const char *pfx, const char *msg)
58{
59dbenv = dbenv;
60#else
059ec3d9
PH
61dbfn_bdb_error_callback(const char *pfx, char *msg)
62{
1f922db1 63#endif
059ec3d9
PH
64pfx = pfx;
65printf("Berkeley DB error: %s\n", msg);
66}
67#endif
68
69
70
71/*************************************************
72* SIGALRM handler *
73*************************************************/
74
c99ce5c9 75SIGNAL_BOOL sigalrm_seen;
059ec3d9
PH
76
77void
78sigalrm_handler(int sig)
79{
80sig = sig; /* Keep picky compilers happy */
81sigalrm_seen = 1;
82}
83
84
85
86/*************************************************
87* Output usage message and exit *
88*************************************************/
89
90static void
91usage(uschar *name, uschar *options)
92{
93printf("Usage: exim_%s%s <spool-directory> <database-name>\n", name, options);
870f6ba8 94printf(" <database-name> = retry | misc | wait-<transport-name> | callout | ratelimit\n");
059ec3d9
PH
95exit(1);
96}
97
98
99
100/*************************************************
101* Sort out the command arguments *
102*************************************************/
103
104/* This function checks that there are exactly 2 arguments, and checks the
105second of them to be sure it is a known database name. */
106
107static int
108check_args(int argc, uschar **argv, uschar *name, uschar *options)
109{
110if (argc == 3)
111 {
112 if (Ustrcmp(argv[2], "retry") == 0) return type_retry;
113 if (Ustrcmp(argv[2], "misc") == 0) return type_misc;
114 if (Ustrncmp(argv[2], "wait-", 5) == 0) return type_wait;
115 if (Ustrcmp(argv[2], "callout") == 0) return type_callout;
870f6ba8 116 if (Ustrcmp(argv[2], "ratelimit") == 0) return type_ratelimit;
059ec3d9
PH
117 }
118usage(name, options);
119return -1; /* Never obeyed */
120}
121
122
123
124/*************************************************
125* Handle attempts to write the log *
126*************************************************/
127
128/* The message gets written to stderr when log_write() is called from a
129utility. The message always gets '\n' added on the end of it. These calls come
130from modules such as store.c when things go drastically wrong (e.g. malloc()
131failing). In normal use they won't get obeyed.
132
133Arguments:
134 selector not relevant when running a utility
135 flags not relevant when running a utility
136 format a printf() format
137 ... arguments for format
138
139Returns: nothing
140*/
141
142void
c99ce5c9 143log_write(unsigned int selector, int flags, const char *format, ...)
059ec3d9
PH
144{
145va_list ap;
146va_start(ap, format);
147vfprintf(stderr, format, ap);
148fprintf(stderr, "\n");
149va_end(ap);
150selector = selector; /* Keep picky compilers happy */
151flags = flags;
152}
153
154
155
156/*************************************************
157* Format a time value for printing *
158*************************************************/
159
160static uschar time_buffer[sizeof("09-xxx-1999 hh:mm:ss ")];
161
162uschar *
163print_time(time_t t)
164{
165struct tm *tmstr = localtime(&t);
166Ustrftime(time_buffer, sizeof(time_buffer), "%d-%b-%Y %H:%M:%S", tmstr);
167return time_buffer;
168}
169
170
171
172/*************************************************
173* Format a cache value for printing *
174*************************************************/
175
176uschar *
177print_cache(int value)
178{
179return (value == ccache_accept)? US"accept" :
180 (value == ccache_reject)? US"reject" :
181 US"unknown";
182}
183
184
185#ifdef EXIM_FIXDB
186/*************************************************
187* Read time value *
188*************************************************/
189
190static time_t
191read_time(uschar *s)
192{
193uschar *t = s;
194int field = 0;
195int value;
196time_t now = time(NULL);
197struct tm *tm = localtime(&now);
198
199tm->tm_sec = 0;
200tm->tm_isdst = -1;
201
202for (t = s + Ustrlen(s) - 1; t >= s; t--)
203 {
204 if (*t == ':') continue;
205 if (!isdigit((uschar)*t)) return -1;
206
207 value = *t - '0';
208 if (--t >= s)
209 {
210 if (!isdigit((uschar)*t)) return -1;
211 value = value + (*t - '0')*10;
212 }
213
214 switch (field++)
215 {
216 case 0: tm->tm_min = value; break;
217 case 1: tm->tm_hour = value; break;
218 case 2: tm->tm_mday = value; break;
219 case 3: tm->tm_mon = value - 1; break;
220 case 4: tm->tm_year = (value < 90)? value + 100 : value; break;
221 default: return -1;
222 }
223 }
224
225return mktime(tm);
226}
227#endif /* EXIM_FIXDB */
228
229
230
231/*************************************************
232* Open and lock a database file *
233*************************************************/
234
235/* This is a cut-down version from the function in dbfn.h that Exim itself
236uses. We assume the database exists, and therefore give up if we cannot open
237the lock file.
238
239Arguments:
059ec3d9
PH
240 name The single-component name of one of Exim's database files.
241 flags O_RDONLY or O_RDWR
242 dbblock Points to an open_db block to be filled in.
c99ce5c9 243 lof Unused.
059ec3d9
PH
244
245Returns: NULL if the open failed, or the locking failed.
246 On success, dbblock is returned. This contains the dbm pointer and
247 the fd of the locked lock file.
248*/
249
c99ce5c9
TF
250open_db *
251dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof)
059ec3d9
PH
252{
253int rc;
254struct flock lock_data;
255BOOL read_only = flags == O_RDONLY;
ebda598a 256uschar * dirname, * filename;
059ec3d9
PH
257
258/* The first thing to do is to open a separate file on which to lock. This
259ensures that Exim has exclusive use of the database before it even tries to
260open it. If there is a database, there should be a lock file in existence. */
261
ebda598a 262#ifdef COMPILE_UTILITY
5d520e57
JH
263if ( asprintf(CSS &dirname, "%s/db", spool_directory) < 0
264 || asprintf(CSS &filename, "%s/%s.lockfile", dirname, name) < 0)
265 return NULL;
ebda598a
JH
266#else
267dirname = string_sprintf("%s/db", spool_directory);
268filename = string_sprintf("%s/%s.lockfile", dirname, name);
269#endif
059ec3d9 270
cfb9cf20 271dbblock->lockfd = Uopen(filename, flags, 0);
059ec3d9
PH
272if (dbblock->lockfd < 0)
273 {
cfb9cf20 274 printf("** Failed to open database lock file %s: %s\n", filename,
059ec3d9
PH
275 strerror(errno));
276 return NULL;
277 }
278
279/* Now we must get a lock on the opened lock file; do this with a blocking
280lock that times out. */
281
ebda598a 282lock_data.l_type = read_only ? F_RDLCK : F_WRLCK;
059ec3d9
PH
283lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
284
285sigalrm_seen = FALSE;
286os_non_restarting_signal(SIGALRM, sigalrm_handler);
287alarm(EXIMDB_LOCK_TIMEOUT);
288rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
289alarm(0);
290
291if (sigalrm_seen) errno = ETIMEDOUT;
292if (rc < 0)
293 {
294 printf("** Failed to get %s lock for %s: %s",
d4ff61d1 295 flags & O_WRONLY ? "write" : "read",
cfb9cf20 296 filename,
d4ff61d1 297 errno == ETIMEDOUT ? "timed out" : strerror(errno));
f1e894f3 298 (void)close(dbblock->lockfd);
059ec3d9
PH
299 return NULL;
300 }
301
302/* At this point we have an opened and locked separate lock file, that is,
303exclusive access to the database, so we can go ahead and open it. */
304
ebda598a 305#ifdef COMPILE_UTILITY
5d520e57 306if (asprintf(CSS &filename, "%s/%s", dirname, name) < 0) return NULL;
ebda598a
JH
307#else
308filename = string_sprintf("%s/%s", dirname, name);
309#endif
cfb9cf20 310EXIM_DBOPEN(filename, dirname, flags, 0, &(dbblock->dbptr));
059ec3d9 311
13c7874e 312if (!dbblock->dbptr)
059ec3d9 313 {
cfb9cf20 314 printf("** Failed to open DBM file %s for %s:\n %s%s\n", filename,
059ec3d9
PH
315 read_only? "reading" : "writing", strerror(errno),
316 #ifdef USE_DB
317 " (or Berkeley DB error while opening)"
318 #else
319 ""
320 #endif
321 );
f1e894f3 322 (void)close(dbblock->lockfd);
059ec3d9
PH
323 return NULL;
324 }
325
326return dbblock;
327}
328
329
330
331
332/*************************************************
333* Unlock and close a database file *
334*************************************************/
335
336/* Closing a file automatically unlocks it, so after closing the database, just
337close the lock file.
338
339Argument: a pointer to an open database block
340Returns: nothing
341*/
342
c99ce5c9 343void
059ec3d9
PH
344dbfn_close(open_db *dbblock)
345{
346EXIM_DBCLOSE(dbblock->dbptr);
f1e894f3 347(void)close(dbblock->lockfd);
059ec3d9
PH
348}
349
350
351
352
353/*************************************************
354* Read from database file *
355*************************************************/
356
357/* Passing back the pointer unchanged is useless, because there is no guarantee
358of alignment. Since all the records used by Exim need to be properly aligned to
359pick out the timestamps, etc., do the copying centrally here.
360
361Arguments:
362 dbblock a pointer to an open database block
363 key the key of the record to be read
364 length where to put the length (or NULL if length not wanted)
365
366Returns: a pointer to the retrieved record, or
367 NULL if the record is not found
368*/
369
c99ce5c9 370void *
55414b25 371dbfn_read_with_length(open_db *dbblock, const uschar *key, int *length)
059ec3d9
PH
372{
373void *yield;
374EXIM_DATUM key_datum, result_datum;
55414b25
JH
375int klen = Ustrlen(key) + 1;
376uschar * key_copy = store_get(klen);
377
378memcpy(key_copy, key, klen);
059ec3d9
PH
379
380EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
381EXIM_DATUM_INIT(result_datum); /* to be cleared before use. */
55414b25
JH
382EXIM_DATUM_DATA(key_datum) = CS key_copy;
383EXIM_DATUM_SIZE(key_datum) = klen;
059ec3d9
PH
384
385if (!EXIM_DBGET(dbblock->dbptr, key_datum, result_datum)) return NULL;
386
387yield = store_get(EXIM_DATUM_SIZE(result_datum));
388memcpy(yield, EXIM_DATUM_DATA(result_datum), EXIM_DATUM_SIZE(result_datum));
389if (length != NULL) *length = EXIM_DATUM_SIZE(result_datum);
390
391EXIM_DATUM_FREE(result_datum); /* Some DBM libs require freeing */
392return yield;
393}
394
395
396
397#if defined(EXIM_TIDYDB) || defined(EXIM_FIXDB)
398
399/*************************************************
400* Write to database file *
401*************************************************/
402
403/*
404Arguments:
405 dbblock a pointer to an open database block
406 key the key of the record to be written
407 ptr a pointer to the record to be written
408 length the length of the record to be written
409
410Returns: the yield of the underlying dbm or db "write" function. If this
411 is dbm, the value is zero for OK.
412*/
413
c99ce5c9 414int
55414b25 415dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length)
059ec3d9
PH
416{
417EXIM_DATUM key_datum, value_datum;
418dbdata_generic *gptr = (dbdata_generic *)ptr;
55414b25
JH
419int klen = Ustrlen(key) + 1;
420uschar * key_copy = store_get(klen);
421
422memcpy(key_copy, key, klen);
059ec3d9
PH
423gptr->time_stamp = time(NULL);
424
425EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
426EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
55414b25
JH
427EXIM_DATUM_DATA(key_datum) = CS key_copy;
428EXIM_DATUM_SIZE(key_datum) = klen;
059ec3d9
PH
429EXIM_DATUM_DATA(value_datum) = CS ptr;
430EXIM_DATUM_SIZE(value_datum) = length;
431return EXIM_DBPUT(dbblock->dbptr, key_datum, value_datum);
432}
433
434
435
436/*************************************************
437* Delete record from database file *
438*************************************************/
439
440/*
441Arguments:
442 dbblock a pointer to an open database block
443 key the key of the record to be deleted
444
445Returns: the yield of the underlying dbm or db "delete" function.
446*/
447
c99ce5c9 448int
55414b25 449dbfn_delete(open_db *dbblock, const uschar *key)
059ec3d9 450{
55414b25
JH
451int klen = Ustrlen(key) + 1;
452uschar * key_copy = store_get(klen);
453
454memcpy(key_copy, key, klen);
059ec3d9
PH
455EXIM_DATUM key_datum;
456EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require clearing */
55414b25
JH
457EXIM_DATUM_DATA(key_datum) = CS key_copy;
458EXIM_DATUM_SIZE(key_datum) = klen;
059ec3d9
PH
459return EXIM_DBDEL(dbblock->dbptr, key_datum);
460}
461
462#endif /* EXIM_TIDYDB || EXIM_FIXDB */
463
464
465
466#if defined(EXIM_DUMPDB) || defined(EXIM_TIDYDB)
467/*************************************************
468* Scan the keys of a database file *
469*************************************************/
470
471/*
472Arguments:
473 dbblock a pointer to an open database block
474 start TRUE if starting a new scan
475 FALSE if continuing with the current scan
476 cursor a pointer to a pointer to a cursor anchor, for those dbm libraries
477 that use the notion of a cursor
478
479Returns: the next record from the file, or
480 NULL if there are no more
481*/
482
c99ce5c9 483uschar *
059ec3d9
PH
484dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
485{
486EXIM_DATUM key_datum, value_datum;
487uschar *yield;
488value_datum = value_datum; /* dummy; not all db libraries use this */
489
490/* Some dbm require an initialization */
491
492if (start) EXIM_DBCREATE_CURSOR(dbblock->dbptr, cursor);
493
494EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
495EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
496
497yield = (EXIM_DBSCAN(dbblock->dbptr, key_datum, value_datum, start, *cursor))?
498 US EXIM_DATUM_DATA(key_datum) : NULL;
499
500/* Some dbm require a termination */
501
502if (!yield) EXIM_DBDELETE_CURSOR(*cursor);
503return yield;
504}
505#endif /* EXIM_DUMPDB || EXIM_TIDYDB */
506
507
508
509#ifdef EXIM_DUMPDB
510/*************************************************
511* The exim_dumpdb main program *
512*************************************************/
513
514int
515main(int argc, char **cargv)
516{
517int dbdata_type = 0;
518int yield = 0;
519open_db dbblock;
520open_db *dbm;
521EXIM_CURSOR *cursor;
522uschar **argv = USS cargv;
523uschar *key;
524uschar keybuffer[1024];
525
526/* Check the arguments, and open the database */
527
528dbdata_type = check_args(argc, argv, US"dumpdb", US"");
c99ce5c9 529spool_directory = argv[1];
0a6c178c
JH
530if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE)))
531 exit(1);
059ec3d9
PH
532
533/* Scan the file, formatting the information for each entry. Note
534that data is returned in a malloc'ed block, in order that it be
535correctly aligned. */
536
0a6c178c
JH
537for (key = dbfn_scan(dbm, TRUE, &cursor);
538 key;
539 key = dbfn_scan(dbm, FALSE, &cursor))
059ec3d9
PH
540 {
541 dbdata_retry *retry;
542 dbdata_wait *wait;
543 dbdata_callout_cache *callout;
870f6ba8 544 dbdata_ratelimit *ratelimit;
c99ce5c9 545 dbdata_ratelimit_unique *rate_unique;
059ec3d9
PH
546 int count_bad = 0;
547 int i, length;
548 uschar *t;
549 uschar name[MESSAGE_ID_LENGTH + 1];
550 void *value;
551
552 /* Keep a copy of the key separate, as in some DBM's the pointer is into data
553 which might change. */
554
555 if (Ustrlen(key) > sizeof(keybuffer) - 1)
556 {
557 printf("**** Overlong key encountered: %s\n", key);
558 return 1;
559 }
560 Ustrcpy(keybuffer, key);
059ec3d9 561
0a6c178c 562 if (!(value = dbfn_read_with_length(dbm, keybuffer, &length)))
059ec3d9
PH
563 fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
564 "was not found in the file - something is wrong!\n",
565 CS keybuffer);
566 else
567 {
568 /* Note: don't use print_time more than once in one statement, since
569 it uses a single buffer. */
570
571 switch(dbdata_type)
572 {
573 case type_retry:
574 retry = (dbdata_retry *)value;
575 printf(" %s %d %d %s\n%s ", keybuffer, retry->basic_errno,
576 retry->more_errno, retry->text,
577 print_time(retry->first_failed));
578 printf("%s ", print_time(retry->last_try));
579 printf("%s %s\n", print_time(retry->next_try),
580 (retry->expired)? "*" : "");
581 break;
582
583 case type_wait:
584 wait = (dbdata_wait *)value;
585 printf("%s ", keybuffer);
586 t = wait->text;
587 name[MESSAGE_ID_LENGTH] = 0;
588
589 if (wait->count > WAIT_NAME_MAX)
590 {
591 fprintf(stderr,
592 "**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
593 CS keybuffer, wait->count, wait->count, WAIT_NAME_MAX);
594 wait->count = WAIT_NAME_MAX;
595 yield = count_bad = 1;
596 }
597 for (i = 1; i <= wait->count; i++)
598 {
599 Ustrncpy(name, t, MESSAGE_ID_LENGTH);
600 if (count_bad && name[0] == 0) break;
601 if (Ustrlen(name) != MESSAGE_ID_LENGTH ||
602 Ustrspn(name, "0123456789"
603 "abcdefghijklmnopqrstuvwxyz"
604 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
605 {
606 int j;
607 fprintf(stderr,
608 "**** Data for %s corrupted: bad character in message id\n",
609 CS keybuffer);
610 for (j = 0; j < MESSAGE_ID_LENGTH; j++)
611 fprintf(stderr, "%02x ", name[j]);
612 fprintf(stderr, "\n");
613 yield = 1;
614 break;
615 }
616 printf("%s ", name);
617 t += MESSAGE_ID_LENGTH;
618 }
619 printf("\n");
620 break;
621
622 case type_misc:
623 printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
624 keybuffer);
625 break;
626
627 case type_callout:
628 callout = (dbdata_callout_cache *)value;
629
630 /* New-style address record */
631
632 if (length == sizeof(dbdata_callout_cache_address))
633 {
634 printf("%s %s callout=%s\n",
635 print_time(((dbdata_generic *)value)->time_stamp),
636 keybuffer,
637 print_cache(callout->result));
638 }
639
640 /* New-style domain record */
641
642 else if (length == sizeof(dbdata_callout_cache))
643 {
644 printf("%s %s callout=%s postmaster=%s",
645 print_time(((dbdata_generic *)value)->time_stamp),
646 keybuffer,
647 print_cache(callout->result),
648 print_cache(callout->postmaster_result));
649 if (callout->postmaster_result != ccache_unknown)
650 printf(" (%s)", print_time(callout->postmaster_stamp));
651 printf(" random=%s", print_cache(callout->random_result));
652 if (callout->random_result != ccache_unknown)
653 printf(" (%s)", print_time(callout->random_stamp));
654 printf("\n");
655 }
656
059ec3d9 657 break;
870f6ba8
TF
658
659 case type_ratelimit:
c99ce5c9
TF
660 if (Ustrstr(key, "/unique/") != NULL && length >= sizeof(*rate_unique))
661 {
662 ratelimit = (dbdata_ratelimit *)value;
663 rate_unique = (dbdata_ratelimit_unique *)value;
664 printf("%s.%06d rate: %10.3f epoch: %s size: %u key: %s\n",
665 print_time(ratelimit->time_stamp),
666 ratelimit->time_usec, ratelimit->rate,
667 print_time(rate_unique->bloom_epoch), rate_unique->bloom_size,
668 keybuffer);
669 }
670 else
671 {
672 ratelimit = (dbdata_ratelimit *)value;
673 printf("%s.%06d rate: %10.3f key: %s\n",
674 print_time(ratelimit->time_stamp),
675 ratelimit->time_usec, ratelimit->rate,
676 keybuffer);
677 }
870f6ba8 678 break;
059ec3d9
PH
679 }
680 store_reset(value);
681 }
059ec3d9
PH
682 }
683
684dbfn_close(dbm);
685return yield;
686}
687
688#endif /* EXIM_DUMPDB */
689
690
691
692
693#ifdef EXIM_FIXDB
694/*************************************************
695* The exim_fixdb main program *
696*************************************************/
697
698/* In order not to hold the database lock any longer than is necessary, each
699operation on the database uses a separate open/close call. This is expensive,
700but then using this utility is not expected to be very common. Its main use is
701to provide a way of patching up hints databases in order to run tests.
702
703Syntax of commands:
704
705(1) <record name>
706 This causes the data from the given record to be displayed, or "not found"
707 to be output. Note that in the retry database, destination names are
708 preceded by R: or T: for router or transport retry info.
709
710(2) <record name> d
711 This causes the given record to be deleted or "not found" to be output.
712
713(3) <record name> <field number> <value>
714 This sets the given value into the given field, identified by a number
715 which is output by the display command. Not all types of record can
716 be changed.
717
718(4) q
719 This exits from exim_fixdb.
720
721If the record name is omitted from (2) or (3), the previously used record name
722is re-used. */
723
724
725int main(int argc, char **cargv)
726{
727int dbdata_type;
728uschar **argv = USS cargv;
729uschar buffer[256];
730uschar name[256];
731void *reset_point = store_get(0);
732
733name[0] = 0; /* No name set */
734
735/* Sort out the database type, verify what we are working on and then process
736user requests */
737
738dbdata_type = check_args(argc, argv, US"fixdb", US"");
739printf("Modifying Exim hints database %s/db/%s\n", argv[1], argv[2]);
740
741for(;;)
742 {
743 open_db dbblock;
744 open_db *dbm;
745 void *record;
746 dbdata_retry *retry;
747 dbdata_wait *wait;
748 dbdata_callout_cache *callout;
870f6ba8 749 dbdata_ratelimit *ratelimit;
c99ce5c9 750 dbdata_ratelimit_unique *rate_unique;
059ec3d9
PH
751 int i, oldlength;
752 uschar *t;
753 uschar field[256], value[256];
754
755 store_reset(reset_point);
756
757 printf("> ");
758 if (Ufgets(buffer, 256, stdin) == NULL) break;
759
760 buffer[Ustrlen(buffer)-1] = 0;
761 field[0] = value[0] = 0;
762
763 /* If the buffer contains just one digit, or just consists of "d", use the
764 previous name for an update. */
765
2695e54e 766 if ((isdigit((uschar)buffer[0]) && (buffer[1] == ' ' || buffer[1] == '\0'))
2e88a017 767 || Ustrcmp(buffer, "d") == 0)
059ec3d9
PH
768 {
769 if (name[0] == 0)
770 {
771 printf("No previous record name is set\n");
772 continue;
773 }
ff790e47 774 (void)sscanf(CS buffer, "%s %s", field, value);
059ec3d9
PH
775 }
776 else
777 {
778 name[0] = 0;
ff790e47 779 (void)sscanf(CS buffer, "%s %s %s", name, field, value);
059ec3d9
PH
780 }
781
782 /* Handle an update request */
783
784 if (field[0] != 0)
785 {
786 int verify = 1;
c99ce5c9 787 spool_directory = argv[1];
0a6c178c
JH
788
789 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE)))
790 continue;
059ec3d9
PH
791
792 if (Ustrcmp(field, "d") == 0)
793 {
794 if (value[0] != 0) printf("unexpected value after \"d\"\n");
795 else printf("%s\n", (dbfn_delete(dbm, name) < 0)?
796 "not found" : "deleted");
797 dbfn_close(dbm);
798 continue;
799 }
800
801 else if (isdigit((uschar)field[0]))
802 {
803 int fieldno = Uatoi(field);
804 if (value[0] == 0)
805 {
806 printf("value missing\n");
807 dbfn_close(dbm);
808 continue;
809 }
810 else
811 {
812 record = dbfn_read_with_length(dbm, name, &oldlength);
813 if (record == NULL) printf("not found\n"); else
814 {
815 time_t tt;
4dc2379a 816 /*int length = 0; Stops compiler warning */
059ec3d9
PH
817
818 switch(dbdata_type)
819 {
820 case type_retry:
821 retry = (dbdata_retry *)record;
4dc2379a 822 /* length = sizeof(dbdata_retry) + Ustrlen(retry->text); */
059ec3d9
PH
823
824 switch(fieldno)
825 {
826 case 0:
827 retry->basic_errno = Uatoi(value);
828 break;
829
830 case 1:
831 retry->more_errno = Uatoi(value);
832 break;
833
834 case 2:
835 if ((tt = read_time(value)) > 0) retry->first_failed = tt;
836 else printf("bad time value\n");
837 break;
838
839 case 3:
840 if ((tt = read_time(value)) > 0) retry->last_try = tt;
841 else printf("bad time value\n");
842 break;
843
844 case 4:
845 if ((tt = read_time(value)) > 0) retry->next_try = tt;
846 else printf("bad time value\n");
847 break;
848
849 case 5:
850 if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE;
851 else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE;
852 else printf("\"yes\" or \"no\" expected=n");
853 break;
854
855 default:
856 printf("unknown field number\n");
857 verify = 0;
858 break;
859 }
860 break;
861
862 case type_wait:
863 printf("Can't change contents of wait database record\n");
864 break;
865
866 case type_misc:
867 printf("Can't change contents of misc database record\n");
868 break;
869
870 case type_callout:
871 callout = (dbdata_callout_cache *)record;
4dc2379a 872 /* length = sizeof(dbdata_callout_cache); */
059ec3d9
PH
873 switch(fieldno)
874 {
875 case 0:
876 callout->result = Uatoi(value);
877 break;
878
879 case 1:
880 callout->postmaster_result = Uatoi(value);
881 break;
882
883 case 2:
884 callout->random_result = Uatoi(value);
885 break;
886
887 default:
888 printf("unknown field number\n");
889 verify = 0;
890 break;
891 }
892 break;
870f6ba8
TF
893
894 case type_ratelimit:
2e88a017 895 ratelimit = (dbdata_ratelimit *)record;
870f6ba8
TF
896 switch(fieldno)
897 {
898 case 0:
899 if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt;
900 else printf("bad time value\n");
901 break;
902
903 case 1:
904 ratelimit->time_usec = Uatoi(value);
2e88a017 905 break;
870f6ba8
TF
906
907 case 2:
908 ratelimit->rate = Ustrtod(value, NULL);
909 break;
910
c99ce5c9
TF
911 case 3:
912 if (Ustrstr(name, "/unique/") != NULL
913 && oldlength >= sizeof(dbdata_ratelimit_unique))
914 {
915 rate_unique = (dbdata_ratelimit_unique *)record;
916 if ((tt = read_time(value)) > 0) rate_unique->bloom_epoch = tt;
917 else printf("bad time value\n");
918 break;
919 }
920 /* else fall through */
921
922 case 4:
923 case 5:
924 if (Ustrstr(name, "/unique/") != NULL
925 && oldlength >= sizeof(dbdata_ratelimit_unique))
926 {
927 /* see acl.c */
928 BOOL seen;
929 unsigned n, hash, hinc;
930 uschar md5sum[16];
931 md5 md5info;
932 md5_start(&md5info);
933 md5_end(&md5info, value, Ustrlen(value), md5sum);
934 hash = md5sum[0] << 0 | md5sum[1] << 8
935 | md5sum[2] << 16 | md5sum[3] << 24;
936 hinc = md5sum[4] << 0 | md5sum[5] << 8
937 | md5sum[6] << 16 | md5sum[7] << 24;
938 rate_unique = (dbdata_ratelimit_unique *)record;
939 seen = TRUE;
940 for (n = 0; n < 8; n++, hash += hinc)
941 {
942 int bit = 1 << (hash % 8);
943 int byte = (hash / 8) % rate_unique->bloom_size;
944 if ((rate_unique->bloom[byte] & bit) == 0)
945 {
946 seen = FALSE;
947 if (fieldno == 5) rate_unique->bloom[byte] |= bit;
948 }
949 }
950 printf("%s %s\n",
951 seen ? "seen" : fieldno == 5 ? "added" : "unseen", value);
952 break;
953 }
954 /* else fall through */
955
870f6ba8
TF
956 default:
957 printf("unknown field number\n");
958 verify = 0;
959 break;
960 }
961 break;
059ec3d9
PH
962 }
963
c99ce5c9 964 dbfn_write(dbm, name, record, oldlength);
059ec3d9
PH
965 }
966 }
967 }
968
969 else
970 {
971 printf("field number or d expected\n");
972 verify = 0;
973 }
974
975 dbfn_close(dbm);
976 if (!verify) continue;
977 }
978
979 /* The "name" q causes an exit */
980
981 else if (Ustrcmp(name, "q") == 0) return 0;
982
983 /* Handle a read request, or verify after an update. */
984
c99ce5c9 985 spool_directory = argv[1];
0a6c178c
JH
986 if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE)))
987 continue;
059ec3d9 988
0a6c178c 989 if (!(record = dbfn_read_with_length(dbm, name, &oldlength)))
059ec3d9
PH
990 {
991 printf("record %s not found\n", name);
992 name[0] = 0;
993 }
994 else
995 {
996 int count_bad = 0;
997 printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp));
998 switch(dbdata_type)
999 {
1000 case type_retry:
1001 retry = (dbdata_retry *)record;
1002 printf("0 error number: %d %s\n", retry->basic_errno, retry->text);
1003 printf("1 extra data: %d\n", retry->more_errno);
1004 printf("2 first failed: %s\n", print_time(retry->first_failed));
1005 printf("3 last try: %s\n", print_time(retry->last_try));
1006 printf("4 next try: %s\n", print_time(retry->next_try));
1007 printf("5 expired: %s\n", (retry->expired)? "yes" : "no");
1008 break;
1009
1010 case type_wait:
1011 wait = (dbdata_wait *)record;
1012 t = wait->text;
1013 printf("Sequence: %d\n", wait->sequence);
1014 if (wait->count > WAIT_NAME_MAX)
1015 {
1016 printf("**** Data corrupted: count=%d=0x%x max=%d ****\n", wait->count,
1017 wait->count, WAIT_NAME_MAX);
1018 wait->count = WAIT_NAME_MAX;
1019 count_bad = 1;
1020 }
1021 for (i = 1; i <= wait->count; i++)
1022 {
1023 Ustrncpy(value, t, MESSAGE_ID_LENGTH);
1024 value[MESSAGE_ID_LENGTH] = 0;
1025 if (count_bad && value[0] == 0) break;
1026 if (Ustrlen(value) != MESSAGE_ID_LENGTH ||
1027 Ustrspn(value, "0123456789"
1028 "abcdefghijklmnopqrstuvwxyz"
1029 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
1030 {
1031 int j;
1032 printf("\n**** Data corrupted: bad character in message id ****\n");
1033 for (j = 0; j < MESSAGE_ID_LENGTH; j++)
1034 printf("%02x ", value[j]);
1035 printf("\n");
1036 break;
1037 }
1038 printf("%s ", value);
1039 t += MESSAGE_ID_LENGTH;
1040 }
1041 printf("\n");
1042 break;
1043
1044 case type_misc:
1045 break;
1046
1047 case type_callout:
1048 callout = (dbdata_callout_cache *)record;
1049 printf("0 callout: %s (%d)\n", print_cache(callout->result),
1050 callout->result);
1051 if (oldlength > sizeof(dbdata_callout_cache_address))
1052 {
1053 printf("1 postmaster: %s (%d)\n", print_cache(callout->postmaster_result),
1054 callout->postmaster_result);
1055 printf("2 random: %s (%d)\n", print_cache(callout->random_result),
1056 callout->random_result);
1057 }
1058 break;
870f6ba8
TF
1059
1060 case type_ratelimit:
2e88a017 1061 ratelimit = (dbdata_ratelimit *)record;
870f6ba8
TF
1062 printf("0 time stamp: %s\n", print_time(ratelimit->time_stamp));
1063 printf("1 fract. time: .%06d\n", ratelimit->time_usec);
1064 printf("2 sender rate: % .3f\n", ratelimit->rate);
c99ce5c9
TF
1065 if (Ustrstr(name, "/unique/") != NULL
1066 && oldlength >= sizeof(dbdata_ratelimit_unique))
1067 {
1068 rate_unique = (dbdata_ratelimit_unique *)record;
1069 printf("3 filter epoch: %s\n", print_time(rate_unique->bloom_epoch));
1070 printf("4 test filter membership\n");
1071 printf("5 add element to filter\n");
1072 }
870f6ba8 1073 break;
059ec3d9
PH
1074 }
1075 }
1076
1077 /* The database is closed after each request */
1078
1079 dbfn_close(dbm);
1080 }
1081
1082printf("\n");
1083return 0;
1084}
1085
1086#endif /* EXIM_FIXDB */
1087
1088
1089
1090#ifdef EXIM_TIDYDB
1091/*************************************************
1092* The exim_tidydb main program *
1093*************************************************/
1094
1095
1096/* Utility program to tidy the contents of an exim database file. There is one
1097option:
1098
1099 -t <time> expiry time for old records - default 30 days
1100
1101For backwards compatibility, an -f option is recognized and ignored. (It used
1102to request a "full" tidy. This version always does the whole job.) */
1103
1104
1105typedef struct key_item {
1106 struct key_item *next;
1107 uschar key[1];
1108} key_item;
1109
1110
1111int main(int argc, char **cargv)
1112{
1113struct stat statbuf;
1114int maxkeep = 30 * 24 * 60 * 60;
1115int dbdata_type, i, oldest, path_len;
1116key_item *keychain = NULL;
1117void *reset_point;
1118open_db dbblock;
1119open_db *dbm;
1120EXIM_CURSOR *cursor;
1121uschar **argv = USS cargv;
1122uschar buffer[256];
1123uschar *key;
1124
1125/* Scan the options */
1126
1127for (i = 1; i < argc; i++)
1128 {
1129 if (argv[i][0] != '-') break;
1130 if (Ustrcmp(argv[i], "-f") == 0) continue;
1131 if (Ustrcmp(argv[i], "-t") == 0)
1132 {
1133 uschar *s;
1134 s = argv[++i];
1135 maxkeep = 0;
1136 while (*s != 0)
1137 {
1138 int value, count;
1139 if (!isdigit(*s)) usage(US"tidydb", US" [-t <time>]");
1140 (void)sscanf(CS s, "%d%n", &value, &count);
1141 s += count;
1142 switch (*s)
1143 {
1144 case 'w': value *= 7;
1145 case 'd': value *= 24;
1146 case 'h': value *= 60;
1147 case 'm': value *= 60;
1148 case 's': s++;
1149 break;
1150 default: usage(US"tidydb", US" [-t <time>]");
1151 }
1152 maxkeep += value;
1153 }
1154 }
1155 else usage(US"tidydb", US" [-t <time>]");
1156 }
1157
1158/* Adjust argument values and process arguments */
1159
1160argc -= --i;
1161argv += i;
1162
1163dbdata_type = check_args(argc, argv, US"tidydb", US" [-t <time>]");
1164
1165/* Compute the oldest keep time, verify what we are doing, and open the
1166database */
1167
1168oldest = time(NULL) - maxkeep;
1169printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
1170
c99ce5c9 1171spool_directory = argv[1];
0a6c178c
JH
1172if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE)))
1173 exit(1);
059ec3d9
PH
1174
1175/* Prepare for building file names */
1176
1177sprintf(CS buffer, "%s/input/", argv[1]);
1178path_len = Ustrlen(buffer);
1179
1180
1181/* It appears, by experiment, that it is a bad idea to make changes
1182to the file while scanning it. Pity the man page doesn't warn you about that.
1183Therefore, we scan and build a list of all the keys. Then we use that to
1184read the records and possibly update them. */
1185
0a6c178c
JH
1186for (key = dbfn_scan(dbm, TRUE, &cursor);
1187 key;
1188 key = dbfn_scan(dbm, FALSE, &cursor))
059ec3d9
PH
1189 {
1190 key_item *k = store_get(sizeof(key_item) + Ustrlen(key));
1191 k->next = keychain;
1192 keychain = k;
1193 Ustrcpy(k->key, key);
059ec3d9
PH
1194 }
1195
1196/* Now scan the collected keys and operate on the records, resetting
1197the store each time round. */
1198
1199reset_point = store_get(0);
1200
0a6c178c 1201while (keychain)
059ec3d9
PH
1202 {
1203 dbdata_generic *value;
1204
1205 store_reset(reset_point);
1206 key = keychain->key;
1207 keychain = keychain->next;
1208 value = dbfn_read_with_length(dbm, key, NULL);
1209
1210 /* A continuation record may have been deleted or renamed already, so
1211 non-existence is not serious. */
1212
1213 if (value == NULL) continue;
1214
1215 /* Delete if too old */
1216
1217 if (value->time_stamp < oldest)
1218 {
1219 printf("deleted %s (too old)\n", key);
1220 dbfn_delete(dbm, key);
1221 continue;
1222 }
1223
1224 /* Do database-specific tidying for wait databases, and message-
1225 specific tidying for the retry database. */
1226
1227 if (dbdata_type == type_wait)
1228 {
1229 dbdata_wait *wait = (dbdata_wait *)value;
1230 BOOL update = FALSE;
1231
1232 /* Leave corrupt records alone */
1233
1234 if (wait->count > WAIT_NAME_MAX)
1235 {
1236 printf("**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
1237 key, wait->count, wait->count, WAIT_NAME_MAX);
1238 continue;
1239 }
1240
1241 /* Loop for renamed continuation records. For each message id,
1242 check to see if the message exists, and if not, remove its entry
1243 from the record. Because of the possibility of split input directories,
1244 we must look in both possible places for a -D file. */
1245
1246 for (;;)
1247 {
1248 int offset;
1249 int length = wait->count * MESSAGE_ID_LENGTH;
1250
1251 for (offset = length - MESSAGE_ID_LENGTH;
1252 offset >= 0; offset -= MESSAGE_ID_LENGTH)
1253 {
1254 Ustrncpy(buffer+path_len, wait->text + offset, MESSAGE_ID_LENGTH);
1255 sprintf(CS(buffer+path_len + MESSAGE_ID_LENGTH), "-D");
1256
1257 if (Ustat(buffer, &statbuf) != 0)
1258 {
1259 buffer[path_len] = wait->text[offset+5];
1260 buffer[path_len+1] = '/';
1261 Ustrncpy(buffer+path_len+2, wait->text + offset, MESSAGE_ID_LENGTH);
1262 sprintf(CS(buffer+path_len+2 + MESSAGE_ID_LENGTH), "-D");
1263
1264 if (Ustat(buffer, &statbuf) != 0)
1265 {
1266 int left = length - offset - MESSAGE_ID_LENGTH;
1267 if (left > 0) Ustrncpy(wait->text + offset,
1268 wait->text + offset + MESSAGE_ID_LENGTH, left);
1269 wait->count--;
1270 length -= MESSAGE_ID_LENGTH;
1271 update = TRUE;
1272 }
1273 }
1274 }
1275
1276 /* If record is empty and the main record, either delete it or rename
1277 the next continuation, repeating if that is also empty. */
1278
1279 if (wait->count == 0 && Ustrchr(key, ':') == NULL)
1280 {
1281 while (wait->count == 0 && wait->sequence > 0)
1282 {
1283 uschar newkey[256];
1284 dbdata_generic *newvalue;
1285 sprintf(CS newkey, "%s:%d", key, wait->sequence - 1);
1286 newvalue = dbfn_read_with_length(dbm, newkey, NULL);
1287 if (newvalue != NULL)
1288 {
1289 value = newvalue;
1290 wait = (dbdata_wait *)newvalue;
1291 dbfn_delete(dbm, newkey);
1292 printf("renamed %s\n", newkey);
1293 update = TRUE;
1294 }
1295 else wait->sequence--;
1296 }
1297
1298 /* If we have ended up with an empty main record, delete it
1299 and break the loop. Otherwise the new record will be scanned. */
1300
1301 if (wait->count == 0 && wait->sequence == 0)
1302 {
1303 dbfn_delete(dbm, key);
1304 printf("deleted %s (empty)\n", key);
1305 update = FALSE;
1306 break;
1307 }
1308 }
1309
1310 /* If not an empty main record, break the loop */
1311
1312 else break;
1313 }
1314
1315 /* Re-write the record if required */
1316
1317 if (update)
1318 {
1319 printf("updated %s\n", key);
1320 dbfn_write(dbm, key, wait, sizeof(dbdata_wait) +
1321 wait->count * MESSAGE_ID_LENGTH);
1322 }
1323 }
1324
1325 /* If a retry record's key ends with a message-id, check that that message
1326 still exists; if not, remove this record. */
1327
1328 else if (dbdata_type == type_retry)
1329 {
1330 uschar *id;
1331 int len = Ustrlen(key);
1332
1333 if (len < MESSAGE_ID_LENGTH + 1) continue;
1334 id = key + len - MESSAGE_ID_LENGTH - 1;
1335 if (*id++ != ':') continue;
1336
1337 for (i = 0; i < MESSAGE_ID_LENGTH; i++)
1338 {
1339 if (i == 6 || i == 13)
1340 { if (id[i] != '-') break; }
1341 else
1342 { if (!isalnum(id[i])) break; }
1343 }
1344 if (i < MESSAGE_ID_LENGTH) continue;
1345
1346 Ustrncpy(buffer + path_len, id, MESSAGE_ID_LENGTH);
1347 sprintf(CS(buffer + path_len + MESSAGE_ID_LENGTH), "-D");
1348
1349 if (Ustat(buffer, &statbuf) != 0)
1350 {
0ec020ea
PH
1351 sprintf(CS(buffer + path_len), "%c/%s-D", id[5], id);
1352 if (Ustat(buffer, &statbuf) != 0)
1353 {
1354 dbfn_delete(dbm, key);
1355 printf("deleted %s (no message)\n", key);
1356 }
059ec3d9
PH
1357 }
1358 }
1359 }
1360
1361dbfn_close(dbm);
1362printf("Tidying complete\n");
1363return 0;
1364}
1365
1366#endif /* EXIM_TIDYDB */
1367
1368/* End of exim_dbutil.c */