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