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