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