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