(1) Typo in redirect router; (2) Update version number; (3) Update
[exim.git] / src / src / exim_dbutil.c
1 /* $Cambridge: exim/src/src/exim_dbutil.c,v 1.3 2005/01/04 10:00:42 ph10 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
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
79 errors. This should help with debugging strange DB problems, e.g. getting "File
80 exists" when you try to open a db file. */
81
82 #if defined(USE_DB) && defined(DB_VERSION_STRING)
83 void
84 dbfn_bdb_error_callback(const char *pfx, char *msg)
85 {
86 pfx = pfx;
87 printf("Berkeley DB error: %s\n", msg);
88 }
89 #endif
90
91
92
93 /*************************************************
94 * SIGALRM handler *
95 *************************************************/
96
97 static int sigalrm_seen;
98
99 void
100 sigalrm_handler(int sig)
101 {
102 sig = sig; /* Keep picky compilers happy */
103 sigalrm_seen = 1;
104 }
105
106
107
108 /*************************************************
109 * Output usage message and exit *
110 *************************************************/
111
112 static void
113 usage(uschar *name, uschar *options)
114 {
115 printf("Usage: exim_%s%s <spool-directory> <database-name>\n", name, options);
116 printf(" <database-name> = retry | misc | wait-<transport-name> | callout\n");
117 exit(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
127 second of them to be sure it is a known database name. */
128
129 static int
130 check_args(int argc, uschar **argv, uschar *name, uschar *options)
131 {
132 if (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 }
139 usage(name, options);
140 return -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
150 utility. The message always gets '\n' added on the end of it. These calls come
151 from modules such as store.c when things go drastically wrong (e.g. malloc()
152 failing). In normal use they won't get obeyed.
153
154 Arguments:
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
160 Returns: nothing
161 */
162
163 void
164 log_write(unsigned int selector, int flags, char *format, ...)
165 {
166 va_list ap;
167 va_start(ap, format);
168 vfprintf(stderr, format, ap);
169 fprintf(stderr, "\n");
170 va_end(ap);
171 selector = selector; /* Keep picky compilers happy */
172 flags = flags;
173 }
174
175
176
177 /*************************************************
178 * Format a time value for printing *
179 *************************************************/
180
181 static uschar time_buffer[sizeof("09-xxx-1999 hh:mm:ss ")];
182
183 uschar *
184 print_time(time_t t)
185 {
186 struct tm *tmstr = localtime(&t);
187 Ustrftime(time_buffer, sizeof(time_buffer), "%d-%b-%Y %H:%M:%S", tmstr);
188 return time_buffer;
189 }
190
191
192
193 /*************************************************
194 * Format a cache value for printing *
195 *************************************************/
196
197 uschar *
198 print_cache(int value)
199 {
200 return (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
211 static time_t
212 read_time(uschar *s)
213 {
214 uschar *t = s;
215 int field = 0;
216 int value;
217 time_t now = time(NULL);
218 struct tm *tm = localtime(&now);
219
220 tm->tm_sec = 0;
221 tm->tm_isdst = -1;
222
223 for (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
246 return 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
257 uses. We assume the database exists, and therefore give up if we cannot open
258 the lock file.
259
260 Arguments:
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
266 Returns: 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
271 static open_db *
272 dbfn_open(uschar *spool, uschar *name, int flags, open_db *dbblock)
273 {
274 int rc;
275 struct flock lock_data;
276 BOOL read_only = flags == O_RDONLY;
277 uschar buffer[256];
278
279 /* The first thing to do is to open a separate file on which to lock. This
280 ensures that Exim has exclusive use of the database before it even tries to
281 open it. If there is a database, there should be a lock file in existence. */
282
283 sprintf(CS buffer, "%s/db/%s.lockfile", spool, name);
284
285 dbblock->lockfd = Uopen(buffer, flags, 0);
286 if (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
294 lock that times out. */
295
296 lock_data.l_type = read_only? F_RDLCK : F_WRLCK;
297 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
298
299 sigalrm_seen = FALSE;
300 os_non_restarting_signal(SIGALRM, sigalrm_handler);
301 alarm(EXIMDB_LOCK_TIMEOUT);
302 rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
303 alarm(0);
304
305 if (sigalrm_seen) errno = ETIMEDOUT;
306 if (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,
316 exclusive access to the database, so we can go ahead and open it. */
317
318 sprintf(CS buffer, "%s/db/%s", spool, name);
319 EXIM_DBOPEN(buffer, flags, 0, &(dbblock->dbptr));
320
321 if (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
335 return 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
346 close the lock file.
347
348 Argument: a pointer to an open database block
349 Returns: nothing
350 */
351
352 static void
353 dbfn_close(open_db *dbblock)
354 {
355 EXIM_DBCLOSE(dbblock->dbptr);
356 close(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
367 of alignment. Since all the records used by Exim need to be properly aligned to
368 pick out the timestamps, etc., do the copying centrally here.
369
370 Arguments:
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
375 Returns: a pointer to the retrieved record, or
376 NULL if the record is not found
377 */
378
379 static void *
380 dbfn_read_with_length(open_db *dbblock, uschar *key, int *length)
381 {
382 void *yield;
383 EXIM_DATUM key_datum, result_datum;
384
385 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
386 EXIM_DATUM_INIT(result_datum); /* to be cleared before use. */
387 EXIM_DATUM_DATA(key_datum) = CS key;
388 EXIM_DATUM_SIZE(key_datum) = Ustrlen(key) + 1;
389
390 if (!EXIM_DBGET(dbblock->dbptr, key_datum, result_datum)) return NULL;
391
392 yield = store_get(EXIM_DATUM_SIZE(result_datum));
393 memcpy(yield, EXIM_DATUM_DATA(result_datum), EXIM_DATUM_SIZE(result_datum));
394 if (length != NULL) *length = EXIM_DATUM_SIZE(result_datum);
395
396 EXIM_DATUM_FREE(result_datum); /* Some DBM libs require freeing */
397 return yield;
398 }
399
400
401
402 #if defined(EXIM_TIDYDB) || defined(EXIM_FIXDB)
403
404 /*************************************************
405 * Write to database file *
406 *************************************************/
407
408 /*
409 Arguments:
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
415 Returns: the yield of the underlying dbm or db "write" function. If this
416 is dbm, the value is zero for OK.
417 */
418
419 static int
420 dbfn_write(open_db *dbblock, uschar *key, void *ptr, int length)
421 {
422 EXIM_DATUM key_datum, value_datum;
423 dbdata_generic *gptr = (dbdata_generic *)ptr;
424 gptr->time_stamp = time(NULL);
425
426 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
427 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
428 EXIM_DATUM_DATA(key_datum) = CS key;
429 EXIM_DATUM_SIZE(key_datum) = Ustrlen(key) + 1;
430 EXIM_DATUM_DATA(value_datum) = CS ptr;
431 EXIM_DATUM_SIZE(value_datum) = length;
432 return EXIM_DBPUT(dbblock->dbptr, key_datum, value_datum);
433 }
434
435
436
437 /*************************************************
438 * Delete record from database file *
439 *************************************************/
440
441 /*
442 Arguments:
443 dbblock a pointer to an open database block
444 key the key of the record to be deleted
445
446 Returns: the yield of the underlying dbm or db "delete" function.
447 */
448
449 static int
450 dbfn_delete(open_db *dbblock, uschar *key)
451 {
452 EXIM_DATUM key_datum;
453 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require clearing */
454 EXIM_DATUM_DATA(key_datum) = CS key;
455 EXIM_DATUM_SIZE(key_datum) = Ustrlen(key) + 1;
456 return 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 /*
469 Arguments:
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
476 Returns: the next record from the file, or
477 NULL if there are no more
478 */
479
480 static uschar *
481 dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
482 {
483 EXIM_DATUM key_datum, value_datum;
484 uschar *yield;
485 value_datum = value_datum; /* dummy; not all db libraries use this */
486
487 /* Some dbm require an initialization */
488
489 if (start) EXIM_DBCREATE_CURSOR(dbblock->dbptr, cursor);
490
491 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
492 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
493
494 yield = (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
499 if (!yield) EXIM_DBDELETE_CURSOR(*cursor);
500 return yield;
501 }
502 #endif /* EXIM_DUMPDB || EXIM_TIDYDB */
503
504
505
506 #ifdef EXIM_DUMPDB
507 /*************************************************
508 * The exim_dumpdb main program *
509 *************************************************/
510
511 int
512 main(int argc, char **cargv)
513 {
514 int dbdata_type = 0;
515 int yield = 0;
516 open_db dbblock;
517 open_db *dbm;
518 EXIM_CURSOR *cursor;
519 uschar **argv = USS cargv;
520 uschar *key;
521 uschar keybuffer[1024];
522
523 /* Check the arguments, and open the database */
524
525 dbdata_type = check_args(argc, argv, US"dumpdb", US"");
526 dbm = dbfn_open(argv[1], argv[2], O_RDONLY, &dbblock);
527 if (dbm == NULL) exit(1);
528
529 /* Scan the file, formatting the information for each entry. Note
530 that data is returned in a malloc'ed block, in order that it be
531 correctly aligned. */
532
533 key = dbfn_scan(dbm, TRUE, &cursor);
534 while (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
671 dbfn_close(dbm);
672 return 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
686 operation on the database uses a separate open/close call. This is expensive,
687 but then using this utility is not expected to be very common. Its main use is
688 to provide a way of patching up hints databases in order to run tests.
689
690 Syntax 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
708 If the record name is omitted from (2) or (3), the previously used record name
709 is re-used. */
710
711
712 int main(int argc, char **cargv)
713 {
714 int dbdata_type;
715 uschar **argv = USS cargv;
716 uschar buffer[256];
717 uschar name[256];
718 void *reset_point = store_get(0);
719
720 name[0] = 0; /* No name set */
721
722 /* Sort out the database type, verify what we are working on and then process
723 user requests */
724
725 dbdata_type = check_args(argc, argv, US"fixdb", US"");
726 printf("Modifying Exim hints database %s/db/%s\n", argv[1], argv[2]);
727
728 for(;;)
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
981 printf("\n");
982 return 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
996 option:
997
998 -t <time> expiry time for old records - default 30 days
999
1000 For backwards compatibility, an -f option is recognized and ignored. (It used
1001 to request a "full" tidy. This version always does the whole job.) */
1002
1003
1004 typedef struct key_item {
1005 struct key_item *next;
1006 uschar key[1];
1007 } key_item;
1008
1009
1010 int main(int argc, char **cargv)
1011 {
1012 struct stat statbuf;
1013 int maxkeep = 30 * 24 * 60 * 60;
1014 int dbdata_type, i, oldest, path_len;
1015 key_item *keychain = NULL;
1016 void *reset_point;
1017 open_db dbblock;
1018 open_db *dbm;
1019 EXIM_CURSOR *cursor;
1020 uschar **argv = USS cargv;
1021 uschar buffer[256];
1022 uschar *key;
1023
1024 /* Scan the options */
1025
1026 for (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
1059 argc -= --i;
1060 argv += i;
1061
1062 dbdata_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
1065 database */
1066
1067 oldest = time(NULL) - maxkeep;
1068 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
1069
1070 dbm = dbfn_open(argv[1], argv[2], O_RDWR, &dbblock);
1071 if (dbm == NULL) exit(1);
1072
1073 /* Prepare for building file names */
1074
1075 sprintf(CS buffer, "%s/input/", argv[1]);
1076 path_len = Ustrlen(buffer);
1077
1078
1079 /* It appears, by experiment, that it is a bad idea to make changes
1080 to the file while scanning it. Pity the man page doesn't warn you about that.
1081 Therefore, we scan and build a list of all the keys. Then we use that to
1082 read the records and possibly update them. */
1083
1084 key = dbfn_scan(dbm, TRUE, &cursor);
1085 while (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
1095 the store each time round. */
1096
1097 reset_point = store_get(0);
1098
1099 while (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 sprintf(CS(buffer + path_len), "%c/%s-D", id[5], id);
1250 if (Ustat(buffer, &statbuf) != 0)
1251 {
1252 dbfn_delete(dbm, key);
1253 printf("deleted %s (no message)\n", key);
1254 }
1255 }
1256 }
1257 }
1258
1259 dbfn_close(dbm);
1260 printf("Tidying complete\n");
1261 return 0;
1262 }
1263
1264 #endif /* EXIM_TIDYDB */
1265
1266 /* End of exim_dbutil.c */