summaryrefslogtreecommitdiffstats
path: root/src/postscreen/postscreen_tests.c
blob: 5e186221ba1a03ef12ffcfebfad11c5105a67c4d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
/*++
/* NAME
/*	postscreen_tests 3
/* SUMMARY
/*	postscreen tests timestamp/flag bulk support
/* SYNOPSIS
/*	#include <postscreen.h>
/*
/*	void	PSC_INIT_TESTS(state)
/*	PSC_STATE *state;
/*
/*	void	psc_new_tests(state)
/*	PSC_STATE *state;
/*
/*	void	psc_parse_tests(state, stamp_text, time_value)
/*	PSC_STATE *state;
/*	const char *stamp_text;
/*	time_t time_value;
/*
/*	void	psc_todo_tests(state, time_value)
/*	PSC_STATE *state;
/*	const char *stamp_text;
/*	time_t time_value;
/*
/*	char	*psc_print_tests(buffer, state)
/*	VSTRING	*buffer;
/*	PSC_STATE *state;
/*
/*	char	*psc_print_grey_key(buffer, client, helo, sender, rcpt)
/*	VSTRING	*buffer;
/*	const char *client;
/*	const char *helo;
/*	const char *sender;
/*	const char *rcpt;
/*
/*	const char *psc_test_name(tindx)
/*	int	tindx;
/* DESCRIPTION
/*	The functions in this module overwrite the per-test expiration
/*	time stamps and all flags bits.  Some functions are implemented
/*	as unsafe macros, meaning they evaluate one or more arguments
/*	multiple times.
/*
/*	PSC_INIT_TESTS() is an unsafe macro that sets the per-test
/*	expiration time stamps to PSC_TIME_STAMP_INVALID, and that
/*	zeroes all the flags bits. These values are not meant to
/*	be stored into the postscreen(8) cache.
/*
/*	PSC_INIT_TEST_FLAGS_ONLY() zeroes all the flag bits.  It
/*	should be used when the time stamps are already initialized.
/*
/*	psc_new_tests() sets all test expiration time stamps to
/*	PSC_TIME_STAMP_NEW, and invokes psc_todo_tests().
/*
/*	psc_parse_tests() parses a cache file record and invokes
/*	psc_todo_tests().
/*
/*	psc_todo_tests() overwrites all per-session flag bits, and
/*	populates the flags based on test expiration time stamp
/*	information.  Tests are considered "expired" when they
/*	would be expired at the specified time value. Only enabled
/*	tests are flagged as "expired"; the object is flagged as
/*	"new" if some enabled tests have "new" time stamps.
/*
/*	psc_print_tests() creates a cache file record for the
/*	specified flags and per-test expiration time stamps.
/*	This may modify the time stamps for disabled tests.
/*
/*	psc_print_grey_key() prints a greylist lookup key.
/*
/*	psc_test_name() returns the name for the specified text
/*	index.
/* LICENSE
/* .ad
/* .fi
/*	The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/*	Wietse Venema
/*	IBM T.J. Watson Research
/*	P.O. Box 704
/*	Yorktown Heights, NY 10598, USA
/*
/*	Wietse Venema
/*	Google, Inc.
/*	111 8th Avenue
/*	New York, NY 10011, USA
/*--*/

/* System library. */

#include <sys_defs.h>
#include <stdio.h>			/* sscanf */

/* Utility library. */

#include <msg.h>
#include <name_code.h>
#include <sane_strtol.h>

/* Global library. */

#include <mail_params.h>

/* Application-specific. */

#include <postscreen.h>

 /*
  * Kludge to detect if some test is enabled.
  */
#define PSC_PREGR_TEST_ENABLE()	(*var_psc_pregr_banner != 0)
#define PSC_DNSBL_TEST_ENABLE()	(*var_psc_dnsbl_sites != 0)

 /*
  * Format of a persistent cache entry (which is almost but not quite the
  * same as the in-memory representation).
  * 
  * Each cache entry has one time stamp for each test.
  * 
  * - A time stamp of PSC_TIME_STAMP_INVALID must never appear in the cache. It
  * is reserved for in-memory objects that are still being initialized.
  * 
  * - A time stamp of PSC_TIME_STAMP_NEW indicates that the test never passed.
  * Postscreen will log the client with "pass new" when it passes the final
  * test.
  * 
  * - A time stamp of PSC_TIME_STAMP_DISABLED indicates that the test never
  * passed, and that the test was disabled when the cache entry was written.
  * 
  * - Otherwise, the test was passed, and the time stamp indicates when that
  * test result expires.
  * 
  * A cache entry is expired when the time stamps of all passed tests are
  * expired.
  */

/* psc_new_tests - initialize new test results from scratch */

