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