#define HASSNPRINTF 1
#ifndef __P
#define __P(proto) proto
#endif
#include <ctype.h>
#include <fcntl.h>
#ifdef linux
#include <getopt.h>
#include <sys/ioctl.h>
#endif
#include <stdio.h>
#include <string.h>
#include <sysexits.h>
#include <termios.h>
#include <unistd.h>
#ifdef __FreeBSD__
#include <stdlib.h>
#include <netdb.h>
#include <unistd.h>
#endif
#include "libmilter/mfapi.h"
/*
* Al Smith <Al.Smith@aeschi.ch.eu.org> May 9 2000
*
* Enhancements submitted by Sven Nielsen <dalvenjah@dal.net> May 13 2000
* Inline rejection suggested by Paul Yeh <Paul.Yeh@abc.com> 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
* February 6 2004 -- vbsfilter-1.15
* Executable name could be in Quoted-Printable format.
* Thanks to David Chapeau for reporting this.
*
* June 26 2003 -- vbsfilter-1.14
* New version of sobig, using the .zi extension. Block this.
* Thanks to David F. Russell for reporting this.
*
* January 30 2003
* Implement some suggestions from David F. Russell.
*
* January 14 2003 -- vbsfilter-1.11
* Recognise quoted filenames with missing end quote.
* Problem (and fix!) supplied by Ian j Hart <ianjhart@ntlworld.com>
* and Andrea Adams <andrea@vividimage.com> 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 <h.straub@aon.at>
* close STDIN, STDOUT and STDERR to avoid hanging processes on exit.
*
* June 12 2002 -- vbsfilter-1.9
* Ross Bergman <rbergman@vividusa.com> 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 <matthew_wong@aelhk.com.hk> 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 <serge@yahoo-inc.com>
*
* Update "dangerous extension" list.
* Noted by David F. Russell <David.F.Russell@ncmail.net>
*
* July 25 2001 -- vbsfilter-1.6
* Also recognise 'name=blah' as well as the more usual 'name="blah"'.
* Patch sent in by Andrea Adams <andrea@vividimage.com>
*
* 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 <dirk.meyer@dinoex.sub.org>
*
* 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", "zi", NULL };
#define VERTEXT "X-Filter-Version"
#define VERSION "1.15"
#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;
for(p = s1; *p; p++) {
for(q = s2, r = p; *q && *r; q++, r++) {
if (tolower(*q) != tolower(*r)) 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;
register int i;
int 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++;
}
/* virus.exe.?= */
if (*(r-7) == '.' && *(r-3) == '.' && *(r-2) == '?' && *(r-1) == '=') {
for(ext = exts; *ext; ext++) {
if (strlen(*ext) != 3) continue;
if ( tolower(*(r-6)) == (*ext)[0] &&
tolower(*(r-5)) == (*ext)[1] &&
tolower(*(r-4)) == (*ext)[2]) {
*(r-6) = ext2[0];
*(r-5) = ext2[1];
*(r-4) = ext2[2];
rc++;
}
}
}
/* virus.exe?= */
if (*(r-6) == '.' && *(r-2) == '?' && *(r-1) == '=') {
for(ext = exts; *ext; ext++) {
if (strlen(*ext) != 3) continue;
if ( tolower(*(r-5)) == (*ext)[0] &&
tolower(*(r-4)) == (*ext)[1] &&
tolower(*(r-3)) == (*ext)[2]) {
*(r-5) = ext2[0];
*(r-4) = ext2[1];
*(r-3) = ext2[2];
rc++;
}
}
}
/* virus.exe. */
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++;
}
}
}
/* virus.exe */
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++;
}
}
}
/* virus.js */
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;
}
syntax highlighted by Code2HTML, v. 0.9.1