void    psc_new_tests(PSC_STATE *state)
{
    time_t *expire_time = state->client_info->expire_time;

    /*
     * Give all tests a PSC_TIME_STAMP_NEW time stamp, so that we can later
     * recognize cache entries that haven't passed all enabled tests. When we
     * write a cache entry to the database, any new-but-disabled tests will
     * get a PSC_TIME_STAMP_DISABLED time stamp.
     */
    expire_time[PSC_TINDX_PREGR] = PSC_TIME_STAMP_NEW;
    expire_time[PSC_TINDX_DNSBL] = PSC_TIME_STAMP_NEW;
    expire_time[PSC_TINDX_PIPEL] = PSC_TIME_STAMP_NEW;
    expire_time[PSC_TINDX_NSMTP] = PSC_TIME_STAMP_NEW;
    expire_time[PSC_TINDX_BARLF] = PSC_TIME_STAMP_NEW;

    /*
     * Determine what tests need to be completed.
     */
    psc_todo_tests(state, PSC_TIME_STAMP_NEW + 1);
}

/* psc_parse_tests - parse test results from cache */

void    psc_parse_tests(PSC_STATE *state,
			        const char *stamp_str,
			        time_t time_value)
{
    const char *start = stamp_str;
    char   *cp;
    time_t *time_stamps = state->client_info->expire_time;
    time_t *sp;

    /*
     * Parse the cache entry, and allow for older postscreen versions that
     * implemented fewer tests. We pretend that the newer tests were disabled
     * at the time that the cache entry was written.
     */
    for (sp = time_stamps; sp < time_stamps + PSC_TINDX_COUNT; sp++) {
	*sp = sane_strtoul(start, &cp, 10);
	if (*start == 0 || (*cp != '\0' && *cp != ';') || errno == ERANGE)
	    *sp = PSC_TIME_STAMP_DISABLED;
	if (msg_verbose)
	    msg_info("%s -> %lu", start, (unsigned long) *sp);
	if (*cp == ';')
	    start = cp + 1;
	else
	    start = cp;
    }

    /*
     * Determine what tests need to be completed.
     */
    psc_todo_tests(state, time_value);
}

/* psc_todo_tests - determine what tests to perform */

void    psc_todo_tests(PSC_STATE *state, time_t time_value)
{
    time_t *expire_time = state->client_info->expire_time;
    time_t *sp;

    /*
     * Reset all per-session flags.
     */
    state->flags = 0;

    /*
     * Flag the tests as "new" when the cache entry has fields for all
     * enabled tests, but the remote SMTP client has not yet passed all those
     * tests.
     */
    for (sp = expire_time; sp < expire_time + PSC_TINDX_COUNT; sp++) {
	if (*sp == PSC_TIME_STAMP_NEW)
	    state->flags |= PSC_STATE_FLAG_NEW;
    }

    /*
     * Don't flag disabled tests as "todo", because there would be no way to
     * make those bits go away.
     */
    if (PSC_PREGR_TEST_ENABLE() && time_value > expire_time[PSC_TINDX_PREGR])
	state->flags |= PSC_STATE_FLAG_PREGR_TODO;
    if (PSC_DNSBL_TEST_ENABLE() && time_value > expire_time[PSC_TINDX_DNSBL])
	state->flags |= PSC_STATE_FLAG_DNSBL_TODO;
    if (var_psc_pipel_enable && time_value > expire_time[PSC_TINDX_PIPEL])
	state->flags |= PSC_STATE_FLAG_PIPEL_TODO;
    if (var_psc_nsmtp_enable && time_value > expire_time[PSC_TINDX_NSMTP])
	state->flags |= PSC_STATE_FLAG_NSMTP_TODO;
    if (var_psc_barlf_enable && time_value > expire_time[PSC_TINDX_BARLF])
	state->flags |= PSC_STATE_FLAG_BARLF_TODO;

    /*
     * If any test has expired, proactively refresh tests that will expire
     * soon. This can increase the occurrence of client-visible delays, but
     * avoids questions about why a client can pass some test and then fail
     * within seconds. The proactive refresh time is really a surrogate for
     * the user's curiosity level, and therefore hard to choose optimally.
     */
#ifdef VAR_PSC_REFRESH_TIME
    if ((state->flags & PSC_STATE_MASK_ANY_TODO) != 0
	&& var_psc_refresh_time > 0) {
	time_t  refresh_time = time_value + var_psc_refresh_time;

	if (PSC_PREGR_TEST_ENABLE() && refresh_time > expire_time[PSC_TINDX_PREGR])
	    state->flags |= PSC_STATE_FLAG_PREGR_TODO;
	if (PSC_DNSBL_TEST_ENABLE() && refresh_time > expire_time[PSC_TINDX_DNSBL])
	    state->flags |= PSC_STATE_FLAG_DNSBL_TODO;
	if (var_psc_pipel_enable && refresh_time > expire_time[PSC_TINDX_PIPEL])
	    state->flags |= PSC_STATE_FLAG_PIPEL_TODO;
	if (var_psc_nsmtp_enable && refresh_time > expire_time[PSC_TINDX_NSMTP])
	    state->flags |= PSC_STATE_FLAG_NSMTP_TODO;
	if (var_psc_barlf_enable && refresh_time > expire_time[PSC_TINDX_BARLF])
	    state->flags |= PSC_STATE_FLAG_BARLF_TODO;
    }
#endif

    /*
     * Gratuitously make postscreen logging more useful by turning on all
     * enabled pre-handshake tests when any pre-handshake test is turned on.
     * 
     * XXX Don't enable PREGREET gratuitously before the test expires. With a
     * short TTL for DNSBL allowlisting, turning on PREGREET would force a
     * full postscreen_greet_wait too frequently.
     */
#if 0
    if (state->flags & PSC_STATE_MASK_EARLY_TODO) {
	if (PSC_PREGR_TEST_ENABLE())
	    state->flags |= PSC_STATE_FLAG_PREGR_TODO;
	if (PSC_DNSBL_TEST_ENABLE())
	    state->flags |= PSC_STATE_FLAG_DNSBL_TODO;
    }
#endif
}

