1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) University of Cambridge 1995 - 2009 */
6 /* See the file NOTICE for conditions of use and distribution. */
9 /* This single source file is used to compile three utility programs for
10 maintaining Exim hints databases.
12 exim_dumpdb dumps out the contents
13 exim_fixdb patches the database (really for Exim maintenance/testing)
14 exim_tidydb removed obsolete data
16 In all cases, the first argument is the name of the spool directory. The second
17 argument is the name of the database file. The available names are:
19 retry: retry delivery information
20 misc: miscellaneous hints data
21 wait-<t>: message waiting information; <t> is a transport name
22 callout: callout verification cache
24 There are a number of common subroutines, followed by three main programs,
25 whose inclusion is controlled by -D on the compilation command. */
31 /* Identifiers for the different database types. */
36 #define type_callout 4
37 #define type_ratelimit 5
40 /* This is used by our cut-down dbfn_open(). */
42 uschar *spool_directory;
46 /*************************************************
47 * Berkeley DB error callback *
48 *************************************************/
50 /* For Berkeley DB >= 2, we can define a function to be called in case of DB
51 errors. This should help with debugging strange DB problems, e.g. getting "File
52 exists" when you try to open a db file. The API changed at release 4.3. */
54 #if defined(USE_DB) && defined(DB_VERSION_STRING)
56 #if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
57 dbfn_bdb_error_callback(const DB_ENV *dbenv, const char *pfx, const char *msg)
61 dbfn_bdb_error_callback(const char *pfx, char *msg)
65 printf("Berkeley DB error: %s\n", msg);
71 /*************************************************
73 *************************************************/
75 SIGNAL_BOOL sigalrm_seen;
78 sigalrm_handler(int sig)
80 sig = sig; /* Keep picky compilers happy */
86 /*************************************************
87 * Output usage message and exit *
88 *************************************************/
91 usage(uschar *name, uschar *options)
93 printf("Usage: exim_%s%s <spool-directory> <database-name>\n", name, options);
94 printf(" <database-name> = retry | misc | wait-<transport-name> | callout | ratelimit\n");
100 /*************************************************
101 * Sort out the command arguments *
102 *************************************************/
104 /* This function checks that there are exactly 2 arguments, and checks the
105 second of them to be sure it is a known database name. */
108 check_args(int argc, uschar **argv, uschar *name, uschar *options)
112 if (Ustrcmp(argv[2], "retry") == 0) return type_retry;
113 if (Ustrcmp(argv[2], "misc") == 0) return type_misc;
114 if (Ustrncmp(argv[2], "wait-", 5) == 0) return type_wait;
115 if (Ustrcmp(argv[2], "callout") == 0) return type_callout;
116 if (Ustrcmp(argv[2], "ratelimit") == 0) return type_ratelimit;
118 usage(name, options);
119 return -1; /* Never obeyed */
124 /*************************************************
125 * Handle attempts to write the log *
126 *************************************************/
128 /* The message gets written to stderr when log_write() is called from a
129 utility. The message always gets '\n' added on the end of it. These calls come
130 from modules such as store.c when things go drastically wrong (e.g. malloc()
131 failing). In normal use they won't get obeyed.
134 selector not relevant when running a utility
135 flags not relevant when running a utility
136 format a printf() format
137 ... arguments for format
143 log_write(unsigned int selector, int flags, const char *format, ...)
146 va_start(ap, format);
147 vfprintf(stderr, format, ap);
148 fprintf(stderr, "\n");
150 selector = selector; /* Keep picky compilers happy */
156 /*************************************************
157 * Format a time value for printing *
158 *************************************************/
160 static uschar time_buffer[sizeof("09-xxx-1999 hh:mm:ss ")];
165 struct tm *tmstr = localtime(&t);
166 Ustrftime(time_buffer, sizeof(time_buffer), "%d-%b-%Y %H:%M:%S", tmstr);
172 /*************************************************
173 * Format a cache value for printing *
174 *************************************************/
177 print_cache(int value)
179 return (value == ccache_accept)? US"accept" :
180 (value == ccache_reject)? US"reject" :
186 /*************************************************
188 *************************************************/
196 time_t now = time(NULL);
197 struct tm *tm = localtime(&now);
202 for (t = s + Ustrlen(s) - 1; t >= s; t--)
204 if (*t == ':') continue;
205 if (!isdigit((uschar)*t)) return -1;
210 if (!isdigit((uschar)*t)) return -1;
211 value = value + (*t - '0')*10;
216 case 0: tm->tm_min = value; break;
217 case 1: tm->tm_hour = value; break;
218 case 2: tm->tm_mday = value; break;
219 case 3: tm->tm_mon = value - 1; break;
220 case 4: tm->tm_year = (value < 90)? value + 100 : value; break;
227 #endif /* EXIM_FIXDB */
231 /*************************************************
232 * Open and lock a database file *
233 *************************************************/
235 /* This is a cut-down version from the function in dbfn.h that Exim itself
236 uses. We assume the database exists, and therefore give up if we cannot open
240 name The single-component name of one of Exim's database files.
241 flags O_RDONLY or O_RDWR
242 dbblock Points to an open_db block to be filled in.
245 Returns: NULL if the open failed, or the locking failed.
246 On success, dbblock is returned. This contains the dbm pointer and
247 the fd of the locked lock file.
251 dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof)
254 struct flock lock_data;
255 BOOL read_only = flags == O_RDONLY;
258 /* The first thing to do is to open a separate file on which to lock. This
259 ensures that Exim has exclusive use of the database before it even tries to
260 open it. If there is a database, there should be a lock file in existence. */
262 sprintf(CS buffer, "%s/db/%s.lockfile", spool_directory, name);
264 dbblock->lockfd = Uopen(buffer, flags, 0);
265 if (dbblock->lockfd < 0)
267 printf("** Failed to open database lock file %s: %s\n", buffer,
272 /* Now we must get a lock on the opened lock file; do this with a blocking
273 lock that times out. */
275 lock_data.l_type = read_only? F_RDLCK : F_WRLCK;
276 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
278 sigalrm_seen = FALSE;
279 os_non_restarting_signal(SIGALRM, sigalrm_handler);
280 alarm(EXIMDB_LOCK_TIMEOUT);
281 rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
284 if (sigalrm_seen) errno = ETIMEDOUT;
287 printf("** Failed to get %s lock for %s: %s",
288 ((flags & O_RDONLY) != 0)? "read" : "write", buffer,
289 (errno == ETIMEDOUT)? "timed out" : strerror(errno));
290 (void)close(dbblock->lockfd);
294 /* At this point we have an opened and locked separate lock file, that is,
295 exclusive access to the database, so we can go ahead and open it. */
297 sprintf(CS buffer, "%s/db/%s", spool_directory, name);
298 EXIM_DBOPEN(buffer, flags, 0, &(dbblock->dbptr));
300 if (dbblock->dbptr == NULL)
302 printf("** Failed to open DBM file %s for %s:\n %s%s\n", buffer,
303 read_only? "reading" : "writing", strerror(errno),
305 " (or Berkeley DB error while opening)"
310 (void)close(dbblock->lockfd);
320 /*************************************************
321 * Unlock and close a database file *
322 *************************************************/
324 /* Closing a file automatically unlocks it, so after closing the database, just
327 Argument: a pointer to an open database block
332 dbfn_close(open_db *dbblock)
334 EXIM_DBCLOSE(dbblock->dbptr);
335 (void)close(dbblock->lockfd);
341 /*************************************************
342 * Read from database file *
343 *************************************************/
345 /* Passing back the pointer unchanged is useless, because there is no guarantee
346 of alignment. Since all the records used by Exim need to be properly aligned to
347 pick out the timestamps, etc., do the copying centrally here.
350 dbblock a pointer to an open database block
351 key the key of the record to be read
352 length where to put the length (or NULL if length not wanted)
354 Returns: a pointer to the retrieved record, or
355 NULL if the record is not found
359 dbfn_read_with_length(open_db *dbblock, uschar *key, int *length)
362 EXIM_DATUM key_datum, result_datum;
364 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
365 EXIM_DATUM_INIT(result_datum); /* to be cleared before use. */
366 EXIM_DATUM_DATA(key_datum) = CS key;
367 EXIM_DATUM_SIZE(key_datum) = Ustrlen(key) + 1;
369 if (!EXIM_DBGET(dbblock->dbptr, key_datum, result_datum)) return NULL;
371 yield = store_get(EXIM_DATUM_SIZE(result_datum));
372 memcpy(yield, EXIM_DATUM_DATA(result_datum), EXIM_DATUM_SIZE(result_datum));
373 if (length != NULL) *length = EXIM_DATUM_SIZE(result_datum);
375 EXIM_DATUM_FREE(result_datum); /* Some DBM libs require freeing */
381 #if defined(EXIM_TIDYDB) || defined(EXIM_FIXDB)
383 /*************************************************
384 * Write to database file *
385 *************************************************/
389 dbblock a pointer to an open database block
390 key the key of the record to be written
391 ptr a pointer to the record to be written
392 length the length of the record to be written
394 Returns: the yield of the underlying dbm or db "write" function. If this
395 is dbm, the value is zero for OK.
399 dbfn_write(open_db *dbblock, uschar *key, void *ptr, int length)
401 EXIM_DATUM key_datum, value_datum;
402 dbdata_generic *gptr = (dbdata_generic *)ptr;
403 gptr->time_stamp = time(NULL);
405 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
406 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
407 EXIM_DATUM_DATA(key_datum) = CS key;
408 EXIM_DATUM_SIZE(key_datum) = Ustrlen(key) + 1;
409 EXIM_DATUM_DATA(value_datum) = CS ptr;
410 EXIM_DATUM_SIZE(value_datum) = length;
411 return EXIM_DBPUT(dbblock->dbptr, key_datum, value_datum);
416 /*************************************************
417 * Delete record from database file *
418 *************************************************/
422 dbblock a pointer to an open database block
423 key the key of the record to be deleted
425 Returns: the yield of the underlying dbm or db "delete" function.
429 dbfn_delete(open_db *dbblock, uschar *key)
431 EXIM_DATUM key_datum;
432 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require clearing */
433 EXIM_DATUM_DATA(key_datum) = CS key;
434 EXIM_DATUM_SIZE(key_datum) = Ustrlen(key) + 1;
435 return EXIM_DBDEL(dbblock->dbptr, key_datum);
438 #endif /* EXIM_TIDYDB || EXIM_FIXDB */
442 #if defined(EXIM_DUMPDB) || defined(EXIM_TIDYDB)
443 /*************************************************
444 * Scan the keys of a database file *
445 *************************************************/
449 dbblock a pointer to an open database block
450 start TRUE if starting a new scan
451 FALSE if continuing with the current scan
452 cursor a pointer to a pointer to a cursor anchor, for those dbm libraries
453 that use the notion of a cursor
455 Returns: the next record from the file, or
456 NULL if there are no more
460 dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
462 EXIM_DATUM key_datum, value_datum;
464 value_datum = value_datum; /* dummy; not all db libraries use this */
466 /* Some dbm require an initialization */
468 if (start) EXIM_DBCREATE_CURSOR(dbblock->dbptr, cursor);
470 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
471 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
473 yield = (EXIM_DBSCAN(dbblock->dbptr, key_datum, value_datum, start, *cursor))?
474 US EXIM_DATUM_DATA(key_datum) : NULL;
476 /* Some dbm require a termination */
478 if (!yield) EXIM_DBDELETE_CURSOR(*cursor);
481 #endif /* EXIM_DUMPDB || EXIM_TIDYDB */
486 /*************************************************
487 * The exim_dumpdb main program *
488 *************************************************/
491 main(int argc, char **cargv)
498 uschar **argv = USS cargv;
500 uschar keybuffer[1024];
502 /* Check the arguments, and open the database */
504 dbdata_type = check_args(argc, argv, US"dumpdb", US"");
505 spool_directory = argv[1];
506 dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE);
507 if (dbm == NULL) exit(1);
509 /* Scan the file, formatting the information for each entry. Note
510 that data is returned in a malloc'ed block, in order that it be
511 correctly aligned. */
513 key = dbfn_scan(dbm, TRUE, &cursor);
518 dbdata_callout_cache *callout;
519 dbdata_ratelimit *ratelimit;
520 dbdata_ratelimit_unique *rate_unique;
524 uschar name[MESSAGE_ID_LENGTH + 1];
527 /* Keep a copy of the key separate, as in some DBM's the pointer is into data
528 which might change. */
530 if (Ustrlen(key) > sizeof(keybuffer) - 1)
532 printf("**** Overlong key encountered: %s\n", key);
535 Ustrcpy(keybuffer, key);
536 value = dbfn_read_with_length(dbm, keybuffer, &length);
539 fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
540 "was not found in the file - something is wrong!\n",
544 /* Note: don't use print_time more than once in one statement, since
545 it uses a single buffer. */
550 retry = (dbdata_retry *)value;
551 printf(" %s %d %d %s\n%s ", keybuffer, retry->basic_errno,
552 retry->more_errno, retry->text,
553 print_time(retry->first_failed));
554 printf("%s ", print_time(retry->last_try));
555 printf("%s %s\n", print_time(retry->next_try),
556 (retry->expired)? "*" : "");
560 wait = (dbdata_wait *)value;
561 printf("%s ", keybuffer);
563 name[MESSAGE_ID_LENGTH] = 0;
565 if (wait->count > WAIT_NAME_MAX)
568 "**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
569 CS keybuffer, wait->count, wait->count, WAIT_NAME_MAX);
570 wait->count = WAIT_NAME_MAX;
571 yield = count_bad = 1;
573 for (i = 1; i <= wait->count; i++)
575 Ustrncpy(name, t, MESSAGE_ID_LENGTH);
576 if (count_bad && name[0] == 0) break;
577 if (Ustrlen(name) != MESSAGE_ID_LENGTH ||
578 Ustrspn(name, "0123456789"
579 "abcdefghijklmnopqrstuvwxyz"
580 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
584 "**** Data for %s corrupted: bad character in message id\n",
586 for (j = 0; j < MESSAGE_ID_LENGTH; j++)
587 fprintf(stderr, "%02x ", name[j]);
588 fprintf(stderr, "\n");
593 t += MESSAGE_ID_LENGTH;
599 printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
604 callout = (dbdata_callout_cache *)value;
606 /* New-style address record */
608 if (length == sizeof(dbdata_callout_cache_address))
610 printf("%s %s callout=%s\n",
611 print_time(((dbdata_generic *)value)->time_stamp),
613 print_cache(callout->result));
616 /* New-style domain record */
618 else if (length == sizeof(dbdata_callout_cache))
620 printf("%s %s callout=%s postmaster=%s",
621 print_time(((dbdata_generic *)value)->time_stamp),
623 print_cache(callout->result),
624 print_cache(callout->postmaster_result));
625 if (callout->postmaster_result != ccache_unknown)
626 printf(" (%s)", print_time(callout->postmaster_stamp));
627 printf(" random=%s", print_cache(callout->random_result));
628 if (callout->random_result != ccache_unknown)
629 printf(" (%s)", print_time(callout->random_stamp));
633 /* Old-style domain record, without separate timestamps. This code can
634 eventually be thrown away, say in 5 years' time (it's now Feb 2003). */
638 printf("%s %s callout=%s postmaster=%s random=%s\n",
639 print_time(((dbdata_generic *)value)->time_stamp),
641 print_cache(callout->result),
642 print_cache(callout->postmaster_result),
643 print_cache(callout->random_result));
649 if (Ustrstr(key, "/unique/") != NULL && length >= sizeof(*rate_unique))
651 ratelimit = (dbdata_ratelimit *)value;
652 rate_unique = (dbdata_ratelimit_unique *)value;
653 printf("%s.%06d rate: %10.3f epoch: %s size: %u key: %s\n",
654 print_time(ratelimit->time_stamp),
655 ratelimit->time_usec, ratelimit->rate,
656 print_time(rate_unique->bloom_epoch), rate_unique->bloom_size,
661 ratelimit = (dbdata_ratelimit *)value;
662 printf("%s.%06d rate: %10.3f key: %s\n",
663 print_time(ratelimit->time_stamp),
664 ratelimit->time_usec, ratelimit->rate,
671 key = dbfn_scan(dbm, FALSE, &cursor);
678 #endif /* EXIM_DUMPDB */
684 /*************************************************
685 * The exim_fixdb main program *
686 *************************************************/
688 /* In order not to hold the database lock any longer than is necessary, each
689 operation on the database uses a separate open/close call. This is expensive,
690 but then using this utility is not expected to be very common. Its main use is
691 to provide a way of patching up hints databases in order to run tests.
696 This causes the data from the given record to be displayed, or "not found"
697 to be output. Note that in the retry database, destination names are
698 preceded by R: or T: for router or transport retry info.
701 This causes the given record to be deleted or "not found" to be output.
703 (3) <record name> <field number> <value>
704 This sets the given value into the given field, identified by a number
705 which is output by the display command. Not all types of record can
709 This exits from exim_fixdb.
711 If the record name is omitted from (2) or (3), the previously used record name
715 int main(int argc, char **cargv)
718 uschar **argv = USS cargv;
721 void *reset_point = store_get(0);
723 name[0] = 0; /* No name set */
725 /* Sort out the database type, verify what we are working on and then process
728 dbdata_type = check_args(argc, argv, US"fixdb", US"");
729 printf("Modifying Exim hints database %s/db/%s\n", argv[1], argv[2]);
738 dbdata_callout_cache *callout;
739 dbdata_ratelimit *ratelimit;
740 dbdata_ratelimit_unique *rate_unique;
743 uschar field[256], value[256];
745 store_reset(reset_point);
748 if (Ufgets(buffer, 256, stdin) == NULL) break;
750 buffer[Ustrlen(buffer)-1] = 0;
751 field[0] = value[0] = 0;
753 /* If the buffer contains just one digit, or just consists of "d", use the
754 previous name for an update. */
756 if ((isdigit((uschar)buffer[0]) && (buffer[1] == ' ' || buffer[1] == '\0'))
757 || Ustrcmp(buffer, "d") == 0)
761 printf("No previous record name is set\n");
764 (void)sscanf(CS buffer, "%s %s", field, value);
769 (void)sscanf(CS buffer, "%s %s %s", name, field, value);
772 /* Handle an update request */
777 spool_directory = argv[1];
778 dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE);
779 if (dbm == NULL) continue;
781 if (Ustrcmp(field, "d") == 0)
783 if (value[0] != 0) printf("unexpected value after \"d\"\n");
784 else printf("%s\n", (dbfn_delete(dbm, name) < 0)?
785 "not found" : "deleted");
790 else if (isdigit((uschar)field[0]))
792 int fieldno = Uatoi(field);
795 printf("value missing\n");
801 record = dbfn_read_with_length(dbm, name, &oldlength);
802 if (record == NULL) printf("not found\n"); else
805 int length = 0; /* Stops compiler warning */
810 retry = (dbdata_retry *)record;
811 length = sizeof(dbdata_retry) + Ustrlen(retry->text);
816 retry->basic_errno = Uatoi(value);
820 retry->more_errno = Uatoi(value);
824 if ((tt = read_time(value)) > 0) retry->first_failed = tt;
825 else printf("bad time value\n");
829 if ((tt = read_time(value)) > 0) retry->last_try = tt;
830 else printf("bad time value\n");
834 if ((tt = read_time(value)) > 0) retry->next_try = tt;
835 else printf("bad time value\n");
839 if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE;
840 else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE;
841 else printf("\"yes\" or \"no\" expected=n");
845 printf("unknown field number\n");
852 printf("Can't change contents of wait database record\n");
856 printf("Can't change contents of misc database record\n");
860 callout = (dbdata_callout_cache *)record;
861 length = sizeof(dbdata_callout_cache);
865 callout->result = Uatoi(value);
869 callout->postmaster_result = Uatoi(value);
873 callout->random_result = Uatoi(value);
877 printf("unknown field number\n");
884 ratelimit = (dbdata_ratelimit *)record;
888 if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt;
889 else printf("bad time value\n");
893 ratelimit->time_usec = Uatoi(value);
897 ratelimit->rate = Ustrtod(value, NULL);
901 if (Ustrstr(name, "/unique/") != NULL
902 && oldlength >= sizeof(dbdata_ratelimit_unique))
904 rate_unique = (dbdata_ratelimit_unique *)record;
905 if ((tt = read_time(value)) > 0) rate_unique->bloom_epoch = tt;
906 else printf("bad time value\n");
909 /* else fall through */
913 if (Ustrstr(name, "/unique/") != NULL
914 && oldlength >= sizeof(dbdata_ratelimit_unique))
918 unsigned n, hash, hinc;
922 md5_end(&md5info, value, Ustrlen(value), md5sum);
923 hash = md5sum[0] << 0 | md5sum[1] << 8
924 | md5sum[2] << 16 | md5sum[3] << 24;
925 hinc = md5sum[4] << 0 | md5sum[5] << 8
926 | md5sum[6] << 16 | md5sum[7] << 24;
927 rate_unique = (dbdata_ratelimit_unique *)record;
929 for (n = 0; n < 8; n++, hash += hinc)
931 int bit = 1 << (hash % 8);
932 int byte = (hash / 8) % rate_unique->bloom_size;
933 if ((rate_unique->bloom[byte] & bit) == 0)
936 if (fieldno == 5) rate_unique->bloom[byte] |= bit;
940 seen ? "seen" : fieldno == 5 ? "added" : "unseen", value);
943 /* else fall through */
946 printf("unknown field number\n");
953 dbfn_write(dbm, name, record, oldlength);
960 printf("field number or d expected\n");
965 if (!verify) continue;
968 /* The "name" q causes an exit */
970 else if (Ustrcmp(name, "q") == 0) return 0;
972 /* Handle a read request, or verify after an update. */
974 spool_directory = argv[1];
975 dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE);
976 if (dbm == NULL) continue;
978 record = dbfn_read_with_length(dbm, name, &oldlength);
981 printf("record %s not found\n", name);
987 printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp));
991 retry = (dbdata_retry *)record;
992 printf("0 error number: %d %s\n", retry->basic_errno, retry->text);
993 printf("1 extra data: %d\n", retry->more_errno);
994 printf("2 first failed: %s\n", print_time(retry->first_failed));
995 printf("3 last try: %s\n", print_time(retry->last_try));
996 printf("4 next try: %s\n", print_time(retry->next_try));
997 printf("5 expired: %s\n", (retry->expired)? "yes" : "no");
1001 wait = (dbdata_wait *)record;
1003 printf("Sequence: %d\n", wait->sequence);
1004 if (wait->count > WAIT_NAME_MAX)
1006 printf("**** Data corrupted: count=%d=0x%x max=%d ****\n", wait->count,
1007 wait->count, WAIT_NAME_MAX);
1008 wait->count = WAIT_NAME_MAX;
1011 for (i = 1; i <= wait->count; i++)
1013 Ustrncpy(value, t, MESSAGE_ID_LENGTH);
1014 value[MESSAGE_ID_LENGTH] = 0;
1015 if (count_bad && value[0] == 0) break;
1016 if (Ustrlen(value) != MESSAGE_ID_LENGTH ||
1017 Ustrspn(value, "0123456789"
1018 "abcdefghijklmnopqrstuvwxyz"
1019 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
1022 printf("\n**** Data corrupted: bad character in message id ****\n");
1023 for (j = 0; j < MESSAGE_ID_LENGTH; j++)
1024 printf("%02x ", value[j]);
1028 printf("%s ", value);
1029 t += MESSAGE_ID_LENGTH;
1038 callout = (dbdata_callout_cache *)record;
1039 printf("0 callout: %s (%d)\n", print_cache(callout->result),
1041 if (oldlength > sizeof(dbdata_callout_cache_address))
1043 printf("1 postmaster: %s (%d)\n", print_cache(callout->postmaster_result),
1044 callout->postmaster_result);
1045 printf("2 random: %s (%d)\n", print_cache(callout->random_result),
1046 callout->random_result);
1050 case type_ratelimit:
1051 ratelimit = (dbdata_ratelimit *)record;
1052 printf("0 time stamp: %s\n", print_time(ratelimit->time_stamp));
1053 printf("1 fract. time: .%06d\n", ratelimit->time_usec);
1054 printf("2 sender rate: % .3f\n", ratelimit->rate);
1055 if (Ustrstr(name, "/unique/") != NULL
1056 && oldlength >= sizeof(dbdata_ratelimit_unique))
1058 rate_unique = (dbdata_ratelimit_unique *)record;
1059 printf("3 filter epoch: %s\n", print_time(rate_unique->bloom_epoch));
1060 printf("4 test filter membership\n");
1061 printf("5 add element to filter\n");
1067 /* The database is closed after each request */
1076 #endif /* EXIM_FIXDB */
1081 /*************************************************
1082 * The exim_tidydb main program *
1083 *************************************************/
1086 /* Utility program to tidy the contents of an exim database file. There is one
1089 -t <time> expiry time for old records - default 30 days
1091 For backwards compatibility, an -f option is recognized and ignored. (It used
1092 to request a "full" tidy. This version always does the whole job.) */
1095 typedef struct key_item {
1096 struct key_item *next;
1101 int main(int argc, char **cargv)
1103 struct stat statbuf;
1104 int maxkeep = 30 * 24 * 60 * 60;
1105 int dbdata_type, i, oldest, path_len;
1106 key_item *keychain = NULL;
1110 EXIM_CURSOR *cursor;
1111 uschar **argv = USS cargv;
1115 /* Scan the options */
1117 for (i = 1; i < argc; i++)
1119 if (argv[i][0] != '-') break;
1120 if (Ustrcmp(argv[i], "-f") == 0) continue;
1121 if (Ustrcmp(argv[i], "-t") == 0)
1129 if (!isdigit(*s)) usage(US"tidydb", US" [-t <time>]");
1130 (void)sscanf(CS s, "%d%n", &value, &count);
1134 case 'w': value *= 7;
1135 case 'd': value *= 24;
1136 case 'h': value *= 60;
1137 case 'm': value *= 60;
1140 default: usage(US"tidydb", US" [-t <time>]");
1145 else usage(US"tidydb", US" [-t <time>]");
1148 /* Adjust argument values and process arguments */
1153 dbdata_type = check_args(argc, argv, US"tidydb", US" [-t <time>]");
1155 /* Compute the oldest keep time, verify what we are doing, and open the
1158 oldest = time(NULL) - maxkeep;
1159 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
1161 spool_directory = argv[1];
1162 dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE);
1163 if (dbm == NULL) exit(1);
1165 /* Prepare for building file names */
1167 sprintf(CS buffer, "%s/input/", argv[1]);
1168 path_len = Ustrlen(buffer);
1171 /* It appears, by experiment, that it is a bad idea to make changes
1172 to the file while scanning it. Pity the man page doesn't warn you about that.
1173 Therefore, we scan and build a list of all the keys. Then we use that to
1174 read the records and possibly update them. */
1176 key = dbfn_scan(dbm, TRUE, &cursor);
1179 key_item *k = store_get(sizeof(key_item) + Ustrlen(key));
1182 Ustrcpy(k->key, key);
1183 key = dbfn_scan(dbm, FALSE, &cursor);
1186 /* Now scan the collected keys and operate on the records, resetting
1187 the store each time round. */
1189 reset_point = store_get(0);
1191 while (keychain != NULL)
1193 dbdata_generic *value;
1195 store_reset(reset_point);
1196 key = keychain->key;
1197 keychain = keychain->next;
1198 value = dbfn_read_with_length(dbm, key, NULL);
1200 /* A continuation record may have been deleted or renamed already, so
1201 non-existence is not serious. */
1203 if (value == NULL) continue;
1205 /* Delete if too old */
1207 if (value->time_stamp < oldest)
1209 printf("deleted %s (too old)\n", key);
1210 dbfn_delete(dbm, key);
1214 /* Do database-specific tidying for wait databases, and message-
1215 specific tidying for the retry database. */
1217 if (dbdata_type == type_wait)
1219 dbdata_wait *wait = (dbdata_wait *)value;
1220 BOOL update = FALSE;
1222 /* Leave corrupt records alone */
1224 if (wait->count > WAIT_NAME_MAX)
1226 printf("**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
1227 key, wait->count, wait->count, WAIT_NAME_MAX);
1231 /* Loop for renamed continuation records. For each message id,
1232 check to see if the message exists, and if not, remove its entry
1233 from the record. Because of the possibility of split input directories,
1234 we must look in both possible places for a -D file. */
1239 int length = wait->count * MESSAGE_ID_LENGTH;
1241 for (offset = length - MESSAGE_ID_LENGTH;
1242 offset >= 0; offset -= MESSAGE_ID_LENGTH)
1244 Ustrncpy(buffer+path_len, wait->text + offset, MESSAGE_ID_LENGTH);
1245 sprintf(CS(buffer+path_len + MESSAGE_ID_LENGTH), "-D");
1247 if (Ustat(buffer, &statbuf) != 0)
1249 buffer[path_len] = wait->text[offset+5];
1250 buffer[path_len+1] = '/';
1251 Ustrncpy(buffer+path_len+2, wait->text + offset, MESSAGE_ID_LENGTH);
1252 sprintf(CS(buffer+path_len+2 + MESSAGE_ID_LENGTH), "-D");
1254 if (Ustat(buffer, &statbuf) != 0)
1256 int left = length - offset - MESSAGE_ID_LENGTH;
1257 if (left > 0) Ustrncpy(wait->text + offset,
1258 wait->text + offset + MESSAGE_ID_LENGTH, left);
1260 length -= MESSAGE_ID_LENGTH;
1266 /* If record is empty and the main record, either delete it or rename
1267 the next continuation, repeating if that is also empty. */
1269 if (wait->count == 0 && Ustrchr(key, ':') == NULL)
1271 while (wait->count == 0 && wait->sequence > 0)
1274 dbdata_generic *newvalue;
1275 sprintf(CS newkey, "%s:%d", key, wait->sequence - 1);
1276 newvalue = dbfn_read_with_length(dbm, newkey, NULL);
1277 if (newvalue != NULL)
1280 wait = (dbdata_wait *)newvalue;
1281 dbfn_delete(dbm, newkey);
1282 printf("renamed %s\n", newkey);
1285 else wait->sequence--;
1288 /* If we have ended up with an empty main record, delete it
1289 and break the loop. Otherwise the new record will be scanned. */
1291 if (wait->count == 0 && wait->sequence == 0)
1293 dbfn_delete(dbm, key);
1294 printf("deleted %s (empty)\n", key);
1300 /* If not an empty main record, break the loop */
1305 /* Re-write the record if required */
1309 printf("updated %s\n", key);
1310 dbfn_write(dbm, key, wait, sizeof(dbdata_wait) +
1311 wait->count * MESSAGE_ID_LENGTH);
1315 /* If a retry record's key ends with a message-id, check that that message
1316 still exists; if not, remove this record. */
1318 else if (dbdata_type == type_retry)
1321 int len = Ustrlen(key);
1323 if (len < MESSAGE_ID_LENGTH + 1) continue;
1324 id = key + len - MESSAGE_ID_LENGTH - 1;
1325 if (*id++ != ':') continue;
1327 for (i = 0; i < MESSAGE_ID_LENGTH; i++)
1329 if (i == 6 || i == 13)
1330 { if (id[i] != '-') break; }
1332 { if (!isalnum(id[i])) break; }
1334 if (i < MESSAGE_ID_LENGTH) continue;
1336 Ustrncpy(buffer + path_len, id, MESSAGE_ID_LENGTH);
1337 sprintf(CS(buffer + path_len + MESSAGE_ID_LENGTH), "-D");
1339 if (Ustat(buffer, &statbuf) != 0)
1341 sprintf(CS(buffer + path_len), "%c/%s-D", id[5], id);
1342 if (Ustat(buffer, &statbuf) != 0)
1344 dbfn_delete(dbm, key);
1345 printf("deleted %s (no message)\n", key);
1352 printf("Tidying complete\n");
1356 #endif /* EXIM_TIDYDB */
1358 /* End of exim_dbutil.c */