#define HASSNPRINTF 1 #ifndef __P #define __P(proto) proto #endif #define true 1 #define false 0 /***************************************/ /* START OF CONFIGURABLE */ /* OPTIONS */ /***************************************/ #define DISCLAIMER 1 /* CHANGE TO 1 FOR DISCLAIMER, 0 FOR NO DISCLAIMER. */ #define DISCARD 0 /* WHEN SET REMOVES MESSAGE SILENTLY, NO RESPONSE TO SENDER */ #define PARANOID 0 /* REJECT DOUBLE EXTENSIONS. */ /* In PARANOID mode attachments that contain multiple dots in the name * or that have extensions longer than 4 characters are NOT accepted at all. * Change to 1 for a very paranoid life-style. (Eg: Banking environment) * Note that this also will block attachments with names like "Reportfrom12.12.2003.xls" , * which are likely to be valid. * Be pretty sure when enabling this. */ #define SPAMLEVEL 10 /* Summed individual values of known spamwords * present in the subject-line that will * classify a subject-line as spam-mail. * Change to what you want. * 0 is NO SPAM-checking at all. */ #define TOKEN_HITS 9 /* Maximum of funny characters allowed in subject-line. */ #define MAX_DISCL_SZ 2500 /* Maximum size of disclaimer. */ #define MAX_ATT_NAME 60 /* Maximum size of attachment-name. */ #define MaxSubjectLen 140 /* Max lenght of Subject-line, adapt if needed. */ #define BND_SZ 180 /* MAX Boundary size. */ #define WRKLEN 512 /* Max Subfield size. */ #define TRACEMODE 0 /* Debug mode only. Do NOT activate. */ /***************************************/ /* END OF CONFIGURABLE */ /* OPTIONS */ /* SEE: REPLACEMENTS below. */ /***************************************/ #include #ifdef linux #include #endif #include #include #include #include #include #include #include #include #include "libmilter/mfapi.h" /* * Remove the *NSPAM* before mailing. * Al Smith May 9 2000 * * Enhancements submitted by Sven Nielsen May 13 2000 * Inline rejection suggested by Paul Yeh May 24 2000 * Standard disclaimer by Wouter Wijker Jul 12 2000 * Standard disclaimer bug-fixes Wouter Wijker SEP 8 2000 * Standard disclaimer for sendmail 8.11.1 06-10-2000 **W.Wijker** * * Please note that this code requires sendmail-8.11.1 or newer. * * This code is distributed under the GNU General Public License. * Share and enjoy. */ /* * HISTORY (*W.Wijker*) * Mrt 08 2006 -- Correction for use of multiple boundaries in Outlook-2003 / Exchange * Symptom: Attachments show up in outlook as ATTxxxxxx.txt. * Reason: Combination of change in Outlook mailclient and * extra boundary checks on MS-Exchange. * The TXT-disclaimer got inserted at the wrong position causing exhange to * invalidate the message-boundaries. Outlook subsequently changes the received attachment * to ATTxxxxxx.txt since it cannot decode the messages and does not find a filename. * Typically happens when sending from a fully updated Outlook mail-client to a fully * updated MS-Exchange server. * Feb 07 2006 -- Correction for boundary not found when compiling with DISCLAIMER==0 * Noticed by Mark Eisenhut. * Aug 30 2005 -- Correction for possible buffer overflow. * Courtesy Andrew McGill. andrew@ledge.co.NSPAMza (Thanks) * Dec 21 2004 -- Corrections received from Sergey Smitienko. (Thanks) * [hunter@NSPAMcomsys.com.ua] * Sep 24 2004 -- Correction for the =?iso-8859-1?q?= format. * Jun 04 2004 -- added. (Thanks M Kerslager) * Feb 27 2004 -- Correction for =?Windows-1252?Q?FW:_Co type messages. * Feb 25 2004 -- Do not accept mails if subject-field is omitted from the mailheader. * Feb 24 2004 -- WINDOWS-1252?Q? spam-subjects also tested. * Feb 20 2004 -- Zip and dll extensions blocked. (MyDoom/NetSky) * Feb 20 2004 -- Discard E-mail silently or Reject-message to possibly wrong sender?(MyDoom/NetSky) * Feb 11 2004 -- Spamwords added. Where does it stop? * Jan 21 2004 -- ISO-8859-1?B? spam-subjects blocked. * Dec 29 2003 -- Anti Spam-enhancements. * Dec 19 2003 -- Disclaimer split in several languages. (Prevents compiler warning). * Dec 19 2003 -- Anti-spam capabilities added by testing the Subject-line. * NOT intended to replace spamassasin or any dnsrbl. Just additional. * Dec 18 2003 -- Changes to detect_header() function. Different way of determining * extension of the attachment. * Dec 17 2003 -- Trace functionality for debugging purposes added. * Dec 15 2003 -- Multiple domain-names allowed for adding disclaimer. * Suggestion from Steven Lockhart [stevel@*NSPAM*hugall.com.au] who has * multiple domains on one mail-server. * Dec 13 2003 -- PARANOID mode added. (Just one and only one extension allowed) * Nov 21 2003 -- Remarks of Milan Kerslager implemented. (finally) * Sep 3 2003 -- Added "zi" extension to also block variant of Sobig virus. * Sep 3 2003 -- Re-positioning of #defines to compile without DISCLAIMER usage. * Thanks: Milan Kerslager * Aug 19 2003 -- QAD-patch to test Lotus-Notes client. * Mar 20 2003 -- Synchronised with vbsfilter 1.11.a * Jul 1 2002 -- Improved detection of Attachment-name. * Jun 26 2002 -- Unquoted boundary in Outlook-Express 5.50.4133.2400 * -- Addition of mpg, mpe, eml, mp3 and wav to list of extensions. * Jun 25 2002 -- Synchronized this version with vbsfilter 1.9 * Modified version of detect_header. * Included the suggestions from : * * Ross Bergman noted that some malformed endings, * for example "foo.exe.". Also that a file can have a CLSID as a file * extension, again hidden by windows, and Windows will use the CLSID to * determine how to open the attachment. * * Updated vbsfilter to detect uuencoded attachments. * Thanks to Matthew Wong for noting * that the "MyParty" virus spreads itself in this way. * * Various fixes for the case where headers and/or body are empty. * Patch sent in by Sergiy Zhuk * * Update "dangerous extension" list. * Noted by David F. Russell * * Mar 05 2002 -- Correction for "filename="=?iso-8859-1?Q?Install=5Fclient.bat?=" * Content-type: application/octect/stream; * Means that the fishy extension is not in the last 4 chars. * Dec 10 2001 -- Block SCR files. * Nov 6 2001 -- Block HTA en REG ivm JS/Seeker.i virus. * Nov 1 2001 -- Block MPE * Okt 24 2001 -- Block pif usage. * Sept 19 2001 -- Block eml usage. (NIMDA virus) * Sept 17 2001 -- Block mpg usage. (too much net traffic) * Aug 6 2001 -- Added extensions to prevent SirCamm virus. (PIF LNK) * May 15 2001 -- Attempt to prevent memory leakage by re-construct * of realloc() phrasing. * Apr 9 2001 -- Prevent double execution of replacebody() when both * exe->txt and disclaimer are choosen. * Mar 20 2001 -- Automatic boundary detection and support of * Netscape-messenger. (MIME) ( Suggestion Jai Lamerton) * Disclaimer tested for: Netscape Communicator 4.76 * OUTLOOK-97 * OUTLOOK-Express * OUTLOOK-2000 * Eudora 5.0.2 * Okt 6 2000 -- Changes for sendmail 8.11.1 * Okt 3 2000 -- Changing MIME exe attachements to txt. * SEPT 8 2000 -- UPDATES to 1.3 and bug-fixing Disclaimer. * July 26 2000 -- Addition of Legal Disclaimer functionality. (W Wijker) * June 20 2000 -- vbsfilter-1.2 * Add recognition of .SHS attachments. * * May 24 2000 -- vbsfilter-1.1 * Add recognition of inline VBS attachments. * Libmilter doesn't let us modify the headers, so such mails * must be rejected. * * May 9 2000 -- vbsfilter-1.0 * Filter incoming visual basic script attachments into text attachments, * by changing the .vbs filename suffix into .txt. */ /******************************************************************************/ /******************************************************************************/ /****** *********/ /******** >> REPLACEMENTS << **********/ /********** CHANGE BELOW LINES TO REFLECT YOUR SITUATION ************/ /************ | **************/ /************** | ****************/ /**************** | ******************/ /****************** | ********************/ /******************** V *********************/ /******************************************************************************/ #if DISCLAIMER == 1 /*****************************************************************************/ /* REPLACE MY LEGAL RUBBISH BELOW WITH YOUR OWN LEGAL RUBBISH FORCED */ /* UPON YOU BY YOUR OWN WELL PAYED LAWYERS. */ /* */ /*****************************************************************************/ static char DISCL[MAX_DISCL_SZ]; static char DISCL_DUTCH[]= "\n Dit e-mail bericht is slechts bestemd voor de persoon of organisatie aan wie het is gericht. Het bericht kan informatie bevatten die persoonlijk of vertrouwelijk is. Tevens kan het informatie bevatten die niet publiekstoegankelijk van aard is. CVG aanvaardt geen aansprakelijkheid indien de inhoud van dit bericht en eventuele bijlagen in strijd is met wet of goede zeden. Indien de ontvanger van dit bericht niet de bedoelde persoon of organisatie is, wordt hierbij vermeld dat verdere verspreiding, openbaarmaking of vermenigvuldiging van dit bericht strikt verboden is en verzoeken wij u de inhoud niet te gebruiken en de afzender direct te informeren door het bericht te retourneren. Dank voor uw medewerking \n"; static char SEPARATOR[]= "+******+"; static char DISCL_ENG[]= "\n This e-mail message is meant solely for the person or organisation to whom it is adressed. The message may contain personal or confidential information, or information that is not public in nature. CVG accepts no responsibility for message content and possible attachments that are unlawful or of questionable decency. Further dissemination, publication or duplication of this message is strictly prohibited if the person or organisation receiving this message is not the intended recipient. In the event that you are not the intended recipient, we request you to refrain from using the content and to immediately inform the the sender of the error by returning the message. Thank you for your co-operation. \r\n"; #endif /*****************************************************************************/ /* REPLACE IN BELOW LINE THE "cvg.nl" BY THE DOMAIN YOU WANT THE DISCLAIMER */ /* USED FOR. */ /*****************************************************************************/ static char OURDOMAIN[]="cvg.nl"; /* <----- Change to YOUR domain!!!! */ /* Suggestion from Steven Lockhart [stevel@NSPAMhugall.com.au] who has * multiple domains on one mail-server. * Just fill in one of the below domains if you need this. * Currently a max of 4 Domains allowed. */ static char OURDOMAIN2[]=""; /* <----- Change to YOUR second domain if needed */ static char OURDOMAIN3[]=""; /* <----- Change to YOUR third domain if needed */ static char OURDOMAIN4[]=""; /* <----- Change to YOUR fourth domain if needed */ /******************************************************************************/ /******** END REPLACEMENTS ****************************************/ /******************************************************************************/ static char ATTACHNAME[MAX_ATT_NAME]; static FILE *fptr; static char WRKFLD[WRKLEN]; static char *tracefile=(char *)NULL; static char ATTACHNAME[MAX_ATT_NAME]; struct mlfiPriv { int len; u_char *body; int addver; int subject_present; int internal_sender; #if DISCLAIMER == 1 int txtdiscl_inserted; int htmldiscl_inserted; int allow; int htmlallow; char boundary[BND_SZ]; int msgtype; #endif }; #if DISCLAIMER == 1 int insert_disclaimer(struct mlfiPriv *priv); void initDisclaimer(SMFICTX *ctx, char *envfrom); int detectMsgFormat(SMFICTX *ctx,char *headerf,char *headerv); #endif int detectSubject(SMFICTX *ctx, char *headerf, char *headerv); void detectSender(SMFICTX *ctx, char *envfrom); void getAttachName(char *endp); void wtrace(char *p1); int size_ok(int x, int y); int b64_decode(char *dest, const char *src); /* * Microsoft considers these extensions dangerous: * http://support.microsoft.com/support/kb/articles/Q262/6/17.ASP */ static char *exts[] = { "ade", "adp", "bas", "bat", "chm", "cmd", "com", "cpl", "crt", "exe", "hlp", "hta", "inf", "ins", "isp", "js", "jse", "lnk", "mdb", "mde", "msc", "msi", "msp", "mst", "pcd", "pif", "reg", "scr", "sct", "shs", "shb", "url", "vb", "vbe", "vbs", "wsc", "wsf", "wsh", "mpg", "mpe", "eml", "wav", "mp3","zi","mpeg", "dll", NULL }; /* The SPAM-words below are all taken from real life spam subjects. * The table below denotes the word followed by its individual value. */ static char *spamwords[] = { "1nches","8", "approval","2", "affordable","2", "absolutely","2", " allegra","4", " allegrra","7", "amazing ","2", "anywhere ","2", " back ", "1", " bigger ", "2", "bottles ", "3", "burner ", "3", " buying ", "3", " buylng ", "3", " casino ", "4", " caslno ", "6", " cart ", "2", "cellphone ", "4", "consultation","2", "crack","3", "credit","3", " cheap ","2", " cheapest","3", " clinically ","3", " deal","2", "delivery ","3", " dick","3", " diet","3", " discount","3", "doctor","3", "dollar","3", "download","2", " drug","4", " earning","2", " easy ","2", " ebay","7", " enhance ","3", " enjoy ","2", "excited","3", "faith","3", " fat","3", "fountain","2", " free ","4", " gain ","3", "games","3", "get ","1", " girls","3", " gold","2", "greastest","7", "greatest","3", "greatly","3", " growth ","1", " half ","1", " hassles ","2", "health","3", "hedge ","2", "home","2", "hormone","3", "huge","3", " immediate","2", "impressing","3", " inch ","2", "inches","2", "incomes","2", "increase","3", "incredible","3", "inexpensive","3", " internet ","3", "insurance","3", " knocks","2", " large","2", " less","2", " levitra","5", "loans","2", " lottery","5", " low ", "1", "lowered", "2", " lowest", "3", "lucky", "3", " males ", "3", "marketing","1", " matters ","3", "mastercard","2", " medication","4", "medicatiion","7", " medicines","3", "money","3", "m0ney","7", "movies","2", "must read","4", " new ","1", " online","2", "opportunity ","3", " order ","2", "overnight","2", " pay ","2", "penis","4", " pennies ","4", " percent","2", "pharmacies ","4", "pharmacy ","4", "phentermine","8", "pills","3", "pillz ","7", "please read","4", "porno","4", " porn ","4", "portfolio","2", " pr1ce","9", " price","2", " prescription","5", " prescr1pt","7", " proven ","3", " products ","2", " quality ", "1", " rates","3", " rate ","2", " rich","2", " r1ch","8", " response", "2", " reduced", "2", "rolex", "2", "satisfy","3", " selling ","3", " shipping ","2", " shop","2", "shopping","2", " size ", "2", " sildenfil","5", " s0ftware", "7", " software", "3", "solutions", "2", " store ", "2", "suffering ","2", " sweepstake","6", " today ","2", "unsecured","4", " ur ","3", "viagra","8", " vicodin","6", " vlagra ","6", " visa","2", "weight","4", "winning","4", " winner","5", " women","3", " woman","3", " wolrdwide","8", " worldwide","3", " xanax","8", "youth","3", " your ","1", " you ","1", NULL, NULL }; /* Base64 decoding tables*/ static const unsigned char *b64_tbl = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; static const unsigned char b64_pad = '='; #define VERTEXT "X-Filter-Version" #define VERSION "2.12.Cvg-Notes-Spam" #define MLFIPRIV ((struct mlfiPriv *) smfi_getpriv(ctx)) extern sfsistat mlfi_cleanup(SMFICTX *, sfsistat); /******************************************************/ static int nocase_strncmp(const char *s1, const char *s2, int len) { register int n = len; for (; n; s1++, s2++, n--) { if (tolower(*s1) != tolower(*s2)) return (1); } return(n > 0); } /******************************************************/ static char * nocase_strstr(const char *s1, const char *s2) { const char *p, *q, *r; char x, y; for(p = s1; *p; p++) { for(q = s2, r = p; *q && *r; q++, r++) { x = (isalpha((int) *q)) ? tolower(*q) : *q; y = (isalpha((int) *r)) ? tolower(*r) : *r; if (x != y) break; } if (!*q) { return((char *) p); } else if (!*r) { return(NULL); } } return(NULL); } /******************************************************/ static int detect_header(u_char *body, char *header, char *subfield, char **exts, char *ext2) { /* Slightly modified by *WW* on 26-6-2002, and even more slightly in december 2003.... * Perhaps not very original anymore. Sorry Al. */ char *p, *q, *r; char *r_save; char *first_dot; char *last_dot; int i, lfcnt, rc = 0, quoted; char **ext; for(p = (char *) body; p && (p = nocase_strstr(p, header)); p++) { for(lfcnt = 0, q = p+strlen(header); *q && lfcnt < 3; q++) { if (*q == '\n') { lfcnt++; } else { if (!nocase_strncmp(q, subfield, strlen(subfield))) { r=q+strlen(subfield); r_save=q; /*WW 21-11-2003 Startpos of subfield string */ quoted=(*r=='"'); if (quoted) { r++; } for(; *r != '\r' && *r != '\n' && *r != '\0'; r++) { /* Determine end-position (r), either by CR/LF or end-quote. * r points to last valid delimiter, either '"' or CR/LF. */ if (quoted && *r == '"') { break; } } if ((quoted && *r == '"') || (*r == '\n' || *r == '\r')) { if (*(r-1) == '}' && r-q > 39 && *(r-38) == '{' && *(r-39) == '.') { for(i = 39; i > 0; i--) { *(r-i) = ' '; } rc++; } memset(WRKFLD,0X00,WRKLEN); memcpy(WRKFLD,r_save,(int)(r-r_save)); if ((last_dot=strrchr(WRKFLD,'.'))!=(char *)NULL) { /* There is a dot present. */ if ((first_dot=strchr(WRKFLD,'.'))!=(char *)NULL) { if (first_dot != last_dot) { /* Double or more extensions used. * This could be a suspicious attachment. * E.G: linux.tar.gz is a valid extension. * funnypic.gif.exe is NOT valid. * monkey..exe is NOT valid. * mule...pif is not valid. * virus.exe. is NOT valid */ if (PARANOID) { /* Block ALL attachments with double extensions. * Note: This also blocks "report 13.6.2003.xls" type of * attachments. */ rc++; getAttachName((char *)(last_dot + 3)); } } if (last_dot==(char *)&WRKFLD[(strlen(WRKFLD)-1)] ) { /* Malformed ending of filename. * e.g: module.exe. */ getAttachName((char *)(last_dot + 3)); rc++; } if ((&WRKFLD[strlen(WRKFLD)]-last_dot) > 5) { /* Extension exceeds 4 chars in length. * EG: games.exelent * Subject: Rename to games.exe before playing. */ if (PARANOID) { rc++; getAttachName((char *)(last_dot + 3)); } } } for (ext = exts; *ext && !rc ; ext++) { /* Go check the extension against the list * of those extensions to be denied. */ if (strlen(*ext) > 4) { /* Check if it is a valid table-entry. * Only 1-4 Char allowed for extensions. */ continue; } if ((WRKFLD+strlen(WRKFLD)-last_dot-1) == (int)strlen(*ext)) { /* Only compare if extension lenght is * exactly the lenght of the attachment's extension. * EG: Block virus.zi but * Allow zippo.zip */ if (!nocase_strncmp((char *)(last_dot+1),*ext,strlen(*ext))) { /* Found a match. Unwanted extension */ getAttachName((char *)(last_dot + strlen(*ext))); rc++; } } } } } } } } } return(rc); } /******************************************************/ sfsistat mlfi_envfrom(SMFICTX *ctx, char **envfrom) { struct mlfiPriv *priv; priv = (struct mlfiPriv *)malloc(sizeof *priv); if (!priv) { /* can't accept this message right now */ wtrace("NO Memory available for malloc"); return SMFIS_TEMPFAIL; } memset(priv,0x00,sizeof(priv)); priv->body = NULL; priv->len = 0; priv->addver = 1; smfi_setpriv(ctx, priv); priv->subject_present=0; detectSender(ctx, *envfrom); #if DISCLAIMER==1 initDisclaimer(ctx,*envfrom); #endif /* save the private data */ /* continue processing */ return SMFIS_CONTINUE; } /******************************************************/ sfsistat mlfi_header(SMFICTX *ctx, char *headerf, char *headerv) { struct mlfiPriv *priv = MLFIPRIV; int found=0; int spamfound=0; char MESG[300]; if (!strcmp(headerf, VERTEXT)) { priv->addver = 0; } #if DISCLAIMER==1 if (!detectMsgFormat(ctx,headerf,headerv)) { /* Message format was not recognized. */ if (DISCARD) { return mlfi_cleanup(ctx, SMFIS_DISCARD); } else { strcpy(MESG,"Rejecting: Reason is invalid message-boundary or format."); smfi_setreply(ctx,"554", "5.6.1", MESG); return mlfi_cleanup(ctx, SMFIS_REJECT); } } #endif if (!priv->internal_sender) { /* Check inbound mail from spam subjects and the * presence of a subject header. */ spamfound=detectSubject(ctx, headerf, headerv); } if (!nocase_strncmp(headerf, "Content-Disposition", strlen("Content-Disposition"))) { found = detect_header((u_char *) headerv, "", "filename=", exts, "txt"); } if (!nocase_strncmp(headerf, "Content-Type", strlen("Content-Type"))) { found += detect_header((u_char *) headerv, "", "name=", exts, "txt"); } if (found) { memset(MESG,0X00,200); if (strlen(ATTACHNAME)) { /* Lets be slightly specific about what module we are rejecting. */ strcpy(MESG,"Rejecting inline attachment: "); strcat(MESG,ATTACHNAME); } else { /* No details given */ strcpy(MESG,"Rejecting inline attachment."); } if (DISCARD) { return mlfi_cleanup(ctx, SMFIS_DISCARD); } else { smfi_setreply(ctx,"554", "5.6.1", MESG); return mlfi_cleanup(ctx, SMFIS_REJECT); } } if (spamfound) { /* The subject-line contained indications of spam. */ strcpy(MESG,"Rejecting: Your Subjectline,\" "); strcat(MESG,headerv); strcat(MESG,"\" looks like Spam-mail. Please change Subject. "); switch (spamfound) { case 1: strcat(MESG,"Lenght"); break; case 2: strcat(MESG,"Binary"); break; case 3: strcat(MESG,"Tokens"); break; case 4: strcat(MESG,"Words"); break; case 5: strcat(MESG,"Space"); break; case 6: strcat(MESG, "Combined"); break; default: break; } if (DISCARD) { return mlfi_cleanup(ctx, SMFIS_DISCARD); } else { smfi_setreply(ctx,"554", "5.6.1", MESG); return mlfi_cleanup(ctx, SMFIS_REJECT); } } return SMFIS_CONTINUE; } /******************************************************/ sfsistat mlfi_eoh(SMFICTX *ctx) { return SMFIS_CONTINUE; } /******************************************************/ sfsistat mlfi_body(SMFICTX *ctx, u_char *bodyp, size_t bodylen) { struct mlfiPriv *priv = MLFIPRIV; u_char *memp; memp = (u_char *)realloc(priv->body, priv->len + bodylen +1); if (!memp) { /* can't accept this message right now */ smfi_setreply(ctx,"554", "5.6.1", "cvgfilter: Memory realloc failed"); wtrace("NO Memory available for realloc"); return SMFIS_TEMPFAIL; } priv->body=memp; memcpy((u_char*)(priv->body+priv->len), bodyp, bodylen); priv->len += bodylen; priv->body[priv->len]= '\0'; /* continue processing */ return SMFIS_CONTINUE; } /******************************************************/ sfsistat mlfi_eom(SMFICTX *ctx) { struct mlfiPriv *priv = MLFIPRIV; int found_disp = 0, found_type = 0, found_uuencode = 0; int found; char buf[1024]; char host[512]; #if DISCLAIMER == 1 int replaceanyway; replaceanyway=0; #endif if (!priv->body) { return mlfi_cleanup(ctx, SMFIS_CONTINUE); } /* * here's an example of what netscape sends out: * * Content-Type: application/octet-stream; name="test.vbs" * Content-Transfer-Encoding: base64 * Content-Disposition: inline; filename="test.vbs" */ found_disp = detect_header(priv->body, "\nContent-Disposition", "filename=", exts, "txt"); found_type = detect_header(priv->body, "\nContent-Type", "name=", exts, "txt"); #if DISCLAIMER == 1 if (!strlen(priv->boundary)) {/* Only test for uuencoded if NO boundary was detected. * This reduces false positives triggered by the word "begin" in the body of an otherwise * valid message. */ found_uuencode = detect_header(priv->body, "\nbegin ", " ", exts, "txt"); } #endif found = (found_type > found_disp) ? found_type : found_disp; found = (found_uuencode > found) ? found_uuencode : found; if (!priv->subject_present && !priv->internal_sender) { /* No subject given on this e-mail and it is comming from external. * Reject it. * Example: * from 218-166-65-193.HINET-IP.hinet.net * (218-166-65-193.HINET-IP.hinet.net [218.166.65.193]) * by cvgpost.cwg.nl (8.12.10/8.12.10) with SMTP id i1GFCuxW022459 * for ; Mon, 16 Feb 2004 16:12:59 +0100 * Date: Mon, 16 Feb 2004 16:12:57 +0100 * From: niinbspbfbfczl@msn.com * Received: from 45.15.204.124 by 218.166.65.193; Mon, 16 Feb 2004 16:12:40 +0100 * Message-ID: addver) smfi_addheader(ctx, VERTEXT, buf); if (found) { sprintf(buf, "%d attachment%s changed to TXT", found, (found == 1) ? "" : "s"); smfi_addheader(ctx, "X-Filter", buf); #if DISCLAIMER == 1 replaceanyway=1; /* Thanks Jai Lamerton */ #else smfi_replacebody(ctx, priv->body, priv->len); #endif } #if DISCLAIMER == 1 if (insert_disclaimer(priv) || replaceanyway) { if (priv->body) { smfi_replacebody(ctx, priv->body, priv->len); } } #endif return mlfi_cleanup(ctx, SMFIS_CONTINUE); } /******************************************************/ sfsistat mlfi_close(SMFICTX *ctx) { return mlfi_cleanup(ctx, SMFIS_ACCEPT); } /******************************************************/ sfsistat mlfi_abort(SMFICTX *ctx) { return mlfi_cleanup(ctx, SMFIS_CONTINUE); } /******************************************************/ sfsistat mlfi_cleanup(SMFICTX *ctx, sfsistat rc) { struct mlfiPriv *priv = MLFIPRIV; if (priv) { if (priv->body) free(priv->body); free(priv); smfi_setpriv(ctx, NULL); } return(rc); } /******************************************************/ struct smfiDesc smfilter = { "cvgfilter", /* filter name */ SMFI_VERSION, /* version code -- do not change */ SMFIF_ADDHDRS | SMFIF_CHGBODY , /* flags */ NULL, /* connection info filter */ NULL, /* SMTP HELO command filter */ mlfi_envfrom, /* envelope sender filter */ NULL, /* envelope recipient filter */ mlfi_header, /* header filter */ mlfi_eoh, /* end of header */ mlfi_body, /* body block filter */ mlfi_eom, /* end of message */ mlfi_abort, /* message aborted */ mlfi_close /* connection cleanup */ }; /******************************************************/ int main(int argc, char *argv[]) { int c, fd; const char *args = "p:"; if (TRACEMODE) { /* Just for the sake of debugging. */ tracefile=(char *)getenv("TRACEFILE"); if (tracefile==(char *)NULL) { /* NO environment */ fptr=fopen("/tmp/TRACE","a+"); } else { /*Own file location */ fptr=fopen(tracefile,"a+"); } } /* Process command line options */ while ((c = getopt(argc, argv, args)) != -1) { switch (c) { case 'p': if (optarg == NULL || *optarg == '\0') { (void) fprintf(stderr, "Illegal conn: %s\n", optarg); exit(EX_USAGE); } (void) smfi_setconn(optarg); break; } } if (smfi_register(smfilter) == MI_FAILURE) { fprintf(stderr, "smfi_register failed\n"); exit(EX_UNAVAILABLE); } if (fork() == 0) { /*Child process */ close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); fd=open("dev/tty",O_RDWR); if (fd>=0) { ioctl(fd,TIOCNOTTY,0); close(fd); } return smfi_main(); /* libmilter/main.c */ } return 0; } /********************************************************/ void detectSender(SMFICTX *ctx, char *envfrom) { struct mlfiPriv *priv = MLFIPRIV; priv->internal_sender = 0; if ( nocase_strstr(envfrom,OURDOMAIN) || nocase_strstr(envfrom,OURDOMAIN2) || nocase_strstr(envfrom,OURDOMAIN3) || nocase_strstr(envfrom,OURDOMAIN4)) { /* Internal sender from standard domain. * Add disclaimer. */ if (strlen(OURDOMAIN)) { if (nocase_strstr(envfrom,OURDOMAIN)) { /* First additional domain. */ priv->internal_sender = 1; } } if (strlen(OURDOMAIN2)) { if (nocase_strstr(envfrom,OURDOMAIN2)) { /* First additional domain. */ priv->internal_sender = 1; } } if (strlen(OURDOMAIN3)) { if (nocase_strstr(envfrom,OURDOMAIN3)) { /* First additional domain */ priv->internal_sender = 1; } } if (strlen(OURDOMAIN4)) { if (nocase_strstr(envfrom,OURDOMAIN4)) { /* First additional domain */ priv->internal_sender = 1; } } } } /********************************/ #if DISCLAIMER==1 int insert_disclaimer(struct mlfiPriv *priv) { /* Insertion of disclaimer in the text/html and text/plain segments * of an E-mail message. * The Html-message is minimized in font. */ int RC; char *end_boundary; char *txtplain_p; char *txthtml_p; u_char *memp; char BOUNDARY[BND_SZ]; char BOUNDARY2[BND_SZ]; char DISCL2[MAX_DISCL_SZ+100]; char *tmp_p; char *p3, *p4, *p5; RC=0; memp = (u_char *)realloc(priv->body, priv->len + 5*strlen(DISCL)); if (!memp) { /* Short of memory * Return without destroying priv->body. (18-5-2001) */ return RC; } priv->body=memp; if (priv->internal_sender && priv->allow && !priv->txtdiscl_inserted) { /* Insertion into first part of multipart message. */ txtplain_p=(char *)priv->body; txtplain_p=(char *)nocase_strstr(txtplain_p,"Content-Type: text/plain"); if (strlen((char *)priv->boundary)) { /* Prefix obtained boundary by "--" */ memset(BOUNDARY,0X00,BND_SZ); strcpy(BOUNDARY,"--"); strcat(BOUNDARY,priv->boundary); } if (strstr(BOUNDARY,"--=_mixed ")) { /* Lotus Notes client. Separate boundaries for individual parts. * =_mixed 23242424 en =_alternative 23242425 * Test both and alternative boundary first. * Boundaries differ in code also. Limit to 7 positions. */ strcpy(BOUNDARY2,BOUNDARY); memset(BOUNDARY,0X00,BND_SZ); strcpy(BOUNDARY,"--=_alternative "); strncat(BOUNDARY,(char *)&BOUNDARY2[10],7); /* The 7 chars following "_mixed " string. */ } else { if (strstr(BOUNDARY,"--=_alternative ")) { memset(BOUNDARY2,0X00,BND_SZ); strcpy(BOUNDARY2,"--=_mixed "); strncat(BOUNDARY2,(char *)&BOUNDARY[16],7); } } /** 8-3-2006 **/ /* OUTLOOK Patch-level Build 10.0.6626 multiple boundaries. * Combined with receiving up-to-date MS-Exchange mailserver results in garbled attachments. * Typically: ATTxxxxxx.txt if attachment is being send. * Concerns: Pre servicepack 3 voor MS-Exchange. * Boundaries are nested. "----=_NextPart_000_0001_")) * ^^^^--> Increasing hex number for every mail send. * ^^^-------> Level number. Increasing for every nest. * * Typical: "------=_NextPart_000_000D_01C637CA.C8D101E0--" * Assuming 000 and 001 are the html and text part. * Forgetting about the remainder of the boundary. */ if (strstr(BOUNDARY,"------=_NextPart_000_")) { strcpy(BOUNDARY2,BOUNDARY); strcpy(BOUNDARY,"------=_NextPart_001_"); } else { if (strstr(BOUNDARY,"------=_NextPart_001_")) { strcpy(BOUNDARY2,"------=_NextPart_000_"); } } /** End change 8-3-2006 **/ if (txtplain_p!=(char *)NULL) { /* First segment of message */ txtplain_p+=13; end_boundary=(char *)strstr(txtplain_p, BOUNDARY); if (end_boundary!=(char *)NULL) { /* Insert the disclaimer */ memmove((char *)(end_boundary+strlen(DISCL)),end_boundary, (strlen(end_boundary) + 1)) ; memcpy((char *)end_boundary,DISCL,strlen(DISCL)); priv->len += strlen(DISCL); priv->txtdiscl_inserted=1; RC=1; } } if (txtplain_p!=(char *)NULL && !priv->txtdiscl_inserted) { /* Test for second boundary of LOTUS-NOTES client. */ txtplain_p+=13; end_boundary=(char *)strstr(txtplain_p, BOUNDARY2); if (end_boundary!=(char *)NULL) { /* Insert the disclaimer */ memmove((char *)(end_boundary+strlen(DISCL)),end_boundary, (strlen(end_boundary) + 1)) ; memcpy((char *)end_boundary,DISCL,strlen(DISCL)); priv->len += strlen(DISCL); priv->txtdiscl_inserted=1; RC=1; } } } if (priv->internal_sender && priv->htmlallow && !priv->htmldiscl_inserted) {/* Insertion in first HTML segment of multipart message. */ txthtml_p=(char *)priv->body; txthtml_p=(char *)nocase_strstr(txthtml_p,"Content-Type: text/html"); if (txthtml_p!=(char *)NULL ) { /* Second segment of Outlook (alternative html format) * For those mailers with HTML preference that forget * everything about their and terminate with * only(NS Communicator) we have to determine what * comes first. End of body or end of html. */ memset(BOUNDARY,0X00,BND_SZ); txthtml_p+=13; p3=(char *)nocase_strstr(txthtml_p, ""); p4=(char *)nocase_strstr(txthtml_p, ""); p5=(char *)nocase_strstr(txthtml_p, ""); /* Incomplete html from Notes */ end_boundary=(char *)NULL; if (p3!=(char *)NULL && p4!=(char *)NULL) { if (p3 as the end of a HTML message. * Apparently Lotus-Notes does not only forget about * and , but also on a lot of other items! */ end_boundary=p5; } strcpy(DISCL2,"


