From 8523533c08c018ac4b750b0e0fab6cfe611e8a49 Mon Sep 17 00:00:00 2001 From: Tom Kistner Date: Thu, 16 Dec 2004 15:11:47 +0000 Subject: [PATCH] Merge from EXISCAN branch. --- src/OS/Makefile-Base | 37 +- src/exim_monitor/em_globals.c | 16 +- src/scripts/MakeLinks | 23 +- src/src/EDITME | 46 +- src/src/acl.c | 358 ++++++++- src/src/bmi_spam.c | 472 ++++++++++++ src/src/bmi_spam.h | 24 + src/src/config.h.defaults | 14 +- src/src/deliver.c | 22 +- src/src/demime.c | 1245 +++++++++++++++++++++++++++++++ src/src/demime.h | 136 ++++ src/src/exim.c | 17 +- src/src/exim.h | 20 +- src/src/expand.c | 59 +- src/src/functions.h | 29 +- src/src/globals.c | 97 ++- src/src/globals.h | 82 +- src/src/header.c | 15 +- src/src/local_scan.h | 6 +- src/src/macros.h | 20 +- src/src/malware.c | 1322 +++++++++++++++++++++++++++++++++ src/src/mime.c | 730 ++++++++++++++++++ src/src/mime.h | 85 +++ src/src/readconf.c | 17 +- src/src/receive.c | 178 ++++- src/src/regex.c | 247 ++++++ src/src/route.c | 52 +- src/src/routers/redirect.c | 81 +- src/src/routers/redirect.h | 9 +- src/src/smtp_in.c | 28 +- src/src/spam.c | 340 +++++++++ src/src/spam.h | 30 + src/src/spf.c | 131 ++++ src/src/spf.h | 39 + src/src/spool_in.c | 20 +- src/src/spool_mbox.c | 174 +++++ src/src/spool_out.c | 9 +- src/src/srs.c | 206 +++++ src/src/srs.h | 31 + src/src/structs.h | 10 +- 40 files changed, 6428 insertions(+), 49 deletions(-) create mode 100644 src/src/bmi_spam.c create mode 100644 src/src/bmi_spam.h create mode 100644 src/src/demime.c create mode 100644 src/src/demime.h create mode 100644 src/src/malware.c create mode 100644 src/src/mime.c create mode 100644 src/src/mime.h create mode 100644 src/src/regex.c create mode 100644 src/src/spam.c create mode 100644 src/src/spam.h create mode 100644 src/src/spf.c create mode 100644 src/src/spf.h create mode 100644 src/src/spool_mbox.c create mode 100644 src/src/srs.c create mode 100644 src/src/srs.h diff --git a/src/OS/Makefile-Base b/src/OS/Makefile-Base index 7a5cf2b43..4e928322f 100644 --- a/src/OS/Makefile-Base +++ b/src/OS/Makefile-Base @@ -1,4 +1,4 @@ -# $Cambridge: exim/src/OS/Makefile-Base,v 1.1 2004/10/06 15:07:39 ph10 Exp $ +# $Cambridge: exim/src/OS/Makefile-Base,v 1.2 2004/12/16 15:11:47 tom Exp $ # This file is the basis of the main makefile for Exim and friends. The # makefile at the top level arranges to build the main makefile by calling @@ -284,6 +284,14 @@ convert4r4: Makefile ../src/convert4r4.src @echo ">>> convert4r4 script built"; echo "" +# These are objects of optional features. They are always compiled, but +# if the corresponding #defines are not set, they wind up empty and +# are thrown away by the linker. + +OBJ_WITH_CONTENT_SCAN = malware.o mime.o regex.o spam.o spool_mbox.o +OBJ_WITH_OLD_DEMIME = demime.o +OBJ_EXPERIMENTAL = bmi_spam.o spf.o srs.o + # Targets for final binaries; the main one has a build number which is # updated each time. We don't bother with that for the auxiliaries. @@ -295,7 +303,8 @@ OBJ_EXIM = acl.o child.o crypt16.o daemon.o dbfn.o debug.o deliver.o \ rda.o readconf.o receive.o retry.o rewrite.o rfc2047.o \ route.o search.o sieve.o smtp_in.o smtp_out.o spool_in.o spool_out.o \ store.o string.o tls.o tod.o transport.o tree.o verify.o \ - local_scan.o $(EXIM_PERL) + local_scan.o $(EXIM_PERL) $(OBJ_WITH_CONTENT_SCAN) \ + $(OBJ_WITH_OLD_DEMIME) $(OBJ_EXPERIMENTAL) exim: pcre/libpcre.a lookups/lookups.a auths/auths.a \ routers/routers.a transports/transports.a \ @@ -311,7 +320,7 @@ exim: pcre/libpcre.a lookups/lookups.a auths/auths.a \ auths/auths.a \ $(LIBRESOLV) $(LIBS) $(LIBS_EXIM) $(IPV6_LIBS) $(EXTRALIBS) \ $(EXTRALIBS_EXIM) $(DBMLIB) $(LOOKUP_LIBS) $(AUTH_LIBS) \ - $(PERL_LIBS) $(TLS_LIBS) + $(PERL_LIBS) $(TLS_LIBS) $(LDFLAGS) @if [ x"$(STRIP_COMMAND)" != x"" ]; then \ echo $(STRIP_COMMAND) exim; \ $(STRIP_COMMAND) exim; \ @@ -546,6 +555,28 @@ transport.o: $(HDRS) transport.c tree.o: $(HDRS) tree.c verify.o: $(HDRS) verify.c + +# Dependencies for WITH_CONTENT_SCAN modules + +malware.o: $(HDRS) malware.c +mime.o: $(HDRS) mime.c +regex.o: $(HDRS) regex.c +spam.o: $(HDRS) spam.c +spool_mbox.o: $(HDRS) spool_mbox.c + + +# Dependencies for WITH_OLD_DEMIME modules + +demime.o: $(HDRS) demime.c + + +# Dependencies for EXPERIMENTAL_* modules + +bmi_spam.o: $(HDRS) bmi_spam.c +spf.o: $(HDRS) spf.c +srs.o: $(HDRS) srs.c + + # The module containing tables of available lookups, routers, auths, and # transports must be rebuilt if any of them are. However, because the makefiles # for the drivers are always run, we don't actually put the dependencies here, diff --git a/src/exim_monitor/em_globals.c b/src/exim_monitor/em_globals.c index ae38e9f8a..d9f92d8be 100644 --- a/src/exim_monitor/em_globals.c +++ b/src/exim_monitor/em_globals.c @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/exim_monitor/em_globals.c,v 1.1 2004/10/07 10:39:01 ph10 Exp $ */ +/* $Cambridge: exim/src/exim_monitor/em_globals.c,v 1.2 2004/12/16 15:11:47 tom Exp $ */ /************************************************* * Exim Monitor * @@ -44,6 +44,11 @@ uschar actioned_message[24]; uschar *action_required; uschar *alternate_config = NULL; +#ifdef EXPERIMENTAL_BRIGHTMAIL +int bmi_run = 0; +uschar *bmi_verdicts = NULL; +#endif + int body_max = 20000; uschar *exim_path = US BIN_DIRECTORY "/exim" @@ -132,6 +137,10 @@ int deliver_frozen_at = 0; BOOL deliver_manual_thaw = FALSE; BOOL dont_deliver = FALSE; +#ifdef WITH_CONTENT_SCAN +BOOL fake_reject = FALSE; +#endif + header_line *header_last = NULL; header_line *header_list = NULL; @@ -142,6 +151,11 @@ int interface_port = 0; BOOL local_error_message = FALSE; uschar *local_scan_data = NULL; BOOL log_timezone = FALSE; + +#ifdef WITH_CONTENT_SCAN +uschar *spam_score_int = NULL; +#endif + int message_age = 0; uschar *message_id; uschar *message_id_external; diff --git a/src/scripts/MakeLinks b/src/scripts/MakeLinks index dd1b9eee0..1e6eaf151 100755 --- a/src/scripts/MakeLinks +++ b/src/scripts/MakeLinks @@ -1,5 +1,5 @@ #!/bin/sh -# $Cambridge: exim/src/scripts/MakeLinks,v 1.1 2004/10/06 15:07:40 ph10 Exp $ +# $Cambridge: exim/src/scripts/MakeLinks,v 1.2 2004/12/16 15:11:47 tom Exp $ # Script to build links for all the exim source files from the system- # specific build directory. It should be run from within that directory. @@ -241,4 +241,25 @@ ln -s ../src/tree.c tree.c ln -s ../src/verify.c verify.c ln -s ../src/version.c version.c +# WITH_CONTENT_SCAN +ln -s ../src/spam.c spam.c +ln -s ../src/spam.h spam.h +ln -s ../src/spool_mbox.c spool_mbox.c +ln -s ../src/regex.c regex.c +ln -s ../src/mime.c mime.c +ln -s ../src/mime.h mime.h +ln -s ../src/malware.c malware.c + +# WITH_OLD_DEMIME +ln -s ../src/demime.c demime.c +ln -s ../src/demime.h demime.h + +# EXPERIMENTAL_* +ln -s ../src/bmi_spam.c bmi_spam.c +ln -s ../src/bmi_spam.h bmi_spam.h +ln -s ../src/spf.c spf.c +ln -s ../src/spf.h spf.h +ln -s ../src/srs.c srs.c +ln -s ../src/srs.h srs.h + # End of MakeLinks diff --git a/src/src/EDITME b/src/src/EDITME index cea00d2c8..aefed22ce 100644 --- a/src/src/EDITME +++ b/src/src/EDITME @@ -1,4 +1,4 @@ -# $Cambridge: exim/src/src/EDITME,v 1.4 2004/11/05 12:33:59 ph10 Exp $ +# $Cambridge: exim/src/src/EDITME,v 1.5 2004/12/16 15:11:47 tom Exp $ ################################################## # The Exim mail transport agent # @@ -314,6 +314,50 @@ LOOKUP_LSEARCH=yes EXIM_MONITOR=eximon.bin +#------------------------------------------------------------------------------ +# Compiling Exim with content scanning support: If you want to compile Exim +# with support for message body content scanning, set WITH_CONTENT_SCAN to +# the value "yes". This will give you malware and spam scanning in the DATA ACL, +# and the MIME ACL. Please read the documentation to learn more about these +# features. + +#WITH_CONTENT_SCAN=yes + +# If you want to use the deprecated "demime" condition in the DATA ACL, +# uncomment the line below. Doing so will also explicitly turn on the +# WITH_CONTENT_SCAN option. If possible, use the MIME ACL instead of +# the "demime" condition. + +#WITH_OLD_DEMIME=yes + +#------------------------------------------------------------------------------ +# Compiling Exim with experimental features. These are documented in +# experimental-spec.txt. "Experimental" means that the way these features are +# implemented may still change. Backward compatability is not guaranteed. + +# Uncomment the following lines to add SPF support. You need to have libspf2 +# installed on your system (www.libspf2.org). Depending on where it is installed +# you may have to edit the CFLAGS and LDFLAGS lines. +#EXPERIMENTAL_SPF=yes +#CFLAGS += -I/usr/local/include +#LDFLAGS += -lspf2 + +# Uncommend the following lines to add SRS (Sender rewriting scheme) support. +# You need to have libsrs_alt installed on your system (srs.mirtol.com). +# Depending on where it is installed you may have to edit the CFLAGS and +# LDFLAGS lines. +#EXPERIMENTAL_SRS=yes +#CFLAGS += -I/usr/local/include +#LDFLAGS += -lsrs_alt + +# Uncommend the following lines to add Brightmail AntiSpam support. You need +# to have the Brightmail client SDK installed. Please check the experimental +# documentation for implementation details. You need to edit the CFLAGS and +# LDFLAGS lines. +#EXPERIMENTAL_BRIGHTMAIL=yes +#CFLAGS += -I/opt/brightmail/bsdk-6.0/include +#LDFLAGS += -lxml2 -lbmiclient_single -L/opt/brightmail/bsdk-6.0/lib + ############################################################################### diff --git a/src/src/acl.c b/src/src/acl.c index 92ebc18ec..247ffbb62 100644 --- a/src/src/acl.c +++ b/src/src/acl.c @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/acl.c,v 1.5 2004/11/04 12:19:48 ph10 Exp $ */ +/* $Cambridge: exim/src/src/acl.c,v 1.6 2004/12/16 15:11:47 tom Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -34,19 +34,77 @@ static int msgcond[] = { FAIL, OK, OK, FAIL, OK, FAIL, OK }; /* ACL condition and modifier codes - keep in step with the table that follows. */ -enum { ACLC_ACL, ACLC_AUTHENTICATED, ACLC_CONDITION, ACLC_CONTROL, ACLC_DELAY, - ACLC_DNSLISTS, ACLC_DOMAINS, ACLC_ENCRYPTED, ACLC_ENDPASS, ACLC_HOSTS, - ACLC_LOCAL_PARTS, ACLC_LOG_MESSAGE, ACLC_LOGWRITE, ACLC_MESSAGE, - ACLC_RECIPIENTS, ACLC_SENDER_DOMAINS, ACLC_SENDERS, ACLC_SET, ACLC_VERIFY }; +enum { ACLC_ACL, ACLC_AUTHENTICATED, +#ifdef EXPERIMENTAL_BRIGHTMAIL + ACLC_BMI_OPTIN, +#endif +ACLC_CONDITION, ACLC_CONTROL, +#ifdef WITH_CONTENT_SCAN + ACLC_DECODE, +#endif + ACLC_DELAY, +#ifdef WITH_OLD_DEMIME + ACLC_DEMIME, +#endif + ACLC_DNSLISTS, ACLC_DOMAINS, ACLC_ENCRYPTED, ACLC_ENDPASS, + ACLC_HOSTS, ACLC_LOCAL_PARTS, ACLC_LOG_MESSAGE, ACLC_LOGWRITE, +#ifdef WITH_CONTENT_SCAN + ACLC_MALWARE, +#endif + ACLC_MESSAGE, +#ifdef WITH_CONTENT_SCAN + ACLC_MIME_REGEX, +#endif + ACLC_RECIPIENTS, +#ifdef WITH_CONTENT_SCAN + ACLC_REGEX, +#endif + ACLC_SENDER_DOMAINS, ACLC_SENDERS, ACLC_SET, +#ifdef WITH_CONTENT_SCAN + ACLC_SPAM, +#endif +#ifdef EXPERIMENTAL_SPF + ACLC_SPF, +#endif + ACLC_VERIFY }; /* ACL conditions/modifiers: "delay", "control", "endpass", "message", "log_message", "logwrite", and "set" are modifiers that look like conditions but always return TRUE. They are used for their side effects. */ -static uschar *conditions[] = { US"acl", US"authenticated", US"condition", - US"control", US"delay", US"dnslists", US"domains", US"encrypted", +static uschar *conditions[] = { US"acl", US"authenticated", +#ifdef EXPERIMENTAL_BRIGHTMAIL + US"bmi_optin", +#endif + US"condition", + US"control", +#ifdef WITH_CONTENT_SCAN + US"decode", +#endif + US"delay", +#ifdef WITH_OLD_DEMIME + US"demime", +#endif + US"dnslists", US"domains", US"encrypted", US"endpass", US"hosts", US"local_parts", US"log_message", US"logwrite", - US"message", US"recipients", US"sender_domains", US"senders", US"set", +#ifdef WITH_CONTENT_SCAN + US"malware", +#endif + US"message", +#ifdef WITH_CONTENT_SCAN + US"mime_regex", +#endif + US"recipients", +#ifdef WITH_CONTENT_SCAN + US"regex", +#endif + US"sender_domains", US"senders", US"set", +#ifdef WITH_CONTENT_SCAN + US"spam", +#endif +#ifdef EXPERIMENTAL_SPF + US"spf", +#endif US"verify" }; /* ACL control names */ @@ -62,9 +120,18 @@ checking functions. */ static uschar cond_expand_at_top[] = { TRUE, /* acl */ FALSE, /* authenticated */ +#ifdef EXPERIMENTAL_BRIGHTMAIL + TRUE, /* bmi_optin */ +#endif TRUE, /* condition */ TRUE, /* control */ +#ifdef WITH_CONTENT_SCAN + TRUE, /* decode */ +#endif TRUE, /* delay */ +#ifdef WITH_OLD_DEMIME + TRUE, /* demime */ +#endif TRUE, /* dnslists */ FALSE, /* domains */ FALSE, /* encrypted */ @@ -73,11 +140,26 @@ static uschar cond_expand_at_top[] = { FALSE, /* local_parts */ TRUE, /* log_message */ TRUE, /* logwrite */ +#ifdef WITH_CONTENT_SCAN + TRUE, /* malware */ +#endif TRUE, /* message */ +#ifdef WITH_CONTENT_SCAN + TRUE, /* mime_regex */ +#endif FALSE, /* recipients */ +#ifdef WITH_CONTENT_SCAN + TRUE, /* regex */ +#endif FALSE, /* sender_domains */ FALSE, /* senders */ TRUE, /* set */ +#ifdef WITH_CONTENT_SCAN + TRUE, /* spam */ +#endif +#ifdef EXPERIMENTAL_SPF + TRUE, /* spf */ +#endif TRUE /* verify */ }; @@ -86,9 +168,18 @@ static uschar cond_expand_at_top[] = { static uschar cond_modifiers[] = { FALSE, /* acl */ FALSE, /* authenticated */ +#ifdef EXPERIMENTAL_BRIGHTMAIL + TRUE, /* bmi_optin */ +#endif FALSE, /* condition */ TRUE, /* control */ +#ifdef WITH_CONTENT_SCAN + FALSE, /* decode */ +#endif TRUE, /* delay */ +#ifdef WITH_OLD_DEMIME + FALSE, /* demime */ +#endif FALSE, /* dnslists */ FALSE, /* domains */ FALSE, /* encrypted */ @@ -96,12 +187,27 @@ static uschar cond_modifiers[] = { FALSE, /* hosts */ FALSE, /* local_parts */ TRUE, /* log_message */ - TRUE, /* log_write */ + TRUE, /* logwrite */ +#ifdef WITH_CONTENT_SCAN + FALSE, /* malware */ +#endif TRUE, /* message */ +#ifdef WITH_CONTENT_SCAN + FALSE, /* mime_regex */ +#endif FALSE, /* recipients */ +#ifdef WITH_CONTENT_SCAN + FALSE, /* regex */ +#endif FALSE, /* sender_domains */ FALSE, /* senders */ TRUE, /* set */ +#ifdef WITH_CONTENT_SCAN + FALSE, /* spam */ +#endif +#ifdef EXPERIMENTAL_SPF + FALSE, /* spf */ +#endif FALSE /* verify */ }; @@ -112,13 +218,46 @@ static unsigned int cond_forbids[] = { 0, /* acl */ (1< 0) newtype = htype_add_rec; p += 16; } + else if (strncmpic(p, US":at_start_rfc:", 14) == 0) + { + newtype = htype_add_rfc; + p += 14; + } else if (strncmpic(p, US":at_start:", 10) == 0) { newtype = htype_add_top; @@ -1253,6 +1469,9 @@ uschar *user_message = NULL; uschar *log_message = NULL; uschar *p; int rc = OK; +#ifdef WITH_CONTENT_SCAN +int sep = '/'; +#endif for (; cb != NULL; cb = cb->next) { @@ -1360,6 +1579,17 @@ for (; cb != NULL; cb = cb->next) TRUE, NULL); break; +#ifdef EXPERIMENTAL_BRIGHTMAIL + case ACLC_BMI_OPTIN: + { + int old_pool = store_pool; + store_pool = POOL_PERM; + bmi_current_optin = string_copy(arg); + store_pool = old_pool; + } + break; +#endif + case ACLC_CONDITION: if (Ustrspn(arg, "0123456789") == Ustrlen(arg)) /* Digits, or empty */ rc = (Uatoi(arg) == 0)? FAIL : OK; @@ -1375,7 +1605,7 @@ for (; cb != NULL; cb = cb->next) case ACLC_CONTROL: control_type = decode_control(arg, &p, where, log_msgptr); - /* Check this control makes sense at this time */ + /* Check if this control makes sense at this time */ if ((control_forbids[control_type] & (1 << where)) != 0) { @@ -1386,6 +1616,12 @@ for (; cb != NULL; cb = cb->next) switch(control_type) { +#ifdef EXPERIMENTAL_BRIGHTMAIL + case CONTROL_BMI_RUN: + bmi_run = 1; + break; +#endif + case CONTROL_ERROR: return ERROR; @@ -1405,10 +1641,34 @@ for (; cb != NULL; cb = cb->next) smtp_enforce_sync = FALSE; break; +#ifdef WITH_CONTENT_SCAN + case CONTROL_NO_MBOX_UNSPOOL: + no_mbox_unspool = TRUE; + break; +#endif + case CONTROL_NO_MULTILINE: no_multiline_responses = TRUE; break; +#ifdef WITH_CONTENT_SCAN + case CONTROL_FAKEREJECT: + fake_reject = TRUE; + if (*p == '/') + { + uschar *pp = p + 1; + while (*pp != 0) pp++; + fake_reject_text = expand_string(string_copyn(p+1, pp-p)); + p = pp; + } + else + { + /* Explicitly reset to default string */ + fake_reject_text = US"Your message has been rejected but is being kept for evaluation.\nIf it was a legit message, it may still be delivered to the target recipient(s)."; + } + break; +#endif + case CONTROL_FREEZE: deliver_freeze = TRUE; deliver_frozen_at = time(NULL); @@ -1446,6 +1706,12 @@ for (; cb != NULL; cb = cb->next) } break; +#ifdef WITH_CONTENT_SCAN + case ACLC_DECODE: + rc = mime_decode(&arg); + break; +#endif + case ACLC_DELAY: { int delay = readconf_readtime(arg, 0, FALSE); @@ -1469,6 +1735,12 @@ for (; cb != NULL; cb = cb->next) } break; +#ifdef WITH_OLD_DEMIME + case ACLC_DEMIME: + rc = demime(&arg); + break; +#endif + case ACLC_DNSLISTS: rc = verify_check_dnsbl(&arg); break; @@ -1548,12 +1820,42 @@ for (; cb != NULL; cb = cb->next) log_write(0, logbits, "%s", string_printing(s)); } break; + +#ifdef WITH_CONTENT_SCAN + case ACLC_MALWARE: + { + /* Seperate the regular expression and any optional parameters. */ + uschar *ss = string_nextinlist(&arg, &sep, big_buffer, big_buffer_size); + /* Run the malware backend. */ + rc = malware(&ss); + /* Modify return code based upon the existance of options. */ + while ((ss = string_nextinlist(&arg, &sep, big_buffer, big_buffer_size)) + != NULL) { + if (strcmpic(ss, US"defer_ok") == 0 && rc == DEFER) + { + /* FAIL so that the message is passed to the next ACL */ + rc = FAIL; + } + } + } + break; + + case ACLC_MIME_REGEX: + rc = mime_regex(&arg); + break; +#endif case ACLC_RECIPIENTS: rc = match_address_list(addr->address, TRUE, TRUE, &arg, NULL, -1, 0, &recipient_data); break; +#ifdef WITH_CONTENT_SCAN + case ACLC_REGEX: + rc = regex(&arg); + break; +#endif + case ACLC_SENDER_DOMAINS: { uschar *sdomain; @@ -1580,6 +1882,32 @@ for (; cb != NULL; cb = cb->next) } break; +#ifdef WITH_CONTENT_SCAN + case ACLC_SPAM: + { + /* Seperate the regular expression and any optional parameters. */ + uschar *ss = string_nextinlist(&arg, &sep, big_buffer, big_buffer_size); + /* Run the spam backend. */ + rc = spam(&ss); + /* Modify return code based upon the existance of options. */ + while ((ss = string_nextinlist(&arg, &sep, big_buffer, big_buffer_size)) + != NULL) { + if (strcmpic(ss, US"defer_ok") == 0 && rc == DEFER) + { + /* FAIL so that the message is passed to the next ACL */ + rc = FAIL; + } + } + } + break; +#endif + +#ifdef EXPERIMENTAL_SPF + case ACLC_SPF: + rc = spf_process(&arg, sender_address); + break; +#endif + /* If the verb is WARN, discard any user message from verification, because such messages are SMTP responses, not header additions. The latter come only from explicit "message" modifiers. */ diff --git a/src/src/bmi_spam.c b/src/src/bmi_spam.c new file mode 100644 index 000000000..bbb9a512a --- /dev/null +++ b/src/src/bmi_spam.c @@ -0,0 +1,472 @@ +/* $Cambridge: exim/src/src/bmi_spam.c,v 1.2 2004/12/16 15:11:47 tom Exp $ */ + +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Code for calling Brightmail AntiSpam. + Copyright (c) Tom Kistner 2004 + License: GPL */ + +#include "exim.h" +#ifdef EXPERIMENTAL_BRIGHTMAIL + +#include "bmi_spam.h" + +uschar *bmi_current_optin = NULL; + +uschar *bmi_process_message(header_line *header_list, int data_fd) { + BmiSystem *system = NULL; + BmiMessage *message = NULL; + BmiError err; + BmiErrorLocation err_loc; + BmiErrorType err_type; + const BmiVerdict *verdict = NULL; + FILE *data_file; + uschar data_buffer[4096]; + uschar localhost[] = "127.0.0.1"; + uschar *host_address; + uschar *verdicts = NULL; + int i,j; + + err = bmiInitSystem(BMI_VERSION, (char *)bmi_config_file, &system); + if (bmiErrorIsFatal(err) == BMI_TRUE) { + err_loc = bmiErrorGetLocation(err); + err_type = bmiErrorGetType(err); + log_write(0, LOG_PANIC, + "bmi error [loc %d type %d]: could not initialize Brightmail system.", (int)err_loc, (int)err_type); + return NULL; + } + + err = bmiInitMessage(system, &message); + if (bmiErrorIsFatal(err) == BMI_TRUE) { + err_loc = bmiErrorGetLocation(err); + err_type = bmiErrorGetType(err); + log_write(0, LOG_PANIC, + "bmi error [loc %d type %d]: could not initialize Brightmail message.", (int)err_loc, (int)err_type); + bmiFreeSystem(system); + return NULL; + } + + /* Send IP address of sending host */ + if (sender_host_address == NULL) + host_address = localhost; + else + host_address = sender_host_address; + err = bmiProcessConnection((char *)host_address, message); + if (bmiErrorIsFatal(err) == BMI_TRUE) { + err_loc = bmiErrorGetLocation(err); + err_type = bmiErrorGetType(err); + log_write(0, LOG_PANIC, + "bmi error [loc %d type %d]: bmiProcessConnection() failed (IP %s).", (int)err_loc, (int)err_type, (char *)host_address); + bmiFreeMessage(message); + bmiFreeSystem(system); + return NULL; + }; + + /* Send envelope sender address */ + err = bmiProcessFROM((char *)sender_address, message); + if (bmiErrorIsFatal(err) == BMI_TRUE) { + err_loc = bmiErrorGetLocation(err); + err_type = bmiErrorGetType(err); + log_write(0, LOG_PANIC, + "bmi error [loc %d type %d]: bmiProcessFROM() failed (address %s).", (int)err_loc, (int)err_type, (char *)sender_address); + bmiFreeMessage(message); + bmiFreeSystem(system); + return NULL; + }; + + /* Send envelope recipients */ + for(i=0;ibmi_optin != NULL) && (Ustrlen(r->bmi_optin) > 1)) { + debug_printf("passing bmiOptin string: %s\n", r->bmi_optin); + bmiOptinInit(&optin); + err = bmiOptinMset(optin, r->bmi_optin, ':'); + if (bmiErrorIsFatal(err) == BMI_TRUE) { + log_write(0, LOG_PANIC|LOG_MAIN, + "bmi warning: [loc %d type %d]: bmiOptinMSet() failed (address '%s', string '%s').", (int)err_loc, (int)err_type, (char *)r->address, (char *)r->bmi_optin); + if (optin != NULL) + bmiOptinFree(optin); + optin = NULL; + }; + }; + + err = bmiAccumulateTO((char *)r->address, optin, message); + + if (optin != NULL) + bmiOptinFree(optin); + + if (bmiErrorIsFatal(err) == BMI_TRUE) { + err_loc = bmiErrorGetLocation(err); + err_type = bmiErrorGetType(err); + log_write(0, LOG_PANIC, + "bmi error [loc %d type %d]: bmiAccumulateTO() failed (address %s).", (int)err_loc, (int)err_type, (char *)r->address); + bmiFreeMessage(message); + bmiFreeSystem(system); + return NULL; + }; + }; + err = bmiEndTO(message); + if (bmiErrorIsFatal(err) == BMI_TRUE) { + err_loc = bmiErrorGetLocation(err); + err_type = bmiErrorGetType(err); + log_write(0, LOG_PANIC, + "bmi error [loc %d type %d]: bmiEndTO() failed.", (int)err_loc, (int)err_type); + bmiFreeMessage(message); + bmiFreeSystem(system); + return NULL; + }; + + /* Send message headers */ + while (header_list != NULL) { + /* skip deleted headers */ + if (header_list->type == '*') { + header_list = header_list->next; + continue; + }; + err = bmiAccumulateHeaders((const char *)header_list->text, header_list->slen, message); + if (bmiErrorIsFatal(err) == BMI_TRUE) { + err_loc = bmiErrorGetLocation(err); + err_type = bmiErrorGetType(err); + log_write(0, LOG_PANIC, + "bmi error [loc %d type %d]: bmiAccumulateHeaders() failed.", (int)err_loc, (int)err_type); + bmiFreeMessage(message); + bmiFreeSystem(system); + return NULL; + }; + header_list = header_list->next; + }; + err = bmiEndHeaders(message); + if (bmiErrorIsFatal(err) == BMI_TRUE) { + err_loc = bmiErrorGetLocation(err); + err_type = bmiErrorGetType(err); + log_write(0, LOG_PANIC, + "bmi error [loc %d type %d]: bmiEndHeaders() failed.", (int)err_loc, (int)err_type); + bmiFreeMessage(message); + bmiFreeSystem(system); + return NULL; + }; + + /* Send body */ + data_file = fdopen(data_fd,"r"); + do { + j = fread(data_buffer, 1, sizeof(data_buffer), data_file); + if (j > 0) { + err = bmiAccumulateBody((const char *)data_buffer, j, message); + if (bmiErrorIsFatal(err) == BMI_TRUE) { + err_loc = bmiErrorGetLocation(err); + err_type = bmiErrorGetType(err); + log_write(0, LOG_PANIC, + "bmi error [loc %d type %d]: bmiAccumulateBody() failed.", (int)err_loc, (int)err_type); + bmiFreeMessage(message); + bmiFreeSystem(system); + return NULL; + }; + }; + } while (j > 0); + err = bmiEndBody(message); + if (bmiErrorIsFatal(err) == BMI_TRUE) { + err_loc = bmiErrorGetLocation(err); + err_type = bmiErrorGetType(err); + log_write(0, LOG_PANIC, + "bmi error [loc %d type %d]: bmiEndBody() failed.", (int)err_loc, (int)err_type); + bmiFreeMessage(message); + bmiFreeSystem(system); + return NULL; + }; + + + /* End message */ + err = bmiEndMessage(message); + if (bmiErrorIsFatal(err) == BMI_TRUE) { + err_loc = bmiErrorGetLocation(err); + err_type = bmiErrorGetType(err); + log_write(0, LOG_PANIC, + "bmi error [loc %d type %d]: bmiEndMessage() failed.", (int)err_loc, (int)err_type); + bmiFreeMessage(message); + bmiFreeSystem(system); + return NULL; + }; + + /* get store for the verdict string */ + verdicts = store_get(1); + *verdicts = '\0'; + + for ( err = bmiAccessFirstVerdict(message, &verdict); + verdict != NULL; + err = bmiAccessNextVerdict(message, verdict, &verdict) ) { + char *verdict_str; + + err = bmiCreateStrFromVerdict(verdict,&verdict_str); + if (!store_extend(verdicts, Ustrlen(verdicts)+1, Ustrlen(verdicts)+1+strlen(verdict_str)+1)) { + /* can't allocate more store */ + return NULL; + }; + if (*verdicts != '\0') + Ustrcat(verdicts, US ":"); + Ustrcat(verdicts, US verdict_str); + bmiFreeStr(verdict_str); + }; + + DEBUG(D_receive) debug_printf("bmi verdicts: %s\n", verdicts); + + if (Ustrlen(verdicts) == 0) + return NULL; + else + return verdicts; +} + + +int bmi_get_delivery_status(uschar *base64_verdict) { + BmiError err; + BmiErrorLocation err_loc; + BmiErrorType err_type; + BmiVerdict *verdict = NULL; + int rc = 1; /* deliver by default */ + + /* always deliver when there is no verdict */ + if (base64_verdict == NULL) + return 1; + + /* create verdict from base64 string */ + err = bmiCreateVerdictFromStr(CS base64_verdict, &verdict); + if (bmiErrorIsFatal(err) == BMI_TRUE) { + err_loc = bmiErrorGetLocation(err); + err_type = bmiErrorGetType(err); + log_write(0, LOG_PANIC, + "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict); + return 1; + }; + + err = bmiVerdictError(verdict); + if (bmiErrorIsFatal(err) == BMI_TRUE) { + /* deliver normally due to error */ + rc = 1; + } + else if (bmiVerdictDestinationIsDefault(verdict) == BMI_TRUE) { + /* deliver normally */ + rc = 1; + } + else if (bmiVerdictAccessDestination(verdict) == NULL) { + /* do not deliver */ + rc = 0; + } + else { + /* deliver to alternate location */ + rc = 1; + }; + + bmiFreeVerdict(verdict); + return rc; +} + + +uschar *bmi_get_alt_location(uschar *base64_verdict) { + BmiError err; + BmiErrorLocation err_loc; + BmiErrorType err_type; + BmiVerdict *verdict = NULL; + uschar *rc = NULL; + + /* always deliver when there is no verdict */ + if (base64_verdict == NULL) + return NULL; + + /* create verdict from base64 string */ + err = bmiCreateVerdictFromStr(CS base64_verdict, &verdict); + if (bmiErrorIsFatal(err) == BMI_TRUE) { + err_loc = bmiErrorGetLocation(err); + err_type = bmiErrorGetType(err); + log_write(0, LOG_PANIC, + "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict); + return NULL; + }; + + err = bmiVerdictError(verdict); + if (bmiErrorIsFatal(err) == BMI_TRUE) { + /* deliver normally due to error */ + rc = NULL; + } + else if (bmiVerdictDestinationIsDefault(verdict) == BMI_TRUE) { + /* deliver normally */ + rc = NULL; + } + else if (bmiVerdictAccessDestination(verdict) == NULL) { + /* do not deliver */ + rc = NULL; + } + else { + /* deliver to alternate location */ + rc = store_get(strlen(bmiVerdictAccessDestination(verdict))+1); + Ustrcpy(rc, bmiVerdictAccessDestination(verdict)); + rc[strlen(bmiVerdictAccessDestination(verdict))] = '\0'; + }; + + bmiFreeVerdict(verdict); + return rc; +} + +uschar *bmi_get_base64_verdict(uschar *bmi_local_part, uschar *bmi_domain) { + BmiError err; + BmiErrorLocation err_loc; + BmiErrorType err_type; + BmiVerdict *verdict = NULL; + const BmiRecipient *recipient = NULL; + const char *verdict_str = NULL; + uschar *verdict_ptr; + uschar *verdict_buffer = NULL; + int sep = 0; + + /* return nothing if there are no verdicts available */ + if (bmi_verdicts == NULL) + return NULL; + + /* allocate room for the b64 verdict string */ + verdict_buffer = store_get(Ustrlen(bmi_verdicts)+1); + + /* loop through verdicts */ + verdict_ptr = bmi_verdicts; + while ((verdict_str = (const char *)string_nextinlist(&verdict_ptr, &sep, + verdict_buffer, + Ustrlen(bmi_verdicts)+1)) != NULL) { + + /* create verdict from base64 string */ + err = bmiCreateVerdictFromStr(verdict_str, &verdict); + if (bmiErrorIsFatal(err) == BMI_TRUE) { + err_loc = bmiErrorGetLocation(err); + err_type = bmiErrorGetType(err); + log_write(0, LOG_PANIC, + "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, verdict_str); + return NULL; + }; + + /* loop through rcpts for this verdict */ + for ( recipient = bmiVerdictAccessFirstRecipient(verdict); + recipient != NULL; + recipient = bmiVerdictAccessNextRecipient(verdict, recipient)) { + uschar *rcpt_local_part; + uschar *rcpt_domain; + + /* compare address against our subject */ + rcpt_local_part = (unsigned char *)bmiRecipientAccessAddress(recipient); + rcpt_domain = Ustrchr(rcpt_local_part,'@'); + if (rcpt_domain == NULL) { + rcpt_domain = US""; + } + else { + *rcpt_domain = '\0'; + rcpt_domain++; + }; + + if ( (strcmpic(rcpt_local_part, bmi_local_part) == 0) && + (strcmpic(rcpt_domain, bmi_domain) == 0) ) { + /* found verdict */ + bmiFreeVerdict(verdict); + return (uschar *)verdict_str; + }; + }; + + bmiFreeVerdict(verdict); + }; + + return NULL; +} + + +uschar *bmi_get_base64_tracker_verdict(uschar *base64_verdict) { + BmiError err; + BmiErrorLocation err_loc; + BmiErrorType err_type; + BmiVerdict *verdict = NULL; + uschar *rc = NULL; + + /* always deliver when there is no verdict */ + if (base64_verdict == NULL) + return NULL; + + /* create verdict from base64 string */ + err = bmiCreateVerdictFromStr(CS base64_verdict, &verdict); + if (bmiErrorIsFatal(err) == BMI_TRUE) { + err_loc = bmiErrorGetLocation(err); + err_type = bmiErrorGetType(err); + log_write(0, LOG_PANIC, + "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict); + return NULL; + }; + + /* create old tracker string from verdict */ + err = bmiCreateOldStrFromVerdict(verdict, &rc); + if (bmiErrorIsFatal(err) == BMI_TRUE) { + err_loc = bmiErrorGetLocation(err); + err_type = bmiErrorGetType(err); + log_write(0, LOG_PANIC, + "bmi error [loc %d type %d]: bmiCreateOldStrFromVerdict() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict); + return NULL; + }; + + bmiFreeVerdict(verdict); + return rc; +} + + +int bmi_check_rule(uschar *base64_verdict, uschar *option_list) { + BmiError err; + BmiErrorLocation err_loc; + BmiErrorType err_type; + BmiVerdict *verdict = NULL; + int rc = 0; + uschar *rule_num; + uschar *rule_ptr; + uschar rule_buffer[32]; + int sep = 0; + + + /* no verdict -> no rule fired */ + if (base64_verdict == NULL) + return 0; + + /* create verdict from base64 string */ + err = bmiCreateVerdictFromStr(CS base64_verdict, &verdict); + if (bmiErrorIsFatal(err) == BMI_TRUE) { + err_loc = bmiErrorGetLocation(err); + err_type = bmiErrorGetType(err); + log_write(0, LOG_PANIC, + "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict); + return 0; + }; + + err = bmiVerdictError(verdict); + if (bmiErrorIsFatal(err) == BMI_TRUE) { + /* error -> no rule fired */ + bmiFreeVerdict(verdict); + return 0; + } + + /* loop through numbers */ + rule_ptr = option_list; + while ((rule_num = string_nextinlist(&rule_ptr, &sep, + rule_buffer, 32)) != NULL) { + int rule_int = -1; + + /* try to translate to int */ + sscanf(rule_num, "%d", &rule_int); + if (rule_int > 0) { + debug_printf("checking rule #%d\n", rule_int); + /* check if rule fired on the message */ + if (bmiVerdictRuleFired(verdict, rule_int) == BMI_TRUE) { + debug_printf("rule #%d fired\n", rule_int); + rc = 1; + break; + }; + }; + }; + + bmiFreeVerdict(verdict); + return rc; +}; + +#endif diff --git a/src/src/bmi_spam.h b/src/src/bmi_spam.h new file mode 100644 index 000000000..994380fca --- /dev/null +++ b/src/src/bmi_spam.h @@ -0,0 +1,24 @@ +/* $Cambridge: exim/src/src/bmi_spam.h,v 1.2 2004/12/16 15:11:47 tom Exp $ */ + +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Code for calling Brightmail AntiSpam. + Copyright (c) Tom Kistner 2004 + License: GPL */ + +#ifdef EXPERIMENTAL_BRIGHTMAIL + +#include + +extern uschar *bmi_process_message(header_line *, int); +extern uschar *bmi_get_base64_verdict(uschar *, uschar *); +extern uschar *bmi_get_base64_tracker_verdict(uschar *); +extern int bmi_get_delivery_status(uschar *); +extern uschar *bmi_get_alt_location(uschar *); +extern int bmi_check_rule(uschar *,uschar *); + +extern uschar *bmi_current_optin; + +#endif diff --git a/src/src/config.h.defaults b/src/src/config.h.defaults index e31cffb10..04e4cc130 100644 --- a/src/src/config.h.defaults +++ b/src/src/config.h.defaults @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/config.h.defaults,v 1.2 2004/10/18 09:16:57 ph10 Exp $ */ +/* $Cambridge: exim/src/src/config.h.defaults,v 1.3 2004/12/16 15:11:47 tom Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -53,7 +53,7 @@ in config.h unless some value is defined in Local/Makefile. */ #define HAVE_CRYPT16 #define HAVE_SA_LEN #define HEADERS_CHARSET "ISO-8859-1" -#define HEADER_ADD_BUFFER_SIZE 8192 +#define HEADER_ADD_BUFFER_SIZE (8192 * 4) #define HEADER_MAXSIZE (1024*1024) #define INPUT_DIRECTORY_MODE 0750 @@ -107,7 +107,7 @@ in config.h unless some value is defined in Local/Makefile. */ #define SPOOL_DIRECTORY #define SPOOL_DIRECTORY_MODE 0750 #define SPOOL_MODE 0640 -#define STRING_SPRINTF_BUFFER_SIZE 8192 +#define STRING_SPRINTF_BUFFER_SIZE (8192 * 4) #define SUPPORT_A6 #define SUPPORT_CRYPTEQ @@ -138,6 +138,14 @@ in config.h unless some value is defined in Local/Makefile. */ #define USE_TCP_WRAPPERS #define USE_TDB +#define WITH_CONTENT_SCAN +#define WITH_OLD_DEMIME + +/* EXPERIMENTAL features */ +#define EXPERIMENTAL_SPF +#define EXPERIMENTAL_SRS +#define EXPERIMENTAL_BRIGHTMAIL + /* Things that are not routinely changed but are nevertheless configurable just in case. */ diff --git a/src/src/deliver.c b/src/src/deliver.c index b2e9ea58c..3dffe78fe 100644 --- a/src/src/deliver.c +++ b/src/src/deliver.c @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/deliver.c,v 1.3 2004/11/24 14:38:13 ph10 Exp $ */ +/* $Cambridge: exim/src/src/deliver.c,v 1.4 2004/12/16 15:11:47 tom Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -156,6 +156,13 @@ deliver_localpart_data = addr->p.localpart_data; deliver_domain = addr->domain; self_hostname = addr->self_hostname; +#ifdef EXPERIMENTAL_BRIGHTMAIL +bmi_deliver = 1; /* deliver by default */ +bmi_alt_location = NULL; +bmi_base64_verdict = NULL; +bmi_base64_tracker_verdict = NULL; +#endif + /* If there's only one address we can set everything. */ if (addr->next == NULL) @@ -205,6 +212,19 @@ if (addr->next == NULL) deliver_localpart_suffix = addr->parent->suffix; } } + +#ifdef EXPERIMENTAL_BRIGHTMAIL + /* Set expansion variables related to Brightmail AntiSpam */ + bmi_base64_verdict = bmi_get_base64_verdict(deliver_localpart_orig, deliver_domain_orig); + bmi_base64_tracker_verdict = bmi_get_base64_tracker_verdict(bmi_base64_verdict); + /* get message delivery status (0 - don't deliver | 1 - deliver) */ + bmi_deliver = bmi_get_delivery_status(bmi_base64_verdict); + /* if message is to be delivered, get eventual alternate location */ + if (bmi_deliver == 1) { + bmi_alt_location = bmi_get_alt_location(bmi_base64_verdict); + }; +#endif + } /* For multiple addresses, don't set local part, and leave the domain and diff --git a/src/src/demime.c b/src/src/demime.c new file mode 100644 index 000000000..724e47561 --- /dev/null +++ b/src/src/demime.c @@ -0,0 +1,1245 @@ +/* $Cambridge: exim/src/src/demime.c,v 1.2 2004/12/16 15:11:47 tom Exp $ */ + +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) Tom Kistner 2003-???? */ +/* License: GPL */ + +/* Code for unpacking MIME containers. Called from acl.c. */ + +#include "exim.h" +#ifdef WITH_OLD_DEMIME + +#include "demime.h" + +uschar demime_reason_buffer[1024]; +struct file_extension *file_extensions = NULL; + +int demime(uschar **listptr) { + int sep = 0; + uschar *list = *listptr; + uschar *option; + uschar option_buffer[64]; + unsigned long long mbox_size; + FILE *mbox_file; + uschar defer_error_buffer[1024]; + int demime_rc = 0; + + /* reset found_extension variable */ + found_extension = NULL; + + /* try to find 1st option */ + if ((option = string_nextinlist(&list, &sep, + option_buffer, + sizeof(option_buffer))) != NULL) { + + /* parse 1st option */ + if ( (Ustrcmp(option,"false") == 0) || (Ustrcmp(option,"0") == 0) ) { + /* explicitly no demimeing */ + return FAIL; + }; + } + else { + /* no options -> no demimeing */ + return FAIL; + }; + + /* make sure the eml mbox file is spooled up */ + mbox_file = spool_mbox(&mbox_size); + + if (mbox_file == NULL) { + /* error while spooling */ + log_write(0, LOG_MAIN|LOG_PANIC, + "demime acl condition: error while creating mbox spool file"); + return DEFER; + }; + + /* call demimer if not already done earlier */ + if (!demime_ok) + demime_rc = mime_demux(mbox_file, defer_error_buffer); + + fclose(mbox_file); + + if (demime_rc == DEFER) { + /* temporary failure (DEFER => DEFER) */ + log_write(0, LOG_MAIN, + "demime acl condition: %s", defer_error_buffer); + return DEFER; + }; + + /* set demime_ok to avoid unpacking again */ + demime_ok = 1; + + /* check for file extensions, if there */ + while (option != NULL) { + struct file_extension *this_extension = file_extensions; + + /* Look for the wildcard. If it is found, we always return true. + The user must then use a custom condition to evaluate demime_errorlevel */ + if (Ustrcmp(option,"*") == 0) { + found_extension = NULL; + return OK; + }; + + /* loop thru extension list */ + while (this_extension != NULL) { + if (strcmpic(option, this_extension->file_extension_string) == 0) { + /* found one */ + found_extension = this_extension->file_extension_string; + return OK; + }; + this_extension = this_extension->next; + }; + + /* grab next extension from option list */ + option = string_nextinlist(&list, &sep, + option_buffer, + sizeof(option_buffer)); + }; + + /* nothing found */ + return FAIL; +} + + +/************************************************* +* small hex_str -> integer conversion function * +*************************************************/ + +/* needed for quoted-printable +*/ + +unsigned int mime_hstr_i(uschar *cptr) { + unsigned int i, j = 0; + + while (cptr && *cptr && isxdigit(*cptr)) { + i = *cptr++ - '0'; + if (9 < i) i -= 7; + j <<= 4; + j |= (i & 0x0f); + } + + return(j); +} + + +/************************************************* +* decode quoted-printable chars * +*************************************************/ + +/* gets called when we hit a = + returns: new pointer position + result code in c: + -2 - decode error + -1 - soft line break, no char + 0-255 - char to write +*/ + +uschar *mime_decode_qp(uschar *qp_p,int *c) { + uschar hex[] = {0,0,0}; + int nan = 0; + uschar *initial_pos = qp_p; + + /* advance one char */ + qp_p++; + + REPEAT_FIRST: + if ( (*qp_p == '\t') || (*qp_p == ' ') || (*qp_p == '\r') ) { + /* tab or whitespace may follow + just ignore it, but remember + that this is not a valid hex + encoding any more */ + nan = 1; + qp_p++; + goto REPEAT_FIRST; + } + else if ( (('0' <= *qp_p) && (*qp_p <= '9')) || (('A' <= *qp_p) && (*qp_p <= 'F')) || (('a' <= *qp_p) && (*qp_p <= 'f')) ) { + /* this is a valid hex char, if nan is unset */ + if (nan) { + /* this is illegal */ + *c = -2; + return initial_pos; + } + else { + hex[0] = *qp_p; + qp_p++; + }; + } + else if (*qp_p == '\n') { + /* hit soft line break already, continue */ + *c = -1; + return qp_p; + } + else { + /* illegal char here */ + *c = -2; + return initial_pos; + }; + + if ( (('0' <= *qp_p) && (*qp_p <= '9')) || (('A' <= *qp_p) && (*qp_p <= 'F')) || (('a' <= *qp_p) && (*qp_p <= 'f')) ) { + if (hex[0] > 0) { + hex[1] = *qp_p; + /* do hex conversion */ + *c = mime_hstr_i(hex); + qp_p++; + return qp_p; + } + else { + /* huh ? */ + *c = -2; + return initial_pos; + }; + } + else { + /* illegal char */ + *c = -2; + return initial_pos; + }; + +} + + +/************************************************* +* open new dump file * +*************************************************/ + +/* open new dump file + returns: -2 soft error + or file #, FILE * in f +*/ + +int mime_get_dump_file(uschar *extension, FILE **f, uschar *info) { + uschar file_name[1024]; + int result; + unsigned int file_nr; + uschar default_extension[] = ".com"; + uschar *p; + + if (extension == NULL) + extension = default_extension; + + /* scan the proposed extension. + if it is longer than 4 chars, or + contains exotic chars, use the default extension */ + +/* if (Ustrlen(extension) > 4) { + extension = default_extension; + }; +*/ + + p = extension+1; + + while (*p != 0) { + *p = (uschar)tolower((uschar)*p); + if ( (*p < 97) || (*p > 122) ) { + extension = default_extension; + break; + }; + p++; + }; + + /* find a new file to write to */ + file_nr = 0; + do { + struct stat mystat; + + snprintf(CS file_name,1024,"%s/scan/%s/%s-%05u%s",spool_directory,message_id,message_id,file_nr,extension); + file_nr++; + if (file_nr >= MIME_SANITY_MAX_DUMP_FILES) { + /* max parts reached */ + mime_trigger_error(MIME_ERRORLEVEL_TOO_MANY_PARTS); + break; + }; + result = stat(CS file_name,&mystat); + } + while(result != -1); + + *f = fopen(CS file_name,"w+"); + if (*f == NULL) { + /* cannot open new dump file, disk full ? -> soft error */ + snprintf(CS info, 1024,"unable to open dump file"); + return -2; + }; + + return file_nr; +} + + +/************************************************* +* Find a string in a mime header * +*************************************************/ + +/* Find a string in a mime header, and optionally fill in + the value associated with it into *value + + returns: 0 - nothing found + 1 - found param + 2 - found param + value +*/ + +int mime_header_find(uschar *header, uschar *param, uschar **value) { + uschar *needle; + + needle = strstric(header,param,FALSE); + if (needle != NULL) { + if (value != NULL) { + needle += Ustrlen(param); + if (*needle == '=') { + uschar *value_start; + uschar *value_end; + + value_start = needle + 1; + value_end = strstric(value_start,US";",FALSE); + if (value_end != NULL) { + /* allocate mem for value */ + *value = (uschar *)malloc((value_end - value_start)+1); + if (*value == NULL) + return 0; + + Ustrncpy(*value,value_start,(value_end - value_start)); + (*value)[(value_end - value_start)] = '\0'; + return 2; + }; + }; + }; + return 1; + }; + return 0; +} + + +/************************************************* +* Read a line of MIME input * +*************************************************/ +/* returns status code, one of + MIME_READ_LINE_EOF 0 + MIME_READ_LINE_OK 1 + MIME_READ_LINE_OVERFLOW 2 + + In header mode, the line will be "cooked". +*/ + +int mime_read_line(FILE *f, int mime_demux_mode, uschar *buffer, long *num_copied) { + int c = EOF; + int done = 0; + int header_value_mode = 0; + int header_open_brackets = 0; + + *num_copied = 0; + + while(!done) { + + c = fgetc(f); + if (c == EOF) break; + + /* --------- header mode -------------- */ + if (mime_demux_mode == MIME_DEMUX_MODE_MIME_HEADERS) { + + /* always skip CRs */ + if (c == '\r') continue; + + if (c == '\n') { + if ((*num_copied) > 0) { + /* look if next char is '\t' or ' ' */ + c = fgetc(f); + if (c == EOF) break; + if ( (c == '\t') || (c == ' ') ) continue; + ungetc(c,f); + }; + /* end of the header, terminate with ';' */ + c = ';'; + done = 1; + }; + + /* skip control characters */ + if (c < 32) continue; + + /* skip whitespace + tabs */ + if ( (c == ' ') || (c == '\t') ) + continue; + + if (header_value_mode) { + /* --------- value mode ----------- */ + /* skip quotes */ + if (c == '"') continue; + + /* leave value mode on ';' */ + if (c == ';') { + header_value_mode = 0; + }; + /* -------------------------------- */ + } + else { + /* -------- non-value mode -------- */ + if (c == '\\') { + /* quote next char. can be used + to escape brackets. */ + c = fgetc(f); + if (c == EOF) break; + } + else if (c == '(') { + header_open_brackets++; + continue; + } + else if ((c == ')') && header_open_brackets) { + header_open_brackets--; + continue; + } + else if ( (c == '=') && !header_open_brackets ) { + /* enter value mode */ + header_value_mode = 1; + }; + + /* skip chars while we are in a comment */ + if (header_open_brackets > 0) + continue; + /* -------------------------------- */ + }; + } + /* ------------------------------------ */ + else { + /* ----------- non-header mode -------- */ + /* break on '\n' */ + if (c == '\n') + done = 1; + /* ------------------------------------ */ + }; + + /* copy the char to the buffer */ + buffer[*num_copied] = (uschar)c; + /* raise counter */ + (*num_copied)++; + + /* break if buffer is full */ + if (*num_copied > MIME_SANITY_MAX_LINE_LENGTH-1) { + done = 1; + }; + } + + /* 0-terminate */ + buffer[*num_copied] = '\0'; + + if (*num_copied > MIME_SANITY_MAX_LINE_LENGTH-1) + return MIME_READ_LINE_OVERFLOW; + else + if (c == EOF) + return MIME_READ_LINE_EOF; + else + return MIME_READ_LINE_OK; +} + + +/************************************************* +* Check for a MIME boundary * +*************************************************/ + +/* returns: 0 - no boundary found + 1 - start boundary found + 2 - end boundary found +*/ + +int mime_check_boundary(uschar *line, struct boundary *boundaries) { + struct boundary *thisboundary = boundaries; + uschar workbuf[MIME_SANITY_MAX_LINE_LENGTH+1]; + unsigned int i,j=0; + + /* check for '--' first */ + if (Ustrncmp(line,"--",2) == 0) { + + /* strip tab and space */ + for (i = 2; i < Ustrlen(line); i++) { + if ((line[i] != ' ') && (line[i] != '\t')) { + workbuf[j] = line[i]; + j++; + }; + }; + workbuf[j+1]='\0'; + + while(thisboundary != NULL) { + if (Ustrncmp(workbuf,thisboundary->boundary_string,Ustrlen(thisboundary->boundary_string)) == 0) { + if (Ustrncmp(&workbuf[Ustrlen(thisboundary->boundary_string)],"--",2) == 0) { + /* final boundary found */ + return 2; + }; + return 1; + }; + thisboundary = thisboundary->next; + }; + }; + + return 0; +} + + +/************************************************* +* Check for start of a UUENCODE block * +*************************************************/ + +/* returns 0 for no hit, + >0 for hit +*/ + +int mime_check_uu_start(uschar *line, uschar *uu_file_extension, int *has_tnef) { + + if ( (strncmpic(line,US"begin ",6) == 0)) { + uschar *uu_filename = &line[6]; + + /* skip perms, if present */ + Ustrtoul(&line[6],&uu_filename,10); + + /* advance one char */ + uu_filename++; + + /* This should be the filename. + Check if winmail.dat is present, + which indicates TNEF. */ + if (strncmpic(uu_filename,US"winmail.dat",11) == 0) { + *has_tnef = 1; + }; + + /* reverse to dot if present, + copy up to 4 chars for the extension */ + if (Ustrrchr(uu_filename,'.') != NULL) + uu_filename = Ustrrchr(uu_filename,'.'); + + return sscanf(CS uu_filename, "%4[.0-9A-Za-z]",CS uu_file_extension); + } + else { + /* nothing found */ + return 0; + }; +} + + +/************************************************* +* Decode a uu line * +*************************************************/ + +/* returns number of decoded bytes + -2 for soft errors +*/ + +int warned_about_uudec_line_sanity_1 = 0; +int warned_about_uudec_line_sanity_2 = 0; +long uu_decode_line(uschar *line, uschar **data, long line_len, uschar *info) { + uschar *p; + long num_decoded = 0; + uschar tmp_c; + uschar *work; + int uu_decoded_line_len, uu_encoded_line_len; + + /* allocate memory for data and work buffer */ + *data = (uschar *)malloc(line_len); + if (*data == NULL) { + snprintf(CS info, 1024,"unable to allocate %lu bytes",line_len); + return -2; + }; + + work = (uschar *)malloc(line_len); + if (work == NULL) { + snprintf(CS info, 1024,"unable to allocate %lu bytes",line_len); + return -2; + }; + + memcpy(work,line,line_len); + + /* First char is line length + This is microsofts way of getting it. Scary. */ + if (work[0] < 32) { + /* ignore this line */ + return 0; + } + else { + uu_decoded_line_len = uudec[work[0]]; + }; + + p = &work[1]; + + while (*p > 32) { + *p = uudec[*p]; + p++; + }; + + uu_encoded_line_len = (p - &work[1]); + p = &work[1]; + + /* check that resulting line length is a multiple of 4 */ + if ( ( uu_encoded_line_len % 4 ) != 0) { + if (!warned_about_uudec_line_sanity_1) { + mime_trigger_error(MIME_ERRORLEVEL_UU_MISALIGNED); + warned_about_uudec_line_sanity_1 = 1; + }; + return -1; + }; + + /* check that the line length matches */ + if ( ( (((uu_encoded_line_len/4)*3)-2) > uu_decoded_line_len ) || (((uu_encoded_line_len/4)*3) < uu_decoded_line_len) ) { + if (!warned_about_uudec_line_sanity_2) { + mime_trigger_error(MIME_ERRORLEVEL_UU_LINE_LENGTH); + warned_about_uudec_line_sanity_2 = 1; + }; + return -1; + }; + + while ( ((p - &work[1]) < uu_encoded_line_len) && (num_decoded < uu_decoded_line_len)) { + + /* byte 0 ---------------------- */ + if ((p - &work[1] + 1) >= uu_encoded_line_len) { + return 0; + } + + (*data)[num_decoded] = *p; + (*data)[num_decoded] <<= 2; + + tmp_c = *(p+1); + tmp_c >>= 4; + (*data)[num_decoded] |= tmp_c; + + num_decoded++; + p++; + + /* byte 1 ---------------------- */ + if ((p - &work[1] + 1) >= uu_encoded_line_len) { + return 0; + } + + (*data)[num_decoded] = *p; + (*data)[num_decoded] <<= 4; + + tmp_c = *(p+1); + tmp_c >>= 2; + (*data)[num_decoded] |= tmp_c; + + num_decoded++; + p++; + + /* byte 2 ---------------------- */ + if ((p - &work[1] + 1) >= uu_encoded_line_len) { + return 0; + } + + (*data)[num_decoded] = *p; + (*data)[num_decoded] <<= 6; + + (*data)[num_decoded] |= *(p+1); + + num_decoded++; + p+=2; + + }; + + return uu_decoded_line_len; +} + + +/************************************************* +* Decode a b64 or qp line * +*************************************************/ + +/* returns number of decoded bytes + -1 for hard errors + -2 for soft errors +*/ + +int warned_about_b64_line_length = 0; +int warned_about_b64_line_sanity = 0; +int warned_about_b64_illegal_char = 0; +int warned_about_qp_line_sanity = 0; +long mime_decode_line(int mime_demux_mode,uschar *line, uschar **data, long max_data_len, uschar *info) { + uschar *p; + long num_decoded = 0; + int offset = 0; + uschar tmp_c; + + /* allocate memory for data */ + *data = (uschar *)malloc(max_data_len); + if (*data == NULL) { + snprintf(CS info, 1024,"unable to allocate %lu bytes",max_data_len); + return -2; + }; + + if (mime_demux_mode == MIME_DEMUX_MODE_BASE64) { + /* ---------------------------------------------- */ + + /* NULL out trailing '\r' and '\n' chars */ + while (Ustrrchr(line,'\r') != NULL) { + *(Ustrrchr(line,'\r')) = '\0'; + }; + while (Ustrrchr(line,'\n') != NULL) { + *(Ustrrchr(line,'\n')) = '\0'; + }; + + /* check maximum base 64 line length */ + if (Ustrlen(line) > MIME_SANITY_MAX_B64_LINE_LENGTH ) { + if (!warned_about_b64_line_length) { + mime_trigger_error(MIME_ERRORLEVEL_B64_LINE_LENGTH); + warned_about_b64_line_length = 1; + }; + }; + + p = line; + offset = 0; + while (*(p+offset) != '\0') { + /* hit illegal char ? */ + if (b64[*(p+offset)] == 128) { + if (!warned_about_b64_illegal_char) { + mime_trigger_error(MIME_ERRORLEVEL_B64_ILLEGAL_CHAR); + warned_about_b64_illegal_char = 1; + }; + offset++; + } + else { + *p = b64[*(p+offset)]; + p++; + }; + }; + *p = 255; + + /* check that resulting line length is a multiple of 4 */ + if ( ( (p - &line[0]) % 4 ) != 0) { + if (!warned_about_b64_line_sanity) { + mime_trigger_error(MIME_ERRORLEVEL_B64_MISALIGNED); + warned_about_b64_line_sanity = 1; + }; + }; + + /* line is translated, start bit shifting */ + p = line; + num_decoded = 0; + + while(*p != 255) { + + /* byte 0 ---------------------- */ + if (*(p+1) == 255) { + break; + } + + (*data)[num_decoded] = *p; + (*data)[num_decoded] <<= 2; + + tmp_c = *(p+1); + tmp_c >>= 4; + (*data)[num_decoded] |= tmp_c; + + num_decoded++; + p++; + + /* byte 1 ---------------------- */ + if (*(p+1) == 255) { + break; + } + + (*data)[num_decoded] = *p; + (*data)[num_decoded] <<= 4; + + tmp_c = *(p+1); + tmp_c >>= 2; + (*data)[num_decoded] |= tmp_c; + + num_decoded++; + p++; + + /* byte 2 ---------------------- */ + if (*(p+1) == 255) { + break; + } + + (*data)[num_decoded] = *p; + (*data)[num_decoded] <<= 6; + + (*data)[num_decoded] |= *(p+1); + + num_decoded++; + p+=2; + + }; + return num_decoded; + /* ---------------------------------------------- */ + } + else if (mime_demux_mode == MIME_DEMUX_MODE_QP) { + /* ---------------------------------------------- */ + p = line; + + while (*p != 0) { + if (*p == '=') { + int decode_qp_result; + + p = mime_decode_qp(p,&decode_qp_result); + + if (decode_qp_result == -2) { + /* Error from decoder. p is unchanged. */ + if (!warned_about_qp_line_sanity) { + mime_trigger_error(MIME_ERRORLEVEL_QP_ILLEGAL_CHAR); + warned_about_qp_line_sanity = 1; + }; + (*data)[num_decoded] = '='; + num_decoded++; + p++; + } + else if (decode_qp_result == -1) { + /* End of the line with soft line break. + Bail out. */ + goto QP_RETURN; + } + else if (decode_qp_result >= 0) { + (*data)[num_decoded] = decode_qp_result; + num_decoded++; + }; + } + else { + (*data)[num_decoded] = *p; + num_decoded++; + p++; + }; + }; + QP_RETURN: + return num_decoded; + /* ---------------------------------------------- */ + }; + + return 0; +} + + + +/************************************************* +* Log demime errors and set mime error level * +*************************************************/ + +/* This sets the global demime_reason expansion +variable and the demime_errorlevel gauge. */ + +void mime_trigger_error(int level, uschar *format, ...) { + char *f; + va_list ap; + + if( (f = malloc(16384+23)) != NULL ) { + /* first log the incident */ + sprintf(f,"demime acl condition: "); + f+=22; + va_start(ap, format); + vsnprintf(f, 16383,(char *)format, ap); + va_end(ap); + f-=22; + log_write(0, LOG_MAIN, f); + /* then copy to demime_reason_buffer if new + level is greater than old level */ + if (level > demime_errorlevel) { + demime_errorlevel = level; + Ustrcpy(demime_reason_buffer, US f); + demime_reason = demime_reason_buffer; + }; + free(f); + }; +} + +/************************************************* +* Demultiplex MIME stream. * +*************************************************/ + +/* We can handle BASE64, QUOTED-PRINTABLE, and UUENCODE. + UUENCODE does not need to have a proper + transfer-encoding header, we detect it with "begin" + + This function will report human parsable errors in + *info. + + returns DEFER -> soft error (see *info) + OK -> EOF hit, all ok +*/ + +int mime_demux(FILE *f, uschar *info) { + int mime_demux_mode = MIME_DEMUX_MODE_MIME_HEADERS; + int uu_mode = MIME_UU_MODE_OFF; + FILE *mime_dump_file = NULL; + FILE *uu_dump_file = NULL; + uschar *line; + int mime_read_line_status = MIME_READ_LINE_OK; + long line_len; + struct boundary *boundaries = NULL; + struct mime_part mime_part_p; + int has_tnef = 0; + int has_rfc822 = 0; + + /* allocate room for our linebuffer */ + line = (uschar *)malloc(MIME_SANITY_MAX_LINE_LENGTH); + if (line == NULL) { + snprintf(CS info, 1024,"unable to allocate %u bytes",MIME_SANITY_MAX_LINE_LENGTH); + return DEFER; + }; + + /* clear MIME header structure */ + memset(&mime_part_p,0,sizeof(mime_part)); + + /* ----------------------- start demux loop --------------------- */ + while (mime_read_line_status == MIME_READ_LINE_OK) { + + /* read a line of input. Depending on the mode we are in, + the returned format will differ. */ + mime_read_line_status = mime_read_line(f,mime_demux_mode,line,&line_len); + + if (mime_read_line_status == MIME_READ_LINE_OVERFLOW) { + mime_trigger_error(MIME_ERRORLEVEL_LONG_LINE); + /* despite the error, continue .. */ + mime_read_line_status = MIME_READ_LINE_OK; + continue; + } + else if (mime_read_line_status == MIME_READ_LINE_EOF) { + break; + }; + + if (mime_demux_mode == MIME_DEMUX_MODE_MIME_HEADERS) { + /* -------------- header mode --------------------- */ + + /* Check for an empty line, which is the end of the headers. + In HEADER mode, the line is returned "cooked", with the + final '\n' replaced by a ';' */ + if (line_len == 1) { + int tmp; + + /* We have reached the end of the headers. Start decoding + with the collected settings. */ + if (mime_part_p.seen_content_transfer_encoding > 1) { + mime_demux_mode = mime_part_p.seen_content_transfer_encoding; + } + else { + /* default to plain mode if no specific encoding type found */ + mime_demux_mode = MIME_DEMUX_MODE_PLAIN; + }; + + /* open new dump file */ + tmp = mime_get_dump_file(mime_part_p.extension, &mime_dump_file, info); + if (tmp < 0) { + return DEFER; + }; + + /* clear out mime_part */ + memset(&mime_part_p,0,sizeof(mime_part)); + } + else { + /* Another header to check for file extensions, + encoding type and boundaries */ + if (strncmpic(US"content-type:",line,Ustrlen("content-type:")) == 0) { + /* ---------------------------- Content-Type header ------------------------------- */ + uschar *value = line; + + /* check for message/partial MIME type and reject it */ + if (mime_header_find(line,US"message/partial",NULL) > 0) + mime_trigger_error(MIME_ERRORLEVEL_MESSAGE_PARTIAL); + + /* check for TNEF content type, remember to unpack TNEF later. */ + if (mime_header_find(line,US"application/ms-tnef",NULL) > 0) + has_tnef = 1; + + /* check for message/rfcxxx attachments */ + if (mime_header_find(line,US"message/rfc822",NULL) > 0) + has_rfc822 = 1; + + /* find the file extension, but do not fill it in + it is already set, since content-disposition has + precedence. */ + if (mime_part_p.extension == NULL) { + if (mime_header_find(line,US"name",&value) == 2) { + if (Ustrlen(value) > MIME_SANITY_MAX_FILENAME) + mime_trigger_error(MIME_ERRORLEVEL_FILENAME_LENGTH); + mime_part_p.extension = value; + mime_part_p.extension = Ustrrchr(value,'.'); + if (mime_part_p.extension == NULL) { + /* file without extension, setting + NULL will use the default extension later */ + mime_part_p.extension = NULL; + } + else { + struct file_extension *this_extension = + (struct file_extension *)malloc(sizeof(file_extension)); + + this_extension->file_extension_string = + (uschar *)malloc(Ustrlen(mime_part_p.extension)+1); + Ustrcpy(this_extension->file_extension_string, + mime_part_p.extension+1); + this_extension->next = file_extensions; + file_extensions = this_extension; + }; + }; + }; + + /* find a boundary and add it to the list, if present */ + value = line; + if (mime_header_find(line,US"boundary",&value) == 2) { + struct boundary *thisboundary; + + if (Ustrlen(value) > MIME_SANITY_MAX_BOUNDARY_LENGTH) { + mime_trigger_error(MIME_ERRORLEVEL_BOUNDARY_LENGTH); + } + else { + thisboundary = (struct boundary*)malloc(sizeof(boundary)); + thisboundary->next = boundaries; + thisboundary->boundary_string = value; + boundaries = thisboundary; + }; + }; + + if (mime_part_p.seen_content_type == 0) { + mime_part_p.seen_content_type = 1; + } + else { + mime_trigger_error(MIME_ERRORLEVEL_DOUBLE_HEADERS); + }; + /* ---------------------------------------------------------------------------- */ + } + else if (strncmpic(US"content-transfer-encoding:",line,Ustrlen("content-transfer-encoding:")) == 0) { + /* ---------------------------- Content-Transfer-Encoding header -------------- */ + + if (mime_part_p.seen_content_transfer_encoding == 0) { + if (mime_header_find(line,US"base64",NULL) > 0) { + mime_part_p.seen_content_transfer_encoding = MIME_DEMUX_MODE_BASE64; + } + else if (mime_header_find(line,US"quoted-printable",NULL) > 0) { + mime_part_p.seen_content_transfer_encoding = MIME_DEMUX_MODE_QP; + } + else { + mime_part_p.seen_content_transfer_encoding = MIME_DEMUX_MODE_PLAIN; + }; + } + else { + mime_trigger_error(MIME_ERRORLEVEL_DOUBLE_HEADERS); + }; + /* ---------------------------------------------------------------------------- */ + } + else if (strncmpic(US"content-disposition:",line,Ustrlen("content-disposition:")) == 0) { + /* ---------------------------- Content-Disposition header -------------------- */ + uschar *value = line; + + if (mime_part_p.seen_content_disposition == 0) { + mime_part_p.seen_content_disposition = 1; + + if (mime_header_find(line,US"filename",&value) == 2) { + if (Ustrlen(value) > MIME_SANITY_MAX_FILENAME) + mime_trigger_error(MIME_ERRORLEVEL_FILENAME_LENGTH); + mime_part_p.extension = value; + mime_part_p.extension = Ustrrchr(value,'.'); + if (mime_part_p.extension == NULL) { + /* file without extension, setting + NULL will use the default extension later */ + mime_part_p.extension = NULL; + } + else { + struct file_extension *this_extension = + (struct file_extension *)malloc(sizeof(file_extension)); + + this_extension->file_extension_string = + (uschar *)malloc(Ustrlen(mime_part_p.extension)+1); + Ustrcpy(this_extension->file_extension_string, + mime_part_p.extension+1); + this_extension->next = file_extensions; + file_extensions = this_extension; + }; + }; + } + else { + mime_trigger_error(MIME_ERRORLEVEL_DOUBLE_HEADERS); + }; + /* ---------------------------------------------------------------------------- */ + }; + }; /* End of header checks */ + /* ------------------------------------------------ */ + } + else { + /* -------------- non-header mode ----------------- */ + int tmp; + + if (uu_mode == MIME_UU_MODE_OFF) { + uschar uu_file_extension[5]; + /* We are not currently decoding UUENCODE + Check for possible UUENCODE start tag. */ + if (mime_check_uu_start(line,uu_file_extension,&has_tnef)) { + /* possible UUENCODING start detected. + Set unconfirmed mode first. */ + uu_mode = MIME_UU_MODE_UNCONFIRMED; + /* open new uu dump file */ + tmp = mime_get_dump_file(uu_file_extension, &uu_dump_file, info); + if (tmp < 0) { + free(line); + return DEFER; + }; + }; + } + else { + uschar *data; + long data_len = 0; + + if (uu_mode == MIME_UU_MODE_UNCONFIRMED) { + /* We are in unconfirmed UUENCODE mode. */ + + data_len = uu_decode_line(line,&data,line_len,info); + + if (data_len == -2) { + /* temp error, turn off uudecode mode */ + if (uu_dump_file != NULL) { + fclose(uu_dump_file); uu_dump_file = NULL; + }; + uu_mode = MIME_UU_MODE_OFF; + return DEFER; + } + else if (data_len == -1) { + if (uu_dump_file != NULL) { + fclose(uu_dump_file); uu_dump_file = NULL; + }; + uu_mode = MIME_UU_MODE_OFF; + data_len = 0; + } + else if (data_len > 0) { + /* we have at least decoded a valid byte + turn on confirmed mode */ + uu_mode = MIME_UU_MODE_CONFIRMED; + }; + } + else if (uu_mode == MIME_UU_MODE_CONFIRMED) { + /* If we are in confirmed UU mode, + check for single "end" tag on line */ + if ((strncmpic(line,US"end",3) == 0) && (line[3] < 32)) { + if (uu_dump_file != NULL) { + fclose(uu_dump_file); uu_dump_file = NULL; + }; + uu_mode = MIME_UU_MODE_OFF; + } + else { + data_len = uu_decode_line(line,&data,line_len,info); + if (data_len == -2) { + /* temp error, turn off uudecode mode */ + if (uu_dump_file != NULL) { + fclose(uu_dump_file); uu_dump_file = NULL; + }; + uu_mode = MIME_UU_MODE_OFF; + return DEFER; + } + else if (data_len == -1) { + /* skip this line */ + data_len = 0; + }; + }; + }; + + /* write data to dump file, if available */ + if (data_len > 0) { + if (fwrite(data,1,data_len,uu_dump_file) < data_len) { + /* short write */ + snprintf(CS info, 1024,"short write on uudecode dump file"); + free(line); + return DEFER; + }; + }; + }; + + if (mime_demux_mode != MIME_DEMUX_MODE_SCANNING) { + /* Non-scanning and Non-header mode. That means + we are currently decoding data to the dump + file. */ + + /* Check for a known boundary. */ + tmp = mime_check_boundary(line,boundaries); + if (tmp == 1) { + /* We have hit a known start boundary. + That will put us back in header mode. */ + mime_demux_mode = MIME_DEMUX_MODE_MIME_HEADERS; + if (mime_dump_file != NULL) { + /* if the attachment was a RFC822 message, recurse into it */ + if (has_rfc822) { + has_rfc822 = 0; + rewind(mime_dump_file); + mime_demux(mime_dump_file,info); + }; + + fclose(mime_dump_file); mime_dump_file = NULL; + }; + } + else if (tmp == 2) { + /* We have hit a known end boundary. + That puts us into scanning mode, which will end when we hit another known start boundary */ + mime_demux_mode = MIME_DEMUX_MODE_SCANNING; + if (mime_dump_file != NULL) { + /* if the attachment was a RFC822 message, recurse into it */ + if (has_rfc822) { + has_rfc822 = 0; + rewind(mime_dump_file); + mime_demux(mime_dump_file,info); + }; + + fclose(mime_dump_file); mime_dump_file = NULL; + }; + } + else { + uschar *data; + long data_len = 0; + + /* decode the line with the appropriate method */ + if (mime_demux_mode == MIME_DEMUX_MODE_PLAIN) { + /* in plain mode, just dump the line */ + data = line; + data_len = line_len; + } + else if ( (mime_demux_mode == MIME_DEMUX_MODE_QP) || (mime_demux_mode == MIME_DEMUX_MODE_BASE64) ) { + data_len = mime_decode_line(mime_demux_mode,line,&data,line_len,info); + if (data_len < 0) { + /* Error reported from the line decoder. */ + data_len = 0; + }; + }; + + /* write data to dump file */ + if (data_len > 0) { + if (fwrite(data,1,data_len,mime_dump_file) < data_len) { + /* short write */ + snprintf(CS info, 1024,"short write on dump file"); + free(line); + return DEFER; + }; + }; + + }; + } + else { + /* Scanning mode. We end up here after a end boundary. + This will usually be at the end of a message or at + the end of a MIME container. + We need to look for another start boundary to get + back into header mode. */ + if (mime_check_boundary(line,boundaries) == 1) { + mime_demux_mode = MIME_DEMUX_MODE_MIME_HEADERS; + }; + + }; + /* ------------------------------------------------ */ + }; + }; + /* ----------------------- end demux loop ----------------------- */ + + /* close files, they could still be open */ + if (mime_dump_file != NULL) + fclose(mime_dump_file); + if (uu_dump_file != NULL) + fclose(uu_dump_file); + + /* release line buffer */ + free(line); + + /* FIXME: release boundary buffers. + Not too much of a problem since + this instance of exim is not resident. */ + + if (has_tnef) { + uschar file_name[1024]; + /* at least one file could be TNEF encoded. + attempt to send all decoded files thru the TNEF decoder */ + + snprintf(CS file_name,1024,"%s/scan/%s",spool_directory,message_id); + /* Removed FTTB. We need to decide on TNEF inclusion */ + /* mime_unpack_tnef(file_name); */ + }; + + return 0; +} + +#endif diff --git a/src/src/demime.h b/src/src/demime.h new file mode 100644 index 000000000..4799e16e5 --- /dev/null +++ b/src/src/demime.h @@ -0,0 +1,136 @@ +/* $Cambridge: exim/src/src/demime.h,v 1.2 2004/12/16 15:11:47 tom Exp $ */ + +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) Tom Kistner 2003-???? */ +/* License: GPL */ + +/* demime defines */ + +#ifdef WITH_OLD_DEMIME + +#define MIME_DEMUX_MODE_SCANNING 0 +#define MIME_DEMUX_MODE_MIME_HEADERS 1 +#define MIME_DEMUX_MODE_BASE64 2 +#define MIME_DEMUX_MODE_QP 3 +#define MIME_DEMUX_MODE_PLAIN 4 + +#define MIME_UU_MODE_OFF 0 +#define MIME_UU_MODE_UNCONFIRMED 1 +#define MIME_UU_MODE_CONFIRMED 2 + +#define MIME_MAX_EXTENSION 128 + +#define MIME_READ_LINE_EOF 0 +#define MIME_READ_LINE_OK 1 +#define MIME_READ_LINE_OVERFLOW 2 + +#define MIME_SANITY_MAX_LINE_LENGTH 131071 +#define MIME_SANITY_MAX_FILENAME 512 +#define MIME_SANITY_MAX_HEADER_OPTION_VALUE 1024 +#define MIME_SANITY_MAX_B64_LINE_LENGTH 76 +#define MIME_SANITY_MAX_BOUNDARY_LENGTH 1024 +#define MIME_SANITY_MAX_DUMP_FILES 1024 + + + +/* MIME errorlevel settings */ + +#define MIME_ERRORLEVEL_LONG_LINE 3,US"line length in message or single header size exceeds %u bytes",MIME_SANITY_MAX_LINE_LENGTH +#define MIME_ERRORLEVEL_TOO_MANY_PARTS 3,US"too many MIME parts (max %u)",MIME_SANITY_MAX_DUMP_FILES +#define MIME_ERRORLEVEL_MESSAGE_PARTIAL 3,US"'message/partial' MIME type" +#define MIME_ERRORLEVEL_FILENAME_LENGTH 3,US"proposed filename exceeds %u characters",MIME_SANITY_MAX_FILENAME +#define MIME_ERRORLEVEL_BOUNDARY_LENGTH 3,US"boundary length exceeds %u characters",MIME_SANITY_MAX_BOUNDARY_LENGTH +#define MIME_ERRORLEVEL_DOUBLE_HEADERS 2,US"double headers (content-type, content-disposition or content-transfer-encoding)" +#define MIME_ERRORLEVEL_UU_MISALIGNED 1,US"uuencoded line length is not a multiple of 4 characters" +#define MIME_ERRORLEVEL_UU_LINE_LENGTH 1,US"uuencoded line length does not match advertised number of bytes" +#define MIME_ERRORLEVEL_B64_LINE_LENGTH 1,US"base64 line length exceeds %u characters",MIME_SANITY_MAX_B64_LINE_LENGTH +#define MIME_ERRORLEVEL_B64_ILLEGAL_CHAR 2,US"base64 line contains illegal character" +#define MIME_ERRORLEVEL_B64_MISALIGNED 1,US"base64 line length is not a multiple of 4 characters" +#define MIME_ERRORLEVEL_QP_ILLEGAL_CHAR 1,US"quoted-printable encoding contains illegal character" + + +/* demime structures */ + +typedef struct mime_part { + /* true if there was a content-type header */ + int seen_content_type; + /* true if there was a content-transfer-encoding header + contains the encoding type */ + int seen_content_transfer_encoding; + /* true if there was a content-disposition header */ + int seen_content_disposition; + /* pointer to a buffer with the proposed file extension */ + uschar *extension; +} mime_part; + +typedef struct boundary { + struct boundary *next; + uschar *boundary_string; +} boundary; + +typedef struct file_extension { + struct file_extension *next; + uschar *file_extension_string; +} file_extension; + +/* demime.c prototypes */ + +unsigned int mime_hstr_i(uschar *); +uschar *mime_decode_qp(uschar *, int *); +int mime_get_dump_file(uschar *, FILE **, uschar *); +int mime_header_find(uschar *, uschar *, uschar **); +int mime_read_line(FILE *, int, uschar *, long *); +int mime_check_boundary(uschar *, struct boundary *); +int mime_check_uu_start(uschar *, uschar *, int *); +long uu_decode_line(uschar *, uschar **, long, uschar *); +long mime_decode_line(int ,uschar *, uschar **, long, uschar *); +void mime_trigger_error(int, uschar *, ...); +int mime_demux(FILE *, uschar *); + + + +/* BASE64 decoder matrix */ +static unsigned char b64[256]={ +/* 0 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, +/* 16 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, +/* 32 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 62, 128, 128, 128, 63, +/* 48 */ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 128, 128, 128, 255, 128, 128, +/* 64 */ 128, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, +/* 80 */ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 128, 128, 128, 128, 128, +/* 96 */ 128, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 +}; + + +/* Microsoft-Style uudecode matrix */ +static unsigned char uudec[256]={ +/* 0 */ 0, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, +/* 16 */ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, +/* 32 */ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, +/* 48 */ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, +/* 64 */ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, +/* 80 */ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, +/* 96 */ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, +/* 112 */ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, +/* 128 */ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, +/* 144 */ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, +/* 160 */ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, +/* 176 */ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, +/* 192 */ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, +/* 208 */ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, +/* 224 */ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, +/* 240 */ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 +}; + +#endif diff --git a/src/src/exim.c b/src/src/exim.c index 46dcd9100..249f819d0 100644 --- a/src/src/exim.c +++ b/src/src/exim.c @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/exim.c,v 1.10 2004/11/25 13:54:31 ph10 Exp $ */ +/* $Cambridge: exim/src/src/exim.c,v 1.11 2004/12/16 15:11:47 tom Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -839,6 +839,21 @@ fprintf(f, "Support for:"); fprintf(f, " OpenSSL"); #endif #endif +#ifdef WITH_CONTENT_SCAN + fprintf(f, " Content_Scanning"); +#endif +#ifdef WITH_OLD_DEMIME + fprintf(f, " Old_Demime"); +#endif +#ifdef EXPERIMENTAL_SPF + fprintf(f, " Experimental_SPF"); +#endif +#ifdef EXPERIMENTAL_SRS + fprintf(f, " Experimental_SRS"); +#endif +#ifdef EXPERIMENTAL_BRIGHTMAIL + fprintf(f, " Experimental_Brightmail"); +#endif fprintf(f, "\n"); fprintf(f, "Lookups:"); diff --git a/src/src/exim.h b/src/src/exim.h index 397a13687..f7a4f7115 100644 --- a/src/src/exim.h +++ b/src/src/exim.h @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/exim.h,v 1.3 2004/11/24 15:43:36 ph10 Exp $ */ +/* $Cambridge: exim/src/src/exim.h,v 1.4 2004/12/16 15:11:47 tom Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -381,10 +381,16 @@ extern int ferror(FILE *); /* Exim includes are in several files. Note that local_scan.h #includes mytypes.h and store.h, so we don't need to mention them explicitly. */ +#include "config.h" + +/* Before including the rest of the Exim headers, lets clear up some content +scanning dependencies. */ +#ifdef WITH_OLD_DEMIME +#define WITH_CONTENT_SCAN +#endif #include "local_scan.h" #include "macros.h" -#include "config.h" #include "dbstuff.h" #include "structs.h" #include "globals.h" @@ -392,6 +398,16 @@ mytypes.h and store.h, so we don't need to mention them explicitly. */ #include "dbfunctions.h" #include "osfunctions.h" +#ifdef EXPERIMENTAL_BRIGHTMAIL +#include "bmi_spam.h" +#endif +#ifdef EXPERIMENTAL_SPF +#include "spf.h" +#endif +#ifdef EXPERIMENTAL_SRS +#include "srs.h" +#endif + /* The following stuff must follow the inclusion of config.h because it requires various things that are set therein. */ diff --git a/src/src/expand.c b/src/src/expand.c index 1bdcd3760..79d1c1f3a 100644 --- a/src/src/expand.c +++ b/src/src/expand.c @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/expand.c,v 1.7 2004/11/18 11:17:33 ph10 Exp $ */ +/* $Cambridge: exim/src/src/expand.c,v 1.8 2004/12/16 15:11:47 tom Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -318,6 +318,12 @@ static var_entry var_table[] = { { "authenticated_id", vtype_stringptr, &authenticated_id }, { "authenticated_sender",vtype_stringptr, &authenticated_sender }, { "authentication_failed",vtype_int, &authentication_failed }, +#ifdef EXPERIMENTAL_BRIGHTMAIL + { "bmi_alt_location", vtype_stringptr, &bmi_alt_location }, + { "bmi_base64_tracker_verdict", vtype_stringptr, &bmi_base64_tracker_verdict }, + { "bmi_base64_verdict", vtype_stringptr, &bmi_base64_verdict }, + { "bmi_deliver", vtype_int, &bmi_deliver }, +#endif { "body_linecount", vtype_int, &body_linecount }, { "body_zerocount", vtype_int, &body_zerocount }, { "bounce_recipient", vtype_stringptr, &bounce_recipient }, @@ -326,6 +332,10 @@ static var_entry var_table[] = { { "caller_uid", vtype_uid, &real_uid }, { "compile_date", vtype_stringptr, &version_date }, { "compile_number", vtype_stringptr, &version_cnumber }, +#ifdef WITH_OLD_DEMIME + { "demime_errorlevel", vtype_int, &demime_errorlevel }, + { "demime_reason", vtype_stringptr, &demime_reason }, +#endif { "dnslist_domain", vtype_stringptr, &dnslist_domain }, { "dnslist_text", vtype_stringptr, &dnslist_text }, { "dnslist_value", vtype_stringptr, &dnslist_value }, @@ -334,6 +344,9 @@ static var_entry var_table[] = { { "exim_gid", vtype_gid, &exim_gid }, { "exim_path", vtype_stringptr, &exim_path }, { "exim_uid", vtype_uid, &exim_uid }, +#ifdef WITH_OLD_DEMIME + { "found_extension", vtype_stringptr, &found_extension }, +#endif { "home", vtype_stringptr, &deliver_home }, { "host", vtype_stringptr, &deliver_host }, { "host_address", vtype_stringptr, &deliver_host_address }, @@ -357,6 +370,9 @@ static var_entry var_table[] = { { "log_inodes", vtype_pinodes, (void *)FALSE }, { "log_space", vtype_pspace, (void *)FALSE }, { "mailstore_basename", vtype_stringptr, &mailstore_basename }, +#ifdef WITH_CONTENT_SCAN + { "malware_name", vtype_stringptr, &malware_name }, +#endif { "message_age", vtype_int, &message_age }, { "message_body", vtype_msgbody, &message_body }, { "message_body_end", vtype_msgbody_end, &message_body_end }, @@ -364,6 +380,24 @@ static var_entry var_table[] = { { "message_headers", vtype_msgheaders, NULL }, { "message_id", vtype_stringptr, &message_id }, { "message_size", vtype_int, &message_size }, +#ifdef WITH_CONTENT_SCAN + { "mime_anomaly_level", vtype_int, &mime_anomaly_level }, + { "mime_anomaly_text", vtype_stringptr, &mime_anomaly_text }, + { "mime_boundary", vtype_stringptr, &mime_boundary }, + { "mime_charset", vtype_stringptr, &mime_charset }, + { "mime_content_description", vtype_stringptr, &mime_content_description }, + { "mime_content_disposition", vtype_stringptr, &mime_content_disposition }, + { "mime_content_id", vtype_stringptr, &mime_content_id }, + { "mime_content_size", vtype_int, &mime_content_size }, + { "mime_content_transfer_encoding",vtype_stringptr, &mime_content_transfer_encoding }, + { "mime_content_type", vtype_stringptr, &mime_content_type }, + { "mime_decoded_filename", vtype_stringptr, &mime_decoded_filename }, + { "mime_filename", vtype_stringptr, &mime_filename }, + { "mime_is_coverletter", vtype_int, &mime_is_coverletter }, + { "mime_is_multipart", vtype_int, &mime_is_multipart }, + { "mime_is_rfc822", vtype_int, &mime_is_rfc822 }, + { "mime_part_count", vtype_int, &mime_part_count }, +#endif { "n0", vtype_filter_int, &filter_n[0] }, { "n1", vtype_filter_int, &filter_n[1] }, { "n2", vtype_filter_int, &filter_n[2] }, @@ -394,6 +428,9 @@ static var_entry var_table[] = { { "recipient_verify_failure",vtype_stringptr,&recipient_verify_failure }, { "recipients", vtype_recipients, NULL }, { "recipients_count", vtype_int, &recipients_count }, +#ifdef WITH_CONTENT_SCAN + { "regex_match_string", vtype_stringptr, ®ex_match_string }, +#endif { "reply_address", vtype_reply, NULL }, { "return_path", vtype_stringptr, &return_path }, { "return_size_limit", vtype_int, &bounce_return_size_limit }, @@ -424,9 +461,29 @@ static var_entry var_table[] = { { "sn7", vtype_filter_int, &filter_sn[7] }, { "sn8", vtype_filter_int, &filter_sn[8] }, { "sn9", vtype_filter_int, &filter_sn[9] }, +#ifdef WITH_CONTENT_SCAN + { "spam_bar", vtype_stringptr, &spam_bar }, + { "spam_report", vtype_stringptr, &spam_report }, + { "spam_score", vtype_stringptr, &spam_score }, + { "spam_score_int", vtype_stringptr, &spam_score_int }, +#endif +#ifdef EXPERIMENTAL_SPF + { "spf_header_comment", vtype_stringptr, &spf_header_comment }, + { "spf_received", vtype_stringptr, &spf_received }, + { "spf_result", vtype_stringptr, &spf_result }, + { "spf_smtp_comment", vtype_stringptr, &spf_smtp_comment }, +#endif { "spool_directory", vtype_stringptr, &spool_directory }, { "spool_inodes", vtype_pinodes, (void *)TRUE }, { "spool_space", vtype_pspace, (void *)TRUE }, +#ifdef EXPERIMENTAL_SRS + { "srs_db_address", vtype_stringptr, &srs_db_address }, + { "srs_db_key", vtype_stringptr, &srs_db_key }, + { "srs_orig_recipient", vtype_stringptr, &srs_orig_recipient }, + { "srs_orig_sender", vtype_stringptr, &srs_orig_sender }, + { "srs_recipient", vtype_stringptr, &srs_recipient }, + { "srs_status", vtype_stringptr, &srs_status }, +#endif { "thisaddress", vtype_stringptr, &filter_thisaddress }, { "tls_certificate_verified", vtype_int, &tls_certificate_verified }, { "tls_cipher", vtype_stringptr, &tls_cipher }, diff --git a/src/src/functions.h b/src/src/functions.h index 32a3c288a..af84860ee 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/functions.h,v 1.6 2004/11/25 13:54:31 ph10 Exp $ */ +/* $Cambridge: exim/src/src/functions.h,v 1.7 2004/12/16 15:11:47 tom Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -68,6 +68,9 @@ extern void deliver_msglog(const char *, ...); extern void deliver_set_expansions(address_item *); extern int deliver_split_address(address_item *); extern void deliver_succeeded(address_item *); +#ifdef WITH_OLD_DEMIME +extern int demime(uschar **); +#endif extern BOOL directory_make(uschar *, uschar *, int, BOOL); extern dns_address *dns_address_from_rr(dns_answer *, dns_record *); extern void dns_build_reverse(uschar *, uschar *); @@ -126,6 +129,9 @@ extern int ip_socket(int, int); extern uschar *local_part_quote(uschar *lpart); extern void log_close_all(void); +#ifdef WITH_CONTENT_SCAN +extern int malware(uschar **); +#endif extern int match_address_list(uschar *, BOOL, BOOL, uschar **, unsigned int *, int, int, uschar **); extern int match_check_list(uschar **, int, tree_node **, unsigned int **, @@ -139,6 +145,13 @@ extern void md5_end(md5 *, const uschar *, int, uschar *); extern void md5_mid(md5 *, const uschar *); extern void md5_start(md5 *); extern void millisleep(int); +#ifdef WITH_CONTENT_SCAN +struct mime_boundary_context; +extern int mime_acl_check(FILE *f, struct mime_boundary_context *, + uschar **, uschar **); +extern int mime_decode(uschar **); +extern int mime_regex(uschar **); +#endif extern uschar *moan_check_errorcopy(uschar *); extern BOOL moan_skipped_syntax_errors(uschar *, error_block *, uschar *, BOOL, uschar *); @@ -163,6 +176,9 @@ extern void queue_count(void); extern void queue_run(uschar *, uschar *, BOOL); extern int random_number(int); +#ifdef WITH_CONTENT_SCAN +extern int recv_line(int, uschar *, int); +#endif extern int rda_interpret(redirect_block *, int, uschar *, uschar *, ugid_block *, address_item **, uschar **, error_block **, int *, uschar *); extern int rda_is_filter(const uschar *); @@ -183,6 +199,9 @@ extern BOOL receive_check_set_sender(uschar *); extern BOOL receive_msg(BOOL); extern int receive_statvfs(BOOL, int *); extern void receive_swallow_smtp(void); +#ifdef WITH_CONTENT_SCAN +extern int regex(uschar **); +#endif extern BOOL regex_match_and_setup(const pcre *, uschar *, int, int); extern const pcre *regex_must_compile(uschar *, BOOL, BOOL); extern void retry_add_item(address_item *, uschar *, int); @@ -243,6 +262,10 @@ extern int smtp_setup_msg(void); extern BOOL smtp_start_session(void); extern int smtp_ungetc(int); extern int smtp_write_command(smtp_outblock *, BOOL, char *, ...); +#ifdef WITH_CONTENT_SCAN +extern int spam(uschar **); +extern FILE *spool_mbox(unsigned long long *); +#endif extern BOOL spool_move_message(uschar *, uschar *, uschar *, uschar *); extern BOOL spool_open_datafile(uschar *); extern int spool_open_temp(uschar *); @@ -294,6 +317,10 @@ extern int tree_insertnode(tree_node **, tree_node *); extern tree_node *tree_search(tree_node *, uschar *); extern void tree_write(tree_node *, FILE *); +#ifdef WITH_CONTENT_SCAN +extern void unspool_mbox(void); +#endif + extern int verify_address(address_item *, FILE *, int, int, int, int, uschar *, uschar *, BOOL *); extern int verify_check_dnsbl(uschar **); diff --git a/src/src/globals.c b/src/src/globals.c index f51033dcb..be70d489d 100644 --- a/src/src/globals.c +++ b/src/src/globals.c @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/globals.c,v 1.8 2004/11/25 13:54:31 ph10 Exp $ */ +/* $Cambridge: exim/src/src/globals.c,v 1.9 2004/12/16 15:11:47 tom Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -164,6 +164,9 @@ uschar *acl_smtp_expn = NULL; uschar *acl_smtp_helo = NULL; uschar *acl_smtp_mail = NULL; uschar *acl_smtp_mailauth = NULL; +#ifdef WITH_CONTENT_SCAN +uschar *acl_smtp_mime = NULL; +#endif uschar *acl_smtp_predata = NULL; uschar *acl_smtp_quit = NULL; uschar *acl_smtp_rcpt = NULL; @@ -181,6 +184,9 @@ error codes - keep in step with definitions of ACL_WHERE_xxxx in macros.h. */ uschar *acl_wherenames[] = { US"RCPT", US"MAIL", US"PREDATA", +#ifdef WITH_CONTENT_SCAN + US"MIME", +#endif US"DATA", US"non-SMTP", US"AUTH", @@ -197,6 +203,9 @@ uschar *acl_wherenames[] = { US"RCPT", int acl_wherecodes[] = { 550, /* RCPT */ 550, /* MAIL */ 550, /* PREDATA */ +#ifdef WITH_CONTENT_SCAN + 550, /* MIME */ +#endif 550, /* DATA */ 0, /* not SMTP; not relevant */ 503, /* AUTH */ @@ -309,6 +318,9 @@ auth_instance auth_defaults = { uschar *auth_defer_msg = US"reason not recorded"; uschar *auth_defer_user_msg = US""; int auto_thaw = 0; +#ifdef WITH_CONTENT_SCAN +uschar *av_scanner = US"sophie:/var/run/sophie"; /* AV scanner */ +#endif BOOL background_daemon = TRUE; uschar *base62_chars= @@ -316,6 +328,15 @@ uschar *base62_chars= uschar *bi_command = NULL; uschar *big_buffer = NULL; int big_buffer_size = BIG_BUFFER_SIZE; +#ifdef EXPERIMENTAL_BRIGHTMAIL +uschar *bmi_alt_location = NULL; +uschar *bmi_base64_tracker_verdict = NULL; +uschar *bmi_base64_verdict = NULL; +uschar *bmi_config_file = US"/opt/brightmail/etc/brightmail.cfg"; +int bmi_deliver = 1; +int bmi_run = 0; +uschar *bmi_verdicts = NULL; +#endif int body_linecount = 0; int body_zerocount = 0; uschar *bounce_message_file = NULL; @@ -435,6 +456,11 @@ uschar *deliver_selectstring = NULL; BOOL deliver_selectstring_regex = FALSE; uschar *deliver_selectstring_sender = NULL; BOOL deliver_selectstring_sender_regex = FALSE; +#ifdef WITH_OLD_DEMIME +int demime_errorlevel = 0; +int demime_ok = 0; +uschar *demime_reason = NULL; +#endif BOOL disable_logging = FALSE; uschar *dns_again_means_nonexist = NULL; @@ -473,6 +499,10 @@ uschar *expand_string_message; BOOL extract_addresses_remove_arguments = TRUE; uschar *extra_local_interfaces = NULL; +#ifdef WITH_CONTENT_SCAN +BOOL fake_reject = FALSE; +uschar *fake_reject_text = US"Your message has been rejected but is being kept for evaluation.\nIf it was a legit message, it may still be delivered to the target recipient(s)."; +#endif int filter_n[FILTER_VARIABLE_COUNT]; BOOL filter_running = FALSE; int filter_sn[FILTER_VARIABLE_COUNT]; @@ -481,6 +511,9 @@ uschar *filter_test_sfile = NULL; uschar *filter_test_ufile = NULL; uschar *filter_thisaddress = NULL; int finduser_retries = 0; +#ifdef WITH_OLD_DEMIME +uschar *found_extension = NULL; +#endif uid_t fixed_never_users[] = { FIXED_NEVER_USERS }; uschar *freeze_tell = NULL; uschar *fudged_queue_times = US""; @@ -636,6 +669,9 @@ uschar *lookup_value = NULL; macro_item *macros = NULL; uschar *mailstore_basename = NULL; +#ifdef WITH_CONTENT_SCAN +uschar *malware_name = NULL; /* Virus Name */ +#endif int max_username_length = 0; int message_age = 0; uschar *message_body = NULL; @@ -656,9 +692,33 @@ int message_size = 0; uschar *message_size_limit = US"50M"; uschar message_subdir[2] = { 0, 0 }; uschar *message_reference = NULL; + +/* MIME ACL expandables */ +#ifdef WITH_CONTENT_SCAN +int mime_anomaly_level = NULL; +uschar *mime_anomaly_text = NULL; +uschar *mime_boundary = NULL; +uschar *mime_charset = NULL; +uschar *mime_content_description = NULL; +uschar *mime_content_disposition = NULL; +uschar *mime_content_id = NULL; +unsigned int mime_content_size = 0; +uschar *mime_content_transfer_encoding = NULL; +uschar *mime_content_type = NULL; +uschar *mime_decoded_filename = NULL; +uschar *mime_filename = NULL; +int mime_is_multipart = 0; +int mime_is_coverletter = 0; +int mime_is_rfc822 = 0; +int mime_part_count = -1; +#endif + BOOL mua_wrapper = FALSE; uid_t *never_users = NULL; +#ifdef WITH_CONTENT_SCAN +BOOL no_mbox_unspool = FALSE; +#endif BOOL no_multiline_responses = FALSE; uid_t original_euid; @@ -759,6 +819,9 @@ const pcre *regex_From = NULL; const pcre *regex_PIPELINING = NULL; const pcre *regex_SIZE = NULL; const pcre *regex_ismsgid = NULL; +#ifdef WITH_CONTENT_SCAN +uschar *regex_match_string = NULL; +#endif int remote_delivery_count = 0; int remote_max_parallel = 2; uschar *remote_sort_domains = NULL; @@ -783,6 +846,9 @@ router_instance router_defaults = { NULL, /* driver name */ NULL, /* address_data */ +#ifdef EXPERIMENTAL_BRIGHTMAIL + NULL, /* bmi_rule */ +#endif NULL, /* cannot_route_message */ NULL, /* condition */ NULL, /* current_directory */ @@ -811,6 +877,11 @@ router_instance router_defaults = { NULL, /* transport_name */ TRUE, /* address_test */ +#ifdef EXPERIMENTAL_BRIGHTMAIL + FALSE, /* bmi_deliver_alternate */ + FALSE, /* bmi_deliver_default */ + FALSE, /* bmi_dont_deliver */ +#endif TRUE, /* expn */ FALSE, /* caseful_local_part */ FALSE, /* check_local_user */ @@ -939,9 +1010,33 @@ int smtp_rlr_limit = 0; int smtp_rlr_threshold = INT_MAX; BOOL smtp_use_pipelining = FALSE; BOOL smtp_use_size = FALSE; + +#ifdef WITH_CONTENT_SCAN +uschar *spamd_address = US"127.0.0.1 783"; +uschar *spam_bar = NULL; +uschar *spam_report = NULL; +uschar *spam_score = NULL; +uschar *spam_score_int = NULL; +#endif +#ifdef EXPERIMENTAL_SPF +uschar *spf_header_comment = NULL; +uschar *spf_received = NULL; +uschar *spf_result = NULL; +uschar *spf_smtp_comment = NULL; +#endif + BOOL split_spool_directory = FALSE; uschar *spool_directory = US SPOOL_DIRECTORY "\0<--------------Space to patch spool_directory->"; +#ifdef EXPERIMENTAL_SRS +uschar *srs_config = NULL; +uschar *srs_db_address = NULL; +uschar *srs_db_key = NULL; +uschar *srs_orig_recipient = NULL; +uschar *srs_orig_sender = NULL; +uschar *srs_recipient = NULL; +uschar *srs_status = NULL; +#endif int string_datestamp_offset= -1; BOOL strip_excess_angle_brackets = FALSE; BOOL strip_trailing_dot = FALSE; diff --git a/src/src/globals.h b/src/src/globals.h index 44731b585..1ac9776f4 100644 --- a/src/src/globals.h +++ b/src/src/globals.h @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/globals.h,v 1.7 2004/11/25 13:54:31 ph10 Exp $ */ +/* $Cambridge: exim/src/src/globals.h,v 1.8 2004/12/16 15:11:47 tom Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -106,6 +106,9 @@ extern uschar *acl_smtp_expn; /* ACL run for EXPN */ extern uschar *acl_smtp_helo; /* ACL run for HELO/EHLO */ extern uschar *acl_smtp_mail; /* ACL run for MAIL */ extern uschar *acl_smtp_mailauth; /* ACL run for MAIL AUTH */ +#ifdef WITH_CONTENT_SCAN +extern uschar *acl_smtp_mime; /* ACL run after DATA, before acl_smtp_data, for each MIME part */ +#endif extern uschar *acl_smtp_predata; /* ACL run for DATA command */ extern uschar *acl_smtp_quit; /* ACL run for QUIT */ extern uschar *acl_smtp_rcpt; /* ACL run for RCPT */ @@ -144,12 +147,24 @@ extern auth_instance auth_defaults; /* Default values */ extern uschar *auth_defer_msg; /* Error message for log */ extern uschar *auth_defer_user_msg; /* Error message for user */ extern int auto_thaw; /* Auto-thaw interval */ +#ifdef WITH_CONTENT_SCAN +extern uschar *av_scanner; /* AntiVirus scanner to use for the malware condition */ +#endif extern BOOL background_daemon; /* Set FALSE to keep in foreground */ extern uschar *base62_chars; /* Table of base-62 characters */ extern uschar *bi_command; /* Command for -bi option */ extern uschar *big_buffer; /* Used for various temp things */ extern int big_buffer_size; /* Current size (can expand) */ +#ifdef EXPERIMENTAL_BRIGHTMAIL +extern uschar *bmi_alt_location; /* expansion variable that contains the alternate location for the rcpt (available during routing) */ +extern uschar *bmi_base64_tracker_verdict; /* expansion variable with base-64 encoded OLD verdict string (available during routing) */ +extern uschar *bmi_base64_verdict; /* expansion variable with base-64 encoded verdict string (available during routing) */ +extern uschar *bmi_config_file; /* Brightmail config file */ +extern int bmi_deliver; /* Flag that determines if the message should be delivered to the rcpt (available during routing) */ +extern int bmi_run; /* Flag that determines if message should be run through Brightmail server */ +extern uschar *bmi_verdicts; /* BASE64-encoded verdicts with recipient lists */ +#endif extern int body_linecount; /* Line count in body */ extern int body_zerocount; /* Binary zero count in body */ extern uschar *bounce_message_file; /* Template file */ @@ -235,6 +250,11 @@ extern uschar *deliver_selectstring; /* For selecting by recipient */ extern BOOL deliver_selectstring_regex; /* String is regex */ extern uschar *deliver_selectstring_sender; /* For selecting by sender */ extern BOOL deliver_selectstring_sender_regex; /* String is regex */ +#ifdef WITH_OLD_DEMIME +extern int demime_errorlevel; /* Severity of MIME error */ +extern int demime_ok; /* Nonzero if message has been demimed */ +extern uschar *demime_reason; /* Reason for broken MIME container */ +#endif extern BOOL disable_logging; /* Disables log writing when TRUE */ extern uschar *dns_again_means_nonexist; /* Domains that are badly set up */ @@ -272,6 +292,10 @@ extern BOOL expand_string_forcedfail; /* TRUE if failure was "expected" */ extern BOOL extract_addresses_remove_arguments; /* Controls -t behaviour */ extern uschar *extra_local_interfaces; /* Local, non-listen interfaces */ +#ifdef WITH_CONTENT_SCAN +extern BOOL fake_reject; /* TRUE if fake reject is to be given */ +extern uschar *fake_reject_text; /* Option for the fakereject control statement: can contain user defined message. Default is in globals.c. */ +#endif extern int filter_n[FILTER_VARIABLE_COUNT]; /* filter variables */ extern BOOL filter_running; /* TRUE while running a filter */ extern int filter_sn[FILTER_VARIABLE_COUNT]; /* variables set by system filter */ @@ -281,6 +305,9 @@ extern uschar *filter_test_ufile; /* User filter test file */ extern uschar *filter_thisaddress; /* For address looping */ extern int finduser_retries; /* Retry count for getpwnam() */ extern uid_t fixed_never_users[]; /* Can't be overridden */ +#ifdef WITH_OLD_DEMIME +extern uschar *found_extension; /* demime acl condition: file extension found */ +#endif extern uschar *freeze_tell; /* Message on (some) freezings */ extern uschar *fudged_queue_times; /* For use in test harness */ @@ -360,6 +387,9 @@ extern uschar *lookup_value; /* Value looked up from file */ extern macro_item *macros; /* Configuration macros */ extern uschar *mailstore_basename; /* For mailstore deliveries */ +#ifdef WITH_CONTENT_SCAN +extern uschar *malware_name; /* Name of virus or malware ("W32/Klez-H") */ +#endif extern int max_username_length; /* For systems with broken getpwnam() */ extern int message_age; /* In seconds */ extern uschar *message_body; /* Start of message body for filter */ @@ -379,9 +409,33 @@ extern int message_size; /* Size of message */ extern uschar *message_size_limit; /* As it says */ extern uschar message_subdir[]; /* Subdirectory for messages */ extern uschar *message_reference; /* Reference for error messages */ + +/* MIME ACL expandables */ +#ifdef WITH_CONTENT_SCAN +extern int mime_anomaly_level; +extern uschar *mime_anomaly_text; +extern uschar *mime_boundary; +extern uschar *mime_charset; +extern uschar *mime_content_description; +extern uschar *mime_content_disposition; +extern uschar *mime_content_id; +extern unsigned int mime_content_size; +extern uschar *mime_content_transfer_encoding; +extern uschar *mime_content_type; +extern uschar *mime_decoded_filename; +extern uschar *mime_filename; +extern int mime_is_multipart; +extern int mime_is_coverletter; +extern int mime_is_rfc822; +extern int mime_part_count; +#endif + extern BOOL mua_wrapper; /* TRUE when Exim is wrapping an MUA */ extern uid_t *never_users; /* List of uids never to be used */ +#ifdef WITH_CONTENT_SCAN +extern BOOL no_mbox_unspool; /* don't unlink files in /scan directory */ +#endif extern BOOL no_multiline_responses; /* For broken clients */ extern optionlist optionlist_auths[]; /* These option lists are made */ @@ -467,6 +521,9 @@ extern const pcre *regex_From; /* For recognizing "From_" lines */ extern const pcre *regex_PIPELINING; /* For recognizing PIPELINING */ extern const pcre *regex_SIZE; /* For recognizing SIZE settings */ extern const pcre *regex_ismsgid; /* Compiled r.e. for message it */ +#ifdef WITH_CONTENT_SCAN +extern uschar *regex_match_string; /* regex that matched a line (regex ACL condition) */ +#endif extern int remote_delivery_count; /* Number of remote addresses */ extern int remote_max_parallel; /* Maximum parallel delivery */ extern uschar *remote_sort_domains; /* Remote domain sorting order */ @@ -558,8 +615,31 @@ extern int smtp_rlr_limit; /* Max delay */ extern int smtp_rlr_threshold; /* Threshold for RCPT rate limit */ extern BOOL smtp_use_pipelining; /* Global for passed connections */ extern BOOL smtp_use_size; /* Global for passed connections */ + +#ifdef WITH_CONTENT_SCAN +extern uschar *spamd_address; /* address for the spamassassin daemon */ +extern uschar *spam_bar; /* the spam "bar" (textual representation of spam_score) */ +extern uschar *spam_report; /* the spamd report (multiline) */ +extern uschar *spam_score; /* the spam score (float) */ +extern uschar *spam_score_int; /* spam_score * 10 (int) */ +#endif +#ifdef EXPERIMENTAL_SPF +extern uschar *spf_header_comment; /* spf header comment */ +extern uschar *spf_received; /* SPF-Received: header */ +extern uschar *spf_result; /* spf result in string form */ +extern uschar *spf_smtp_comment; /* spf comment to include in SMTP reply */ +#endif extern BOOL split_spool_directory; /* TRUE to use multiple subdirs */ extern uschar *spool_directory; /* Name of spool directory */ +#ifdef EXPERIMENTAL_SRS +extern uschar *srs_config; /* SRS config secret:max age:hash length:use timestamp:use hash */ +extern uschar *srs_db_address; /* SRS db address */ +extern uschar *srs_db_key; /* SRS db key */ +extern uschar *srs_orig_sender; /* SRS original sender */ +extern uschar *srs_orig_recipient; /* SRS original recipient */ +extern uschar *srs_recipient; /* SRS recipient */ +extern uschar *srs_status; /* SRS staus */ +#endif extern int string_datestamp_offset;/* After insertion by string_format */ extern BOOL strip_excess_angle_brackets; /* Surrounding route-addrs */ extern BOOL strip_trailing_dot; /* Remove dots at ends of domains */ diff --git a/src/src/header.c b/src/src/header.c index 2cac551c6..a9d8e599c 100644 --- a/src/src/header.c +++ b/src/src/header.c @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/header.c,v 1.1 2004/10/07 10:39:01 ph10 Exp $ */ +/* $Cambridge: exim/src/src/header.c,v 1.2 2004/12/16 15:11:47 tom Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -40,6 +40,19 @@ while (*tt == ' ' || *tt == '\t') tt++; return *tt == ':'; } +/* This is a copy of the function above, only that it is possible to pass + only the beginning of a header name. It simply does a front-anchored + substring match. Arguments and Return codes are the same as for + header_testname() above. */ + +BOOL +header_testname_incomplete(header_line *h, uschar *name, int len, BOOL notdel) +{ +uschar *tt; +if (h->type == '*' && notdel) return FALSE; +if (h->text == NULL || strncmpic(h->text, name, len) != 0) return FALSE; +return TRUE; +} /************************************************* diff --git a/src/src/local_scan.h b/src/src/local_scan.h index 4e6f88a31..deb7a4c9b 100644 --- a/src/src/local_scan.h +++ b/src/src/local_scan.h @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/local_scan.h,v 1.1 2004/10/07 10:39:01 ph10 Exp $ */ +/* $Cambridge: exim/src/src/local_scan.h,v 1.2 2004/12/16 15:11:47 tom Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -109,6 +109,9 @@ typedef struct recipient_item { uschar *address; /* the recipient address */ int pno; /* parent number for "one_time" alias, or -1 */ uschar *errors_to; /* the errors_to address or NULL */ +#ifdef EXPERIMENTAL_BRIGHTMAIL + uschar *bmi_optin; +#endif } recipient_item; @@ -147,6 +150,7 @@ extern void header_add(int, char *, ...); extern void header_add_at_position(BOOL, uschar *, BOOL, int, char *, ...); extern void header_remove(int, uschar *); extern BOOL header_testname(header_line *, uschar *, int, BOOL); +extern BOOL header_testname_incomplete(header_line *, uschar *, int, BOOL); extern void log_write(unsigned int, int, char *format, ...); extern int lss_b64decode(uschar *, uschar **); extern uschar *lss_b64encode(uschar *, int); diff --git a/src/src/macros.h b/src/src/macros.h index 996e2e089..da96396f8 100644 --- a/src/src/macros.h +++ b/src/src/macros.h @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/macros.h,v 1.4 2004/11/25 13:54:31 ph10 Exp $ */ +/* $Cambridge: exim/src/src/macros.h,v 1.5 2004/12/16 15:11:47 tom Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -131,12 +131,12 @@ enough to hold all the headers from a normal kind of message. */ into big_buffer_size and in some circumstances increased. It should be at least as long as the maximum path length. */ -#if defined PATH_MAX && PATH_MAX > 1024 +#if defined PATH_MAX && PATH_MAX > 16384 #define BIG_BUFFER_SIZE PATH_MAX -#elif defined MAXPATHLEN && MAXPATHLEN > 1024 +#elif defined MAXPATHLEN && MAXPATHLEN > 16384 #define BIG_BUFFER_SIZE MAXPATHLEN #else -#define BIG_BUFFER_SIZE 1024 +#define BIG_BUFFER_SIZE 16384 #endif /* This limits the length of data returned by local_scan(). Because it is @@ -552,6 +552,7 @@ to the header name, by calling header_checkname(). */ #define htype_add_top 'a' #define htype_add_rec 'r' #define htype_add_bot 'z' +#define htype_add_rfc 'f' /* Types of item in options lists. These are the bottom 8 bits of the "type" field, which is an int. The opt_void value is used for entries in tables that @@ -741,11 +742,18 @@ ordered to make it easy to implement tests for certain ACLs when processing order without checking carefully! Furthermore, remember to keep these in step with the tables of names and response codes in globals.c. */ +/* FIXME: the #ifdef below does not work here. Why? */ + enum { ACL_WHERE_RCPT, /* Some controls are for RCPT only */ ACL_WHERE_MAIL, /* ) */ ACL_WHERE_PREDATA, /* ) There are several tests for "in message", */ - ACL_WHERE_DATA, /* ) implemented by <= WHERE_NOTSMTP */ - ACL_WHERE_NOTSMTP, /* ) */ + /* ) implemented by <= WHERE_NOTSMTP */ + /* ) */ +#ifdef WITH_CONTENT_SCAN + ACL_WHERE_MIME, +#endif + ACL_WHERE_DATA, + ACL_WHERE_NOTSMTP, ACL_WHERE_AUTH, /* These remaining ones are not currently */ ACL_WHERE_CONNECT, /* required to be in a special order so they */ diff --git a/src/src/malware.c b/src/src/malware.c new file mode 100644 index 000000000..916480197 --- /dev/null +++ b/src/src/malware.c @@ -0,0 +1,1322 @@ +/* $Cambridge: exim/src/src/malware.c,v 1.2 2004/12/16 15:11:47 tom Exp $ */ + +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) Tom Kistner 2003-???? */ +/* License: GPL */ + +/* Code for calling virus (malware) scanners. Called from acl.c. */ + +#include "exim.h" +#ifdef WITH_CONTENT_SCAN + +/* declaration of private routines */ +int mksd_scan_packed(int sock); + +/* SHUT_WR seems to be undefined on Unixware? */ +#ifndef SHUT_WR +#define SHUT_WR 1 +#endif + +#define DRWEBD_SCAN_CMD (1) /* scan file, buffer or diskfile */ +#define DRWEBD_RETURN_VIRUSES (1<<0) /* ask daemon return to us viruses names from report */ +#define DRWEBD_IS_MAIL (1<<19) /* say to daemon that format is "archive MAIL" */ + +/* Routine to check whether a system is big- or litte-endian. + Ripped from http://www.faqs.org/faqs/graphics/fileformats-faq/part4/section-7.html + Needed for proper kavdaemon implementation. Sigh. */ +#define BIG_MY_ENDIAN 0 +#define LITTLE_MY_ENDIAN 1 +int test_byte_order(void); +int test_byte_order() { + short int word = 0x0001; + char *byte = (char *) &word; + return(byte[0] ? LITTLE_MY_ENDIAN : BIG_MY_ENDIAN); +} + +uschar malware_name_buffer[256]; +int malware_ok = 0; + +int malware(uschar **listptr) { + int sep = 0; + uschar *list = *listptr; + uschar *av_scanner_work = av_scanner; + uschar *scanner_name; + uschar scanner_name_buffer[16]; + uschar *malware_regex; + uschar malware_regex_buffer[64]; + uschar malware_regex_default[] = ".+"; + unsigned long long mbox_size; + FILE *mbox_file; + int roffset; + const pcre *re; + const uschar *rerror; + + /* make sure the eml mbox file is spooled up */ + mbox_file = spool_mbox(&mbox_size); + if (mbox_file == NULL) { + /* error while spooling */ + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: error while creating mbox spool file"); + return DEFER; + }; + /* none of our current scanners need the mbox + file as a stream, so we can close it right away */ + fclose(mbox_file); + + /* extract the malware regex to match against from the option list */ + if ((malware_regex = string_nextinlist(&list, &sep, + malware_regex_buffer, + sizeof(malware_regex_buffer))) != NULL) { + + /* parse 1st option */ + if ( (strcmpic(malware_regex,US"false") == 0) || + (Ustrcmp(malware_regex,"0") == 0) ) { + /* explicitly no matching */ + return FAIL; + }; + + /* special cases (match anything except empty) */ + if ( (strcmpic(malware_regex,US"true") == 0) || + (Ustrcmp(malware_regex,"*") == 0) || + (Ustrcmp(malware_regex,"1") == 0) ) { + malware_regex = malware_regex_default; + }; + } + else { + /* empty means "don't match anything" */ + return FAIL; + }; + + /* compile the regex, see if it works */ + re = pcre_compile(CS malware_regex, PCRE_COPT, (const char **)&rerror, &roffset, NULL); + if (re == NULL) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: regular expression error in '%s': %s at offset %d", malware_regex, rerror, roffset); + return DEFER; + }; + + /* if av_scanner starts with a dollar, expand it first */ + if (*av_scanner == '$') { + av_scanner_work = expand_string(av_scanner); + if (av_scanner_work == NULL) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: av_scanner starts with $, but expansion failed: %s", expand_string_message); + return DEFER; + } + else { + debug_printf("Expanded av_scanner global: %s\n", av_scanner_work); + /* disable result caching in this case */ + malware_name = NULL; + malware_ok = 0; + }; + } + + /* Do not scan twice. */ + if (malware_ok == 0) { + + /* find the scanner type from the av_scanner option */ + if ((scanner_name = string_nextinlist(&av_scanner_work, &sep, + scanner_name_buffer, + sizeof(scanner_name_buffer))) == NULL) { + /* no scanner given */ + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: av_scanner configuration variable is empty"); + return DEFER; + }; + + /* "drweb" scanner type ----------------------------------------------- */ + /* v0.1 - added support for tcp sockets */ + /* v0.0 - initial release -- support for unix sockets */ + if (strcmpic(scanner_name,US"drweb") == 0) { + uschar *drweb_options; + uschar drweb_options_buffer[1024]; + uschar drweb_options_default[] = "/usr/local/drweb/run/drwebd.sock"; + struct sockaddr_un server; + int sock, port, result, ovector[30]; + unsigned int fsize; + uschar tmpbuf[1024], *drweb_fbuf; + uschar scanrequest[1024]; + uschar drweb_match_string[128]; + int drweb_rc, drweb_cmd, drweb_flags = 0x0000, drweb_fd, + drweb_vnum, drweb_slen, drweb_fin = 0x0000; + unsigned long bread; + uschar hostname[256]; + struct hostent *he; + struct in_addr in; + pcre *drweb_re; + + if ((drweb_options = string_nextinlist(&av_scanner_work, &sep, + drweb_options_buffer, sizeof(drweb_options_buffer))) == NULL) { + /* no options supplied, use default options */ + drweb_options = drweb_options_default; + }; + + if (*drweb_options != '/') { + + /* extract host and port part */ + if( sscanf(CS drweb_options, "%s %u", hostname, &port) != 2 ) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: drweb: invalid socket '%s'", drweb_options); + return DEFER; + } + + /* Lookup the host */ + if((he = gethostbyname(CS hostname)) == 0) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: drweb: failed to lookup host '%s'", hostname); + return DEFER; + } + + in = *(struct in_addr *) he->h_addr_list[0]; + + /* Open the drwebd TCP socket */ + if ( (sock = ip_socket(SOCK_STREAM, AF_INET)) < 0) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: drweb: unable to acquire socket (%s)", + strerror(errno)); + return DEFER; + } + + if (ip_connect(sock, AF_INET, (uschar*)inet_ntoa(in), port, 5) < 0) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: drweb: connection to %s, port %u failed (%s)", + inet_ntoa(in), port, strerror(errno)); + return DEFER; + } + + /* prepare variables */ + drweb_cmd = htonl(DRWEBD_SCAN_CMD); + drweb_flags = htonl(DRWEBD_RETURN_VIRUSES | DRWEBD_IS_MAIL); + snprintf(CS scanrequest, 1024,CS"%s/scan/%s/%s.eml", + spool_directory, message_id, message_id); + + /* calc file size */ + drweb_fd = open(CS scanrequest, O_RDONLY); + if (drweb_fd == -1) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: drweb: can't open spool file %s: %s", + scanrequest, strerror(errno)); + return DEFER; + } + fsize = lseek(drweb_fd, 0, SEEK_END); + if (fsize == -1) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: drweb: can't seek spool file %s: %s", + scanrequest, strerror(errno)); + return DEFER; + } + drweb_slen = htonl(fsize); + lseek(drweb_fd, 0, SEEK_SET); + + /* send scan request */ + if ((send(sock, &drweb_cmd, sizeof(drweb_cmd), 0) < 0) || + (send(sock, &drweb_flags, sizeof(drweb_flags), 0) < 0) || + (send(sock, &drweb_fin, sizeof(drweb_fin), 0) < 0) || + (send(sock, &drweb_slen, sizeof(drweb_slen), 0) < 0)) { + close(sock); + close(drweb_fd); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: drweb: unable to send commands to socket (%s)", drweb_options); + return DEFER; + } + + drweb_fbuf = (uschar *) malloc (fsize); + if (!drweb_fbuf) { + close(sock); + close(drweb_fd); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: drweb: unable to allocate memory %u for file (%s)", + fsize, scanrequest); + return DEFER; + } + + result = read (drweb_fd, drweb_fbuf, fsize); + if (result == -1) { + close(sock); + close(drweb_fd); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: drweb: can't read spool file %s: %s", + scanrequest, strerror(errno)); + return DEFER; + } + + /* send file body to socket */ + if (send(sock, drweb_fbuf, fsize, 0) < 0) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: drweb: unable to send file body to socket (%s)", drweb_options); + return DEFER; + } + close(drweb_fd); + free(drweb_fbuf); + } + else { + /* open the drwebd UNIX socket */ + sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock < 0) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: drweb: can't open UNIX socket"); + return DEFER; + } + server.sun_family = AF_UNIX; + Ustrcpy(server.sun_path, drweb_options); + if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: drweb: unable to connect to socket (%s). errno=%d", drweb_options, errno); + return DEFER; + } + + /* prepare variables */ + drweb_cmd = htonl(DRWEBD_SCAN_CMD); + drweb_flags = htonl(DRWEBD_RETURN_VIRUSES | DRWEBD_IS_MAIL); + snprintf(CS scanrequest, 1024,CS"%s/scan/%s/%s.eml", spool_directory, message_id, message_id); + drweb_slen = htonl(Ustrlen(scanrequest)); + + /* send scan request */ + if ((send(sock, &drweb_cmd, sizeof(drweb_cmd), 0) < 0) || + (send(sock, &drweb_flags, sizeof(drweb_flags), 0) < 0) || + (send(sock, &drweb_slen, sizeof(drweb_slen), 0) < 0) || + (send(sock, scanrequest, Ustrlen(scanrequest), 0) < 0) || + (send(sock, &drweb_fin, sizeof(drweb_fin), 0) < 0)) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: drweb: unable to send commands to socket (%s)", drweb_options); + return DEFER; + } + } + + /* wait for result */ + if ((bread = recv(sock, &drweb_rc, sizeof(drweb_rc), 0) != sizeof(drweb_rc))) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: drweb: unable to read return code"); + return DEFER; + } + drweb_rc = ntohl(drweb_rc); + + if ((bread = recv(sock, &drweb_vnum, sizeof(drweb_vnum), 0) != sizeof(drweb_vnum))) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: drweb: unable to read the number of viruses"); + return DEFER; + } + drweb_vnum = ntohl(drweb_vnum); + + /* "virus(es) found" if virus number is > 0 */ + if (drweb_vnum) + { + int i; + uschar pre_malware_nb[256]; + + malware_name = malware_name_buffer; + + /* setup default virus name */ + Ustrcpy(malware_name_buffer,"unknown"); + + /* read and concatenate virus names into one string */ + for (i=0;i= 2) { + pcre_copy_substring(CS tmpbuf, ovector, result, 1, CS pre_malware_nb, 255); + } + /* the first name we just copy to malware_name */ + if (i==0) + Ustrcpy(CS malware_name_buffer, CS pre_malware_nb); + else { + /* concatenate each new virus name to previous */ + int slen = Ustrlen(malware_name_buffer); + if (slen < (slen+Ustrlen(pre_malware_nb))) { + Ustrcat(malware_name_buffer, "/"); + Ustrcat(malware_name_buffer, pre_malware_nb); + } + } + } + } + else { + /* no virus found */ + malware_name = NULL; + }; + close(sock); + } + /* ----------------------------------------------------------------------- */ + else if (strcmpic(scanner_name,US"aveserver") == 0) { + uschar *kav_options; + uschar kav_options_buffer[1024]; + uschar kav_options_default[] = "/var/run/aveserver"; + uschar buf[32768]; + uschar *p; + struct sockaddr_un server; + int sock; + + if ((kav_options = string_nextinlist(&av_scanner_work, &sep, + kav_options_buffer, + sizeof(kav_options_buffer))) == NULL) { + /* no options supplied, use default options */ + kav_options = kav_options_default; + }; + + /* open the aveserver socket */ + sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock < 0) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: can't open UNIX socket."); + return DEFER; + } + server.sun_family = AF_UNIX; + Ustrcpy(server.sun_path, kav_options); + if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: unable to connect to aveserver UNIX socket (%s). errno=%d", kav_options, errno); + return DEFER; + } + + /* read aveserver's greeting and see if it is ready (2xx greeting) */ + recv_line(sock, buf, 32768); + + if (buf[0] != '2') { + /* aveserver is having problems */ + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: aveserver is unavailable (Responded: %s).", ((buf[0] != 0) ? buf : (uschar *)"nothing") ); + return DEFER; + }; + + /* prepare our command */ + snprintf(CS buf, 32768, "SCAN bPQRSTUW %s/scan/%s/%s.eml\r\n", spool_directory, message_id, message_id); + + /* and send it */ + if (send(sock, buf, Ustrlen(buf), 0) < 0) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: unable to write to aveserver UNIX socket (%s)", kav_options); + return DEFER; + } + + malware_name = NULL; + /* read response lines, find malware name and final response */ + while (recv_line(sock, buf, 32768) > 0) { + debug_printf("aveserver: %s\n", buf); + if (buf[0] == '2') break; + if (Ustrncmp(buf,"322",3) == 0) { + uschar *p = Ustrchr(&buf[4],' '); + *p = '\0'; + Ustrcpy(malware_name_buffer,&buf[4]); + malware_name = malware_name_buffer; + }; + } + + close(sock); + } + /* "fsecure" scanner type ------------------------------------------------- */ + else if (strcmpic(scanner_name,US"fsecure") == 0) { + uschar *fsecure_options; + uschar fsecure_options_buffer[1024]; + uschar fsecure_options_default[] = "/var/run/.fsav"; + struct sockaddr_un server; + int sock, i, j, bread = 0; + uschar file_name[1024]; + uschar av_buffer[1024]; + pcre *fs_inf; + static uschar *cmdoptions[] = { "CONFIGURE\tARCHIVE\t1\n","CONFIGURE\tTIMEOUT\t0\n","CONFIGURE\tMAXARCH\t5\n","CONFIGURE\tMIME\t1\n" }; + + malware_name = NULL; + if ((fsecure_options = string_nextinlist(&av_scanner_work, &sep, + fsecure_options_buffer, + sizeof(fsecure_options_buffer))) == NULL) { + /* no options supplied, use default options */ + fsecure_options = fsecure_options_default; + }; + + /* open the fsecure socket */ + sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock < 0) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: unable to open fsecure socket %s (%s)", + fsecure_options, strerror(errno)); + return DEFER; + } + server.sun_family = AF_UNIX; + Ustrcpy(server.sun_path, fsecure_options); + if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: unable to connect to fsecure socket %s (%s)", + fsecure_options, strerror(errno)); + return DEFER; + } + + /* pass options */ + memset(av_buffer, 0, sizeof(av_buffer)); + for (i=0; i != 4; i++) { + /* debug_printf("send option \"%s\"",cmdoptions[i]); */ + if (write(sock, cmdoptions[i], Ustrlen(cmdoptions[i])) < 0) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: unable to write fsecure option %d to %s (%s)", + i, fsecure_options, strerror(errno)); + return DEFER; + }; + + bread = read(sock, av_buffer, sizeof(av_buffer)); + if (bread >0) av_buffer[bread]='\0'; + if (bread < 0) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: unable to read fsecure answer %d (%s)", i, strerror(errno)); + return DEFER; + }; + for (j=0;j= 2) { + /* Got it */ + pcre_copy_substring(CS av_buffer, ovector, i, 1, CS malware_name_buffer, 255); + malware_name = malware_name_buffer; + }; + }; + } + while (Ustrstr(av_buffer, "OK\tScan ok.") == NULL); + close(sock); + } + /* ----------------------------------------------------------------------- */ + + /* "kavdaemon" scanner type ------------------------------------------------ */ + else if (strcmpic(scanner_name,US"kavdaemon") == 0) { + uschar *kav_options; + uschar kav_options_buffer[1024]; + uschar kav_options_default[] = "/var/run/AvpCtl"; + struct sockaddr_un server; + int sock; + time_t t; + uschar tmpbuf[1024]; + uschar scanrequest[1024]; + uschar kav_match_string[128]; + int kav_rc; + unsigned long kav_reportlen, bread; + pcre *kav_re; + + if ((kav_options = string_nextinlist(&av_scanner_work, &sep, + kav_options_buffer, + sizeof(kav_options_buffer))) == NULL) { + /* no options supplied, use default options */ + kav_options = kav_options_default; + }; + + /* open the kavdaemon socket */ + sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock < 0) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: can't open UNIX socket."); + return DEFER; + } + server.sun_family = AF_UNIX; + Ustrcpy(server.sun_path, kav_options); + if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: unable to connect to kavdaemon UNIX socket (%s). errno=%d", kav_options, errno); + return DEFER; + } + + /* get current date and time, build scan request */ + time(&t); + strftime(CS tmpbuf, sizeof(tmpbuf), "<0>%d %b %H:%M:%S:%%s/scan/%%s", localtime(&t)); + snprintf(CS scanrequest, 1024,CS tmpbuf, spool_directory, message_id); + + /* send scan request */ + if (send(sock, scanrequest, Ustrlen(scanrequest)+1, 0) < 0) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: unable to write to kavdaemon UNIX socket (%s)", kav_options); + return DEFER; + } + + /* wait for result */ + if ((bread = recv(sock, tmpbuf, 2, 0) != 2)) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: unable to read 2 bytes from kavdaemon socket."); + return DEFER; + } + + /* get errorcode from one nibble */ + if (test_byte_order() == LITTLE_MY_ENDIAN) { + kav_rc = tmpbuf[0] & 0x0F; + } + else { + kav_rc = tmpbuf[1] & 0x0F; + }; + + /* improper kavdaemon configuration */ + if ( (kav_rc == 5) || (kav_rc == 6) ) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: please reconfigure kavdaemon to NOT disinfect or remove infected files."); + return DEFER; + }; + + if (kav_rc == 1) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: kavdaemon reported 'scanning not completed' (code 1)."); + return DEFER; + }; + + if (kav_rc == 7) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: kavdaemon reported 'kavdaemon damaged' (code 7)."); + return DEFER; + }; + + /* code 8 is not handled, since it is ambigous. It appears mostly on + bounces where part of a file has been cut off */ + + /* "virus found" return codes (2-4) */ + if ((kav_rc > 1) && (kav_rc < 5)) { + int report_flag = 0; + + /* setup default virus name */ + Ustrcpy(malware_name_buffer,"unknown"); + malware_name = malware_name_buffer; + + if (test_byte_order() == LITTLE_MY_ENDIAN) { + report_flag = tmpbuf[1]; + } + else { + report_flag = tmpbuf[0]; + }; + + /* read the report, if available */ + if( report_flag == 1 ) { + /* read report size */ + if ((bread = recv(sock, &kav_reportlen, 4, 0)) != 4) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: cannot read report size from kavdaemon"); + return DEFER; + }; + + /* it's possible that avp returns av_buffer[1] == 1 but the + reportsize is 0 (!?) */ + if (kav_reportlen > 0) { + /* set up match regex, depends on retcode */ + if( kav_rc == 3 ) + Ustrcpy(kav_match_string, "suspicion:\\s*(.+?)\\s*$"); + else + Ustrcpy(kav_match_string, "infected:\\s*(.+?)\\s*$"); + + kav_re = pcre_compile( CS kav_match_string, + PCRE_COPT, + (const char **)&rerror, + &roffset, + NULL ); + + /* read report, linewise */ + while (kav_reportlen > 0) { + int result = 0; + int ovector[30]; + + bread = 0; + while ( recv(sock, &tmpbuf[bread], 1, 0) == 1 ) { + kav_reportlen--; + if ( (tmpbuf[bread] == '\n') || (bread > 1021) ) break; + bread++; + }; + bread++; + tmpbuf[bread] = '\0'; + + /* try matcher on the line, grab substring */ + result = pcre_exec(kav_re, NULL, CS tmpbuf, Ustrlen(tmpbuf), 0, 0, ovector, 30); + if (result >= 2) { + pcre_copy_substring(CS tmpbuf, ovector, result, 1, CS malware_name_buffer, 255); + break; + }; + }; + }; + }; + } + else { + /* no virus found */ + malware_name = NULL; + }; + + close(sock); + } + /* ----------------------------------------------------------------------- */ + + + /* "cmdline" scanner type ------------------------------------------------ */ + else if (strcmpic(scanner_name,US"cmdline") == 0) { + uschar *cmdline_scanner; + uschar cmdline_scanner_buffer[1024]; + uschar *cmdline_trigger; + uschar cmdline_trigger_buffer[1024]; + const pcre *cmdline_trigger_re; + uschar *cmdline_regex; + uschar cmdline_regex_buffer[1024]; + const pcre *cmdline_regex_re; + uschar file_name[1024]; + uschar commandline[1024]; + void (*eximsigchld)(int); + void (*eximsigpipe)(int); + FILE *scanner_out = NULL; + FILE *scanner_record = NULL; + uschar linebuffer[32767]; + int trigger = 0; + int result; + int ovector[30]; + + /* find scanner command line */ + if ((cmdline_scanner = string_nextinlist(&av_scanner_work, &sep, + cmdline_scanner_buffer, + sizeof(cmdline_scanner_buffer))) == NULL) { + /* no command line supplied */ + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: missing commandline specification for cmdline scanner type."); + return DEFER; + }; + + /* find scanner output trigger */ + if ((cmdline_trigger = string_nextinlist(&av_scanner_work, &sep, + cmdline_trigger_buffer, + sizeof(cmdline_trigger_buffer))) == NULL) { + /* no trigger regex supplied */ + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: missing trigger specification for cmdline scanner type."); + return DEFER; + }; + + /* precompile trigger regex */ + cmdline_trigger_re = pcre_compile(CS cmdline_trigger, PCRE_COPT, (const char **)&rerror, &roffset, NULL); + if (cmdline_trigger_re == NULL) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: regular expression error in '%s': %s at offset %d", cmdline_trigger_re, rerror, roffset); + return DEFER; + }; + + /* find scanner name regex */ + if ((cmdline_regex = string_nextinlist(&av_scanner_work, &sep, + cmdline_regex_buffer, + sizeof(cmdline_regex_buffer))) == NULL) { + /* no name regex supplied */ + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: missing virus name regex specification for cmdline scanner type."); + return DEFER; + }; + + /* precompile name regex */ + cmdline_regex_re = pcre_compile(CS cmdline_regex, PCRE_COPT, (const char **)&rerror, &roffset, NULL); + if (cmdline_regex_re == NULL) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: regular expression error in '%s': %s at offset %d", cmdline_regex_re, rerror, roffset); + return DEFER; + }; + + /* prepare scanner call */ + snprintf(CS file_name,1024,"%s/scan/%s", spool_directory, message_id); + snprintf(CS commandline,1024, CS cmdline_scanner,file_name); + /* redirect STDERR too */ + Ustrcat(commandline," 2>&1"); + + /* store exims signal handlers */ + eximsigchld = signal(SIGCHLD,SIG_DFL); + eximsigpipe = signal(SIGPIPE,SIG_DFL); + + scanner_out = popen(CS commandline,"r"); + if (scanner_out == NULL) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: calling cmdline scanner (%s) failed: %s.", commandline, strerror(errno)); + signal(SIGCHLD,eximsigchld); + signal(SIGPIPE,eximsigpipe); + return DEFER; + }; + + snprintf(CS file_name,1024,"%s/scan/%s/%s_scanner_output", spool_directory, message_id, message_id); + scanner_record = fopen(CS file_name,"w"); + + if (scanner_record == NULL) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: opening scanner output file (%s) failed: %s.", file_name, strerror(errno)); + pclose(scanner_out); + signal(SIGCHLD,eximsigchld); + signal(SIGPIPE,eximsigpipe); + return DEFER; + }; + + /* look for trigger while recording output */ + while(fgets(CS linebuffer,32767,scanner_out) != NULL) { + if ( Ustrlen(linebuffer) > fwrite(linebuffer, 1, Ustrlen(linebuffer), scanner_record) ) { + /* short write */ + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: short write on scanner output file (%s).", file_name); + pclose(scanner_out); + signal(SIGCHLD,eximsigchld); + signal(SIGPIPE,eximsigpipe); + return DEFER; + }; + /* try trigger match */ + if (!trigger && regex_match_and_setup(cmdline_trigger_re, linebuffer, 0, -1)) + trigger = 1; + }; + + fclose(scanner_record); + pclose(scanner_out); + signal(SIGCHLD,eximsigchld); + signal(SIGPIPE,eximsigpipe); + + if (trigger) { + /* setup default virus name */ + Ustrcpy(malware_name_buffer,"unknown"); + malware_name = malware_name_buffer; + + /* re-open the scanner output file, look for name match */ + scanner_record = fopen(CS file_name,"r"); + while(fgets(CS linebuffer,32767,scanner_record) != NULL) { + /* try match */ + result = pcre_exec(cmdline_regex_re, NULL, CS linebuffer, Ustrlen(linebuffer), 0, 0, ovector, 30); + if (result >= 2) { + pcre_copy_substring(CS linebuffer, ovector, result, 1, CS malware_name_buffer, 255); + }; + }; + fclose(scanner_record); + } + else { + /* no virus found */ + malware_name = NULL; + }; + } + /* ----------------------------------------------------------------------- */ + + + /* "sophie" scanner type ------------------------------------------------- */ + else if (strcmpic(scanner_name,US"sophie") == 0) { + uschar *sophie_options; + uschar sophie_options_buffer[1024]; + uschar sophie_options_default[] = "/var/run/sophie"; + int bread = 0; + struct sockaddr_un server; + int sock; + uschar file_name[1024]; + uschar av_buffer[1024]; + + if ((sophie_options = string_nextinlist(&av_scanner_work, &sep, + sophie_options_buffer, + sizeof(sophie_options_buffer))) == NULL) { + /* no options supplied, use default options */ + sophie_options = sophie_options_default; + }; + + /* open the sophie socket */ + sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock < 0) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: can't open UNIX socket."); + return DEFER; + } + server.sun_family = AF_UNIX; + Ustrcpy(server.sun_path, sophie_options); + if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: unable to connect to sophie UNIX socket (%s). errno=%d", sophie_options, errno); + return DEFER; + } + + /* pass the scan directory to sophie */ + snprintf(CS file_name,1024,"%s/scan/%s", spool_directory, message_id); + if (write(sock, file_name, Ustrlen(file_name)) < 0) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: unable to write to sophie UNIX socket (%s)", sophie_options); + return DEFER; + }; + + write(sock, "\n", 1); + + /* wait for result */ + memset(av_buffer, 0, sizeof(av_buffer)); + if ((!(bread = read(sock, av_buffer, sizeof(av_buffer))) > 0)) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: unable to read from sophie UNIX socket (%s)", sophie_options); + return DEFER; + }; + + close(sock); + + /* infected ? */ + if (av_buffer[0] == '1') { + if (Ustrchr(av_buffer, '\n')) *Ustrchr(av_buffer, '\n') = '\0'; + Ustrcpy(malware_name_buffer,&av_buffer[2]); + malware_name = malware_name_buffer; + } + else if (!strncmp(CS av_buffer, "-1", 2)) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: malware acl condition: sophie reported error"); + return DEFER; + } + else { + /* all ok, no virus */ + malware_name = NULL; + }; + } + /* ----------------------------------------------------------------------- */ + + + /* "clamd" scanner type ------------------------------------------------- */ + /* This code was contributed by David Saez */ + else if (strcmpic(scanner_name,US"clamd") == 0) { + uschar *clamd_options; + uschar clamd_options_buffer[1024]; + uschar clamd_options_default[] = "/tmp/clamd"; + uschar *p,*vname; + struct sockaddr_un server; + int sock,port,bread=0; + uschar file_name[1024]; + uschar av_buffer[1024]; + uschar hostname[256]; + struct hostent *he; + struct in_addr in; + + if ((clamd_options = string_nextinlist(&av_scanner_work, &sep, + clamd_options_buffer, + sizeof(clamd_options_buffer))) == NULL) { + /* no options supplied, use default options */ + clamd_options = clamd_options_default; + } + + /* socket does not start with '/' -> network socket */ + if (*clamd_options != '/') { + + /* extract host and port part */ + if( sscanf(CS clamd_options, "%s %u", hostname, &port) != 2 ) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: clamd: invalid socket '%s'", clamd_options); + return DEFER; + }; + + /* Lookup the host */ + if((he = gethostbyname(CS hostname)) == 0) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: clamd: failed to lookup host '%s'", hostname); + return DEFER; + } + + in = *(struct in_addr *) he->h_addr_list[0]; + + /* Open the ClamAV Socket */ + if ( (sock = ip_socket(SOCK_STREAM, AF_INET)) < 0) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: clamd: unable to acquire socket (%s)", + strerror(errno)); + return DEFER; + } + + if (ip_connect(sock, AF_INET, (uschar*)inet_ntoa(in), port, 5) < 0) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: clamd: connection to %s, port %u failed (%s)", + inet_ntoa(in), port, strerror(errno)); + return DEFER; + } + } + else { + /* open the local socket */ + if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: clamd: unable to acquire socket (%s)", + strerror(errno)); + return DEFER; + } + + server.sun_family = AF_UNIX; + Ustrcpy(server.sun_path, clamd_options); + + if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: clamd: unable to connect to UNIX socket %s (%s)", + clamd_options, strerror(errno) ); + return DEFER; + } + } + + /* Pass the string to ClamAV (7 = "SCAN \n" + \0) */ + + snprintf(CS file_name,1024,"SCAN %s/scan/%s\n", spool_directory, message_id); + + if (send(sock, file_name, Ustrlen(file_name), 0) < 0) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC,"malware acl condition: clamd: unable to write to socket (%s)", + strerror(errno)); + return DEFER; + } + + /* + We're done sending, close socket for writing. + + One user reported that clamd 0.70 does not like this any more ... + + */ + + /* shutdown(sock, SHUT_WR); */ + + /* Read the result */ + memset(av_buffer, 0, sizeof(av_buffer)); + bread = read(sock, av_buffer, sizeof(av_buffer)); + close(sock); + + if (!(bread > 0)) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: clamd: unable to read from socket (%s)", + strerror(errno)); + return DEFER; + } + + if (bread == sizeof(av_buffer)) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: clamd: buffer too small"); + return DEFER; + } + + /* Check the result. ClamAV Returns + infected: -> ": FOUND" + not-infected: -> ": OK" + error: -> ": ERROR */ + + if (!(*av_buffer)) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: clamd: ClamAV returned null"); + return DEFER; + } + + /* colon in returned output? */ + if((p = Ustrrchr(av_buffer,':')) == NULL) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: clamd: ClamAV returned malformed result: %s", + av_buffer); + return DEFER; + } + + /* strip filename strip CR at the end */ + ++p; + while (*p == ' ') ++p; + vname = p; + p = vname + Ustrlen(vname) - 1; + if( *p == '\n' ) *p = '\0'; + + if ((p = Ustrstr(vname, "FOUND"))!=NULL) { + *p=0; + for (--p;p>vname && *p<=32;p--) *p=0; + for (;*vname==32;vname++); + Ustrcpy(malware_name_buffer,vname); + malware_name = malware_name_buffer; + } + else { + if (Ustrstr(vname, "ERROR")!=NULL) { + /* ClamAV reports ERROR + Find line start */ + for (;*vname!='\n' && vname>av_buffer; vname--); + if (*vname=='\n') vname++; + + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: clamd: ClamAV returned %s",vname); + return DEFER; + } + else { + /* Everything should be OK */ + malware_name = NULL; + } + } + } + /* ----------------------------------------------------------------------- */ + + + /* "mksd" scanner type --------------------------------------------------- */ + else if (strcmpic(scanner_name,US"mksd") == 0) { + uschar *mksd_options; + char *mksd_options_end; + uschar mksd_options_buffer[32]; + int mksd_maxproc = 1; /* default, if no option supplied */ + struct sockaddr_un server; + int sock; + int retval; + + if ((mksd_options = string_nextinlist(&av_scanner_work, &sep, + mksd_options_buffer, + sizeof(mksd_options_buffer))) != NULL) { + mksd_maxproc = (int) strtol(CS mksd_options, &mksd_options_end, 10); + if ((*mksd_options == '\0') || (*mksd_options_end != '\0') || + (mksd_maxproc < 1) || (mksd_maxproc > 32)) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: mksd: invalid option '%s'", mksd_options); + return DEFER; + } + } + + /* open the mksd socket */ + sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock < 0) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: can't open UNIX socket."); + return DEFER; + } + server.sun_family = AF_UNIX; + Ustrcpy(server.sun_path, "/var/run/mksd/socket"); + if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { + close(sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: unable to connect to mksd UNIX socket (/var/run/mksd/socket). errno=%d", errno); + return DEFER; + } + + malware_name = NULL; + + retval = mksd_scan_packed(sock); + + if (retval != OK) + return retval; + } + /* ----------------------------------------------------------------------- */ + + /* "unknown" scanner type ------------------------------------------------- */ + else { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware condition: unknown scanner type '%s'", scanner_name); + return DEFER; + }; + /* ----------------------------------------------------------------------- */ + + /* set "been here, done that" marker */ + malware_ok = 1; + }; + + /* match virus name against pattern (caseless ------->----------v) */ + if ( (malware_name != NULL) && + (regex_match_and_setup(re, malware_name, 0, -1)) ) { + return OK; + } + else { + return FAIL; + }; +} + + +/* simple wrapper for reading lines from sockets */ +int recv_line(int sock, uschar *buffer, int size) { + uschar *p = buffer; + + memset(buffer,0,size); + /* read until \n */ + while(recv(sock,p,1,0) > -1) { + if ((p-buffer) > (size-2)) break; + if (*p == '\n') break; + if (*p != '\r') p++; + }; + *p = '\0'; + + return (p-buffer); +} + + +/* ============= private routines for the "mksd" scanner type ============== */ + +#include + +int mksd_writev (int sock, struct iovec *iov, int iovcnt) +{ + int i; + + for (;;) { + do + i = writev (sock, iov, iovcnt); + while ((i < 0) && (errno == EINTR)); + if (i <= 0) { + close (sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: unable to write to mksd UNIX socket (/var/run/mksd/socket)"); + return -1; + } + + for (;;) + if (i >= iov->iov_len) { + if (--iovcnt == 0) + return 0; + i -= iov->iov_len; + iov++; + } else { + iov->iov_len -= i; + iov->iov_base = CS iov->iov_base + i; + break; + } + } +} + +int mksd_read_lines (int sock, uschar *av_buffer, int av_buffer_size) +{ + int offset = 0; + int i; + + do { + if ((i = recv (sock, av_buffer+offset, av_buffer_size-offset, 0)) <= 0) { + close (sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: unable to read from mksd UNIX socket (/var/run/mksd/socket)"); + return -1; + } + + offset += i; + /* offset == av_buffer_size -> buffer full */ + if (offset == av_buffer_size) { + close (sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: malformed reply received from mksd"); + return -1; + } + } while (av_buffer[offset-1] != '\n'); + + av_buffer[offset] = '\0'; + return offset; +} + +int mksd_parse_line (char *line) +{ + char *p; + + switch (*line) { + case 'O': + /* OK */ + return OK; + case 'E': + case 'A': + /* ERR */ + if ((p = strchr (line, '\n')) != NULL) + (*p) = '\0'; + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: mksd scanner failed: %s", line); + return DEFER; + default: + /* VIR */ + if ((p = strchr (line, '\n')) != NULL) { + (*p) = '\0'; + if (((p-line) > 5) && ((p-line) < sizeof (malware_name_buffer)) && (line[3] == ' ')) + if (((p = strchr (line+4, ' ')) != NULL) && ((p-line) > 4)) { + (*p) = '\0'; + Ustrcpy (malware_name_buffer, line+4); + malware_name = malware_name_buffer; + return OK; + } + } + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: malformed reply received from mksd: %s", line); + return DEFER; + } +} + +int mksd_scan_packed (int sock) +{ + struct iovec iov[7]; + char *cmd = "MSQ/scan/.eml\n"; + uschar av_buffer[1024]; + + iov[0].iov_base = cmd; + iov[0].iov_len = 3; + iov[1].iov_base = CS spool_directory; + iov[1].iov_len = Ustrlen (spool_directory); + iov[2].iov_base = cmd + 3; + iov[2].iov_len = 6; + iov[3].iov_base = iov[5].iov_base = CS message_id; + iov[3].iov_len = iov[5].iov_len = Ustrlen (message_id); + iov[4].iov_base = cmd + 3; + iov[4].iov_len = 1; + iov[6].iov_base = cmd + 9; + iov[6].iov_len = 5; + + if (mksd_writev (sock, iov, 7) < 0) + return DEFER; + + if (mksd_read_lines (sock, av_buffer, sizeof (av_buffer)) < 0) + return DEFER; + + close (sock); + + return mksd_parse_line (CS av_buffer); +} + +#endif diff --git a/src/src/mime.c b/src/src/mime.c new file mode 100644 index 000000000..1e8deec1b --- /dev/null +++ b/src/src/mime.c @@ -0,0 +1,730 @@ +/* $Cambridge: exim/src/src/mime.c,v 1.2 2004/12/16 15:11:47 tom Exp $ */ + +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) Tom Kistner 2004 */ +/* License: GPL */ + +#include "exim.h" +#ifdef WITH_CONTENT_SCAN +#include "mime.h" +#include + +FILE *mime_stream = NULL; +uschar *mime_current_boundary = NULL; + +/************************************************* +* set MIME anomaly level + text * +*************************************************/ + +/* Small wrapper to set the two expandables which + give info on detected "problems" in MIME + encodings. Those are defined in mime.h. */ + +void mime_set_anomaly(int level, char *text) { + mime_anomaly_level = level; + mime_anomaly_text = text; +}; + + +/************************************************* +* decode quoted-printable chars * +*************************************************/ + +/* gets called when we hit a = + returns: new pointer position + result code in c: + -2 - decode error + -1 - soft line break, no char + 0-255 - char to write +*/ + +unsigned int mime_qp_hstr_i(uschar *cptr) { + unsigned int i, j = 0; + while (cptr && *cptr && isxdigit(*cptr)) { + i = *cptr++ - '0'; + if (9 < i) i -= 7; + j <<= 4; + j |= (i & 0x0f); + } + return(j); +} + +uschar *mime_decode_qp_char(uschar *qp_p,int *c) { + uschar hex[] = {0,0,0}; + int nan = 0; + uschar *initial_pos = qp_p; + + /* advance one char */ + qp_p++; + + REPEAT_FIRST: + if ( (*qp_p == '\t') || (*qp_p == ' ') || (*qp_p == '\r') ) { + /* tab or whitespace may follow + just ignore it, but remember + that this is not a valid hex + encoding any more */ + nan = 1; + qp_p++; + goto REPEAT_FIRST; + } + else if ( (('0' <= *qp_p) && (*qp_p <= '9')) || (('A' <= *qp_p) && (*qp_p <= 'F')) || (('a' <= *qp_p) && (*qp_p <= 'f')) ) { + /* this is a valid hex char, if nan is unset */ + if (nan) { + /* this is illegal */ + *c = -2; + return initial_pos; + } + else { + hex[0] = *qp_p; + qp_p++; + }; + } + else if (*qp_p == '\n') { + /* hit soft line break already, continue */ + *c = -1; + return qp_p; + } + else { + /* illegal char here */ + *c = -2; + return initial_pos; + }; + + if ( (('0' <= *qp_p) && (*qp_p <= '9')) || (('A' <= *qp_p) && (*qp_p <= 'F')) || (('a' <= *qp_p) && (*qp_p <= 'f')) ) { + if (hex[0] > 0) { + hex[1] = *qp_p; + /* do hex conversion */ + *c = mime_qp_hstr_i(hex); + qp_p++; + return qp_p; + } + else { + /* huh ? */ + *c = -2; + return initial_pos; + }; + } + else { + /* illegal char */ + *c = -2; + return initial_pos; + }; +} + + +uschar *mime_parse_line(uschar *buffer, uschar *encoding, int *num_decoded) { + uschar *data = NULL; + + data = (uschar *)malloc(Ustrlen(buffer)+2); + + if (encoding == NULL) { + /* no encoding type at all */ + NO_DECODING: + memcpy(data, buffer, Ustrlen(buffer)); + data[(Ustrlen(buffer))] = 0; + *num_decoded = Ustrlen(data); + return data; + } + else if (Ustrcmp(encoding,"base64") == 0) { + uschar *p = buffer; + int offset = 0; + + /* ----- BASE64 ---------------------------------------------------- */ + /* NULL out '\r' and '\n' chars */ + while (Ustrrchr(p,'\r') != NULL) { + *(Ustrrchr(p,'\r')) = '\0'; + }; + while (Ustrrchr(p,'\n') != NULL) { + *(Ustrrchr(p,'\n')) = '\0'; + }; + + while (*(p+offset) != '\0') { + /* hit illegal char ? */ + if (mime_b64[*(p+offset)] == 128) { + mime_set_anomaly(MIME_ANOMALY_BROKEN_BASE64); + offset++; + } + else { + *p = mime_b64[*(p+offset)]; + p++; + }; + }; + *p = 255; + + /* line is translated, start bit shifting */ + p = buffer; + *num_decoded = 0; + while(*p != 255) { + uschar tmp_c; + + /* byte 0 ---------------------- */ + if (*(p+1) == 255) { + mime_set_anomaly(MIME_ANOMALY_BROKEN_BASE64); + break; + } + data[(*num_decoded)] = *p; + data[(*num_decoded)] <<= 2; + tmp_c = *(p+1); + tmp_c >>= 4; + data[(*num_decoded)] |= tmp_c; + (*num_decoded)++; + p++; + /* byte 1 ---------------------- */ + if (*(p+1) == 255) { + mime_set_anomaly(MIME_ANOMALY_BROKEN_BASE64); + break; + } + data[(*num_decoded)] = *p; + data[(*num_decoded)] <<= 4; + tmp_c = *(p+1); + tmp_c >>= 2; + data[(*num_decoded)] |= tmp_c; + (*num_decoded)++; + p++; + /* byte 2 ---------------------- */ + if (*(p+1) == 255) { + mime_set_anomaly(MIME_ANOMALY_BROKEN_BASE64); + break; + } + data[(*num_decoded)] = *p; + data[(*num_decoded)] <<= 6; + data[(*num_decoded)] |= *(p+1); + (*num_decoded)++; + p+=2; + + }; + return data; + /* ----------------------------------------------------------------- */ + } + else if (Ustrcmp(encoding,"quoted-printable") == 0) { + uschar *p = buffer; + + /* ----- QP -------------------------------------------------------- */ + *num_decoded = 0; + while (*p != 0) { + if (*p == '=') { + int decode_qp_result; + + p = mime_decode_qp_char(p,&decode_qp_result); + + if (decode_qp_result == -2) { + /* Error from decoder. p is unchanged. */ + mime_set_anomaly(MIME_ANOMALY_BROKEN_QP); + data[(*num_decoded)] = '='; + (*num_decoded)++; + p++; + } + else if (decode_qp_result == -1) { + break; + } + else if (decode_qp_result >= 0) { + data[(*num_decoded)] = decode_qp_result; + (*num_decoded)++; + }; + } + else { + data[(*num_decoded)] = *p; + (*num_decoded)++; + p++; + }; + }; + return data; + /* ----------------------------------------------------------------- */ + } + /* unknown encoding type, just dump as-is */ + else goto NO_DECODING; +} + + +FILE *mime_get_decode_file(uschar *pname, uschar *fname) { + FILE *f; + uschar *filename; + + filename = (uschar *)malloc(2048); + + if ((pname != NULL) && (fname != NULL)) { + snprintf(CS filename, 2048, "%s/%s", pname, fname); + f = fopen(CS filename,"w+"); + } + else if (pname == NULL) { + f = fopen(CS fname,"w+"); + } + else if (fname == NULL) { + int file_nr = 0; + int result = 0; + + /* must find first free sequential filename */ + do { + struct stat mystat; + snprintf(CS filename,2048,"%s/%s-%05u", pname, message_id, file_nr); + file_nr++; + /* security break */ + if (file_nr >= 1024) + break; + result = stat(CS filename,&mystat); + } + while(result != -1); + f = fopen(CS filename,"w+"); + }; + + /* set expansion variable */ + mime_decoded_filename = filename; + + return f; +} + + +int mime_decode(uschar **listptr) { + int sep = 0; + uschar *list = *listptr; + uschar *option; + uschar option_buffer[1024]; + uschar decode_path[1024]; + FILE *decode_file = NULL; + uschar *buffer = NULL; + long f_pos = 0; + unsigned int size_counter = 0; + + if (mime_stream == NULL) + return FAIL; + + f_pos = ftell(mime_stream); + + /* build default decode path (will exist since MBOX must be spooled up) */ + snprintf(CS decode_path,1024,"%s/scan/%s",spool_directory,message_id); + + /* reserve a line buffer to work in */ + buffer = (uschar *)malloc(MIME_MAX_LINE_LENGTH+1); + if (buffer == NULL) { + log_write(0, LOG_PANIC, + "decode ACL condition: can't allocate %d bytes of memory.", MIME_MAX_LINE_LENGTH+1); + return DEFER; + }; + + /* try to find 1st option */ + if ((option = string_nextinlist(&list, &sep, + option_buffer, + sizeof(option_buffer))) != NULL) { + + /* parse 1st option */ + if ( (Ustrcmp(option,"false") == 0) || (Ustrcmp(option,"0") == 0) ) { + /* explicitly no decoding */ + return FAIL; + }; + + if (Ustrcmp(option,"default") == 0) { + /* explicit default path + file names */ + goto DEFAULT_PATH; + }; + + if (option[0] == '/') { + struct stat statbuf; + + memset(&statbuf,0,sizeof(statbuf)); + + /* assume either path or path+file name */ + if ( (stat(CS option, &statbuf) == 0) && S_ISDIR(statbuf.st_mode) ) + /* is directory, use it as decode_path */ + decode_file = mime_get_decode_file(option, NULL); + else + /* does not exist or is a file, use as full file name */ + decode_file = mime_get_decode_file(NULL, option); + } + else + /* assume file name only, use default path */ + decode_file = mime_get_decode_file(decode_path, option); + } + else + /* no option? patch default path */ + DEFAULT_PATH: decode_file = mime_get_decode_file(decode_path, NULL); + + if (decode_file == NULL) + return DEFER; + + /* read data linewise and dump it to the file, + while looking for the current boundary */ + while(fgets(CS buffer, MIME_MAX_LINE_LENGTH, mime_stream) != NULL) { + uschar *decoded_line = NULL; + int decoded_line_length = 0; + + if (mime_current_boundary != NULL) { + /* boundary line must start with 2 dashes */ + if (Ustrncmp(buffer,"--",2) == 0) { + if (Ustrncmp((buffer+2),mime_current_boundary,Ustrlen(mime_current_boundary)) == 0) + break; + }; + }; + + decoded_line = mime_parse_line(buffer, mime_content_transfer_encoding, &decoded_line_length); + /* write line to decode file */ + if (fwrite(decoded_line, 1, decoded_line_length, decode_file) < decoded_line_length) { + /* error/short write */ + clearerr(mime_stream); + fseek(mime_stream,f_pos,SEEK_SET); + return DEFER; + }; + size_counter += decoded_line_length; + + if (size_counter > 1023) { + if ((mime_content_size + (size_counter / 1024)) < 65535) + mime_content_size += (size_counter / 1024); + else + mime_content_size = 65535; + size_counter = (size_counter % 1024); + }; + + free(decoded_line); + } + + fclose(decode_file); + + clearerr(mime_stream); + fseek(mime_stream,f_pos,SEEK_SET); + + /* round up remaining size bytes to one k */ + if (size_counter) { + mime_content_size++; + }; + + return OK; +} + +int mime_get_header(FILE *f, uschar *header) { + int c = EOF; + int done = 0; + int header_value_mode = 0; + int header_open_brackets = 0; + int num_copied = 0; + + while(!done) { + + c = fgetc(f); + if (c == EOF) break; + + /* always skip CRs */ + if (c == '\r') continue; + + if (c == '\n') { + if (num_copied > 0) { + /* look if next char is '\t' or ' ' */ + c = fgetc(f); + if (c == EOF) break; + if ( (c == '\t') || (c == ' ') ) continue; + ungetc(c,f); + }; + /* end of the header, terminate with ';' */ + c = ';'; + done = 1; + }; + + /* skip control characters */ + if (c < 32) continue; + + if (header_value_mode) { + /* --------- value mode ----------- */ + /* skip leading whitespace */ + if ( ((c == '\t') || (c == ' ')) && (header_value_mode == 1) ) + continue; + + /* we have hit a non-whitespace char, start copying value data */ + header_value_mode = 2; + + /* skip quotes */ + if (c == '"') continue; + + /* leave value mode on ';' */ + if (c == ';') { + header_value_mode = 0; + }; + /* -------------------------------- */ + } + else { + /* -------- non-value mode -------- */ + /* skip whitespace + tabs */ + if ( (c == ' ') || (c == '\t') ) + continue; + if (c == '\\') { + /* quote next char. can be used + to escape brackets. */ + c = fgetc(f); + if (c == EOF) break; + } + else if (c == '(') { + header_open_brackets++; + continue; + } + else if ((c == ')') && header_open_brackets) { + header_open_brackets--; + continue; + } + else if ( (c == '=') && !header_open_brackets ) { + /* enter value mode */ + header_value_mode = 1; + }; + + /* skip chars while we are in a comment */ + if (header_open_brackets > 0) + continue; + /* -------------------------------- */ + }; + + /* copy the char to the buffer */ + header[num_copied] = (uschar)c; + /* raise counter */ + num_copied++; + + /* break if header buffer is full */ + if (num_copied > MIME_MAX_HEADER_SIZE-1) { + done = 1; + }; + }; + + if (header[num_copied-1] != ';') { + header[num_copied-1] = ';'; + }; + + /* 0-terminate */ + header[num_copied] = '\0'; + + /* return 0 for EOF or empty line */ + if ((c == EOF) || (num_copied == 1)) + return 0; + else + return 1; +} + + +int mime_acl_check(FILE *f, struct mime_boundary_context *context, uschar + **user_msgptr, uschar **log_msgptr) { + int rc = OK; + uschar *header = NULL; + struct mime_boundary_context nested_context; + + /* reserve a line buffer to work in */ + header = (uschar *)malloc(MIME_MAX_HEADER_SIZE+1); + if (header == NULL) { + log_write(0, LOG_PANIC, + "acl_smtp_mime: can't allocate %d bytes of memory.", MIME_MAX_HEADER_SIZE+1); + return DEFER; + }; + + /* Not actually used at the moment, but will be vital to fixing + * some RFC 2046 nonconformance later... */ + nested_context.parent = context; + + /* loop through parts */ + while(1) { + + /* reset all per-part mime variables */ + mime_anomaly_level = NULL; + mime_anomaly_text = NULL; + mime_boundary = NULL; + mime_charset = NULL; + mime_decoded_filename = NULL; + mime_filename = NULL; + mime_content_description = NULL; + mime_content_disposition = NULL; + mime_content_id = NULL; + mime_content_transfer_encoding = NULL; + mime_content_type = NULL; + mime_is_multipart = 0; + mime_content_size = 0; + + /* + If boundary is null, we assume that *f is positioned on the start of headers (for example, + at the very beginning of a message. + If a boundary is given, we must first advance to it to reach the start of the next header + block. + */ + + /* NOTE -- there's an error here -- RFC2046 specifically says to + * check for outer boundaries. This code doesn't do that, and + * I haven't fixed this. + * + * (I have moved partway towards adding support, however, by adding + * a "parent" field to my new boundary-context structure.) + */ + if (context != NULL) { + while(fgets(CS header, MIME_MAX_HEADER_SIZE, f) != NULL) { + /* boundary line must start with 2 dashes */ + if (Ustrncmp(header,"--",2) == 0) { + if (Ustrncmp((header+2),context->boundary,Ustrlen(context->boundary)) == 0) { + /* found boundary */ + if (Ustrncmp((header+2+Ustrlen(context->boundary)),"--",2) == 0) { + /* END boundary found */ + debug_printf("End boundary found %s\n", context->boundary); + return rc; + } + else { + debug_printf("Next part with boundary %s\n", context->boundary); + }; + /* can't use break here */ + goto DECODE_HEADERS; + } + }; + } + /* Hit EOF or read error. Ugh. */ + debug_printf("Hit EOF ...\n"); + return rc; + }; + + DECODE_HEADERS: + /* parse headers, set up expansion variables */ + while(mime_get_header(f,header)) { + int i; + /* loop through header list */ + for (i = 0; i < mime_header_list_size; i++) { + uschar *header_value = NULL; + int header_value_len = 0; + + /* found an interesting header? */ + if (strncmpic(mime_header_list[i].name,header,mime_header_list[i].namelen) == 0) { + uschar *p = header + mime_header_list[i].namelen; + /* yes, grab the value (normalize to lower case) + and copy to its corresponding expansion variable */ + while(*p != ';') { + *p = tolower(*p); + p++; + }; + header_value_len = (p - (header + mime_header_list[i].namelen)); + header_value = (uschar *)malloc(header_value_len+1); + memset(header_value,0,header_value_len+1); + p = header + mime_header_list[i].namelen; + Ustrncpy(header_value, p, header_value_len); + debug_printf("Found %s MIME header, value is '%s'\n", mime_header_list[i].name, header_value); + *((uschar **)(mime_header_list[i].value)) = header_value; + + /* make p point to the next character after the closing ';' */ + p += (header_value_len+1); + + /* grab all param=value tags on the remaining line, check if they are interesting */ + NEXT_PARAM_SEARCH: while (*p != 0) { + int j; + for (j = 0; j < mime_parameter_list_size; j++) { + uschar *param_value = NULL; + int param_value_len = 0; + + /* found an interesting parameter? */ + if (strncmpic(mime_parameter_list[j].name,p,mime_parameter_list[j].namelen) == 0) { + uschar *q = p + mime_parameter_list[j].namelen; + /* yes, grab the value and copy to its corresponding expansion variable */ + while(*q != ';') q++; + param_value_len = (q - (p + mime_parameter_list[j].namelen)); + param_value = (uschar *)malloc(param_value_len+1); + memset(param_value,0,param_value_len+1); + q = p + mime_parameter_list[j].namelen; + Ustrncpy(param_value, q, param_value_len); + param_value = rfc2047_decode(param_value, TRUE, NULL, 32, ¶m_value_len, &q); + debug_printf("Found %s MIME parameter in %s header, value is '%s'\n", mime_parameter_list[j].name, mime_header_list[i].name, param_value); + *((uschar **)(mime_parameter_list[j].value)) = param_value; + p += (mime_parameter_list[j].namelen + param_value_len + 1); + goto NEXT_PARAM_SEARCH; + }; + } + /* There is something, but not one of our interesting parameters. + Advance to the next semicolon */ + while(*p != ';') p++; + p++; + }; + }; + }; + }; + + /* set additional flag variables (easier access) */ + if ( (mime_content_type != NULL) && + (Ustrncmp(mime_content_type,"multipart",9) == 0) ) + mime_is_multipart = 1; + + /* Make a copy of the boundary pointer. + Required since mime_boundary is global + and can be overwritten further down in recursion */ + nested_context.boundary = mime_boundary; + + /* raise global counter */ + mime_part_count++; + + /* copy current file handle to global variable */ + mime_stream = f; + mime_current_boundary = context ? context->boundary : 0; + + /* Note the context */ + mime_is_coverletter = !(context && context->context == MBC_ATTACHMENT); + + /* call ACL handling function */ + rc = acl_check(ACL_WHERE_MIME, NULL, acl_smtp_mime, user_msgptr, log_msgptr); + + mime_stream = NULL; + mime_current_boundary = NULL; + + if (rc != OK) break; + + /* If we have a multipart entity and a boundary, go recursive */ + if ( (mime_content_type != NULL) && + (nested_context.boundary != NULL) && + (Ustrncmp(mime_content_type,"multipart",9) == 0) ) { + debug_printf("Entering multipart recursion, boundary '%s'\n", nested_context.boundary); + + if (context && context->context == MBC_ATTACHMENT) + nested_context.context = MBC_ATTACHMENT; + else if (!Ustrcmp(mime_content_type,"multipart/alternative") + || !Ustrcmp(mime_content_type,"multipart/related")) + nested_context.context = MBC_COVERLETTER_ALL; + else + nested_context.context = MBC_COVERLETTER_ONESHOT; + + rc = mime_acl_check(f, &nested_context, user_msgptr, log_msgptr); + if (rc != OK) break; + } + else if ( (mime_content_type != NULL) && + (Ustrncmp(mime_content_type,"message/rfc822",14) == 0) ) { + uschar *rfc822name = NULL; + uschar filename[2048]; + int file_nr = 0; + int result = 0; + + /* must find first free sequential filename */ + do { + struct stat mystat; + snprintf(CS filename,2048,"%s/scan/%s/__rfc822_%05u", spool_directory, message_id, file_nr); + file_nr++; + /* security break */ + if (file_nr >= 128) + goto NO_RFC822; + result = stat(CS filename,&mystat); + } + while(result != -1); + + rfc822name = filename; + + /* decode RFC822 attachment */ + mime_decoded_filename = NULL; + mime_stream = f; + mime_current_boundary = context ? context->boundary : NULL; + mime_decode(&rfc822name); + mime_stream = NULL; + mime_current_boundary = NULL; + if (mime_decoded_filename == NULL) { + /* decoding failed */ + log_write(0, LOG_MAIN, + "mime_regex acl condition warning - could not decode RFC822 MIME part to file."); + return DEFER; + }; + mime_decoded_filename = NULL; + }; + + NO_RFC822: + /* If the boundary of this instance is NULL, we are finished here */ + if (context == NULL) break; + + if (context->context == MBC_COVERLETTER_ONESHOT) + context->context = MBC_ATTACHMENT; + + }; + + return rc; +} + +#endif diff --git a/src/src/mime.h b/src/src/mime.h new file mode 100644 index 000000000..cb30645e4 --- /dev/null +++ b/src/src/mime.h @@ -0,0 +1,85 @@ +/* $Cambridge: exim/src/src/mime.h,v 1.2 2004/12/16 15:11:47 tom Exp $ */ + +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) Tom Kistner 2004 */ +/* License: GPL */ + +#ifdef WITH_CONTENT_SCAN + +#define MIME_MAX_HEADER_SIZE 8192 +#define MIME_MAX_LINE_LENGTH 32768 + +#define MBC_ATTACHMENT 0 +#define MBC_COVERLETTER_ONESHOT 1 +#define MBC_COVERLETTER_ALL 2 + +struct mime_boundary_context +{ + struct mime_boundary_context *parent; + unsigned char *boundary; + int context; +}; + +typedef struct mime_header { + uschar *name; + int namelen; + void *value; +} mime_header; + +static mime_header mime_header_list[] = { + { US"content-type:", 13, &mime_content_type }, + { US"content-disposition:", 20, &mime_content_disposition }, + { US"content-transfer-encoding:", 26, &mime_content_transfer_encoding }, + { US"content-id:", 11, &mime_content_id }, + { US"content-description:", 20 , &mime_content_description } +}; + +static int mime_header_list_size = sizeof(mime_header_list)/sizeof(mime_header); + + + +typedef struct mime_parameter { + uschar *name; + int namelen; + void *value; +} mime_parameter; + +static mime_parameter mime_parameter_list[] = { + { US"name=", 5, &mime_filename }, + { US"filename=", 9, &mime_filename }, + { US"charset=", 8, &mime_charset }, + { US"boundary=", 9, &mime_boundary } +}; + +static int mime_parameter_list_size = sizeof(mime_parameter_list)/sizeof(mime_parameter); + + +/* MIME Anomaly list */ +#define MIME_ANOMALY_BROKEN_BASE64 2, "Broken BASE64 encoding detected" +#define MIME_ANOMALY_BROKEN_QP 1, "Broken Quoted-Printable encoding detected" + + +/* BASE64 decoder matrix */ +static unsigned char mime_b64[256]={ +/* 0 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, +/* 16 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, +/* 32 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 62, 128, 128, 128, 63, +/* 48 */ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 128, 128, 128, 255, 128, 128, +/* 64 */ 128, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, +/* 80 */ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 128, 128, 128, 128, 128, +/* 96 */ 128, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, +/* 112 */ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 128, 128, 128, 128, 128, +/* 128 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, +/* 144 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, +/* 160 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, +/* 176 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, +/* 192 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, +/* 208 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, +/* 224 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, +/* 240 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 +}; + +#endif diff --git a/src/src/readconf.c b/src/src/readconf.c index caa78ee90..1c8a4eea5 100644 --- a/src/src/readconf.c +++ b/src/src/readconf.c @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/readconf.c,v 1.2 2004/10/18 09:16:57 ph10 Exp $ */ +/* $Cambridge: exim/src/src/readconf.c,v 1.3 2004/12/16 15:11:47 tom Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -143,6 +143,9 @@ static optionlist optionlist_config[] = { { "acl_smtp_helo", opt_stringptr, &acl_smtp_helo }, { "acl_smtp_mail", opt_stringptr, &acl_smtp_mail }, { "acl_smtp_mailauth", opt_stringptr, &acl_smtp_mailauth }, +#ifdef WITH_CONTENT_SCAN + { "acl_smtp_mime", opt_stringptr, &acl_smtp_mime }, +#endif { "acl_smtp_predata", opt_stringptr, &acl_smtp_predata }, { "acl_smtp_quit", opt_stringptr, &acl_smtp_quit }, { "acl_smtp_rcpt", opt_stringptr, &acl_smtp_rcpt }, @@ -156,7 +159,13 @@ static optionlist optionlist_config[] = { { "allow_utf8_domains", opt_bool, &allow_utf8_domains }, { "auth_advertise_hosts", opt_stringptr, &auth_advertise_hosts }, { "auto_thaw", opt_time, &auto_thaw }, +#ifdef WITH_CONTENT_SCAN + { "av_scanner", opt_stringptr, &av_scanner }, +#endif { "bi_command", opt_stringptr, &bi_command }, +#ifdef EXPERIMENTAL_BRIGHTMAIL + { "bmi_config_file", opt_stringptr, &bmi_config_file }, +#endif { "bounce_message_file", opt_stringptr, &bounce_message_file }, { "bounce_message_text", opt_stringptr, &bounce_message_text }, { "bounce_return_body", opt_bool, &bounce_return_body }, @@ -318,8 +327,14 @@ static optionlist optionlist_config[] = { { "smtp_receive_timeout", opt_time, &smtp_receive_timeout }, { "smtp_reserve_hosts", opt_stringptr, &smtp_reserve_hosts }, { "smtp_return_error_details",opt_bool, &smtp_return_error_details }, +#ifdef WITH_CONTENT_SCAN + { "spamd_address", opt_stringptr, &spamd_address }, +#endif { "split_spool_directory", opt_bool, &split_spool_directory }, { "spool_directory", opt_stringptr, &spool_directory }, +#ifdef EXPERIMENTAL_SRS + { "srs_config", opt_stringptr, &srs_config }, +#endif { "strip_excess_angle_brackets", opt_bool, &strip_excess_angle_brackets }, { "strip_trailing_dot", opt_bool, &strip_trailing_dot }, { "syslog_duplication", opt_bool, &syslog_duplication }, diff --git a/src/src/receive.c b/src/src/receive.c index 0ed05f1db..7b97b4b22 100644 --- a/src/src/receive.c +++ b/src/src/receive.c @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/receive.c,v 1.5 2004/11/25 13:54:31 ph10 Exp $ */ +/* $Cambridge: exim/src/src/receive.c,v 1.6 2004/12/16 15:11:47 tom Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -470,6 +470,11 @@ if (recipients_count >= recipients_list_max) recipients_list[recipients_count].address = recipient; recipients_list[recipients_count].pno = pno; +#ifdef EXPERIMENTAL_BRIGHTMAIL +recipients_list[recipients_count].bmi_optin = bmi_current_optin; +/* reset optin string pointer for next recipient */ +bmi_current_optin = NULL; +#endif recipients_list[recipients_count++].errors_to = NULL; } @@ -918,6 +923,21 @@ for (h = acl_warn_headers; h != NULL; h = next) DEBUG(D_receive|D_acl) debug_printf(" (after Received:)"); break; + case htype_add_rfc: + /* add header before any header which is NOT Received: or Resent- */ + last_received = header_list; + while ( (last_received->next != NULL) && + ( (header_testname(last_received->next, US"Received", 8, FALSE)) || + (header_testname_incomplete(last_received->next, US"Resent-", 7, FALSE)) ) ) + last_received = last_received->next; + /* last_received now points to the last Received: or Resent-* header + in an uninterrupted chain of those header types (seen from the beginning + of all headers. Our current header must follow it. */ + h->next = last_received->next; + last_received->next = h; + DEBUG(D_receive|D_acl) debug_printf(" (before any non-Received: or Resent-*: header)"); + break; + default: h->next = NULL; header_last->next = h; @@ -2716,6 +2736,130 @@ else if (smtp_input && !smtp_batched_input) { + +#ifdef WITH_CONTENT_SCAN + /* MIME ACL hook */ + if (acl_smtp_mime != NULL && recipients_count > 0) + { + FILE *mbox_file; + uschar rfc822_file_path[2048]; + unsigned long long mbox_size; + header_line *my_headerlist; + uschar *user_msg, *log_msg; + int mime_part_count_buffer = -1; + + memset(CS rfc822_file_path,0,2048); + + /* check if it is a MIME message */ + my_headerlist = header_list; + while (my_headerlist != NULL) { + /* skip deleted headers */ + if (my_headerlist->type == '*') { + my_headerlist = my_headerlist->next; + continue; + }; + if (strncmpic(my_headerlist->text, US"Content-Type:", 13) == 0) { + DEBUG(D_receive) debug_printf("Found Content-Type: header - executing acl_smtp_mime.\n"); + goto DO_MIME_ACL; + }; + my_headerlist = my_headerlist->next; + }; + + DEBUG(D_receive) debug_printf("No Content-Type: header - presumably not a MIME message.\n"); + goto NO_MIME_ACL; + + DO_MIME_ACL: + /* make sure the eml mbox file is spooled up */ + mbox_file = spool_mbox(&mbox_size); + if (mbox_file == NULL) { + /* error while spooling */ + log_write(0, LOG_MAIN|LOG_PANIC, + "acl_smtp_mime: error while creating mbox spool file, message temporarily rejected."); + Uunlink(spool_name); + unspool_mbox(); + smtp_respond(451, TRUE, US"temporary local problem"); + message_id[0] = 0; /* Indicate no message accepted */ + smtp_reply = US""; /* Indicate reply already sent */ + goto TIDYUP; /* Skip to end of function */ + }; + + mime_is_rfc822 = 0; + + MIME_ACL_CHECK: + mime_part_count = -1; + rc = mime_acl_check(mbox_file, NULL, &user_msg, &log_msg); + fclose(mbox_file); + + if (Ustrlen(rfc822_file_path) > 0) { + mime_part_count = mime_part_count_buffer; + + if (unlink(CS rfc822_file_path) == -1) { + log_write(0, LOG_PANIC, + "acl_smtp_mime: can't unlink RFC822 spool file, skipping."); + goto END_MIME_ACL; + }; + }; + + /* check if we must check any message/rfc822 attachments */ + if (rc == OK) { + uschar temp_path[1024]; + int n; + struct dirent *entry; + DIR *tempdir; + + snprintf(CS temp_path, 1024, "%s/scan/%s", spool_directory, message_id); + + tempdir = opendir(CS temp_path); + n = 0; + do { + entry = readdir(tempdir); + if (entry == NULL) break; + if (strncmpic(US entry->d_name,US"__rfc822_",9) == 0) { + snprintf(CS rfc822_file_path, 2048,"%s/scan/%s/%s", spool_directory, message_id, entry->d_name); + debug_printf("RFC822 attachment detected: running MIME ACL for '%s'\n", rfc822_file_path); + break; + }; + } while (1); + closedir(tempdir); + + if (entry != NULL) { + mbox_file = Ufopen(rfc822_file_path,"r"); + if (mbox_file == NULL) { + log_write(0, LOG_PANIC, + "acl_smtp_mime: can't open RFC822 spool file, skipping."); + unlink(CS rfc822_file_path); + goto END_MIME_ACL; + }; + /* set RFC822 expansion variable */ + mime_is_rfc822 = 1; + mime_part_count_buffer = mime_part_count; + goto MIME_ACL_CHECK; + }; + }; + + END_MIME_ACL: + add_acl_headers(US"MIME"); + if (rc == DISCARD) + { + recipients_count = 0; + blackholed_by = US"MIME ACL"; + } + else if (rc != OK) + { + Uunlink(spool_name); + unspool_mbox(); + if (smtp_handle_acl_fail(ACL_WHERE_MIME, rc, user_msg, log_msg) != 0) + smtp_yield = FALSE; /* No more messsages after dropped connection */ + smtp_reply = US""; /* Indicate reply already sent */ + message_id[0] = 0; /* Indicate no message accepted */ + goto TIDYUP; /* Skip to end of function */ + }; + } + + NO_MIME_ACL: +#endif /* WITH_CONTENT_SCAN */ + + if (acl_smtp_data != NULL && recipients_count > 0) { uschar *user_msg, *log_msg; @@ -2729,6 +2873,9 @@ else else if (rc != OK) { Uunlink(spool_name); +#ifdef WITH_CONTENT_SCAN + unspool_mbox(); +#endif if (smtp_handle_acl_fail(ACL_WHERE_DATA, rc, user_msg, log_msg) != 0) smtp_yield = FALSE; /* No more messsages after dropped connection */ smtp_reply = US""; /* Indicate reply already sent */ @@ -2779,6 +2926,10 @@ else enable_dollar_recipients = FALSE; } +#ifdef WITH_CONTENT_SCAN +unspool_mbox(); +#endif + /* The final check on the message is to run the scan_local() function. The version supplied with Exim always accepts, but this is a hook for sysadmins to supply their own checking code. The local_scan() function is run even when all @@ -2949,6 +3100,14 @@ signal(SIGINT, SIG_IGN); deliver_firsttime = TRUE; +#ifdef EXPERIMENTAL_BRIGHTMAIL +if (bmi_run == 1) { + /* rewind data file */ + lseek(data_fd, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET); + bmi_verdicts = bmi_process_message(header_list, data_fd); +}; +#endif + /* Update the timstamp in our Received: header to account for any time taken by an ACL or by local_scan(). The new time is the time that all reception processing is complete. */ @@ -3235,12 +3394,29 @@ if (smtp_input) { if (smtp_reply == NULL) { +#ifndef WITH_CONTENT_SCAN smtp_printf("250 OK id=%s\r\n", message_id); +#else + if (fake_reject) + smtp_respond(550,TRUE,fake_reject_text); + else + smtp_printf("250 OK id=%s\r\n", message_id); +#endif if (host_checking) fprintf(stdout, "\n**** SMTP testing: that is not a real message id!\n\n"); } +#ifndef WITH_CONTENT_SCAN else if (smtp_reply[0] != 0) smtp_printf("%.1024s\r\n", smtp_reply); +#else + else if (smtp_reply[0] != 0) + { + if (fake_reject && (smtp_reply[0] == '2')) + smtp_respond(550,TRUE,fake_reject_text); + else + smtp_printf("%.1024s\r\n", smtp_reply); + }; +#endif } /* For batched SMTP, generate an error message on failure, and do diff --git a/src/src/regex.c b/src/src/regex.c new file mode 100644 index 000000000..5c008596d --- /dev/null +++ b/src/src/regex.c @@ -0,0 +1,247 @@ +/* $Cambridge: exim/src/src/regex.c,v 1.2 2004/12/16 15:11:47 tom Exp $ */ + +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) Tom Kistner 2003-???? */ +/* License: GPL */ + +/* Code for matching regular expressions against headers and body. + Called from acl.c. */ + +#include "exim.h" +#ifdef WITH_CONTENT_SCAN +#include +#include + +/* Structure to hold a list of Regular expressions */ +typedef struct pcre_list { + pcre *re; + uschar *pcre_text; + struct pcre_list *next; +} pcre_list; + +uschar regex_match_string_buffer[1024]; + +extern FILE *mime_stream; +extern uschar *mime_current_boundary; + +int regex(uschar **listptr) { + int sep = 0; + uschar *list = *listptr; + uschar *regex_string; + uschar regex_string_buffer[1024]; + unsigned long long mbox_size; + FILE *mbox_file; + pcre *re; + pcre_list *re_list_head = NULL; + pcre_list *re_list_item; + const char *pcre_error; + int pcre_erroffset; + uschar *linebuffer; + long f_pos = 0; + + /* reset expansion variable */ + regex_match_string = NULL; + + if (mime_stream == NULL) { + /* We are in the DATA ACL */ + mbox_file = spool_mbox(&mbox_size); + if (mbox_file == NULL) { + /* error while spooling */ + log_write(0, LOG_MAIN|LOG_PANIC, + "regex acl condition: error while creating mbox spool file"); + return DEFER; + }; + } + else { + f_pos = ftell(mime_stream); + mbox_file = mime_stream; + }; + + /* precompile our regexes */ + while ((regex_string = string_nextinlist(&list, &sep, + regex_string_buffer, + sizeof(regex_string_buffer))) != NULL) { + + /* parse option */ + if ( (strcmpic(regex_string,US"false") == 0) || + (Ustrcmp(regex_string,"0") == 0) ) { + /* explicitly no matching */ + continue; + }; + + /* compile our regular expression */ + re = pcre_compile( CS regex_string, + 0, + &pcre_error, + &pcre_erroffset, + NULL ); + + if (re == NULL) { + log_write(0, LOG_MAIN, + "regex acl condition warning - error in regex '%s': %s at offset %d, skipped.", regex_string, pcre_error, pcre_erroffset); + continue; + } + else { + re_list_item = store_get(sizeof(pcre_list)); + re_list_item->re = re; + re_list_item->pcre_text = string_copy(regex_string); + re_list_item->next = re_list_head; + re_list_head = re_list_item; + }; + }; + + /* no regexes -> nothing to do */ + if (re_list_head == NULL) { + return FAIL; + }; + + /* match each line against all regexes */ + linebuffer = store_get(32767); + while (fgets(CS linebuffer, 32767, mbox_file) != NULL) { + if ( (mime_stream != NULL) && (mime_current_boundary != NULL) ) { + /* check boundary */ + if (Ustrncmp(linebuffer,"--",2) == 0) { + if (Ustrncmp((linebuffer+2),mime_current_boundary,Ustrlen(mime_current_boundary)) == 0) + /* found boundary */ + break; + }; + }; + re_list_item = re_list_head; + do { + /* try matcher on the line */ + if (pcre_exec(re_list_item->re, NULL, CS linebuffer, + (int)Ustrlen(linebuffer), 0, 0, NULL, 0) >= 0) { + Ustrncpy(regex_match_string_buffer, re_list_item->pcre_text, 1023); + regex_match_string = regex_match_string_buffer; + if (mime_stream == NULL) + fclose(mbox_file); + else { + clearerr(mime_stream); + fseek(mime_stream,f_pos,SEEK_SET); + }; + return OK; + }; + re_list_item = re_list_item->next; + } while (re_list_item != NULL); + }; + + if (mime_stream == NULL) + fclose(mbox_file); + else { + clearerr(mime_stream); + fseek(mime_stream,f_pos,SEEK_SET); + }; + + /* no matches ... */ + return FAIL; +} + + +int mime_regex(uschar **listptr) { + int sep = 0; + uschar *list = *listptr; + uschar *regex_string; + uschar regex_string_buffer[1024]; + pcre *re; + pcre_list *re_list_head = NULL; + pcre_list *re_list_item; + const char *pcre_error; + int pcre_erroffset; + FILE *f; + uschar *mime_subject = NULL; + int mime_subject_len = 0; + + /* reset expansion variable */ + regex_match_string = NULL; + + /* precompile our regexes */ + while ((regex_string = string_nextinlist(&list, &sep, + regex_string_buffer, + sizeof(regex_string_buffer))) != NULL) { + + /* parse option */ + if ( (strcmpic(regex_string,US"false") == 0) || + (Ustrcmp(regex_string,"0") == 0) ) { + /* explicitly no matching */ + continue; + }; + + /* compile our regular expression */ + re = pcre_compile( CS regex_string, + 0, + &pcre_error, + &pcre_erroffset, + NULL ); + + if (re == NULL) { + log_write(0, LOG_MAIN, + "regex acl condition warning - error in regex '%s': %s at offset %d, skipped.", regex_string, pcre_error, pcre_erroffset); + continue; + } + else { + re_list_item = store_get(sizeof(pcre_list)); + re_list_item->re = re; + re_list_item->pcre_text = string_copy(regex_string); + re_list_item->next = re_list_head; + re_list_head = re_list_item; + }; + }; + + /* no regexes -> nothing to do */ + if (re_list_head == NULL) { + return FAIL; + }; + + /* check if the file is already decoded */ + if (mime_decoded_filename == NULL) { + uschar *empty = US""; + /* no, decode it first */ + mime_decode(&empty); + if (mime_decoded_filename == NULL) { + /* decoding failed */ + log_write(0, LOG_MAIN, + "mime_regex acl condition warning - could not decode MIME part to file."); + return DEFER; + }; + }; + + + /* open file */ + f = fopen(CS mime_decoded_filename, "r"); + if (f == NULL) { + /* open failed */ + log_write(0, LOG_MAIN, + "mime_regex acl condition warning - can't open '%s' for reading.", mime_decoded_filename); + return DEFER; + }; + + /* get 32k memory */ + mime_subject = (uschar *)store_get(32767); + + /* read max 32k chars from file */ + mime_subject_len = fread(mime_subject, 1, 32766, f); + + re_list_item = re_list_head; + do { + /* try matcher on the mmapped file */ + debug_printf("Matching '%s'\n", re_list_item->pcre_text); + if (pcre_exec(re_list_item->re, NULL, CS mime_subject, + mime_subject_len, 0, 0, NULL, 0) >= 0) { + Ustrncpy(regex_match_string_buffer, re_list_item->pcre_text, 1023); + regex_match_string = regex_match_string_buffer; + fclose(f); + return OK; + }; + re_list_item = re_list_item->next; + } while (re_list_item != NULL); + + fclose(f); + + /* no matches ... */ + return FAIL; +} + +#endif diff --git a/src/src/route.c b/src/src/route.c index 427570ef7..c9e79bf94 100644 --- a/src/src/route.c +++ b/src/src/route.c @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/route.c,v 1.1 2004/10/07 10:39:01 ph10 Exp $ */ +/* $Cambridge: exim/src/src/route.c,v 1.2 2004/12/16 15:11:47 tom Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -34,6 +34,16 @@ optionlist optionlist_routers[] = { (void *)(offsetof(router_instance, address_data)) }, { "address_test", opt_bool|opt_public, (void *)(offsetof(router_instance, address_test)) }, +#ifdef EXPERIMENTAL_BRIGHTMAIL + { "bmi_deliver_alternate", opt_bool | opt_public, + (void *)(offsetof(router_instance, bmi_deliver_alternate)) }, + { "bmi_deliver_default", opt_bool | opt_public, + (void *)(offsetof(router_instance, bmi_deliver_default)) }, + { "bmi_dont_deliver", opt_bool | opt_public, + (void *)(offsetof(router_instance, bmi_dont_deliver)) }, + { "bmi_rule", opt_stringptr|opt_public, + (void *)(offsetof(router_instance, bmi_rule)) }, +#endif { "cannot_route_message", opt_stringptr | opt_public, (void *)(offsetof(router_instance, cannot_route_message)) }, { "caseful_local_part", opt_bool | opt_public, @@ -982,6 +992,46 @@ if (r->condition != NULL) } } +#ifdef EXPERIMENTAL_BRIGHTMAIL +/* check if a specific Brightmail AntiSpam rule fired on the message */ +if (r->bmi_rule != NULL) { + DEBUG(D_route) debug_printf("checking bmi_rule\n"); + if (bmi_check_rule(bmi_base64_verdict, r->bmi_rule) == 0) { + /* none of the rules fired */ + DEBUG(D_route) + debug_printf("%s router skipped: none of bmi_rule rules fired\n", r->name); + return SKIP; + }; +}; + +/* check if message should not be delivered */ +if (r->bmi_dont_deliver) { + if (bmi_deliver == 1) { + DEBUG(D_route) + debug_printf("%s router skipped: bmi_dont_deliver is FALSE\n", r->name); + return SKIP; + }; +}; + +/* check if message should go to an alternate location */ +if (r->bmi_deliver_alternate) { + if ((bmi_deliver == 0) || (bmi_alt_location == NULL)) { + DEBUG(D_route) + debug_printf("%s router skipped: bmi_deliver_alternate is FALSE\n", r->name); + return SKIP; + }; +}; + +/* check if message should go to default location */ +if (r->bmi_deliver_default) { + if ((bmi_deliver == 0) || (bmi_alt_location != NULL)) { + DEBUG(D_route) + debug_printf("%s router skipped: bmi_deliver_default is FALSE\n", r->name); + return SKIP; + }; +}; +#endif + /* All the checks passed. */ return OK; diff --git a/src/src/routers/redirect.c b/src/src/routers/redirect.c index 0e23bb0b3..7fad1f35f 100644 --- a/src/src/routers/redirect.c +++ b/src/src/routers/redirect.c @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/routers/redirect.c,v 1.2 2004/11/04 10:42:11 ph10 Exp $ */ +/* $Cambridge: exim/src/src/routers/redirect.c,v 1.3 2004/12/16 15:11:48 tom Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -101,6 +101,16 @@ optionlist redirect_router_options[] = { (void *)offsetof(redirect_router_options_block, sieve_vacation_directory) }, { "skip_syntax_errors", opt_bool, (void *)offsetof(redirect_router_options_block, skip_syntax_errors) }, +#ifdef EXPERIMENTAL_SRS + { "srs", opt_stringptr, + (void *)offsetof(redirect_router_options_block, srs) }, + { "srs_alias", opt_stringptr, + (void *)offsetof(redirect_router_options_block, srs_alias) }, + { "srs_condition", opt_stringptr, + (void *)offsetof(redirect_router_options_block, srs_condition) }, + { "srs_db", opt_stringptr, + (void *)offsetof(redirect_router_options_block, srs_db) }, +#endif { "syntax_errors_text", opt_stringptr, (void *)offsetof(redirect_router_options_block, syntax_errors_text) }, { "syntax_errors_to", opt_stringptr, @@ -134,6 +144,12 @@ redirect_router_options_block redirect_router_option_defaults = { NULL, /* qualify_domain */ NULL, /* owners */ NULL, /* owngroups */ +#ifdef EXPERIMENTAL_SRS + NULL, /* srs */ + NULL, /* srs_condition */ + NULL, /* srs_db */ + NULL, /* srs_alias */ +#endif 022, /* modemask */ RDO_REWRITE, /* bit_options */ FALSE, /* check_ancestor */ @@ -526,6 +542,36 @@ if (!ugid.gid_set && pw != NULL) ugid.gid_set = TRUE; } +#ifdef EXPERIMENTAL_SRS + /* For reverse SRS, fill the srs_recipient expandsion variable, + on failure, return decline/fail as relevant */ + if(ob->srs != NULL) + { + BOOL usesrs = TRUE; + + if(ob->srs_condition != NULL) + usesrs = expand_check_condition(ob->srs_condition, "srs_condition expansion failed", NULL); + + if(usesrs) + if(Ustrcmp(ob->srs, "reverse") == 0 || Ustrcmp(ob->srs, "reverseandforward") == 0) + { + uschar *res; + int n_srs; + + srs_orig_recipient = addr->address; + eximsrs_init(); + if(ob->srs_db) + eximsrs_db_set(TRUE, ob->srs_db); + if((n_srs = eximsrs_reverse(&res, addr->address)) != OK) + return n_srs; + srs_recipient = res; + eximsrs_done(); + DEBUG(D_any) + debug_printf("SRS: Recipient '%s' rewriteen to '%s'\n", srs_orig_recipient, srs_recipient); + } + } +#endif + /* Call the function that interprets redirection data, either inline or from a file. This is a separate function so that the system filter can use it. It will run the function in a subprocess if necessary. If qualify_preserve_domain is @@ -749,6 +795,39 @@ else (addr_prop.errors_address != NULL)? "\n" : ""); } +#ifdef EXPERIMENTAL_SRS + /* On successful redirection, check for SRS forwarding and adjust sender */ + if(ob->srs != NULL) + { + BOOL usesrs = TRUE; + + if(ob->srs_condition != NULL) + usesrs = expand_check_condition(ob->srs_condition, "srs_condition expansion failed", NULL); + + if(usesrs) + if((Ustrcmp(ob->srs, "forward") == 0 || Ustrcmp(ob->srs, "reverseandforward") == 0) && !verify) + { + uschar *res; + uschar *usedomain; + int n_srs; + + srs_orig_sender = sender_address; + eximsrs_init(); + if(ob->srs_db) + eximsrs_db_set(FALSE, ob->srs_db); + + if(ob->srs_alias != NULL ? (usedomain = expand_string(ob->srs_alias)) == NULL : 1) + usedomain = deliver_domain; + + if((n_srs = eximsrs_forward(&res, sender_address, usedomain)) != OK) + return n_srs; + sender_address = res; + DEBUG(D_any) + debug_printf("SRS: Sender '%s' rewritten to '%s'\n", srs_orig_sender, sender_address); + } + } +#endif + /* Control gets here only when the address has been completely handled. Put the original address onto the succeed queue so that any retry items that get attached to it get processed. */ diff --git a/src/src/routers/redirect.h b/src/src/routers/redirect.h index 4a6400743..4a2c7bc58 100644 --- a/src/src/routers/redirect.h +++ b/src/src/routers/redirect.h @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/routers/redirect.h,v 1.1 2004/10/07 13:10:02 ph10 Exp $ */ +/* $Cambridge: exim/src/src/routers/redirect.h,v 1.2 2004/12/16 15:11:48 tom Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -33,6 +33,13 @@ typedef struct { uid_t *owners; gid_t *owngroups; +#ifdef EXPERIMENTAL_SRS + uschar *srs; + uschar *srs_condition; + uschar *srs_db; + uschar *srs_alias; +#endif + int modemask; int bit_options; BOOL check_ancestor; diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index 9b25c2333..b5bef64cd 100644 --- a/src/src/smtp_in.c +++ b/src/src/smtp_in.c @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/smtp_in.c,v 1.5 2004/11/10 15:21:16 ph10 Exp $ */ +/* $Cambridge: exim/src/src/smtp_in.c,v 1.6 2004/12/16 15:11:47 tom Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -805,6 +805,10 @@ message_size = -1; acl_warn_headers = NULL; queue_only_policy = FALSE; deliver_freeze = FALSE; /* Can be set by ACL */ +#ifdef WITH_CONTENT_SCAN +fake_reject = FALSE; /* Can be set by ACL */ +no_mbox_unspool = FALSE; /* Can be set by ACL */ +#endif submission_mode = FALSE; /* Can be set by ACL */ active_local_from_check = local_from_check; /* Can be set by ACL */ active_local_sender_retain = local_sender_retain; /* Can be set by ACL */ @@ -815,6 +819,16 @@ sender_verified_list = NULL; /* No senders verified */ memset(sender_address_cache, 0, sizeof(sender_address_cache)); memset(sender_domain_cache, 0, sizeof(sender_domain_cache)); authenticated_sender = NULL; +#ifdef EXPERIMENTAL_BRIGHTMAIL +bmi_run = 0; +bmi_verdicts = NULL; +#endif +#ifdef EXPERIMENTAL_SPF +spf_header_comment = NULL; +spf_received = NULL; +spf_result = NULL; +spf_smtp_comment = NULL; +#endif body_linecount = body_zerocount = 0; for (i = 0; i < ACL_M_MAX; i++) acl_var[ACL_C_MAX + i] = NULL; @@ -1774,6 +1788,9 @@ BOOL drop = rc == FAIL_DROP; uschar *lognl; uschar *sender_info = US""; uschar *what = (where == ACL_WHERE_PREDATA)? US"DATA" : +#ifdef WITH_CONTENT_SCAN + (where == ACL_WHERE_MIME)? US"during MIME ACL checks" : +#endif (where == ACL_WHERE_DATA)? US"after DATA" : string_sprintf("%s %s", acl_wherenames[where], smtp_data); @@ -1785,7 +1802,11 @@ fixed, sender_address at this point became the rewritten address. I'm not sure this is what should be logged, so I've changed to logging the unrewritten address to retain backward compatibility. */ +#ifndef WITH_CONTENT_SCAN if (where == ACL_WHERE_RCPT || where == ACL_WHERE_DATA) +#else +if (where == ACL_WHERE_RCPT || where == ACL_WHERE_DATA || where == ACL_WHERE_MIME) +#endif { sender_info = string_sprintf("F=<%s> ", (sender_address_unrewritten != NULL)? sender_address_unrewritten : sender_address); @@ -2340,6 +2361,11 @@ while (done <= 0) } } +#ifdef EXPERIMENTAL_SPF + /* set up SPF context */ + spf_init(sender_helo_name, sender_host_address); +#endif + /* Apply an ACL check if one is defined */ if (acl_smtp_helo != NULL) diff --git a/src/src/spam.c b/src/src/spam.c new file mode 100644 index 000000000..406daef00 --- /dev/null +++ b/src/src/spam.c @@ -0,0 +1,340 @@ +/* $Cambridge: exim/src/src/spam.c,v 1.2 2004/12/16 15:11:47 tom Exp $ */ + +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) Tom Kistner 2003-???? */ +/* License: GPL */ + +/* Code for calling spamassassin's spamd. Called from acl.c. */ + +#include "exim.h" +#ifdef WITH_CONTENT_SCAN +#include "spam.h" + +uschar spam_score_buffer[16]; +uschar spam_score_int_buffer[16]; +uschar spam_bar_buffer[128]; +uschar spam_report_buffer[32600]; +uschar prev_user_name[128] = ""; +int spam_ok = 0; +int spam_rc = 0; + +int spam(uschar **listptr) { + int sep = 0; + uschar *list = *listptr; + uschar *user_name; + uschar user_name_buffer[128]; + unsigned long long mbox_size; + FILE *mbox_file; + int spamd_sock; + uschar spamd_buffer[32600]; + int i, j, offset; + uschar spamd_version[8]; + uschar spamd_score_char; + double spamd_threshold, spamd_score; + int spamd_report_offset; + uschar *p,*q; + int override = 0; + struct sockaddr_un server; + + /* find the username from the option list */ + if ((user_name = string_nextinlist(&list, &sep, + user_name_buffer, + sizeof(user_name_buffer))) == NULL) { + /* no username given, this means no scanning should be done */ + return FAIL; + }; + + /* if username is "0" or "false", do not scan */ + if ( (Ustrcmp(user_name,"0") == 0) || + (strcmpic(user_name,US"false") == 0) ) { + return FAIL; + }; + + /* if there is an additional option, check if it is "true" */ + if (strcmpic(list,US"true") == 0) { + /* in that case, always return true later */ + override = 1; + }; + + /* if we scanned for this username last time, just return */ + if ( spam_ok && ( Ustrcmp(prev_user_name, user_name) == 0 ) ) { + if (override) + return OK; + else + return spam_rc; + }; + + /* make sure the eml mbox file is spooled up */ + mbox_file = spool_mbox(&mbox_size); + + if (mbox_file == NULL) { + /* error while spooling */ + log_write(0, LOG_MAIN|LOG_PANIC, + "spam acl condition: error while creating mbox spool file"); + return DEFER; + }; + + /* socket does not start with '/' -> network socket */ + if (*spamd_address != '/') { + time_t now = time(NULL); + int num_servers = 0; + int current_server = 0; + int start_server = 0; + uschar *address = NULL; + uschar *spamd_address_list_ptr = spamd_address; + uschar address_buffer[256]; + spamd_address_container * spamd_address_vector[32]; + + /* Check how many spamd servers we have + and register their addresses */ + while ((address = string_nextinlist(&spamd_address_list_ptr, &sep, + address_buffer, + sizeof(address_buffer))) != NULL) { + + spamd_address_container *this_spamd = + (spamd_address_container *)store_get(sizeof(spamd_address_container)); + + /* grok spamd address and port */ + if( sscanf(CS address, "%s %u", this_spamd->tcp_addr, &(this_spamd->tcp_port)) != 2 ) { + log_write(0, LOG_MAIN, + "spam acl condition: warning - invalid spamd address: '%s'", address); + continue; + }; + + spamd_address_vector[num_servers] = this_spamd; + num_servers++; + if (num_servers > 31) + break; + }; + + /* check if we have at least one server */ + if (!num_servers) { + log_write(0, LOG_MAIN|LOG_PANIC, + "spam acl condition: no useable spamd server addresses in spamd_address configuration option."); + fclose(mbox_file); + return DEFER; + }; + + current_server = start_server = (int)now % num_servers; + + while (1) { + + debug_printf("trying server %s, port %u\n", + spamd_address_vector[current_server]->tcp_addr, + spamd_address_vector[current_server]->tcp_port); + + /* contact a spamd */ + if ( (spamd_sock = ip_socket(SOCK_STREAM, AF_INET)) < 0) { + log_write(0, LOG_MAIN|LOG_PANIC, + "spam acl condition: error creating IP socket for spamd"); + fclose(mbox_file); + return DEFER; + }; + + if (ip_connect( spamd_sock, + AF_INET, + spamd_address_vector[current_server]->tcp_addr, + spamd_address_vector[current_server]->tcp_port, + 5 ) > -1) { + /* connection OK */ + break; + }; + + log_write(0, LOG_MAIN|LOG_PANIC, + "spam acl condition: warning - spamd connection to %s, port %u failed: %s", + spamd_address_vector[current_server]->tcp_addr, + spamd_address_vector[current_server]->tcp_port, + strerror(errno)); + current_server++; + if (current_server >= num_servers) + current_server = 0; + if (current_server == start_server) { + log_write(0, LOG_MAIN|LOG_PANIC, "spam acl condition: all spamd servers failed"); + fclose(mbox_file); + close(spamd_sock); + return DEFER; + }; + }; + + } + else { + /* open the local socket */ + + if ((spamd_sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: spamd: unable to acquire socket (%s)", + strerror(errno)); + fclose(mbox_file); + return DEFER; + } + + server.sun_family = AF_UNIX; + Ustrcpy(server.sun_path, spamd_address); + + if (connect(spamd_sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { + log_write(0, LOG_MAIN|LOG_PANIC, + "malware acl condition: spamd: unable to connect to UNIX socket %s (%s)", + spamd_address, strerror(errno) ); + fclose(mbox_file); + close(spamd_sock); + return DEFER; + } + + } + + /* now we are connected to spamd on spamd_sock */ + snprintf(CS spamd_buffer, + sizeof(spamd_buffer), + "REPORT SPAMC/1.2\r\nUser: %s\r\nContent-length: %lld\r\n\r\n", + user_name, + mbox_size); + + /* send our request */ + if (send(spamd_sock, spamd_buffer, Ustrlen(spamd_buffer), 0) < 0) { + close(spamd_sock); + log_write(0, LOG_MAIN|LOG_PANIC, + "spam acl condition: spamd send failed: %s", strerror(errno)); + fclose(mbox_file); + close(spamd_sock); + return DEFER; + }; + + /* now send the file */ + do { + j = fread(spamd_buffer,1,sizeof(spamd_buffer),mbox_file); + if (j > 0) { + i = send(spamd_sock,spamd_buffer,j,0); + if (i != j) { + log_write(0, LOG_MAIN|LOG_PANIC, + "spam acl condition: error/short send to spamd"); + close(spamd_sock); + fclose(mbox_file); + return DEFER; + }; + }; + } + while (j > 0); + + fclose(mbox_file); + + /* we're done sending, close socket for writing */ + shutdown(spamd_sock,SHUT_WR); + + /* read spamd response */ + memset(spamd_buffer, 0, sizeof(spamd_buffer)); + offset = 0; + while((i = ip_recv(spamd_sock, + spamd_buffer + offset, + sizeof(spamd_buffer) - offset - 1, + SPAMD_READ_TIMEOUT)) > 0 ) { + offset += i; + } + + /* error handling */ + if((i <= 0) && (errno != 0)) { + log_write(0, LOG_MAIN|LOG_PANIC, + "spam acl condition: error reading from spamd socket: %s", strerror(errno)); + close(spamd_sock); + return DEFER; + } + + /* reading done */ + close(spamd_sock); + + /* dig in the spamd output and put the report in a multiline header, if requested */ + if( sscanf(CS spamd_buffer,"SPAMD/%s 0 EX_OK\r\nContent-length: %*u\r\n\r\n%lf/%lf\r\n%n", + spamd_version,&spamd_score,&spamd_threshold,&spamd_report_offset) != 3 ) { + + /* try to fall back to pre-2.50 spamd output */ + if( sscanf(CS spamd_buffer,"SPAMD/%s 0 EX_OK\r\nSpam: %*s ; %lf / %lf\r\n\r\n%n", + spamd_version,&spamd_score,&spamd_threshold,&spamd_report_offset) != 3 ) { + log_write(0, LOG_MAIN|LOG_PANIC, + "spam acl condition: cannot parse spamd output"); + return DEFER; + }; + }; + + /* Create report. Since this is a multiline string, + we must hack it into shape first */ + p = &spamd_buffer[spamd_report_offset]; + q = spam_report_buffer; + while (*p != '\0') { + /* skip \r */ + if (*p == '\r') { + p++; + continue; + }; + *q = *p; + q++; + if (*p == '\n') { + *q = '\t'; + q++; + /* eat whitespace */ + while( (*p <= ' ') && (*p != '\0') ) { + p++; + }; + p--; + }; + p++; + }; + /* NULL-terminate */ + *q = '\0'; + q--; + /* cut off trailing leftovers */ + while (*q <= ' ') { + *q = '\0'; + q--; + }; + spam_report = spam_report_buffer; + + /* create spam bar */ + spamd_score_char = spamd_score > 0 ? '+' : '-'; + j = abs((int)(spamd_score)); + i = 0; + if( j != 0 ) { + while((i < j) && (i <= MAX_SPAM_BAR_CHARS)) + spam_bar_buffer[i++] = spamd_score_char; + } + else{ + spam_bar_buffer[0] = '/'; + i = 1; + } + spam_bar_buffer[i] = '\0'; + spam_bar = spam_bar_buffer; + + /* create "float" spam score */ + snprintf(CS spam_score_buffer, sizeof(spam_score_buffer),"%.1f", spamd_score); + spam_score = spam_score_buffer; + + /* create "int" spam score */ + j = (int)((spamd_score + 0.001)*10); + snprintf(CS spam_score_int_buffer, sizeof(spam_score_int_buffer), "%d", j); + spam_score_int = spam_score_int_buffer; + + /* compare threshold against score */ + if (spamd_score >= spamd_threshold) { + /* spam as determined by user's threshold */ + spam_rc = OK; + } + else { + /* not spam */ + spam_rc = FAIL; + }; + + /* remember user name and "been here" for it */ + Ustrcpy(prev_user_name, user_name); + spam_ok = 1; + + if (override) { + /* always return OK, no matter what the score */ + return OK; + } + else { + return spam_rc; + }; +} + +#endif diff --git a/src/src/spam.h b/src/src/spam.h new file mode 100644 index 000000000..b2a12e347 --- /dev/null +++ b/src/src/spam.h @@ -0,0 +1,30 @@ +/* $Cambridge: exim/src/src/spam.h,v 1.2 2004/12/16 15:11:47 tom Exp $ */ + +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) Tom Kistner 2003-???? */ +/* License: GPL */ + +/* spam defines */ + +#ifdef WITH_CONTENT_SCAN + +/* timeout for reading from spamd */ +#define SPAMD_READ_TIMEOUT 3600 + +/* maximum length of the spam bar */ +#define MAX_SPAM_BAR_CHARS 50 + +/* SHUT_WR seems to be undefined on Unixware ? */ +#ifndef SHUT_WR +#define SHUT_WR 1 +#endif + +typedef struct spamd_address_container { + uschar tcp_addr[24]; + unsigned int tcp_port; +} spamd_address_container; + +#endif diff --git a/src/src/spf.c b/src/src/spf.c new file mode 100644 index 000000000..f3f8d7b56 --- /dev/null +++ b/src/src/spf.c @@ -0,0 +1,131 @@ +/* $Cambridge: exim/src/src/spf.c,v 1.2 2004/12/16 15:11:47 tom Exp $ */ + +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Experimental SPF support. + Copyright (c) Tom Kistner 2004 + License: GPL */ + +/* Code for calling spf checks via libspf-alt. Called from acl.c. */ + +#include "exim.h" +#ifdef EXPERIMENTAL_SPF + +/* #include "spf.h" */ + +SPF_config_t spfcid = NULL; +SPF_dns_config_t spfdcid_resolv = NULL; +SPF_dns_config_t spfdcid = NULL; + + +/* spf_init sets up a context that can be re-used for several + messages on the same SMTP connection (that come from the + same host with the same HELO string) */ + +int spf_init(uschar *spf_helo_domain, uschar *spf_remote_addr) { + uschar *p; + + /* paranoia */ + spfcid = NULL; + spfdcid_resolv = NULL; + spfdcid = NULL; + + spfcid = SPF_create_config(); + if ( spfcid == NULL ) { + debug_printf("spf: SPF_create_config() failed.\n"); + return 0; + } + + /* set up resolver */ + spfdcid_resolv = SPF_dns_create_config_resolv(NULL, 0); + spfdcid = SPF_dns_create_config_cache(spfdcid_resolv, 8, 0); + + if (spfdcid == NULL) { + debug_printf("spf: SPF_dns_create_config_cache() failed.\n"); + spfcid = NULL; + spfdcid_resolv = NULL; + return 0; + } + + if (SPF_set_ip_str(spfcid, spf_remote_addr)) { + debug_printf("spf: SPF_set_ip_str() failed.\n"); + spfcid = NULL; + spfdcid_resolv = NULL; + return 0; + } + + if (SPF_set_helo_dom(spfcid, spf_helo_domain)) { + debug_printf("spf: SPF_set_helo_dom() failed.\n"); + spfcid = NULL; + spfdcid_resolv = NULL; + return 0; + } + + return 1; +} + + +/* spf_process adds the envelope sender address to the existing + context (if any), retrieves the result, sets up expansion + strings and evaluates the condition outcome. */ + +int spf_process(uschar **listptr, uschar *spf_envelope_sender) { + int sep = 0; + uschar *list = *listptr; + uschar *spf_result_id; + uschar spf_result_id_buffer[128]; + SPF_output_t spf_output; + int rc = SPF_RESULT_ERROR; + + if (!(spfcid && spfdcid)) { + /* no global context, assume temp error and skip to evaluation */ + rc = SPF_RESULT_ERROR; + goto SPF_EVALUATE; + }; + + if (SPF_set_env_from(spfcid, spf_envelope_sender)) { + /* Invalid sender address. This should be a real rare occurence */ + rc = SPF_RESULT_ERROR; + goto SPF_EVALUATE; + } + + /* get SPF result */ + spf_output = SPF_result(spfcid, spfdcid); + + /* set up expansion items */ + spf_header_comment = spf_output.header_comment ? (uschar *)spf_output.header_comment : NULL; + spf_received = spf_output.received_spf ? (uschar *)spf_output.received_spf : NULL; + spf_result = (uschar *)SPF_strresult(spf_output.result); + spf_smtp_comment = spf_output.smtp_comment ? (uschar *)spf_output.smtp_comment : NULL; + + rc = spf_output.result; + + /* We got a result. Now see if we should return OK or FAIL for it */ + SPF_EVALUATE: + debug_printf("SPF result is %s (%d)\n", SPF_strresult(rc), rc); + while ((spf_result_id = string_nextinlist(&list, &sep, + spf_result_id_buffer, + sizeof(spf_result_id_buffer))) != NULL) { + int negate = 0; + int result = 0; + + /* Check for negation */ + if (spf_result_id[0] == '!') { + negate = 1; + spf_result_id++; + }; + + /* Check the result identifier */ + result = Ustrcmp(spf_result_id, spf_result_id_list[rc].name); + if (!negate && result==0) return OK; + if (negate && result!=0) return OK; + }; + + /* no match */ + return FAIL; +} + +#endif + diff --git a/src/src/spf.h b/src/src/spf.h new file mode 100644 index 000000000..de7b741fd --- /dev/null +++ b/src/src/spf.h @@ -0,0 +1,39 @@ +/* $Cambridge: exim/src/src/spf.h,v 1.2 2004/12/16 15:11:47 tom Exp $ */ + +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Experimental SPF support. + Copyright (c) Tom Kistner 2004 + License: GPL */ + +#ifdef EXPERIMENTAL_SPF + +#include +#include +#include + +typedef struct spf_result_id { + uschar *name; + int value; +} spf_result_id; + +/* must be kept in numeric order */ +static spf_result_id spf_result_id_list[] = { + { US"pass", 0 }, + { US"fail", 1 }, + { US"softfail", 2 }, + { US"neutral", 3 }, + { US"err_perm", 4 }, + { US"err_temp", 5 }, + { US"none", 6 } +}; + +static int spf_result_id_list_size = sizeof(spf_result_id_list)/sizeof(spf_result_id); + +/* prototypes */ +int spf_init(uschar *,uschar *); +int spf_process(uschar **, uschar *); + +#endif diff --git a/src/src/spool_in.c b/src/src/spool_in.c index f43556bfa..55d78b957 100644 --- a/src/src/spool_in.c +++ b/src/src/spool_in.c @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/spool_in.c,v 1.1 2004/10/07 10:39:01 ph10 Exp $ */ +/* $Cambridge: exim/src/src/spool_in.c,v 1.2 2004/12/16 15:11:47 tom Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -271,12 +271,22 @@ sender_local = FALSE; sender_set_untrusted = FALSE; tree_nonrecipients = NULL; +#ifdef EXPERIMENTAL_BRIGHTMAIL +bmi_run = 0; +bmi_verdicts = NULL; +#endif + #ifdef SUPPORT_TLS tls_certificate_verified = FALSE; tls_cipher = NULL; tls_peerdn = NULL; #endif +#ifdef WITH_CONTENT_SCAN +fake_reject = FALSE; +spam_score_int = NULL; +#endif + /* Generate the full name and open the file. If message_subdir is already set, just look in the given directory. Otherwise, look in both the split and unsplit directories, as for the data file above. */ @@ -369,6 +379,14 @@ for (;;) local_error_message = TRUE; else if (Ustrncmp(big_buffer, "-local_scan ", 12) == 0) local_scan_data = string_copy(big_buffer + 12); +#ifdef WITH_CONTENT_SCAN + else if (Ustrncmp(big_buffer, "-spam_score_int ", 16) == 0) + spam_score_int = string_copy(big_buffer + 16); +#endif +#ifdef EXPERIMENTAL_BRIGHTMAIL + else if (Ustrncmp(big_buffer, "-bmi_verdicts ", 14) == 0) + bmi_verdicts = string_copy(big_buffer + 14); +#endif else if (Ustrcmp(big_buffer, "-host_lookup_failed") == 0) host_lookup_failed = TRUE; else if (Ustrncmp(big_buffer, "-body_linecount", 15) == 0) diff --git a/src/src/spool_mbox.c b/src/src/spool_mbox.c new file mode 100644 index 000000000..c2799f585 --- /dev/null +++ b/src/src/spool_mbox.c @@ -0,0 +1,174 @@ +/* $Cambridge: exim/src/src/spool_mbox.c,v 1.2 2004/12/16 15:11:47 tom Exp $ */ + +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) Tom Kistner 2003-???? */ +/* License: GPL */ + +/* Code for setting up a MBOX style spool file inside a /scan/ +sub directory of exim's spool directory. */ + +#include "exim.h" +#ifdef WITH_CONTENT_SCAN + +/* externals, we must reset them on unspooling */ +#ifdef WITH_OLD_DEMIME +extern int demime_ok; +extern struct file_extension *file_extensions; +#endif + +extern int malware_ok; +extern int spam_ok; + +int spool_mbox_ok = 0; +uschar spooled_message_id[17]; + +/* returns a pointer to the FILE, and puts the size in bytes into mbox_file_size */ + +FILE *spool_mbox(unsigned long long *mbox_file_size) { + uschar mbox_path[1024]; + uschar message_subdir[2]; + uschar data_buffer[65535]; + FILE *mbox_file; + FILE *data_file = NULL; + header_line *my_headerlist; + struct stat statbuf; + int i,j; + + if (!spool_mbox_ok) { + /* create scan directory, if not present */ + if (!directory_make(spool_directory, US "scan", 0750, FALSE)) { + debug_printf("unable to create directory: %s/scan\n", spool_directory); + return NULL; + }; + + /* create temp directory inside scan dir */ + snprintf(CS mbox_path, 1024, "%s/scan/%s", spool_directory, message_id); + if (!directory_make(NULL, mbox_path, 0750, FALSE)) { + debug_printf("unable to create directory: %s/scan/%s\n", spool_directory, message_id); + return NULL; + }; + + /* open [message_id].eml file for writing */ + snprintf(CS mbox_path, 1024, "%s/scan/%s/%s.eml", spool_directory, message_id, message_id); + mbox_file = Ufopen(mbox_path,"w"); + + if (mbox_file == NULL) { + debug_printf("unable to open file for writing: %s\n", mbox_path); + return NULL; + }; + + /* write all header lines to mbox file */ + my_headerlist = header_list; + while (my_headerlist != NULL) { + + /* skip deleted headers */ + if (my_headerlist->type == '*') { + my_headerlist = my_headerlist->next; + continue; + }; + + i = fwrite(my_headerlist->text, 1, my_headerlist->slen, mbox_file); + if (i != my_headerlist->slen) { + debug_printf("error/short write on writing in: %s", mbox_path); + fclose(mbox_file); + return NULL; + }; + + my_headerlist = my_headerlist->next; + }; + + /* copy body file */ + message_subdir[1] = '\0'; + for (i = 0; i < 2; i++) { + message_subdir[0] = (split_spool_directory == (i == 0))? message_id[5] : 0; + sprintf(CS mbox_path, "%s/input/%s/%s-D", spool_directory, message_subdir, message_id); + data_file = Ufopen(mbox_path,"r"); + if (data_file != NULL) + break; + }; + + fread(data_buffer, 1, 18, data_file); + + do { + j = fread(data_buffer, 1, sizeof(data_buffer), data_file); + if (j > 0) { + i = fwrite(data_buffer, 1, j, mbox_file); + if (i != j) { + debug_printf("error/short write on writing in: %s", mbox_path); + fclose(mbox_file); + fclose(data_file); + return NULL; + }; + }; + } while (j > 0); + + fclose(data_file); + fclose(mbox_file); + Ustrcpy(spooled_message_id, message_id); + spool_mbox_ok = 1; + }; + + snprintf(CS mbox_path, 1024, "%s/scan/%s/%s.eml", spool_directory, message_id, message_id); + + /* get the size of the mbox message */ + stat(CS mbox_path, &statbuf); + *mbox_file_size = statbuf.st_size; + + /* open [message_id].eml file for reading */ + mbox_file = Ufopen(mbox_path,"r"); + + return mbox_file; +} + +/* remove mbox spool file, demimed files and temp directory */ +void unspool_mbox(void) { + + /* reset all exiscan state variables */ + #ifdef WITH_OLD_DEMIME + demime_ok = 0; + demime_errorlevel = 0; + demime_reason = NULL; + file_extensions = NULL; + #endif + + spam_ok = 0; + malware_ok = 0; + + if (spool_mbox_ok) { + + spool_mbox_ok = 0; + + if (!no_mbox_unspool) { + uschar mbox_path[1024]; + uschar file_path[1024]; + int n; + struct dirent *entry; + DIR *tempdir; + + snprintf(CS mbox_path, 1024, "%s/scan/%s", spool_directory, spooled_message_id); + + tempdir = opendir(CS mbox_path); + /* loop thru dir & delete entries */ + n = 0; + do { + entry = readdir(tempdir); + if (entry == NULL) break; + snprintf(CS file_path, 1024,"%s/scan/%s/%s", spool_directory, spooled_message_id, entry->d_name); + if ( (Ustrcmp(entry->d_name,"..") != 0) && (Ustrcmp(entry->d_name,".") != 0) ) { + debug_printf("unspool_mbox(): unlinking '%s'\n", file_path); + n = unlink(CS file_path); + }; + } while (n > -1); + + closedir(tempdir); + + /* remove directory */ + n = rmdir(CS mbox_path); + }; + }; +} + +#endif diff --git a/src/src/spool_out.c b/src/src/spool_out.c index af36f4169..577d0f68c 100644 --- a/src/src/spool_out.c +++ b/src/src/spool_out.c @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/spool_out.c,v 1.1 2004/10/07 10:39:01 ph10 Exp $ */ +/* $Cambridge: exim/src/src/spool_out.c,v 1.2 2004/12/16 15:11:47 tom Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -216,9 +216,16 @@ if (host_lookup_failed) fprintf(f, "-host_lookup_failed\n"); if (sender_local) fprintf(f, "-local\n"); if (local_error_message) fprintf(f, "-localerror\n"); if (local_scan_data != NULL) fprintf(f, "-local_scan %s\n", local_scan_data); +#ifdef WITH_CONTENT_SCAN +if (spam_score_int != NULL) fprintf(f,"-spam_score_int %s\n", spam_score_int); +#endif if (deliver_manual_thaw) fprintf(f, "-manual_thaw\n"); if (sender_set_untrusted) fprintf(f, "-sender_set_untrusted\n"); +#ifdef EXPERIMENTAL_BRIGHTMAIL +if (bmi_verdicts != NULL) fprintf(f, "-bmi_verdicts %s\n", bmi_verdicts); +#endif + #ifdef SUPPORT_TLS if (tls_certificate_verified) fprintf(f, "-tls_certificate_verified\n"); if (tls_cipher != NULL) fprintf(f, "-tls_cipher %s\n", tls_cipher); diff --git a/src/src/srs.c b/src/src/srs.c new file mode 100644 index 000000000..a1e08c36e --- /dev/null +++ b/src/src/srs.c @@ -0,0 +1,206 @@ +/* $Cambridge: exim/src/src/srs.c,v 1.2 2004/12/16 15:11:47 tom Exp $ */ + +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* SRS - Sender rewriting scheme support + ©2004 Miles Wilton + License: GPL */ + +#include "exim.h" +#ifdef EXPERIMENTAL_SRS + +#include +#include "srs.h" + +srs_t *srs = NULL; +uschar *srs_db_forward = NULL; +uschar *srs_db_reverse = NULL; + + +/* srs_init just initialises libsrs and creates (if necessary) + an srs object to use for all srs calls in this instance */ + +int eximsrs_init() +{ + int co; + uschar *list = srs_config; + char secret_buf[SRS_MAX_SECRET_LENGTH]; + char *secret; + char sbuf[4]; + char *sbufp; + int hashlen, maxage; + + + if(!srs) + { + // Check config + if(!srs_config) + { + log_write(0, LOG_MAIN | LOG_PANIC, + "SRS Configuration Error"); + return DEFER; + } + + // Get config + co = 0; + if((secret = string_nextinlist(&list, &co, secret_buf, + SRS_MAX_SECRET_LENGTH)) == NULL) + { + log_write(0, LOG_MAIN | LOG_PANIC, + "SRS Configuration Error: No secret specified"); + return DEFER; + } + + if((sbufp = string_nextinlist(&list, &co, sbuf, sizeof(sbuf))) == NULL) + maxage = 31; + else + maxage = atoi(sbuf); + if(maxage < 0 || maxage > 365) + { + log_write(0, LOG_MAIN | LOG_PANIC, + "SRS Configuration Error: Invalid maximum timestamp age"); + return DEFER; + } + + if((sbufp = string_nextinlist(&list, &co, sbuf, sizeof(sbuf))) == NULL) + hashlen = 6; + else + hashlen = atoi(sbuf); + if(hashlen < 1 || hashlen > 20) + { + log_write(0, LOG_MAIN | LOG_PANIC, + "SRS Configuration Error: Invalid hash length"); + return DEFER; + } + + + if((srs = srs_open(secret, strnlen(secret, SRS_MAX_SECRET_LENGTH), + maxage, hashlen, hashlen)) == NULL) + { + log_write(0, LOG_MAIN | LOG_PANIC, + "Failed to allocate SRS memory"); + return DEFER; + } + + + if((sbufp = string_nextinlist(&list, &co, sbuf, sizeof(sbuf))) != NULL) + srs_set_option(srs, SRS_OPTION_USETIMESTAMP, atoi(sbuf)); + + if((sbufp = string_nextinlist(&list, &co, sbuf, sizeof(sbuf))) != NULL) + srs_set_option(srs, SRS_OPTION_USEHASH, atoi(sbuf)); + + DEBUG(D_any) + debug_printf("SRS initialized\n"); + } + + return OK; +} + + +int eximsrs_done() +{ + if(srs) + srs_close(srs); + + srs = NULL; + + return OK; +} + + +int eximsrs_forward(uschar **result, uschar *orig_sender, uschar *domain) +{ + char res[512]; + int n; + + if((n = srs_forward(srs, orig_sender, domain, res, sizeof(res))) & SRS_RESULT_FAIL) + { + DEBUG(D_any) + debug_printf("srs_forward failed (%s, %s): %s\n", orig_sender, domain, srs_geterrormsg(n)); + return DEFER; + } + + *result = string_copy(res); + return OK; +} + + +int eximsrs_reverse(uschar **result, uschar *address) +{ + char res[512]; + int n; + + if((n = srs_reverse(srs, address, res, sizeof(res))) & SRS_RESULT_FAIL) + { + DEBUG(D_any) + debug_printf("srs_reverse failed (%s): %s\n", address, srs_geterrormsg(n)); + if(n == SRS_RESULT_NOTSRS || n == SRS_RESULT_BADSRS) + return DECLINE; + if(n == SRS_RESULT_BADHASH || n == SRS_RESULT_BADTIMESTAMP || n == SRS_RESULT_TIMESTAMPEXPIRED) + return FAIL; + return DEFER; + } + + *result = string_copy(res); + return OK; +} + + +int eximsrs_db_set(BOOL reverse, uschar *srs_db) +{ + if(reverse) + srs_db_reverse = string_copy(srs_db); + else + srs_db_forward = string_copy(srs_db); + + if(srs_set_db_functions(srs, eximsrs_db_insert, eximsrs_db_lookup) * SRS_RESULT_FAIL) + return DEFER; + + return OK; +} + + +srs_result eximsrs_db_insert(srs_t *srs, char *data, uint data_len, char *result, uint result_len) +{ + uschar *res; + char buf[64]; + + srs_db_address = string_copyn(data, data_len); + if(srs_generate_unique_id(srs, srs_db_address, buf, 64) & SRS_RESULT_FAIL) + return DEFER; + + srs_db_key = string_copyn(buf, 16); + + if((res = expand_string(srs_db_forward)) == NULL) + return SRS_RESULT_DBERROR; + + if(result_len < 17) + return SRS_RESULT_DBERROR; + + strncpy(result, srs_db_key, result_len); + + return SRS_RESULT_OK; +} + + +srs_result eximsrs_db_lookup(srs_t *srs, char *data, uint data_len, char *result, uint result_len) +{ + uschar *res; + + srs_db_key = string_copyn(data, data_len); + if((res = expand_string(srs_db_reverse)) == NULL) + return SRS_RESULT_DBERROR; + + if(Ustrlen(res) >= result_len) + return SRS_RESULT_ADDRESSTOOLONG; + + strncpy(result, res, result_len); + + return SRS_RESULT_OK; +} + + +#endif + diff --git a/src/src/srs.h b/src/src/srs.h new file mode 100644 index 000000000..3d88132cc --- /dev/null +++ b/src/src/srs.h @@ -0,0 +1,31 @@ +/* $Cambridge: exim/src/src/srs.h,v 1.2 2004/12/16 15:11:47 tom Exp $ */ + +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* SRS - Sender rewriting scheme support + ©2004 Miles Wilton + License: GPL */ + +#ifndef __SRS_H__ + +#define __SRS_H__ 1 + +#ifdef EXPERIMENTAL_SRS + +#include "mytypes.h" +#include + +int eximsrs_init(); +int eximsrs_done(); +int eximsrs_forward(uschar **result, uschar *orig_sender, uschar *domain); +int eximsrs_reverse(uschar **result, uschar *address); +int eximsrs_db(BOOL reverse, uschar *srs_db); + +srs_result eximsrs_db_insert(srs_t *srs, char *data, uint data_len, char *result, uint result_len); +srs_result eximsrs_db_lookup(srs_t *srs, char *data, uint data_len, char *result, uint result_len); + +#endif + +#endif diff --git a/src/src/structs.h b/src/src/structs.h index 453582766..3ffe0cecf 100644 --- a/src/src/structs.h +++ b/src/src/structs.h @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/structs.h,v 1.1 2004/10/07 10:39:01 ph10 Exp $ */ +/* $Cambridge: exim/src/src/structs.h,v 1.2 2004/12/16 15:11:47 tom Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -221,6 +221,9 @@ typedef struct router_instance { uschar *driver_name; /* Must be first */ uschar *address_data; /* Arbitrary data */ +#ifdef EXPERIMENTAL_BRIGHTMAIL + uschar *bmi_rule; /* Brightmail AntiSpam rule checking */ +#endif uschar *cannot_route_message; /* Used when routing fails */ uschar *condition; /* General condition */ uschar *current_directory; /* For use during delivery */ @@ -249,6 +252,11 @@ typedef struct router_instance { uschar *transport_name; /* Transport name */ BOOL address_test; /* Use this router when testing addresses */ +#ifdef EXPERIMENTAL_BRIGHTMAIL + BOOL bmi_deliver_alternate; /* TRUE => BMI said that message should be delivered to alternate location */ + BOOL bmi_deliver_default; /* TRUE => BMI said that message should be delivered to default location */ + BOOL bmi_dont_deliver; /* TRUE => BMI said that message should not be delivered at all */ +#endif BOOL expn; /* Use this router when processing EXPN */ BOOL caseful_local_part; /* TRUE => don't lowercase */ BOOL check_local_user; /* TRUE => check local user */ -- 2.25.1