diff -Naur snort-2.3.0RC2/config.h.in snort-2.3.0RC2.oden/config.h.in --- snort-2.3.0RC2/config.h.in 2004-12-15 16:25:12.000000000 +0100 +++ snort-2.3.0RC2.oden/config.h.in 2004-12-24 06:59:12.690860546 +0100 @@ -18,6 +18,12 @@ /* Define to 1 if you have the header file. */ #undef HAVE_INTTYPES_H +/* Define to 1 if you have the header file. */ +#undef HAVE_CLAMAV_H + +/* Define to 1 if you have the `clamav' library (-lclamav). */ +#undef HAVE_LIBCLAMAV + /* Define to 1 if you have the `ipq' library (-lipq). */ #undef HAVE_LIBIPQ diff -Naur snort-2.3.0RC2/configure.in snort-2.3.0RC2.oden/configure.in --- snort-2.3.0RC2/configure.in 2004-12-09 20:44:49.000000000 +0100 +++ snort-2.3.0RC2.oden/configure.in 2004-12-24 06:59:12.691860225 +0100 @@ -786,6 +786,53 @@ fi +AC_ARG_ENABLE(clamav, +[ --enable-clamav Enable the clamav preprocessor], + enable_clamav="$enableval", enable_clamav="no") +if test "$enable_clamav" = "yes"; then + CFLAGS="$CFLAGS -DCLAMAV" + + AC_ARG_WITH(clamav_includes, + [ --with-clamav-includes=DIR clamav include directory], + [with_clamav_includes="$withval"],[with_clamav_includes=no]) + + AC_ARG_WITH(clamav_defdir, + [ --with-clamav-defdir=DIR clamav virusdefinitions directory], + [with_clamav_defdir="$withval"],[with_clamav_defdir=no]) + + + if test "$with_clamav_defdir" != "no"; then + echo "Virusdefs: $with_clamav_defdir" + CFLAGS="$CFLAGS -DCLAMAV_DEFDIR=\"$with_clamav_defdir\"" + fi + + if test "$with_clamav_includes" != "no"; then + CPPFLAGS="${CPPFLAGS} -I${with_clamav_includes}" + fi + + LCLAM="" + AC_CHECK_HEADERS(clamav.h,, LCLAM="no") + if test "$LCLAM" = "no"; then + echo + echo " ERROR! clamav.h header not found, go get it from" + echo " http://www.clamav.net/ or use the --with-clamav-includes" + echo " options, if you have it installed in an unusual place" + exit + fi + + LCLAM="" + AC_CHECK_LIB(clamav,cl_scanbuff,, LCLAM="no") + if test "$LCLAM" = "no"; then + echo + echo " ERROR! libclamav library not found, go get it from" + echo " http://www.clamav.net/ or make sure that the place" + echo " you installed it is in the library path." + exit + fi + + LIBS="${LIBS} -lclamav" +fi + if test "$tru64_types" = "yes"; then AC_CHECK_TYPE(u_int8_t, unsigned char) AC_CHECK_TYPE(u_int16_t, unsigned short) diff -Naur snort-2.3.0RC2/doc/README.clamav snort-2.3.0RC2.oden/doc/README.clamav --- snort-2.3.0RC2/doc/README.clamav 1970-01-01 01:00:00.000000000 +0100 +++ snort-2.3.0RC2.oden/doc/README.clamav 2004-12-24 06:59:12.691860225 +0100 @@ -0,0 +1,32 @@ +Known limitations +================= +- Please note that detection depends on ClamAV. If clam doesn't know a virus, it will not be detected. So keep your defs up-to-date. +- Archives are not scanned, unless so small that it fits in one packet/uber-packet. +- OLE2 virusses are not detected. +- Attachments to email that are in some way encoded are not scanned. + +turn on clamav by going into snort_inline.conf + +preprocessor clamav + +This turns on the defaults for clamav which are to listen on ports 21 25 80 81 110 119 139 445 143 +uses the default database location of /var/lib/clamav unless another dbdir was specified at ./configure +Alerts are written to alert logs no packets are rejected or dropped. + +options are + +preprocessor clamav: ports {portlist separated by " "}, {flow can be toclientonly or toserveronly or defaults to both} {action can be action-drop or action-reset otherwise default to writing to alert file},{dbdir} + +so + +preprocessor clamav: ports all !25 !443 !22, action-reset + + +will turn on clamav will listen for virus activity on all ports except 25 443 22 and send a reset and drop the packet if a virus is detected. + + +preprocessor clamav: ports 139 445 21, toclientonly, action-drop, dbdir /var/lib2/clamav + +will turn on clamav, will listen for virus activity on ports 129 445 21 will only watch traffic that flows to the client, will drop the packet, sets the virus-sig database path to /var/lib2/clamav + +to scan uberpackets from stream4 reassembly make sure that stream4 is initialized before ClamAV in your snort_inline.conf diff -Naur snort-2.3.0RC2/etc/snort.conf snort-2.3.0RC2.oden/etc/snort.conf --- snort-2.3.0RC2/etc/snort.conf 2004-12-15 16:26:02.000000000 +0100 +++ snort-2.3.0RC2.oden/etc/snort.conf 2004-12-24 06:59:12.691860225 +0100 @@ -317,6 +317,30 @@ # oversize_dir_length 300 \ # no_alerts +# ClamAV virusscanning preprocessor +# +# This preprocessor will scan the data in the packets for virusses. +# See README.clamav for details and limitations. +# +# Available options (comma delimited): +# +# ports: a space delimited list of ports that will be scanned. +# all: all ports +# n : single port to be scanned +# !n : not scan port n (to be used with 'all' +# +# toclientonly: scan only the traffic to the client (tcp only) +# toserveronly: scan only the traffic to the server (tcp only) +# +# action-drop : drop the infected packet (snort_inline only) +# action-reset: reset the connection (snort_inline only) +# +# dbdir: path to the clamav definitions directory. +# +# Example: +# preprocessor clamav: ports all !22 !443, toclientonly, dbdir /usr/share/clamav +# +#preprocessor clamav: ports all !22 !443, toclientonly # rpc_decode: normalize RPC traffic # --------------------------------- diff -Naur snort-2.3.0RC2/src/generators.h snort-2.3.0RC2.oden/src/generators.h --- snort-2.3.0RC2/src/generators.h 2004-09-13 19:44:49.000000000 +0200 +++ snort-2.3.0RC2.oden/src/generators.h 2004-12-24 06:59:12.692859903 +0100 @@ -241,6 +241,9 @@ #define PSNG_OPEN_PORT 27 +#define GENERATOR_SPP_CLAMAV 123 +#define CLAMAV_VIRUSFOUND 1 + /* This is where all the alert messages will be archived for each internal alerts */ @@ -390,4 +392,6 @@ #define PSNG_OPEN_PORT_STR "(portscan) Open Port" +#define CLAMAV_VIRUSFOUND_STR "(spp_clamav) Virus Found:" + #endif /* __GENERATORS_H__ */ diff -Naur snort-2.3.0RC2/src/plugbase.c snort-2.3.0RC2.oden/src/plugbase.c --- snort-2.3.0RC2/src/plugbase.c 2004-11-02 23:07:18.000000000 +0100 +++ snort-2.3.0RC2.oden/src/plugbase.c 2004-12-24 06:59:12.692859903 +0100 @@ -60,6 +60,7 @@ #include "preprocessors/spp_perfmonitor.h" #include "preprocessors/spp_httpinspect.h" #include "preprocessors/spp_flow.h" +#include "preprocessors/spp_clamav.h" #include "preprocessors/spp_sfportscan.h" /* built-in detection plugins */ @@ -409,6 +410,9 @@ SetupHttpInspect(); SetupPerfMonitor(); SetupFlow(); +#ifdef CLAMAV + SetupClamAV(); +#endif /* CLAMAV */ SetupPsng(); } diff -Naur snort-2.3.0RC2/src/preprocessors/Makefile.am snort-2.3.0RC2.oden/src/preprocessors/Makefile.am --- snort-2.3.0RC2/src/preprocessors/Makefile.am 2004-09-13 19:44:49.000000000 +0200 +++ snort-2.3.0RC2.oden/src/preprocessors/Makefile.am 2004-12-24 06:59:12.693859581 +0100 @@ -20,6 +20,7 @@ spp_httpinspect.c spp_httpinspect.h \ snort_httpinspect.c snort_httpinspect.h \ spp_flow.c spp_flow.h \ +spp_clamav.c spp_clamav.h \ portscan.c portscan.h \ spp_sfportscan.c spp_sfportscan.h diff -Naur snort-2.3.0RC2/src/preprocessors/spp_clamav.c snort-2.3.0RC2.oden/src/preprocessors/spp_clamav.c --- snort-2.3.0RC2/src/preprocessors/spp_clamav.c 1970-01-01 01:00:00.000000000 +0100 +++ snort-2.3.0RC2.oden/src/preprocessors/spp_clamav.c 2004-12-24 07:00:18.653628127 +0100 @@ -0,0 +1,577 @@ +/* $Id: spp_clamav.c, v 1.0 Victor Julien/William Metcalf Exp $ */ +/* Snort Preprocessor for Antivirus Checking with ClamAV */ + +/* +** Copyright (C) 1998-2002 Martin Roesch +** Copyright (C) 2003 Sourcefire, Inc. +** Copyright (C) 2004 William Metcalf and +** Victor Julien +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software +** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifdef CLAMAV + +/* spp_clamav.c + * + * Purpose: Sends packet p to ClamAV for Antivirus checking. + * + * Arguments: None + * + * Effect: Who needs virus.rules??? : -) + * + * Comments: + * + * + * TODO: + * - option for virusdefs dir + * - documentation + * - are the defaultports in ParseClamAVArgs ok? + * - options structure like s4data in Stream4 for cl_root, VirusScanPorts, drop/reject/alert, defs dirlocation **IN PROGRESS** + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include "generators.h" +#include "event_wrapper.h" +#include "util.h" +#include "plugbase.h" +#include "parser.h" +#include "decode.h" +#include "debug.h" +#include "mstring.h" +#include "log.h" +#include "spp_clamav.h" + +#ifdef GIDS +#include "inline.h" +#endif + +#include "snort.h" +#include + +#ifdef HAVE_STRINGS_H +#include +#endif + +/* we need this to stringify the CLAMAV_DEFDIR which is supplied at compiletime see: + http://gcc.gnu.org/onlinedocs/gcc-3.4.1/cpp/Stringification.html#Stringification */ +#define xstr(s) str(s) +#define str(s) #s + +/* the config struct */ +struct ClamAVConfig +{ + /* scan limitations */ + char toclientonly; /* if set to 1 scan only traffic to the client */ + char toserveronly; /* if set to 1 scan only traffic to the server */ + char VirusScanPorts[65536/8]; /* array containing info about which ports we care about */ + + /* actions */ + char drop; + char reset; + + /* virdef dir */ + char dbdir[255]; +} clamcnf; + +/* pointer to ClamAV's in-memory virusdatabase */ +struct cl_node *cl_root; + + +static void ClamAVInit(u_char *); +extern void SetupClamAV(); +static int VirusInPacket(Packet *); +static void VirusChecker(Packet *); +extern u_int32_t event_id; + + +/* + * Function: SetupClamAV() + * + * Purpose: Registers the preprocessor. + * + * Arguments: None. + * + * Returns: void function + * + */ +void SetupClamAV() +{ + RegisterPreprocessor("ClamAV", ClamAVInit); +} + + +/* + * Function: ProcessPorts(u_char *) + * + * Purpose: Sets the port limits + * + * Arguments: pointer to string with portlist. + * + * Returns: void function + * + */ +static void ProcessPorts(u_char *portlist) +{ + int j = 0; + int i = 0; + char **ports; + int num_ports; + char *port; + u_int32_t portnum; + + /* reset the ports array */ + bzero(&clamcnf.VirusScanPorts, sizeof(clamcnf.VirusScanPorts)); + + ports = mSplit(portlist, " ", 40, &num_ports, 0); + + /* run through the ports */ + for(j = 0; j < num_ports; j++) + { + port = ports[j]; + + /* we need to set this port */ + if(isdigit((int)port[0])) + { + portnum = atoi(port); + if(portnum > 65535) + { + FatalError("%s(%d) => Bad port list to scan: " + "port '%d' out of range\n", portnum, file_name, file_line); + } + + /* mark this port as being interesting using some portscan2-type voodoo, + and also add it to the port list string while we're at it so we can + later print out all the ports with a single LogMessage() */ + clamcnf.VirusScanPorts[(portnum/8)] |= 1<<(portnum%8); + } + /* we need to unset this port */ + else if(port[0] == '!') + { + for(i = 0; i < strlen(port) && port[i+1] != '\0'; i++) + { + port[i] = port[i+1]; + } + port[i] = '\0'; + + if(isdigit((int)port[0])) + { + portnum = atoi(port); + if(portnum > 65535) + { + FatalError("%s(%d) => Bad port list to scan: " + "port '%d' out of range\n", portnum, file_name, file_line); + } + + /* clear the bit - this removes the port from the array */ + clamcnf.VirusScanPorts[(portnum/8)] &= ~(1<<(portnum%8)); + } + else + { + FatalError("%s(%d) => Bad port list to scan: " + "bad port\n", file_name, file_line); + } + } + /* we need to set all ports */ + else if(!strncasecmp(port, "all", 3)) + { + /* enable all ports */ + for(portnum = 0; portnum <= 65535; portnum++) + clamcnf.VirusScanPorts[(portnum/8)] |= 1<<(portnum%8); + } + else if(!strncasecmp(port, "ports", 5)); + else + { + FatalError("%s(%d) => Bad port list to scan: " + "bad port\n", file_name, file_line); + } + } + + mSplitFree(&ports, num_ports); + + /* some pretty printing */ + if(!pv.quiet_flag) + { + /* print the portlist */ + LogMessage(" Ports: "); + + for(portnum = 0, j = 0; portnum <= 65535; portnum++) + { + if((clamcnf.VirusScanPorts[(portnum/8)] & (1<<(portnum%8)))) + { + LogMessage("%d ", portnum); + j++; + } + + if(j > 20) + { + LogMessage("...\n"); + return; + } + } + } +} + + + /* + * Function: ParseClamAVArgs(u_char *) + * + * Purpose: reads the options. + * + * Arguments: pointer to string with options + * + * Returns: void function + */ +void ParseClamAVArgs(u_char *args) +{ + char **toks; + int num_toks; + int i = 0; + char *index; + int ports_done = 0; + char **dbdirtoks; + int num_dbdirtoks = 0; + + /* ftp, smtp, http, pop3, nntp, samba (2x), imap */ + u_char *default_ports = "21 25 80 81 110 119 139 445 143"; + + + /* set the default values */ +#ifdef GIDS + clamcnf.drop = 0; + clamcnf.reset = 0; +#endif /* GIDS */ + clamcnf.toclientonly = 0; + clamcnf.toserveronly = 0; + + +#ifdef CLAMAV_DEFDIR + /* copy the default that was set at compile time, if any */ + if(strlcpy(clamcnf.dbdir, xstr(CLAMAV_DEFDIR), sizeof(clamcnf.dbdir)) >= sizeof(clamcnf.dbdir)) +#else + /* otherwise a buildin default */ + if(strlcpy(clamcnf.dbdir, "/var/lib/clamav/", sizeof(clamcnf.dbdir)) >= sizeof(clamcnf.dbdir)) +#endif + { + FatalError("The defdir supplied at compile time is too long\n"); + } + + + if(!pv.quiet_flag) + { + LogMessage("ClamAV config:\n"); + } + + + /* if no args, load the default config */ + if(args == NULL) + { + if(!pv.quiet_flag) + { + LogMessage(" no options, using defaults.\n"); + } + } + /* process the args */ + else + { + toks = mSplit(args, ",", 12, &num_toks, 0); + + for(i = 0; i < num_toks; i++) + { + index = toks[i]; + while(isspace((int)*index)) index++; + + if(!strncasecmp(index, "ports", 5)) + { + ProcessPorts(toks[i]); + ports_done = 1; + } +#ifdef GIDS + else if(!strncasecmp(index, "action-reset", 12)) + { + clamcnf.reset = 1; + } + else if(!strncasecmp(index, "action-drop", 11)) + { + clamcnf.drop = 1; + } +#endif /* GIDS */ + else if(!strncasecmp(index, "toclientonly", 12)) + { + clamcnf.toclientonly = 1; + } + else if(!strncasecmp(index, "toserveronly", 12)) + { + clamcnf.toserveronly = 1; + } + else if(!strncasecmp(index, "dbdir", 5)) + { + /* get the argument for the option */ + dbdirtoks = mSplit(index, " ", 1, &num_dbdirtoks, 0); + + /* copy it to the clamcnf */ + if(strlcpy(clamcnf.dbdir, dbdirtoks[1], sizeof(clamcnf.dbdir)) >= sizeof(clamcnf.dbdir)) + { + FatalError("The defdir supplied in the config is too long\n"); + } + } + else + { + FatalError("%s(%d) => Bad ClamAV option specified: " + "\"%s\"\n", file_name, file_line, toks[i]); + } + } + + mSplitFree(&toks, num_toks); + } + +#ifdef GIDS + /* sanety checks */ + if(clamcnf.drop && clamcnf.reset) + { + FatalError("Can't set action-drop and action-reset together!\n"); + } +#endif /* GIDS */ + if(clamcnf.toclientonly && clamcnf.toserveronly) + { + FatalError("Can't set toclientonly and toserveronly together!\n"); + } + + + /* if at this stage the ports are not yet done, load the default ports */ + if(!ports_done) + ProcessPorts(default_ports); + + + /* some pretty printing */ + if(!pv.quiet_flag) + { + /* action */ +#ifdef GIDS + if(clamcnf.drop == 1) + LogMessage(" Virus found action: DROP\n"); + else if(clamcnf.reset == 1) + LogMessage(" Virus found action: RESET\n"); + else + LogMessage(" Virus found action: ALERT\n"); +#endif /* GIDS */ + /* dbdir */ + LogMessage(" Virus definitions dir: '%s'\n", clamcnf.dbdir); + /* limits */ + if(clamcnf.toclientonly == 1) + LogMessage(" Scan only traffic to the client\n"); + else if(clamcnf.toserveronly == 1) + LogMessage(" Scan only traffic to the server\n"); + } +} + + +/* + * Function: ClamAVInit(u_char *) + * + * All it does right now is register the plugin, eventually we will take port args + */ +void ClamAVInit(u_char *args) +{ + int ret = 0; + int n = 0; + cl_root = NULL; + + + /* first parse the commandline args */ + ParseClamAVArgs(args); + + + /* open the defs dir */ + ret = cl_loaddbdir(clamcnf.dbdir, &cl_root, &n); + if(ret != 0) + { + FatalError("ClamAV: cl_loaddbdir() %s\n", cl_strerror(ret)); + } + + + /* run buildtrie */ + if((ret = cl_buildtrie(cl_root))) + { + FatalError("ClamAV: cl_buildtrie() %s\n", cl_strerror(ret)); + } + + + /* register the VirusChecker */ + AddFuncToPreprocList(VirusChecker); +} + + +/* + * Function: ScanPort(Packet *p) + * + * Purpose: Determines if a packet needs to be scanned based + * on the source and destination ports, and for tcp + * packets also if we want to scan only toclient or + * toserver packets. + * + * Arguments: pointer to the packet + * + * Returns: returns 1 if the packet needs to be scanned, + * 0 otherwise. + */ +static INLINE int ScanPort(Packet *p) +{ + /* tcp packet */ + if(p->tcph) + { + if(p->packet_flags & PKT_FROM_SERVER && !clamcnf.toserveronly) + { + /* server to client packet: check sp */ + if(!(clamcnf.VirusScanPorts[(p->sp/8)] & (1<<(p->sp%8)))) + return 0; + else + return 1; + } + else if(p->packet_flags & PKT_FROM_CLIENT && !clamcnf.toclientonly) + { + /* client to server packet: check dp */ + if(!(clamcnf.VirusScanPorts[(p->dp/8)] & (1<<(p->dp%8)))) + return 0; + else + return 1; + } + else + { + /* if we get here the packet was FROM_SERVER while in toserveronly mode + or vice-versa. So we don't scan. */ + return 0; + } + } + /* if the packet is not tcp we have no idea about the direction of the + * packet. So we check both the dp and the sp. */ + else if(!(clamcnf.VirusScanPorts[(p->dp/8)] & (1<<(p->dp%8))) && /* destination ports */ + !(clamcnf.VirusScanPorts[(p->sp/8)] & (1<<(p->sp%8)))) /* source ports */ + { + return 0; + } + else + { + return 1; + } +} + + +/* + * Function: VirusInPacket(Packet *) + * + * Purpose: Perform Virusscanning on the payload of a packet. + * + * Arguments: p => pointer to the current packet data struct + * + * Returns: 1: is a virus was found + * 0: if the packet is clean + * + * @todo: logging and alerting + * see if we can have more checks to prevent scanning packets when we don't have to + */ +static int VirusInPacket(Packet *p) +{ + int ret = 0; + Event event; + char outstring[255]; + const char *cl_virusname = NULL; + + + /* virusscanning requires data, so check if we have any */ + if(p->dsize == 0) + { + return 0; + } + + + /* check the port list to see if we need to scan this port */ + if(!ScanPort(p)) + { + return 0; + } + + + /* if cl_root is NULL we return */ + if(!cl_root) + { + return 0; + } + + + /* call cl_scanbuff the function from ClamAV to scan the buffer. */ + ret = cl_scanbuff(p->data, p->dsize, &cl_virusname, cl_root); + if(ret == CL_CLEAN) + { + /* clean */ + } + else if(ret == CL_VIRUS) + { + snprintf(outstring, sizeof(outstring), "%s %s", CLAMAV_VIRUSFOUND_STR, cl_virusname); + + /* alert! */ + SetEvent(&event, GENERATOR_SPP_CLAMAV, CLAMAV_VIRUSFOUND, 1, 0, 0, 0); + CallAlertFuncs(p, outstring, NULL, &event); + CallLogFuncs(p, outstring, NULL, &event); + return 1; + } + else + { + /* error */ + FatalError("ClamAV scan error: %s.\n", cl_strerror(ret)); + } + + return 0; +} + + +/* + * Function: PreprocFunction(Packet *) + * + * Purpose: Perform the preprocessor's intended function. This can be + * simple (statistics collection) or complex (IP defragmentation) + * as you like. Try not to destroy the performance of the whole + * system by trying to do too much.... + * + * Arguments: p => pointer to the current packet data struct + * + * Returns: void function + * + */ +static void VirusChecker(Packet *p) +{ + if(VirusInPacket(p)) + { +#ifdef GIDS + if(clamcnf.reset) + { + InlineReject(p); + } + else if(clamcnf.drop) + { + InlineDrop(); + } +#endif /* GIDS */ + } + return; +} + +#endif /* CLAMAV */ diff -Naur snort-2.3.0RC2/src/preprocessors/spp_clamav.h snort-2.3.0RC2.oden/src/preprocessors/spp_clamav.h --- snort-2.3.0RC2/src/preprocessors/spp_clamav.h 1970-01-01 01:00:00.000000000 +0100 +++ snort-2.3.0RC2.oden/src/preprocessors/spp_clamav.h 2004-12-24 06:59:12.694859259 +0100 @@ -0,0 +1,18 @@ +/* $Id: spp_template.h,v 1.3.2.1 2004/02/25 16:52:53 jh8 Exp $ */ +/* Snort Preprocessor Plugin Header File Template */ + +/* This file gets included in plugbase.h when it is integrated into the rest + * of the program. + */ +#ifndef __SPP_CLAMAV_H__ +#define __SPP_CLAMAV_H__ + +#ifdef CLAMAV +/* + * list of function prototypes to export for this preprocessor + */ +void SetupClamAV(); + +#endif /* CLAMAV */ + +#endif /* __SPP_CLAMAV_H__ */