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