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