#define HASSNPRINTF 1 #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 will only run with a sendmail-8.10.1 or * 8.10.2 MTA. Upgrade to v1.3 of the filter to use it with sendmail-8.11. * * This code is distributed under the GNU General Public License. * Share and enjoy. */ /* * HISTORY * 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; }; #define VERTEXT "X-Filter-Version" #define VERSION "1.2" #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) { int n; const char *p, *q; char x, y; for(p = s1, q = s2, n = 0; *p && *q && n < len; p++, q++, n++) { x = (isalpha((int) *p)) ? tolower(*p) : *p; y = (isalpha((int) *q)) ? tolower(*q) : *q; if (x != y) return(1); } if (n == len) { return(0); } return(1); } 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 *ext1, char *ext2) { char *p, *q, *r; int lfcnt, rc = 0; 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))) { for(r=q+strlen(subfield); *r != '\n' && *r != '\0' && *r != '"'; r++); if (*r == '"') { if (*(r-4) == '.' && tolower(*(r-3)) == ext1[0] && tolower(*(r-2)) == ext1[1] && tolower(*(r-1)) == ext1[2]) { /* vb script found */ *(r-3) = ext2[0]; *(r-2) = ext2[1]; *(r-1) = ext2[2]; rc++; } } } } } } return(rc); } sfsistat mlfi_envfrom(SMFICTX *ctx, char **envfrom) { struct mlfiPriv *priv; priv = malloc(sizeof *priv); if (!priv) { /* can't accept this message right now */ return SMFIS_TEMPFAIL; } priv->body = NULL; priv->len = 0; priv->addver = 1; /* 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 vbsfound = 0; int shsfound = 0; if (!strcmp(headerf, VERTEXT)) { priv->addver = 0; } if (!nocase_strncmp(headerf, "Content-Disposition", strlen("Content-Disposition"))) { vbsfound += detect_header((u_char *) headerv, "", "filename=\"", "vbs", "txt"); shsfound += detect_header((u_char *) headerv, "", "filename=\"", "shs", "txt"); } if (!nocase_strncmp(headerf, "Content-Type", strlen("Content-Type"))) { vbsfound += detect_header((u_char *) headerv, "", "name=\"", "vbs", "txt"); shsfound += detect_header((u_char *) headerv, "", "name=\"", "shs", "txt"); } if (vbsfound) { smfi_setreply(ctx, "554", "5.6.1", "Rejecting message with inline Visual Basic script"); return mlfi_cleanup(ctx, SMFIS_REJECT); } if (shsfound) { smfi_setreply(ctx, "554", "5.6.1", "Rejecting message with inline Shell Scrap data"); return mlfi_cleanup(ctx, SMFIS_REJECT); } return SMFIS_CONTINUE; } sfsistat mlfi_body(SMFICTX *ctx, u_char *bodyp, size_t bodylen) { struct mlfiPriv *priv = MLFIPRIV; priv->body = realloc(priv->body, priv->len + bodylen); 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; /* continue processing */ return SMFIS_CONTINUE; } sfsistat mlfi_eom(SMFICTX *ctx) { struct mlfiPriv *priv = MLFIPRIV; int found_disp = 0, found_type = 0; int found; char buf[1024]; char host[512]; /* * 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=\"", "vbs", "txt"); found_disp += detect_header(priv->body, "\nContent-Disposition", "filename=\"", "shs", "txt"); found_type = detect_header(priv->body, "\nContent-Type", "name=\"", "vbs", "txt"); found_type += detect_header(priv->body, "\nContent-Type", "name=\"", "shs", "txt"); found = (found_type > found_disp) ? found_type : found_disp; if (gethostname(host, sizeof(host)) < 0) strlcpy(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, priv->len); } 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_MODHDRS | SMFIF_MODBODY, /* flags */ NULL, /* connection info filter */ NULL, /* SMTP HELO command filter */ mlfi_envfrom, /* envelope sender filter */ NULL, /* envelope recipient filter */ mlfi_header, /* header filter */ NULL, /* 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; 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) { return smfi_main(); } return 0; }