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