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