Patrick Welche <prlw1@cam.ac.uk>
[netbsd-mini2440.git] / external / ibm-public / postfix / dist / src / global / header_body_checks.c
blob1e66d6b67f69c859b49bd8cc247e07e383e3df5b
1 /* $NetBSD$ */
3 /*++
4 /* NAME
5 /* header_body_checks 3
6 /* SUMMARY
7 /* header/body checks
8 /* SYNOPSIS
9 /* #include <header_body_checks.h>
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;
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;
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;
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;
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;
56 /* void hbc_header_checks_free(hbc)
57 /* HBC_CHECKS *hbc;
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: WARN, REPLACE, PREPEND,
65 /* IGNORE, DUNNO, and OK. These actions are safe for use in
66 /* delivery agents.
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.
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.
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()).
86 /* hbc_header_checks_free() returns memory to the pool.
88 /* hbc_body_checks_create(), dbhc_body_checks(), dbhc_body_free()
89 /* perform similar functions for body lines.
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_CHECK_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 /*--*/
160 /* System library. */
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
169 /* Utility library. */
171 #include <msg.h>
172 #include <mymalloc.h>
174 /* Global library. */
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>
183 /* Application-specific. */
186 * Something that is guaranteed to be different from a real string result
187 * from header/body_checks.
189 const char hbc_checks_unknown;
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).
196 * Body checks are stored as one single HBC_MAP_INFO structure, because we make
197 * no distinction between body segments.
199 #define HBC_HEADER_INDEX(class) ((class) - MIME_HDR_FIRST)
200 #define HBC_BODY_INDEX (0)
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; \
211 } while (0)
213 /* How does the action routine know where we are? */
215 #define HBC_CTXT_HEADER "header"
216 #define HBC_CTXT_BODY "body"
218 /* Silly little macros. */
220 #define STR(x) vstring_str(x)
221 #define LEN(x) VSTRING_LEN(x)
223 /* hbc_action - act upon a header/body match */
225 static 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)
230 const char *cmd_args = cmd + strcspn(cmd, " \t");
231 int cmd_len = cmd_args - cmd;
232 char *ret;
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.
239 while (*cmd_args && ISSPACE(*cmd_args))
240 cmd_args++;
242 #define STREQUAL(x,y,l) (strncasecmp((x), (y), (l)) == 0 && (y)[l] == 0)
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);
249 if (STREQUAL(cmd, "WARN", cmd_len)) {
250 cb->logger(context, "warning", where, line, cmd_args);
251 return ((char *) line);
253 if (STREQUAL(cmd, "REPLACE", cmd_len)) {
254 if (*cmd_args == 0) {
255 msg_warn("REPLACE action without text in %s map", map_class);
256 return ((char *) line);
257 } else if (strcmp(where, HBC_CTXT_HEADER) == 0
258 && !is_header(cmd_args)) {
259 msg_warn("bad REPLACE header text \"%s\" in %s map -- "
260 "need \"headername: headervalue\"", cmd_args, map_class);
261 return ((char *) line);
262 } else {
263 cb->logger(context, "replace", where, line, cmd_args);
264 return (mystrdup(cmd_args));
267 if (cb->prepend && STREQUAL(cmd, "PREPEND", cmd_len)) {
268 if (*cmd_args == 0) {
269 msg_warn("PREPEND action without text in %s map", map_class);
270 } else if (strcmp(where, HBC_CTXT_HEADER) == 0
271 && !is_header(cmd_args)) {
272 msg_warn("bad PREPEND header text \"%s\" in %s map -- "
273 "need \"headername: headervalue\"", cmd_args, map_class);
274 } else {
275 cb->logger(context, "prepend", where, line, cmd_args);
276 cb->prepend(context, REC_TYPE_NORM, cmd_args, strlen(cmd_args), offset);
278 return ((char *) line);
280 /* Allow and ignore optional text after the action. */
282 if (STREQUAL(cmd, "IGNORE", cmd_len))
283 /* XXX Not logged for compatibility with cleanup(8). */
284 return (HBC_CHECKS_STAT_IGNORE);
286 if (STREQUAL(cmd, "DUNNO", cmd_len) /* preferred */
287 ||STREQUAL(cmd, "OK", cmd_len)) /* compatibility */
288 return ((char *) line);
290 msg_warn("unsupported command in %s map: %s", map_class, cmd);
291 return ((char *) line);
294 /* hbc_header_checks - process one complete header line */
296 char *hbc_header_checks(void *context, HBC_CHECKS *hbc, int header_class,
297 const HEADER_OPTS *hdr_opts,
298 VSTRING *header, off_t offset)
300 const char *myname = "hbc_header_checks";
301 const char *action;
302 HBC_MAP_INFO *mp;
304 if (msg_verbose)
305 msg_info("%s: '%.30s'", myname, STR(header));
308 * XXX This is for compatibility with the cleanup(8) server.
310 if (hdr_opts && (hdr_opts->flags & HDR_OPT_MIME))
311 header_class = MIME_HDR_MULTIPART;
313 mp = hbc->map_info + HBC_HEADER_INDEX(header_class);
315 if (mp->maps != 0 && (action = maps_find(mp->maps, STR(header), 0)) != 0) {
316 return (hbc_action(context, hbc->call_backs,
317 mp->map_class, HBC_CTXT_HEADER, action,
318 STR(header), LEN(header), offset));
319 } else {
320 return (STR(header));
324 /* hbc_body_checks - inspect one body record */
326 char *hbc_body_checks(void *context, HBC_CHECKS *hbc, const char *line,
327 ssize_t len, off_t offset)
329 const char *myname = "hbc_body_checks";
330 const char *action;
331 HBC_MAP_INFO *mp;
333 if (msg_verbose)
334 msg_info("%s: '%.30s'", myname, line);
336 mp = hbc->map_info;
338 if ((action = maps_find(mp->maps, line, 0)) != 0) {
339 return (hbc_action(context, hbc->call_backs,
340 mp->map_class, HBC_CTXT_BODY, action,
341 line, len, offset));
342 } else {
343 return ((char *) line);
347 /* hbc_header_checks_create - create header checking context */
349 HBC_CHECKS *hbc_header_checks_create(const char *header_checks_name,
350 const char *header_checks_value,
351 const char *mime_header_checks_name,
352 const char *mime_header_checks_value,
353 const char *nested_header_checks_name,
354 const char *nested_header_checks_value,
355 HBC_CALL_BACKS *call_backs)
357 HBC_CHECKS *hbc;
360 * Optimize for the common case.
362 if (*header_checks_value == 0 && *mime_header_checks_value == 0
363 && *nested_header_checks_value == 0) {
364 return (0);
365 } else {
366 hbc = (HBC_CHECKS *) mymalloc(sizeof(*hbc)
367 + (MIME_HDR_LAST - MIME_HDR_FIRST) * sizeof(HBC_MAP_INFO));
368 hbc->call_backs = call_backs;
369 HBC_INIT(hbc, HBC_HEADER_INDEX(MIME_HDR_PRIMARY),
370 header_checks_name, header_checks_value);
371 HBC_INIT(hbc, HBC_HEADER_INDEX(MIME_HDR_MULTIPART),
372 mime_header_checks_name, mime_header_checks_value);
373 HBC_INIT(hbc, HBC_HEADER_INDEX(MIME_HDR_NESTED),
374 nested_header_checks_name, nested_header_checks_value);
375 return (hbc);
379 /* hbc_body_checks_create - create body checking context */
381 HBC_CHECKS *hbc_body_checks_create(const char *body_checks_name,
382 const char *body_checks_value,
383 HBC_CALL_BACKS *call_backs)
385 HBC_CHECKS *hbc;
388 * Optimize for the common case.
390 if (*body_checks_value == 0) {
391 return (0);
392 } else {
393 hbc = (HBC_CHECKS *) mymalloc(sizeof(*hbc));
394 hbc->call_backs = call_backs;
395 HBC_INIT(hbc, HBC_BODY_INDEX, body_checks_name, body_checks_value);
396 return (hbc);
400 /* _hbc_checks_free - destroy header/body checking context */
402 void _hbc_checks_free(HBC_CHECKS *hbc, ssize_t len)
404 HBC_MAP_INFO *mp;
406 for (mp = hbc->map_info; mp < hbc->map_info + len; mp++)
407 if (mp->maps)
408 maps_free(mp->maps);
409 myfree((char *) hbc);
413 * Test program. Specify the four maps on the command line, and feed a
414 * MIME-formatted message on stdin.
417 #ifdef TEST
419 #include <stdlib.h>
420 #include <stringops.h>
421 #include <vstream.h>
422 #include <msg_vstream.h>
423 #include <rec_streamlf.h>
425 typedef struct {
426 HBC_CHECKS *header_checks;
427 HBC_CHECKS *body_checks;
428 HBC_CALL_BACKS *call_backs;
429 VSTREAM *fp;
430 VSTRING *buf;
431 const char *queueid;
432 int recno;
433 } HBC_TEST_CONTEXT;
435 /*#define REC_LEN 40*/
436 #define REC_LEN 1024
438 /* log_cb - log action with context */
440 static void log_cb(void *context, const char *action, const char *where,
441 const char *content, const char *text)
443 const HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context;
445 if (*text) {
446 msg_info("%s: %s: %s %.200s: %s",
447 dp->queueid, action, where, content, text);
448 } else {
449 msg_info("%s: %s: %s %.200s",
450 dp->queueid, action, where, content);
454 /* out_cb - output call-back */
456 static void out_cb(void *context, int rec_type, const char *buf,
457 ssize_t len, off_t offset)
459 const HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context;
461 vstream_fwrite(dp->fp, buf, len);
462 VSTREAM_PUTC('\n', dp->fp);
463 vstream_fflush(dp->fp);
466 /* head_out - MIME_STATE header call-back */
468 static void head_out(void *context, int header_class,
469 const HEADER_OPTS *header_info,
470 VSTRING *buf, off_t offset)
472 HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context;
473 char *out;
475 if (dp->header_checks == 0
476 || (out = hbc_header_checks(context, dp->header_checks, header_class,
477 header_info, buf, offset)) == STR(buf)) {
478 vstring_sprintf(dp->buf, "%d %s %ld\t|%s",
479 dp->recno,
480 header_class == MIME_HDR_PRIMARY ? "MAIN" :
481 header_class == MIME_HDR_MULTIPART ? "MULT" :
482 header_class == MIME_HDR_NESTED ? "NEST" :
483 "ERROR", (long) offset, STR(buf));
484 out_cb(dp, REC_TYPE_NORM, STR(dp->buf), LEN(dp->buf), offset);
485 } else if (out != 0) {
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, out);
492 out_cb(dp, REC_TYPE_NORM, STR(dp->buf), LEN(dp->buf), offset);
493 myfree(out);
495 dp->recno += 1;
498 /* header_end - MIME_STATE end-of-header call-back */
500 static void head_end(void *context)
502 HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context;
504 out_cb(dp, 0, "HEADER END", sizeof("HEADER END") - 1, 0);
507 /* body_out - MIME_STATE body line call-back */
509 static void body_out(void *context, int rec_type, const char *buf,
510 ssize_t len, off_t offset)
512 HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context;
513 char *out;
515 if (dp->body_checks == 0
516 || (out = hbc_body_checks(context, dp->body_checks,
517 buf, len, offset)) == buf) {
518 vstring_sprintf(dp->buf, "%d BODY %c %ld\t|%s",
519 dp->recno, rec_type, (long) offset, buf);
520 out_cb(dp, rec_type, STR(dp->buf), LEN(dp->buf), offset);
521 } else if (out != 0) {
522 vstring_sprintf(dp->buf, "%d BODY %c %ld\t|%s",
523 dp->recno, rec_type, (long) offset, out);
524 out_cb(dp, rec_type, STR(dp->buf), LEN(dp->buf), offset);
525 myfree(out);
527 dp->recno += 1;
530 /* body_end - MIME_STATE end-of-message call-back */
532 static void body_end(void *context)
534 HBC_TEST_CONTEXT *dp = (HBC_TEST_CONTEXT *) context;
536 out_cb(dp, 0, "BODY END", sizeof("BODY END") - 1, 0);
539 /* err_print - print MIME_STATE errors */
541 static void err_print(void *unused_context, int err_flag,
542 const char *text, ssize_t len)
544 msg_warn("%s: %.*s", mime_state_error(err_flag),
545 len < 100 ? (int) len : 100, text);
548 int var_header_limit = 2000;
549 int var_mime_maxdepth = 20;
550 int var_mime_bound_len = 2000;
552 int main(int argc, char **argv)
554 int rec_type;
555 VSTRING *buf;
556 int err;
557 MIME_STATE *mime_state;
558 HBC_TEST_CONTEXT context;
559 static HBC_CALL_BACKS call_backs[1] = {
560 log_cb, /* logger */
561 out_cb, /* prepend */
565 * Sanity check.
567 if (argc != 5)
568 msg_fatal("usage: %s header_checks mime_header_checks nested_header_checks body_checks", argv[0]);
571 * Initialize.
573 #define MIME_OPTIONS \
574 (MIME_OPT_REPORT_8BIT_IN_7BIT_BODY \
575 | MIME_OPT_REPORT_8BIT_IN_HEADER \
576 | MIME_OPT_REPORT_ENCODING_DOMAIN \
577 | MIME_OPT_REPORT_TRUNC_HEADER \
578 | MIME_OPT_REPORT_NESTING \
579 | MIME_OPT_DOWNGRADE)
580 msg_vstream_init(basename(argv[0]), VSTREAM_OUT);
581 buf = vstring_alloc(10);
582 mime_state = mime_state_alloc(MIME_OPTIONS,
583 head_out, head_end,
584 body_out, body_end,
585 err_print,
586 (void *) &context);
587 context.header_checks =
588 hbc_header_checks_create("header_checks", argv[1],
589 "mime_header_checks", argv[2],
590 "nested_header_checks", argv[3],
591 call_backs);
592 context.body_checks =
593 hbc_body_checks_create("body_checks", argv[4], call_backs);
594 context.buf = vstring_alloc(100);
595 context.fp = VSTREAM_OUT;
596 context.queueid = "test-queueID";
597 context.recno = 0;
600 * Main loop.
602 do {
603 rec_type = rec_streamlf_get(VSTREAM_IN, buf, REC_LEN);
604 VSTRING_TERMINATE(buf);
605 err = mime_state_update(mime_state, rec_type, STR(buf), LEN(buf));
606 vstream_fflush(VSTREAM_OUT);
607 } while (rec_type > 0);
610 * Error reporting.
612 if (err & MIME_ERR_TRUNC_HEADER)
613 msg_warn("message header length exceeds safety limit");
614 if (err & MIME_ERR_NESTING)
615 msg_warn("MIME nesting exceeds safety limit");
616 if (err & MIME_ERR_8BIT_IN_HEADER)
617 msg_warn("improper use of 8-bit data in message header");
618 if (err & MIME_ERR_8BIT_IN_7BIT_BODY)
619 msg_warn("improper use of 8-bit data in message body");
620 if (err & MIME_ERR_ENCODING_DOMAIN)
621 msg_warn("improper message/* or multipart/* encoding domain");
624 * Cleanup.
626 if (context.header_checks)
627 hbc_header_checks_free(context.header_checks);
628 if (context.body_checks)
629 hbc_body_checks_free(context.body_checks);
630 vstring_free(context.buf);
631 mime_state_free(mime_state);
632 vstring_free(buf);
633 exit(0);
636 #endif