]> git.netwichtig.de Git - user/henk/code/exim.git/blob - src/src/exim_dbutil.c
5c7b6650ed5726f00160ea63008ff36d217e9877
[user/henk/code/exim.git] / src / src / exim_dbutil.c
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4
5 /* Copyright (c) University of Cambridge 1995 - 2018 */
6 /* Copyright (c) The Exim Maintainers 2020 */
7 /* See the file NOTICE for conditions of use and distribution. */
8
9
10 /* This single source file is used to compile three utility programs for
11 maintaining Exim hints databases.
12
13   exim_dumpdb     dumps out the contents
14   exim_fixdb      patches the database (really for Exim maintenance/testing)
15   exim_tidydb     removed obsolete data
16
17 In all cases, the first argument is the name of the spool directory. The second
18 argument is the name of the database file. The available names are:
19
20   retry:      retry delivery information
21   misc:       miscellaneous hints data
22   wait-<t>:   message waiting information; <t> is a transport name
23   callout:    callout verification cache
24   tls:        TLS session resumption cache
25
26 There are a number of common subroutines, followed by three main programs,
27 whose inclusion is controlled by -D on the compilation command. */
28
29
30 #include "exim.h"
31
32
33 /* Identifiers for the different database types. */
34
35 #define type_retry     1
36 #define type_wait      2
37 #define type_misc      3
38 #define type_callout   4
39 #define type_ratelimit 5
40 #define type_tls       6
41
42
43 /* This is used by our cut-down dbfn_open(). */
44
45 uschar *spool_directory;
46
47
48 /******************************************************************************/
49       /* dummies needed by Solaris build */
50 void
51 millisleep(int msec)
52 {}
53 uschar *
54 readconf_printtime(int t)
55 { return NULL; }
56 gstring *
57 string_vformat_trc(gstring * g, const uschar * func, unsigned line,
58   unsigned size_limit, unsigned flags, const char *format, va_list ap)
59 { return NULL; }
60 uschar *
61 string_sprintf_trc(const char * fmt, const uschar * func, unsigned line, ...)
62 { return NULL; }
63 BOOL
64 string_format_trc(uschar * buf, int len, const uschar * func, unsigned line,
65   const char * fmt, ...)
66 { return FALSE; }
67
68 struct global_flags     f;
69 unsigned int            log_selector[1];
70 uschar *                queue_name;
71 BOOL                    split_spool_directory;
72 /******************************************************************************/
73
74
75 /*************************************************
76 *         Berkeley DB error callback             *
77 *************************************************/
78
79 /* For Berkeley DB >= 2, we can define a function to be called in case of DB
80 errors. This should help with debugging strange DB problems, e.g. getting "File
81 exists" when you try to open a db file. The API changed at release 4.3. */
82
83 #if defined(USE_DB) && defined(DB_VERSION_STRING)
84 void
85 #if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
86 dbfn_bdb_error_callback(const DB_ENV *dbenv, const char *pfx, const char *msg)
87 {
88 dbenv = dbenv;
89 #else
90 dbfn_bdb_error_callback(const char *pfx, char *msg)
91 {
92 #endif
93 pfx = pfx;
94 printf("Berkeley DB error: %s\n", msg);
95 }
96 #endif
97
98
99
100 /*************************************************
101 *              SIGALRM handler                   *
102 *************************************************/
103
104 SIGNAL_BOOL sigalrm_seen;
105
106 void
107 sigalrm_handler(int sig)
108 {
109 sig = sig;            /* Keep picky compilers happy */
110 sigalrm_seen = 1;
111 }
112
113
114
115 /*************************************************
116 *        Output usage message and exit           *
117 *************************************************/
118
119 static void
120 usage(uschar *name, uschar *options)
121 {
122 printf("Usage: exim_%s%s  <spool-directory> <database-name>\n", name, options);
123 printf("  <database-name> = retry | misc | wait-<transport-name> | callout | ratelimit | tls\n");
124 exit(1);
125 }
126
127
128
129 /*************************************************
130 *           Sort out the command arguments       *
131 *************************************************/
132
133 /* This function checks that there are exactly 2 arguments, and checks the
134 second of them to be sure it is a known database name. */
135
136 static int
137 check_args(int argc, uschar **argv, uschar *name, uschar *options)
138 {
139 if (argc == 3)
140   {
141   if (Ustrcmp(argv[2], "retry") == 0) return type_retry;
142   if (Ustrcmp(argv[2], "misc") == 0) return type_misc;
143   if (Ustrncmp(argv[2], "wait-", 5) == 0) return type_wait;
144   if (Ustrcmp(argv[2], "callout") == 0) return type_callout;
145   if (Ustrcmp(argv[2], "ratelimit") == 0) return type_ratelimit;
146   if (Ustrcmp(argv[2], "tls") == 0) return type_tls;
147   }
148 usage(name, options);
149 return -1;              /* Never obeyed */
150 }
151
152
153
154 /*************************************************
155 *         Handle attempts to write the log       *
156 *************************************************/
157
158 /* The message gets written to stderr when log_write() is called from a
159 utility. The message always gets '\n' added on the end of it. These calls come
160 from modules such as store.c when things go drastically wrong (e.g. malloc()
161 failing). In normal use they won't get obeyed.
162
163 Arguments:
164   selector  not relevant when running a utility
165   flags     not relevant when running a utility
166   format    a printf() format
167   ...       arguments for format
168
169 Returns:    nothing
170 */
171
172 void
173 log_write(unsigned int selector, int flags, const char *format, ...)
174 {
175 va_list ap;
176 va_start(ap, format);
177 vfprintf(stderr, format, ap);
178 fprintf(stderr, "\n");
179 va_end(ap);
180 selector = selector;     /* Keep picky compilers happy */
181 flags = flags;
182 }
183
184
185
186 /*************************************************
187 *        Format a time value for printing        *
188 *************************************************/
189
190 static uschar time_buffer[sizeof("09-xxx-1999 hh:mm:ss  ")];
191
192 uschar *
193 print_time(time_t t)
194 {
195 struct tm *tmstr = localtime(&t);
196 Ustrftime(time_buffer, sizeof(time_buffer), "%d-%b-%Y %H:%M:%S", tmstr);
197 return time_buffer;
198 }
199
200
201
202 /*************************************************
203 *        Format a cache value for printing       *
204 *************************************************/
205
206 uschar *
207 print_cache(int value)
208 {
209 return (value == ccache_accept)? US"accept" :
210        (value == ccache_reject)? US"reject" :
211        US"unknown";
212 }
213
214
215 #ifdef EXIM_FIXDB
216 /*************************************************
217 *                Read time value                 *
218 *************************************************/
219
220 static time_t
221 read_time(uschar *s)
222 {
223 int field = 0;
224 int value;
225 time_t now = time(NULL);
226 struct tm *tm = localtime(&now);
227
228 tm->tm_sec = 0;
229 tm->tm_isdst = -1;
230
231 for (uschar * t = s + Ustrlen(s) - 1; t >= s; t--)
232   {
233   if (*t == ':') continue;
234   if (!isdigit((uschar)*t)) return -1;
235
236   value = *t - '0';
237   if (--t >= s)
238     {
239     if (!isdigit((uschar)*t)) return -1;
240     value = value + (*t - '0')*10;
241     }
242
243   switch (field++)
244     {
245     case 0: tm->tm_min = value; break;
246     case 1: tm->tm_hour = value; break;
247     case 2: tm->tm_mday = value; break;
248     case 3: tm->tm_mon = value - 1; break;
249     case 4: tm->tm_year = (value < 90)? value + 100 : value; break;
250     default: return -1;
251     }
252   }
253
254 return mktime(tm);
255 }
256 #endif  /* EXIM_FIXDB */
257
258
259
260 /*************************************************
261 *       Open and lock a database file            *
262 *************************************************/
263
264 /* This is a cut-down version from the function in dbfn.h that Exim itself
265 uses. We assume the database exists, and therefore give up if we cannot open
266 the lock file.
267
268 Arguments:
269   name     The single-component name of one of Exim's database files.
270   flags    O_RDONLY or O_RDWR
271   dbblock  Points to an open_db block to be filled in.
272   lof      Unused.
273   panic    Unused
274
275 Returns:   NULL if the open failed, or the locking failed.
276            On success, dbblock is returned. This contains the dbm pointer and
277            the fd of the locked lock file.
278 */
279
280 open_db *
281 dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof, BOOL panic)
282 {
283 int rc;
284 struct flock lock_data;
285 BOOL read_only = flags == O_RDONLY;
286 uschar * dirname, * filename;
287
288 /* The first thing to do is to open a separate file on which to lock. This
289 ensures that Exim has exclusive use of the database before it even tries to
290 open it. If there is a database, there should be a lock file in existence. */
291
292 #ifdef COMPILE_UTILITY
293 if (  asprintf(CSS &dirname, "%s/db", spool_directory) < 0
294    || asprintf(CSS &filename, "%s/%s.lockfile", dirname, name) < 0)
295   return NULL;
296 #else
297 dirname = string_sprintf("%s/db", spool_directory);
298 filename = string_sprintf("%s/%s.lockfile", dirname, name);
299 #endif
300
301 dbblock->lockfd = Uopen(filename, flags, 0);
302 if (dbblock->lockfd < 0)
303   {
304   printf("** Failed to open database lock file %s: %s\n", filename,
305     strerror(errno));
306   return NULL;
307   }
308
309 /* Now we must get a lock on the opened lock file; do this with a blocking
310 lock that times out. */
311
312 lock_data.l_type = read_only ? F_RDLCK : F_WRLCK;
313 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
314
315 sigalrm_seen = FALSE;
316 os_non_restarting_signal(SIGALRM, sigalrm_handler);
317 ALARM(EXIMDB_LOCK_TIMEOUT);
318 rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
319 ALARM_CLR(0);
320
321 if (sigalrm_seen) errno = ETIMEDOUT;
322 if (rc < 0)
323   {
324   printf("** Failed to get %s lock for %s: %s",
325     flags & O_WRONLY ? "write" : "read",
326     filename,
327     errno == ETIMEDOUT ? "timed out" : strerror(errno));
328   (void)close(dbblock->lockfd);
329   return NULL;
330   }
331
332 /* At this point we have an opened and locked separate lock file, that is,
333 exclusive access to the database, so we can go ahead and open it. */
334
335 #ifdef COMPILE_UTILITY
336 if (asprintf(CSS &filename, "%s/%s", dirname, name) < 0) return NULL;
337 #else
338 filename = string_sprintf("%s/%s", dirname, name);
339 #endif
340 EXIM_DBOPEN(filename, dirname, flags, 0, &dbblock->dbptr);
341
342 if (!dbblock->dbptr)
343   {
344   printf("** Failed to open DBM file %s for %s:\n   %s%s\n", filename,
345     read_only? "reading" : "writing", strerror(errno),
346     #ifdef USE_DB
347     " (or Berkeley DB error while opening)"
348     #else
349     ""
350     #endif
351     );
352   (void)close(dbblock->lockfd);
353   return NULL;
354   }
355
356 return dbblock;
357 }
358
359
360
361
362 /*************************************************
363 *         Unlock and close a database file       *
364 *************************************************/
365
366 /* Closing a file automatically unlocks it, so after closing the database, just
367 close the lock file.
368
369 Argument: a pointer to an open database block
370 Returns:  nothing
371 */
372
373 void
374 dbfn_close(open_db *dbblock)
375 {
376 EXIM_DBCLOSE(dbblock->dbptr);
377 (void)close(dbblock->lockfd);
378 }
379
380
381
382
383 /*************************************************
384 *             Read from database file            *
385 *************************************************/
386
387 /* Passing back the pointer unchanged is useless, because there is no guarantee
388 of alignment. Since all the records used by Exim need to be properly aligned to
389 pick out the timestamps, etc., do the copying centrally here.
390
391 Arguments:
392   dbblock   a pointer to an open database block
393   key       the key of the record to be read
394   length    where to put the length (or NULL if length not wanted)
395
396 Returns: a pointer to the retrieved record, or
397          NULL if the record is not found
398 */
399
400 void *
401 dbfn_read_with_length(open_db *dbblock, const uschar *key, int *length)
402 {
403 void *yield;
404 EXIM_DATUM key_datum, result_datum;
405 int klen = Ustrlen(key) + 1;
406 uschar * key_copy = store_get(klen, is_tainted(key));
407
408 memcpy(key_copy, key, klen);
409
410 EXIM_DATUM_INIT(key_datum);         /* Some DBM libraries require the datum */
411 EXIM_DATUM_INIT(result_datum);      /* to be cleared before use. */
412 EXIM_DATUM_DATA(key_datum) = CS key_copy;
413 EXIM_DATUM_SIZE(key_datum) = klen;
414
415 if (!EXIM_DBGET(dbblock->dbptr, key_datum, result_datum)) return NULL;
416
417 /* Assume for now that anything stored could have been tainted. Properly
418 we should store the taint status along with the data. */
419
420 yield = store_get(EXIM_DATUM_SIZE(result_datum), TRUE);
421 memcpy(yield, EXIM_DATUM_DATA(result_datum), EXIM_DATUM_SIZE(result_datum));
422 if (length != NULL) *length = EXIM_DATUM_SIZE(result_datum);
423
424 EXIM_DATUM_FREE(result_datum);    /* Some DBM libs require freeing */
425 return yield;
426 }
427
428
429
430 #if defined(EXIM_TIDYDB) || defined(EXIM_FIXDB)
431
432 /*************************************************
433 *             Write to database file             *
434 *************************************************/
435
436 /*
437 Arguments:
438   dbblock   a pointer to an open database block
439   key       the key of the record to be written
440   ptr       a pointer to the record to be written
441   length    the length of the record to be written
442
443 Returns:    the yield of the underlying dbm or db "write" function. If this
444             is dbm, the value is zero for OK.
445 */
446
447 int
448 dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length)
449 {
450 EXIM_DATUM key_datum, value_datum;
451 dbdata_generic *gptr = (dbdata_generic *)ptr;
452 int klen = Ustrlen(key) + 1;
453 uschar * key_copy = store_get(klen, is_tainted(key));
454
455 memcpy(key_copy, key, klen);
456 gptr->time_stamp = time(NULL);
457
458 EXIM_DATUM_INIT(key_datum);         /* Some DBM libraries require the datum */
459 EXIM_DATUM_INIT(value_datum);       /* to be cleared before use. */
460 EXIM_DATUM_DATA(key_datum) = CS key_copy;
461 EXIM_DATUM_SIZE(key_datum) = klen;
462 EXIM_DATUM_DATA(value_datum) = CS ptr;
463 EXIM_DATUM_SIZE(value_datum) = length;
464 return EXIM_DBPUT(dbblock->dbptr, key_datum, value_datum);
465 }
466
467
468
469 /*************************************************
470 *           Delete record from database file     *
471 *************************************************/
472
473 /*
474 Arguments:
475   dbblock    a pointer to an open database block
476   key        the key of the record to be deleted
477
478 Returns: the yield of the underlying dbm or db "delete" function.
479 */
480
481 int
482 dbfn_delete(open_db *dbblock, const uschar *key)
483 {
484 int klen = Ustrlen(key) + 1;
485 uschar * key_copy = store_get(klen, is_tainted(key));
486
487 memcpy(key_copy, key, klen);
488 EXIM_DATUM key_datum;
489 EXIM_DATUM_INIT(key_datum);         /* Some DBM libraries require clearing */
490 EXIM_DATUM_DATA(key_datum) = CS key_copy;
491 EXIM_DATUM_SIZE(key_datum) = klen;
492 return EXIM_DBDEL(dbblock->dbptr, key_datum);
493 }
494
495 #endif  /* EXIM_TIDYDB || EXIM_FIXDB */
496
497
498
499 #if defined(EXIM_DUMPDB) || defined(EXIM_TIDYDB)
500 /*************************************************
501 *         Scan the keys of a database file       *
502 *************************************************/
503
504 /*
505 Arguments:
506   dbblock  a pointer to an open database block
507   start    TRUE if starting a new scan
508            FALSE if continuing with the current scan
509   cursor   a pointer to a pointer to a cursor anchor, for those dbm libraries
510            that use the notion of a cursor
511
512 Returns:   the next record from the file, or
513            NULL if there are no more
514 */
515
516 uschar *
517 dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
518 {
519 EXIM_DATUM key_datum, value_datum;
520 uschar *yield;
521 value_datum = value_datum;    /* dummy; not all db libraries use this */
522
523 /* Some dbm require an initialization */
524
525 if (start) EXIM_DBCREATE_CURSOR(dbblock->dbptr, cursor);
526
527 EXIM_DATUM_INIT(key_datum);         /* Some DBM libraries require the datum */
528 EXIM_DATUM_INIT(value_datum);       /* to be cleared before use. */
529
530 yield = (EXIM_DBSCAN(dbblock->dbptr, key_datum, value_datum, start, *cursor))?
531   US EXIM_DATUM_DATA(key_datum) : NULL;
532
533 /* Some dbm require a termination */
534
535 if (!yield) EXIM_DBDELETE_CURSOR(*cursor);
536 return yield;
537 }
538 #endif  /* EXIM_DUMPDB || EXIM_TIDYDB */
539
540
541
542 #ifdef EXIM_DUMPDB
543 /*************************************************
544 *           The exim_dumpdb main program         *
545 *************************************************/
546
547 int
548 main(int argc, char **cargv)
549 {
550 int dbdata_type = 0;
551 int yield = 0;
552 open_db dbblock;
553 open_db *dbm;
554 EXIM_CURSOR *cursor;
555 uschar **argv = USS cargv;
556 uschar keybuffer[1024];
557
558 /* Check the arguments, and open the database */
559
560 dbdata_type = check_args(argc, argv, US"dumpdb", US"");
561 spool_directory = argv[1];
562 if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE, TRUE)))
563   exit(1);
564
565 /* Scan the file, formatting the information for each entry. Note
566 that data is returned in a malloc'ed block, in order that it be
567 correctly aligned. */
568
569 for (uschar * key = dbfn_scan(dbm, TRUE, &cursor);
570      key;
571      key = dbfn_scan(dbm, FALSE, &cursor))
572   {
573   dbdata_retry *retry;
574   dbdata_wait *wait;
575   dbdata_callout_cache *callout;
576   dbdata_ratelimit *ratelimit;
577   dbdata_ratelimit_unique *rate_unique;
578   dbdata_tls_session *session;
579   int count_bad = 0;
580   int length;
581   uschar *t;
582   uschar name[MESSAGE_ID_LENGTH + 1];
583   void *value;
584   rmark reset_point = store_mark();
585
586   /* Keep a copy of the key separate, as in some DBM's the pointer is into data
587   which might change. */
588
589   if (Ustrlen(key) > sizeof(keybuffer) - 1)
590     {
591     printf("**** Overlong key encountered: %s\n", key);
592     return 1;
593     }
594   Ustrcpy(keybuffer, key);
595
596   if (!(value = dbfn_read_with_length(dbm, keybuffer, &length)))
597     fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
598                     "was not found in the file - something is wrong!\n",
599       CS keybuffer);
600   else
601     {
602     /* Note: don't use print_time more than once in one statement, since
603     it uses a single buffer. */
604
605     switch(dbdata_type)
606       {
607       case type_retry:
608         retry = (dbdata_retry *)value;
609         printf("  %s %d %d %s\n%s  ", keybuffer, retry->basic_errno,
610           retry->more_errno, retry->text,
611           print_time(retry->first_failed));
612         printf("%s  ", print_time(retry->last_try));
613         printf("%s %s\n", print_time(retry->next_try),
614           (retry->expired)? "*" : "");
615         break;
616
617       case type_wait:
618         wait = (dbdata_wait *)value;
619         printf("%s ", keybuffer);
620         t = wait->text;
621         name[MESSAGE_ID_LENGTH] = 0;
622
623         if (wait->count > WAIT_NAME_MAX)
624           {
625           fprintf(stderr,
626             "**** Data for %s corrupted\n  count=%d=0x%x max=%d\n",
627             CS keybuffer, wait->count, wait->count, WAIT_NAME_MAX);
628           wait->count = WAIT_NAME_MAX;
629           yield = count_bad = 1;
630           }
631         for (int i = 1; i <= wait->count; i++)
632           {
633           Ustrncpy(name, t, MESSAGE_ID_LENGTH);
634           if (count_bad && name[0] == 0) break;
635           if (Ustrlen(name) != MESSAGE_ID_LENGTH ||
636               Ustrspn(name, "0123456789"
637                             "abcdefghijklmnopqrstuvwxyz"
638                             "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
639             {
640             fprintf(stderr,
641               "**** Data for %s corrupted: bad character in message id\n",
642               CS keybuffer);
643             for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
644               fprintf(stderr, "%02x ", name[j]);
645             fprintf(stderr, "\n");
646             yield = 1;
647             break;
648             }
649           printf("%s ", name);
650           t += MESSAGE_ID_LENGTH;
651           }
652         printf("\n");
653         break;
654
655       case type_misc:
656         printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
657           keybuffer);
658         break;
659
660       case type_callout:
661         callout = (dbdata_callout_cache *)value;
662
663         /* New-style address record */
664
665         if (length == sizeof(dbdata_callout_cache_address))
666           {
667           printf("%s %s callout=%s\n",
668             print_time(((dbdata_generic *)value)->time_stamp),
669             keybuffer,
670             print_cache(callout->result));
671           }
672
673         /* New-style domain record */
674
675         else if (length == sizeof(dbdata_callout_cache))
676           {
677           printf("%s %s callout=%s postmaster=%s",
678             print_time(((dbdata_generic *)value)->time_stamp),
679             keybuffer,
680             print_cache(callout->result),
681             print_cache(callout->postmaster_result));
682           if (callout->postmaster_result != ccache_unknown)
683             printf(" (%s)", print_time(callout->postmaster_stamp));
684           printf(" random=%s", print_cache(callout->random_result));
685           if (callout->random_result != ccache_unknown)
686             printf(" (%s)", print_time(callout->random_stamp));
687           printf("\n");
688           }
689
690         break;
691
692       case type_ratelimit:
693         if (Ustrstr(key, "/unique/") != NULL && length >= sizeof(*rate_unique))
694           {
695           ratelimit = (dbdata_ratelimit *)value;
696           rate_unique = (dbdata_ratelimit_unique *)value;
697           printf("%s.%06d rate: %10.3f epoch: %s size: %u key: %s\n",
698             print_time(ratelimit->time_stamp),
699             ratelimit->time_usec, ratelimit->rate,
700             print_time(rate_unique->bloom_epoch), rate_unique->bloom_size,
701             keybuffer);
702           }
703         else
704           {
705           ratelimit = (dbdata_ratelimit *)value;
706           printf("%s.%06d rate: %10.3f key: %s\n",
707             print_time(ratelimit->time_stamp),
708             ratelimit->time_usec, ratelimit->rate,
709             keybuffer);
710           }
711         break;
712
713       case type_tls:
714         session = (dbdata_tls_session *)value;
715         printf("  %s %.*s\n", keybuffer, length, session->session);
716         break;
717       }
718     }
719   store_reset(reset_point);
720   }
721
722 dbfn_close(dbm);
723 return yield;
724 }
725
726 #endif  /* EXIM_DUMPDB */
727
728
729
730
731 #ifdef EXIM_FIXDB
732 /*************************************************
733 *           The exim_fixdb main program          *
734 *************************************************/
735
736 /* In order not to hold the database lock any longer than is necessary, each
737 operation on the database uses a separate open/close call. This is expensive,
738 but then using this utility is not expected to be very common. Its main use is
739 to provide a way of patching up hints databases in order to run tests.
740
741 Syntax of commands:
742
743 (1) <record name>
744     This causes the data from the given record to be displayed, or "not found"
745     to be output. Note that in the retry database, destination names are
746     preceded by R: or T: for router or transport retry info.
747
748 (2) <record name> d
749     This causes the given record to be deleted or "not found" to be output.
750
751 (3) <record name> <field number> <value>
752     This sets the given value into the given field, identified by a number
753     which is output by the display command. Not all types of record can
754     be changed.
755
756 (4) q
757     This exits from exim_fixdb.
758
759 If the record name is omitted from (2) or (3), the previously used record name
760 is re-used. */
761
762
763 int main(int argc, char **cargv)
764 {
765 int dbdata_type;
766 uschar **argv = USS cargv;
767 uschar buffer[256];
768 uschar name[256];
769 rmark reset_point;
770
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 /* Scan the options */
1141
1142 for (i = 1; i < argc; i++)
1143   {
1144   if (argv[i][0] != '-') break;
1145   if (Ustrcmp(argv[i], "-f") == 0) continue;
1146   if (Ustrcmp(argv[i], "-t") == 0)
1147     {
1148     uschar *s;
1149     s = argv[++i];
1150     maxkeep = 0;
1151     while (*s != 0)
1152       {
1153       int value, count;
1154       if (!isdigit(*s)) usage(US"tidydb", US" [-t <time>]");
1155       (void)sscanf(CS s, "%d%n", &value, &count);
1156       s += count;
1157       switch (*s)
1158         {
1159         case 'w': value *= 7;
1160         case 'd': value *= 24;
1161         case 'h': value *= 60;
1162         case 'm': value *= 60;
1163         case 's': s++;
1164         break;
1165         default: usage(US"tidydb", US" [-t <time>]");
1166         }
1167       maxkeep += value;
1168       }
1169     }
1170   else usage(US"tidydb", US" [-t <time>]");
1171   }
1172
1173 /* Adjust argument values and process arguments */
1174
1175 argc -= --i;
1176 argv += i;
1177
1178 dbdata_type = check_args(argc, argv, US"tidydb", US" [-t <time>]");
1179
1180 /* Compute the oldest keep time, verify what we are doing, and open the
1181 database */
1182
1183 oldest = time(NULL) - maxkeep;
1184 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
1185
1186 spool_directory = argv[1];
1187 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE, TRUE)))
1188   exit(1);
1189
1190 /* Prepare for building file names */
1191
1192 sprintf(CS buffer, "%s/input/", argv[1]);
1193 path_len = Ustrlen(buffer);
1194
1195
1196 /* It appears, by experiment, that it is a bad idea to make changes
1197 to the file while scanning it. Pity the man page doesn't warn you about that.
1198 Therefore, we scan and build a list of all the keys. Then we use that to
1199 read the records and possibly update them. */
1200
1201 for (key = dbfn_scan(dbm, TRUE, &cursor);
1202      key;
1203      key = dbfn_scan(dbm, FALSE, &cursor))
1204   {
1205   key_item *k = store_get(sizeof(key_item) + Ustrlen(key), is_tainted(key));
1206   k->next = keychain;
1207   keychain = k;
1208   Ustrcpy(k->key, key);
1209   }
1210
1211 /* Now scan the collected keys and operate on the records, resetting
1212 the store each time round. */
1213
1214 for (; keychain && (reset_point = store_mark()); store_reset(reset_point))
1215   {
1216   dbdata_generic *value;
1217
1218   key = keychain->key;
1219   keychain = keychain->next;
1220   value = dbfn_read_with_length(dbm, key, NULL);
1221
1222   /* A continuation record may have been deleted or renamed already, so
1223   non-existence is not serious. */
1224
1225   if (value == NULL) continue;
1226
1227   /* Delete if too old */
1228
1229   if (value->time_stamp < oldest)
1230     {
1231     printf("deleted %s (too old)\n", key);
1232     dbfn_delete(dbm, key);
1233     continue;
1234     }
1235
1236   /* Do database-specific tidying for wait databases, and message-
1237   specific tidying for the retry database. */
1238
1239   if (dbdata_type == type_wait)
1240     {
1241     dbdata_wait *wait = (dbdata_wait *)value;
1242     BOOL update = FALSE;
1243
1244     /* Leave corrupt records alone */
1245
1246     if (wait->count > WAIT_NAME_MAX)
1247       {
1248       printf("**** Data for %s corrupted\n  count=%d=0x%x max=%d\n",
1249         key, wait->count, wait->count, WAIT_NAME_MAX);
1250       continue;
1251       }
1252
1253     /* Loop for renamed continuation records. For each message id,
1254     check to see if the message exists, and if not, remove its entry
1255     from the record. Because of the possibility of split input directories,
1256     we must look in both possible places for a -D file. */
1257
1258     for (;;)
1259       {
1260       int length = wait->count * MESSAGE_ID_LENGTH;
1261
1262       for (int offset = length - MESSAGE_ID_LENGTH;
1263            offset >= 0; offset -= MESSAGE_ID_LENGTH)
1264         {
1265         Ustrncpy(buffer+path_len, wait->text + offset, MESSAGE_ID_LENGTH);
1266         sprintf(CS(buffer+path_len + MESSAGE_ID_LENGTH), "-D");
1267
1268         if (Ustat(buffer, &statbuf) != 0)
1269           {
1270           buffer[path_len] = wait->text[offset+5];
1271           buffer[path_len+1] = '/';
1272           Ustrncpy(buffer+path_len+2, wait->text + offset, MESSAGE_ID_LENGTH);
1273           sprintf(CS(buffer+path_len+2 + MESSAGE_ID_LENGTH), "-D");
1274
1275           if (Ustat(buffer, &statbuf) != 0)
1276             {
1277             int left = length - offset - MESSAGE_ID_LENGTH;
1278             if (left > 0) Ustrncpy(wait->text + offset,
1279               wait->text + offset + MESSAGE_ID_LENGTH, left);
1280             wait->count--;
1281             length -= MESSAGE_ID_LENGTH;
1282             update = TRUE;
1283             }
1284           }
1285         }
1286
1287       /* If record is empty and the main record, either delete it or rename
1288       the next continuation, repeating if that is also empty. */
1289
1290       if (wait->count == 0 && Ustrchr(key, ':') == NULL)
1291         {
1292         while (wait->count == 0 && wait->sequence > 0)
1293           {
1294           uschar newkey[256];
1295           dbdata_generic *newvalue;
1296           sprintf(CS newkey, "%s:%d", key, wait->sequence - 1);
1297           newvalue = dbfn_read_with_length(dbm, newkey, NULL);
1298           if (newvalue != NULL)
1299             {
1300             value = newvalue;
1301             wait = (dbdata_wait *)newvalue;
1302             dbfn_delete(dbm, newkey);
1303             printf("renamed %s\n", newkey);
1304             update = TRUE;
1305             }
1306           else wait->sequence--;
1307           }
1308
1309         /* If we have ended up with an empty main record, delete it
1310         and break the loop. Otherwise the new record will be scanned. */
1311
1312         if (wait->count == 0 && wait->sequence == 0)
1313           {
1314           dbfn_delete(dbm, key);
1315           printf("deleted %s (empty)\n", key);
1316           update = FALSE;
1317           break;
1318           }
1319         }
1320
1321       /* If not an empty main record, break the loop */
1322
1323       else break;
1324       }
1325
1326     /* Re-write the record if required */
1327
1328     if (update)
1329       {
1330       printf("updated %s\n", key);
1331       dbfn_write(dbm, key, wait, sizeof(dbdata_wait) +
1332         wait->count * MESSAGE_ID_LENGTH);
1333       }
1334     }
1335
1336   /* If a retry record's key ends with a message-id, check that that message
1337   still exists; if not, remove this record. */
1338
1339   else if (dbdata_type == type_retry)
1340     {
1341     uschar *id;
1342     int len = Ustrlen(key);
1343
1344     if (len < MESSAGE_ID_LENGTH + 1) continue;
1345     id = key + len - MESSAGE_ID_LENGTH - 1;
1346     if (*id++ != ':') continue;
1347
1348     for (i = 0; i < MESSAGE_ID_LENGTH; i++)
1349       if (i == 6 || i == 13)
1350         { if (id[i] != '-') break; }
1351       else
1352         { if (!isalnum(id[i])) break; }
1353     if (i < MESSAGE_ID_LENGTH) continue;
1354
1355     Ustrncpy(buffer + path_len, id, MESSAGE_ID_LENGTH);
1356     sprintf(CS(buffer + path_len + MESSAGE_ID_LENGTH), "-D");
1357
1358     if (Ustat(buffer, &statbuf) != 0)
1359       {
1360       sprintf(CS(buffer + path_len), "%c/%s-D", id[5], id);
1361       if (Ustat(buffer, &statbuf) != 0)
1362         {
1363         dbfn_delete(dbm, key);
1364         printf("deleted %s (no message)\n", key);
1365         }
1366       }
1367     }
1368   }
1369
1370 dbfn_close(dbm);
1371 printf("Tidying complete\n");
1372 return 0;
1373 }
1374
1375 #endif  /* EXIM_TIDYDB */
1376
1377 /* End of exim_dbutil.c */