/* psc_print_tests - print postscreen cache record */

char   *psc_print_tests(VSTRING *buf, PSC_STATE *state)
{
    const char *myname = "psc_print_tests";
    time_t *expire_time = state->client_info->expire_time;

    /*
     * Sanity check.
     */
    if ((state->flags & PSC_STATE_MASK_ANY_UPDATE) == 0)
	msg_panic("%s: attempt to save a no-update record", myname);

    /*
     * Give disabled tests a dummy time stamp so that we don't log a client
     * with "pass new" when some disabled test becomes enabled at some later
     * time.
     */
    if (PSC_PREGR_TEST_ENABLE() == 0 && expire_time[PSC_TINDX_PREGR] == PSC_TIME_STAMP_NEW)
	expire_time[PSC_TINDX_PREGR] = PSC_TIME_STAMP_DISABLED;
    if (PSC_DNSBL_TEST_ENABLE() == 0 && expire_time[PSC_TINDX_DNSBL] == PSC_TIME_STAMP_NEW)
	expire_time[PSC_TINDX_DNSBL] = PSC_TIME_STAMP_DISABLED;
    if (var_psc_pipel_enable == 0 && expire_time[PSC_TINDX_PIPEL] == PSC_TIME_STAMP_NEW)
	expire_time[PSC_TINDX_PIPEL] = PSC_TIME_STAMP_DISABLED;
    if (var_psc_nsmtp_enable == 0 && expire_time[PSC_TINDX_NSMTP] == PSC_TIME_STAMP_NEW)
	expire_time[PSC_TINDX_NSMTP] = PSC_TIME_STAMP_DISABLED;
    if (var_psc_barlf_enable == 0 && expire_time[PSC_TINDX_BARLF] == PSC_TIME_STAMP_NEW)
	expire_time[PSC_TINDX_BARLF] = PSC_TIME_STAMP_DISABLED;

    vstring_sprintf(buf, "%lu;%lu;%lu;%lu;%lu",
		    (unsigned long) expire_time[PSC_TINDX_PREGR],
		    (unsigned long) expire_time[PSC_TINDX_DNSBL],
		    (unsigned long) expire_time[PSC_TINDX_PIPEL],
		    (unsigned long) expire_time[PSC_TINDX_NSMTP],
		    (unsigned long) expire_time[PSC_TINDX_BARLF]);
    return (STR(buf));
}

/* psc_print_grey_key - print postscreen cache record */

char   *psc_print_grey_key(VSTRING *buf, const char *client,
			           const char *helo, const char *sender,
			           const char *rcpt)
{
    return (STR(vstring_sprintf(buf, "%s/%s/%s/%s",
				client, helo, sender, rcpt)));
}

/* psc_test_name - map test index to symbolic name */

const char *psc_test_name(int tindx)
{
    const char *myname = "psc_test_name";
    const NAME_CODE test_name_map[] = {
	PSC_TNAME_PREGR, PSC_TINDX_PREGR,
	PSC_TNAME_DNSBL, PSC_TINDX_DNSBL,
	PSC_TNAME_PIPEL, PSC_TINDX_PIPEL,
	PSC_TNAME_NSMTP, PSC_TINDX_NSMTP,
	PSC_TNAME_BARLF, PSC_TINDX_BARLF,
	0, -1,
    };
    const char *result;

    if ((result = str_name_code(test_name_map, tindx)) == 0)
	msg_panic("%s: bad index %d", myname, tindx);
    return (result);
}