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