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