5 /* header_body_checks 3
9 /* #include <header_body_checks.h>
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);
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,
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,
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)
47 /* const HEADER_OPTS *hdr_opts;
50 /* char *hbc_body_checks(context, hbc, body_line, body_line_len)
53 /* const char *body_line;
54 /* ssize_t body_line_len;
56 /* void hbc_header_checks_free(hbc)
59 /* void hbc_body_checks_free(hbc)
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
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
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.
93 /* One line of body text.
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.
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.
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.
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
125 /* Application context for call-back functions specified with the
126 /* call_backs argument.
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
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.
139 /* A number in the range MIME_HDR_FIRST..MIME_HDR_LAST.
141 /* A handle created with hbc_header_checks_create() or
142 /* hbc_body_checks_create().
144 /* Message header properties.
146 /* msg(3) diagnostics interface
148 /* Fatal errors: memory allocation problem.
152 /* The Secure Mailer license must be distributed with this software.
155 /* IBM T.J. Watson Research
157 /* Yorktown Heights, NY 10598, USA
160 /* System library. */
162 #include <sys_defs.h>
165 #ifdef STRCASECMP_IN_STRINGS_H
169 /* Utility library. */
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
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); \
208 _mp->map_class = 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
;
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
))
242 #define STREQUAL(x,y,l) (strncasecmp((x), (y), (l)) == 0 && (y)[l] == 0)
245 && (ret
= cb
->extend(context
, cmd
, cmd_len
, cmd_args
, where
, line
,
246 line_len
, offset
)) != HBC_CHECKS_STAT_UNKNOWN
)
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
);
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
);
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";
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
));
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";
334 msg_info("%s: '%.30s'", myname
, line
);
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
,
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
)
360 * Optimize for the common case.
362 if (*header_checks_value
== 0 && *mime_header_checks_value
== 0
363 && *nested_header_checks_value
== 0) {
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
);
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
)
388 * Optimize for the common case.
390 if (*body_checks_value
== 0) {
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
);
400 /* _hbc_checks_free - destroy header/body checking context */
402 void _hbc_checks_free(HBC_CHECKS
*hbc
, ssize_t len
)
406 for (mp
= hbc
->map_info
; mp
< hbc
->map_info
+ len
; mp
++)
409 myfree((char *) hbc
);
413 * Test program. Specify the four maps on the command line, and feed a
414 * MIME-formatted message on stdin.
420 #include <stringops.h>
422 #include <msg_vstream.h>
423 #include <rec_streamlf.h>
426 HBC_CHECKS
*header_checks
;
427 HBC_CHECKS
*body_checks
;
428 HBC_CALL_BACKS
*call_backs
;
435 /*#define REC_LEN 40*/
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
;
446 msg_info("%s: %s: %s %.200s: %s",
447 dp
->queueid
, action
, where
, content
, text
);
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
;
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",
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",
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
);
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
;
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
);
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
)
557 MIME_STATE
*mime_state
;
558 HBC_TEST_CONTEXT context
;
559 static HBC_CALL_BACKS call_backs
[1] = {
561 out_cb
, /* prepend */
568 msg_fatal("usage: %s header_checks mime_header_checks nested_header_checks body_checks", argv
[0]);
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
,
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],
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";
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);
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");
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
);