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