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