8513bc0c7a3dc8756212604ba2ee473e715d4698
[users/heiko/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)
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 != NULL) *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 value_datum = value_datum;    /* dummy; not all db libraries use this */
519
520 /* Some dbm require an initialization */
521
522 if (start) EXIM_DBCREATE_CURSOR(dbblock->dbptr, cursor);
523
524 EXIM_DATUM_INIT(key_datum);         /* Some DBM libraries require the datum */
525 EXIM_DATUM_INIT(value_datum);       /* to be cleared before use. */
526
527 yield = (EXIM_DBSCAN(dbblock->dbptr, key_datum, value_datum, start, *cursor))?
528   US EXIM_DATUM_DATA(key_datum) : NULL;
529
530 /* Some dbm require a termination */
531
532 if (!yield) EXIM_DBDELETE_CURSOR(*cursor);
533 return yield;
534 }
535 #endif  /* EXIM_DUMPDB || EXIM_TIDYDB */
536
537
538
539 #ifdef EXIM_DUMPDB
540 /*************************************************
541 *           The exim_dumpdb main program         *
542 *************************************************/
543
544 int
545 main(int argc, char **cargv)
546 {
547 int dbdata_type = 0;
548 int yield = 0;
549 open_db dbblock;
550 open_db *dbm;
551 EXIM_CURSOR *cursor;
552 uschar **argv = USS cargv;
553 uschar keybuffer[1024];
554
555 /* Check the arguments, and open the database */
556
557 dbdata_type = check_args(argc, argv, US"dumpdb", US"");
558 spool_directory = argv[1];
559 if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE, TRUE)))
560   exit(1);
561
562 /* Scan the file, formatting the information for each entry. Note
563 that data is returned in a malloc'ed block, in order that it be
564 correctly aligned. */
565
566 for (uschar * key = dbfn_scan(dbm, TRUE, &cursor);
567      key;
568      key = dbfn_scan(dbm, FALSE, &cursor))
569   {
570   dbdata_retry *retry;
571   dbdata_wait *wait;
572   dbdata_callout_cache *callout;
573   dbdata_ratelimit *ratelimit;
574   dbdata_ratelimit_unique *rate_unique;
575   dbdata_tls_session *session;
576   int count_bad = 0;
577   int length;
578   uschar *t;
579   uschar name[MESSAGE_ID_LENGTH + 1];
580   void *value;
581   rmark reset_point = store_mark();
582
583   /* Keep a copy of the key separate, as in some DBM's the pointer is into data
584   which might change. */
585
586   if (Ustrlen(key) > sizeof(keybuffer) - 1)
587     {
588     printf("**** Overlong key encountered: %s\n", key);
589     return 1;
590     }
591   Ustrcpy(keybuffer, key);
592
593   if (!(value = dbfn_read_with_length(dbm, keybuffer, &length)))
594     fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
595                     "was not found in the file - something is wrong!\n",
596       CS keybuffer);
597   else
598     {
599     /* Note: don't use print_time more than once in one statement, since
600     it uses a single buffer. */
601
602     switch(dbdata_type)
603       {
604       case type_retry:
605         retry = (dbdata_retry *)value;
606         printf("  %s %d %d %s\n%s  ", keybuffer, retry->basic_errno,
607           retry->more_errno, retry->text,
608           print_time(retry->first_failed));
609         printf("%s  ", print_time(retry->last_try));
610         printf("%s %s\n", print_time(retry->next_try),
611           (retry->expired)? "*" : "");
612         break;
613
614       case type_wait:
615         wait = (dbdata_wait *)value;
616         printf("%s ", keybuffer);
617         t = wait->text;
618         name[MESSAGE_ID_LENGTH] = 0;
619
620         if (wait->count > WAIT_NAME_MAX)
621           {
622           fprintf(stderr,
623             "**** Data for %s corrupted\n  count=%d=0x%x max=%d\n",
624             CS keybuffer, wait->count, wait->count, WAIT_NAME_MAX);
625           wait->count = WAIT_NAME_MAX;
626           yield = count_bad = 1;
627           }
628         for (int i = 1; i <= wait->count; i++)
629           {
630           Ustrncpy(name, t, MESSAGE_ID_LENGTH);
631           if (count_bad && name[0] == 0) break;
632           if (Ustrlen(name) != MESSAGE_ID_LENGTH ||
633               Ustrspn(name, "0123456789"
634                             "abcdefghijklmnopqrstuvwxyz"
635                             "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
636             {
637             fprintf(stderr,
638               "**** Data for %s corrupted: bad character in message id\n",
639               CS keybuffer);
640             for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
641               fprintf(stderr, "%02x ", name[j]);
642             fprintf(stderr, "\n");
643             yield = 1;
644             break;
645             }
646           printf("%s ", name);
647           t += MESSAGE_ID_LENGTH;
648           }
649         printf("\n");
650         break;
651
652       case type_misc:
653         printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
654           keybuffer);
655         break;
656
657       case type_callout:
658         callout = (dbdata_callout_cache *)value;
659
660         /* New-style address record */
661
662         if (length == sizeof(dbdata_callout_cache_address))
663           {
664           printf("%s %s callout=%s\n",
665             print_time(((dbdata_generic *)value)->time_stamp),
666             keybuffer,
667             print_cache(callout->result));
668           }
669
670         /* New-style domain record */
671
672         else if (length == sizeof(dbdata_callout_cache))
673           {
674           printf("%s %s callout=%s postmaster=%s",
675             print_time(((dbdata_generic *)value)->time_stamp),
676             keybuffer,
677             print_cache(callout->result),
678             print_cache(callout->postmaster_result));
679           if (callout->postmaster_result != ccache_unknown)
680             printf(" (%s)", print_time(callout->postmaster_stamp));
681           printf(" random=%s", print_cache(callout->random_result));
682           if (callout->random_result != ccache_unknown)
683             printf(" (%s)", print_time(callout->random_stamp));
684           printf("\n");
685           }
686
687         break;
688
689       case type_ratelimit:
690         if (Ustrstr(key, "/unique/") != NULL && length >= sizeof(*rate_unique))
691           {
692           ratelimit = (dbdata_ratelimit *)value;
693           rate_unique = (dbdata_ratelimit_unique *)value;
694           printf("%s.%06d rate: %10.3f epoch: %s size: %u key: %s\n",
695             print_time(ratelimit->time_stamp),
696             ratelimit->time_usec, ratelimit->rate,
697             print_time(rate_unique->bloom_epoch), rate_unique->bloom_size,
698             keybuffer);
699           }
700         else
701           {
702           ratelimit = (dbdata_ratelimit *)value;
703           printf("%s.%06d rate: %10.3f key: %s\n",
704             print_time(ratelimit->time_stamp),
705             ratelimit->time_usec, ratelimit->rate,
706             keybuffer);
707           }
708         break;
709
710       case type_tls:
711         session = (dbdata_tls_session *)value;
712         printf("  %s %.*s\n", keybuffer, length, session->session);
713         break;
714       }
715     }
716   store_reset(reset_point);
717   }
718
719 dbfn_close(dbm);
720 return yield;
721 }
722
723 #endif  /* EXIM_DUMPDB */
724
725
726
727
728 #ifdef EXIM_FIXDB
729 /*************************************************
730 *           The exim_fixdb main program          *
731 *************************************************/
732
733 /* In order not to hold the database lock any longer than is necessary, each
734 operation on the database uses a separate open/close call. This is expensive,
735 but then using this utility is not expected to be very common. Its main use is
736 to provide a way of patching up hints databases in order to run tests.
737
738 Syntax of commands:
739
740 (1) <record name>
741     This causes the data from the given record to be displayed, or "not found"
742     to be output. Note that in the retry database, destination names are
743     preceded by R: or T: for router or transport retry info.
744
745 (2) <record name> d
746     This causes the given record to be deleted or "not found" to be output.
747
748 (3) <record name> <field number> <value>
749     This sets the given value into the given field, identified by a number
750     which is output by the display command. Not all types of record can
751     be changed.
752
753 (4) q
754     This exits from exim_fixdb.
755
756 If the record name is omitted from (2) or (3), the previously used record name
757 is re-used. */
758
759
760 int main(int argc, char **cargv)
761 {
762 int dbdata_type;
763 uschar **argv = USS cargv;
764 uschar buffer[256];
765 uschar name[256];
766 rmark reset_point;
767
768 name[0] = 0;  /* No name set */
769
770 /* Sort out the database type, verify what we are working on and then process
771 user requests */
772
773 dbdata_type = check_args(argc, argv, US"fixdb", US"");
774 printf("Modifying Exim hints database %s/db/%s\n", argv[1], argv[2]);
775
776 for(; (reset_point = store_mark()); store_reset(reset_point))
777   {
778   open_db dbblock;
779   open_db *dbm;
780   void *record;
781   dbdata_retry *retry;
782   dbdata_wait *wait;
783   dbdata_callout_cache *callout;
784   dbdata_ratelimit *ratelimit;
785   dbdata_ratelimit_unique *rate_unique;
786   dbdata_tls_session *session;
787   int oldlength;
788   uschar *t;
789   uschar field[256], value[256];
790
791   printf("> ");
792   if (Ufgets(buffer, 256, stdin) == NULL) break;
793
794   buffer[Ustrlen(buffer)-1] = 0;
795   field[0] = value[0] = 0;
796
797   /* If the buffer contains just one digit, or just consists of "d", use the
798   previous name for an update. */
799
800   if ((isdigit((uschar)buffer[0]) && (buffer[1] == ' ' || buffer[1] == '\0'))
801        || Ustrcmp(buffer, "d") == 0)
802     {
803     if (name[0] == 0)
804       {
805       printf("No previous record name is set\n");
806       continue;
807       }
808     (void)sscanf(CS buffer, "%s %s", field, value);
809     }
810   else
811     {
812     name[0] = 0;
813     (void)sscanf(CS buffer, "%s %s %s", name, field, value);
814     }
815
816   /* Handle an update request */
817
818   if (field[0] != 0)
819     {
820     int verify = 1;
821     spool_directory = argv[1];
822
823     if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE, TRUE)))
824       continue;
825
826     if (Ustrcmp(field, "d") == 0)
827       {
828       if (value[0] != 0) printf("unexpected value after \"d\"\n");
829         else printf("%s\n", (dbfn_delete(dbm, name) < 0)?
830           "not found" : "deleted");
831       dbfn_close(dbm);
832       continue;
833       }
834
835     else if (isdigit((uschar)field[0]))
836       {
837       int fieldno = Uatoi(field);
838       if (value[0] == 0)
839         {
840         printf("value missing\n");
841         dbfn_close(dbm);
842         continue;
843         }
844       else
845         {
846         record = dbfn_read_with_length(dbm, name, &oldlength);
847         if (record == NULL) printf("not found\n"); else
848           {
849           time_t tt;
850           /*int length = 0;      Stops compiler warning */
851
852           switch(dbdata_type)
853             {
854             case type_retry:
855               retry = (dbdata_retry *)record;
856               /* length = sizeof(dbdata_retry) + Ustrlen(retry->text); */
857
858               switch(fieldno)
859                 {
860                 case 0: retry->basic_errno = Uatoi(value);
861                         break;
862                 case 1: retry->more_errno = Uatoi(value);
863                         break;
864                 case 2: if ((tt = read_time(value)) > 0) retry->first_failed = tt;
865                         else printf("bad time value\n");
866                         break;
867                 case 3: if ((tt = read_time(value)) > 0) retry->last_try = tt;
868                         else printf("bad time value\n");
869                         break;
870                 case 4: if ((tt = read_time(value)) > 0) retry->next_try = tt;
871                         else printf("bad time value\n");
872                         break;
873                 case 5: if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE;
874                         else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE;
875                         else printf("\"yes\" or \"no\" expected=n");
876                         break;
877                 default: printf("unknown field number\n");
878                          verify = 0;
879                          break;
880                 }
881               break;
882
883             case type_wait:
884               printf("Can't change contents of wait database record\n");
885               break;
886
887             case type_misc:
888               printf("Can't change contents of misc database record\n");
889               break;
890
891             case type_callout:
892               callout = (dbdata_callout_cache *)record;
893               /* length = sizeof(dbdata_callout_cache); */
894               switch(fieldno)
895                 {
896                 case 0: callout->result = Uatoi(value);
897                         break;
898                 case 1: callout->postmaster_result = Uatoi(value);
899                         break;
900                 case 2: callout->random_result = Uatoi(value);
901                         break;
902                 default: printf("unknown field number\n");
903                          verify = 0;
904                          break;
905                 }
906                 break;
907
908             case type_ratelimit:
909               ratelimit = (dbdata_ratelimit *)record;
910               switch(fieldno)
911                 {
912                 case 0: if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt;
913                         else printf("bad time value\n");
914                         break;
915                 case 1: ratelimit->time_usec = Uatoi(value);
916                         break;
917                 case 2: ratelimit->rate = Ustrtod(value, NULL);
918                         break;
919                 case 3: if (Ustrstr(name, "/unique/") != NULL
920                             && oldlength >= sizeof(dbdata_ratelimit_unique))
921                           {
922                           rate_unique = (dbdata_ratelimit_unique *)record;
923                           if ((tt = read_time(value)) > 0) rate_unique->bloom_epoch = tt;
924                             else printf("bad time value\n");
925                           break;
926                           }
927                         /* else fall through */
928                 case 4:
929                 case 5: if (Ustrstr(name, "/unique/") != NULL
930                             && oldlength >= sizeof(dbdata_ratelimit_unique))
931                           {
932                           /* see acl.c */
933                           BOOL seen;
934                           unsigned hash, hinc;
935                           uschar md5sum[16];
936                           md5 md5info;
937                           md5_start(&md5info);
938                           md5_end(&md5info, value, Ustrlen(value), md5sum);
939                           hash = md5sum[0] <<  0 | md5sum[1] <<  8
940                                | md5sum[2] << 16 | md5sum[3] << 24;
941                           hinc = md5sum[4] <<  0 | md5sum[5] <<  8
942                                | md5sum[6] << 16 | md5sum[7] << 24;
943                           rate_unique = (dbdata_ratelimit_unique *)record;
944                           seen = TRUE;
945                           for (unsigned n = 0; n < 8; n++, hash += hinc)
946                             {
947                             int bit = 1 << (hash % 8);
948                             int byte = (hash / 8) % rate_unique->bloom_size;
949                             if ((rate_unique->bloom[byte] & bit) == 0)
950                               {
951                               seen = FALSE;
952                               if (fieldno == 5) rate_unique->bloom[byte] |= bit;
953                               }
954                             }
955                           printf("%s %s\n",
956                             seen ? "seen" : fieldno == 5 ? "added" : "unseen", value);
957                           break;
958                           }
959                         /* else fall through */
960                 default: printf("unknown field number\n");
961                          verify = 0;
962                          break;
963                 }
964               break;
965
966             case type_tls:
967               printf("Can't change contents of tls database record\n");
968               break;
969             }
970
971           dbfn_write(dbm, name, record, oldlength);
972           }
973         }
974       }
975
976     else
977       {
978       printf("field number or d expected\n");
979       verify = 0;
980       }
981
982     dbfn_close(dbm);
983     if (!verify) continue;
984     }
985
986   /* The "name" q causes an exit */
987
988   else if (Ustrcmp(name, "q") == 0) return 0;
989
990   /* Handle a read request, or verify after an update. */
991
992   spool_directory = argv[1];
993   if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE, TRUE)))
994     continue;
995
996   if (!(record = dbfn_read_with_length(dbm, name, &oldlength)))
997     {
998     printf("record %s not found\n", name);
999     name[0] = 0;
1000     }
1001   else
1002     {
1003     int count_bad = 0;
1004     printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp));
1005     switch(dbdata_type)
1006       {
1007       case type_retry:
1008         retry = (dbdata_retry *)record;
1009         printf("0 error number: %d %s\n", retry->basic_errno, retry->text);
1010         printf("1 extra data:   %d\n", retry->more_errno);
1011         printf("2 first failed: %s\n", print_time(retry->first_failed));
1012         printf("3 last try:     %s\n", print_time(retry->last_try));
1013         printf("4 next try:     %s\n", print_time(retry->next_try));
1014         printf("5 expired:      %s\n", (retry->expired)? "yes" : "no");
1015         break;
1016
1017       case type_wait:
1018         wait = (dbdata_wait *)record;
1019         t = wait->text;
1020         printf("Sequence: %d\n", wait->sequence);
1021         if (wait->count > WAIT_NAME_MAX)
1022           {
1023           printf("**** Data corrupted: count=%d=0x%x max=%d ****\n", wait->count,
1024             wait->count, WAIT_NAME_MAX);
1025           wait->count = WAIT_NAME_MAX;
1026           count_bad = 1;
1027           }
1028         for (int i = 1; i <= wait->count; i++)
1029           {
1030           Ustrncpy(value, t, MESSAGE_ID_LENGTH);
1031           value[MESSAGE_ID_LENGTH] = 0;
1032           if (count_bad && value[0] == 0) break;
1033           if (Ustrlen(value) != MESSAGE_ID_LENGTH ||
1034               Ustrspn(value, "0123456789"
1035                             "abcdefghijklmnopqrstuvwxyz"
1036                             "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
1037             {
1038             printf("\n**** Data corrupted: bad character in message id ****\n");
1039             for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
1040               printf("%02x ", value[j]);
1041             printf("\n");
1042             break;
1043             }
1044           printf("%s ", value);
1045           t += MESSAGE_ID_LENGTH;
1046           }
1047         printf("\n");
1048         break;
1049
1050       case type_misc:
1051         break;
1052
1053       case type_callout:
1054         callout = (dbdata_callout_cache *)record;
1055         printf("0 callout:    %s (%d)\n", print_cache(callout->result),
1056             callout->result);
1057         if (oldlength > sizeof(dbdata_callout_cache_address))
1058           {
1059           printf("1 postmaster: %s (%d)\n", print_cache(callout->postmaster_result),
1060               callout->postmaster_result);
1061           printf("2 random:     %s (%d)\n", print_cache(callout->random_result),
1062               callout->random_result);
1063           }
1064         break;
1065
1066       case type_ratelimit:
1067         ratelimit = (dbdata_ratelimit *)record;
1068         printf("0 time stamp:  %s\n", print_time(ratelimit->time_stamp));
1069         printf("1 fract. time: .%06d\n", ratelimit->time_usec);
1070         printf("2 sender rate: % .3f\n", ratelimit->rate);
1071         if (Ustrstr(name, "/unique/") != NULL
1072          && oldlength >= sizeof(dbdata_ratelimit_unique))
1073          {
1074          rate_unique = (dbdata_ratelimit_unique *)record;
1075          printf("3 filter epoch: %s\n", print_time(rate_unique->bloom_epoch));
1076          printf("4 test filter membership\n");
1077          printf("5 add element to filter\n");
1078          }
1079         break;
1080
1081       case type_tls:
1082         session = (dbdata_tls_session *)value;
1083         printf("0 time stamp:  %s\n", print_time(session->time_stamp));
1084         printf("1 session: .%s\n", session->session);
1085         break;
1086       }
1087     }
1088
1089   /* The database is closed after each request */
1090
1091   dbfn_close(dbm);
1092   }
1093
1094 printf("\n");
1095 return 0;
1096 }
1097
1098 #endif  /* EXIM_FIXDB */
1099
1100
1101
1102 #ifdef EXIM_TIDYDB
1103 /*************************************************
1104 *           The exim_tidydb main program         *
1105 *************************************************/
1106
1107
1108 /* Utility program to tidy the contents of an exim database file. There is one
1109 option:
1110
1111    -t <time>  expiry time for old records - default 30 days
1112
1113 For backwards compatibility, an -f option is recognized and ignored. (It used
1114 to request a "full" tidy. This version always does the whole job.) */
1115
1116
1117 typedef struct key_item {
1118   struct key_item *next;
1119   uschar key[1];
1120 } key_item;
1121
1122
1123 int main(int argc, char **cargv)
1124 {
1125 struct stat statbuf;
1126 int maxkeep = 30 * 24 * 60 * 60;
1127 int dbdata_type, i, oldest, path_len;
1128 key_item *keychain = NULL;
1129 rmark reset_point;
1130 open_db dbblock;
1131 open_db *dbm;
1132 EXIM_CURSOR *cursor;
1133 uschar **argv = USS cargv;
1134 uschar buffer[256];
1135 uschar *key;
1136
1137 /* Scan the options */
1138
1139 for (i = 1; i < argc; i++)
1140   {
1141   if (argv[i][0] != '-') break;
1142   if (Ustrcmp(argv[i], "-f") == 0) continue;
1143   if (Ustrcmp(argv[i], "-t") == 0)
1144     {
1145     uschar *s;
1146     s = argv[++i];
1147     maxkeep = 0;
1148     while (*s != 0)
1149       {
1150       int value, count;
1151       if (!isdigit(*s)) usage(US"tidydb", US" [-t <time>]");
1152       (void)sscanf(CS s, "%d%n", &value, &count);
1153       s += count;
1154       switch (*s)
1155         {
1156         case 'w': value *= 7;
1157         case 'd': value *= 24;
1158         case 'h': value *= 60;
1159         case 'm': value *= 60;
1160         case 's': s++;
1161         break;
1162         default: usage(US"tidydb", US" [-t <time>]");
1163         }
1164       maxkeep += value;
1165       }
1166     }
1167   else usage(US"tidydb", US" [-t <time>]");
1168   }
1169
1170 /* Adjust argument values and process arguments */
1171
1172 argc -= --i;
1173 argv += i;
1174
1175 dbdata_type = check_args(argc, argv, US"tidydb", US" [-t <time>]");
1176
1177 /* Compute the oldest keep time, verify what we are doing, and open the
1178 database */
1179
1180 oldest = time(NULL) - maxkeep;
1181 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
1182
1183 spool_directory = argv[1];
1184 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE, TRUE)))
1185   exit(1);
1186
1187 /* Prepare for building file names */
1188
1189 sprintf(CS buffer, "%s/input/", argv[1]);
1190 path_len = Ustrlen(buffer);
1191
1192
1193 /* It appears, by experiment, that it is a bad idea to make changes
1194 to the file while scanning it. Pity the man page doesn't warn you about that.
1195 Therefore, we scan and build a list of all the keys. Then we use that to
1196 read the records and possibly update them. */
1197
1198 for (key = dbfn_scan(dbm, TRUE, &cursor);
1199      key;
1200      key = dbfn_scan(dbm, FALSE, &cursor))
1201   {
1202   key_item *k = store_get(sizeof(key_item) + Ustrlen(key), is_tainted(key));
1203   k->next = keychain;
1204   keychain = k;
1205   Ustrcpy(k->key, key);
1206   }
1207
1208 /* Now scan the collected keys and operate on the records, resetting
1209 the store each time round. */
1210
1211 for (; keychain && (reset_point = store_mark()); store_reset(reset_point))
1212   {
1213   dbdata_generic *value;
1214
1215   key = keychain->key;
1216   keychain = keychain->next;
1217   value = dbfn_read_with_length(dbm, key, NULL);
1218
1219   /* A continuation record may have been deleted or renamed already, so
1220   non-existence is not serious. */
1221
1222   if (value == NULL) continue;
1223
1224   /* Delete if too old */
1225
1226   if (value->time_stamp < oldest)
1227     {
1228     printf("deleted %s (too old)\n", key);
1229     dbfn_delete(dbm, key);
1230     continue;
1231     }
1232
1233   /* Do database-specific tidying for wait databases, and message-
1234   specific tidying for the retry database. */
1235
1236   if (dbdata_type == type_wait)
1237     {
1238     dbdata_wait *wait = (dbdata_wait *)value;
1239     BOOL update = FALSE;
1240
1241     /* Leave corrupt records alone */
1242
1243     if (wait->count > WAIT_NAME_MAX)
1244       {
1245       printf("**** Data for %s corrupted\n  count=%d=0x%x max=%d\n",
1246         key, wait->count, wait->count, WAIT_NAME_MAX);
1247       continue;
1248       }
1249
1250     /* Loop for renamed continuation records. For each message id,
1251     check to see if the message exists, and if not, remove its entry
1252     from the record. Because of the possibility of split input directories,
1253     we must look in both possible places for a -D file. */
1254
1255     for (;;)
1256       {
1257       int length = wait->count * MESSAGE_ID_LENGTH;
1258
1259       for (int offset = length - MESSAGE_ID_LENGTH;
1260            offset >= 0; offset -= MESSAGE_ID_LENGTH)
1261         {
1262         Ustrncpy(buffer+path_len, wait->text + offset, MESSAGE_ID_LENGTH);
1263         sprintf(CS(buffer+path_len + MESSAGE_ID_LENGTH), "-D");
1264
1265         if (Ustat(buffer, &statbuf) != 0)
1266           {
1267           buffer[path_len] = wait->text[offset+5];
1268           buffer[path_len+1] = '/';
1269           Ustrncpy(buffer+path_len+2, wait->text + offset, MESSAGE_ID_LENGTH);
1270           sprintf(CS(buffer+path_len+2 + MESSAGE_ID_LENGTH), "-D");
1271
1272           if (Ustat(buffer, &statbuf) != 0)
1273             {
1274             int left = length - offset - MESSAGE_ID_LENGTH;
1275             if (left > 0) Ustrncpy(wait->text + offset,
1276               wait->text + offset + MESSAGE_ID_LENGTH, left);
1277             wait->count--;
1278             length -= MESSAGE_ID_LENGTH;
1279             update = TRUE;
1280             }
1281           }
1282         }
1283
1284       /* If record is empty and the main record, either delete it or rename
1285       the next continuation, repeating if that is also empty. */
1286
1287       if (wait->count == 0 && Ustrchr(key, ':') == NULL)
1288         {
1289         while (wait->count == 0 && wait->sequence > 0)
1290           {
1291           uschar newkey[256];
1292           dbdata_generic *newvalue;
1293           sprintf(CS newkey, "%s:%d", key, wait->sequence - 1);
1294           newvalue = dbfn_read_with_length(dbm, newkey, NULL);
1295           if (newvalue != NULL)
1296             {
1297             value = newvalue;
1298             wait = (dbdata_wait *)newvalue;
1299             dbfn_delete(dbm, newkey);
1300             printf("renamed %s\n", newkey);
1301             update = TRUE;
1302             }
1303           else wait->sequence--;
1304           }
1305
1306         /* If we have ended up with an empty main record, delete it
1307         and break the loop. Otherwise the new record will be scanned. */
1308
1309         if (wait->count == 0 && wait->sequence == 0)
1310           {
1311           dbfn_delete(dbm, key);
1312           printf("deleted %s (empty)\n", key);
1313           update = FALSE;
1314           break;
1315           }
1316         }
1317
1318       /* If not an empty main record, break the loop */
1319
1320       else break;
1321       }
1322
1323     /* Re-write the record if required */
1324
1325     if (update)
1326       {
1327       printf("updated %s\n", key);
1328       dbfn_write(dbm, key, wait, sizeof(dbdata_wait) +
1329         wait->count * MESSAGE_ID_LENGTH);
1330       }
1331     }
1332
1333   /* If a retry record's key ends with a message-id, check that that message
1334   still exists; if not, remove this record. */
1335
1336   else if (dbdata_type == type_retry)
1337     {
1338     uschar *id;
1339     int len = Ustrlen(key);
1340
1341     if (len < MESSAGE_ID_LENGTH + 1) continue;
1342     id = key + len - MESSAGE_ID_LENGTH - 1;
1343     if (*id++ != ':') continue;
1344
1345     for (i = 0; i < MESSAGE_ID_LENGTH; i++)
1346       if (i == 6 || i == 13)
1347         { if (id[i] != '-') break; }
1348       else
1349         { if (!isalnum(id[i])) break; }
1350     if (i < MESSAGE_ID_LENGTH) continue;
1351
1352     Ustrncpy(buffer + path_len, id, MESSAGE_ID_LENGTH);
1353     sprintf(CS(buffer + path_len + MESSAGE_ID_LENGTH), "-D");
1354
1355     if (Ustat(buffer, &statbuf) != 0)
1356       {
1357       sprintf(CS(buffer + path_len), "%c/%s-D", id[5], id);
1358       if (Ustat(buffer, &statbuf) != 0)
1359         {
1360         dbfn_delete(dbm, key);
1361         printf("deleted %s (no message)\n", key);
1362         }
1363       }
1364     }
1365   }
1366
1367 dbfn_close(dbm);
1368 printf("Tidying complete\n");
1369 return 0;
1370 }
1371
1372 #endif  /* EXIM_TIDYDB */
1373
1374 /* End of exim_dbutil.c */