SPDX: license tags (mostly by guesswork)
[exim.git] / src / src / exim_dbutil.c
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4
5 /* Copyright (c) The Exim Maintainers 2020 - 2022 */
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-only */
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_vformat_trc(gstring * g, const uschar * func, unsigned line,
65   unsigned size_limit, unsigned flags, const char *format, va_list ap)
66 { return NULL; }
67 uschar *
68 string_sprintf_trc(const char * fmt, const uschar * func, unsigned line, ...)
69 { return NULL; }
70 BOOL
71 string_format_trc(uschar * buf, int len, const uschar * func, unsigned line,
72   const char * fmt, ...)
73 { return FALSE; }
74
75 struct global_flags     f;
76 unsigned int            log_selector[1];
77 uschar *                queue_name;
78 BOOL                    split_spool_directory;
79
80
81 /* These introduced by the taintwarn handling */
82 #ifdef ALLOW_INSECURE_TAINTED_DATA
83 BOOL    allow_insecure_tainted_data;
84 #endif
85
86 /******************************************************************************/
87
88
89 /*************************************************
90 *              SIGALRM handler                   *
91 *************************************************/
92
93 SIGNAL_BOOL sigalrm_seen;
94
95 void
96 sigalrm_handler(int sig)
97 {
98 sigalrm_seen = 1;
99 }
100
101
102
103 /*************************************************
104 *        Output usage message and exit           *
105 *************************************************/
106
107 static void
108 usage(uschar *name, uschar *options)
109 {
110 printf("Usage: exim_%s%s  <spool-directory> <database-name>\n", name, options);
111 printf("  <database-name> = retry | misc | wait-<transport-name> | callout | ratelimit | tls | seen\n");
112 exit(EXIT_FAILURE);
113 }
114
115
116
117 /*************************************************
118 *           Sort out the command arguments       *
119 *************************************************/
120
121 /* This function checks that there are exactly 2 arguments, and checks the
122 second of them to be sure it is a known database name. */
123
124 static int
125 check_args(int argc, uschar **argv, uschar *name, uschar *options)
126 {
127 uschar * aname = argv[optind + 1];
128 if (argc - optind == 2)
129   {
130   if (Ustrcmp(aname, "retry") == 0)     return type_retry;
131   if (Ustrcmp(aname, "misc") == 0)      return type_misc;
132   if (Ustrncmp(aname, "wait-", 5) == 0) return type_wait;
133   if (Ustrcmp(aname, "callout") == 0)   return type_callout;
134   if (Ustrcmp(aname, "ratelimit") == 0) return type_ratelimit;
135   if (Ustrcmp(aname, "tls") == 0)       return type_tls;
136   if (Ustrcmp(aname, "seen") == 0)      return type_seen;
137   }
138 usage(name, options);
139 return -1;              /* Never obeyed */
140 }
141
142
143 FUNC_MAYBE_UNUSED
144 static void
145 options(int argc, uschar * argv[], uschar * name, const uschar * opts)
146 {
147 int opt;
148
149 opterr = 0;
150 while ((opt = getopt(argc, (char * const *)argv, CCS opts)) != -1)
151   switch (opt)
152   {
153   case 'k':     keyonly = TRUE; break;
154   case 'z':     utc = TRUE; break;
155   default:      usage(name, US" [-z] [-k]");
156   }
157 }
158
159
160
161
162 /*************************************************
163 *         Handle attempts to write the log       *
164 *************************************************/
165
166 /* The message gets written to stderr when log_write() is called from a
167 utility. The message always gets '\n' added on the end of it. These calls come
168 from modules such as store.c when things go drastically wrong (e.g. malloc()
169 failing). In normal use they won't get obeyed.
170
171 Arguments:
172   selector  not relevant when running a utility
173   flags     not relevant when running a utility
174   format    a printf() format
175   ...       arguments for format
176
177 Returns:    nothing
178 */
179
180 void
181 log_write(unsigned int selector, int flags, const char *format, ...)
182 {
183 va_list ap;
184 va_start(ap, format);
185 vfprintf(stderr, format, ap);
186 fprintf(stderr, "\n");
187 va_end(ap);
188 }
189
190
191
192 /*************************************************
193 *        Format a time value for printing        *
194 *************************************************/
195
196 static uschar time_buffer[sizeof("09-xxx-1999 hh:mm:ss  ")];
197
198 uschar *
199 print_time(time_t t)
200 {
201 struct tm *tmstr = utc ? gmtime(&t) : localtime(&t);
202 Ustrftime(time_buffer, sizeof(time_buffer), "%d-%b-%Y %H:%M:%S", tmstr);
203 return time_buffer;
204 }
205
206
207
208 /*************************************************
209 *        Format a cache value for printing       *
210 *************************************************/
211
212 uschar *
213 print_cache(int value)
214 {
215 return value == ccache_accept ? US"accept" :
216        value == ccache_reject ? US"reject" :
217        US"unknown";
218 }
219
220
221 #ifdef EXIM_FIXDB
222 /*************************************************
223 *                Read time value                 *
224 *************************************************/
225
226 static time_t
227 read_time(uschar *s)
228 {
229 int field = 0;
230 int value;
231 time_t now = time(NULL);
232 struct tm *tm = localtime(&now);
233
234 tm->tm_sec = 0;
235 tm->tm_isdst = -1;
236
237 for (uschar * t = s + Ustrlen(s) - 1; t >= s; t--)
238   {
239   if (*t == ':') continue;
240   if (!isdigit((uschar)*t)) return -1;
241
242   value = *t - '0';
243   if (--t >= s)
244     {
245     if (!isdigit((uschar)*t)) return -1;
246     value = value + (*t - '0')*10;
247     }
248
249   switch (field++)
250     {
251     case 0: tm->tm_min = value; break;
252     case 1: tm->tm_hour = value; break;
253     case 2: tm->tm_mday = value; break;
254     case 3: tm->tm_mon = value - 1; break;
255     case 4: tm->tm_year = (value < 90)? value + 100 : value; break;
256     default: return -1;
257     }
258   }
259
260 return mktime(tm);
261 }
262 #endif  /* EXIM_FIXDB */
263
264
265
266 /*************************************************
267 *       Open and lock a database file            *
268 *************************************************/
269
270 /* This is a cut-down version from the function in dbfn.h that Exim itself
271 uses. We assume the database exists, and therefore give up if we cannot open
272 the lock file.
273
274 Arguments:
275   name     The single-component name of one of Exim's database files.
276   flags    O_RDONLY or O_RDWR
277   dbblock  Points to an open_db block to be filled in.
278   lof      Unused.
279   panic    Unused
280
281 Returns:   NULL if the open failed, or the locking failed.
282            On success, dbblock is returned. This contains the dbm pointer and
283            the fd of the locked lock file.
284 */
285
286 open_db *
287 dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof, BOOL panic)
288 {
289 int rc;
290 struct flock lock_data;
291 BOOL read_only = flags == O_RDONLY;
292 uschar * dirname, * filename;
293
294 /* The first thing to do is to open a separate file on which to lock. This
295 ensures that Exim has exclusive use of the database before it even tries to
296 open it. If there is a database, there should be a lock file in existence. */
297
298 #ifdef COMPILE_UTILITY
299 if (  asprintf(CSS &dirname, "%s/db", spool_directory) < 0
300    || asprintf(CSS &filename, "%s/%s.lockfile", dirname, name) < 0)
301   return NULL;
302 #else
303 dirname = string_sprintf("%s/db", spool_directory);
304 filename = string_sprintf("%s/%s.lockfile", dirname, name);
305 #endif
306
307 dbblock->lockfd = Uopen(filename, flags, 0);
308 if (dbblock->lockfd < 0)
309   {
310   printf("** Failed to open database lock file %s: %s\n", filename,
311     strerror(errno));
312   return NULL;
313   }
314
315 /* Now we must get a lock on the opened lock file; do this with a blocking
316 lock that times out. */
317
318 lock_data.l_type = read_only ? F_RDLCK : F_WRLCK;
319 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
320
321 sigalrm_seen = FALSE;
322 os_non_restarting_signal(SIGALRM, sigalrm_handler);
323 ALARM(EXIMDB_LOCK_TIMEOUT);
324 rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
325 ALARM_CLR(0);
326
327 if (sigalrm_seen) errno = ETIMEDOUT;
328 if (rc < 0)
329   {
330   printf("** Failed to get %s lock for %s: %s",
331     flags & O_WRONLY ? "write" : "read",
332     filename,
333     errno == ETIMEDOUT ? "timed out" : strerror(errno));
334   (void)close(dbblock->lockfd);
335   return NULL;
336   }
337
338 /* At this point we have an opened and locked separate lock file, that is,
339 exclusive access to the database, so we can go ahead and open it. */
340
341 #ifdef COMPILE_UTILITY
342 if (asprintf(CSS &filename, "%s/%s", dirname, name) < 0) return NULL;
343 #else
344 filename = string_sprintf("%s/%s", dirname, name);
345 #endif
346 dbblock->dbptr = exim_dbopen(filename, dirname, flags, 0);
347
348 if (!dbblock->dbptr)
349   {
350   printf("** Failed to open DBM file %s for %s:\n   %s%s\n", filename,
351     read_only? "reading" : "writing", strerror(errno),
352     #ifdef USE_DB
353     " (or Berkeley DB error while opening)"
354     #else
355     ""
356     #endif
357     );
358   (void)close(dbblock->lockfd);
359   return NULL;
360   }
361
362 return dbblock;
363 }
364
365
366
367
368 /*************************************************
369 *         Unlock and close a database file       *
370 *************************************************/
371
372 /* Closing a file automatically unlocks it, so after closing the database, just
373 close the lock file.
374
375 Argument: a pointer to an open database block
376 Returns:  nothing
377 */
378
379 void
380 dbfn_close(open_db *dbblock)
381 {
382 exim_dbclose(dbblock->dbptr);
383 (void)close(dbblock->lockfd);
384 }
385
386
387
388
389 /*************************************************
390 *             Read from database file            *
391 *************************************************/
392
393 /* Passing back the pointer unchanged is useless, because there is no guarantee
394 of alignment. Since all the records used by Exim need to be properly aligned to
395 pick out the timestamps, etc., do the copying centrally here.
396
397 Arguments:
398   dbblock   a pointer to an open database block
399   key       the key of the record to be read
400   length    where to put the length (or NULL if length not wanted). Includes overhead.
401
402 Returns: a pointer to the retrieved record, or
403          NULL if the record is not found
404 */
405
406 void *
407 dbfn_read_with_length(open_db *dbblock, const uschar *key, int *length)
408 {
409 void *yield;
410 EXIM_DATUM key_datum, result_datum;
411 int klen = Ustrlen(key) + 1;
412 uschar * key_copy = store_get(klen, key);
413
414 memcpy(key_copy, key, klen);
415
416 exim_datum_init(&key_datum);         /* Some DBM libraries require the datum */
417 exim_datum_init(&result_datum);      /* to be cleared before use. */
418 exim_datum_data_set(&key_datum, key_copy);
419 exim_datum_size_set(&key_datum, klen);
420
421 if (!exim_dbget(dbblock->dbptr, &key_datum, &result_datum)) return NULL;
422
423 /* Assume for now that anything stored could have been tainted. Properly
424 we should store the taint status along with the data. */
425
426 yield = store_get(exim_datum_size_get(&result_datum), GET_TAINTED);
427 memcpy(yield, exim_datum_data_get(&result_datum), exim_datum_size_get(&result_datum));
428 if (length) *length = exim_datum_size_get(&result_datum);
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 record 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 */