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