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