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