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_WRONLY ? "write" : "read",
289 buffer,
290 errno == ETIMEDOUT ? "timed out" : strerror(errno));
291 (void)close(dbblock->lockfd);
292 return NULL;
293 }
294
295 /* At this point we have an opened and locked separate lock file, that is,
296 exclusive access to the database, so we can go ahead and open it. */
297
298 sprintf(CS buffer, "%s/db/%s", spool_directory, name);
299 EXIM_DBOPEN(buffer, flags, 0, &(dbblock->dbptr));
300
301 if (dbblock->dbptr == NULL)
302 {
303 printf("** Failed to open DBM file %s for %s:\n %s%s\n", buffer,
304 read_only? "reading" : "writing", strerror(errno),
305 #ifdef USE_DB
306 " (or Berkeley DB error while opening)"
307 #else
308 ""
309 #endif
310 );
311 (void)close(dbblock->lockfd);
312 return NULL;
313 }
314
315 return dbblock;
316 }
317
318
319
320
321 /*************************************************
322 * Unlock and close a database file *
323 *************************************************/
324
325 /* Closing a file automatically unlocks it, so after closing the database, just
326 close the lock file.
327
328 Argument: a pointer to an open database block
329 Returns: nothing
330 */
331
332 void
333 dbfn_close(open_db *dbblock)
334 {
335 EXIM_DBCLOSE(dbblock->dbptr);
336 (void)close(dbblock->lockfd);
337 }
338
339
340
341
342 /*************************************************
343 * Read from database file *
344 *************************************************/
345
346 /* Passing back the pointer unchanged is useless, because there is no guarantee
347 of alignment. Since all the records used by Exim need to be properly aligned to
348 pick out the timestamps, etc., do the copying centrally here.
349
350 Arguments:
351 dbblock a pointer to an open database block
352 key the key of the record to be read
353 length where to put the length (or NULL if length not wanted)
354
355 Returns: a pointer to the retrieved record, or
356 NULL if the record is not found
357 */
358
359 void *
360 dbfn_read_with_length(open_db *dbblock, const uschar *key, int *length)
361 {
362 void *yield;
363 EXIM_DATUM key_datum, result_datum;
364 int klen = Ustrlen(key) + 1;
365 uschar * key_copy = store_get(klen);
366
367 memcpy(key_copy, key, klen);
368
369 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
370 EXIM_DATUM_INIT(result_datum); /* to be cleared before use. */
371 EXIM_DATUM_DATA(key_datum) = CS key_copy;
372 EXIM_DATUM_SIZE(key_datum) = klen;
373
374 if (!EXIM_DBGET(dbblock->dbptr, key_datum, result_datum)) return NULL;
375
376 yield = store_get(EXIM_DATUM_SIZE(result_datum));
377 memcpy(yield, EXIM_DATUM_DATA(result_datum), EXIM_DATUM_SIZE(result_datum));
378 if (length != NULL) *length = EXIM_DATUM_SIZE(result_datum);
379
380 EXIM_DATUM_FREE(result_datum); /* Some DBM libs require freeing */
381 return yield;
382 }
383
384
385
386 #if defined(EXIM_TIDYDB) || defined(EXIM_FIXDB)
387
388 /*************************************************
389 * Write to database file *
390 *************************************************/
391
392 /*
393 Arguments:
394 dbblock a pointer to an open database block
395 key the key of the record to be written
396 ptr a pointer to the record to be written
397 length the length of the record to be written
398
399 Returns: the yield of the underlying dbm or db "write" function. If this
400 is dbm, the value is zero for OK.
401 */
402
403 int
404 dbfn_write(open_db *dbblock, const uschar *key, void *ptr, int length)
405 {
406 EXIM_DATUM key_datum, value_datum;
407 dbdata_generic *gptr = (dbdata_generic *)ptr;
408 int klen = Ustrlen(key) + 1;
409 uschar * key_copy = store_get(klen);
410
411 memcpy(key_copy, key, klen);
412 gptr->time_stamp = time(NULL);
413
414 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
415 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
416 EXIM_DATUM_DATA(key_datum) = CS key_copy;
417 EXIM_DATUM_SIZE(key_datum) = klen;
418 EXIM_DATUM_DATA(value_datum) = CS ptr;
419 EXIM_DATUM_SIZE(value_datum) = length;
420 return EXIM_DBPUT(dbblock->dbptr, key_datum, value_datum);
421 }
422
423
424
425 /*************************************************
426 * Delete record from database file *
427 *************************************************/
428
429 /*
430 Arguments:
431 dbblock a pointer to an open database block
432 key the key of the record to be deleted
433
434 Returns: the yield of the underlying dbm or db "delete" function.
435 */
436
437 int
438 dbfn_delete(open_db *dbblock, const uschar *key)
439 {
440 int klen = Ustrlen(key) + 1;
441 uschar * key_copy = store_get(klen);
442
443 memcpy(key_copy, key, klen);
444 EXIM_DATUM key_datum;
445 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require clearing */
446 EXIM_DATUM_DATA(key_datum) = CS key_copy;
447 EXIM_DATUM_SIZE(key_datum) = klen;
448 return EXIM_DBDEL(dbblock->dbptr, key_datum);
449 }
450
451 #endif /* EXIM_TIDYDB || EXIM_FIXDB */
452
453
454
455 #if defined(EXIM_DUMPDB) || defined(EXIM_TIDYDB)
456 /*************************************************
457 * Scan the keys of a database file *
458 *************************************************/
459
460 /*
461 Arguments:
462 dbblock a pointer to an open database block
463 start TRUE if starting a new scan
464 FALSE if continuing with the current scan
465 cursor a pointer to a pointer to a cursor anchor, for those dbm libraries
466 that use the notion of a cursor
467
468 Returns: the next record from the file, or
469 NULL if there are no more
470 */
471
472 uschar *
473 dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
474 {
475 EXIM_DATUM key_datum, value_datum;
476 uschar *yield;
477 value_datum = value_datum; /* dummy; not all db libraries use this */
478
479 /* Some dbm require an initialization */
480
481 if (start) EXIM_DBCREATE_CURSOR(dbblock->dbptr, cursor);
482
483 EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
484 EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
485
486 yield = (EXIM_DBSCAN(dbblock->dbptr, key_datum, value_datum, start, *cursor))?
487 US EXIM_DATUM_DATA(key_datum) : NULL;
488
489 /* Some dbm require a termination */
490
491 if (!yield) EXIM_DBDELETE_CURSOR(*cursor);
492 return yield;
493 }
494 #endif /* EXIM_DUMPDB || EXIM_TIDYDB */
495
496
497
498 #ifdef EXIM_DUMPDB
499 /*************************************************
500 * The exim_dumpdb main program *
501 *************************************************/
502
503 int
504 main(int argc, char **cargv)
505 {
506 int dbdata_type = 0;
507 int yield = 0;
508 open_db dbblock;
509 open_db *dbm;
510 EXIM_CURSOR *cursor;
511 uschar **argv = USS cargv;
512 uschar *key;
513 uschar keybuffer[1024];
514
515 /* Check the arguments, and open the database */
516
517 dbdata_type = check_args(argc, argv, US"dumpdb", US"");
518 spool_directory = argv[1];
519 dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE);
520 if (dbm == NULL) exit(1);
521
522 /* Scan the file, formatting the information for each entry. Note
523 that data is returned in a malloc'ed block, in order that it be
524 correctly aligned. */
525
526 key = dbfn_scan(dbm, TRUE, &cursor);
527 while (key != NULL)
528 {
529 dbdata_retry *retry;
530 dbdata_wait *wait;
531 dbdata_callout_cache *callout;
532 dbdata_ratelimit *ratelimit;
533 dbdata_ratelimit_unique *rate_unique;
534 int count_bad = 0;
535 int i, length;
536 uschar *t;
537 uschar name[MESSAGE_ID_LENGTH + 1];
538 void *value;
539
540 /* Keep a copy of the key separate, as in some DBM's the pointer is into data
541 which might change. */
542
543 if (Ustrlen(key) > sizeof(keybuffer) - 1)
544 {
545 printf("**** Overlong key encountered: %s\n", key);
546 return 1;
547 }
548 Ustrcpy(keybuffer, key);
549 value = dbfn_read_with_length(dbm, keybuffer, &length);
550
551 if (value == NULL)
552 fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
553 "was not found in the file - something is wrong!\n",
554 CS keybuffer);
555 else
556 {
557 /* Note: don't use print_time more than once in one statement, since
558 it uses a single buffer. */
559
560 switch(dbdata_type)
561 {
562 case type_retry:
563 retry = (dbdata_retry *)value;
564 printf(" %s %d %d %s\n%s ", keybuffer, retry->basic_errno,
565 retry->more_errno, retry->text,
566 print_time(retry->first_failed));
567 printf("%s ", print_time(retry->last_try));
568 printf("%s %s\n", print_time(retry->next_try),
569 (retry->expired)? "*" : "");
570 break;
571
572 case type_wait:
573 wait = (dbdata_wait *)value;
574 printf("%s ", keybuffer);
575 t = wait->text;
576 name[MESSAGE_ID_LENGTH] = 0;
577
578 if (wait->count > WAIT_NAME_MAX)
579 {
580 fprintf(stderr,
581 "**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
582 CS keybuffer, wait->count, wait->count, WAIT_NAME_MAX);
583 wait->count = WAIT_NAME_MAX;
584 yield = count_bad = 1;
585 }
586 for (i = 1; i <= wait->count; i++)
587 {
588 Ustrncpy(name, t, MESSAGE_ID_LENGTH);
589 if (count_bad && name[0] == 0) break;
590 if (Ustrlen(name) != MESSAGE_ID_LENGTH ||
591 Ustrspn(name, "0123456789"
592 "abcdefghijklmnopqrstuvwxyz"
593 "ABCDEFGHIJKLMNOPQRSTUVWXYZ-") != MESSAGE_ID_LENGTH)
594 {
595 int j;
596 fprintf(stderr,
597 "**** Data for %s corrupted: bad character in message id\n",
598 CS keybuffer);
599 for (j = 0; j < MESSAGE_ID_LENGTH; j++)
600 fprintf(stderr, "%02x ", name[j]);
601 fprintf(stderr, "\n");
602 yield = 1;
603 break;
604 }
605 printf("%s ", name);
606 t += MESSAGE_ID_LENGTH;
607 }
608 printf("\n");
609 break;
610
611 case type_misc:
612 printf("%s %s\n", print_time(((dbdata_generic *)value)->time_stamp),
613 keybuffer);
614 break;
615
616 case type_callout:
617 callout = (dbdata_callout_cache *)value;
618
619 /* New-style address record */
620
621 if (length == sizeof(dbdata_callout_cache_address))
622 {
623 printf("%s %s callout=%s\n",
624 print_time(((dbdata_generic *)value)->time_stamp),
625 keybuffer,
626 print_cache(callout->result));
627 }
628
629 /* New-style domain record */
630
631 else if (length == sizeof(dbdata_callout_cache))
632 {
633 printf("%s %s callout=%s postmaster=%s",
634 print_time(((dbdata_generic *)value)->time_stamp),
635 keybuffer,
636 print_cache(callout->result),
637 print_cache(callout->postmaster_result));
638 if (callout->postmaster_result != ccache_unknown)
639 printf(" (%s)", print_time(callout->postmaster_stamp));
640 printf(" random=%s", print_cache(callout->random_result));
641 if (callout->random_result != ccache_unknown)
642 printf(" (%s)", print_time(callout->random_stamp));
643 printf("\n");
644 }
645
646 break;
647
648 case type_ratelimit:
649 if (Ustrstr(key, "/unique/") != NULL && length >= sizeof(*rate_unique))
650 {
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,
657 keybuffer);
658 }
659 else
660 {
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,
665 keybuffer);
666 }
667 break;
668 }
669 store_reset(value);
670 }
671 key = dbfn_scan(dbm, FALSE, &cursor);
672 }
673
674 dbfn_close(dbm);
675 return yield;
676 }
677
678 #endif /* EXIM_DUMPDB */
679
680
681
682
683 #ifdef EXIM_FIXDB
684 /*************************************************
685 * The exim_fixdb main program *
686 *************************************************/
687
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.
692
693 Syntax of commands:
694
695 (1) <record name>
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.
699
700 (2) <record name> d
701 This causes the given record to be deleted or "not found" to be output.
702
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
706 be changed.
707
708 (4) q
709 This exits from exim_fixdb.
710
711 If the record name is omitted from (2) or (3), the previously used record name
712 is re-used. */
713
714
715 int main(int argc, char **cargv)
716 {
717 int dbdata_type;
718 uschar **argv = USS cargv;
719 uschar buffer[256];
720 uschar name[256];
721 void *reset_point = store_get(0);
722
723 name[0] = 0; /* No name set */
724
725 /* Sort out the database type, verify what we are working on and then process
726 user requests */
727
728 dbdata_type = check_args(argc, argv, US"fixdb", US"");
729 printf("Modifying Exim hints database %s/db/%s\n", argv[1], argv[2]);
730
731 for(;;)
732 {
733 open_db dbblock;
734 open_db *dbm;
735 void *record;
736 dbdata_retry *retry;
737 dbdata_wait *wait;
738 dbdata_callout_cache *callout;
739 dbdata_ratelimit *ratelimit;
740 dbdata_ratelimit_unique *rate_unique;
741 int i, oldlength;
742 uschar *t;
743 uschar field[256], value[256];
744
745 store_reset(reset_point);
746
747 printf("> ");
748 if (Ufgets(buffer, 256, stdin) == NULL) break;
749
750 buffer[Ustrlen(buffer)-1] = 0;
751 field[0] = value[0] = 0;
752
753 /* If the buffer contains just one digit, or just consists of "d", use the
754 previous name for an update. */
755
756 if ((isdigit((uschar)buffer[0]) && (buffer[1] == ' ' || buffer[1] == '\0'))
757 || Ustrcmp(buffer, "d") == 0)
758 {
759 if (name[0] == 0)
760 {
761 printf("No previous record name is set\n");
762 continue;
763 }
764 (void)sscanf(CS buffer, "%s %s", field, value);
765 }
766 else
767 {
768 name[0] = 0;
769 (void)sscanf(CS buffer, "%s %s %s", name, field, value);
770 }
771
772 /* Handle an update request */
773
774 if (field[0] != 0)
775 {
776 int verify = 1;
777 spool_directory = argv[1];
778 dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE);
779 if (dbm == NULL) continue;
780
781 if (Ustrcmp(field, "d") == 0)
782 {
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");
786 dbfn_close(dbm);
787 continue;
788 }
789
790 else if (isdigit((uschar)field[0]))
791 {
792 int fieldno = Uatoi(field);
793 if (value[0] == 0)
794 {
795 printf("value missing\n");
796 dbfn_close(dbm);
797 continue;
798 }
799 else
800 {
801 record = dbfn_read_with_length(dbm, name, &oldlength);
802 if (record == NULL) printf("not found\n"); else
803 {
804 time_t tt;
805 int length = 0; /* Stops compiler warning */
806
807 switch(dbdata_type)
808 {
809 case type_retry:
810 retry = (dbdata_retry *)record;
811 length = sizeof(dbdata_retry) + Ustrlen(retry->text);
812
813 switch(fieldno)
814 {
815 case 0:
816 retry->basic_errno = Uatoi(value);
817 break;
818
819 case 1:
820 retry->more_errno = Uatoi(value);
821 break;
822
823 case 2:
824 if ((tt = read_time(value)) > 0) retry->first_failed = tt;
825 else printf("bad time value\n");
826 break;
827
828 case 3:
829 if ((tt = read_time(value)) > 0) retry->last_try = tt;
830 else printf("bad time value\n");
831 break;
832
833 case 4:
834 if ((tt = read_time(value)) > 0) retry->next_try = tt;
835 else printf("bad time value\n");
836 break;
837
838 case 5:
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");
842 break;
843
844 default:
845 printf("unknown field number\n");
846 verify = 0;
847 break;
848 }
849 break;
850
851 case type_wait:
852 printf("Can't change contents of wait database record\n");
853 break;
854
855 case type_misc:
856 printf("Can't change contents of misc database record\n");
857 break;
858
859 case type_callout:
860 callout = (dbdata_callout_cache *)record;
861 length = sizeof(dbdata_callout_cache);
862 switch(fieldno)
863 {
864 case 0:
865 callout->result = Uatoi(value);
866 break;
867
868 case 1:
869 callout->postmaster_result = Uatoi(value);
870 break;
871
872 case 2:
873 callout->random_result = Uatoi(value);
874 break;
875
876 default:
877 printf("unknown field number\n");
878 verify = 0;
879 break;
880 }
881 break;
882
883 case type_ratelimit:
884 ratelimit = (dbdata_ratelimit *)record;
885 switch(fieldno)
886 {
887 case 0:
888 if ((tt = read_time(value)) > 0) ratelimit->time_stamp = tt;
889 else printf("bad time value\n");
890 break;
891
892 case 1:
893 ratelimit->time_usec = Uatoi(value);
894 break;
895
896 case 2:
897 ratelimit->rate = Ustrtod(value, NULL);
898 break;
899
900 case 3:
901 if (Ustrstr(name, "/unique/") != NULL
902 && oldlength >= sizeof(dbdata_ratelimit_unique))
903 {
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");
907 break;
908 }
909 /* else fall through */
910
911 case 4:
912 case 5:
913 if (Ustrstr(name, "/unique/") != NULL
914 && oldlength >= sizeof(dbdata_ratelimit_unique))
915 {
916 /* see acl.c */
917 BOOL seen;
918 unsigned n, hash, hinc;
919 uschar md5sum[16];
920 md5 md5info;
921 md5_start(&md5info);
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;
928 seen = TRUE;
929 for (n = 0; n < 8; n++, hash += hinc)
930 {
931 int bit = 1 << (hash % 8);
932 int byte = (hash / 8) % rate_unique->bloom_size;
933 if ((rate_unique->bloom[byte] & bit) == 0)
934 {
935 seen = FALSE;
936 if (fieldno == 5) rate_unique->bloom[byte] |= bit;
937 }
938 }
939 printf("%s %s\n",
940 seen ? "seen" : fieldno == 5 ? "added" : "unseen", value);
941 break;
942 }
943 /* else fall through */
944
945 default:
946 printf("unknown field number\n");
947 verify = 0;
948 break;
949 }
950 break;
951 }
952
953 dbfn_write(dbm, name, record, oldlength);
954 }
955 }
956 }
957
958 else
959 {
960 printf("field number or d expected\n");
961 verify = 0;
962 }
963
964 dbfn_close(dbm);
965 if (!verify) continue;
966 }
967
968 /* The "name" q causes an exit */
969
970 else if (Ustrcmp(name, "q") == 0) return 0;
971
972 /* Handle a read request, or verify after an update. */
973
974 spool_directory = argv[1];
975 dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE);
976 if (dbm == NULL) continue;
977
978 record = dbfn_read_with_length(dbm, name, &oldlength);
979 if (record == NULL)
980 {
981 printf("record %s not found\n", name);
982 name[0] = 0;
983 }
984 else
985 {
986 int count_bad = 0;
987 printf("%s\n", CS print_time(((dbdata_generic *)record)->time_stamp));
988 switch(dbdata_type)
989 {
990 case type_retry:
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");
998 break;
999
1000 case type_wait:
1001 wait = (dbdata_wait *)record;
1002 t = wait->text;
1003 printf("Sequence: %d\n", wait->sequence);
1004 if (wait->count > WAIT_NAME_MAX)
1005 {
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;
1009 count_bad = 1;
1010 }
1011 for (i = 1; i <= wait->count; i++)
1012 {
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)
1020 {
1021 int j;
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]);
1025 printf("\n");
1026 break;
1027 }
1028 printf("%s ", value);
1029 t += MESSAGE_ID_LENGTH;
1030 }
1031 printf("\n");
1032 break;
1033
1034 case type_misc:
1035 break;
1036
1037 case type_callout:
1038 callout = (dbdata_callout_cache *)record;
1039 printf("0 callout: %s (%d)\n", print_cache(callout->result),
1040 callout->result);
1041 if (oldlength > sizeof(dbdata_callout_cache_address))
1042 {
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);
1047 }
1048 break;
1049
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))
1057 {
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");
1062 }
1063 break;
1064 }
1065 }
1066
1067 /* The database is closed after each request */
1068
1069 dbfn_close(dbm);
1070 }
1071
1072 printf("\n");
1073 return 0;
1074 }
1075
1076 #endif /* EXIM_FIXDB */
1077
1078
1079
1080 #ifdef EXIM_TIDYDB
1081 /*************************************************
1082 * The exim_tidydb main program *
1083 *************************************************/
1084
1085
1086 /* Utility program to tidy the contents of an exim database file. There is one
1087 option:
1088
1089 -t <time> expiry time for old records - default 30 days
1090
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.) */
1093
1094
1095 typedef struct key_item {
1096 struct key_item *next;
1097 uschar key[1];
1098 } key_item;
1099
1100
1101 int main(int argc, char **cargv)
1102 {
1103 struct stat statbuf;
1104 int maxkeep = 30 * 24 * 60 * 60;
1105 int dbdata_type, i, oldest, path_len;
1106 key_item *keychain = NULL;
1107 void *reset_point;
1108 open_db dbblock;
1109 open_db *dbm;
1110 EXIM_CURSOR *cursor;
1111 uschar **argv = USS cargv;
1112 uschar buffer[256];
1113 uschar *key;
1114
1115 /* Scan the options */
1116
1117 for (i = 1; i < argc; i++)
1118 {
1119 if (argv[i][0] != '-') break;
1120 if (Ustrcmp(argv[i], "-f") == 0) continue;
1121 if (Ustrcmp(argv[i], "-t") == 0)
1122 {
1123 uschar *s;
1124 s = argv[++i];
1125 maxkeep = 0;
1126 while (*s != 0)
1127 {
1128 int value, count;
1129 if (!isdigit(*s)) usage(US"tidydb", US" [-t <time>]");
1130 (void)sscanf(CS s, "%d%n", &value, &count);
1131 s += count;
1132 switch (*s)
1133 {
1134 case 'w': value *= 7;
1135 case 'd': value *= 24;
1136 case 'h': value *= 60;
1137 case 'm': value *= 60;
1138 case 's': s++;
1139 break;
1140 default: usage(US"tidydb", US" [-t <time>]");
1141 }
1142 maxkeep += value;
1143 }
1144 }
1145 else usage(US"tidydb", US" [-t <time>]");
1146 }
1147
1148 /* Adjust argument values and process arguments */
1149
1150 argc -= --i;
1151 argv += i;
1152
1153 dbdata_type = check_args(argc, argv, US"tidydb", US" [-t <time>]");
1154
1155 /* Compute the oldest keep time, verify what we are doing, and open the
1156 database */
1157
1158 oldest = time(NULL) - maxkeep;
1159 printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
1160
1161 spool_directory = argv[1];
1162 dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE);
1163 if (dbm == NULL) exit(1);
1164
1165 /* Prepare for building file names */
1166
1167 sprintf(CS buffer, "%s/input/", argv[1]);
1168 path_len = Ustrlen(buffer);
1169
1170
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. */
1175
1176 key = dbfn_scan(dbm, TRUE, &cursor);
1177 while (key != NULL)
1178 {
1179 key_item *k = store_get(sizeof(key_item) + Ustrlen(key));
1180 k->next = keychain;
1181 keychain = k;
1182 Ustrcpy(k->key, key);
1183 key = dbfn_scan(dbm, FALSE, &cursor);
1184 }
1185
1186 /* Now scan the collected keys and operate on the records, resetting
1187 the store each time round. */
1188
1189 reset_point = store_get(0);
1190
1191 while (keychain != NULL)
1192 {
1193 dbdata_generic *value;
1194
1195 store_reset(reset_point);
1196 key = keychain->key;
1197 keychain = keychain->next;
1198 value = dbfn_read_with_length(dbm, key, NULL);
1199
1200 /* A continuation record may have been deleted or renamed already, so
1201 non-existence is not serious. */
1202
1203 if (value == NULL) continue;
1204
1205 /* Delete if too old */
1206
1207 if (value->time_stamp < oldest)
1208 {
1209 printf("deleted %s (too old)\n", key);
1210 dbfn_delete(dbm, key);
1211 continue;
1212 }
1213
1214 /* Do database-specific tidying for wait databases, and message-
1215 specific tidying for the retry database. */
1216
1217 if (dbdata_type == type_wait)
1218 {
1219 dbdata_wait *wait = (dbdata_wait *)value;
1220 BOOL update = FALSE;
1221
1222 /* Leave corrupt records alone */
1223
1224 if (wait->count > WAIT_NAME_MAX)
1225 {
1226 printf("**** Data for %s corrupted\n count=%d=0x%x max=%d\n",
1227 key, wait->count, wait->count, WAIT_NAME_MAX);
1228 continue;
1229 }
1230
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. */
1235
1236 for (;;)
1237 {
1238 int offset;
1239 int length = wait->count * MESSAGE_ID_LENGTH;
1240
1241 for (offset = length - MESSAGE_ID_LENGTH;
1242 offset >= 0; offset -= MESSAGE_ID_LENGTH)
1243 {
1244 Ustrncpy(buffer+path_len, wait->text + offset, MESSAGE_ID_LENGTH);
1245 sprintf(CS(buffer+path_len + MESSAGE_ID_LENGTH), "-D");
1246
1247 if (Ustat(buffer, &statbuf) != 0)
1248 {
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");
1253
1254 if (Ustat(buffer, &statbuf) != 0)
1255 {
1256 int left = length - offset - MESSAGE_ID_LENGTH;
1257 if (left > 0) Ustrncpy(wait->text + offset,
1258 wait->text + offset + MESSAGE_ID_LENGTH, left);
1259 wait->count--;
1260 length -= MESSAGE_ID_LENGTH;
1261 update = TRUE;
1262 }
1263 }
1264 }
1265
1266 /* If record is empty and the main record, either delete it or rename
1267 the next continuation, repeating if that is also empty. */
1268
1269 if (wait->count == 0 && Ustrchr(key, ':') == NULL)
1270 {
1271 while (wait->count == 0 && wait->sequence > 0)
1272 {
1273 uschar newkey[256];
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)
1278 {
1279 value = newvalue;
1280 wait = (dbdata_wait *)newvalue;
1281 dbfn_delete(dbm, newkey);
1282 printf("renamed %s\n", newkey);
1283 update = TRUE;
1284 }
1285 else wait->sequence--;
1286 }
1287
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. */
1290
1291 if (wait->count == 0 && wait->sequence == 0)
1292 {
1293 dbfn_delete(dbm, key);
1294 printf("deleted %s (empty)\n", key);
1295 update = FALSE;
1296 break;
1297 }
1298 }
1299
1300 /* If not an empty main record, break the loop */
1301
1302 else break;
1303 }
1304
1305 /* Re-write the record if required */
1306
1307 if (update)
1308 {
1309 printf("updated %s\n", key);
1310 dbfn_write(dbm, key, wait, sizeof(dbdata_wait) +
1311 wait->count * MESSAGE_ID_LENGTH);
1312 }
1313 }
1314
1315 /* If a retry record's key ends with a message-id, check that that message
1316 still exists; if not, remove this record. */
1317
1318 else if (dbdata_type == type_retry)
1319 {
1320 uschar *id;
1321 int len = Ustrlen(key);
1322
1323 if (len < MESSAGE_ID_LENGTH + 1) continue;
1324 id = key + len - MESSAGE_ID_LENGTH - 1;
1325 if (*id++ != ':') continue;
1326
1327 for (i = 0; i < MESSAGE_ID_LENGTH; i++)
1328 {
1329 if (i == 6 || i == 13)
1330 { if (id[i] != '-') break; }
1331 else
1332 { if (!isalnum(id[i])) break; }
1333 }
1334 if (i < MESSAGE_ID_LENGTH) continue;
1335
1336 Ustrncpy(buffer + path_len, id, MESSAGE_ID_LENGTH);
1337 sprintf(CS(buffer + path_len + MESSAGE_ID_LENGTH), "-D");
1338
1339 if (Ustat(buffer, &statbuf) != 0)
1340 {
1341 sprintf(CS(buffer + path_len), "%c/%s-D", id[5], id);
1342 if (Ustat(buffer, &statbuf) != 0)
1343 {
1344 dbfn_delete(dbm, key);
1345 printf("deleted %s (no message)\n", key);
1346 }
1347 }
1348 }
1349 }
1350
1351 dbfn_close(dbm);
1352 printf("Tidying complete\n");
1353 return 0;
1354 }
1355
1356 #endif /* EXIM_TIDYDB */
1357
1358 /* End of exim_dbutil.c */