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