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