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