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