1 /* $Cambridge: exim/src/src/exim_dbutil.c,v 1.12 2009/11/16 19:50:36 nm4 Exp $ */
3 /*************************************************
4 * Exim - an Internet mail transport agent *
5 *************************************************/
7 /* Copyright (c) University of Cambridge 1995 - 2009 */
8 /* See the file NOTICE for conditions of use and distribution. */
11 /* This single source file is used to compile three utility programs for
12 maintaining Exim hints databases.
14 exim_dumpdb dumps out the contents
15 exim_fixdb patches the database (really for Exim maintenance/testing)
16 exim_tidydb removed obsolete data
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:
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
26 There are a number of common subroutines, followed by three main programs,
27 whose inclusion is controlled by -D on the compilation command. */
33 /* Identifiers for the different database types. */
38 #define type_callout 4
39 #define type_ratelimit 5
42 /* This is used by our cut-down dbfn_open(). */
44 uschar *spool_directory;
48 /*************************************************
49 * Berkeley DB error callback *
50 *************************************************/
52 /* For Berkeley DB >= 2, we can define a function to be called in case of DB
53 errors. This should help with debugging strange DB problems, e.g. getting "File
54 exists" when you try to open a db file. The API changed at release 4.3. */
56 #if defined(USE_DB) && defined(DB_VERSION_STRING)
58 #if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
59 dbfn_bdb_error_callback(const DB_ENV *dbenv, const char *pfx, const char *msg)
63 dbfn_bdb_error_callback(const char *pfx, char *msg)
67 printf("Berkeley DB error: %s\n", msg);
73 /*************************************************
75 *************************************************/
77 SIGNAL_BOOL sigalrm_seen;
80 sigalrm_handler(int sig)
82 sig = sig; /* Keep picky compilers happy */
88 /*************************************************
89 * Output usage message and exit *
90 *************************************************/
93 usage(uschar *name, uschar *options)
95 printf("Usage: exim_%s%s <spool-directory> <database-name>\n", name, options);
96 printf(" <database-name> = retry | misc | wait-<transport-name> | callout | ratelimit\n");
102 /*************************************************
103 * Sort out the command arguments *
104 *************************************************/
106 /* This function checks that there are exactly 2 arguments, and checks the
107 second of them to be sure it is a known database name. */
110 check_args(int argc, uschar **argv, uschar *name, uschar *options)
114 if (Ustrcmp(argv[2], "retry") == 0) return type_retry;
115 if (Ustrcmp(argv[2], "misc") == 0) return type_misc;
116 if (Ustrncmp(argv[2], "wait-", 5) == 0) return type_wait;
117 if (Ustrcmp(argv[2], "callout") == 0) return type_callout;
118 if (Ustrcmp(argv[2], "ratelimit") == 0) return type_ratelimit;
120 usage(name, options);
121 return -1; /* Never obeyed */
126 /*************************************************
127 * Handle attempts to write the log *
128 *************************************************/
130 /* The message gets written to stderr when log_write() is called from a
131 utility. The message always gets '\n' added on the end of it. These calls come
132 from modules such as store.c when things go drastically wrong (e.g. malloc()
133 failing). In normal use they won't get obeyed.
136 selector not relevant when running a utility
137 flags not relevant when running a utility
138 format a printf() format
139 ... arguments for format
145 log_write(unsigned int selector, int flags, const char *format, ...)
148 va_start(ap, format);
149 vfprintf(stderr, format, ap);
150 fprintf(stderr, "\n");
152 selector = selector; /* Keep picky compilers happy */
158 /*************************************************
159 * Format a time value for printing *
160 *************************************************/
162 static uschar time_buffer[sizeof("09-xxx-1999 hh:mm:ss ")];
167 struct tm *tmstr = localtime(&t);
168 Ustrftime(time_buffer, sizeof(time_buffer), "%d-%b-%Y %H:%M:%S", tmstr);
174 /*************************************************
175 * Format a cache value for printing *
176 *************************************************/
179 print_cache(int value)
181 return (value == ccache_accept)? US"accept" :
182 (value == ccache_reject)? US"reject" :
188 /*************************************************
190 *************************************************/
198 time_t now = time(NULL);
199 struct tm *tm = localtime(&now);
204 for (t = s + Ustrlen(s) - 1; t >= s; t--)
206 if (*t == ':') continue;
207 if (!isdigit((uschar)*t)) return -1;
212 if (!isdigit((uschar)*t)) return -1;
213 value = value + (*t - '0')*10;
218 case 0: tm->tm_min = value; break;
219 case 1: tm->tm_hour = value; break;
220 case 2: tm->tm_mday = value; break;
221 case 3: tm->tm_mon = value - 1; break;
222 case 4: tm->tm_year = (value < 90)? value + 100 : value; break;
229 #endif /* EXIM_FIXDB */
233 /*************************************************
234 * Open and lock a database file *
235 *************************************************/
237 /* This is a cut-down version from the function in dbfn.h that Exim itself
238 uses. We assume the database exists, and therefore give up if we cannot open
242 name The single-component name of one of Exim's database files.
243 flags O_RDONLY or O_RDWR
244 dbblock Points to an open_db block to be filled in.
247 Returns: NULL if the open failed, or the locking failed.
248 On success, dbblock is returned. This contains the dbm pointer and
249 the fd of the locked lock file.
253 dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof)
256 struct flock lock_data;
257 BOOL read_only = flags == O_RDONLY;
260 /* The first thing to do is to open a separate file on which to lock. This
261 ensures that Exim has exclusive use of the database before it even tries to
262 open it. If there is a database, there should be a lock file in existence. */
264 sprintf(CS buffer, "%s/db/%s.lockfile", spool_directory, name);
266 dbblock->lockfd = Uopen(buffer, flags, 0);
267 if (dbblock->lockfd < 0)
269 printf("** Failed to open database lock file %s: %s\n", buffer,
274 /* Now we must get a lock on the opened lock file; do this with a blocking
275 lock that times out. */
277 lock_data.l_type = read_only? F_RDLCK : F_WRLCK;
278 lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
280 sigalrm_seen = FALSE;
281 os_non_restarting_signal(SIGALRM, sigalrm_handler);
282 alarm(EXIMDB_LOCK_TIMEOUT);
283 rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
286 if (sigalrm_seen) errno = ETIMEDOUT;
289 printf("** Failed to get %s lock for %s: %s",
290 ((flags & O_RDONLY) != 0)? "read" : "write", buffer,
291 (errno == ETIMEDOUT)? "timed out" : strerror(errno));
292 (void)close(dbblock->lockfd);
296 /* At this point we have an opened and locked separate lock file, that is,
297 exclusive access to the database, so we can go ahead and open it. */
299 sprintf(CS buffer, "%s/db/%s", spool_directory, name);
300 EXIM_DBOPEN(buffer, flags, 0, &(dbblock->dbptr));
302 if (dbblock->dbptr == NULL)
304 printf("** Failed to open DBM file %s for %s:\n %s%s\n", buffer,
305 read_only? "reading" : "writing", strerror(errno),
307 " (or Berkeley DB error while opening)"
312 (void)close(dbblock->lockfd);
322 /*************************************************
323 * Unlock and close a database file *
324 *************************************************/
326 /* Closing a file automatically unlocks it, so after closing the database, just
329 Argument: a pointer to an open database block
334 dbfn_close(open_db *dbblock)
336 EXIM_DBCLOSE(dbblock->dbptr);
337 (void)close(dbblock->lockfd);
343 /*************************************************
344 * Read from database file *
345 *************************************************/
347 /* Passing back the pointer unchanged is useless, because there is no guarantee
348 of alignment. Since all the records used by Exim need to be properly aligned to
349 pick out the timestamps, etc., do the copying centrally here.
352 dbblock a pointer to an open database block
353 key the key of the record to be read
354 length where to put the length (or NULL if length not wanted)
356 Returns: a pointer to the retrieved record, or
357 NULL if the record is not found
361 dbfn_read_with_length(open_db *dbblock, uschar *key, int *length)
364 EXIM_DATUM key_datum, result_datum;
366 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
367 EXIM_DATUM_INIT(result_datum); /* to be cleared before use. */
368 EXIM_DATUM_DATA(key_datum) = CS key;
369 EXIM_DATUM_SIZE(key_datum) = Ustrlen(key) + 1;
371 if (!EXIM_DBGET(dbblock->dbptr, key_datum, result_datum)) return NULL;
373 yield = store_get(EXIM_DATUM_SIZE(result_datum));
374 memcpy(yield, EXIM_DATUM_DATA(result_datum), EXIM_DATUM_SIZE(result_datum));
375 if (length != NULL) *length = EXIM_DATUM_SIZE(result_datum);
377 EXIM_DATUM_FREE(result_datum); /* Some DBM libs require freeing */
383 #if defined(EXIM_TIDYDB) || defined(EXIM_FIXDB)
385 /*************************************************
386 * Write to database file *
387 *************************************************/
391 dbblock a pointer to an open database block
392 key the key of the record to be written
393 ptr a pointer to the record to be written
394 length the length of the record to be written
396 Returns: the yield of the underlying dbm or db "write" function. If this
397 is dbm, the value is zero for OK.
401 dbfn_write(open_db *dbblock, uschar *key, void *ptr, int length)
403 EXIM_DATUM key_datum, value_datum;
404 dbdata_generic *gptr = (dbdata_generic *)ptr;
405 gptr->time_stamp = time(NULL);
407 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
408 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
409 EXIM_DATUM_DATA(key_datum) = CS key;
410 EXIM_DATUM_SIZE(key_datum) = Ustrlen(key) + 1;
411 EXIM_DATUM_DATA(value_datum) = CS ptr;
412 EXIM_DATUM_SIZE(value_datum) = length;
413 return EXIM_DBPUT(dbblock->dbptr, key_datum, value_datum);
418 /*************************************************
419 * Delete record from database file *
420 *************************************************/
424 dbblock a pointer to an open database block
425 key the key of the record to be deleted
427 Returns: the yield of the underlying dbm or db "delete" function.
431 dbfn_delete(open_db *dbblock, uschar *key)
433 EXIM_DATUM key_datum;
434 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require clearing */
435 EXIM_DATUM_DATA(key_datum) = CS key;
436 EXIM_DATUM_SIZE(key_datum) = Ustrlen(key) + 1;
437 return EXIM_DBDEL(dbblock->dbptr, key_datum);
440 #endif /* EXIM_TIDYDB || EXIM_FIXDB */
444 #if defined(EXIM_DUMPDB) || defined(EXIM_TIDYDB)
445 /*************************************************
446 * Scan the keys of a database file *
447 *************************************************/
451 dbblock a pointer to an open database block
452 start TRUE if starting a new scan
453 FALSE if continuing with the current scan
454 cursor a pointer to a pointer to a cursor anchor, for those dbm libraries
455 that use the notion of a cursor
457 Returns: the next record from the file, or
458 NULL if there are no more
462 dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
464 EXIM_DATUM key_datum, value_datum;
466 value_datum = value_datum; /* dummy; not all db libraries use this */
468 /* Some dbm require an initialization */
470 if (start) EXIM_DBCREATE_CURSOR(dbblock->dbptr, cursor);
472 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
473 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
475 yield = (EXIM_DBSCAN(dbblock->dbptr, key_datum, value_datum, start, *cursor))?
476 US EXIM_DATUM_DATA(key_datum) : NULL;
478 /* Some dbm require a termination */
480 if (!yield) EXIM_DBDELETE_CURSOR(*cursor);
483 #endif /* EXIM_DUMPDB || EXIM_TIDYDB */
488 /*************************************************
489 * The exim_dumpdb main program *
490 *************************************************/
493 main(int argc, char **cargv)
500 uschar **argv = USS cargv;
502 uschar keybuffer[1024];
504 /* Check the arguments, and open the database */
506 dbdata_type = check_args(argc, argv, US"dumpdb", US"");
507 spool_directory = argv[1];
508 dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE);
509 if (dbm == NULL) exit(1);
511 /* Scan the file, formatting the information for each entry. Note
512 that data is returned in a malloc'ed block, in order that it be
513 correctly aligned. */
515 key = dbfn_scan(dbm, TRUE, &cursor);
520 dbdata_callout_cache *callout;
521 dbdata_ratelimit *ratelimit;
522 dbdata_ratelimit_unique *rate_unique;
526 uschar name[MESSAGE_ID_LENGTH + 1];
529 /* Keep a copy of the key separate, as in some DBM's the pointer is into data
530 which might change. */
532 if (Ustrlen(key) > sizeof(keybuffer) - 1)
534 printf("**** Overlong key encountered: %s\n", key);
537 Ustrcpy(keybuffer, key);
538 value = dbfn_read_with_length(dbm, keybuffer, &length);
541 fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
542 "was not found in the file - something is wrong!\n",
546 /* Note: don't use print_time more than once in one statement, since
547 it uses a single buffer. */
552 retry = (dbdata_retry *)value;
553 printf(" %s %d %d %s\n%s ", keybuffer, retry->basic_errno,
554 retry->more_errno, retry->text,
555 print_time(retry->first_failed));
556 printf("%s ", print_time(retry->last_try));
557 printf("%s %s\n", print_time(retry->next_try),
558 (retry->expired)? "*" : "");
562 wait = (dbdata_wait *)value;
563 printf("%s ", keybuffer);
565 name[MESSAGE_ID_LENGTH] = 0;
567 if (wait->count > WAIT_NAME_MAX)
570 "**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
571 CS keybuffer, wait->count, wait->count, WAIT_NAME_MAX);
572 wait->count = WAIT_NAME_MAX;
573 yield = count_bad = 1;
575 for (i = 1; i <= wait->count; i++)
577 Ustrncpy(name, t, MESSAGE_ID_LENGTH);
578 if (count_bad && name[0] == 0) break;
579 if (Ustrlen(name) != MESSAGE_ID_LENGTH ||
580 Ustrspn(name, "0123456789"
581 "abcdefghijklmnopqrstuvwxyz"
582 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
586 "**** Data for %s corrupted: bad character in message id\n",
588 for (j = 0; j < MESSAGE_ID_LENGTH; j++)
589 fprintf(stderr, "%02x ", name[j]);
590 fprintf(stderr, "\n");
595 t += MESSAGE_ID_LENGTH;
601 printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
606 callout = (dbdata_callout_cache *)value;
608 /* New-style address record */
610 if (length == sizeof(dbdata_callout_cache_address))
612 printf("%s %s callout=%s\n",
613 print_time(((dbdata_generic *)value)->time_stamp),
615 print_cache(callout->result));
618 /* New-style domain record */
620 else if (length == sizeof(dbdata_callout_cache))
622 printf("%s %s callout=%s postmaster=%s",
623 print_time(((dbdata_generic *)value)->time_stamp),
625 print_cache(callout->result),
626 print_cache(callout->postmaster_result));
627 if (callout->postmaster_result != ccache_unknown)
628 printf(" (%s)", print_time(callout->postmaster_stamp));
629 printf(" random=%s", print_cache(callout->random_result));
630 if (callout->random_result != ccache_unknown)
631 printf(" (%s)", print_time(callout->random_stamp));
635 /* Old-style domain record, without separate timestamps. This code can
636 eventually be thrown away, say in 5 years' time (it's now Feb 2003). */
640 printf("%s %s callout=%s postmaster=%s random=%s\n",
641 print_time(((dbdata_generic *)value)->time_stamp),
643 print_cache(callout->result),
644 print_cache(callout->postmaster_result),
645 print_cache(callout->random_result));
651 if (Ustrstr(key, "/unique/") != NULL && length >= sizeof(*rate_unique))
653 ratelimit = (dbdata_ratelimit *)value;
654 rate_unique = (dbdata_ratelimit_unique *)value;
655 printf("%s.%06d rate: %10.3f epoch: %s size: %u key: %s\n",
656 print_time(ratelimit->time_stamp),
657 ratelimit->time_usec, ratelimit->rate,
658 print_time(rate_unique->bloom_epoch), rate_unique->bloom_size,
663 ratelimit = (dbdata_ratelimit *)value;
664 printf("%s.%06d rate: %10.3f key: %s\n",
665 print_time(ratelimit->time_stamp),
666 ratelimit->time_usec, ratelimit->rate,
673 key = dbfn_scan(dbm, FALSE, &cursor);
680 #endif /* EXIM_DUMPDB */
686 /*************************************************
687 * The exim_fixdb main program *
688 *************************************************/
690 /* In order not to hold the database lock any longer than is necessary, each
691 operation on the database uses a separate open/close call. This is expensive,
692 but then using this utility is not expected to be very common. Its main use is
693 to provide a way of patching up hints databases in order to run tests.
698 This causes the data from the given record to be displayed, or "not found"
699 to be output. Note that in the retry database, destination names are
700 preceded by R: or T: for router or transport retry info.
703 This causes the given record to be deleted or "not found" to be output.
705 (3) <record name> <field number> <value>
706 This sets the given value into the given field, identified by a number
707 which is output by the display command. Not all types of record can
711 This exits from exim_fixdb.
713 If the record name is omitted from (2) or (3), the previously used record name
717 int main(int argc, char **cargv)
720 uschar **argv = USS cargv;
723 void *reset_point = store_get(0);
725 name[0] = 0; /* No name set */
727 /* Sort out the database type, verify what we are working on and then process
730 dbdata_type = check_args(argc, argv, US"fixdb", US"");
731 printf("Modifying Exim hints database %s/db/%s\n", argv[1], argv[2]);
740 dbdata_callout_cache *callout;
741 dbdata_ratelimit *ratelimit;
742 dbdata_ratelimit_unique *rate_unique;
745 uschar field[256], value[256];
747 store_reset(reset_point);
750 if (Ufgets(buffer, 256, stdin) == NULL) break;
752 buffer[Ustrlen(buffer)-1] = 0;
753 field[0] = value[0] = 0;
755 /* If the buffer contains just one digit, or just consists of "d", use the
756 previous name for an update. */
758 if ((isdigit((uschar)buffer[0]) && (buffer[1] == ' ' || buffer[1] == '\0'))
759 || Ustrcmp(buffer, "d") == 0)
763 printf("No previous record name is set\n");
766 (void)sscanf(CS buffer, "%s %s", field, value);
771 (void)sscanf(CS buffer, "%s %s %s", name, field, value);
774 /* Handle an update request */
779 spool_directory = argv[1];
780 dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE);
781 if (dbm == NULL) continue;
783 if (Ustrcmp(field, "d") == 0)
785 if (value[0] != 0) printf("unexpected value after \"d\"\n");
786 else printf("%s\n", (dbfn_delete(dbm, name) < 0)?
787 "not found" : "deleted");
792 else if (isdigit((uschar)field[0]))
794 int fieldno = Uatoi(field);
797 printf("value missing\n");
803 record = dbfn_read_with_length(dbm, name, &oldlength);
804 if (record == NULL) printf("not found\n"); else
807 int length = 0; /* Stops compiler warning */
812 retry = (dbdata_retry *)record;
813 length = sizeof(dbdata_retry) + Ustrlen(retry->text);
818 retry->basic_errno = Uatoi(value);
822 retry->more_errno = Uatoi(value);
826 if ((tt = read_time(value)) > 0) retry->first_failed = tt;
827 else printf("bad time value\n");
831 if ((tt = read_time(value)) > 0) retry->last_try = tt;
832 else printf("bad time value\n");
836 if ((tt = read_time(value)) > 0) retry->next_try = tt;
837 else printf("bad time value\n");
841 if (Ustrcmp(value, "yes") == 0) retry->expired = TRUE;
842 else if (Ustrcmp(value, "no") == 0) retry->expired = FALSE;
843 else printf("\"yes\" or \"no\" expected=n");
847 printf("unknown field number\n");
854 printf("Can't change contents of wait database record\n");
858 printf("Can't change contents of misc database record\n");
862 callout = (dbdata_callout_cache *)record;
863 length = sizeof(dbdata_callout_cache);
867 callout->result = Uatoi(value);
871 callout->postmaster_result = Uatoi(value);
875 callout->random_result = Uatoi(value);
879 printf("unknown field number\n");
886 ratelimit = (dbdata_ratelimit *)record;
890 if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt;
891 else printf("bad time value\n");
895 ratelimit->time_usec = Uatoi(value);
899 ratelimit->rate = Ustrtod(value, NULL);
903 if (Ustrstr(name, "/unique/") != NULL
904 && oldlength >= sizeof(dbdata_ratelimit_unique))
906 rate_unique = (dbdata_ratelimit_unique *)record;
907 if ((tt = read_time(value)) > 0) rate_unique->bloom_epoch = tt;
908 else printf("bad time value\n");
911 /* else fall through */
915 if (Ustrstr(name, "/unique/") != NULL
916 && oldlength >= sizeof(dbdata_ratelimit_unique))
920 unsigned n, hash, hinc;
924 md5_end(&md5info, value, Ustrlen(value), md5sum);
925 hash = md5sum[0] << 0 | md5sum[1] << 8
926 | md5sum[2] << 16 | md5sum[3] << 24;
927 hinc = md5sum[4] << 0 | md5sum[5] << 8
928 | md5sum[6] << 16 | md5sum[7] << 24;
929 rate_unique = (dbdata_ratelimit_unique *)record;
931 for (n = 0; n < 8; n++, hash += hinc)
933 int bit = 1 << (hash % 8);
934 int byte = (hash / 8) % rate_unique->bloom_size;
935 if ((rate_unique->bloom[byte] & bit) == 0)
938 if (fieldno == 5) rate_unique->bloom[byte] |= bit;
942 seen ? "seen" : fieldno == 5 ? "added" : "unseen", value);
945 /* else fall through */
948 printf("unknown field number\n");
955 dbfn_write(dbm, name, record, oldlength);
962 printf("field number or d expected\n");
967 if (!verify) continue;
970 /* The "name" q causes an exit */
972 else if (Ustrcmp(name, "q") == 0) return 0;
974 /* Handle a read request, or verify after an update. */
976 spool_directory = argv[1];
977 dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE);
978 if (dbm == NULL) continue;
980 record = dbfn_read_with_length(dbm, name, &oldlength);
983 printf("record %s not found\n", name);
989 printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp));
993 retry = (dbdata_retry *)record;
994 printf("0 error number: %d %s\n", retry->basic_errno, retry->text);
995 printf("1 extra data: %d\n", retry->more_errno);
996 printf("2 first failed: %s\n", print_time(retry->first_failed));
997 printf("3 last try: %s\n", print_time(retry->last_try));
998 printf("4 next try: %s\n", print_time(retry->next_try));
999 printf("5 expired: %s\n", (retry->expired)? "yes" : "no");
1003 wait = (dbdata_wait *)record;
1005 printf("Sequence: %d\n", wait->sequence);
1006 if (wait->count > WAIT_NAME_MAX)
1008 printf("**** Data corrupted: count=%d=0x%x max=%d ****\n", wait->count,
1009 wait->count, WAIT_NAME_MAX);
1010 wait->count = WAIT_NAME_MAX;
1013 for (i = 1; i <= wait->count; i++)
1015 Ustrncpy(value, t, MESSAGE_ID_LENGTH);
1016 value[MESSAGE_ID_LENGTH] = 0;
1017 if (count_bad && value[0] == 0) break;
1018 if (Ustrlen(value) != MESSAGE_ID_LENGTH ||
1019 Ustrspn(value, "0123456789"
1020 "abcdefghijklmnopqrstuvwxyz"
1021 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
1024 printf("\n**** Data corrupted: bad character in message id ****\n");
1025 for (j = 0; j < MESSAGE_ID_LENGTH; j++)
1026 printf("%02x ", value[j]);
1030 printf("%s ", value);
1031 t += MESSAGE_ID_LENGTH;
1040 callout = (dbdata_callout_cache *)record;
1041 printf("0 callout: %s (%d)\n", print_cache(callout->result),
1043 if (oldlength > sizeof(dbdata_callout_cache_address))
1045 printf("1 postmaster: %s (%d)\n", print_cache(callout->postmaster_result),
1046 callout->postmaster_result);
1047 printf("2 random: %s (%d)\n", print_cache(callout->random_result),
1048 callout->random_result);
1052 case type_ratelimit:
1053 ratelimit = (dbdata_ratelimit *)record;
1054 printf("0 time stamp: %s\n", print_time(ratelimit->time_stamp));
1055 printf("1 fract. time: .%06d\n", ratelimit->time_usec);
1056 printf("2 sender rate: % .3f\n", ratelimit->rate);
1057 if (Ustrstr(name, "/unique/") != NULL
1058 && oldlength >= sizeof(dbdata_ratelimit_unique))
1060 rate_unique = (dbdata_ratelimit_unique *)record;
1061 printf("3 filter epoch: %s\n", print_time(rate_unique->bloom_epoch));
1062 printf("4 test filter membership\n");
1063 printf("5 add element to filter\n");
1069 /* The database is closed after each request */
1078 #endif /* EXIM_FIXDB */
1083 /*************************************************
1084 * The exim_tidydb main program *
1085 *************************************************/
1088 /* Utility program to tidy the contents of an exim database file. There is one
1091 -t <time> expiry time for old records - default 30 days
1093 For backwards compatibility, an -f option is recognized and ignored. (It used
1094 to request a "full" tidy. This version always does the whole job.) */
1097 typedef struct key_item {
1098 struct key_item *next;
1103 int main(int argc, char **cargv)
1105 struct stat statbuf;
1106 int maxkeep = 30 * 24 * 60 * 60;
1107 int dbdata_type, i, oldest, path_len;
1108 key_item *keychain = NULL;
1112 EXIM_CURSOR *cursor;
1113 uschar **argv = USS cargv;
1117 /* Scan the options */
1119 for (i = 1; i < argc; i++)
1121 if (argv[i][0] != '-') break;
1122 if (Ustrcmp(argv[i], "-f") == 0) continue;
1123 if (Ustrcmp(argv[i], "-t") == 0)
1131 if (!isdigit(*s)) usage(US"tidydb", US" [-t <time>]");
1132 (void)sscanf(CS s, "%d%n", &value, &count);
1136 case 'w': value *= 7;
1137 case 'd': value *= 24;
1138 case 'h': value *= 60;
1139 case 'm': value *= 60;
1142 default: usage(US"tidydb", US" [-t <time>]");
1147 else usage(US"tidydb", US" [-t <time>]");
1150 /* Adjust argument values and process arguments */
1155 dbdata_type = check_args(argc, argv, US"tidydb", US" [-t <time>]");
1157 /* Compute the oldest keep time, verify what we are doing, and open the
1160 oldest = time(NULL) - maxkeep;
1161 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
1163 spool_directory = argv[1];
1164 dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE);
1165 if (dbm == NULL) exit(1);
1167 /* Prepare for building file names */
1169 sprintf(CS buffer, "%s/input/", argv[1]);
1170 path_len = Ustrlen(buffer);
1173 /* It appears, by experiment, that it is a bad idea to make changes
1174 to the file while scanning it. Pity the man page doesn't warn you about that.
1175 Therefore, we scan and build a list of all the keys. Then we use that to
1176 read the records and possibly update them. */
1178 key = dbfn_scan(dbm, TRUE, &cursor);
1181 key_item *k = store_get(sizeof(key_item) + Ustrlen(key));
1184 Ustrcpy(k->key, key);
1185 key = dbfn_scan(dbm, FALSE, &cursor);
1188 /* Now scan the collected keys and operate on the records, resetting
1189 the store each time round. */
1191 reset_point = store_get(0);
1193 while (keychain != NULL)
1195 dbdata_generic *value;
1197 store_reset(reset_point);
1198 key = keychain->key;
1199 keychain = keychain->next;
1200 value = dbfn_read_with_length(dbm, key, NULL);
1202 /* A continuation record may have been deleted or renamed already, so
1203 non-existence is not serious. */
1205 if (value == NULL) continue;
1207 /* Delete if too old */
1209 if (value->time_stamp < oldest)
1211 printf("deleted %s (too old)\n", key);
1212 dbfn_delete(dbm, key);
1216 /* Do database-specific tidying for wait databases, and message-
1217 specific tidying for the retry database. */
1219 if (dbdata_type == type_wait)
1221 dbdata_wait *wait = (dbdata_wait *)value;
1222 BOOL update = FALSE;
1224 /* Leave corrupt records alone */
1226 if (wait->count > WAIT_NAME_MAX)
1228 printf("**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
1229 key, wait->count, wait->count, WAIT_NAME_MAX);
1233 /* Loop for renamed continuation records. For each message id,
1234 check to see if the message exists, and if not, remove its entry
1235 from the record. Because of the possibility of split input directories,
1236 we must look in both possible places for a -D file. */
1241 int length = wait->count * MESSAGE_ID_LENGTH;
1243 for (offset = length - MESSAGE_ID_LENGTH;
1244 offset >= 0; offset -= MESSAGE_ID_LENGTH)
1246 Ustrncpy(buffer+path_len, wait->text + offset, MESSAGE_ID_LENGTH);
1247 sprintf(CS(buffer+path_len + MESSAGE_ID_LENGTH), "-D");
1249 if (Ustat(buffer, &statbuf) != 0)
1251 buffer[path_len] = wait->text[offset+5];
1252 buffer[path_len+1] = '/';
1253 Ustrncpy(buffer+path_len+2, wait->text + offset, MESSAGE_ID_LENGTH);
1254 sprintf(CS(buffer+path_len+2 + MESSAGE_ID_LENGTH), "-D");
1256 if (Ustat(buffer, &statbuf) != 0)
1258 int left = length - offset - MESSAGE_ID_LENGTH;
1259 if (left > 0) Ustrncpy(wait->text + offset,
1260 wait->text + offset + MESSAGE_ID_LENGTH, left);
1262 length -= MESSAGE_ID_LENGTH;
1268 /* If record is empty and the main record, either delete it or rename
1269 the next continuation, repeating if that is also empty. */
1271 if (wait->count == 0 && Ustrchr(key, ':') == NULL)
1273 while (wait->count == 0 && wait->sequence > 0)
1276 dbdata_generic *newvalue;
1277 sprintf(CS newkey, "%s:%d", key, wait->sequence - 1);
1278 newvalue = dbfn_read_with_length(dbm, newkey, NULL);
1279 if (newvalue != NULL)
1282 wait = (dbdata_wait *)newvalue;
1283 dbfn_delete(dbm, newkey);
1284 printf("renamed %s\n", newkey);
1287 else wait->sequence--;
1290 /* If we have ended up with an empty main record, delete it
1291 and break the loop. Otherwise the new record will be scanned. */
1293 if (wait->count == 0 && wait->sequence == 0)
1295 dbfn_delete(dbm, key);
1296 printf("deleted %s (empty)\n", key);
1302 /* If not an empty main record, break the loop */
1307 /* Re-write the record if required */
1311 printf("updated %s\n", key);
1312 dbfn_write(dbm, key, wait, sizeof(dbdata_wait) +
1313 wait->count * MESSAGE_ID_LENGTH);
1317 /* If a retry record's key ends with a message-id, check that that message
1318 still exists; if not, remove this record. */
1320 else if (dbdata_type == type_retry)
1323 int len = Ustrlen(key);
1325 if (len < MESSAGE_ID_LENGTH + 1) continue;
1326 id = key + len - MESSAGE_ID_LENGTH - 1;
1327 if (*id++ != ':') continue;
1329 for (i = 0; i < MESSAGE_ID_LENGTH; i++)
1331 if (i == 6 || i == 13)
1332 { if (id[i] != '-') break; }
1334 { if (!isalnum(id[i])) break; }
1336 if (i < MESSAGE_ID_LENGTH) continue;
1338 Ustrncpy(buffer + path_len, id, MESSAGE_ID_LENGTH);
1339 sprintf(CS(buffer + path_len + MESSAGE_ID_LENGTH), "-D");
1341 if (Ustat(buffer, &statbuf) != 0)
1343 sprintf(CS(buffer + path_len), "%c/%s-D", id[5], id);
1344 if (Ustat(buffer, &statbuf) != 0)
1346 dbfn_delete(dbm, key);
1347 printf("deleted %s (no message)\n", key);
1354 printf("Tidying complete\n");
1358 #endif /* EXIM_TIDYDB */
1360 /* End of exim_dbutil.c */