Testsuite: more munge tweaks
[exim.git] / src / src / exim_dbutil.c
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4
5 /* Copyright (c) The Exim Maintainers 2020 - 2023 */
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(uschar *name, int flags, open_db *dbblock, BOOL lof, BOOL panic)
291 {
292 int rc;
293 struct flock lock_data;
294 BOOL read_only = flags == O_RDONLY;
295 uschar * dirname, * filename;
296
297 /* The first thing to do is to open a separate file on which to lock. This
298 ensures that Exim has exclusive use of the database before it even tries to
299 open it. If there is a database, there should be a lock file in existence. */
300
301 #ifdef COMPILE_UTILITY
302 if (  asprintf(CSS &dirname, "%s/db", spool_directory) < 0
303    || asprintf(CSS &filename, "%s/%s.lockfile", dirname, name) < 0)
304   return NULL;
305 #else
306 dirname = string_sprintf("%s/db", spool_directory);
307 filename = string_sprintf("%s/%s.lockfile", dirname, name);
308 #endif
309
310 dbblock->lockfd = Uopen(filename, flags, 0);
311 if (dbblock->lockfd < 0)
312   {
313   printf("** Failed to open database lock file %s: %s\n", filename,
314     strerror(errno));
315   return NULL;
316   }
317
318 /* Now we must get a lock on the opened lock file; do this with a blocking
319 lock that times out. */
320
321 lock_data.l_type = read_only ? F_RDLCK : F_WRLCK;
322 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
323
324 sigalrm_seen = FALSE;
325 os_non_restarting_signal(SIGALRM, sigalrm_handler);
326 ALARM(EXIMDB_LOCK_TIMEOUT);
327 rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
328 ALARM_CLR(0);
329
330 if (sigalrm_seen) errno = ETIMEDOUT;
331 if (rc < 0)
332   {
333   printf("** Failed to get %s lock for %s: %s",
334     flags & O_WRONLY ? "write" : "read",
335     filename,
336     errno == ETIMEDOUT ? "timed out" : strerror(errno));
337   (void)close(dbblock->lockfd);
338   return NULL;
339   }
340
341 /* At this point we have an opened and locked separate lock file, that is,
342 exclusive access to the database, so we can go ahead and open it. */
343
344 #ifdef COMPILE_UTILITY
345 if (asprintf(CSS &filename, "%s/%s", dirname, name) < 0) return NULL;
346 #else
347 filename = string_sprintf("%s/%s", dirname, name);
348 #endif
349 dbblock->dbptr = exim_dbopen(filename, dirname, flags, 0);
350
351 if (!dbblock->dbptr)
352   {
353   printf("** Failed to open DBM file %s for %s:\n   %s%s\n", filename,
354     read_only? "reading" : "writing", strerror(errno),
355     #ifdef USE_DB
356     " (or Berkeley DB error while opening)"
357     #else
358     ""
359     #endif
360     );
361   (void)close(dbblock->lockfd);
362   return NULL;
363   }
364
365 return dbblock;
366 }
367
368
369
370
371 /*************************************************
372 *         Unlock and close a database file       *
373 *************************************************/
374
375 /* Closing a file automatically unlocks it, so after closing the database, just
376 close the lock file.
377
378 Argument: a pointer to an open database block
379 Returns:  nothing
380 */
381
382 void
383 dbfn_close(open_db *dbblock)
384 {
385 exim_dbclose(dbblock->dbptr);
386 (void)close(dbblock->lockfd);
387 }
388
389
390
391
392 /*************************************************
393 *             Read from database file            *
394 *************************************************/
395
396 /* Passing back the pointer unchanged is useless, because there is no guarantee
397 of alignment. Since all the records used by Exim need to be properly aligned to
398 pick out the timestamps, etc., do the copying centrally here.
399
400 Arguments:
401   dbblock   a pointer to an open database block
402   key       the key of the record to be read
403   length    where to put the length (or NULL if length not wanted). Includes overhead.
404
405 Returns: a pointer to the retrieved record, or
406          NULL if the record is not found
407 */
408
409 void *
410 dbfn_read_with_length(open_db * dbblock, const uschar * key, int * length)
411 {
412 void * yield;
413 EXIM_DATUM key_datum, result_datum;
414 int klen = Ustrlen(key) + 1;
415 uschar * key_copy = store_get(klen, key);
416 unsigned dlen;
417
418 memcpy(key_copy, key, klen);
419
420 exim_datum_init(&key_datum);         /* Some DBM libraries require the datum */
421 exim_datum_init(&result_datum);      /* to be cleared before use. */
422 exim_datum_data_set(&key_datum, key_copy);
423 exim_datum_size_set(&key_datum, klen);
424
425 if (!exim_dbget(dbblock->dbptr, &key_datum, &result_datum)) return NULL;
426
427 /* Assume for now that anything stored could have been tainted. Properly
428 we should store the taint status along with the data. */
429
430 dlen = exim_datum_size_get(&result_datum);
431 yield = store_get(dlen, GET_TAINTED);
432 memcpy(yield, exim_datum_data_get(&result_datum), dlen);
433 if (length) *length = dlen;
434
435 exim_datum_free(&result_datum);    /* Some DBM libs require freeing */
436 return yield;
437 }
438
439
440
441 #if defined(EXIM_TIDYDB) || defined(EXIM_FIXDB)
442
443 /*************************************************
444 *             Write to database file             *
445 *************************************************/
446
447 /*
448 Arguments:
449   dbblock   a pointer to an open database block
450   key       the key of the record to be written
451   ptr       a pointer to the record to be written
452   length    the length of the record to be written
453
454 Returns:    the yield of the underlying dbm or db "write" function. If this
455             is dbm, the value is zero for OK.
456 */
457
458 int
459 dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length)
460 {
461 EXIM_DATUM key_datum, value_datum;
462 dbdata_generic *gptr = (dbdata_generic *)ptr;
463 int klen = Ustrlen(key) + 1;
464 uschar * key_copy = store_get(klen, key);
465
466 memcpy(key_copy, key, klen);
467 gptr->time_stamp = time(NULL);
468
469 exim_datum_init(&key_datum);         /* Some DBM libraries require the datum */
470 exim_datum_init(&value_datum);       /* to be cleared before use. */
471 exim_datum_data_set(&key_datum, key_copy);
472 exim_datum_size_set(&key_datum, klen);
473 exim_datum_data_set(&value_datum, ptr);
474 exim_datum_size_set(&value_datum, length);
475 return exim_dbput(dbblock->dbptr, &key_datum, &value_datum);
476 }
477
478
479
480 /*************************************************
481 *           Delete record from database file     *
482 *************************************************/
483
484 /*
485 Arguments:
486   dbblock    a pointer to an open database block
487   key        the key of the record to be deleted
488
489 Returns: the yield of the underlying dbm or db "delete" function.
490 */
491
492 int
493 dbfn_delete(open_db *dbblock, const uschar *key)
494 {
495 int klen = Ustrlen(key) + 1;
496 uschar * key_copy = store_get(klen, key);
497 EXIM_DATUM key_datum;
498
499 memcpy(key_copy, key, klen);
500 exim_datum_init(&key_datum);         /* Some DBM libraries require clearing */
501 exim_datum_data_set(&key_datum, key_copy);
502 exim_datum_size_set(&key_datum, klen);
503 return exim_dbdel(dbblock->dbptr, &key_datum);
504 }
505
506 #endif  /* EXIM_TIDYDB || EXIM_FIXDB */
507
508
509
510 #if defined(EXIM_DUMPDB) || defined(EXIM_TIDYDB)
511 /*************************************************
512 *         Scan the keys of a database file       *
513 *************************************************/
514
515 /*
516 Arguments:
517   dbblock  a pointer to an open database block
518   start    TRUE if starting a new scan
519            FALSE if continuing with the current scan
520   cursor   a pointer to a pointer to a cursor anchor, for those dbm libraries
521            that use the notion of a cursor
522
523 Returns:   the next record from the file, or
524            NULL if there are no more
525 */
526
527 uschar *
528 dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
529 {
530 EXIM_DATUM key_datum, value_datum;
531 uschar *yield;
532
533 /* Some dbm require an initialization */
534
535 if (start) *cursor = exim_dbcreate_cursor(dbblock->dbptr);
536
537 exim_datum_init(&key_datum);         /* Some DBM libraries require the datum */
538 exim_datum_init(&value_datum);       /* to be cleared before use. */
539
540 yield = exim_dbscan(dbblock->dbptr, &key_datum, &value_datum, start, *cursor)
541   ? US exim_datum_data_get(&key_datum) : NULL;
542
543 /* Some dbm require a termination */
544
545 if (!yield) exim_dbdelete_cursor(*cursor);
546 return yield;
547 }
548 #endif  /* EXIM_DUMPDB || EXIM_TIDYDB */
549
550
551
552 #ifdef EXIM_DUMPDB
553 /*************************************************
554 *           The exim_dumpdb main program         *
555 *************************************************/
556
557 int
558 main(int argc, char **cargv)
559 {
560 int dbdata_type = 0;
561 int yield = 0;
562 open_db dbblock;
563 open_db *dbm;
564 EXIM_CURSOR *cursor;
565 uschar **argv = USS cargv;
566 uschar keybuffer[1024];
567
568 store_init();
569 options(argc, argv, US"dumpdb", US"kz");
570
571 /* Check the arguments, and open the database */
572
573 dbdata_type = check_args(argc, argv, US"dumpdb", US" [-z] [-k]");
574 argc -= optind; argv += optind;
575 spool_directory = argv[0];
576
577 if (!(dbm = dbfn_open(argv[1], O_RDONLY, &dbblock, FALSE, TRUE)))
578   exit(1);
579
580 /* Scan the file, formatting the information for each entry. Note
581 that data is returned in a malloc'ed block, in order that it be
582 correctly aligned. */
583
584 for (uschar * key = dbfn_scan(dbm, TRUE, &cursor);
585      key;
586      key = dbfn_scan(dbm, FALSE, &cursor))
587   {
588   dbdata_retry *retry;
589   dbdata_wait *wait;
590   dbdata_callout_cache *callout;
591   dbdata_ratelimit *ratelimit;
592   dbdata_ratelimit_unique *rate_unique;
593   dbdata_tls_session *session;
594   dbdata_seen *seen;
595   int count_bad = 0;
596   int length;
597   uschar *t;
598   uschar name[MESSAGE_ID_LENGTH + 1];
599   void *value;
600   rmark reset_point = store_mark();
601
602   /* Keep a copy of the key separate, as in some DBM's the pointer is into data
603   which might change. */
604
605   if (Ustrlen(key) > sizeof(keybuffer) - 1)
606     {
607     printf("**** Overlong key encountered: %s\n", key);
608     return 1;
609     }
610   Ustrcpy(keybuffer, key);
611
612   if (keyonly)
613     printf("  %s\n", keybuffer);
614   else if (!(value = dbfn_read_with_length(dbm, keybuffer, &length)))
615     fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
616                     "was not found in the file - something is wrong!\n",
617       CS keybuffer);
618   else
619     /* Note: don't use print_time more than once in one statement, since
620     it uses a single buffer. */
621
622     switch(dbdata_type)
623       {
624       case type_retry:
625         retry = (dbdata_retry *)value;
626         printf("  %s %d %d %s\n%s  ", keybuffer, retry->basic_errno,
627           retry->more_errno, retry->text,
628           print_time(retry->first_failed));
629         printf("%s  ", print_time(retry->last_try));
630         printf("%s %s\n", print_time(retry->next_try),
631           retry->expired ? "*" : "");
632         break;
633
634       case type_wait:
635         wait = (dbdata_wait *)value;
636         printf("%s ", keybuffer);
637         t = wait->text;
638         name[MESSAGE_ID_LENGTH] = 0;
639
640     /* Leave corrupt records alone */
641         if (wait->count > WAIT_NAME_MAX)
642           {
643           fprintf(stderr,
644             "**** Data for %s corrupted\n  count=%d=0x%x max=%d\n",
645             CS keybuffer, wait->count, wait->count, WAIT_NAME_MAX);
646           wait->count = WAIT_NAME_MAX;
647           yield = count_bad = 1;
648           }
649         for (int i = 1; i <= wait->count; i++)
650           {
651           Ustrncpy(name, t, MESSAGE_ID_LENGTH);
652           if (count_bad && name[0] == 0) break;
653           if (Ustrlen(name) != MESSAGE_ID_LENGTH ||
654               Ustrspn(name, "0123456789"
655                             "abcdefghijklmnopqrstuvwxyz"
656                             "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
657             {
658             fprintf(stderr,
659               "**** Data for %s corrupted: bad character in message id\n",
660               CS keybuffer);
661             for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
662               fprintf(stderr, "%02x ", name[j]);
663             fprintf(stderr, "\n");
664             yield = 1;
665             break;
666             }
667           printf("%s ", name);
668           t += MESSAGE_ID_LENGTH;
669           }
670         printf("\n");
671         break;
672
673       case type_misc:
674         printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
675           keybuffer);
676         break;
677
678       case type_callout:
679         callout = (dbdata_callout_cache *)value;
680
681         /* New-style address record */
682
683         if (length == sizeof(dbdata_callout_cache_address))
684           {
685           printf("%s %s callout=%s\n",
686             print_time(((dbdata_generic *)value)->time_stamp),
687             keybuffer,
688             print_cache(callout->result));
689           }
690
691         /* New-style domain record */
692
693         else if (length == sizeof(dbdata_callout_cache))
694           {
695           printf("%s %s callout=%s postmaster=%s",
696             print_time(((dbdata_generic *)value)->time_stamp),
697             keybuffer,
698             print_cache(callout->result),
699             print_cache(callout->postmaster_result));
700           if (callout->postmaster_result != ccache_unknown)
701             printf(" (%s)", print_time(callout->postmaster_stamp));
702           printf(" random=%s", print_cache(callout->random_result));
703           if (callout->random_result != ccache_unknown)
704             printf(" (%s)", print_time(callout->random_stamp));
705           printf("\n");
706           }
707
708         break;
709
710       case type_ratelimit:
711         if (Ustrstr(key, "/unique/") != NULL && length >= sizeof(*rate_unique))
712           {
713           ratelimit = (dbdata_ratelimit *)value;
714           rate_unique = (dbdata_ratelimit_unique *)value;
715           printf("%s.%06d rate: %10.3f epoch: %s size: %u key: %s\n",
716             print_time(ratelimit->time_stamp),
717             ratelimit->time_usec, ratelimit->rate,
718             print_time(rate_unique->bloom_epoch), rate_unique->bloom_size,
719             keybuffer);
720           }
721         else
722           {
723           ratelimit = (dbdata_ratelimit *)value;
724           printf("%s.%06d rate: %10.3f key: %s\n",
725             print_time(ratelimit->time_stamp),
726             ratelimit->time_usec, ratelimit->rate,
727             keybuffer);
728           }
729         break;
730
731       case type_tls:
732         session = (dbdata_tls_session *)value;
733         printf("  %s %.*s\n", keybuffer, length, session->session);
734         break;
735
736       case type_seen:
737         seen = (dbdata_seen *)value;
738         printf("%s\t%s\n", keybuffer, print_time(seen->time_stamp));
739         break;
740       }
741   store_reset(reset_point);
742   }
743
744 dbfn_close(dbm);
745 return yield;
746 }
747
748 #endif  /* EXIM_DUMPDB */
749
750
751
752
753 #ifdef EXIM_FIXDB
754 /*************************************************
755 *           The exim_fixdb main program          *
756 *************************************************/
757
758 /* In order not to hold the database lock any longer than is necessary, each
759 operation on the database uses a separate open/close call. This is expensive,
760 but then using this utility is not expected to be very common. Its main use is
761 to provide a way of patching up hints databases in order to run tests.
762
763 Syntax of commands:
764
765 (1) <record name>
766     This causes the data from the given record to be displayed, or "not found"
767     to be output. Note that in the retry database, destination names are
768     preceded by R: or T: for router or transport retry info.
769
770 (2) <record name> d
771     This causes the given record to be deleted or "not found" to be output.
772
773 (3) <record name> <field number> <value>
774     This sets the given value into the given field, identified by a number
775     which is output by the display command. Not all types of record can
776     be changed.
777
778 (4) q
779     This exits from exim_fixdb.
780
781 If the record name is omitted from (2) or (3), the previously used record name
782 is re-used. */
783
784
785 int
786 main(int argc, char **cargv)
787 {
788 int dbdata_type;
789 uschar **argv = USS cargv;
790 uschar buffer[256];
791 uschar name[256];
792 rmark reset_point;
793 uschar * aname;
794
795 store_init();
796 options(argc, argv, US"fixdb", US"z");
797 name[0] = 0;  /* No name set */
798
799 /* Sort out the database type, verify what we are working on and then process
800 user requests */
801
802 dbdata_type = check_args(argc, argv, US"fixdb", US" [-z]");
803 argc -= optind; argv += optind;
804 spool_directory = argv[0];
805 aname = argv[1];
806
807 printf("Modifying Exim hints database %s/db/%s\n", spool_directory, aname);
808
809 for(; (reset_point = store_mark()); store_reset(reset_point))
810   {
811   open_db dbblock;
812   open_db *dbm;
813   void *record;
814   dbdata_retry *retry;
815   dbdata_wait *wait;
816   dbdata_callout_cache *callout;
817   dbdata_ratelimit *ratelimit;
818   dbdata_ratelimit_unique *rate_unique;
819   dbdata_tls_session *session;
820   int oldlength;
821   uschar *t;
822   uschar field[256], value[256];
823
824   printf("> ");
825   if (Ufgets(buffer, 256, stdin) == NULL) break;
826
827   buffer[Ustrlen(buffer)-1] = 0;
828   field[0] = value[0] = 0;
829
830   /* If the buffer contains just one digit, or just consists of "d", use the
831   previous name for an update. */
832
833   if ((isdigit((uschar)buffer[0]) && (buffer[1] == ' ' || buffer[1] == '\0'))
834        || Ustrcmp(buffer, "d") == 0)
835     {
836     if (name[0] == 0)
837       {
838       printf("No previous record name is set\n");
839       continue;
840       }
841     (void)sscanf(CS buffer, "%s %s", field, value);
842     }
843   else
844     {
845     name[0] = 0;
846     (void)sscanf(CS buffer, "%s %s %s", name, field, value);
847     }
848
849   /* Handle an update request */
850
851   if (field[0] != 0)
852     {
853     int verify = 1;
854
855     if (!(dbm = dbfn_open(aname, O_RDWR, &dbblock, FALSE, TRUE)))
856       continue;
857
858     if (Ustrcmp(field, "d") == 0)
859       {
860       if (value[0] != 0) printf("unexpected value after \"d\"\n");
861         else printf("%s\n", (dbfn_delete(dbm, name) < 0)?
862           "not found" : "deleted");
863       dbfn_close(dbm);
864       continue;
865       }
866
867     else if (isdigit((uschar)field[0]))
868       {
869       int fieldno = Uatoi(field);
870       if (value[0] == 0)
871         {
872         printf("value missing\n");
873         dbfn_close(dbm);
874         continue;
875         }
876       else
877         {
878         record = dbfn_read_with_length(dbm, name, &oldlength);
879         if (record == NULL) printf("not found\n"); else
880           {
881           time_t tt;
882           /*int length = 0;      Stops compiler warning */
883
884           switch(dbdata_type)
885             {
886             case type_retry:
887               retry = (dbdata_retry *)record;
888               /* length = sizeof(dbdata_retry) + Ustrlen(retry->text); */
889
890               switch(fieldno)
891                 {
892                 case 0: retry->basic_errno = Uatoi(value);
893                         break;
894                 case 1: retry->more_errno = Uatoi(value);
895                         break;
896                 case 2: if ((tt = read_time(value)) > 0) retry->first_failed = tt;
897                         else printf("bad time value\n");
898                         break;
899                 case 3: if ((tt = read_time(value)) > 0) retry->last_try = tt;
900                         else printf("bad time value\n");
901                         break;
902                 case 4: if ((tt = read_time(value)) > 0) retry->next_try = tt;
903                         else printf("bad time value\n");
904                         break;
905                 case 5: if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE;
906                         else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE;
907                         else printf("\"yes\" or \"no\" expected=n");
908                         break;
909                 default: printf("unknown field number\n");
910                          verify = 0;
911                          break;
912                 }
913               break;
914
915             case type_wait:
916               printf("Can't change contents of wait database record\n");
917               break;
918
919             case type_misc:
920               printf("Can't change contents of misc database record\n");
921               break;
922
923             case type_callout:
924               callout = (dbdata_callout_cache *)record;
925               /* length = sizeof(dbdata_callout_cache); */
926               switch(fieldno)
927                 {
928                 case 0: callout->result = Uatoi(value);
929                         break;
930                 case 1: callout->postmaster_result = Uatoi(value);
931                         break;
932                 case 2: callout->random_result = Uatoi(value);
933                         break;
934                 default: printf("unknown field number\n");
935                          verify = 0;
936                          break;
937                 }
938                 break;
939
940             case type_ratelimit:
941               ratelimit = (dbdata_ratelimit *)record;
942               switch(fieldno)
943                 {
944                 case 0: if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt;
945                         else printf("bad time value\n");
946                         break;
947                 case 1: ratelimit->time_usec = Uatoi(value);
948                         break;
949                 case 2: ratelimit->rate = Ustrtod(value, NULL);
950                         break;
951                 case 3: if (Ustrstr(name, "/unique/") != NULL
952                             && oldlength >= sizeof(dbdata_ratelimit_unique))
953                           {
954                           rate_unique = (dbdata_ratelimit_unique *)record;
955                           if ((tt = read_time(value)) > 0) rate_unique->bloom_epoch = tt;
956                             else printf("bad time value\n");
957                           break;
958                           }
959                         /* else fall through */
960                 case 4:
961                 case 5: if (Ustrstr(name, "/unique/") != NULL
962                             && oldlength >= sizeof(dbdata_ratelimit_unique))
963                           {
964                           /* see acl.c */
965                           BOOL seen;
966                           unsigned hash, hinc;
967                           uschar md5sum[16];
968                           md5 md5info;
969                           md5_start(&md5info);
970                           md5_end(&md5info, value, Ustrlen(value), md5sum);
971                           hash = md5sum[0] <<  0 | md5sum[1] <<  8
972                                | md5sum[2] << 16 | md5sum[3] << 24;
973                           hinc = md5sum[4] <<  0 | md5sum[5] <<  8
974                                | md5sum[6] << 16 | md5sum[7] << 24;
975                           rate_unique = (dbdata_ratelimit_unique *)record;
976                           seen = TRUE;
977                           for (unsigned n = 0; n < 8; n++, hash += hinc)
978                             {
979                             int bit = 1 << (hash % 8);
980                             int byte = (hash / 8) % rate_unique->bloom_size;
981                             if ((rate_unique->bloom[byte] & bit) == 0)
982                               {
983                               seen = FALSE;
984                               if (fieldno == 5) rate_unique->bloom[byte] |= bit;
985                               }
986                             }
987                           printf("%s %s\n",
988                             seen ? "seen" : fieldno == 5 ? "added" : "unseen", value);
989                           break;
990                           }
991                         /* else fall through */
992                 default: printf("unknown field number\n");
993                          verify = 0;
994                          break;
995                 }
996               break;
997
998             case type_tls:
999               printf("Can't change contents of tls database record\n");
1000               break;
1001             }
1002
1003           dbfn_write(dbm, name, record, oldlength);
1004           }
1005         }
1006       }
1007
1008     else
1009       {
1010       printf("field number or d expected\n");
1011       verify = 0;
1012       }
1013
1014     dbfn_close(dbm);
1015     if (!verify) continue;
1016     }
1017
1018   /* The "name" q causes an exit */
1019
1020   else if (Ustrcmp(name, "q") == 0) return 0;
1021
1022   /* Handle a read request, or verify after an update. */
1023
1024   if (!(dbm = dbfn_open(aname, O_RDONLY, &dbblock, FALSE, TRUE)))
1025     continue;
1026
1027   if (!(record = dbfn_read_with_length(dbm, name, &oldlength)))
1028     {
1029     printf("record %s not found\n", name);
1030     name[0] = 0;
1031     }
1032   else
1033     {
1034     int count_bad = 0;
1035     printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp));
1036     switch(dbdata_type)
1037       {
1038       case type_retry:
1039         retry = (dbdata_retry *)record;
1040         printf("0 error number: %d %s\n", retry->basic_errno, retry->text);
1041         printf("1 extra data:   %d\n", retry->more_errno);
1042         printf("2 first failed: %s\n", print_time(retry->first_failed));
1043         printf("3 last try:     %s\n", print_time(retry->last_try));
1044         printf("4 next try:     %s\n", print_time(retry->next_try));
1045         printf("5 expired:      %s\n", (retry->expired)? "yes" : "no");
1046         break;
1047
1048       case type_wait:
1049         wait = (dbdata_wait *)record;
1050         t = wait->text;
1051         printf("Sequence: %d\n", wait->sequence);
1052         if (wait->count > WAIT_NAME_MAX)
1053           {
1054           printf("**** Data corrupted: count=%d=0x%x max=%d ****\n", wait->count,
1055             wait->count, WAIT_NAME_MAX);
1056           wait->count = WAIT_NAME_MAX;
1057           count_bad = 1;
1058           }
1059         for (int i = 1; i <= wait->count; i++)
1060           {
1061           Ustrncpy(value, t, MESSAGE_ID_LENGTH);
1062           value[MESSAGE_ID_LENGTH] = 0;
1063           if (count_bad && value[0] == 0) break;
1064           if (Ustrlen(value) != MESSAGE_ID_LENGTH ||
1065               Ustrspn(value, "0123456789"
1066                             "abcdefghijklmnopqrstuvwxyz"
1067                             "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
1068             {
1069             printf("\n**** Data corrupted: bad character in message id ****\n");
1070             for (int j = 0; j < MESSAGE_ID_LENGTH; j++)
1071               printf("%02x ", value[j]);
1072             printf("\n");
1073             break;
1074             }
1075           printf("%s ", value);
1076           t += MESSAGE_ID_LENGTH;
1077           }
1078         printf("\n");
1079         break;
1080
1081       case type_misc:
1082         break;
1083
1084       case type_callout:
1085         callout = (dbdata_callout_cache *)record;
1086         printf("0 callout:    %s (%d)\n", print_cache(callout->result),
1087             callout->result);
1088         if (oldlength > sizeof(dbdata_callout_cache_address))
1089           {
1090           printf("1 postmaster: %s (%d)\n", print_cache(callout->postmaster_result),
1091               callout->postmaster_result);
1092           printf("2 random:     %s (%d)\n", print_cache(callout->random_result),
1093               callout->random_result);
1094           }
1095         break;
1096
1097       case type_ratelimit:
1098         ratelimit = (dbdata_ratelimit *)record;
1099         printf("0 time stamp:  %s\n", print_time(ratelimit->time_stamp));
1100         printf("1 fract. time: .%06d\n", ratelimit->time_usec);
1101         printf("2 sender rate: % .3f\n", ratelimit->rate);
1102         if (Ustrstr(name, "/unique/") != NULL
1103          && oldlength >= sizeof(dbdata_ratelimit_unique))
1104          {
1105          rate_unique = (dbdata_ratelimit_unique *)record;
1106          printf("3 filter epoch: %s\n", print_time(rate_unique->bloom_epoch));
1107          printf("4 test filter membership\n");
1108          printf("5 add element to filter\n");
1109          }
1110         break;
1111
1112       case type_tls:
1113         session = (dbdata_tls_session *)value;
1114         printf("0 time stamp:  %s\n", print_time(session->time_stamp));
1115         printf("1 session: .%s\n", session->session);
1116         break;
1117       }
1118     }
1119
1120   /* The database is closed after each request */
1121
1122   dbfn_close(dbm);
1123   }
1124
1125 printf("\n");
1126 return 0;
1127 }
1128
1129 #endif  /* EXIM_FIXDB */
1130
1131
1132
1133 #ifdef EXIM_TIDYDB
1134 /*************************************************
1135 *           The exim_tidydb main program         *
1136 *************************************************/
1137
1138
1139 /* Utility program to tidy the contents of an exim database file. There is one
1140 option:
1141
1142    -t <time>  expiry time for old records - default 30 days
1143
1144 For backwards compatibility, an -f option is recognized and ignored. (It used
1145 to request a "full" tidy. This version always does the whole job.) */
1146
1147
1148 typedef struct key_item {
1149   struct key_item *next;
1150   uschar key[1];
1151 } key_item;
1152
1153
1154 int
1155 main(int argc, char **cargv)
1156 {
1157 struct stat statbuf;
1158 int maxkeep = 30 * 24 * 60 * 60;
1159 int dbdata_type, i, oldest, path_len;
1160 key_item *keychain = NULL;
1161 rmark reset_point;
1162 open_db dbblock;
1163 open_db *dbm;
1164 EXIM_CURSOR *cursor;
1165 uschar **argv = USS cargv;
1166 uschar buffer[256];
1167 uschar *key;
1168
1169 store_init();
1170
1171 /* Scan the options */
1172
1173 for (i = 1; i < argc; i++)
1174   {
1175   if (argv[i][0] != '-') break;
1176   if (Ustrcmp(argv[i], "-f") == 0) continue;
1177   if (Ustrcmp(argv[i], "-t") == 0)
1178     {
1179     uschar *s;
1180     s = argv[++i];
1181     maxkeep = 0;
1182     while (*s != 0)
1183       {
1184       int value, count;
1185       if (!isdigit(*s)) usage(US"tidydb", US" [-t <time>]");
1186       (void)sscanf(CS s, "%d%n", &value, &count);
1187       s += count;
1188       switch (*s)
1189         {
1190         case 'w': value *= 7;
1191         case 'd': value *= 24;
1192         case 'h': value *= 60;
1193         case 'm': value *= 60;
1194         case 's': s++;
1195         break;
1196         default: usage(US"tidydb", US" [-t <time>]");
1197         }
1198       maxkeep += value;
1199       }
1200     }
1201   else usage(US"tidydb", US" [-t <time>]");
1202   }
1203
1204 /* Adjust argument values and process arguments */
1205
1206 argc -= --i;
1207 argv += i;
1208
1209 dbdata_type = check_args(argc, argv, US"tidydb", US" [-t <time>]");
1210
1211 /* Compute the oldest keep time, verify what we are doing, and open the
1212 database */
1213
1214 oldest = time(NULL) - maxkeep;
1215 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
1216
1217 spool_directory = argv[1];
1218 if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE, TRUE)))
1219   exit(1);
1220
1221 /* Prepare for building file names */
1222
1223 sprintf(CS buffer, "%s/input/", argv[1]);
1224 path_len = Ustrlen(buffer);
1225
1226
1227 /* It appears, by experiment, that it is a bad idea to make changes
1228 to the file while scanning it. Pity the man page doesn't warn you about that.
1229 Therefore, we scan and build a list of all the keys. Then we use that to
1230 read the records and possibly update them. */
1231
1232 for (key = dbfn_scan(dbm, TRUE, &cursor);
1233      key;
1234      key = dbfn_scan(dbm, FALSE, &cursor))
1235   {
1236   key_item * k = store_get(sizeof(key_item) + Ustrlen(key), key);
1237   k->next = keychain;
1238   keychain = k;
1239   Ustrcpy(k->key, key);
1240   }
1241
1242 /* Now scan the collected keys and operate on the records, resetting
1243 the store each time round. */
1244
1245 for (; keychain && (reset_point = store_mark()); store_reset(reset_point))
1246   {
1247   dbdata_generic *value;
1248
1249   key = keychain->key;
1250   keychain = keychain->next;
1251   value = dbfn_read_with_length(dbm, key, NULL);
1252
1253   /* A continuation record may have been deleted or renamed already, so
1254   non-existence is not serious. */
1255
1256   if (!value) continue;
1257
1258   /* Delete if too old */
1259
1260   if (value->time_stamp < oldest)
1261     {
1262     printf("deleted %s (too old)\n", key);
1263     dbfn_delete(dbm, key);
1264     continue;
1265     }
1266
1267   /* Do database-specific tidying for wait databases, and message-
1268   specific tidying for the retry database. */
1269
1270   if (dbdata_type == type_wait)
1271     {
1272     dbdata_wait *wait = (dbdata_wait *)value;
1273     BOOL update = FALSE;
1274
1275     /* Leave corrupt records alone */
1276
1277     if (wait->time_stamp > time(NULL))
1278       {
1279       printf("**** Data for '%s' corrupted\n  time in future: %s\n",
1280         key, print_time(((dbdata_generic *)value)->time_stamp));
1281       continue;
1282       }
1283     if (wait->count > WAIT_NAME_MAX)
1284       {
1285       printf("**** Data for '%s' corrupted\n  count=%d=0x%x max=%d\n",
1286         key, wait->count, wait->count, WAIT_NAME_MAX);
1287       continue;
1288       }
1289     if (wait->sequence > WAIT_CONT_MAX)
1290       {
1291       printf("**** Data for '%s' corrupted\n  sequence=%d=0x%x max=%d\n",
1292         key, wait->sequence, wait->sequence, WAIT_CONT_MAX);
1293       continue;
1294       }
1295
1296     /* Record over 1 year old; just remove it */
1297
1298     if (wait->time_stamp < time(NULL) - 365*24*60*60)
1299       {
1300       dbfn_delete(dbm, key);
1301       printf("deleted %s (too old)\n", key);
1302       continue;
1303       }
1304
1305     /* Loop for renamed continuation records. For each message id,
1306     check to see if the message exists, and if not, remove its entry
1307     from the record. Because of the possibility of split input directories,
1308     we must look in both possible places for a -D file. */
1309
1310     for (;;)
1311       {
1312       int length = wait->count * MESSAGE_ID_LENGTH;
1313
1314       for (int offset = length - MESSAGE_ID_LENGTH;
1315            offset >= 0; offset -= MESSAGE_ID_LENGTH)
1316         {
1317         Ustrncpy(buffer+path_len, wait->text + offset, MESSAGE_ID_LENGTH);
1318         sprintf(CS(buffer+path_len + MESSAGE_ID_LENGTH), "-D");
1319
1320         if (Ustat(buffer, &statbuf) != 0)
1321           {
1322           buffer[path_len] = wait->text[offset+5];
1323           buffer[path_len+1] = '/';
1324           Ustrncpy(buffer+path_len+2, wait->text + offset, MESSAGE_ID_LENGTH);
1325           sprintf(CS(buffer+path_len+2 + MESSAGE_ID_LENGTH), "-D");
1326
1327           if (Ustat(buffer, &statbuf) != 0)
1328             {
1329             int left = length - offset - MESSAGE_ID_LENGTH;
1330             if (left > 0) Ustrncpy(wait->text + offset,
1331               wait->text + offset + MESSAGE_ID_LENGTH, left);
1332             wait->count--;
1333             length -= MESSAGE_ID_LENGTH;
1334             update = TRUE;
1335             }
1336           }
1337         }
1338
1339       /* If record is empty and the main record, either delete it or rename
1340       the next continuation, repeating if that is also empty. */
1341
1342       if (wait->count == 0 && Ustrchr(key, ':') == NULL)
1343         {
1344         while (wait->count == 0 && wait->sequence > 0)
1345           {
1346           uschar newkey[256];
1347           dbdata_generic *newvalue;
1348           sprintf(CS newkey, "%s:%d", key, wait->sequence - 1);
1349           newvalue = dbfn_read_with_length(dbm, newkey, NULL);
1350           if (newvalue != NULL)
1351             {
1352             value = newvalue;
1353             wait = (dbdata_wait *)newvalue;
1354             dbfn_delete(dbm, newkey);
1355             printf("renamed %s\n", newkey);
1356             update = TRUE;
1357             }
1358           else wait->sequence--;
1359           }
1360
1361         /* If we have ended up with an empty main record, delete it
1362         and break the loop. Otherwise the new record will be scanned. */
1363
1364         if (wait->count == 0 && wait->sequence == 0)
1365           {
1366           dbfn_delete(dbm, key);
1367           printf("deleted %s (empty)\n", key);
1368           update = FALSE;
1369           break;
1370           }
1371         }
1372
1373       /* If not an empty main record, break the loop */
1374
1375       else break;
1376       }
1377
1378     /* Re-write the record if required */
1379
1380     if (update)
1381       {
1382       printf("updated %s\n", key);
1383       dbfn_write(dbm, key, wait, sizeof(dbdata_wait) +
1384         wait->count * MESSAGE_ID_LENGTH);
1385       }
1386     }
1387
1388   /* If a retry record's key ends with a message-id, check that that message
1389   still exists; if not, remove this record. */
1390
1391   else if (dbdata_type == type_retry)
1392     {
1393     uschar *id;
1394     int len = Ustrlen(key);
1395
1396     if (len < MESSAGE_ID_LENGTH + 1) continue;
1397     id = key + len - MESSAGE_ID_LENGTH - 1;
1398     if (*id++ != ':') continue;
1399
1400     for (i = 0; i < MESSAGE_ID_LENGTH; i++)
1401       if (i == 6 || i == 13)
1402         { if (id[i] != '-') break; }
1403       else
1404         { if (!isalnum(id[i])) break; }
1405     if (i < MESSAGE_ID_LENGTH) continue;
1406
1407     Ustrncpy(buffer + path_len, id, MESSAGE_ID_LENGTH);
1408     sprintf(CS(buffer + path_len + MESSAGE_ID_LENGTH), "-D");
1409
1410     if (Ustat(buffer, &statbuf) != 0)
1411       {
1412       sprintf(CS(buffer + path_len), "%c/%s-D", id[5], id);
1413       if (Ustat(buffer, &statbuf) != 0)
1414         {
1415         dbfn_delete(dbm, key);
1416         printf("deleted %s (no message)\n", key);
1417         }
1418       }
1419     }
1420   }
1421
1422 dbfn_close(dbm);
1423 printf("Tidying complete\n");
1424 return 0;
1425 }
1426
1427 #endif  /* EXIM_TIDYDB */
1428
1429 /* End of exim_dbutil.c */