dd1444593b0f5c9d1203e3f69f6d40fc2338145c
[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 /******************************************************************************/
85
86
87 /*************************************************
88 *              SIGALRM handler                   *
89 *************************************************/
90
91 SIGNAL_BOOL sigalrm_seen;
92
93 void
94 sigalrm_handler(int sig)
95 {
96 sigalrm_seen = 1;
97 }
98
99
100
101 /*************************************************
102 *        Output usage message and exit           *
103 *************************************************/
104
105 static void
106 usage(uschar *name, uschar *options)
107 {
108 printf("Usage: exim_%s%s  <spool-directory> <database-name>\n", name, options);
109 printf("  <database-name> = retry | misc | wait-<transport-name> | callout | ratelimit | tls | seen\n");
110 exit(EXIT_FAILURE);
111 }
112
113
114
115 /*************************************************
116 *           Sort out the command arguments       *
117 *************************************************/
118
119 /* This function checks that there are exactly 2 arguments, and checks the
120 second of them to be sure it is a known database name. */
121
122 static int
123 check_args(int argc, uschar **argv, uschar *name, uschar *options)
124 {
125 uschar * aname = argv[optind + 1];
126 if (argc - optind == 2)
127   {
128   if (Ustrcmp(aname, "retry") == 0)     return type_retry;
129   if (Ustrcmp(aname, "misc") == 0)      return type_misc;
130   if (Ustrncmp(aname, "wait-", 5) == 0) return type_wait;
131   if (Ustrcmp(aname, "callout") == 0)   return type_callout;
132   if (Ustrcmp(aname, "ratelimit") == 0) return type_ratelimit;
133   if (Ustrcmp(aname, "tls") == 0)       return type_tls;
134   if (Ustrcmp(aname, "seen") == 0)      return type_seen;
135   }
136 usage(name, options);
137 return -1;              /* Never obeyed */
138 }
139
140
141 FUNC_MAYBE_UNUSED
142 static void
143 options(int argc, uschar * argv[], uschar * name, const uschar * opts)
144 {
145 int opt;
146
147 opterr = 0;
148 while ((opt = getopt(argc, (char * const *)argv, CCS opts)) != -1)
149   switch (opt)
150   {
151   case 'k':     keyonly = TRUE; break;
152   case 'z':     utc = TRUE; break;
153   default:      usage(name, US" [-z] [-k]");
154   }
155 }
156
157
158
159
160 /*************************************************
161 *         Handle attempts to write the log       *
162 *************************************************/
163
164 /* The message gets written to stderr when log_write() is called from a
165 utility. The message always gets '\n' added on the end of it. These calls come
166 from modules such as store.c when things go drastically wrong (e.g. malloc()
167 failing). In normal use they won't get obeyed.
168
169 Arguments:
170   selector  not relevant when running a utility
171   flags     not relevant when running a utility
172   format    a printf() format
173   ...       arguments for format
174
175 Returns:    nothing
176 */
177
178 void
179 log_write(unsigned int selector, int flags, const char *format, ...)
180 {
181 va_list ap;
182 va_start(ap, format);
183 vfprintf(stderr, format, ap);
184 fprintf(stderr, "\n");
185 va_end(ap);
186 }
187
188
189
190 /*************************************************
191 *        Format a time value for printing        *
192 *************************************************/
193
194 static uschar time_buffer[sizeof("09-xxx-1999 hh:mm:ss  ")];
195
196 uschar *
197 print_time(time_t t)
198 {
199 struct tm *tmstr = utc ? gmtime(&t) : localtime(&t);
200 Ustrftime(time_buffer, sizeof(time_buffer), "%d-%b-%Y %H:%M:%S", tmstr);
201 return time_buffer;
202 }
203
204
205
206 /*************************************************
207 *        Format a cache value for printing       *
208 *************************************************/
209
210 uschar *
211 print_cache(int value)
212 {
213 return value == ccache_accept ? US"accept" :
214        value == ccache_reject ? US"reject" :
215        US"unknown";
216 }
217
218
219 #ifdef EXIM_FIXDB
220 /*************************************************
221 *                Read time value                 *
222 *************************************************/
223
224 static time_t
225 read_time(uschar *s)
226 {
227 int field = 0;
228 int value;
229 time_t now = time(NULL);
230 struct tm *tm = localtime(&now);
231
232 tm->tm_sec = 0;
233 tm->tm_isdst = -1;
234
235 for (uschar * t = s + Ustrlen(s) - 1; t >= s; t--)
236   {
237   if (*t == ':') continue;
238   if (!isdigit((uschar)*t)) return -1;
239
240   value = *t - '0';
241   if (--t >= s)
242     {
243     if (!isdigit((uschar)*t)) return -1;
244     value = value + (*t - '0')*10;
245     }
246
247   switch (field++)
248     {
249     case 0: tm->tm_min = value; break;
250     case 1: tm->tm_hour = value; break;
251     case 2: tm->tm_mday = value; break;
252     case 3: tm->tm_mon = value - 1; break;
253     case 4: tm->tm_year = (value < 90)? value + 100 : value; break;
254     default: return -1;
255     }
256   }
257
258 return mktime(tm);
259 }
260 #endif  /* EXIM_FIXDB */
261
262
263
264 /*************************************************
265 *       Open and lock a database file            *
266 *************************************************/
267
268 /* This is a cut-down version from the function in dbfn.h that Exim itself
269 uses. We assume the database exists, and therefore give up if we cannot open
270 the lock file.
271
272 Arguments:
273   name     The single-component name of one of Exim's database files.
274   flags    O_RDONLY or O_RDWR, O_CREAT
275   dbblock  Points to an open_db block to be filled in.
276   lof      Unused.
277   panic    Unused
278
279 Returns:   NULL if the open failed, or the locking failed.
280            On success, dbblock is returned. This contains the dbm pointer and
281            the fd of the locked lock file.
282 */
283
284 open_db *
285 dbfn_open(const uschar * name, int flags, open_db * dbblock,
286   BOOL lof, BOOL panic)
287 {
288 int rc;
289 struct flock lock_data;
290 BOOL read_only = (flags & (O_WRONLY|O_RDWR)) == O_RDONLY;
291 uschar * dirname, * filename;
292
293 /* The first thing to do is to open a separate file on which to lock. This
294 ensures that Exim has exclusive use of the database before it even tries to
295 open it. If there is a database, there should be a lock file in existence;
296 if no lockfile we infer there is no database and error out.  We open the
297 lockfile using the r/w mode requested for the DB, users lacking permission
298 for the DB access mode will error out here. */
299
300 if (  asprintf(CSS &dirname, "%s/db", spool_directory) < 0
301    || asprintf(CSS &filename, "%s/%s.lockfile", dirname, name) < 0)
302   return NULL;
303
304 dbblock->lockfd = -1;
305 if (exim_lockfile_needed())
306   {
307   if ((dbblock->lockfd = Uopen(filename, flags, 0)) < 0)
308     {
309     printf("** Failed to open database lock file %s: %s\n", filename,
310       strerror(errno));
311     return NULL;
312     }
313
314   /* Now we must get a lock on the opened lock file; do this with a blocking
315   lock that times out. */
316
317   lock_data.l_type = read_only ? F_RDLCK : F_WRLCK;
318   lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
319
320   sigalrm_seen = FALSE;
321   os_non_restarting_signal(SIGALRM, sigalrm_handler);
322   ALARM(EXIMDB_LOCK_TIMEOUT);
323   rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
324   ALARM_CLR(0);
325
326   if (sigalrm_seen) errno = ETIMEDOUT;
327   if (rc < 0)
328     {
329     printf("** Failed to get %s lock for %s: %s",
330       read_only ? "read" : "write",
331       filename,
332       errno == ETIMEDOUT ? "timed out" : strerror(errno));
333     (void)close(dbblock->lockfd);
334     return NULL;
335     }
336
337   /* At this point we have an opened and locked separate lock file, that is,
338   exclusive access to the database, so we can go ahead and open it. */
339   }
340
341 if (asprintf(CSS &filename, "%s/%s", dirname, name) < 0) return NULL;
342
343 if (!(dbblock->dbptr = exim_dbopen(filename, dirname, flags, 0)))
344   {
345   printf("** Failed to open hintsdb file %s for %s: %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   if (dbblock->lockfd >= 0) (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 if there was one.
369
370 Argument: a pointer to an open database block
371 Returns:  nothing
372 */
373
374 void
375 dbfn_close(open_db * dbp)
376 {
377 exim_dbclose(dbp->dbptr);
378 if (dbp->lockfd >= 0) (void) close(dbp->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(EXIT_FAILURE);
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|O_CREAT, &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|O_CREAT, &dbblock, FALSE, TRUE)))
1211   exit(EXIT_FAILURE);
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 */