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