"); strcat(DISCL2,DISCL); strcat(DISCL2,"


"); tmp_p=(char *)nocase_strstr(DISCL2,SEPARATOR); if (tmp_p!=(char *)NULL) { /* Substitute the marker with a ruler */ memcpy( tmp_p,"

",8); } if (end_boundary!=(char *)NULL) { /* Insert the disclaimer */ memmove((char *)(end_boundary+strlen(DISCL2)), end_boundary, (strlen(end_boundary)+1)); memcpy((char *)end_boundary,DISCL2,strlen(DISCL2)); priv->len += strlen(DISCL2); priv->htmldiscl_inserted=1; RC=1; } } } if ( priv->internal_sender && !priv->htmldiscl_inserted && !priv->txtdiscl_inserted && (priv->msgtype==3 || priv->msgtype==1 || priv->msgtype==2)) { /* Just one /text/plain message no attachments OUTLOOK-97 MIME. * We did not insert anything else before. * It is save to add the text disclaimer to the end of the message * body. */ memcpy((char *)(priv->body+ priv->len), DISCL, strlen(DISCL)); priv->len +=strlen(DISCL); RC=1; } if ( priv->internal_sender && !priv->htmldiscl_inserted && !priv->txtdiscl_inserted && priv->msgtype==4 ) { /* Just one /text/html message no attachments. * We did not insert anything else before. */ txthtml_p=(char *)priv->body; if (txthtml_p!=(char *)NULL ) { /* Second segment of Outlook (alternative html format) * For those mailers with HTML preference that forget * everything about their and terminate with * only(NS Communicator) we have to determine what * comes first. End of body or end of html. */ memset(BOUNDARY,0X00,BND_SZ); txthtml_p+=13; end_boundary=(char *)NULL; p3=(char *)nocase_strstr(txthtml_p, ""); p4=(char *)nocase_strstr(txthtml_p, ""); if (p3!=(char *)NULL && p4!=(char *)NULL) { /* Both tags present. Find the lowest position. */ if (p3

