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