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