#define HASSNPRINTF 1 #ifndef __P #define __P(proto) proto #endif #include #ifdef linux #include #endif #include #include #include #include #include #include #include "libmilter/mfapi.h" /* * Al Smith May 9 2000 * * Enhancements submitted by Sven Nielsen May 13 2000 * Inline rejection suggested by Paul Yeh May 24 2000 * * Please note that this code requires sendmail-8.11.0 or newer. * * This code is distributed under the GNU General Public License. * Share and enjoy. */ /* * HISTORY * January 15 2003 -- vbsfilter-1.11a * Include sys/ioctl.h to get TIOCNOTTY on some systems. * * January 14 2003 -- vbsfilter-1.11 * Recognise quoted filenames with missing end quote. * Problem (and fix!) supplied by Ian j Hart * and Andrea Adams in response to a virus * termed W32.Sobig.A@mm. Thanks! * * November 12 2002 -- vbsfilter-1.10 * Use ioctl(TIOCNOTTY) on /dev/tty. * * Herbert Straub * close STDIN, STDOUT and STDERR to avoid hanging processes on exit. * * June 12 2002 -- vbsfilter-1.9 * 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. * * January 30 2002 -- vbsfilter-1.8 * Updated vbsfilter to detect uuencoded attachments. * Thanks to Matthew Wong for noting * that the "MyParty" virus spreads itself in this way. * * August 6 2001 -- vbsfilter-1.7 * 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 * * July 25 2001 -- vbsfilter-1.6 * Also recognise 'name=blah' as well as the more usual 'name="blah"'. * Patch sent in by Andrea Adams * * July 9 2001 -- vbsfilter-1.5 * Add support to reject arbitrary extensions. * * March 31 2001 -- vbsfilter-1.4 * Update for sendmail-8.12 * * February 19 2001 -- vbsfilter-1.3a * Non-zero terminated string error, noted by * Dirk Meyer * * July 21 2000 -- vbsfilter-1.3 * Update for sendmail-8.11 * * 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. */ /* * TODO * * detect any mime type, such as ms/vb-script or whathaveyou and * also change that into something harmless like text/plain */ struct mlfiPriv { int len; u_char *body; int addver; }; /* * 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", NULL }; #define VERTEXT "X-Filter-Version" #define VERSION "1.11a" #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) { char *p, *q, *r; 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); quoted = (*r == '"'); if (quoted) r++; for(; *r != '\r' && *r != '\n' && *r != '\0'; r++) { 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++; } if (*(r-5) == '.' && *(r-1) == '.') { for(ext = exts; *ext; ext++) { if (strlen(*ext) != 3) continue; if ( tolower(*(r-4)) == (*ext)[0] && tolower(*(r-3)) == (*ext)[1] && tolower(*(r-2)) == (*ext)[2]) { *(r-4) = ext2[0]; *(r-3) = ext2[1]; *(r-2) = ext2[2]; rc++; } } } if (*(r-4) == '.') { for(ext = exts; *ext; ext++) { if (strlen(*ext) != 3) continue; if ( tolower(*(r-3)) == (*ext)[0] && tolower(*(r-2)) == (*ext)[1] && tolower(*(r-1)) == (*ext)[2]) { *(r-3) = ext2[0]; *(r-2) = ext2[1]; *(r-1) = ext2[2]; rc++; } } } if (*(r-3) == '.') { for(ext = exts; *ext; ext++) { if (strlen(*ext) != 2) continue; if ( tolower(*(r-2)) == (*ext)[0] && tolower(*(r-1)) == (*ext)[1]) { *(r-2) = ext2[0]; *(r-1) = ext2[1]; 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 */ return SMFIS_TEMPFAIL; } priv->body = (u_char *) strdup("\n"); priv->len = 1; priv->addver = 1; if (!priv->body) { /* can't accept this message right now */ return SMFIS_TEMPFAIL; } /* save the private data */ smfi_setpriv(ctx, priv); /* continue processing */ return SMFIS_CONTINUE; } sfsistat mlfi_header(SMFICTX *ctx, char *headerf, char *headerv) { struct mlfiPriv *priv = MLFIPRIV; int found = 0; if (!strcmp(headerf, VERTEXT)) { priv->addver = 0; } 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) { smfi_setreply(ctx, "554", "5.6.1", "Rejecting inline executable attachment"); 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; priv->body = (u_char *) realloc(priv->body, priv->len + bodylen + 1); if (!priv->body) { /* can't accept this message right now */ return SMFIS_TEMPFAIL; } 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 (!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"); found_uuencode = detect_header(priv->body, "\nbegin ", " ", exts, "txt"); found = (found_type > found_disp) ? found_type : found_disp; found = (found_uuencode > found) ? found_uuencode : found; if (gethostname(host, sizeof(host)) < 0) { strncpy(host, "localhost", sizeof host); } sprintf(buf, "%s (%s)", VERSION, host); if (priv->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); smfi_replacebody (ctx, priv->body+1, priv->len-1); } 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 = { "VBFilter", /* 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:"; /* 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) { 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(); } return 0; }