45bad208c48f2d7a432ddfa50783df599a5593e8
[exim.git] / src / src / exim_dbutil.c
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4
5 /* Copyright (c) University of Cambridge 1995 - 2018 */
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
10 maintaining 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
16 In all cases, the first argument is the name of the spool directory. The second
17 argument 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
24 There are a number of common subroutines, followed by three main programs,
25 whose inclusion is controlled by -D on the compilation command. */
26
27
28 #include "exim.h"
29
30
31 /* Identifiers for the different database types. */
32
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
38
39
40 /* This is used by our cut-down dbfn_open(). */
41
42 uschar *spool_directory;
43
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
51 errors. This should help with debugging strange DB problems, e.g. getting "File
52 exists" when you try to open a db file. The API changed at release 4.3. */
53
54 #if defined(USE_DB) && defined(DB_VERSION_STRING)
55 void
56 #if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
57 dbfn_bdb_error_callback(const DB_ENV *dbenv, const char *pfx, const char *msg)
58 {
59 dbenv = dbenv;
60 #else
61 dbfn_bdb_error_callback(const char *pfx, char *msg)
62 {
63 #endif
64 pfx = pfx;
65 printf("Berkeley DB error: %s\n", msg);
66 }
67 #endif
68
69
70
71 /*************************************************
72 *              SIGALRM handler                   *
73 *************************************************/
74
75 SIGNAL_BOOL sigalrm_seen;
76
77 void
78 sigalrm_handler(int sig)
79 {
80 sig = sig;            /* Keep picky compilers happy */
81 sigalrm_seen = 1;
82 }
83
84
85
86 /*************************************************
87 *        Output usage message and exit           *
88 *************************************************/
89
90 static void
91 usage(uschar *name, uschar *options)
92 {
93 printf("Usage: exim_%s%s  <spool-directory> <database-name>\n", name, options);
94 printf("  <database-name> = retry | misc | wait-<transport-name> | callout | ratelimit\n");
95 exit(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
105 second of them to be sure it is a known database name. */
106
107 static int
108 check_args(int argc, uschar **argv, uschar *name, uschar *options)
109 {
110 if (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;
116   if (Ustrcmp(argv[2], "ratelimit") == 0) return type_ratelimit;
117   }
118 usage(name, options);
119 return -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
129 utility. The message always gets '\n' added on the end of it. These calls come
130 from modules such as store.c when things go drastically wrong (e.g. malloc()
131 failing). In normal use they won't get obeyed.
132
133 Arguments:
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
139 Returns:    nothing
140 */
141
142 void
143 log_write(unsigned int selector, int flags, const char *format, ...)
144 {
145 va_list ap;
146 va_start(ap, format);
147 vfprintf(stderr, format, ap);
148 fprintf(stderr, "\n");
149 va_end(ap);
150 selector = selector;     /* Keep picky compilers happy */
151 flags = flags;
152 }
153
154
155
156 /*************************************************
157 *        Format a time value for printing        *
158 *************************************************/
159
160 static uschar time_buffer[sizeof("09-xxx-1999 hh:mm:ss  ")];
161
162 uschar *
163 print_time(time_t t)
164 {
165 struct tm *tmstr = localtime(&t);
166 Ustrftime(time_buffer, sizeof(time_buffer), "%d-%b-%Y %H:%M:%S", tmstr);
167 return time_buffer;
168 }
169
170
171
172 /*************************************************
173 *        Format a cache value for printing       *
174 *************************************************/
175
176 uschar *
177 print_cache(int value)
178 {
179 return (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
190 static time_t
191 read_time(uschar *s)
192 {
193 uschar *t = s;
194 int field = 0;
195 int value;
196 time_t now = time(NULL);
197 struct tm *tm = localtime(&now);
198
199 tm->tm_sec = 0;
200 tm->tm_isdst = -1;
201
202 for (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
225 return 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
236 uses. We assume the database exists, and therefore give up if we cannot open
237 the lock file.
238
239 Arguments:
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.
243   lof      Unused.
244
245 Returns:   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
250 open_db *
251 dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof)
252 {
253 int rc;
254 struct flock lock_data;
255 BOOL read_only = flags == O_RDONLY;
256 uschar * dirname, * filename;
257
258 /* The first thing to do is to open a separate file on which to lock. This
259 ensures that Exim has exclusive use of the database before it even tries to
260 open it. If there is a database, there should be a lock file in existence. */
261
262 #ifdef COMPILE_UTILITY
263 if (  asprintf(CSS &dirname, "%s/db", spool_directory) < 0
264    || asprintf(CSS &filename, "%s/%s.lockfile", dirname, name) < 0)
265   return NULL;
266 #else
267 dirname = string_sprintf("%s/db", spool_directory);
268 filename = string_sprintf("%s/%s.lockfile", dirname, name);
269 #endif
270
271 dbblock->lockfd = Uopen(filename, flags, 0);
272 if (dbblock->lockfd < 0)
273   {
274   printf("** Failed to open database lock file %s: %s\n", filename,
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
280 lock that times out. */
281
282 lock_data.l_type = read_only ? F_RDLCK : F_WRLCK;
283 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
284
285 sigalrm_seen = FALSE;
286 os_non_restarting_signal(SIGALRM, sigalrm_handler);
287 ALARM(EXIMDB_LOCK_TIMEOUT);
288 rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
289 ALARM_CLR(0);
290
291 if (sigalrm_seen) errno = ETIMEDOUT;
292 if (rc < 0)
293   {
294   printf("** Failed to get %s lock for %s: %s",
295     flags & O_WRONLY ? "write" : "read",
296     filename,
297     errno == ETIMEDOUT ? "timed out" : strerror(errno));
298   (void)close(dbblock->lockfd);
299   return NULL;
300   }
301
302 /* At this point we have an opened and locked separate lock file, that is,
303 exclusive access to the database, so we can go ahead and open it. */
304
305 #ifdef COMPILE_UTILITY
306 if (asprintf(CSS &filename, "%s/%s", dirname, name) < 0) return NULL;
307 #else
308 filename = string_sprintf("%s/%s", dirname, name);
309 #endif
310 EXIM_DBOPEN(filename, dirname, flags, 0, &(dbblock->dbptr));
311
312 if (!dbblock->dbptr)
313   {
314   printf("** Failed to open DBM file %s for %s:\n   %s%s\n", filename,
315     read_only? "reading" : "writing", strerror(errno),
316     #ifdef USE_DB
317     " (or Berkeley DB error while opening)"
318     #else
319     ""
320     #endif
321     );
322   (void)close(dbblock->lockfd);
323   return NULL;
324   }
325
326 return 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
337 close the lock file.
338
339 Argument: a pointer to an open database block
340 Returns:  nothing
341 */
342
343 void
344 dbfn_close(open_db *dbblock)
345 {
346 EXIM_DBCLOSE(dbblock->dbptr);
347 (void)close(dbblock->lockfd);
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
358 of alignment. Since all the records used by Exim need to be properly aligned to
359 pick out the timestamps, etc., do the copying centrally here.
360
361 Arguments:
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
366 Returns: a pointer to the retrieved record, or
367          NULL if the record is not found
368 */
369
370 void *
371 dbfn_read_with_length(open_db *dbblock, const uschar *key, int *length)
372 {
373 void *yield;
374 EXIM_DATUM key_datum, result_datum;
375 int klen = Ustrlen(key) + 1;
376 uschar * key_copy = store_get(klen);
377
378 memcpy(key_copy, key, klen);
379
380 EXIM_DATUM_INIT(key_datum);         /* Some DBM libraries require the datum */
381 EXIM_DATUM_INIT(result_datum);      /* to be cleared before use. */
382 EXIM_DATUM_DATA(key_datum) = CS key_copy;
383 EXIM_DATUM_SIZE(key_datum) = klen;
384
385 if (!EXIM_DBGET(dbblock->dbptr, key_datum, result_datum)) return NULL;
386
387 yield = store_get(EXIM_DATUM_SIZE(result_datum));
388 memcpy(yield, EXIM_DATUM_DATA(result_datum), EXIM_DATUM_SIZE(result_datum));
389 if (length != NULL) *length = EXIM_DATUM_SIZE(result_datum);
390
391 EXIM_DATUM_FREE(result_datum);    /* Some DBM libs require freeing */
392 return yield;
393 }
394
395
396
397 #if defined(EXIM_TIDYDB) || defined(EXIM_FIXDB)
398
399 /*************************************************
400 *             Write to database file             *
401 *************************************************/
402
403 /*
404 Arguments:
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
410 Returns:    the yield of the underlying dbm or db "write" function. If this
411             is dbm, the value is zero for OK.
412 */
413
414 int
415 dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length)
416 {
417 EXIM_DATUM key_datum, value_datum;
418 dbdata_generic *gptr = (dbdata_generic *)ptr;
419 int klen = Ustrlen(key) + 1;
420 uschar * key_copy = store_get(klen);
421
422 memcpy(key_copy, key, klen);
423 gptr->time_stamp = time(NULL);
424
425 EXIM_DATUM_INIT(key_datum);         /* Some DBM libraries require the datum */
426 EXIM_DATUM_INIT(value_datum);       /* to be cleared before use. */
427 EXIM_DATUM_DATA(key_datum) = CS key_copy;
428 EXIM_DATUM_SIZE(key_datum) = klen;
429 EXIM_DATUM_DATA(value_datum) = CS ptr;
430 EXIM_DATUM_SIZE(value_datum) = length;
431 return EXIM_DBPUT(dbblock->dbptr, key_datum, value_datum);
432 }
433
434
435
436 /*************************************************
437 *           Delete record from database file     *
438 *************************************************/
439
440 /*
441 Arguments:
442   dbblock    a pointer to an open database block
443   key        the key of the record to be deleted
444
445 Returns: the yield of the underlying dbm or db "delete" function.
446 */
447
448 int
449 dbfn_delete(open_db *dbblock, const uschar *key)
450 {
451 int klen = Ustrlen(key) + 1;
452 uschar * key_copy = store_get(klen);
453
454 memcpy(key_copy, key, klen);
455 EXIM_DATUM key_datum;
456 EXIM_DATUM_INIT(key_datum);         /* Some DBM libraries require clearing */
457 EXIM_DATUM_DATA(key_datum) = CS key_copy;
458 EXIM_DATUM_SIZE(key_datum) = klen;
459 return 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 /*
472 Arguments:
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
479 Returns:   the next record from the file, or
480            NULL if there are no more
481 */
482
483 uschar *
484 dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
485 {
486 EXIM_DATUM key_datum, value_datum;
487 uschar *yield;
488 value_datum = value_datum;    /* dummy; not all db libraries use this */
489
490 /* Some dbm require an initialization */
491
492 if (start) EXIM_DBCREATE_CURSOR(dbblock->dbptr, cursor);
493
494 EXIM_DATUM_INIT(key_datum);         /* Some DBM libraries require the datum */
495 EXIM_DATUM_INIT(value_datum);       /* to be cleared before use. */
496
497 yield = (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
502 if (!yield) EXIM_DBDELETE_CURSOR(*cursor);
503 return yield;
504 }
505 #endif  /* EXIM_DUMPDB || EXIM_TIDYDB */
506
507
508
509 #ifdef EXIM_DUMPDB
510 /*************************************************
511 *           The exim_dumpdb main program         *
512 *************************************************/
513
514 int
515 main(int argc, char **cargv)
516 {
517 int dbdata_type = 0;
518 int yield = 0;
519 open_db dbblock;
520 open_db *dbm;
521 EXIM_CURSOR *cursor;
522 uschar **argv = USS cargv;
523 uschar *key;
524 uschar keybuffer[1024];
525
526 /* Check the arguments, and open the database */
527
528 dbdata_type = check_args(argc, argv, US"dumpdb", US"");
529 spool_directory = argv[1];
530 if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE)))
531   exit(1);
532
533 /* Scan the file, formatting the information for each entry. Note
534 that data is returned in a malloc'ed block, in order that it be
535 correctly aligned. */
536
537 for (key = dbfn_scan(dbm, TRUE, &cursor);
538      key;
539      key = dbfn_scan(dbm, FALSE, &cursor))
540   {
541   dbdata_retry *retry;
542   dbdata_wait *wait;
543   dbdata_callout_cache *callout;
544   dbdata_ratelimit *ratelimit;
545   dbdata_ratelimit_unique *rate_unique;
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);
561
562   if (!(value = dbfn_read_with_length(dbm, keybuffer, &length)))
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
657       break;
658
659       case type_ratelimit:
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         }
678       break;
679       }
680     store_reset(value);
681     }
682   }
683
684 dbfn_close(dbm);
685 return 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
699 operation on the database uses a separate open/close call. This is expensive,
700 but then using this utility is not expected to be very common. Its main use is
701 to provide a way of patching up hints databases in order to run tests.
702
703 Syntax 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
721 If the record name is omitted from (2) or (3), the previously used record name
722 is re-used. */
723
724
725 int main(int argc, char **cargv)
726 {
727 int dbdata_type;
728 uschar **argv = USS cargv;
729 uschar buffer[256];
730 uschar name[256];
731 void *reset_point = store_get(0);
732
733 name[0] = 0;  /* No name set */
734
735 /* Sort out the database type, verify what we are working on and then process
736 user requests */
737
738 dbdata_type = check_args(argc, argv, US"fixdb", US"");
739 printf("Modifying Exim hints database %s/db/%s\n", argv[1], argv[2]);
740
741 for(;;)
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;
749   dbdata_ratelimit *ratelimit;
750   dbdata_ratelimit_unique *rate_unique;
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
766   if ((isdigit((uschar)buffer[0]) && (buffer[1] == ' ' || buffer[1] == '\0'))
767        || Ustrcmp(buffer, "d") == 0)
768     {
769     if (name[0] == 0)
770       {
771       printf("No previous record name is set\n");
772       continue;
773       }
774     (void)sscanf(CS buffer, "%s %s", field, value);
775     }
776   else
777     {
778     name[0] = 0;
779     (void)sscanf(CS buffer, "%s %s %s", name, field, value);
780     }
781
782   /* Handle an update request */
783
784   if (field[0] != 0)
785     {
786     int verify = 1;
787     spool_directory = argv[1];
788
789     if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE)))
790       continue;
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;
816           /*int length = 0;      Stops compiler warning */
817
818           switch(dbdata_type)
819             {
820             case type_retry:
821             retry = (dbdata_retry *)record;
822             /* length = sizeof(dbdata_retry) + Ustrlen(retry->text); */
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;
872             /* length = sizeof(dbdata_callout_cache); */
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;
893
894             case type_ratelimit:
895             ratelimit = (dbdata_ratelimit *)record;
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);
905               break;
906
907               case 2:
908               ratelimit->rate = Ustrtod(value, NULL);
909               break;
910
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
956               default:
957               printf("unknown field number\n");
958               verify = 0;
959               break;
960               }
961             break;
962             }
963
964           dbfn_write(dbm, name, record, oldlength);
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
985   spool_directory = argv[1];
986   if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE)))
987     continue;
988
989   if (!(record = dbfn_read_with_length(dbm, name, &oldlength)))
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;
1059
1060       case type_ratelimit:
1061       ratelimit = (dbdata_ratelimit *)record;
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);
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        }
1073       break;
1074       }
1075     }
1076
1077   /* The database is closed after each request */
1078
1079   dbfn_close(dbm);
1080   }
1081
1082 printf("\n");
1083 return 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
1097 option:
1098
1099    -t <time>  expiry time for old records - default 30 days
1100
1101 For backwards compatibility, an -f option is recognized and ignored. (It used
1102 to request a "full" tidy. This version always does the whole job.) */
1103
1104
1105 typedef struct key_item {
1106   struct key_item *next;
1107   uschar key[1];
1108 } key_item;
1109
1110
1111 int main(int argc, char **cargv)
1112 {
1113 struct stat statbuf;
1114 int maxkeep = 30 * 24 * 60 * 60;
1115 int dbdata_type, i, oldest, path_len;
1116 key_item *keychain = NULL;
1117 void *reset_point;
1118 open_db dbblock;
1119 open_db *dbm;
1120 EXIM_CURSOR *cursor;
1121 uschar **argv = USS cargv;
1122 uschar buffer[256];
1123 uschar *key;
1124
1125 /* Scan the options */
1126
1127 for (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
1160 argc -= --i;
1161 argv += i;
1162
1163 dbdata_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
1166 database */
1167
1168 oldest = time(NULL) - maxkeep;
1169 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
1170
1171 spool_directory = argv[1];
1172 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE)))
1173   exit(1);
1174
1175 /* Prepare for building file names */
1176
1177 sprintf(CS buffer, "%s/input/", argv[1]);
1178 path_len = Ustrlen(buffer);
1179
1180
1181 /* It appears, by experiment, that it is a bad idea to make changes
1182 to the file while scanning it. Pity the man page doesn't warn you about that.
1183 Therefore, we scan and build a list of all the keys. Then we use that to
1184 read the records and possibly update them. */
1185
1186 for (key = dbfn_scan(dbm, TRUE, &cursor);
1187      key;
1188      key = dbfn_scan(dbm, FALSE, &cursor))
1189   {
1190   key_item *k = store_get(sizeof(key_item) + Ustrlen(key));
1191   k->next = keychain;
1192   keychain = k;
1193   Ustrcpy(k->key, key);
1194   }
1195
1196 /* Now scan the collected keys and operate on the records, resetting
1197 the store each time round. */
1198
1199 reset_point = store_get(0);
1200
1201 while (keychain)
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       {
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         }
1357       }
1358     }
1359   }
1360
1361 dbfn_close(dbm);
1362 printf("Tidying complete\n");
1363 return 0;
1364 }
1365
1366 #endif  /* EXIM_TIDYDB */
1367
1368 /* End of exim_dbutil.c */