--- /dev/null
+/*++
+/* NAME
+/* cleanup_message_test 1t
+/* SUMMARY
+/* cleanup_message unit tests
+/* SYNOPSIS
+/* ./cleanup_message_test
+/* DESCRIPTION
+/* cleanup_message_test runs and logs each configured test, reports if
+/* a test is a PASS or FAIL, and returns an exit status of zero if
+/* all tests are a PASS.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* porcupine.org
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+ /*
+ * Utility library.
+ */
+#include <msg.h>
+#include <msg_vstream.h>
+#include <stringops.h>
+#include <vstream.h>
+#include <vstring.h>
+
+ /*
+ * Global library.
+ */
+#include <cleanup_user.h>
+#include <hfrom_format.h>
+#include <mail_params.h>
+#include <record.h>
+#include <rec_type.h>
+
+ /*
+ * Application-specific.
+ */
+#include <cleanup.h>
+
+ /*
+ * Stubs for configuration parameter dependencies.
+ */
+int var_always_add_hdrs;
+int var_auto_8bit_enc_hdr;
+int var_body_check_len;
+int var_cleanup_mask_stray_cr_lf;
+int var_dup_filter_limit;
+int var_force_mime_iconv;
+char *var_full_name_encoding_charset;
+char *var_header_checks;
+int var_hopcount_limit;
+char *var_mimehdr_checks;
+char *var_nesthdr_checks;
+char *var_rcpt_witheld;
+int var_reqtls_esmtp_hdr;
+
+MAPS *cleanup_comm_canon_maps;
+MAPS *cleanup_send_canon_maps;
+MAPS *cleanup_rcpt_canon_maps;
+MAPS *cleanup_virt_alias_maps;
+int cleanup_comm_canon_flags;
+int cleanup_send_canon_flags;
+int cleanup_rcpt_canon_flags;
+ARGV *cleanup_masq_domains;
+MAPS *cleanup_header_checks;
+MAPS *cleanup_mimehdr_checks;
+MAPS *cleanup_nesthdr_checks;
+MAPS *cleanup_body_checks;
+STRING_LIST *cleanup_masq_exceptions;
+char *var_masq_classes;
+MAPS *cleanup_send_bcc_maps;
+MAPS *cleanup_rcpt_bcc_maps;
+MILTERS *cleanup_milters;
+int cleanup_ext_prop_mask;
+int cleanup_hfrom_format;
+int cleanup_masq_flags;
+int cleanup_non_empty_eoh_action;
+char *cleanup_path;
+VSTRING *cleanup_reject_chars;
+VSTRING *cleanup_strip_chars;
+
+static void test_setup(void)
+{
+ var_always_add_hdrs = DEF_ALWAYS_ADD_HDRS;
+ var_auto_8bit_enc_hdr = DEF_AUTO_8BIT_ENC_HDR;
+ var_body_check_len = DEF_BODY_CHECK_LEN;
+ var_cleanup_mask_stray_cr_lf = DEF_CLEANUP_MASK_STRAY_CR_LF;
+ var_dup_filter_limit = DEF_DUP_FILTER_LIMIT;
+ var_force_mime_iconv = DEF_FORCE_MIME_ICONV;
+ var_full_name_encoding_charset = DEF_FULL_NAME_ENCODING_CHARSET;
+ var_header_checks = DEF_HEADER_CHECKS;
+ var_hopcount_limit = DEF_HOPCOUNT_LIMIT;
+ var_mimehdr_checks = DEF_MIMEHDR_CHECKS;
+ var_nesthdr_checks = DEF_NESTHDR_CHECKS;
+ var_rcpt_witheld = DEF_RCPT_WITHELD;
+ cleanup_hfrom_format = HFROM_FORMAT_CODE_STD;
+ cleanup_masq_flags = (CLEANUP_MASQ_FLAG_ENV_FROM | \
+ CLEANUP_MASQ_FLAG_HDR_FROM | CLEANUP_MASQ_FLAG_HDR_RCPT);
+ cleanup_non_empty_eoh_action = NON_EMPTY_EOH_CODE_FIX_QUIETLY;
+ var_masq_classes = DEF_MASQ_CLASSES;
+ var_drop_hdrs = DEF_DROP_HDRS;
+ var_header_limit = 2000;
+ var_line_limit = DEF_LINE_LIMIT;
+ var_info_log_addr_form = DEF_INFO_LOG_ADDR_FORM;
+ var_reqtls_esmtp_hdr = 1;
+}
+
+ /*
+ * Stubs for cleanup_message.c dependencies.
+ */
+void cleanup_extracted(CLEANUP_STATE *state, int type, const char *data,
+ ssize_t len)
+{
+ msg_panic("cleanup_extracted");
+}
+
+int cleanup_map11_tree(CLEANUP_STATE *state, TOK822 *tree,
+ MAPS *maps, int propagate)
+{
+ return (0);
+}
+
+int cleanup_masquerade_tree(CLEANUP_STATE *state, TOK822 *tree,
+ ARGV *masq_domains)
+{
+ return (0);
+}
+
+int cleanup_rewrite_tree(const char *context_name, TOK822 *tree)
+{
+ return (0);
+}
+
+ /*
+ * Stubs for cleanup_state.c dependencies.
+ */
+void cleanup_envelope(CLEANUP_STATE *state, int type, const char *data,
+ ssize_t len)
+{
+ msg_panic("cleanup_envelope");
+}
+
+void cleanup_region_done(CLEANUP_STATE *state)
+{
+}
+
+ /*
+ * Intercept cleanup_out_header(), cleanup_out_string(),
+ * cleanup_out_format()
+ */
+int rec_put(VSTREAM *stream, int type, const char *data, ssize_t len)
+{
+ if (msg_verbose)
+ msg_info("fake_%s: %c '%.*s' %d",
+ __func__, type, (int) len, data, (int) len);
+
+ /*
+ * TOO(wietse) envelope records.
+ */ if (type == REC_TYPE_NORM)
+ vstream_fprintf(stream, "%.*s\n", (int) len, data);
+ else if (type == REC_TYPE_CONT)
+ vstream_fprintf(stream, "%.*s", (int) len, data);
+ return (type);
+}
+
+ /*
+ * Tests and test cases.
+ */
+typedef struct TEST_CASE {
+ const char *label; /* identifies test case */
+ int (*action) (const struct TEST_CASE *);
+} TEST_CASE;
+
+#define PASS 1
+#define FAIL 0
+
+static int test_action(const TEST_CASE *tp, const char *inputs[],
+ int want_errs, const char *want_text)
+{
+ VSTRING *got_text = vstring_alloc(100);
+ VSTREAM *dst = vstream_memopen(got_text, O_WRONLY);
+ CLEANUP_STATE *state = 0;
+ int ret = PASS;
+ const char *const * cpp;
+
+ if (dst == 0) {
+ msg_warn("vstream_memopen: %m");
+ ret = FAIL;
+ } else {
+ state = cleanup_state_alloc((VSTREAM *) 0);
+ state->queue_id = mystrdup("queue_id");
+ state->flags |= CLEANUP_FLAG_FILTER;
+ state->sender = mystrdup("sender");
+ state->recip = mystrdup("recip");
+ /* Don't add 'missing' headers. */
+ state->headers_seen = ~0;
+
+ state->dst = dst;
+ state->action = cleanup_message;
+ for (cpp = inputs; *cpp; cpp++)
+ state->action(state, REC_TYPE_NORM, *cpp, strlen(*cpp));
+ state->action(state, REC_TYPE_XTRA, "", 0);
+ (void) vstream_fclose(state->dst);
+ state->dst = 0;
+ if (state->errs != want_errs) {
+ msg_warn("cleanup_envelope: got: '%s', want: '%s'",
+ cleanup_strerror(state->errs),
+ cleanup_strerror(want_errs));
+ ret = FAIL;
+ } else if (want_errs == CLEANUP_STAT_OK
+ && strcmp(vstring_str(got_text), want_text) != 0) {
+ msg_warn("got '%s', want: '%s'", vstring_str(got_text),
+ want_text);
+ ret = FAIL;
+ }
+ }
+
+ /*
+ * Cleanup.
+ */
+ vstring_free(got_text);
+ if (state)
+ cleanup_state_free(state);
+ return (ret);
+}
+
+static int silently_adds_empty_line(const TEST_CASE *tp)
+{
+ static const char *inputs[] = {
+ "Received: text",
+ "bad header: text",
+ 0,
+ };
+ int want_errs = CLEANUP_STAT_OK;
+ const char *want_text = {
+ "Received: text\n"
+ "\n"
+ "bad header: text\n"
+ };
+
+ cleanup_non_empty_eoh_action = NON_EMPTY_EOH_CODE_FIX_QUIETLY;
+ return (test_action(tp, inputs, want_errs, want_text));
+}
+
+static int adds_informative_header(const TEST_CASE *tp)
+{
+ static const char *inputs[] = {
+ "Received: text",
+ "bad header: text",
+ 0,
+ };
+ int want_errs = CLEANUP_STAT_OK;
+ const char *want_text = {
+ "Received: text\n"
+ "MIME-Error: message header was not terminated by empty line\n"
+ "\n"
+ "bad header: text\n"
+ };
+
+ cleanup_non_empty_eoh_action = NON_EMPTY_EOH_CODE_ADD_HDR;
+ return (test_action(tp, inputs, want_errs, want_text));
+}
+
+static int rejects_non_empty_header_end(const TEST_CASE *tp)
+{
+ static const char *inputs[] = {
+ "Received: text",
+ "bad header text",
+ 0,
+ };
+ int want_errs = CLEANUP_STAT_CONT;
+ const char *want_text = 0;
+
+ cleanup_non_empty_eoh_action = NON_EMPTY_EOH_CODE_REJECT;
+ return (test_action(tp, inputs, want_errs, want_text));
+}
+
+static const TEST_CASE test_cases[] = {
+ {"silently_adds_empty_line",
+ silently_adds_empty_line,
+ },
+ {"adds_informative_header",
+ adds_informative_header,
+ },
+ {"rejects_non_empty_header_end",
+ rejects_non_empty_header_end,
+ },
+ {0},
+};
+
+static NORETURN usage(const char *myname)
+{
+ msg_fatal("usage: %s [-v]", myname);
+}
+
+int main(int argc, char **argv)
+{
+ const char *myname = sane_basename((VSTRING *) 0, argv[0]);
+ const TEST_CASE *tp;
+ int pass = 0;
+ int fail = 0;
+ int ch;
+
+ /* XXX How to avoid linking in mail_params.o? */
+
+ msg_vstream_init(myname, VSTREAM_ERR);
+ while ((ch = GETOPT(argc, argv, "v")) > 0) {
+ switch (ch) {
+ default:
+ usage(myname);
+ case 'v':
+ msg_verbose++;
+ break;
+ }
+ }
+
+ for (tp = test_cases; tp->label != 0; tp++) {
+ test_setup();
+ msg_info("RUN %s", tp->label);
+ if (tp->action(tp) != PASS) {
+ fail++;
+ msg_info("FAIL %s", tp->label);
+ } else {
+ msg_info("PASS %s", tp->label);
+ pass++;
+ }
+ }
+ msg_info("PASS=%d FAIL=%d", pass, fail);
+ exit(fail != 0);
+}