1/*	$NetBSD$	*/
2
3/*++
4/* NAME
5/*	header_body_checks 3
6/* SUMMARY
7/*	header/body checks
8/* SYNOPSIS
9/*	#include <header_body_checks.h>
10/*
11/*	typedef struct {
12/*		void	(*logger) (void *context, const char *action,
13/*				const char *where, const char *line,
14/*				const char *optional_text);
15/*		void	(*prepend) (void *context, int rec_type,
16/*				const char *buf, ssize_t len, off_t offset);
17/*		char	*(*extend) (void *context, const char *command,
18/*				int cmd_len, const char *cmd_args,
19/*				const char *where, const char *line,
20/*				ssize_t line_len, off_t offset);
21/*	} HBC_CALL_BACKS;
22/*
23/*	HBC_CHECKS *hbc_header_checks_create(
24/*			header_checks_name, header_checks_value
25/*			mime_header_checks_name, mime_header_checks_value,
26/*			nested_header_checks_name, nested_header_checks_value,
27/*			call_backs)
28/*	const char *header_checks_name;
29/*	const char *header_checks_value;
30/*	const char *mime_header_checks_name;
31/*	const char *mime_header_checks_value;
32/*	const char *nested_header_checks_name;
33/*	const char *nested_header_checks_value;
34/*	HBC_CALL_BACKS *call_backs;
35/*
36/*	HBC_CHECKS *hbc_body_checks_create(
37/*			body_checks_name, body_checks_value,
38/*			call_backs)
39/*	const char *body_checks_name;
40/*	const char *body_checks_value;
41/*	HBC_CALL_BACKS *call_backs;
42/*
43/*	char	*hbc_header_checks(context, hbc, header_class, hdr_opts, header)
44/*	void	*context;
45/*	HBC_CHECKS *hbc;
46/*	int	header_class;
47/*	const HEADER_OPTS *hdr_opts;
48/*	VSTRING *header;
49/*
50/*	char	*hbc_body_checks(context, hbc, body_line, body_line_len)
51/*	void	*context;
52/*	HBC_CHECKS *hbc;
53/*	const char *body_line;
54/*	ssize_t	body_line_len;
55/*
56/*	void	hbc_header_checks_free(hbc)
57/*	HBC_CHECKS *hbc;
58/*
59/*	void	hbc_body_checks_free(hbc)
60/*	HBC_CHECKS *hbc;
61/* DESCRIPTION
62/*	This module implements header_checks and body_checks.
63/*	Actions are executed while mail is being delivered. The
64/*	following actions are recognized: INFO, WARN, REPLACE,
65/*	PREPEND, IGNORE, DUNNO, and OK. These actions are safe for
66/*	use in delivery agents.
67/*
68/*	Other actions may be supplied via the extension mechanism
69/*	described below.  For example, actions that change the
70/*	message delivery time or destination. Such actions do not
71/*	make sense in delivery agents, but they can be appropriate
72/*	in, for example, before-queue filters.
73/*
74/*	hbc_header_checks_create() creates a context for header
75/*	inspection. This function is typically called once during
76/*	program initialization.  The result is a null pointer when
77/*	all _value arguments specify zero-length strings; in this
78/*	case, hbc_header_checks() and hbc_header_checks_free() must
79/*	not be called.
80/*
81/*	hbc_header_checks() inspects the specified logical header.
82/*	The result is either the original header, HBC_CHECK_STAT_IGNORE
83/*	(meaning: discard the header) or a new header (meaning:
84/*	replace the header and destroy the new header with myfree()).
85/*
86/*	hbc_header_checks_free() returns memory to the pool.
87/*
88/*	hbc_body_checks_create(), dbhc_body_checks(), dbhc_body_free()
89/*	perform similar functions for body lines.
90/*
91/*	Arguments:
92/* .IP body_line
93/*	One line of body text.
94/* .IP body_line_len
95/*	Body line length.
96/* .IP call_backs
97/*	Table with call-back function pointers. This argument is
98/*	not copied.  Note: the description below is not necessarily
99/*	in data structure order.
100/* .RS
101/* .IP logger
102/*	Call-back function for logging an action with the action's
103/*	name in lower case, a location within a message ("header"
104/*	or "body"), the content of the header or body line that
105/*	triggered the action, and optional text or a zero-length
106/*	string. This call-back feature must be specified.
107/* .IP prepend
108/*	Call-back function for the PREPEND action. The arguments
109/*	are the same as those of mime_state(3) body output call-back
110/*	functions.  Specify a null pointer to disable this action.
111/* .IP extend
112/*	Call-back function that logs and executes other actions.
113/*	This function receives as arguments the command name and
114/*	name length, the command arguments if any, the location
115/*	within the message ("header" or "body"), the content and
116/*	length of the header or body line that triggered the action,
117/*	and the input byte offset within the current header or body
118/*	segment.  The result value is either the original line
119/*	argument, HBC_CHECKS_STAT_IGNORE (delete the line from the
120/*	input stream) or HBC_CHECKS_STAT_UNKNOWN (the command was
121/*	not recognized).  Specify a null pointer to disable this
122/*	feature.
123/* .RE
124/* .IP context
125/*	Application context for call-back functions specified with the
126/*	call_backs argument.
127/* .IP header
128/*	A logical message header. Lines within a multi-line header
129/*	are separated by a newline character.
130/* .IP "header_checks_name, mime_header_checks_name"
131/* .IP "nested_header_checks_name, body_checks_name"
132/*	The main.cf configuration parameter names for header and body
133/*	map lists.
134/* .IP "header_checks_value, mime_header_checks_value"
135/* .IP "nested_header_checks_value, body_checks_value"
136/*	The values of main.cf configuration parameters for header and body
137/*	map lists. Specify a zero-length string to disable a specific list.
138/* .IP header_class
139/*	A number in the range MIME_HDR_FIRST..MIME_HDR_LAST.
140/* .IP hbc
141/*	A handle created with hbc_header_checks_create() or
142/*	hbc_body_checks_create().
143/* .IP hdr_opts
144/*	Message header properties.
145/* SEE ALSO
146/*	msg(3) diagnostics interface
147/* DIAGNOSTICS
148/*	Fatal errors: memory allocation problem.
149/* LICENSE
150/* .ad
151/* .fi
152/*	The Secure Mailer license must be distributed with this software.
153/* AUTHOR(S)
154/*	Wietse Venema
155/*	IBM T.J. Watson Research
156/*	P.O. Box 704
157/*	Yorktown Heights, NY 10598, USA
158/*--*/
159
160/* System library. */
161
162#include <sys_defs.h>
163#include <ctype.h>
164#include <string.h>
165#ifdef STRCASECMP_IN_STRINGS_H
166#include <strings.h>
167#endif
168
169/* Utility library. */
170
171#include <msg.h>
172#include <mymalloc.h>
173
174/* Global library. */
175
176#include <mime_state.h>
177#include <rec_type.h>
178#include <is_header.h>
179#include <cleanup_user.h>
180#include <dsn_util.h>
181#include <header_body_checks.h>
182
183/* Application-specific. */
184
185 /*
186  * Something that is guaranteed to be different from a real string result
187  * from header/body_checks.
188  */
189const char hbc_checks_unknown;
190
191 /*
192  * Header checks are stored as an array of HBC_MAP_INFO structures, one
193  * structure for each header class (MIME_HDR_PRIMARY, MIME_HDR_MULTIPART, or
194  * MIME_HDR_NESTED).
195  *
196  * Body checks are stored as one single HBC_MAP_INFO structure, because we make
197  * no distinction between body segments.
198  */
199#define HBC_HEADER_INDEX(class)	((class) - MIME_HDR_FIRST)
200#define HBC_BODY_INDEX	(0)
201
202#define HBC_INIT(hbc, index, name, value) do { \
203	HBC_MAP_INFO *_mp = (hbc)->map_info + (index); \
204	if (*(value) != 0) { \
205	    _mp->map_class = (name); \
206	    _mp->maps = maps_create((name), (value), DICT_FLAG_LOCK); \
207	} else { \
208	    _mp->map_class = 0; \
209	    _mp->maps = 0; \
210	} \
211    } while (0)
212
213/* How does the action routine know where we are? */
214
215#define	HBC_CTXT_HEADER	"header"
216#define HBC_CTXT_BODY	"body"
217
218/* Silly little macros. */
219
220#define STR(x)	vstring_str(x)
221#define LEN(x)	VSTRING_LEN(x)
222
223/* hbc_action - act upon a header/body match */
224
225static char *hbc_action(void *context, HBC_CALL_BACKS *cb,
226			        const char *map_class, const char *where,
227			        const char *cmd, const char *line,
228			        ssize_t line_len, off_t offset)
229{
230    const char *cmd_args = cmd + strcspn(cmd, " \t");
231    int     cmd_len = cmd_args - cmd;
232    char   *ret;
233
234    /*
235     * XXX We don't use a hash table for action lookup. Mail rarely triggers
236     * an action, and mail that triggers multiple actions is even rarer.
237     * Setting up the hash table costs more than we would gain from using it.
238     */
239    while (*cmd_args && ISSPACE(*cmd_args))
240	cmd_args++;
241
242#define STREQUAL(x,y,l) (strncasecmp((x), (y), (l)) == 0 && (y)[l] == 0)
243
244    if (cb->extend
245	&& (ret = cb->extend(context, cmd, cmd_len, cmd_args, where, line,
246			     line_len, offset)) != HBC_CHECKS_STAT_UNKNOWN)
247	return (ret);
248
249    if (STREQUAL(cmd, "WARN", cmd_len)) {
250	cb->logger(context, "warning", where, line, cmd_args);
251	return ((char *) line);
252    }
253    if (STREQUAL(cmd, "INFO", cmd_len)) {
254	cb->logger(context, "info", where, line, cmd_args);
255	return ((char *) line);
256    }
257    if (STREQUAL(cmd, "REPLACE", cmd_len)) {
258	if (*cmd_args == 0) {
259	    msg_warn("REPLACE action without text in %s map", map_class);
260	    return ((char *) line);
261	} else if (strcmp(where, HBC_CTXT_HEADER) == 0
262		   && !is_header(cmd_args)) {
263	    msg_warn("bad REPLACE header text \"%s\" in %s map -- "
264		   "need \"headername: headervalue\"", cmd_args, map_class);
265	    return ((char *) line);
266	} else {
267	    cb->logger(context, "replace", where, line, cmd_args);
268	    return (mystrdup(cmd_args));
269	}
270    }
271    if (cb->prepend && STREQUAL(cmd, "PREPEND", cmd_len)) {
272	if (*cmd_args == 0) {
273	    msg_warn("PREPEND action without text in %s map", map_class);
274	} else if (strcmp(where, HBC_CTXT_HEADER) == 0
275		   && !is_header(cmd_args)) {
276	    msg_warn("bad PREPEND header text \"%s\" in %s map -- "
277		   "need \"headername: headervalue\"", cmd_args, map_class);
278	} else {
279	    cb->logger(context, "prepend", where, line, cmd_args);
280	    cb->prepend(context, REC_TYPE_NORM, cmd_args, strlen(cmd_args), offset);
281	}
282	return ((char *) line);
283    }
284    /* Allow and ignore optional text after the action. */
285
286    if (STREQUAL(cmd, "IGNORE", cmd_len))
287	/* XXX Not logged for compatibility with cleanup(8). */
288	return (HBC_CHECKS_STAT_IGNORE);
289
290    if (STREQUAL(cmd, "DUNNO", cmd_len)		/* preferred */
291	||STREQUAL(cmd, "OK", cmd_len))		/* compatibility */
292	return ((char *) line);
293
294    msg_warn("unsupported command in %s map: %s", map_class, cmd);
295    return ((char *) line);
296}
297
298/* hbc_header_checks - process one complete header line */
299
300char   *hbc_header_checks(void *context, HBC_CHECKS *hbc, int header_class,
301			          const HEADER_OPTS *hdr_opts,
302			          VSTRING *header, off_t offset)
303{
304    const char *myname = "hbc_header_checks";
305    const char *action;
306    HBC_MAP_INFO *mp;
307
308    if (msg_verbose)
309	msg_info("%s: '%.30s'", myname, STR(header));
310
311    /*
312     * XXX This is for compatibility with the cleanup(8) server.
313     */
314    if (hdr_opts && (hdr_opts->flags & HDR_OPT_MIME))
315	header_class = MIME_HDR_MULTIPART;
316
317    mp = hbc->map_info + HBC_HEADER_INDEX(header_class);
318
319    if (mp->maps != 0 && (action = maps_find(mp->maps, STR(header), 0)) != 0) {
320	return (hbc_action(context, hbc->call_backs,
321			   mp->map_class, HBC_CTXT_HEADER, action,
322			   STR(header), LEN(header), offset));
323    } else {
324	return (STR(header));
325    }
326}
327
328/* hbc_body_checks - inspect one body record */
329
330char   *hbc_body_checks(void *context, HBC_CHECKS *hbc, const char *line,
331			        ssize_t len, off_t offset)
332{
333    const char *myname = "hbc_body_checks";
334    const char *action;
335    HBC_MAP_INFO *mp;
336
337    if (msg_verbose)
338	msg_info("%s: '%.30s'", myname, line);
339
340    mp = hbc->map_info;
341
342    if ((action = maps_find(mp->maps, line, 0)) != 0) {
343	return (hbc_action(context, hbc->call_backs,
344			   mp->map_class, HBC_CTXT_BODY, action,
345			   line, len, offset));
346    } else {
347	return ((char *) line);
348    }
349}
350
351/* hbc_header_checks_create - create header checking context */
352
353HBC_CHECKS *hbc_header_checks_create(const char *header_checks_name,
354				             const char *header_checks_value,
355				        const char *mime_header_checks_name,
356			               const char *mime_header_checks_value,
357			              const char *nested_header_checks_name,
358			             const char *nested_header_checks_value,
359				             HBC_CALL_BACKS *call_backs)
360{
361    HBC_CHECKS *hbc;
362
363    /*
364     * Optimize for the common case.
365     */
366    if (*header_checks_value == 0 && *mime_header_checks_value == 0
367	&& *nested_header_checks_value == 0) {
368	return (0);
369    } else {
370	hbc = (HBC_CHECKS *) mymalloc(sizeof(*hbc)
371		 + (MIME_HDR_LAST - MIME_HDR_FIRST) * sizeof(HBC_MAP_INFO));
372	hbc->call_backs = call_backs;
373	HBC_INIT(hbc, HBC_HEADER_INDEX(MIME_HDR_PRIMARY),
374		 header_checks_name, header_checks_value);
375	HBC_INIT(hbc, HBC_HEADER_INDEX(MIME_HDR_MULTIPART),
376		 mime_header_checks_name, mime_header_checks_value);
377	HBC_INIT(hbc, HBC_HEADER_INDEX(MIME_HDR_NESTED),
378		 nested_header_checks_name, nested_header_checks_value);
379	return (hbc);
380    }
381}
382
383/* hbc_body_checks_create - create body checking context */
384
385HBC_CHECKS *hbc_body_checks_create(const char *body_checks_name,
386				           const char *body_checks_value,
387				           HBC_CALL_BACKS *call_backs)
388{
389    HBC_CHECKS *hbc;
390
391    /*
392     * Optimize for the common case.
393     */
394    if (*body_checks_value == 0) {
395	return (0);
396    } else {
397	hbc = (HBC_CHECKS *) mymalloc(sizeof(*hbc));
398	hbc->call_backs = call_backs;
399	HBC_INIT(hbc, HBC_BODY_INDEX, body_checks_name, body_checks_value);
400	return (hbc);
401    }
402}
403
404/* _hbc_checks_free - destroy header/body checking context */
405
406void    _hbc_checks_free(HBC_CHECKS *hbc, ssize_t len)
407{
408    HBC_MAP_INFO *mp;
409
410    for (mp = hbc->map_info; mp < hbc->map_info + len; mp++)
411	if (mp->maps)
412	    maps_free(mp->maps);
413    myfree((char *) hbc);
414}
415
416 /*
417  * Test program. Specify the four maps on the command line, and feed a
418  * MIME-formatted message on stdin.
419  */
420
421#ifdef TEST
422
423#include <stdlib.h>
424#include <stringops.h>
425#include <vstream.h>
426#include <msg_vstream.h>
427#include <rec_streamlf.h>
428
429typedef struct {
430    HBC_CHECKS *header_checks;
431    HBC_CHECKS *body_checks;
432    HBC_CALL_BACKS *call_backs;
433    VSTREAM *fp;
434    VSTRING *buf;
435    const char *queueid;
436    int     recno;
437} HBC_TEST_CONTEXT;
438
439/*#define REC_LEN	40*/
440#define REC_LEN	1024
441
442/* log_cb - log action with context */
443
444static void log_cb(void *context, const char *action, const char *where,
445		           const char *content, const char *text)
446{
447    const HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context;
448
449    if (*text) {
450	msg_info("%s: %s: %s %.200s: %s",
451		 dp->queueid, action, where, content, text);
452    } else {
453	msg_info("%s: %s: %s %.200s",
454		 dp->queueid, action, where, content);
455    }
456}
457
458/* out_cb - output call-back */
459
460static void out_cb(void *context, int rec_type, const char *buf,
461		           ssize_t len, off_t offset)
462{
463    const HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context;
464
465    vstream_fwrite(dp->fp, buf, len);
466    VSTREAM_PUTC('\n', dp->fp);
467    vstream_fflush(dp->fp);
468}
469
470/* head_out - MIME_STATE header call-back */
471
472static void head_out(void *context, int header_class,
473		             const HEADER_OPTS *header_info,
474		             VSTRING *buf, off_t offset)
475{
476    HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context;
477    char   *out;
478
479    if (dp->header_checks == 0
480	|| (out = hbc_header_checks(context, dp->header_checks, header_class,
481				    header_info, buf, offset)) == STR(buf)) {
482	vstring_sprintf(dp->buf, "%d %s %ld\t|%s",
483			dp->recno,
484			header_class == MIME_HDR_PRIMARY ? "MAIN" :
485			header_class == MIME_HDR_MULTIPART ? "MULT" :
486			header_class == MIME_HDR_NESTED ? "NEST" :
487			"ERROR", (long) offset, STR(buf));
488	out_cb(dp, REC_TYPE_NORM, STR(dp->buf), LEN(dp->buf), offset);
489    } else if (out != 0) {
490	vstring_sprintf(dp->buf, "%d %s %ld\t|%s",
491			dp->recno,
492			header_class == MIME_HDR_PRIMARY ? "MAIN" :
493			header_class == MIME_HDR_MULTIPART ? "MULT" :
494			header_class == MIME_HDR_NESTED ? "NEST" :
495			"ERROR", (long) offset, out);
496	out_cb(dp, REC_TYPE_NORM, STR(dp->buf), LEN(dp->buf), offset);
497	myfree(out);
498    }
499    dp->recno += 1;
500}
501
502/* header_end - MIME_STATE end-of-header call-back */
503
504static void head_end(void *context)
505{
506    HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context;
507
508    out_cb(dp, 0, "HEADER END", sizeof("HEADER END") - 1, 0);
509}
510
511/* body_out - MIME_STATE body line call-back */
512
513static void body_out(void *context, int rec_type, const char *buf,
514		             ssize_t len, off_t offset)
515{
516    HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context;
517    char   *out;
518
519    if (dp->body_checks == 0
520	|| (out = hbc_body_checks(context, dp->body_checks,
521				  buf, len, offset)) == buf) {
522	vstring_sprintf(dp->buf, "%d BODY %c %ld\t|%s",
523			dp->recno, rec_type, (long) offset, buf);
524	out_cb(dp, rec_type, STR(dp->buf), LEN(dp->buf), offset);
525    } else if (out != 0) {
526	vstring_sprintf(dp->buf, "%d BODY %c %ld\t|%s",
527			dp->recno, rec_type, (long) offset, out);
528	out_cb(dp, rec_type, STR(dp->buf), LEN(dp->buf), offset);
529	myfree(out);
530    }
531    dp->recno += 1;
532}
533
534/* body_end - MIME_STATE end-of-message call-back */
535
536static void body_end(void *context)
537{
538    HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context;
539
540    out_cb(dp, 0, "BODY END", sizeof("BODY END") - 1, 0);
541}
542
543/* err_print - print MIME_STATE errors */
544
545static void err_print(void *unused_context, int err_flag,
546		              const char *text, ssize_t len)
547{
548    msg_warn("%s: %.*s", mime_state_error(err_flag),
549	     len < 100 ? (int) len : 100, text);
550}
551
552int     var_header_limit = 2000;
553int     var_mime_maxdepth = 20;
554int     var_mime_bound_len = 2000;
555
556int     main(int argc, char **argv)
557{
558    int     rec_type;
559    VSTRING *buf;
560    int     err;
561    MIME_STATE *mime_state;
562    HBC_TEST_CONTEXT context;
563    static HBC_CALL_BACKS call_backs[1] = {
564	log_cb,				/* logger */
565	out_cb,				/* prepend */
566    };
567
568    /*
569     * Sanity check.
570     */
571    if (argc != 5)
572	msg_fatal("usage: %s header_checks mime_header_checks nested_header_checks body_checks", argv[0]);
573
574    /*
575     * Initialize.
576     */
577#define MIME_OPTIONS \
578            (MIME_OPT_REPORT_8BIT_IN_7BIT_BODY \
579            | MIME_OPT_REPORT_8BIT_IN_HEADER \
580            | MIME_OPT_REPORT_ENCODING_DOMAIN \
581            | MIME_OPT_REPORT_TRUNC_HEADER \
582            | MIME_OPT_REPORT_NESTING \
583            | MIME_OPT_DOWNGRADE)
584    msg_vstream_init(basename(argv[0]), VSTREAM_OUT);
585    buf = vstring_alloc(10);
586    mime_state = mime_state_alloc(MIME_OPTIONS,
587				  head_out, head_end,
588				  body_out, body_end,
589				  err_print,
590				  (void *) &context);
591    context.header_checks =
592	hbc_header_checks_create("header_checks", argv[1],
593				 "mime_header_checks", argv[2],
594				 "nested_header_checks", argv[3],
595				 call_backs);
596    context.body_checks =
597	hbc_body_checks_create("body_checks", argv[4], call_backs);
598    context.buf = vstring_alloc(100);
599    context.fp = VSTREAM_OUT;
600    context.queueid = "test-queueID";
601    context.recno = 0;
602
603    /*
604     * Main loop.
605     */
606    do {
607	rec_type = rec_streamlf_get(VSTREAM_IN, buf, REC_LEN);
608	VSTRING_TERMINATE(buf);
609	err = mime_state_update(mime_state, rec_type, STR(buf), LEN(buf));
610	vstream_fflush(VSTREAM_OUT);
611    } while (rec_type > 0);
612
613    /*
614     * Error reporting.
615     */
616    if (err & MIME_ERR_TRUNC_HEADER)
617	msg_warn("message header length exceeds safety limit");
618    if (err & MIME_ERR_NESTING)
619	msg_warn("MIME nesting exceeds safety limit");
620    if (err & MIME_ERR_8BIT_IN_HEADER)
621	msg_warn("improper use of 8-bit data in message header");
622    if (err & MIME_ERR_8BIT_IN_7BIT_BODY)
623	msg_warn("improper use of 8-bit data in message body");
624    if (err & MIME_ERR_ENCODING_DOMAIN)
625	msg_warn("improper message/* or multipart/* encoding domain");
626
627    /*
628     * Cleanup.
629     */
630    if (context.header_checks)
631	hbc_header_checks_free(context.header_checks);
632    if (context.body_checks)
633	hbc_body_checks_free(context.body_checks);
634    vstring_free(context.buf);
635    mime_state_free(mime_state);
636    vstring_free(buf);
637    exit(0);
638}
639
640#endif
641