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