Merged patched from Alex Miller: 1) Improve aveserver failure handling 2) Write prope...
[exim.git] / src / src / exim_dbutil.c
1 /* $Cambridge: exim/src/src/exim_dbutil.c,v 1.4 2005/05/23 16:58:56 fanf2 Exp $ */
2
3 /*************************************************
4 * Exim - an Internet mail transport agent *
5 *************************************************/
6
7 /* Copyright (c) University of Cambridge 1995 - 2005 */
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
12 maintaining 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
18 In all cases, the first argument is the name of the spool directory. The second
19 argument 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
26 There are a number of common subroutines, followed by three main programs,
27 whose 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
47 some 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
54 not 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 #define type_ratelimit 5
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
80 errors. This should help with debugging strange DB problems, e.g. getting "File
81 exists" when you try to open a db file. */
82
83 #if defined(USE_DB) && defined(DB_VERSION_STRING)
84 void
85 dbfn_bdb_error_callback(const char *pfx, char *msg)
86 {
87 pfx = pfx;
88 printf("Berkeley DB error: %s\n", msg);
89 }
90 #endif
91
92
93
94 /*************************************************
95 * SIGALRM handler *
96 *************************************************/
97
98 static int sigalrm_seen;
99
100 void
101 sigalrm_handler(int sig)
102 {
103 sig = sig; /* Keep picky compilers happy */
104 sigalrm_seen = 1;
105 }
106
107
108
109 /*************************************************
110 * Output usage message and exit *
111 *************************************************/
112
113 static void
114 usage(uschar *name, uschar *options)
115 {
116 printf("Usage: exim_%s%s <spool-directory> <database-name>\n", name, options);
117 printf(" <database-name> = retry | misc | wait-<transport-name> | callout | ratelimit\n");
118 exit(1);
119 }
120
121
122
123 /*************************************************
124 * Sort out the command arguments *
125 *************************************************/
126
127 /* This function checks that there are exactly 2 arguments, and checks the
128 second of them to be sure it is a known database name. */
129
130 static int
131 check_args(int argc, uschar **argv, uschar *name, uschar *options)
132 {
133 if (argc == 3)
134 {
135 if (Ustrcmp(argv[2], "retry") == 0) return type_retry;
136 if (Ustrcmp(argv[2], "misc") == 0) return type_misc;
137 if (Ustrncmp(argv[2], "wait-", 5) == 0) return type_wait;
138 if (Ustrcmp(argv[2], "callout") == 0) return type_callout;
139 if (Ustrcmp(argv[2], "ratelimit") == 0) return type_ratelimit;
140 }
141 usage(name, options);
142 return -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
152 utility. The message always gets '\n' added on the end of it. These calls come
153 from modules such as store.c when things go drastically wrong (e.g. malloc()
154 failing). In normal use they won't get obeyed.
155
156 Arguments:
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
162 Returns: nothing
163 */
164
165 void
166 log_write(unsigned int selector, int flags, char *format, ...)
167 {
168 va_list ap;
169 va_start(ap, format);
170 vfprintf(stderr, format, ap);
171 fprintf(stderr, "\n");
172 va_end(ap);
173 selector = selector; /* Keep picky compilers happy */
174 flags = flags;
175 }
176
177
178
179 /*************************************************
180 * Format a time value for printing *
181 *************************************************/
182
183 static uschar time_buffer[sizeof("09-xxx-1999 hh:mm:ss ")];
184
185 uschar *
186 print_time(time_t t)
187 {
188 struct tm *tmstr = localtime(&t);
189 Ustrftime(time_buffer, sizeof(time_buffer), "%d-%b-%Y %H:%M:%S", tmstr);
190 return time_buffer;
191 }
192
193
194
195 /*************************************************
196 * Format a cache value for printing *
197 *************************************************/
198
199 uschar *
200 print_cache(int value)
201 {
202 return (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
213 static time_t
214 read_time(uschar *s)
215 {
216 uschar *t = s;
217 int field = 0;
218 int value;
219 time_t now = time(NULL);
220 struct tm *tm = localtime(&now);
221
222 tm->tm_sec = 0;
223 tm->tm_isdst = -1;
224
225 for (t = s + Ustrlen(s) - 1; t >= s; t--)
226 {
227 if (*t == ':') continue;
228 if (!isdigit((uschar)*t)) return -1;
229
230 value = *t - '0';
231 if (--t >= s)
232 {
233 if (!isdigit((uschar)*t)) return -1;
234 value = value + (*t - '0')*10;
235 }
236
237 switch (field++)
238 {
239 case 0: tm->tm_min = value; break;
240 case 1: tm->tm_hour = value; break;
241 case 2: tm->tm_mday = value; break;
242 case 3: tm->tm_mon = value - 1; break;
243 case 4: tm->tm_year = (value < 90)? value + 100 : value; break;
244 default: return -1;
245 }
246 }
247
248 return mktime(tm);
249 }
250 #endif /* EXIM_FIXDB */
251
252
253
254 /*************************************************
255 * Open and lock a database file *
256 *************************************************/
257
258 /* This is a cut-down version from the function in dbfn.h that Exim itself
259 uses. We assume the database exists, and therefore give up if we cannot open
260 the lock file.
261
262 Arguments:
263 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
268 Returns: 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
273 static open_db *
274 dbfn_open(uschar *spool, uschar *name, int flags, open_db *dbblock)
275 {
276 int rc;
277 struct flock lock_data;
278 BOOL read_only = flags == O_RDONLY;
279 uschar buffer[256];
280
281 /* The first thing to do is to open a separate file on which to lock. This
282 ensures that Exim has exclusive use of the database before it even tries to
283 open it. If there is a database, there should be a lock file in existence. */
284
285 sprintf(CS buffer, "%s/db/%s.lockfile", spool, name);
286
287 dbblock->lockfd = Uopen(buffer, flags, 0);
288 if (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
296 lock that times out. */
297
298 lock_data.l_type = read_only? F_RDLCK : F_WRLCK;
299 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
300
301 sigalrm_seen = FALSE;
302 os_non_restarting_signal(SIGALRM, sigalrm_handler);
303 alarm(EXIMDB_LOCK_TIMEOUT);
304 rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
305 alarm(0);
306
307 if (sigalrm_seen) errno = ETIMEDOUT;
308 if (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,
318 exclusive access to the database, so we can go ahead and open it. */
319
320 sprintf(CS buffer, "%s/db/%s", spool, name);
321 EXIM_DBOPEN(buffer, flags, 0, &(dbblock->dbptr));
322
323 if (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
337 return 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
348 close the lock file.
349
350 Argument: a pointer to an open database block
351 Returns: nothing
352 */
353
354 static void
355 dbfn_close(open_db *dbblock)
356 {
357 EXIM_DBCLOSE(dbblock->dbptr);
358 close(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
369 of alignment. Since all the records used by Exim need to be properly aligned to
370 pick out the timestamps, etc., do the copying centrally here.
371
372 Arguments:
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
377 Returns: a pointer to the retrieved record, or
378 NULL if the record is not found
379 */
380
381 static void *
382 dbfn_read_with_length(open_db *dbblock, uschar *key, int *length)
383 {
384 void *yield;
385 EXIM_DATUM key_datum, result_datum;
386
387 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
388 EXIM_DATUM_INIT(result_datum); /* to be cleared before use. */
389 EXIM_DATUM_DATA(key_datum) = CS key;
390 EXIM_DATUM_SIZE(key_datum) = Ustrlen(key) + 1;
391
392 if (!EXIM_DBGET(dbblock->dbptr, key_datum, result_datum)) return NULL;
393
394 yield = store_get(EXIM_DATUM_SIZE(result_datum));
395 memcpy(yield, EXIM_DATUM_DATA(result_datum), EXIM_DATUM_SIZE(result_datum));
396 if (length != NULL) *length = EXIM_DATUM_SIZE(result_datum);
397
398 EXIM_DATUM_FREE(result_datum); /* Some DBM libs require freeing */
399 return yield;
400 }
401
402
403
404 #if defined(EXIM_TIDYDB) || defined(EXIM_FIXDB)
405
406 /*************************************************
407 * Write to database file *
408 *************************************************/
409
410 /*
411 Arguments:
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
417 Returns: the yield of the underlying dbm or db "write" function. If this
418 is dbm, the value is zero for OK.
419 */
420
421 static int
422 dbfn_write(open_db *dbblock, uschar *key, void *ptr, int length)
423 {
424 EXIM_DATUM key_datum, value_datum;
425 dbdata_generic *gptr = (dbdata_generic *)ptr;
426 gptr->time_stamp = time(NULL);
427
428 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
429 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
430 EXIM_DATUM_DATA(key_datum) = CS key;
431 EXIM_DATUM_SIZE(key_datum) = Ustrlen(key) + 1;
432 EXIM_DATUM_DATA(value_datum) = CS ptr;
433 EXIM_DATUM_SIZE(value_datum) = length;
434 return EXIM_DBPUT(dbblock->dbptr, key_datum, value_datum);
435 }
436
437
438
439 /*************************************************
440 * Delete record from database file *
441 *************************************************/
442
443 /*
444 Arguments:
445 dbblock a pointer to an open database block
446 key the key of the record to be deleted
447
448 Returns: the yield of the underlying dbm or db "delete" function.
449 */
450
451 static int
452 dbfn_delete(open_db *dbblock, uschar *key)
453 {
454 EXIM_DATUM key_datum;
455 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require clearing */
456 EXIM_DATUM_DATA(key_datum) = CS key;
457 EXIM_DATUM_SIZE(key_datum) = Ustrlen(key) + 1;
458 return 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 /*
471 Arguments:
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
478 Returns: the next record from the file, or
479 NULL if there are no more
480 */
481
482 static uschar *
483 dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
484 {
485 EXIM_DATUM key_datum, value_datum;
486 uschar *yield;
487 value_datum = value_datum; /* dummy; not all db libraries use this */
488
489 /* Some dbm require an initialization */
490
491 if (start) EXIM_DBCREATE_CURSOR(dbblock->dbptr, cursor);
492
493 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
494 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
495
496 yield = (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
501 if (!yield) EXIM_DBDELETE_CURSOR(*cursor);
502 return yield;
503 }
504 #endif /* EXIM_DUMPDB || EXIM_TIDYDB */
505
506
507
508 #ifdef EXIM_DUMPDB
509 /*************************************************
510 * The exim_dumpdb main program *
511 *************************************************/
512
513 int
514 main(int argc, char **cargv)
515 {
516 int dbdata_type = 0;
517 int yield = 0;
518 open_db dbblock;
519 open_db *dbm;
520 EXIM_CURSOR *cursor;
521 uschar **argv = USS cargv;
522 uschar *key;
523 uschar keybuffer[1024];
524
525 /* Check the arguments, and open the database */
526
527 dbdata_type = check_args(argc, argv, US"dumpdb", US"");
528 dbm = dbfn_open(argv[1], argv[2], O_RDONLY, &dbblock);
529 if (dbm == NULL) exit(1);
530
531 /* Scan the file, formatting the information for each entry. Note
532 that data is returned in a malloc'ed block, in order that it be
533 correctly aligned. */
534
535 key = dbfn_scan(dbm, TRUE, &cursor);
536 while (key != NULL)
537 {
538 dbdata_retry *retry;
539 dbdata_wait *wait;
540 dbdata_callout_cache *callout;
541 dbdata_ratelimit *ratelimit;
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
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
676 break;
677 }
678 store_reset(value);
679 }
680 key = dbfn_scan(dbm, FALSE, &cursor);
681 }
682
683 dbfn_close(dbm);
684 return 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
698 operation on the database uses a separate open/close call. This is expensive,
699 but then using this utility is not expected to be very common. Its main use is
700 to provide a way of patching up hints databases in order to run tests.
701
702 Syntax 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
720 If the record name is omitted from (2) or (3), the previously used record name
721 is re-used. */
722
723
724 int main(int argc, char **cargv)
725 {
726 int dbdata_type;
727 uschar **argv = USS cargv;
728 uschar buffer[256];
729 uschar name[256];
730 void *reset_point = store_get(0);
731
732 name[0] = 0; /* No name set */
733
734 /* Sort out the database type, verify what we are working on and then process
735 user requests */
736
737 dbdata_type = check_args(argc, argv, US"fixdb", US"");
738 printf("Modifying Exim hints database %s/db/%s\n", argv[1], argv[2]);
739
740 for(;;)
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;
748 dbdata_ratelimit *ratelimit;
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;
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;
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;
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;
1016 }
1017 }
1018
1019 /* The database is closed after each request */
1020
1021 dbfn_close(dbm);
1022 }
1023
1024 printf("\n");
1025 return 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
1039 option:
1040
1041 -t <time> expiry time for old records - default 30 days
1042
1043 For backwards compatibility, an -f option is recognized and ignored. (It used
1044 to request a "full" tidy. This version always does the whole job.) */
1045
1046
1047 typedef struct key_item {
1048 struct key_item *next;
1049 uschar key[1];
1050 } key_item;
1051
1052
1053 int main(int argc, char **cargv)
1054 {
1055 struct stat statbuf;
1056 int maxkeep = 30 * 24 * 60 * 60;
1057 int dbdata_type, i, oldest, path_len;
1058 key_item *keychain = NULL;
1059 void *reset_point;
1060 open_db dbblock;
1061 open_db *dbm;
1062 EXIM_CURSOR *cursor;
1063 uschar **argv = USS cargv;
1064 uschar buffer[256];
1065 uschar *key;
1066
1067 /* Scan the options */
1068
1069 for (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
1102 argc -= --i;
1103 argv += i;
1104
1105 dbdata_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
1108 database */
1109
1110 oldest = time(NULL) - maxkeep;
1111 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
1112
1113 dbm = dbfn_open(argv[1], argv[2], O_RDWR, &dbblock);
1114 if (dbm == NULL) exit(1);
1115
1116 /* Prepare for building file names */
1117
1118 sprintf(CS buffer, "%s/input/", argv[1]);
1119 path_len = Ustrlen(buffer);
1120
1121
1122 /* It appears, by experiment, that it is a bad idea to make changes
1123 to the file while scanning it. Pity the man page doesn't warn you about that.
1124 Therefore, we scan and build a list of all the keys. Then we use that to
1125 read the records and possibly update them. */
1126
1127 key = dbfn_scan(dbm, TRUE, &cursor);
1128 while (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
1138 the store each time round. */
1139
1140 reset_point = store_get(0);
1141
1142 while (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 {
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 }
1298 }
1299 }
1300 }
1301
1302 dbfn_close(dbm);
1303 printf("Tidying complete\n");
1304 return 0;
1305 }
1306
1307 #endif /* EXIM_TIDYDB */
1308
1309 /* End of exim_dbutil.c */