This is a digest of patches # 411, 440 and fix of bug mentioned by patch 586. Option letters have been changed from the original patches for consistency Options: -e issue an SMTP error upon reception of spam -r <e-mail-addr> reroute spam to <e-mail-addr> -t throw spam away (no redirection) Notes: 1) -t and -r are mutually exclusive 2) to allow option -e to work without -t, the second patch must be applied to sendmail (8.11.6-15 that is: RH 7.3 original distribution sendmail). For developers: this patch allows the use of smfi_setreply() with SMFIS_ACCEPT. Patch for spamass-milter: ================================================================= diff -Naur spamass-milter-0.1.2.orig/spamass-milter.cpp spamass-milter-0.1.2/spamass-milter.cpp --- spamass-milter-0.1.2.orig/spamass-milter.cpp Tue Jul 23 05:48:09 2002 +++ spamass-milter-0.1.2/spamass-milter.cpp Fri Nov 22 20:17:21 2002 @@ -88,6 +88,7 @@ #include <string> #include <iostream> #include <fstream> +#include <list> #ifdef __cplusplus extern "C" { @@ -112,7 +113,7 @@ NULL, // info filter callback NULL, // HELO filter callback mlfi_envfrom, // envelope filter callback - NULL, // envelope recipient filter callback + mlfi_envto, // envelope recipient filter callback mlfi_header, // header filter callback mlfi_eoh, // end of header callback mlfi_body, // body filter callback @@ -122,6 +123,9 @@ }; int flag_debug=0; +bool flag_error = false; +// where to redirect spam +char *spambucket; // {{{ main() @@ -129,25 +133,51 @@ main(int argc, char* argv[]) { int c, err = 0; - const char *args = "p:fd:"; + const char *args = "p:fd:hr:et"; char *sock = NULL; bool dofork = false; /* Process command line options */ while ((c = getopt(argc, argv, args)) != -1) { switch (c) { - case 'p': - sock = strdup(optarg); - break; - case 'f': - dofork = true; - break; - case 'd': - flag_debug = atoi(optarg); - break; - case '?': - err = 1; - break; + case 'p': + sock = strdup(optarg); + break; + case 'f': + dofork = true; + break; + case 'd': + flag_debug = atoi(optarg); + break; + case 'h': + // spamassassin is configured to not + // modify the message body + smfilter.xxfi_flags &= ~SMFIF_CHGBODY; + break; + case 'r': + // we will modify the recipient list; if spamc + // returns indicating that this mail is spam, the + // message will be sent to <optarg>. + smfilter.xxfi_flags |= SMFIF_ADDRCPT; // May add recipients + smfilter.xxfi_flags |= SMFIF_DELRCPT; // May delete recipients + // XXX verify optarg is vaguely sane + spambucket = strdup(optarg); + break; + case 'e': + // Terminate the connection with SMTP status + // "553 5.7.1 We do not accept advertising" if + // spam detected. + flag_error = true; + break; + case 't': + // Throw spam away (no redirection). + smfilter.xxfi_flags &= ~SMFIF_ADDRCPT; + smfilter.xxfi_flags |= SMFIF_DELRCPT; + spambucket = NULL; + break; + case '?': + err = 1; + break; } } @@ -158,6 +188,10 @@ cout << " -p socket: path to create socket" << endl; cout << " -f: fork into background" << endl; cout << " -d nn: set debug level to nn (1 or 2). Logs to syslog" << endl; + cout << " -h: modify headers only" << endl; + cout << " -r <spambucket>: redirect spam to <spambucket>" <<endl; + cout << " -e: spam causes SMTP status 553" << endl; + cout << " -t: throw spam away (no redirection)" << endl; exit(EX_USAGE); } @@ -235,7 +269,7 @@ else if (assassin->spam_flag().size()>0) smfi_addheader(ctx, "X-Spam-Flag", const_cast<char*>(assassin->spam_flag().c_str())); - + // X-Spam-Report header // // find it: @@ -339,13 +373,15 @@ const_cast<char*>(newstring.c_str())); - // Replace body with the one SpamAssassin provided // - string::size_type body_size = assassin->d().size() - bob; - unsigned char* bodyp = (unsigned char*) + // Replace body with the one SpamAssassin provided, unless we've + // specified header modifications only. + if ( smfilter.xxfi_flags & SMFIF_CHGBODY ) { + string::size_type body_size = assassin->d().size() - bob; + unsigned char* bodyp = (unsigned char*) const_cast<char*>(assassin->d().substr(bob, string::npos).c_str()); - if ( smfi_replacebody(ctx, bodyp, body_size) == MI_FAILURE ) - throw string("error. could not replace body."); - + if ( smfi_replacebody(ctx, bodyp, body_size) == MI_FAILURE ) + throw string("error. could not replace body."); + }; }; // erase mail right away @@ -353,6 +389,60 @@ }; + +// +// Perform any requested action on a mail detected as spam. +// +sfsistat +route_spam(SMFICTX* ctx, SpamAssassin* assassin) + +{ + // Delete original recipients. + // Move recipients to a non-active header, one at a time. + // Note, we may end up with multiple X-Spam-Orig-To headers, + // but this is not breaking anyone's spec. + + if (smfilter.xxfi_flags & SMFIF_DELRCPT) + while (!assassin->recipients.empty()) { + if (smfi_addheader(ctx, "X-Spam-Orig-To", (char *) + assassin->recipients.front().c_str()) != MI_SUCCESS) + throw + string("Failed to save recipient address."); + + // It's not 100 0mportant that this succeeds, so + // we'll just warn on failure rather than + // throwing. + + if (smfi_delrcpt(ctx, (char *) + assassin->recipients.front().c_str()) != MI_SUCCESS) + throw_error( + "Failed to remove original recipients."); + + assassin->recipients.pop_front(); + } + + // Add spam catcher. + + if (smfilter.xxfi_flags & SMFIF_ADDRCPT) + if (smfi_addrcpt(ctx, spambucket) != MI_SUCCESS) + throw string("Failed to add spam to recipient list"); + + // Issue SMTP error. + + if (flag_error) + smfi_setreply(ctx, "553", + "5.7.1", " We do not accept advertising"); + + // Reject the spam. + + if ((smfilter.xxfi_flags & (SMFIF_ADDRCPT | SMFIF_DELRCPT)) == + SMFIF_DELRCPT) + return SMFIS_REJECT; + + return SMFIS_ACCEPT; +} + + // retrieve the content of a specific field in the header // and return it. string @@ -369,7 +459,7 @@ pos = find_nocase(header, string(" "), pos) + 1; // look for end of content - string::size_type pos2(pos); + string::size_type pos2(pos - 1); do { pos2 = find_nocase(header, string("\n"), pos2+1); @@ -416,6 +506,22 @@ return SMFIS_CONTINUE; }; +// Collect recipients of current message. If spamassassin deems the +// message to be spam, we may be modifying this. The function can be +// called multiple times per mail, so we aggregate the recipients into +// the SpamAssassin object. +sfsistat mlfi_envto( SMFICTX *ctx, char **envto ) +{ + SpamAssassin *assassin = static_cast<SpamAssassin*>(smfi_getpriv(ctx)); + char **rcpt; + + for ( rcpt = envto; *rcpt; rcpt++ ) { + assassin->recipients.push_back( *rcpt ); // FIXME error check + } + + return SMFIS_CONTINUE; +} + // // Gets called repeatedly for all header fields // @@ -492,6 +598,7 @@ return SMFIS_CONTINUE; }; + // // Gets called once when the header is finished. // @@ -561,6 +668,7 @@ sfsistat mlfi_eom(SMFICTX* ctx) { + sfsistat status = SMFIS_CONTINUE; SpamAssassin* assassin = static_cast<SpamAssassin*>(smfi_getpriv(ctx)); debug(1, "mlfi_eom: enter"); @@ -579,6 +687,9 @@ if (assassin->spam_flag().size()==0) assassinate(ctx, assassin); + if (assassin->spam_flag().size() > 0) + status = route_spam(ctx, assassin); + // now cleanup the element. smfi_setpriv(ctx, static_cast<void*>(0)); delete assassin; @@ -594,7 +705,7 @@ // go on... debug(1, "mlfi_eom: exit"); - return SMFIS_CONTINUE; + return status; }; // @@ -732,6 +843,11 @@ waitpid(pid, &status, 0); }; + // Empty the recipient list + while( !recipients.empty()) { + recipients.pop_front(); + } + }; // write to SpamAssassin @@ -1141,4 +1257,4 @@ } // }}} -// vim6:ai:noexpandtab +// vim6:noexpandtab diff -Naur spamass-milter-0.1.2.orig/spamass-milter.h spamass-milter-0.1.2/spamass-milter.h --- spamass-milter-0.1.2.orig/spamass-milter.h Tue Jul 23 04:02:12 2002 +++ spamass-milter-0.1.2/spamass-milter.h Fri Nov 22 16:39:13 2002 @@ -31,6 +31,7 @@ string retrieve_field(const string&, const string&); sfsistat mlfi_envfrom(SMFICTX*, char**); +sfsistat mlfi_envto(SMFICTX *, char**); sfsistat mlfi_header(SMFICTX*, char*, char*); sfsistat mlfi_eoh(SMFICTX*); sfsistat mlfi_body(SMFICTX*, u_char *, size_t); @@ -86,6 +87,9 @@ string x_spam_status, x_spam_flag, x_spam_report, x_spam_prev_content_type; string x_spam_checker_version, x_spam_level, _content_type, _subject; + // The list of recipients for the current message + list <string> recipients; + // Process handling variables pid_t pid; int pipe_io[2][2]; ========================================================================== Patch for sendmail: ========================================================================== diff -Naur sendmail-8.11.6.orig/libmilter/engine.c sendmail-8.11.6/libmilter/engine.c --- sendmail-8.11.6.orig/libmilter/engine.c Mon Jan 22 20:00:16 2001 +++ sendmail-8.11.6/libmilter/engine.c Mon Nov 25 14:26:21 2002 @@ -406,8 +406,17 @@ ret = mi_wr_cmd(sd, timeout_ptr, SMFIR_DISCARD, NULL, 0); break; case SMFIS_ACCEPT: - ret = mi_wr_cmd(sd, timeout_ptr, SMFIR_ACCEPT, NULL, 0); + if (ctx->ctx_reply != NULL) { + ret = mi_wr_cmd(sd, timeout_ptr, SMFIR_ACCEPT, + ctx->ctx_reply, strlen(ctx->ctx_reply) + 1); + free(ctx->ctx_reply); + ctx->ctx_reply = NULL; + } + else + ret = mi_wr_cmd(sd, timeout_ptr, SMFIR_ACCEPT, NULL, 0); + break; + case _SMFIS_OPTIONS: { char buf[MILTER_OPTLEN]; diff -Naur sendmail-8.11.6.orig/sendmail/milter.c sendmail-8.11.6/sendmail/milter.c --- sendmail-8.11.6.orig/sendmail/milter.c Wed Aug 15 23:59:07 2001 +++ sendmail-8.11.6/sendmail/milter.c Mon Nov 25 14:56:52 2002 @@ -3425,7 +3425,7 @@ milter_error(m); break; } - if (rcmd != SMFIR_REPLYCODE && + if (rcmd != SMFIR_REPLYCODE && rcmd != SMFIR_ACCEPT && response != NULL) { sm_free(response); diff -Naur sendmail-8.11.6.orig/sendmail/srvrsmtp.c sendmail-8.11.6/sendmail/srvrsmtp.c --- sendmail-8.11.6.orig/sendmail/srvrsmtp.c Tue Jun 26 20:54:12 2001 +++ sendmail-8.11.6/sendmail/srvrsmtp.c Mon Nov 25 14:50:51 2002 @@ -206,6 +206,7 @@ volatile time_t wt; /* timeout after too many commands */ volatile time_t previous; /* time after checksmtpattack() */ volatile bool lognullconnection = TRUE; + volatile char * okresponse; register char *q; char *addr; char *greetcode = "220"; @@ -1819,6 +1820,7 @@ SmtpPhase = "collect"; buffer_errors(); collect(InChannel, TRUE, NULL, e); + okresponse = (char *) NULL; # if _FFR_MILTER if (milterize && @@ -1846,6 +1848,11 @@ case SMFIR_TEMPFAIL: usrerr("451 4.7.1 Please try again later"); break; + + case SMFIR_ACCEPT: + okresponse = response; + response = (char *) NULL; + break; } if (response != NULL) sm_free(response); @@ -1886,6 +1893,8 @@ } flush_errors(TRUE); buffer_errors(); + if (okresponse != NULL) + sm_free(okresponse); goto abortmessage; } @@ -1945,7 +1954,12 @@ e->e_to = NULL; /* issue success message */ - message("250 2.0.0 Message accepted for delivery", id); + if (okresponse) { + message(okresponse); + sm_free(okresponse); + } + else + message("250 2.0.0 Message accepted for delivery", id); /* if we just queued, poke it */ if (doublequeue && ==========================================================================