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