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