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