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