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