constification
[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, const uschar *key, int *length)
360 {
361 void *yield;
362 EXIM_DATUM key_datum, result_datum;
363 int klen = Ustrlen(key) + 1;
364 uschar * key_copy = store_get(klen);
365
366 memcpy(key_copy, key, klen);
367
368 EXIM_DATUM_INIT(key_datum);         /* Some DBM libraries require the datum */
369 EXIM_DATUM_INIT(result_datum);      /* to be cleared before use. */
370 EXIM_DATUM_DATA(key_datum) = CS key_copy;
371 EXIM_DATUM_SIZE(key_datum) = klen;
372
373 if (!EXIM_DBGET(dbblock->dbptr, key_datum, result_datum)) return NULL;
374
375 yield = store_get(EXIM_DATUM_SIZE(result_datum));
376 memcpy(yield, EXIM_DATUM_DATA(result_datum), EXIM_DATUM_SIZE(result_datum));
377 if (length != NULL) *length = EXIM_DATUM_SIZE(result_datum);
378
379 EXIM_DATUM_FREE(result_datum);    /* Some DBM libs require freeing */
380 return yield;
381 }
382
383
384
385 #if defined(EXIM_TIDYDB) || defined(EXIM_FIXDB)
386
387 /*************************************************
388 *             Write to database file             *
389 *************************************************/
390
391 /*
392 Arguments:
393   dbblock   a pointer to an open database block
394   key       the key of the record to be written
395   ptr       a pointer to the record to be written
396   length    the length of the record to be written
397
398 Returns:    the yield of the underlying dbm or db "write" function. If this
399             is dbm, the value is zero for OK.
400 */
401
402 int
403 dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length)
404 {
405 EXIM_DATUM key_datum, value_datum;
406 dbdata_generic *gptr = (dbdata_generic *)ptr;
407 int klen = Ustrlen(key) + 1;
408 uschar * key_copy = store_get(klen);
409
410 memcpy(key_copy, key, klen);
411 gptr->time_stamp = time(NULL);
412
413 EXIM_DATUM_INIT(key_datum);         /* Some DBM libraries require the datum */
414 EXIM_DATUM_INIT(value_datum);       /* to be cleared before use. */
415 EXIM_DATUM_DATA(key_datum) = CS key_copy;
416 EXIM_DATUM_SIZE(key_datum) = klen;
417 EXIM_DATUM_DATA(value_datum) = CS ptr;
418 EXIM_DATUM_SIZE(value_datum) = length;
419 return EXIM_DBPUT(dbblock->dbptr, key_datum, value_datum);
420 }
421
422
423
424 /*************************************************
425 *           Delete record from database file     *
426 *************************************************/
427
428 /*
429 Arguments:
430   dbblock    a pointer to an open database block
431   key        the key of the record to be deleted
432
433 Returns: the yield of the underlying dbm or db "delete" function.
434 */
435
436 int
437 dbfn_delete(open_db *dbblock, const uschar *key)
438 {
439 int klen = Ustrlen(key) + 1;
440 uschar * key_copy = store_get(klen);
441
442 memcpy(key_copy, key, klen);
443 EXIM_DATUM key_datum;
444 EXIM_DATUM_INIT(key_datum);         /* Some DBM libraries require clearing */
445 EXIM_DATUM_DATA(key_datum) = CS key_copy;
446 EXIM_DATUM_SIZE(key_datum) = klen;
447 return EXIM_DBDEL(dbblock->dbptr, key_datum);
448 }
449
450 #endif  /* EXIM_TIDYDB || EXIM_FIXDB */
451
452
453
454 #if defined(EXIM_DUMPDB) || defined(EXIM_TIDYDB)
455 /*************************************************
456 *         Scan the keys of a database file       *
457 *************************************************/
458
459 /*
460 Arguments:
461   dbblock  a pointer to an open database block
462   start    TRUE if starting a new scan
463            FALSE if continuing with the current scan
464   cursor   a pointer to a pointer to a cursor anchor, for those dbm libraries
465            that use the notion of a cursor
466
467 Returns:   the next record from the file, or
468            NULL if there are no more
469 */
470
471 uschar *
472 dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
473 {
474 EXIM_DATUM key_datum, value_datum;
475 uschar *yield;
476 value_datum = value_datum;    /* dummy; not all db libraries use this */
477
478 /* Some dbm require an initialization */
479
480 if (start) EXIM_DBCREATE_CURSOR(dbblock->dbptr, cursor);
481
482 EXIM_DATUM_INIT(key_datum);         /* Some DBM libraries require the datum */
483 EXIM_DATUM_INIT(value_datum);       /* to be cleared before use. */
484
485 yield = (EXIM_DBSCAN(dbblock->dbptr, key_datum, value_datum, start, *cursor))?
486   US EXIM_DATUM_DATA(key_datum) : NULL;
487
488 /* Some dbm require a termination */
489
490 if (!yield) EXIM_DBDELETE_CURSOR(*cursor);
491 return yield;
492 }
493 #endif  /* EXIM_DUMPDB || EXIM_TIDYDB */
494
495
496
497 #ifdef EXIM_DUMPDB
498 /*************************************************
499 *           The exim_dumpdb main program         *
500 *************************************************/
501
502 int
503 main(int argc, char **cargv)
504 {
505 int dbdata_type = 0;
506 int yield = 0;
507 open_db dbblock;
508 open_db *dbm;
509 EXIM_CURSOR *cursor;
510 uschar **argv = USS cargv;
511 uschar *key;
512 uschar keybuffer[1024];
513
514 /* Check the arguments, and open the database */
515
516 dbdata_type = check_args(argc, argv, US"dumpdb", US"");
517 spool_directory = argv[1];
518 dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE);
519 if (dbm == NULL) exit(1);
520
521 /* Scan the file, formatting the information for each entry. Note
522 that data is returned in a malloc'ed block, in order that it be
523 correctly aligned. */
524
525 key = dbfn_scan(dbm, TRUE, &cursor);
526 while (key != NULL)
527   {
528   dbdata_retry *retry;
529   dbdata_wait *wait;
530   dbdata_callout_cache *callout;
531   dbdata_ratelimit *ratelimit;
532   dbdata_ratelimit_unique *rate_unique;
533   int count_bad = 0;
534   int i, length;
535   uschar *t;
536   uschar name[MESSAGE_ID_LENGTH + 1];
537   void *value;
538
539   /* Keep a copy of the key separate, as in some DBM's the pointer is into data
540   which might change. */
541
542   if (Ustrlen(key) > sizeof(keybuffer) - 1)
543     {
544     printf("**** Overlong key encountered: %s\n", key);
545     return 1;
546     }
547   Ustrcpy(keybuffer, key);
548   value = dbfn_read_with_length(dbm, keybuffer, &length);
549
550   if (value == NULL)
551     fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
552                     "was not found in the file - something is wrong!\n",
553       CS keybuffer);
554   else
555     {
556     /* Note: don't use print_time more than once in one statement, since
557     it uses a single buffer. */
558
559     switch(dbdata_type)
560       {
561       case type_retry:
562       retry = (dbdata_retry *)value;
563       printf("  %s %d %d %s\n%s  ", keybuffer, retry->basic_errno,
564         retry->more_errno, retry->text,
565         print_time(retry->first_failed));
566       printf("%s  ", print_time(retry->last_try));
567       printf("%s %s\n", print_time(retry->next_try),
568         (retry->expired)? "*" : "");
569       break;
570
571       case type_wait:
572       wait = (dbdata_wait *)value;
573       printf("%s ", keybuffer);
574       t = wait->text;
575       name[MESSAGE_ID_LENGTH] = 0;
576
577       if (wait->count > WAIT_NAME_MAX)
578         {
579         fprintf(stderr,
580           "**** Data for %s corrupted\n  count=%d=0x%x max=%d\n",
581           CS keybuffer, wait->count, wait->count, WAIT_NAME_MAX);
582         wait->count = WAIT_NAME_MAX;
583         yield = count_bad = 1;
584         }
585       for (i = 1; i <= wait->count; i++)
586         {
587         Ustrncpy(name, t, MESSAGE_ID_LENGTH);
588         if (count_bad && name[0] == 0) break;
589         if (Ustrlen(name) != MESSAGE_ID_LENGTH ||
590             Ustrspn(name, "0123456789"
591                           "abcdefghijklmnopqrstuvwxyz"
592                           "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
593           {
594           int j;
595           fprintf(stderr,
596             "**** Data for %s corrupted: bad character in message id\n",
597             CS keybuffer);
598           for (j = 0; j < MESSAGE_ID_LENGTH; j++)
599             fprintf(stderr, "%02x ", name[j]);
600           fprintf(stderr, "\n");
601           yield = 1;
602           break;
603           }
604         printf("%s ", name);
605         t += MESSAGE_ID_LENGTH;
606         }
607       printf("\n");
608       break;
609
610       case type_misc:
611       printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
612         keybuffer);
613       break;
614
615       case type_callout:
616       callout = (dbdata_callout_cache *)value;
617
618       /* New-style address record */
619
620       if (length == sizeof(dbdata_callout_cache_address))
621         {
622         printf("%s %s callout=%s\n",
623           print_time(((dbdata_generic *)value)->time_stamp),
624           keybuffer,
625           print_cache(callout->result));
626         }
627
628       /* New-style domain record */
629
630       else if (length == sizeof(dbdata_callout_cache))
631         {
632         printf("%s %s callout=%s postmaster=%s",
633           print_time(((dbdata_generic *)value)->time_stamp),
634           keybuffer,
635           print_cache(callout->result),
636           print_cache(callout->postmaster_result));
637         if (callout->postmaster_result != ccache_unknown)
638           printf(" (%s)", print_time(callout->postmaster_stamp));
639         printf(" random=%s", print_cache(callout->random_result));
640         if (callout->random_result != ccache_unknown)
641           printf(" (%s)", print_time(callout->random_stamp));
642         printf("\n");
643         }
644
645       /* Old-style domain record, without separate timestamps. This code can
646       eventually be thrown away, say in 5 years' time (it's now Feb 2003). */
647
648       else
649         {
650         printf("%s %s callout=%s postmaster=%s random=%s\n",
651           print_time(((dbdata_generic *)value)->time_stamp),
652           keybuffer,
653           print_cache(callout->result),
654           print_cache(callout->postmaster_result),
655           print_cache(callout->random_result));
656         }
657
658       break;
659
660       case type_ratelimit:
661       if (Ustrstr(key, "/unique/") != NULL && length >= sizeof(*rate_unique))
662         {
663         ratelimit = (dbdata_ratelimit *)value;
664         rate_unique = (dbdata_ratelimit_unique *)value;
665         printf("%s.%06d rate: %10.3f epoch: %s size: %u key: %s\n",
666           print_time(ratelimit->time_stamp),
667           ratelimit->time_usec, ratelimit->rate,
668           print_time(rate_unique->bloom_epoch), rate_unique->bloom_size,
669           keybuffer);
670         }
671       else
672         {
673         ratelimit = (dbdata_ratelimit *)value;
674         printf("%s.%06d rate: %10.3f key: %s\n",
675           print_time(ratelimit->time_stamp),
676           ratelimit->time_usec, ratelimit->rate,
677           keybuffer);
678         }
679       break;
680       }
681     store_reset(value);
682     }
683   key = dbfn_scan(dbm, FALSE, &cursor);
684   }
685
686 dbfn_close(dbm);
687 return yield;
688 }
689
690 #endif  /* EXIM_DUMPDB */
691
692
693
694
695 #ifdef EXIM_FIXDB
696 /*************************************************
697 *           The exim_fixdb main program          *
698 *************************************************/
699
700 /* In order not to hold the database lock any longer than is necessary, each
701 operation on the database uses a separate open/close call. This is expensive,
702 but then using this utility is not expected to be very common. Its main use is
703 to provide a way of patching up hints databases in order to run tests.
704
705 Syntax of commands:
706
707 (1) <record name>
708     This causes the data from the given record to be displayed, or "not found"
709     to be output. Note that in the retry database, destination names are
710     preceded by R: or T: for router or transport retry info.
711
712 (2) <record name> d
713     This causes the given record to be deleted or "not found" to be output.
714
715 (3) <record name> <field number> <value>
716     This sets the given value into the given field, identified by a number
717     which is output by the display command. Not all types of record can
718     be changed.
719
720 (4) q
721     This exits from exim_fixdb.
722
723 If the record name is omitted from (2) or (3), the previously used record name
724 is re-used. */
725
726
727 int main(int argc, char **cargv)
728 {
729 int dbdata_type;
730 uschar **argv = USS cargv;
731 uschar buffer[256];
732 uschar name[256];
733 void *reset_point = store_get(0);
734
735 name[0] = 0;  /* No name set */
736
737 /* Sort out the database type, verify what we are working on and then process
738 user requests */
739
740 dbdata_type = check_args(argc, argv, US"fixdb", US"");
741 printf("Modifying Exim hints database %s/db/%s\n", argv[1], argv[2]);
742
743 for(;;)
744   {
745   open_db dbblock;
746   open_db *dbm;
747   void *record;
748   dbdata_retry *retry;
749   dbdata_wait *wait;
750   dbdata_callout_cache *callout;
751   dbdata_ratelimit *ratelimit;
752   dbdata_ratelimit_unique *rate_unique;
753   int i, oldlength;
754   uschar *t;
755   uschar field[256], value[256];
756
757   store_reset(reset_point);
758
759   printf("> ");
760   if (Ufgets(buffer, 256, stdin) == NULL) break;
761
762   buffer[Ustrlen(buffer)-1] = 0;
763   field[0] = value[0] = 0;
764
765   /* If the buffer contains just one digit, or just consists of "d", use the
766   previous name for an update. */
767
768   if ((isdigit((uschar)buffer[0]) && (buffer[1] == ' ' || buffer[1] == '\0'))
769        || Ustrcmp(buffer, "d") == 0)
770     {
771     if (name[0] == 0)
772       {
773       printf("No previous record name is set\n");
774       continue;
775       }
776     (void)sscanf(CS buffer, "%s %s", field, value);
777     }
778   else
779     {
780     name[0] = 0;
781     (void)sscanf(CS buffer, "%s %s %s", name, field, value);
782     }
783
784   /* Handle an update request */
785
786   if (field[0] != 0)
787     {
788     int verify = 1;
789     spool_directory = argv[1];
790     dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE);
791     if (dbm == NULL) continue;
792
793     if (Ustrcmp(field, "d") == 0)
794       {
795       if (value[0] != 0) printf("unexpected value after \"d\"\n");
796         else printf("%s\n", (dbfn_delete(dbm, name) < 0)?
797           "not found" : "deleted");
798       dbfn_close(dbm);
799       continue;
800       }
801
802     else if (isdigit((uschar)field[0]))
803       {
804       int fieldno = Uatoi(field);
805       if (value[0] == 0)
806         {
807         printf("value missing\n");
808         dbfn_close(dbm);
809         continue;
810         }
811       else
812         {
813         record = dbfn_read_with_length(dbm, name, &oldlength);
814         if (record == NULL) printf("not found\n"); else
815           {
816           time_t tt;
817           int length = 0;     /* Stops compiler warning */
818
819           switch(dbdata_type)
820             {
821             case type_retry:
822             retry = (dbdata_retry *)record;
823             length = sizeof(dbdata_retry) + Ustrlen(retry->text);
824
825             switch(fieldno)
826               {
827               case 0:
828               retry->basic_errno = Uatoi(value);
829               break;
830
831               case 1:
832               retry->more_errno = Uatoi(value);
833               break;
834
835               case 2:
836               if ((tt = read_time(value)) > 0) retry->first_failed = tt;
837                 else printf("bad time value\n");
838               break;
839
840               case 3:
841               if ((tt = read_time(value)) > 0) retry->last_try = tt;
842                 else printf("bad time value\n");
843               break;
844
845               case 4:
846               if ((tt = read_time(value)) > 0) retry->next_try = tt;
847                 else printf("bad time value\n");
848               break;
849
850               case 5:
851               if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE;
852               else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE;
853               else printf("\"yes\" or \"no\" expected=n");
854               break;
855
856               default:
857               printf("unknown field number\n");
858               verify = 0;
859               break;
860               }
861             break;
862
863             case type_wait:
864             printf("Can't change contents of wait database record\n");
865             break;
866
867             case type_misc:
868             printf("Can't change contents of misc database record\n");
869             break;
870
871             case type_callout:
872             callout = (dbdata_callout_cache *)record;
873             length = sizeof(dbdata_callout_cache);
874             switch(fieldno)
875               {
876               case 0:
877               callout->result = Uatoi(value);
878               break;
879
880               case 1:
881               callout->postmaster_result = Uatoi(value);
882               break;
883
884               case 2:
885               callout->random_result = Uatoi(value);
886               break;
887
888               default:
889               printf("unknown field number\n");
890               verify = 0;
891               break;
892               }
893             break;
894
895             case type_ratelimit:
896             ratelimit = (dbdata_ratelimit *)record;
897             switch(fieldno)
898               {
899               case 0:
900               if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt;
901                 else printf("bad time value\n");
902               break;
903
904               case 1:
905               ratelimit->time_usec = Uatoi(value);
906               break;
907
908               case 2:
909               ratelimit->rate = Ustrtod(value, NULL);
910               break;
911
912               case 3:
913               if (Ustrstr(name, "/unique/") != NULL
914                 && oldlength >= sizeof(dbdata_ratelimit_unique))
915                 {
916                 rate_unique = (dbdata_ratelimit_unique *)record;
917                 if ((tt = read_time(value)) > 0) rate_unique->bloom_epoch = tt;
918                   else printf("bad time value\n");
919                 break;
920                 }
921               /* else fall through */
922
923               case 4:
924               case 5:
925               if (Ustrstr(name, "/unique/") != NULL
926                 && oldlength >= sizeof(dbdata_ratelimit_unique))
927                 {
928                 /* see acl.c */
929                 BOOL seen;
930                 unsigned n, hash, hinc;
931                 uschar md5sum[16];
932                 md5 md5info;
933                 md5_start(&md5info);
934                 md5_end(&md5info, value, Ustrlen(value), md5sum);
935                 hash = md5sum[0] <<  0 | md5sum[1] <<  8
936                      | md5sum[2] << 16 | md5sum[3] << 24;
937                 hinc = md5sum[4] <<  0 | md5sum[5] <<  8
938                      | md5sum[6] << 16 | md5sum[7] << 24;
939                 rate_unique = (dbdata_ratelimit_unique *)record;
940                 seen = TRUE;
941                 for (n = 0; n < 8; n++, hash += hinc)
942                   {
943                   int bit = 1 << (hash % 8);
944                   int byte = (hash / 8) % rate_unique->bloom_size;
945                   if ((rate_unique->bloom[byte] & bit) == 0)
946                     {
947                     seen = FALSE;
948                     if (fieldno == 5) rate_unique->bloom[byte] |= bit;
949                     }
950                   }
951                 printf("%s %s\n",
952                   seen ? "seen" : fieldno == 5 ? "added" : "unseen", value);
953                 break;
954                 }
955               /* else fall through */
956
957               default:
958               printf("unknown field number\n");
959               verify = 0;
960               break;
961               }
962             break;
963             }
964
965           dbfn_write(dbm, name, record, oldlength);
966           }
967         }
968       }
969
970     else
971       {
972       printf("field number or d expected\n");
973       verify = 0;
974       }
975
976     dbfn_close(dbm);
977     if (!verify) continue;
978     }
979
980   /* The "name" q causes an exit */
981
982   else if (Ustrcmp(name, "q") == 0) return 0;
983
984   /* Handle a read request, or verify after an update. */
985
986   spool_directory = argv[1];
987   dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE);
988   if (dbm == NULL) continue;
989
990   record = dbfn_read_with_length(dbm, name, &oldlength);
991   if (record == NULL)
992     {
993     printf("record %s not found\n", name);
994     name[0] = 0;
995     }
996   else
997     {
998     int count_bad = 0;
999     printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp));
1000     switch(dbdata_type)
1001       {
1002       case type_retry:
1003       retry = (dbdata_retry *)record;
1004       printf("0 error number: %d %s\n", retry->basic_errno, retry->text);
1005       printf("1 extra data:   %d\n", retry->more_errno);
1006       printf("2 first failed: %s\n", print_time(retry->first_failed));
1007       printf("3 last try:     %s\n", print_time(retry->last_try));
1008       printf("4 next try:     %s\n", print_time(retry->next_try));
1009       printf("5 expired:      %s\n", (retry->expired)? "yes" : "no");
1010       break;
1011
1012       case type_wait:
1013       wait = (dbdata_wait *)record;
1014       t = wait->text;
1015       printf("Sequence: %d\n", wait->sequence);
1016       if (wait->count > WAIT_NAME_MAX)
1017         {
1018         printf("**** Data corrupted: count=%d=0x%x max=%d ****\n", wait->count,
1019           wait->count, WAIT_NAME_MAX);
1020         wait->count = WAIT_NAME_MAX;
1021         count_bad = 1;
1022         }
1023       for (i = 1; i <= wait->count; i++)
1024         {
1025         Ustrncpy(value, t, MESSAGE_ID_LENGTH);
1026         value[MESSAGE_ID_LENGTH] = 0;
1027         if (count_bad && value[0] == 0) break;
1028         if (Ustrlen(value) != MESSAGE_ID_LENGTH ||
1029             Ustrspn(value, "0123456789"
1030                           "abcdefghijklmnopqrstuvwxyz"
1031                           "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
1032           {
1033           int j;
1034           printf("\n**** Data corrupted: bad character in message id ****\n");
1035           for (j = 0; j < MESSAGE_ID_LENGTH; j++)
1036             printf("%02x ", value[j]);
1037           printf("\n");
1038           break;
1039           }
1040         printf("%s ", value);
1041         t += MESSAGE_ID_LENGTH;
1042         }
1043       printf("\n");
1044       break;
1045
1046       case type_misc:
1047       break;
1048
1049       case type_callout:
1050       callout = (dbdata_callout_cache *)record;
1051       printf("0 callout:    %s (%d)\n", print_cache(callout->result),
1052           callout->result);
1053       if (oldlength > sizeof(dbdata_callout_cache_address))
1054         {
1055         printf("1 postmaster: %s (%d)\n", print_cache(callout->postmaster_result),
1056             callout->postmaster_result);
1057         printf("2 random:     %s (%d)\n", print_cache(callout->random_result),
1058             callout->random_result);
1059         }
1060       break;
1061
1062       case type_ratelimit:
1063       ratelimit = (dbdata_ratelimit *)record;
1064       printf("0 time stamp:  %s\n", print_time(ratelimit->time_stamp));
1065       printf("1 fract. time: .%06d\n", ratelimit->time_usec);
1066       printf("2 sender rate: % .3f\n", ratelimit->rate);
1067       if (Ustrstr(name, "/unique/") != NULL
1068        && oldlength >= sizeof(dbdata_ratelimit_unique))
1069        {
1070        rate_unique = (dbdata_ratelimit_unique *)record;
1071        printf("3 filter epoch: %s\n", print_time(rate_unique->bloom_epoch));
1072        printf("4 test filter membership\n");
1073        printf("5 add element to filter\n");
1074        }
1075       break;
1076       }
1077     }
1078
1079   /* The database is closed after each request */
1080
1081   dbfn_close(dbm);
1082   }
1083
1084 printf("\n");
1085 return 0;
1086 }
1087
1088 #endif  /* EXIM_FIXDB */
1089
1090
1091
1092 #ifdef EXIM_TIDYDB
1093 /*************************************************
1094 *           The exim_tidydb main program         *
1095 *************************************************/
1096
1097
1098 /* Utility program to tidy the contents of an exim database file. There is one
1099 option:
1100
1101    -t <time>  expiry time for old records - default 30 days
1102
1103 For backwards compatibility, an -f option is recognized and ignored. (It used
1104 to request a "full" tidy. This version always does the whole job.) */
1105
1106
1107 typedef struct key_item {
1108   struct key_item *next;
1109   uschar key[1];
1110 } key_item;
1111
1112
1113 int main(int argc, char **cargv)
1114 {
1115 struct stat statbuf;
1116 int maxkeep = 30 * 24 * 60 * 60;
1117 int dbdata_type, i, oldest, path_len;
1118 key_item *keychain = NULL;
1119 void *reset_point;
1120 open_db dbblock;
1121 open_db *dbm;
1122 EXIM_CURSOR *cursor;
1123 uschar **argv = USS cargv;
1124 uschar buffer[256];
1125 uschar *key;
1126
1127 /* Scan the options */
1128
1129 for (i = 1; i < argc; i++)
1130   {
1131   if (argv[i][0] != '-') break;
1132   if (Ustrcmp(argv[i], "-f") == 0) continue;
1133   if (Ustrcmp(argv[i], "-t") == 0)
1134     {
1135     uschar *s;
1136     s = argv[++i];
1137     maxkeep = 0;
1138     while (*s != 0)
1139       {
1140       int value, count;
1141       if (!isdigit(*s)) usage(US"tidydb", US" [-t <time>]");
1142       (void)sscanf(CS s, "%d%n", &value, &count);
1143       s += count;
1144       switch (*s)
1145         {
1146         case 'w': value *= 7;
1147         case 'd': value *= 24;
1148         case 'h': value *= 60;
1149         case 'm': value *= 60;
1150         case 's': s++;
1151         break;
1152         default: usage(US"tidydb", US" [-t <time>]");
1153         }
1154       maxkeep += value;
1155       }
1156     }
1157   else usage(US"tidydb", US" [-t <time>]");
1158   }
1159
1160 /* Adjust argument values and process arguments */
1161
1162 argc -= --i;
1163 argv += i;
1164
1165 dbdata_type = check_args(argc, argv, US"tidydb", US" [-t <time>]");
1166
1167 /* Compute the oldest keep time, verify what we are doing, and open the
1168 database */
1169
1170 oldest = time(NULL) - maxkeep;
1171 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
1172
1173 spool_directory = argv[1];
1174 dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE);
1175 if (dbm == NULL) exit(1);
1176
1177 /* Prepare for building file names */
1178
1179 sprintf(CS buffer, "%s/input/", argv[1]);
1180 path_len = Ustrlen(buffer);
1181
1182
1183 /* It appears, by experiment, that it is a bad idea to make changes
1184 to the file while scanning it. Pity the man page doesn't warn you about that.
1185 Therefore, we scan and build a list of all the keys. Then we use that to
1186 read the records and possibly update them. */
1187
1188 key = dbfn_scan(dbm, TRUE, &cursor);
1189 while (key != NULL)
1190   {
1191   key_item *k = store_get(sizeof(key_item) + Ustrlen(key));
1192   k->next = keychain;
1193   keychain = k;
1194   Ustrcpy(k->key, key);
1195   key = dbfn_scan(dbm, FALSE, &cursor);
1196   }
1197
1198 /* Now scan the collected keys and operate on the records, resetting
1199 the store each time round. */
1200
1201 reset_point = store_get(0);
1202
1203 while (keychain != NULL)
1204   {
1205   dbdata_generic *value;
1206
1207   store_reset(reset_point);
1208   key = keychain->key;
1209   keychain = keychain->next;
1210   value = dbfn_read_with_length(dbm, key, NULL);
1211
1212   /* A continuation record may have been deleted or renamed already, so
1213   non-existence is not serious. */
1214
1215   if (value == NULL) continue;
1216
1217   /* Delete if too old */
1218
1219   if (value->time_stamp < oldest)
1220     {
1221     printf("deleted %s (too old)\n", key);
1222     dbfn_delete(dbm, key);
1223     continue;
1224     }
1225
1226   /* Do database-specific tidying for wait databases, and message-
1227   specific tidying for the retry database. */
1228
1229   if (dbdata_type == type_wait)
1230     {
1231     dbdata_wait *wait = (dbdata_wait *)value;
1232     BOOL update = FALSE;
1233
1234     /* Leave corrupt records alone */
1235
1236     if (wait->count > WAIT_NAME_MAX)
1237       {
1238       printf("**** Data for %s corrupted\n  count=%d=0x%x max=%d\n",
1239         key, wait->count, wait->count, WAIT_NAME_MAX);
1240       continue;
1241       }
1242
1243     /* Loop for renamed continuation records. For each message id,
1244     check to see if the message exists, and if not, remove its entry
1245     from the record. Because of the possibility of split input directories,
1246     we must look in both possible places for a -D file. */
1247
1248     for (;;)
1249       {
1250       int offset;
1251       int length = wait->count * MESSAGE_ID_LENGTH;
1252
1253       for (offset = length - MESSAGE_ID_LENGTH;
1254            offset >= 0; offset -= MESSAGE_ID_LENGTH)
1255         {
1256         Ustrncpy(buffer+path_len, wait->text + offset, MESSAGE_ID_LENGTH);
1257         sprintf(CS(buffer+path_len + MESSAGE_ID_LENGTH), "-D");
1258
1259         if (Ustat(buffer, &statbuf) != 0)
1260           {
1261           buffer[path_len] = wait->text[offset+5];
1262           buffer[path_len+1] = '/';
1263           Ustrncpy(buffer+path_len+2, wait->text + offset, MESSAGE_ID_LENGTH);
1264           sprintf(CS(buffer+path_len+2 + MESSAGE_ID_LENGTH), "-D");
1265
1266           if (Ustat(buffer, &statbuf) != 0)
1267             {
1268             int left = length - offset - MESSAGE_ID_LENGTH;
1269             if (left > 0) Ustrncpy(wait->text + offset,
1270               wait->text + offset + MESSAGE_ID_LENGTH, left);
1271             wait->count--;
1272             length -= MESSAGE_ID_LENGTH;
1273             update = TRUE;
1274             }
1275           }
1276         }
1277
1278       /* If record is empty and the main record, either delete it or rename
1279       the next continuation, repeating if that is also empty. */
1280
1281       if (wait->count == 0 && Ustrchr(key, ':') == NULL)
1282         {
1283         while (wait->count == 0 && wait->sequence > 0)
1284           {
1285           uschar newkey[256];
1286           dbdata_generic *newvalue;
1287           sprintf(CS newkey, "%s:%d", key, wait->sequence - 1);
1288           newvalue = dbfn_read_with_length(dbm, newkey, NULL);
1289           if (newvalue != NULL)
1290             {
1291             value = newvalue;
1292             wait = (dbdata_wait *)newvalue;
1293             dbfn_delete(dbm, newkey);
1294             printf("renamed %s\n", newkey);
1295             update = TRUE;
1296             }
1297           else wait->sequence--;
1298           }
1299
1300         /* If we have ended up with an empty main record, delete it
1301         and break the loop. Otherwise the new record will be scanned. */
1302
1303         if (wait->count == 0 && wait->sequence == 0)
1304           {
1305           dbfn_delete(dbm, key);
1306           printf("deleted %s (empty)\n", key);
1307           update = FALSE;
1308           break;
1309           }
1310         }
1311
1312       /* If not an empty main record, break the loop */
1313
1314       else break;
1315       }
1316
1317     /* Re-write the record if required */
1318
1319     if (update)
1320       {
1321       printf("updated %s\n", key);
1322       dbfn_write(dbm, key, wait, sizeof(dbdata_wait) +
1323         wait->count * MESSAGE_ID_LENGTH);
1324       }
1325     }
1326
1327   /* If a retry record's key ends with a message-id, check that that message
1328   still exists; if not, remove this record. */
1329
1330   else if (dbdata_type == type_retry)
1331     {
1332     uschar *id;
1333     int len = Ustrlen(key);
1334
1335     if (len < MESSAGE_ID_LENGTH + 1) continue;
1336     id = key + len - MESSAGE_ID_LENGTH - 1;
1337     if (*id++ != ':') continue;
1338
1339     for (i = 0; i < MESSAGE_ID_LENGTH; i++)
1340       {
1341       if (i == 6 || i == 13)
1342         { if (id[i] != '-') break; }
1343       else
1344         { if (!isalnum(id[i])) break; }
1345       }
1346     if (i < MESSAGE_ID_LENGTH) continue;
1347
1348     Ustrncpy(buffer + path_len, id, MESSAGE_ID_LENGTH);
1349     sprintf(CS(buffer + path_len + MESSAGE_ID_LENGTH), "-D");
1350
1351     if (Ustat(buffer, &statbuf) != 0)
1352       {
1353       sprintf(CS(buffer + path_len), "%c/%s-D", id[5], id);
1354       if (Ustat(buffer, &statbuf) != 0)
1355         {
1356         dbfn_delete(dbm, key);
1357         printf("deleted %s (no message)\n", key);
1358         }
1359       }
1360     }
1361   }
1362
1363 dbfn_close(dbm);
1364 printf("Tidying complete\n");
1365 return 0;
1366 }
1367
1368 #endif  /* EXIM_TIDYDB */
1369
1370 /* End of exim_dbutil.c */