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