"); strcat(DISCL2,DISCL); strcat(DISCL2,"


"); tmp_p=(char *)nocase_strstr(DISCL2,"+******+"); if (tmp_p!=(char *)NULL) { /* Substitute the marker with a ruler */ memcpy( tmp_p,"

",8); } if (end_boundary!=(char *)NULL) { /* Insert the disclaimer */ memmove((char *)(end_boundary+strlen(DISCL2)), end_boundary, (strlen(end_boundary)+1)); memcpy((char *)end_boundary,DISCL2,strlen(DISCL2)); priv->len += strlen(DISCL2); priv->htmldiscl_inserted=1; RC=1; } } } return(RC); } /********************************************************/ void initDisclaimer(SMFICTX *ctx, char *envfrom) { /* Initializes the disclaimer and inserts a separator string. */ struct mlfiPriv *priv = MLFIPRIV; priv->htmldiscl_inserted = 0; priv->txtdiscl_inserted = 0; priv->allow=0; priv->htmlallow=0; priv->msgtype =0; memset(DISCL,0X00,MAX_DISCL_SZ); strcat(DISCL,DISCL_DUTCH); strcat(DISCL,SEPARATOR); strcat(DISCL,DISCL_ENG); memset(priv->boundary,0X00,BND_SZ); } /***********************************************************/ int detectMsgFormat(SMFICTX *ctx, char *headerf, char *headerv) { /* Tries to figure out what kind of mailer is being used * and what separator string is used. * Returns "false" (0) if size of boundary is too long (>BND_SZ). */ struct mlfiPriv *priv = MLFIPRIV; char *p1, *p2, *p3; if (!nocase_strncmp(headerf,"Content-Type",strlen("Content-Type")) && !priv->msgtype) { if ((p3=nocase_strstr(headerv,"multipart/")) !=(char *)NULL) { /* Allow hanky-panky with the message-body. * Test for multipart message. * If so, allow insertion. * Determine the boundary-string for separate messages * in a mulipart MIME-message. */ if ((p3=nocase_strstr(headerv,"multipart/alternative"))!=(char *)NULL) { /* Messages consisting of at least two identical * parts. (First part is plain/text, second is * the same message in HTML format. */ priv->allow=1; priv->htmlallow=1; priv->msgtype=1; } if ((p3=nocase_strstr(headerv,"multipart/mixed"))!=(char *)NULL) { /* Message consisting of multiple parts. * First two parts need not contain the same message. * The first chunk contains the message. * Can be /text/plain or /text/html */ priv->allow=1; priv->htmlallow=1; priv->msgtype=2; } p1=nocase_strstr(headerv,"boundary=\""); if (p1!=(char *)NULL) { /*Quoted boundary */ p1+=strlen("boundary=\""); p2=nocase_strstr((char *)p1,"\""); if (p2!=(char *)NULL) {/* Properly terminated by quote */ memset(priv->boundary,0X00,BND_SZ); if ( size_ok((int)(p2-p1),BND_SZ) ) { strncpy(priv->boundary,p1,(int)(p2-p1)); return(true); } if (p1) wtrace(p1); wtrace("No termination quote. Multipart."); return(false); } wtrace(p1); wtrace("Default fail. Multipart."); return(false); } else { /* Unquoted boundary. Whats up next?? * Outlook Express 5.50.4133.2400 * Example: * Subject: 6 nieuwe GRATIS produkten - laatste aanbieding ooit * MIME-Version: 1.0 * Content-Type: multipart/alternative; boundary=349873318062989 */ p1=nocase_strstr(headerv,"boundary="); if (p1!=(char *)NULL) { p1+=strlen("boundary="); p2=strstr((char *)p1,"\n"); if (p2==(char *)NULL) { p2=strstr((char *)p1,"\r"); } if (p2!=(char *)NULL) { memset(priv->boundary,0X00,BND_SZ); if ( size_ok((int)(p2-p1),BND_SZ) ) { strncpy(priv->boundary,p1,(int)(p2-p1)); return(true); } if (p1) wtrace(p1); wtrace("Failed. Unquoted boundary."); return(false); } wtrace(p1); wtrace("Default fail. Unquoted boundary. Er was er een!"); return(true); /*return(false); */ } } return(true); /* Moet true zijn omdat anders Multipart messages die * als eerste een text/plain inhoud hebben anders fout gaan. */ } if((p3=nocase_strstr(headerv,"text/plain"))!=(char *)NULL) { /* Test for singular plain text message. */ priv->msgtype=3; return(true); } if((p3=nocase_strstr(headerv,"text/html"))!=(char *)NULL) { /* Test for singular message in HTML format. */ priv->htmlallow=1; priv->msgtype=4; return(true); } } return(true); } #endif /***********************************************************/ void getAttachName(char *endp) { /* Try to get the offending module-name that we are rejecting.. * Is a bit more informative in the logfiles. * Suggestions from M Kerslager implemented. */ int cnt=0; char *startp; startp=endp; memset(ATTACHNAME,0x00,MAX_ATT_NAME); while (*startp!='=' && *startp!='"' && cntsubject_present=1; if (strlen(headerv)>MaxSubjectLen) { /* A subject line larger than MaxSubject characters we are willing to accept. * Indication for a crafted subject line. (SPAM) */ return(1); } memset(veld,0x00,256); memset(veld2,0x00,256); memset(subject_dec,0x00,256); memset(decoded,0x00,256); memset(tmpfld,0x00,256); pv=veld; last=headerv; count=0; n=0; lensub=strlen(headerv); p1=headerv; p5=subject_dec; strcpy(pattern,"=?iso-"); len=6; if ((nocase_strstr(p1,"=?windows-"))!=(char *)NULL) { /*EG: =?Windows-1252?Q?FW:_Consci=EAncias */ strcpy(pattern,"=?windows-"); len=4; } while ((p1=nocase_strstr(p1,pattern))!=(char *)NULL ) { /* Coded subject line character set. */ if (p2!=(char *)NULL) { p1=p2; } if (p1>last) { /* Uncoded part followed by coded part. * First copy the uncoded part to subject. */ strncat(subject_dec,last,(int)(p1-last)); } p1+=(strlen(pattern)+len); if (!nocase_strncmp(p1,"?b?",3)) { /* Base64 coded subjectline. */ if (( p3=nocase_strstr(p1,"?="))!=(char *)NULL) { last=(char *)(p3+2); strncpy(tmpfld,(char *)(p1+3),(int)(p3-p1+3)); b64_decode(decoded, tmpfld ); strcat(subject_dec,decoded); } if (!count) { /* A headstart for crafted messages. */ count+=4; } } if (!nocase_strncmp(p1, "?q?",3)) { /* Quoted printable subjectline. * EG: =?iso-8859-1?q?_Bitte_bem=FChem_Sie_sich_es_f=FCr_Kinder_Gottes_zu_nutzen?= * =?iso-8859-1?Q?=C4nderung_=28_Form_80=2F45_=29?= * =?ISO-2022-JP?B?GyRCJDRNOztxJE4kNDBGRmIbKEI=?= */ if (( p3=nocase_strstr((char *)(p1+3),"?="))!=(char *)NULL) { /* A valid end-marker exists. * =?iso-8859-1?Q?=C4nderung_=28_Form_80=2F45_=29?= */ last=(char *)(p3+2); p4=(char *)(p1+3); while (p4 < p3) { if ( *p4=='_') { *p5=' '; *p4++; *p5++; } else if ( *p4=='=') { int dec; char hulp[5]; memset(hulp,0X00,5); strncpy(hulp,"0X",2); strncat(hulp,(char *)(p4+1),2); sscanf(hulp,"%x",&dec); p4+=3; if ((dec >= 249 && dec <=252) || (dec >= 217 && dec <=220)) { /* All U variants to u. (iso8859-1) */ *p5='u'; } else if ((dec >= 224 && dec <=229) || (dec >= 192 && dec <=198)) { /* All A variants to a. (iso8859-1) */ *p5='a'; } else if ((dec >= 200 && dec <=203) || (dec >= 232 && dec <=235)) { /* All E variants to e. (iso8859-1) */ *p5='e'; } else if ((dec >= 242 && dec <=246) || (dec >= 210 && dec <=214)) { /* All O variants to o. (iso8859-1) */ *p5='o'; } else if ((dec >= 236 && dec <=239) || (dec >= 204 && dec <=207)) { /* All I variants to i. (iso8859-1) */ *p5='i'; } *p5++; } else if ( *p4=='?' && *(p4+1)=='=') { p4+=2; /*Thanks Sergey... */ *p5++; } else { *p5=*p4; *p4++; *p5++; } } } } *p1++; } if (last< (char *)( headerv+strlen(headerv))) { /* Remainder of the line. Coded text folllowed by non-coded. */ strcat(subject_dec,last); } if (strlen(subject_dec)) { /* If a coded subjectline was present in the mail, then * the filter tests against the decoded subjectline. */ psub=subject_dec; lensub=strlen(subject_dec); } else { psub=headerv; lensub=strlen(headerv); } while (n < lensub ) { /* Fill veld , remove and count the tokens. */ if (strchr(TOKENS,*psub)) { /* Character is in the TOKEN string. */ count++; } else {/* Move all none-garbage(token) characters to temporary field. * So that it contains a nice string of pure detectable words. * Thus bypassing the crap spammers put in the Subject Lines. * E.g.: _;Large D'I:C;K or MON_E"Y ;back k zyioqjc */ *pv=*psub; *pv++; } *psub++; n++; } if (count>TOKEN_HITS) { /* Maximum of "funny" tokens in subject-line exceeded. */ return(3); } if (SPAMLEVEL>0) { /* Combination of token count-value and number of hits on * known spam-words determines if E-mail is rejected * as possible spam. * Do not set Spamlevel too low to prevent false positives. */ spamwrdsum=0; for (spam = spamwords; *spam ; spam++) { /* Go check the Subject-line against the list * of known spamwords. */ if (nocase_strstr(veld, *spam)!=(char*) NULL) { spam++; spamwrdsum+=atoi(*spam); } else { spam++; } if (spamwrdsum >= SPAMLEVEL) { /* This subject classifies as spam. */ return(4); } } if (nocase_strstr(veld," ")) {/* Large amount of white space in subject line. */ return(5); } if (spamwrdsum > (SPAMLEVEL/2) && count> (TOKEN_HITS/2)) { /* A combination of tokens and spamwords found, but none of the * individual values tripped. * The combined values do. */ return(6); } } } return(0); } /************************************************************/ static void decode_group(unsigned char output[], const unsigned char input[], int *n) { /* Base64 decode a group of 4 input chars into a group of between 0 and * 3 output chars. */ unsigned char *t1,*t2; *n = 0; if (input[0] == b64_pad) return; if (input[1] == b64_pad) { return; } t1 = strchr(b64_tbl, input[0]); t2 = strchr(b64_tbl, input[1]); if ((t1 == NULL) || (t2 == NULL)) { return; } output[(*n)++] = ((t1 - b64_tbl) << 2) | ((t2 - b64_tbl) >> 4); if (input[2] == b64_pad) return; t1 = strchr(b64_tbl, input[2]); if (t1 == NULL) { return; } output[(*n)++] = ((t2 - b64_tbl) << 4) | ((t1 - b64_tbl) >> 2); if (input[3] == b64_pad) return; t2 = strchr(b64_tbl, input[3]); if (t2 == NULL) { return; } output[(*n)++] = ((t1 - b64_tbl) << 6) | (t2 - b64_tbl); return; } /************************************************************/ int b64_decode(char *dest, const char *src) { int len; int outsz = 0; while (*src) { decode_group(dest + outsz, src, &len); src += 4; outsz += len; } return outsz; } /************************************************************/ int size_ok(int x, int y) { /* Checks if x contains a positive integer and is smaller than y. * returns true if x< y and not negative. * Based on suggestions from Andrew McGill. 30-8-2005 * Prevents unwanted buffer-overflow that could crash cvgfilter and * stop the delivering of mail. Thnks Pngs :-) */ if (x<0 || y<0) { return(false); } else { if (x0) { return(true); } else { return(false); } } } /************************************************************/