1/*	$NetBSD: postscreen_tests.c,v 1.4 2022/10/08 16:12:48 christos Exp $	*/
2
3/*++
4/* NAME
5/*	postscreen_tests 3
6/* SUMMARY
7/*	postscreen tests timestamp/flag bulk support
8/* SYNOPSIS
9/*	#include <postscreen.h>
10/*
11/*	void	PSC_INIT_TESTS(state)
12/*	PSC_STATE *state;
13/*
14/*	void	psc_new_tests(state)
15/*	PSC_STATE *state;
16/*
17/*	void	psc_parse_tests(state, stamp_text, time_value)
18/*	PSC_STATE *state;
19/*	const char *stamp_text;
20/*	time_t time_value;
21/*
22/*	void	psc_todo_tests(state, time_value)
23/*	PSC_STATE *state;
24/*	const char *stamp_text;
25/*	time_t time_value;
26/*
27/*	char	*psc_print_tests(buffer, state)
28/*	VSTRING	*buffer;
29/*	PSC_STATE *state;
30/*
31/*	char	*psc_print_grey_key(buffer, client, helo, sender, rcpt)
32/*	VSTRING	*buffer;
33/*	const char *client;
34/*	const char *helo;
35/*	const char *sender;
36/*	const char *rcpt;
37/*
38/*	const char *psc_test_name(tindx)
39/*	int	tindx;
40/* DESCRIPTION
41/*	The functions in this module overwrite the per-test expiration
42/*	time stamps and all flags bits.  Some functions are implemented
43/*	as unsafe macros, meaning they evaluate one or more arguments
44/*	multiple times.
45/*
46/*	PSC_INIT_TESTS() is an unsafe macro that sets the per-test
47/*	expiration time stamps to PSC_TIME_STAMP_INVALID, and that
48/*	zeroes all the flags bits. These values are not meant to
49/*	be stored into the postscreen(8) cache.
50/*
51/*	PSC_INIT_TEST_FLAGS_ONLY() zeroes all the flag bits.  It
52/*	should be used when the time stamps are already initialized.
53/*
54/*	psc_new_tests() sets all test expiration time stamps to
55/*	PSC_TIME_STAMP_NEW, and invokes psc_todo_tests().
56/*
57/*	psc_parse_tests() parses a cache file record and invokes
58/*	psc_todo_tests().
59/*
60/*	psc_todo_tests() overwrites all per-session flag bits, and
61/*	populates the flags based on test expiration time stamp
62/*	information.  Tests are considered "expired" when they
63/*	would be expired at the specified time value. Only enabled
64/*	tests are flagged as "expired"; the object is flagged as
65/*	"new" if some enabled tests have "new" time stamps.
66/*
67/*	psc_print_tests() creates a cache file record for the
68/*	specified flags and per-test expiration time stamps.
69/*	This may modify the time stamps for disabled tests.
70/*
71/*	psc_print_grey_key() prints a greylist lookup key.
72/*
73/*	psc_test_name() returns the name for the specified text
74/*	index.
75/* LICENSE
76/* .ad
77/* .fi
78/*	The Secure Mailer license must be distributed with this software.
79/* AUTHOR(S)
80/*	Wietse Venema
81/*	IBM T.J. Watson Research
82/*	P.O. Box 704
83/*	Yorktown Heights, NY 10598, USA
84/*
85/*	Wietse Venema
86/*	Google, Inc.
87/*	111 8th Avenue
88/*	New York, NY 10011, USA
89/*--*/
90
91/* System library. */
92
93#include <sys_defs.h>
94#include <stdio.h>			/* sscanf */
95
96/* Utility library. */
97
98#include <msg.h>
99#include <name_code.h>
100#include <sane_strtol.h>
101
102/* Global library. */
103
104#include <mail_params.h>
105
106/* Application-specific. */
107
108#include <postscreen.h>
109
110 /*
111  * Kludge to detect if some test is enabled.
112  */
113#define PSC_PREGR_TEST_ENABLE()	(*var_psc_pregr_banner != 0)
114#define PSC_DNSBL_TEST_ENABLE()	(*var_psc_dnsbl_sites != 0)
115
116 /*
117  * Format of a persistent cache entry (which is almost but not quite the
118  * same as the in-memory representation).
119  *
120  * Each cache entry has one time stamp for each test.
121  *
122  * - A time stamp of PSC_TIME_STAMP_INVALID must never appear in the cache. It
123  * is reserved for in-memory objects that are still being initialized.
124  *
125  * - A time stamp of PSC_TIME_STAMP_NEW indicates that the test never passed.
126  * Postscreen will log the client with "pass new" when it passes the final
127  * test.
128  *
129  * - A time stamp of PSC_TIME_STAMP_DISABLED indicates that the test never
130  * passed, and that the test was disabled when the cache entry was written.
131  *
132  * - Otherwise, the test was passed, and the time stamp indicates when that
133  * test result expires.
134  *
135  * A cache entry is expired when the time stamps of all passed tests are
136  * expired.
137  */
138
139/* psc_new_tests - initialize new test results from scratch */
140
141void    psc_new_tests(PSC_STATE *state)
142{
143    time_t *expire_time = state->client_info->expire_time;
144
145    /*
146     * Give all tests a PSC_TIME_STAMP_NEW time stamp, so that we can later
147     * recognize cache entries that haven't passed all enabled tests. When we
148     * write a cache entry to the database, any new-but-disabled tests will
149     * get a PSC_TIME_STAMP_DISABLED time stamp.
150     */
151    expire_time[PSC_TINDX_PREGR] = PSC_TIME_STAMP_NEW;
152    expire_time[PSC_TINDX_DNSBL] = PSC_TIME_STAMP_NEW;
153    expire_time[PSC_TINDX_PIPEL] = PSC_TIME_STAMP_NEW;
154    expire_time[PSC_TINDX_NSMTP] = PSC_TIME_STAMP_NEW;
155    expire_time[PSC_TINDX_BARLF] = PSC_TIME_STAMP_NEW;
156
157    /*
158     * Determine what tests need to be completed.
159     */
160    psc_todo_tests(state, PSC_TIME_STAMP_NEW + 1);
161}
162
163/* psc_parse_tests - parse test results from cache */
164
165void    psc_parse_tests(PSC_STATE *state,
166			        const char *stamp_str,
167			        time_t time_value)
168{
169    const char *start = stamp_str;
170    char   *cp;
171    time_t *time_stamps = state->client_info->expire_time;
172    time_t *sp;
173
174    /*
175     * Parse the cache entry, and allow for older postscreen versions that
176     * implemented fewer tests. We pretend that the newer tests were disabled
177     * at the time that the cache entry was written.
178     */
179    for (sp = time_stamps; sp < time_stamps + PSC_TINDX_COUNT; sp++) {
180	*sp = sane_strtoul(start, &cp, 10);
181	if (*start == 0 || (*cp != '\0' && *cp != ';') || errno == ERANGE)
182	    *sp = PSC_TIME_STAMP_DISABLED;
183	if (msg_verbose)
184	    msg_info("%s -> %lu", start, (unsigned long) *sp);
185	if (*cp == ';')
186	    start = cp + 1;
187	else
188	    start = cp;
189    }
190
191    /*
192     * Determine what tests need to be completed.
193     */
194    psc_todo_tests(state, time_value);
195}
196
197/* psc_todo_tests - determine what tests to perform */
198
199void    psc_todo_tests(PSC_STATE *state, time_t time_value)
200{
201    time_t *expire_time = state->client_info->expire_time;
202    time_t *sp;
203
204    /*
205     * Reset all per-session flags.
206     */
207    state->flags = 0;
208
209    /*
210     * Flag the tests as "new" when the cache entry has fields for all
211     * enabled tests, but the remote SMTP client has not yet passed all those
212     * tests.
213     */
214    for (sp = expire_time; sp < expire_time + PSC_TINDX_COUNT; sp++) {
215	if (*sp == PSC_TIME_STAMP_NEW)
216	    state->flags |= PSC_STATE_FLAG_NEW;
217    }
218
219    /*
220     * Don't flag disabled tests as "todo", because there would be no way to
221     * make those bits go away.
222     */
223    if (PSC_PREGR_TEST_ENABLE() && time_value > expire_time[PSC_TINDX_PREGR])
224	state->flags |= PSC_STATE_FLAG_PREGR_TODO;
225    if (PSC_DNSBL_TEST_ENABLE() && time_value > expire_time[PSC_TINDX_DNSBL])
226	state->flags |= PSC_STATE_FLAG_DNSBL_TODO;
227    if (var_psc_pipel_enable && time_value > expire_time[PSC_TINDX_PIPEL])
228	state->flags |= PSC_STATE_FLAG_PIPEL_TODO;
229    if (var_psc_nsmtp_enable && time_value > expire_time[PSC_TINDX_NSMTP])
230	state->flags |= PSC_STATE_FLAG_NSMTP_TODO;
231    if (var_psc_barlf_enable && time_value > expire_time[PSC_TINDX_BARLF])
232	state->flags |= PSC_STATE_FLAG_BARLF_TODO;
233
234    /*
235     * If any test has expired, proactively refresh tests that will expire
236     * soon. This can increase the occurrence of client-visible delays, but
237     * avoids questions about why a client can pass some test and then fail
238     * within seconds. The proactive refresh time is really a surrogate for
239     * the user's curiosity level, and therefore hard to choose optimally.
240     */
241#ifdef VAR_PSC_REFRESH_TIME
242    if ((state->flags & PSC_STATE_MASK_ANY_TODO) != 0
243	&& var_psc_refresh_time > 0) {
244	time_t  refresh_time = time_value + var_psc_refresh_time;
245
246	if (PSC_PREGR_TEST_ENABLE() && refresh_time > expire_time[PSC_TINDX_PREGR])
247	    state->flags |= PSC_STATE_FLAG_PREGR_TODO;
248	if (PSC_DNSBL_TEST_ENABLE() && refresh_time > expire_time[PSC_TINDX_DNSBL])
249	    state->flags |= PSC_STATE_FLAG_DNSBL_TODO;
250	if (var_psc_pipel_enable && refresh_time > expire_time[PSC_TINDX_PIPEL])
251	    state->flags |= PSC_STATE_FLAG_PIPEL_TODO;
252	if (var_psc_nsmtp_enable && refresh_time > expire_time[PSC_TINDX_NSMTP])
253	    state->flags |= PSC_STATE_FLAG_NSMTP_TODO;
254	if (var_psc_barlf_enable && refresh_time > expire_time[PSC_TINDX_BARLF])
255	    state->flags |= PSC_STATE_FLAG_BARLF_TODO;
256    }
257#endif
258
259    /*
260     * Gratuitously make postscreen logging more useful by turning on all
261     * enabled pre-handshake tests when any pre-handshake test is turned on.
262     *
263     * XXX Don't enable PREGREET gratuitously before the test expires. With a
264     * short TTL for DNSBL allowlisting, turning on PREGREET would force a
265     * full postscreen_greet_wait too frequently.
266     */
267#if 0
268    if (state->flags & PSC_STATE_MASK_EARLY_TODO) {
269	if (PSC_PREGR_TEST_ENABLE())
270	    state->flags |= PSC_STATE_FLAG_PREGR_TODO;
271	if (PSC_DNSBL_TEST_ENABLE())
272	    state->flags |= PSC_STATE_FLAG_DNSBL_TODO;
273    }
274#endif
275}
276
277/* psc_print_tests - print postscreen cache record */
278
279char   *psc_print_tests(VSTRING *buf, PSC_STATE *state)
280{
281    const char *myname = "psc_print_tests";
282    time_t *expire_time = state->client_info->expire_time;
283
284    /*
285     * Sanity check.
286     */
287    if ((state->flags & PSC_STATE_MASK_ANY_UPDATE) == 0)
288	msg_panic("%s: attempt to save a no-update record", myname);
289
290    /*
291     * Give disabled tests a dummy time stamp so that we don't log a client
292     * with "pass new" when some disabled test becomes enabled at some later
293     * time.
294     */
295    if (PSC_PREGR_TEST_ENABLE() == 0 && expire_time[PSC_TINDX_PREGR] == PSC_TIME_STAMP_NEW)
296	expire_time[PSC_TINDX_PREGR] = PSC_TIME_STAMP_DISABLED;
297    if (PSC_DNSBL_TEST_ENABLE() == 0 && expire_time[PSC_TINDX_DNSBL] == PSC_TIME_STAMP_NEW)
298	expire_time[PSC_TINDX_DNSBL] = PSC_TIME_STAMP_DISABLED;
299    if (var_psc_pipel_enable == 0 && expire_time[PSC_TINDX_PIPEL] == PSC_TIME_STAMP_NEW)
300	expire_time[PSC_TINDX_PIPEL] = PSC_TIME_STAMP_DISABLED;
301    if (var_psc_nsmtp_enable == 0 && expire_time[PSC_TINDX_NSMTP] == PSC_TIME_STAMP_NEW)
302	expire_time[PSC_TINDX_NSMTP] = PSC_TIME_STAMP_DISABLED;
303    if (var_psc_barlf_enable == 0 && expire_time[PSC_TINDX_BARLF] == PSC_TIME_STAMP_NEW)
304	expire_time[PSC_TINDX_BARLF] = PSC_TIME_STAMP_DISABLED;
305
306    vstring_sprintf(buf, "%lu;%lu;%lu;%lu;%lu",
307		    (unsigned long) expire_time[PSC_TINDX_PREGR],
308		    (unsigned long) expire_time[PSC_TINDX_DNSBL],
309		    (unsigned long) expire_time[PSC_TINDX_PIPEL],
310		    (unsigned long) expire_time[PSC_TINDX_NSMTP],
311		    (unsigned long) expire_time[PSC_TINDX_BARLF]);
312    return (STR(buf));
313}
314
315/* psc_print_grey_key - print postscreen cache record */
316
317char   *psc_print_grey_key(VSTRING *buf, const char *client,
318			           const char *helo, const char *sender,
319			           const char *rcpt)
320{
321    return (STR(vstring_sprintf(buf, "%s/%s/%s/%s",
322				client, helo, sender, rcpt)));
323}
324
325/* psc_test_name - map test index to symbolic name */
326
327const char *psc_test_name(int tindx)
328{
329    const char *myname = "psc_test_name";
330    const NAME_CODE test_name_map[] = {
331	PSC_TNAME_PREGR, PSC_TINDX_PREGR,
332	PSC_TNAME_DNSBL, PSC_TINDX_DNSBL,
333	PSC_TNAME_PIPEL, PSC_TINDX_PIPEL,
334	PSC_TNAME_NSMTP, PSC_TINDX_NSMTP,
335	PSC_TNAME_BARLF, PSC_TINDX_BARLF,
336	0, -1,
337    };
338    const char *result;
339
340    if ((result = str_name_code(test_name_map, tindx)) == 0)
341	msg_panic("%s: bad index %d", myname, tindx);
342    return (result);
343}
344