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