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