diff -urN --exclude=CVS --exclude=pcx dansguardian-2.7.1-0/AntiVirusINSTALL dansguardian2_7/AntiVirusINSTALL --- dansguardian-2.7.1-0/AntiVirusINSTALL Wed Dec 31 18:00:00 1969 +++ dansguardian2_7/AntiVirusINSTALL Wed May 14 16:24:32 2003 @@ -0,0 +1,167 @@ +AntiVirusINSTALL - INSTALL file for the Anti-Virus Plugin for DansGuardian. +Created by James A. Pattie (james@pcxperience.com) +(c) 2002 by Xperience, Inc. (http://www.xperienceinc.com/) +Licensed under the GPL. + +05/14/2003 Version 4.1 for DansGuardian 2.7 + +This documents how to add Anti-Virus scanning to the DansGuardian project. + + +REQUIREMENTS: + perl 5.6.1 or 5.8.0 + DansGuardian 2.7.1-0 + dansguardian-virus-scripts 1.04 + Mail::Sender 0.8.00 + +Download the latest Dansguardian-Virus-Scripts tarball or rpm package from +http://www.pcxperience.org/dgvirus/. These scripts come from the MailScanner +project. Also, some of the code that is integrated into DansGuardian is from +the MailScanner package. See the MailScanner site for any installation notes +regarding your Anti-Virus product. http://www.mailscanner.info/ + +If installing from the tarball, copy the usr/lib/DGVirus directory structure +to /usr. If installing from rpm, you need to install before the +DansGuardian-Virus package. + +Download the tarball or our rpm of Mail::Sender 0.8.00 from CPAN or the support +directory. This needs to be installed before installing DansGuardian. + +Download the source to DansGuardian 2.7.1-0. Now you need to patch the source. + + gunzip DansGuardian-2.7.1-0-AntiVirus-4.1.patch.gz + +You can either build manually: + + tar xvzf DansGuardian-2.7.1-0.source.tar.gz + cd DansGuardian-2.7.1 + patch -p1 < ../DansGuardian-2.7.1-0-AntiVirus-4.1.patch.gz + + This assumes you have the patches in the same directory as the source + tarball for DansGuardian 2.7.1-0. + + Now read the INSTALL file and follow the steps to build DansGuardian. + +Or you can build an rpm from the included DGVirus.spec file: + + cp DansGuardian-2.7.1-0.source.tar.gz /usr/src/redhat/SOURCES + cp DansGuardian-2.7.1-0-AntiVirus-4.1.patch /usr/src/redhat/SOURCES + cp linux.in-rpmfixup.patch /usr/src/redhat/SOURCES + + cp DGVirus.spec /usr/src/redhat/SPECS + + cd /usr/src/redhat/SPECS + rpm -bb DGVirus.spec + + first install the DansGuardian-Virus-Scripts support package: + rpm -Uvh DansGuardian-Virus-Scripts-1.04-1.noarch.rpm + + next install the perl-Mail-Sender package: + rpm -Uvh perl-Mail-Sender-0.8.00-1.i386.rpm + + now install the newly created DansGuardian package: + rpm -Uvh ../RPMS/i386/DansGuardian-Virus-4.1-1.i386.rpm + + +VIRUS Engine Configuration: + +To enable your virus scanner (if not F-Prot and it's not installed in +/usr/local/f-prot) then you need to edit virusscanner.conf in the dansguardian +configuration directory. This file also has other options that can be tweaked, +though not all options are currently being used. + + +NOTE: + +Currently, once this patch is applied, all content (html/text, etc.) other than +SSL connections are scanned by default. If you want to disable the virus +scanning code, modify the generated dansguardian.conf file and change +on to off for the virusscan option. + +If a virus is detected, an HTML document stating that fact will be sent to the +browser. In the event that you were downloading a big file, you might have the +first couple of bytes of the file in question already downloaded, and so the +HTML error document will be appended to it. + +If downloading and saving to disk, always check to make sure that the resulting +file is what you were expecting. + +The Virus scanning code will log, via syslog, the scanning messages and output +the results of any virus found messages. + +F-Prot is the anti-virus engine I'm testing. The other virus programs that +MailScanner supports have been converted but since I don't own them I haven't +tested the code yet. If you own one of the other engines I would appreciate +any help in debugging the code and getting it supported. + +I have had confirmation that the Sophos code works. + +I have added code provided by G.H.J. Dorssers to make the AntiVir virus scanner +work, but it is totally unsupported by me. You will have to modify +virusscanner.conf and change 'Minimum Code Status' from supported to unsupported +along with the Virus Scanner and Sweep values. + +2002/12/17 - I now rely on the DansGuardian-Virus-Scripts package I'm creating +from the MailScanner 4.x release for virus wrapper scripts. This is now in +/usr/lib/DGVirus and the names of the virus wrapper scripts are slightly +different. If things break after an upgrade, check to make sure you are +pointing at the right file. + +2002/11/27 - Postmaster will now always receive an email when a virus is +detected. If squid is requiring proxy authentication, then the detected user +will also get an email (as long as virusscanner.conf is properly configured). +I recommend using pam_auth (provided with squid) and configuring to use whatever +authentication mechanism that PAM provides that you have easily available to +specify valid users where their username is also a valid email address user. +Any emails generated to users, will have the Mail Domain value appended to them +as follows: user@MailDomain where user is the detected username. + +Quarantining of the infected files is now available and will store the files in +the following format: + +prefix/dgvirus/quarantine/DATE/USER//FILE + +where prefix/dgvirus/quarantine is user defined in virusscanner.conf, +DATE is todays date in YYYYMMDD format, +USER is the detected users name or no-user if proxy auth disabled, + is the url with any & escaped, +FILE is the temporary file DansGuardian uses in format fileXXXXXX + +01/13/2003 - A status file is now generated that will allow the user to see the +status of large files being downloaded. The status files are stored in the +DownloadDir directory under status and are grouped by the IP address of the +client or the detected user name (no-user if none detected). This grouping is +configurable in virusscanner.conf. +Ex: DownloadDir = /tmp/dgvirus, +Temp file = fileXYZ123, +User Name = james, +Status File = /tmp/dgvirus/status/james/fileXYZ123 +If grouping by IP and the IP was 10.0.0.2, then +Status File = /tmp/dgvirus/status/10.0.0.2/fileXYZ123 + +The status file will be deleted when the file is done downloading or the file +transfer is canceled or on any other error condition. + +The status file is locked via flock, so any monitoring scripts should acquire +a LOCK_SH before reading from the file to make sure that they get complete data. + +The output format is: +started: Start TimeStamp +last: Last Updated TimeStamp +url: url +file: name of temporary file +fileLength: # bytes in file +bytesDownloaded: # bytes currently downloaded +user: detected user name +userIP: IP of browser + + +Changelog: + +4.0 - Ported 2.2 forward into the 2.7.1-0 tree. + +4.1 - Added optional simultaneous write to client socket, activated by + tricklelength variable in dansguardian.conf. + Added firsttrickledelay and followingtrickledelay variables in + dansguardian.conf. + diff -urN --exclude=CVS --exclude=pcx dansguardian-2.7.1-0/ConnectionHandler.cpp dansguardian2_7/ConnectionHandler.cpp --- dansguardian-2.7.1-0/ConnectionHandler.cpp Wed May 14 11:30:23 2003 +++ dansguardian2_7/ConnectionHandler.cpp Wed May 14 14:52:03 2003 @@ -17,11 +17,15 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// Modified by James A. Pattie (james@pcxperience.com) Xperience, Inc. (www.pcxperience.com) + #include "autoconf/platform.h" #include #include "ImageContainer.hpp" #include "ConnectionHandler.hpp" #include "DataBuffer.hpp" +#include "TrickleBuffer.hpp" +#include "VirusScanner.hpp" #include "Socket.hpp" #include "UDSocket.hpp" #include "Ident.hpp" @@ -78,9 +82,18 @@ docbody.setTimeout(60); + TrickleBuffer docscan; // to hold any data to be virus scanned + + docscan.setTimeout(60); + + VirusScanner virusscan; // class that actually scans files, etc. + + virusscan.setTimeout(60); + bool waschecked = false; // flags bool wasrequested = false; bool isexception = false; + bool dontvirusscan = false; bool isourwebserver = false; bool wasclean = true; bool cachehit = false; @@ -191,13 +204,111 @@ && !o.inBannedIPList(clientip) // bad users pc && !o.inBannedUserList(clientuser) ) { // bad user - proxysock.readyForOutput(10); // exception on timeout or error + #ifdef DGDEBUG + std::cout << "in exception block" << std::endl; + #endif + + proxysock.readyForOutput(10); // exception on timeout or error header.out(proxysock); // send proxy the request + proxysock.checkForInput(60); + docheader.in(proxysock); // get header from proxy + #ifdef DGDEBUG + std::cout << "got header from proxy" << std::endl; + #endif + + #ifdef DGDEBUG + std::cout << "sending header to client" << std::endl; + #endif + peerconn.readyForOutput(10); // exceptions on error/timeout + docheader.out(peerconn); // send header to client + #ifdef DGDEBUG + std::cout << "sent header to client" << std::endl; + #endif + + String temp = docheader.getcontenttype(); + + if (o.virus_scan) // see if we do not need to virus scan the content. + { + #ifdef DGDEBUG + std::cout << "checking to see if we don't have to virus scan case 1" << std::endl; + std::cout << "url:" << urld << std::endl; + #endif + + if (header.requesttype().startsWith("CONNECT")) // can't virus scan HTTPS traffic. + { + dontvirusscan = true; + #ifdef DGDEBUG + std::cout << "url: " << urld << " is CONNECT" << std::endl; + #endif + } + else + { + if (o.inVirusExceptionExtensionList(urld) >= 0) + { + dontvirusscan = true; + #ifdef DGDEBUG + std::cout << "url: " << urld << " in extension exception list" << std::endl; + #endif + } + + if (!dontvirusscan) // only check if we haven't found an exception yet. + { + String temp = docheader.getcontenttype(); + + int i = o.exception_virus_mimetype_list.findInList(temp.toCharArray()); + if (i >= 0) { + dontvirusscan = true; + #ifdef DGDEBUG + std::cout << "url: " << urld << " mimetype '" << temp.toCharArray() << "' in exception list" << std::endl; + #endif + } + } + } + } + try { - FDTunnel fdt; // make a tunnel object - // tunnel from client to proxy and back - fdt.tunnel(proxysock.getFD(), peerconn.getFD()); // not expected to exception - docsize = fdt.throughput; + if ((!o.virus_scan) || dontvirusscan) + { + FDTunnel fdt; // make a tunnel object + // tunnel from client to proxy and back + fdt.tunnel(proxysock.getFD(), peerconn.getFD()); // not expected to exception + docsize = fdt.throughput; + } + else + { + proxysock.checkForInput(60); + try { + docscan.in(proxysock, peerconn, virusscan.getTmpName(), url, docheader.contentlength(), clientuser, clientip); + } catch (exception& e) { + #ifdef DGDEBUG + std::cout << "exception caught - reading url" << std::endl; + #endif + docsize = -1; // signal I don't know the file size. + exceptionreason = "*ERROR* - exception caught reading url" + exceptionreason; + decideHowToLog(clientuser, clientip, url.toCharArray(), exceptionreason, header.requesttype().toCharArray(), docsize, o.ll, false, isexception, o.log_exception_hits, false, &thestart, cachehit, 200, mimetype); + try { + proxysock.close(); + peerconn.close(); + } catch (exception& e2) {} + return; + } + try { + virusscan.scanFile(docscan.fileName(), peerconn, url, docscan.tricklePos(), clientuser, clientip); + } catch (exception& e) { + #ifdef DGDEBUG + std::cout << "exception caught - scanning/sending url" << std::endl; + #endif + docsize = virusscan.length(); + exceptionreason = "*ERROR* - exception caught scanning/sending url" + exceptionreason; + decideHowToLog(clientuser, clientip, url.toCharArray(), exceptionreason, header.requesttype().toCharArray(), docsize, o.ll, false, isexception, o.log_exception_hits, false, &thestart, cachehit, 200, mimetype); + try { + proxysock.close(); + peerconn.close(); + } catch (exception& e2) {} + return; + } + docsize = virusscan.length(); + } if (!isourwebserver) { // don't log requests to the web server decideHowToLog(clientuser, clientip, url.toCharArray(), exceptionreason, header.requesttype().toCharArray(), docsize, o.ll, false, isexception, o.log_exception_hits, false, &thestart, cachehit, 200, mimetype); } @@ -615,25 +726,128 @@ #ifdef DGDEBUG std::cout << "sent header to client" << std::endl; #endif - if (waschecked) { - decideHowToLog(clientuser, clientip, url.toCharArray(), exceptionreason, header.requesttype().toCharArray(), docsize, o.ll, false, isexception, o.log_exception_hits, docheader.iscontenttype("text"), &thestart, cachehit, 200, mimetype); - + if (o.virus_scan) // see if we do not need to virus scan the content. + { #ifdef DGDEBUG - std::cout << "sending body to client" << std::endl; - #endif - peerconn.readyForOutput(10); // check for error/timeout needed - docbody.out(peerconn); // send doc body to client - #ifdef DGDEBUG - std::cout << "sent body to client" << std::endl; + std::cout << "checking to see if we don't have to virus scan case 2" << std::endl; + std::cout << "url:" << urld << std::endl; #endif + + if (o.inVirusExceptionExtensionList(urld) >= 0) + { + dontvirusscan = true; + #ifdef DGDEBUG + std::cout << "url: " << urld << " in extension exception list" << std::endl; + #endif + } + + if (!dontvirusscan) // only check if we haven't found an exception yet. + { + String temp = docheader.getcontenttype(); + + int i = o.exception_virus_mimetype_list.findInList(temp.toCharArray()); + if (i >= 0) { + dontvirusscan = true; + #ifdef DGDEBUG + std::cout << "url: " << urld << " mimetype '" << temp.toCharArray() << "' in exception list" << std::endl; + #endif + } + } } - else { // was not supposed to be checked - FDTunnel fdt; - #ifdef DGDEBUG - std::cout << "tunnel activated" << std::endl; - #endif - fdt.tunnel(proxysock.getFD(), peerconn.getFD()); - docsize = fdt.throughput; + + if (waschecked) { + #ifdef DGDEBUG + std::cout << "sending body to client" << std::endl; + #endif + peerconn.readyForOutput(10); // check for error/timeout needed + + if ((!o.virus_scan) || dontvirusscan) + { + docbody.out(peerconn); // send doc body to client + } + else + { + try { + docbody.writeFile(virusscan.getTmpName()); + } catch (exception& e) { + #ifdef DGDEBUG + std::cout << "exception caught - writing file" << std::endl; + #endif + exceptionreason = "*ERROR* - exception caught writing file" + exceptionreason; + decideHowToLog(clientuser, clientip, url.toCharArray(), exceptionreason, header.requesttype().toCharArray(), docsize, o.ll, false, isexception, o.log_exception_hits, docheader.iscontenttype("text"), &thestart, cachehit, 200, mimetype); + try { + proxysock.close(); + peerconn.close(); + } catch (exception& e2) {} + return; + } + try { + virusscan.scanFile(docbody.fileName(), peerconn, url, 0, clientuser, clientip); + } catch (exception& e) { + #ifdef DGDEBUG + std::cout << "exception caught - scanning/sending url" << std::endl; + #endif + docsize = virusscan.length(); + exceptionreason = "*ERROR* - exception caught scanning/sending url" + exceptionreason; + decideHowToLog(clientuser, clientip, url.toCharArray(), exceptionreason, header.requesttype().toCharArray(), docsize, o.ll, false, isexception, o.log_exception_hits, docheader.iscontenttype("text"), &thestart, cachehit, 200, mimetype); + try { + proxysock.close(); + peerconn.close(); + } catch (exception& e2) {} + return; + } + } + #ifdef DGDEBUG + std::cout << "sent body to client" << std::endl; + #endif + + decideHowToLog(clientuser, clientip, url.toCharArray(), exceptionreason, header.requesttype().toCharArray(), docsize, o.ll, false, isexception, o.log_exception_hits, docheader.iscontenttype("text"), &thestart, cachehit, 200, mimetype); + } + else { // was not supposed to be checked + if ((!o.virus_scan) || dontvirusscan) + { + FDTunnel fdt; + fdt.tunnel(peerconn.getFD(), proxysock.getFD()); + docsize = fdt.throughput; + } + else + { + #ifdef DGDEBUG + std::cout << "scan activated" << std::endl; + #endif + proxysock.checkForInput(60); + try { + docscan.in(proxysock, peerconn, virusscan.getTmpName(), url, docheader.contentlength(), clientuser, clientip); + } catch (exception& e) { + #ifdef DGDEBUG + std::cout << "exception caught - reading url" << std::endl; + #endif + docsize = -1; // signal I don't know the file size. + exceptionreason = "*ERROR* - exception caught reading url" + exceptionreason; + decideHowToLog(clientuser, clientip, url.toCharArray(), exceptionreason, header.requesttype().toCharArray(), docsize, o.ll, false, isexception, o.log_exception_hits, docheader.iscontenttype("text"), &thestart, cachehit, 200, mimetype); + try { + proxysock.close(); + peerconn.close(); + } catch (exception& e2) {} + return; + } + try { + virusscan.scanFile(docscan.fileName(), peerconn, url, docscan.tricklePos(), clientuser, clientip); + } catch (exception& e) { + #ifdef DGDEBUG + std::cout << "exception caught - scanning/sending url" << std::endl; + #endif + docsize = virusscan.length(); + exceptionreason = "*ERROR* - exception caught scanning/sending url" + exceptionreason; + decideHowToLog(clientuser, clientip, url.toCharArray(), exceptionreason, header.requesttype().toCharArray(), docsize, o.ll, false, isexception, o.log_exception_hits, docheader.iscontenttype("text"), &thestart, cachehit, 200, mimetype); + try { + proxysock.close(); + peerconn.close(); + } catch (exception& e2) {} + return; + } + docsize = virusscan.length(); + } decideHowToLog(clientuser, clientip, url.toCharArray(), exceptionreason, header.requesttype().toCharArray(), docsize, o.ll, false, isexception, o.log_exception_hits, docheader.iscontenttype("text"), &thestart, cachehit, 200, mimetype); diff -urN --exclude=CVS --exclude=pcx dansguardian-2.7.1-0/DataBuffer.cpp dansguardian2_7/DataBuffer.cpp --- dansguardian-2.7.1-0/DataBuffer.cpp Fri May 2 17:36:38 2003 +++ dansguardian2_7/DataBuffer.cpp Wed May 14 14:32:36 2003 @@ -17,6 +17,8 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// Modified by James A. Pattie (james@pcxperience.com) Xperience, Inc. (www.pcxperience.com) + #include #include "String.hpp" #include @@ -32,6 +34,10 @@ #include #endif +#include +#include +#include + extern OptionContainer o; DataBuffer::DataBuffer() @@ -42,6 +48,7 @@ DataBuffer::~DataBuffer() { delete[] data; + delete[] filename; } // delete the memory block when the class is destryed @@ -366,3 +373,58 @@ } } } + +void DataBuffer::writeFile(char* fname) throw(exception) { + ofstream tmpFile; + + char* tmpDir = getDownloadDir(); + + filename = fname; + + char* tmpfilename = new char[strlen(tmpDir) + strlen(fname) + 2]; + strcpy(tmpfilename, tmpDir); // store in the class. + strcat(tmpfilename, "/"); // need the seperator. + strcat(tmpfilename, fname); + + delete[] tmpDir; // free up the directory name memory. + + tmpFile.open(tmpfilename, ios::out); + if (!tmpFile) + { + #ifdef DGDEBUG + std::cerr << "Could not create " << tmpfilename << "!" << std::endl; + #endif + throw exception(); + } + + #ifdef DGDEBUG + std::cout << "writing to file " << tmpfilename << ", length = '" << buffer_length << "'" << std::endl; + #endif + + for (int i=0; i < buffer_length; i++) + { + tmpFile.put(data[i]); + } + tmpFile.close(); + + delete[] tmpfilename; +} + +char* DataBuffer::getDownloadDir(void) { + char *tmpDownloadDir; + + tmpDownloadDir = new char[256]; + + // call tempDownloadDir. + dSP; // initialize stack pointer + ENTER; + SAVETMPS; + perl_call_pv("tempDownloadDir", G_SCALAR|G_NOARGS); + SPAGAIN; + strcpy(tmpDownloadDir, POPp); // Get the return value off the stack. + PUTBACK; + FREETMPS; + LEAVE; + + return tmpDownloadDir; +} diff -urN --exclude=CVS --exclude=pcx dansguardian-2.7.1-0/DataBuffer.hpp dansguardian2_7/DataBuffer.hpp --- dansguardian-2.7.1-0/DataBuffer.hpp Fri May 2 17:36:38 2003 +++ dansguardian2_7/DataBuffer.hpp Wed May 14 14:32:36 2003 @@ -17,6 +17,8 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// Modified by James A. Pattie (james@pcxperience.com) Xperience, Inc. (www.pcxperience.com) + #ifndef __HPP_DATABUFFER #define __HPP_DATABUFFER #include @@ -39,9 +41,13 @@ void setTimeout(int t); void setDecompress(String d); void contentRegExp(); + void writeFile(char* fname) throw(exception); + char* getDownloadDir(void); + char* fileName() { return filename; } private: int timeout; + char* filename; String decompress; void ingzip(Socket sock); void zlibinflate(); diff -urN --exclude=CVS --exclude=pcx dansguardian-2.7.1-0/OptionContainer.cpp dansguardian2_7/OptionContainer.cpp --- dansguardian-2.7.1-0/OptionContainer.cpp Wed May 14 11:30:23 2003 +++ dansguardian2_7/OptionContainer.cpp Wed May 14 16:20:29 2003 @@ -38,6 +38,8 @@ exception_user_list.reset(); exception_ip_list.reset(); exception_url_list.reset(); + exception_virus_mimetype_list.reset(); + exception_virus_extension_list.reset(); banned_extension_list.reset(); banned_mimetype_list.reset(); banned_site_list.reset(); @@ -265,6 +267,31 @@ else { reverse_lookups = 0; } + if (findoptionS("virusscan") == "on") { + virus_scan = 1; + } + else { + virus_scan = 0; + } + + // Included by Aecio F. Neto (afn@harvest.com.br) - Harvest Consultoria (http://www.harvest.com.br) + trickle_length = findoptionI("tricklelength"); + if (!realitycheck(String(trickle_length), 1, 6, "tricklelength")) + { return false; } + if (trickle_length < 0) + trickle_length = -1; + + first_trickle_delay = findoptionI("firsttrickledelay"); + if (!realitycheck(String(first_trickle_delay), 1, 3, "firsttrickledelay")) + { return false; } + if (first_trickle_delay < 1) + first_trickle_delay = 60; + + following_trickle_delay = findoptionI("followingtrickledelay"); + if (!realitycheck(String(following_trickle_delay), 1, 3, "followingtrickledelay")) + { return false; } + if (following_trickle_delay < 1) + following_trickle_delay = 30; if (findoptionS("usexforwardedfor") == "on") { use_xforwardedfor = 1; @@ -288,6 +315,10 @@ exceptions_url_list_location = findoptionS("exceptionurllist"); exceptions_user_list_location = findoptionS("exceptionuserlist"); exceptions_ip_list_location = findoptionS("exceptioniplist"); + + exceptions_virus_mimetype_list_location = findoptionS("exceptionvirusmimetypelist"); + exceptions_virus_extension_list_location = findoptionS("exceptionvirusextensionlist"); + language_list_location = findoptionS("languagedir") + "/" + findoptionS("language") + "/messages"; access_denied_address = findoptionS("accessdeniedaddress"); ada = access_denied_address.c_str(); @@ -433,6 +464,15 @@ return false; } // messages language file + if (virus_scan) { + if (!readevmlfile(exceptions_virus_mimetype_list_location.c_str())) { + return false; + } // virus mimetype exceptions + if (!readevelfile(exceptions_virus_extension_list_location.c_str())) { + return false; + } // virus extensions exceptions + } + if (banned_site_list.inList("**")) { blanketblock = 1; } @@ -585,6 +625,35 @@ return true; } +bool OptionContainer::readevmlfile(const char* filename) { + + bool result = exception_virus_mimetype_list.readItemList(filename, false, 0); + if (!result) { + if (!isDaemonised) { + std::cerr << "Error opening exceptionvirusmimetypelist" << std::endl; + } + syslog(LOG_ERR, "%s","Error opening exceptionvirusmimetypelist"); + return false; + } + exception_virus_mimetype_list.endsWithSort(); + return true; +} + + +bool OptionContainer::readevelfile(const char* filename) { + + bool result = exception_virus_extension_list.readItemList(filename, false, 0); + if (!result) { + if (!isDaemonised) { + std::cerr << "Error opening exceptionvirusextensionlist" << std::endl; + } + syslog(LOG_ERR, "%s","Error opening exceptionvirusextensionlist"); + return false; + } + exception_virus_extension_list.endsWithSort(); + return true; +} + bool OptionContainer::readbelfile(const char* filename) { bool result = banned_extension_list.readItemList(filename, false, 0); @@ -838,6 +907,18 @@ } +int OptionContainer::inVirusExceptionExtensionList(String url) { + url.removeWhiteSpace(); // just in case of weird browser crap + url.toLower(); + url.removePTP(); // chop off the ht(f)tp(s):// + url = url.after("/"); // chop off any domain before the path + if (url.length() < 2) { // will never match + return -1; + } + return exception_virus_extension_list.findEndsWith(url.toCharArray()); +} + + int OptionContainer::inBannedURLList(String url) { int i; url.removeWhiteSpace(); // just in case of weird browser crap diff -urN --exclude=CVS --exclude=pcx dansguardian-2.7.1-0/OptionContainer.hpp dansguardian2_7/OptionContainer.hpp --- dansguardian-2.7.1-0/OptionContainer.hpp Wed May 14 11:30:23 2003 +++ dansguardian2_7/OptionContainer.hpp Wed May 14 16:21:18 2003 @@ -44,6 +44,7 @@ int inBannedURLList(String url); int inBannedRegExpURLList(String url); int inBannedExtensionList(String url); + int inVirusExceptionExtensionList(String url); int log_exception_hits; int log_file_format; int weighted_phrase_mode; @@ -84,6 +85,13 @@ int proxy_user; int proxy_group; int root_user; + + int virus_scan; // signals if we are using the Virus Scanner Code. + + // Included by Aecio F. Neto (afn@harvest.com.br) - Harvest Consultoria (http://www.harvest.com.br) + int first_trickle_delay; + int following_trickle_delay; + int trickle_length; int pics_rsac_violence; int pics_rsac_sex; @@ -171,6 +179,8 @@ std::string exceptions_url_list_location; std::string exceptions_user_list_location; std::string exceptions_ip_list_location; + std::string exceptions_virus_mimetype_list_location; + std::string exceptions_virus_extension_list_location; std::string language_list_location; std::string access_denied_address; std::string loglocation; @@ -183,6 +193,8 @@ ListContainer exception_url_list; ListContainer exception_user_list; ListContainer exception_ip_list; + ListContainer exception_virus_mimetype_list; + ListContainer exception_virus_extension_list; ListContainer banned_extension_list; ListContainer banned_mimetype_list; ListContainer banned_site_list; @@ -223,6 +235,8 @@ bool readwplfile(const char* filename); bool readwpmfile(const char* filename); bool readeplfile(const char* filename); + bool readevmlfile(const char* filename); // exception virus mimetype list + bool readevelfile(const char* filename); // exception virus extension list void bwlfilehelper(String line, int index); void bwlfilehelperhelper(String line, int index); int findoptionI(const char* option); diff -urN --exclude=CVS --exclude=pcx dansguardian-2.7.1-0/TrickleBuffer.cpp dansguardian2_7/TrickleBuffer.cpp --- dansguardian-2.7.1-0/TrickleBuffer.cpp Wed Dec 31 18:00:00 1969 +++ dansguardian2_7/TrickleBuffer.cpp Wed May 14 16:15:20 2003 @@ -0,0 +1,356 @@ +// Written by James A. Pattie (james@pcxperience.com) Xperience, Inc. (www.pcxperience.com) + +// 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 + +#include "TrickleBuffer.hpp" +#include +#include "String.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_BUF 5*1024*1024 + +extern OptionContainer o; + +TrickleBuffer::TrickleBuffer() +:data(new char[0]),buffer_length(0),trickle_pos(0),timeout(20) {} +// set buffer length to zero and initialise the char* memory block to hold +// the data - initialisation/creation function + + +TrickleBuffer::~TrickleBuffer() { + delete[] data; + delete[] filename; +} +// delete the memory block when the class is destroyed + + +void TrickleBuffer::in(Socket sock, Socket outSock, char* fname, String url, int contentLength, std::string username, std::string clientip) throw(exception) { + + int rc, newsize; + // Included by Aecio F. Neto (afn@harvest.com.br) - Harvest Consultoria (http://www.harvest.com.br) + int tricklebuffer; + int tricklelength = o.trickle_length; + + char* block; // buffer for storing a grabbed block from the + // input stream + time_t lastWrite; + time_t current; + char* startTimeStr; + + fstream tmpFile; + + char* tmpDir = getDownloadDir(); + + filename = fname; + + char* tmpFilename = new char[strlen(tmpDir) + strlen(fname) + 2]; + strcpy(tmpFilename, tmpDir); // store in the class. + strcat(tmpFilename, "/"); // need the seperator. + strcat(tmpFilename, fname); + + delete[] tmpDir; // free up the directory name memory. + + tmpFile.open(tmpFilename, ios::in|ios::out|ios::trunc|ios::binary); + if (!tmpFile) + { + #ifdef DGDEBUG + std::cerr << "Could not create " << tmpFilename << "!" << std::endl; + #endif + throw exception(); + } + + #ifdef DGDEBUG + std::cout << "'" << fname << "', File Length = " << contentLength << std::endl; + #endif + + lastWrite = time(NULL); + current = lastWrite; + startTimeStr = charTime(lastWrite); + while(true) { + // Included by Aecio F. Neto (afn@harvest.com.br) - Harvest Consultoria (http://www.harvest.com.br) + tricklebuffer = tricklelength; + char* lastWriteStr = charTime(current); + try { + status(startTimeStr, lastWriteStr, fname, url, contentLength, buffer_length, username, clientip); + } catch (exception& e) { + tmpFile.close(); + unlink(tmpFilename); // delete the file. + cleanupStatus(fname, username, clientip); // remove the status file. + throw e; // propogate it out. + } + delete[] lastWriteStr; // free the memory. + + if (buffer_length >= 16384) + newsize = buffer_length; + else + newsize = 16384; + + // make sure we don't eat up too much memory! Caught by Wu Jiafu. + if (MAX_BUF < newsize) + newsize = MAX_BUF; + + block = new char[newsize]; + try { + sock.checkForInput(timeout); + } catch (exception& e) { + delete[] block; + break; + } + rc = sock.readFromSocket(block, newsize, 0, timeout); + // grab a block of input, doubled each time + + if (rc < 1) { + delete[] block; + break; // an error occured so end the while() + // or none received so pipe is closed + } + if (rc > 0) { + for (int i=0; i < rc; i++) + { + tmpFile.put(block[i]); + } + buffer_length += rc; // update data size counter + // Included by Aecio F. Neto (afn@harvest.com.br) - Harvest Consultoria (http://www.harvest.com.br) + if (rc > tricklelength) + tricklebuffer = rc; + } + delete[] block; + current = time(NULL); + // Included by Aecio F. Neto (afn@harvest.com.br) - Harvest Consultoria (http://www.harvest.com.br) + if (tricklelength < 1) { + if (trickle_pos == 0 && (current - lastWrite >= o.first_trickle_delay)) { + #ifdef DGDEBUG + std::cout << o.first_trickle_delay << " second trickle. filename = '" << tmpFilename << "'" << std::endl; + #endif + + lastWrite = current; + char *temp2 = new char[1]; + tmpFile.seekg(trickle_pos); + tmpFile.get(temp2[0]); + outSock.readyForOutput(timeout); // exceptions on timeout or error + if (!outSock.writeToSocket(temp2, 1, 0, timeout)) { + tmpFile.close(); + unlink(tmpFilename); // delete the file. + cleanupStatus(fname, username, clientip); // remove the status file. + #ifdef DGDEBUG + std::cerr << "Could not trickle byte 0 = '" << temp2 << "'! = '" << tmpFilename << "'" << std::endl; + #endif + delete[] temp2; + throw exception(); + } // write the data block out to the stream + trickle_pos++; + delete[] temp2; + tmpFile.seekg(0, ios::end); // go back to the end of the file to append the following data. + } + else if (trickle_pos > 0 && (current - lastWrite >= o.following_trickle_delay)) { + #ifdef DGDEBUG + std::cout << o.following_trickle_delay << " second trickle, byte = '" << trickle_pos << "' filename = '" << tmpFilename << "'" << std::endl; + #endif + + lastWrite = current; + char *temp2 = new char[1]; + tmpFile.seekg(trickle_pos); + tmpFile.get(temp2[0]); + outSock.readyForOutput(timeout); // exceptions on timeout or error + if (!outSock.writeToSocket(temp2, 1, 0, timeout)) { + tmpFile.close(); + unlink(tmpFilename); // delete the file. + cleanupStatus(fname, username, clientip); // remove the status file. + #ifdef DGDEBUG + std::cerr << "Could not trickle byte " << trickle_pos << " = '" << temp2 << "'! filename = '" << tmpFilename << "'" << std::endl; + #endif + delete[] temp2; + throw exception(); + } // write the data block out to the stream + trickle_pos++; + delete[] temp2; + tmpFile.seekg(0, ios::end); // go back to the end of the file to append the following data. + } + } + // Included by Aecio F. Neto (afn@harvest.com.br) - Harvest Consultoria (http://www.harvest.com.br) + else if ((buffer_length > (trickle_pos + tricklebuffer)) && ((contentLength - trickle_pos) > tricklelength)) { + char *temp2 = new char[tricklebuffer]; + tmpFile.seekg(trickle_pos); + + for (int i = 0; i < tricklebuffer; i++) + tmpFile.get(temp2[i]); + + outSock.readyForOutput(timeout); // exceptions on timeout or error + if (!outSock.writeToSocket(temp2, tricklebuffer, 0, timeout)) { + tmpFile.close(); + unlink(tmpFilename); // delete the file. + cleanupStatus(fname, username, clientip); // remove the status file. + #ifdef DGDEBUG + std::cerr << "Could not write block to socket." << std::endl; + #endif + delete[] temp2; + throw exception(); + } // write the data block out to the stream + trickle_pos += tricklebuffer; + delete[] temp2; + tmpFile.seekg(0, ios::end); // go back to the end of the file to append the following data. + } + } + tmpFile.close(); + delete[] startTimeStr; // free the memory. +} + +void TrickleBuffer::read(Socket sock, int l) throw (exception) { + delete[] data; // delete the current data store (should be emtpy anyway) + data = new char[l + 2]; // create a new store large enough + int rc; + + rc = sock.readFromSocketn(data, l, 0, timeout); // read in the [POST] data + + if (rc < 0) { + #ifdef DGDEBUG + std::cerr << "Could not read! filename = '" << filename << "'" << std::endl; + #endif + throw exception(); // danger, danger Will Robinson + } + + // The above should be all that's needed - but wait there's more! + // Normal data appended to the header by POST is actually 2 bytes longer + // than the Content-Length header says. It contains a carrage return and + // a new line character. Simple - just add a fudgefactor of 2. + // No. Because when uploading a file via a form the POST data is + // *exactly* as stated and trying to read even 1 more byte will cause the + // read to hang. Also Netscape 4.7x it does it differently. + // So we need to check the status of the connection to see if there really + // are more bytes to read. + + if (sock.checkForInput()) { + rc = sock.readFromSocket(data + l, 2, 0, timeout); + // if an error occured (rc < 1) we ignore it and try and continue + if (rc > 0) { + l += 2; // adjust the length + } + } + + buffer_length = l; // update data size counter + +} + +void TrickleBuffer::out(Socket sock) throw(exception) { + + sock.readyForOutput(timeout); // exceptions on timeout or error + + // need exception or something for a bad write + if (!sock.writeToSocket(data, buffer_length, 0, timeout)) { + #ifdef DGDEBUG + std::cerr << "Could not write! filename = '" << filename << "'" << std::endl; + #endif + throw exception(); + } // write the data block out to the stream +} + +void TrickleBuffer::copytomemory(char* location) { + memcpy(location, data, buffer_length); // doh? what does this do? +} + +void TrickleBuffer::setTimeout(int t) { + timeout = t; +} + +char* TrickleBuffer::getDownloadDir(void) { + char *tmpDownloadDir; + + tmpDownloadDir = new char[256]; + + // call tempDownloadDir. + dSP; // initialize stack pointer + ENTER; + SAVETMPS; + perl_call_pv("tempDownloadDir", G_SCALAR|G_NOARGS); + SPAGAIN; + strcpy(tmpDownloadDir, POPp); // Get the return value off the stack. + PUTBACK; + FREETMPS; + LEAVE; + + return tmpDownloadDir; +} + +void TrickleBuffer::status(char* startTime, char* currTime, char* fname, String url, int contentLength, int bytesDownloaded, std::string username, std::string clientip) throw(exception) { + int result; // holds return from function. + + char contentLengthStr[51]; // 50 digits worth! + sprintf(contentLengthStr, "%i", contentLength); + + char bytesDownloadedStr[51]; // 50 digits worth! + sprintf(bytesDownloadedStr, "%i", bytesDownloaded); + + // call tempDownloadDir. + dSP; // initialize stack pointer + ENTER; + SAVETMPS; + PUSHMARK(SP); + XPUSHs(sv_2mortal(newSVpv(startTime, 0))); + XPUSHs(sv_2mortal(newSVpv(currTime, 0))); + XPUSHs(sv_2mortal(newSVpv(fname, 0))); + XPUSHs(sv_2mortal(newSVpv(url.toCharArray(), 0))); + XPUSHs(sv_2mortal(newSVpv(contentLengthStr, 0))); + XPUSHs(sv_2mortal(newSVpv(bytesDownloadedStr, 0))); + XPUSHs(sv_2mortal(newSVpv(username.c_str(), 0))); + XPUSHs(sv_2mortal(newSVpv(clientip.c_str(), 0))); + PUTBACK; + perl_call_pv("updateStatusFile", G_SCALAR); + SPAGAIN; + result = POPi; // Get the return value off the stack. + PUTBACK; + FREETMPS; + LEAVE; + + if (!result) + { + throw exception(); // signal an error occurred. + } +} + +void TrickleBuffer::cleanupStatus(char *fname, std::string username, std::string clientip) { + int result; // holds return from function. + + // call removeStatusFile. + dSP; // initialize stack pointer + ENTER; + SAVETMPS; + PUSHMARK(SP); + XPUSHs(sv_2mortal(newSVpv(fname, 0))); + XPUSHs(sv_2mortal(newSVpv(username.c_str(), 0))); + XPUSHs(sv_2mortal(newSVpv(clientip.c_str(), 0))); + PUTBACK; + perl_call_pv("removeStatusFile", G_SCALAR); + SPAGAIN; + result = POPi; // Get the return value off the stack. + PUTBACK; + FREETMPS; + LEAVE; +} + +char* TrickleBuffer::charTime(time_t time) { + char* timeStr = new char[27]; + ctime_r(&time, timeStr); // convert to string. + + return timeStr; +} diff -urN --exclude=CVS --exclude=pcx dansguardian-2.7.1-0/TrickleBuffer.hpp dansguardian2_7/TrickleBuffer.hpp --- dansguardian-2.7.1-0/TrickleBuffer.hpp Wed Dec 31 18:00:00 1969 +++ dansguardian2_7/TrickleBuffer.hpp Wed May 14 16:15:20 2003 @@ -0,0 +1,50 @@ +// Written by James A. Pattie (james@pcxperience.com) Xperience, Inc. (www.pcxperience.com) + +// 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 + +#ifndef __HPP_TRICKLEBUFFER +#define __HPP_TRICKLEBUFFER +#include +#include "Socket.hpp" +#include +#include "OptionContainer.hpp" + +class TrickleBuffer { + +public: + char* buffer[1024]; + char* data; + int buffer_length; + int trickle_pos; + TrickleBuffer(); + ~TrickleBuffer(); + void read(Socket sock, int length) throw(exception); + int length() {return buffer_length;} + void copytomemory(char* location); + void in(Socket sock, Socket outSock, char* fname, String url, int contentLength, std::string username, std::string clientip) throw(exception); + void out(Socket sock) throw(exception); + void setTimeout(int t); + char* fileName() { return filename; } + char* getDownloadDir(void); + int tricklePos() { return trickle_pos; } + void status(char* startTime, char* currTime, char* fname, String url, int contentLength, int bytesDownloaded, std::string username, std::string clientip) throw(exception); + void cleanupStatus(char *fname, std::string username, std::string clientip); + char* charTime(time_t time); +private: + int timeout; + char* filename; +}; + +#endif diff -urN --exclude=CVS --exclude=pcx dansguardian-2.7.1-0/VirusScanner.cpp dansguardian2_7/VirusScanner.cpp --- dansguardian-2.7.1-0/VirusScanner.cpp Wed Dec 31 18:00:00 1969 +++ dansguardian2_7/VirusScanner.cpp Wed May 14 14:32:36 2003 @@ -0,0 +1,284 @@ +// Written by James A. Pattie (james@pcxperience.com) Xperience, Inc. (www.pcxperience.com) + +// 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 + +#include +#include "String.hpp" +#include +#include "VirusScanner.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + +VirusScanner::VirusScanner() +:data(new char[0]),file_length(0),buffer_length(0),timeout(20) {} +// set buffer length to zero and initialise the char* memory block to hold +// the data - initialisation/creation function + + +VirusScanner::~VirusScanner() { + delete[] data; + delete[] filename; +} +// delete the memory block when the class is destroyed + + +void VirusScanner::out(Socket sock) throw(exception) { + + sock.readyForOutput(timeout); // exceptions on timeout or error + + // need exception or something for a bad write + if (!sock.writeToSocket(data, buffer_length, 0, timeout)) { + throw exception(); + } // write the data block out to the stream +} + +void VirusScanner::setTimeout(int t) { + timeout = t; +} + +void VirusScanner::scanFile(char* fname, Socket sock, String url, int trickle_pos, std::string username, std::string clientip) throw(exception) { + fstream tmpFile; + int infected = 0; + + char* tmpDir = getDownloadDir(); + + filename = new char[strlen(tmpDir) + strlen(fname) + 2]; + strcpy(filename, tmpDir); // store in the class. + strcat(filename, "/"); // need the seperator. + strcat(filename, fname); + + delete[] tmpDir; // free up the directory name memory. + + // now call the scanner script + #ifdef DGDEBUG + std::cout << "scanning " << filename << " for user '" << username << "'..." << std::endl; + #endif + + // open the file for reading. + tmpFile.open(filename, ios::in); + if (!tmpFile) + { + #ifdef DGDEBUG + std::cerr << "Could not open " << filename << "!" << std::endl; + #endif + throw exception(); + } + tmpFile.seekg(0, ios::end); + file_length = tmpFile.tellg(); + tmpFile.close(); + + char bufferLengthStr[51]; // 50 digits worth! + sprintf(bufferLengthStr, "%i", file_length); + + // call scan with the file name and the size of the file in bytes. + dSP; // initialize stack pointer + ENTER; + SAVETMPS; + PUSHMARK(SP); + XPUSHs(sv_2mortal(newSVpv(filename, 0))); + XPUSHs(sv_2mortal(newSVpv(bufferLengthStr, 0))); + XPUSHs(sv_2mortal(newSVpv(url.toCharArray(), 0))); + XPUSHs(sv_2mortal(newSVpv(username.c_str(), 0))); + XPUSHs(sv_2mortal(newSVpv(clientip.c_str(), 0))); + PUTBACK; + perl_call_pv("scan", G_SCALAR); + SPAGAIN; + infected = POPi; // Get the return value off the stack. + PUTBACK; + FREETMPS; + LEAVE; + + #ifdef DGDEBUG + std::cout << "infected = " << infected << std::endl; + #endif + + if (!infected) + { + // open the file for reading. + tmpFile.open(filename, ios::in); + if (!tmpFile) + { + #ifdef DGDEBUG + std::cerr << "Could not open " << filename << "!" << std::endl; + #endif + // remove the status file + cleanupStatus(fname, username, clientip); + throw exception(); + } + + #ifdef DGDEBUG + std::cout << "sending data from trickle_pos = '" << trickle_pos << "', length = '" << file_length << "', filename = '" << filename << "'" << std::endl; + #endif + + file_length = 0; + + // position ourselves. + tmpFile.seekg(trickle_pos); // position at the first byte to send. + + // while not the end of the file (get a byte) + char ch; + int size = 16384; + while (tmpFile.get(ch)) + { + // see if we need to allocate memory for the data array + if (buffer_length == 0) + { + data = new char[size]; + } + + // insert in data array + data[buffer_length] = ch; + buffer_length++; + file_length++; // increment the size of the file. + + // if size bytes read, then send current data, free data + if (buffer_length == size - 1) + { + try { + out(sock); + } catch (exception& e) { + tmpFile.close(); + unlink(filename); // delete the file. + // remove the status file + cleanupStatus(fname, username, clientip); + #ifdef DGDEBUG + std::cout << "sending data failed! filename = '" << filename << "'" << std::endl; + #endif + throw e; + } + buffer_length = 0; + delete[] data; + } + } + + // see if there is any data left to send + if (buffer_length > 0) + { + try { + out(sock); + } catch (exception& e) { + tmpFile.close(); + unlink(filename); // delete the file. + // remove the status file + cleanupStatus(fname, username, clientip); + #ifdef DGDEBUG + std::cout << "sending data failed! filename = '" << filename << "'" << std::endl; + #endif + throw e; + } + } + + tmpFile.close(); // close the file. + + // now delete the file + unlink(filename); + + // remove the status file + cleanupStatus(fname, username, clientip); + } + else + { + // in the future we will try to archive and then potentially disinfect this file. + // for now, delete it. It is archived in the perl code now. + unlink(filename); + + // remove the status file + cleanupStatus(fname, username, clientip); + + // send the browser an error html document stating that the file was infected w/ a virus. + try { + // I'm not 100% sure that I don't need the following 2 lines at some point in time. + // sock.writeString("HTTP/1.0 200 OK\n"); + // sock.writeString("Content-type: text/html\n\n"); + sock.writeString("DansGuardian - File Infected"); + sock.writeString("

DansGuardian - File Infected

"); + sock.writeString("url = "); + sock.writeString(url.toCharArray()); + sock.writeString(" was infected with a virus!

Contact your Administrator for details.
"); + } catch (exception& e) { + #ifdef DGDEBUG + std::cout << "sending infected message failed! filename = '" << filename << "'" << std::endl; + #endif + throw e; + } + } +} + +char* VirusScanner::getTmpName(void) { + char *tmpFileName; + + tmpFileName = new char[24]; + + // call tempFileName. + dSP; // initialize stack pointer + ENTER; + SAVETMPS; + perl_call_pv("tempFileName", G_SCALAR|G_NOARGS); + SPAGAIN; + strcpy(tmpFileName, POPp); // Get the return value off the stack. + PUTBACK; + FREETMPS; + LEAVE; + + return tmpFileName; +} + +char* VirusScanner::getDownloadDir(void) { + char *tmpDownloadDir; + + tmpDownloadDir = new char[256]; + + // call tempDownloadDir. + dSP; // initialize stack pointer + ENTER; + SAVETMPS; + perl_call_pv("tempDownloadDir", G_SCALAR|G_NOARGS); + SPAGAIN; + strcpy(tmpDownloadDir, POPp); // Get the return value off the stack. + PUTBACK; + FREETMPS; + LEAVE; + + return tmpDownloadDir; +} + +void VirusScanner::cleanupStatus(char *fname, std::string username, std::string clientip) { + int result; // holds return from function. + + // call removeStatusFile. + dSP; // initialize stack pointer + ENTER; + SAVETMPS; + PUSHMARK(SP); + XPUSHs(sv_2mortal(newSVpv(fname, 0))); + XPUSHs(sv_2mortal(newSVpv(username.c_str(), 0))); + XPUSHs(sv_2mortal(newSVpv(clientip.c_str(), 0))); + PUTBACK; + perl_call_pv("removeStatusFile", G_SCALAR); + SPAGAIN; + result = POPi; // Get the return value off the stack. + PUTBACK; + FREETMPS; + LEAVE; +} diff -urN --exclude=CVS --exclude=pcx dansguardian-2.7.1-0/VirusScanner.hpp dansguardian2_7/VirusScanner.hpp --- dansguardian-2.7.1-0/VirusScanner.hpp Wed Dec 31 18:00:00 1969 +++ dansguardian2_7/VirusScanner.hpp Wed May 14 14:32:36 2003 @@ -0,0 +1,44 @@ +// Written by James A. Pattie (james@pcxperience.com) Xperience, Inc. (www.pcxperience.com) + +// 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 + +#ifndef __HPP_VIRUSSCANNER +#define __HPP_VIRUSSCANNER +#include +#include "Socket.hpp" +#include "String.hpp" + +class VirusScanner { + +public: + char* data; + int file_length; + int buffer_length; + VirusScanner(); + ~VirusScanner(); + int length() {return file_length;} + void out(Socket sock) throw(exception); + void setTimeout(int t); + char* getTmpName(void); + char* getDownloadDir(void); + void scanFile(char* fname, Socket sock, String url, int trickle_pos, std::string username, std::string clientip) throw(exception); + void cleanupStatus(char *fname, std::string username, std::string clientip); + +private: + int timeout; + char *filename; +}; + +#endif diff -urN --exclude=CVS --exclude=pcx dansguardian-2.7.1-0/autoconf/fbsd.in dansguardian2_7/autoconf/fbsd.in --- dansguardian-2.7.1-0/autoconf/fbsd.in Wed May 14 11:30:24 2003 +++ dansguardian2_7/autoconf/fbsd.in Wed May 14 15:01:42 2003 @@ -7,7 +7,8 @@ OBJ = String.o OptionContainer.o FDTunnel.o ConnectionHandler.o \ DataBuffer.o HTTPHeader.o NaughtyFilter.o RegExp.o Socket.o \ FatController.o UDSocket.o SysV.o ListContainer.o Ident.o \ - HTMLTemplate.o LanguageContainer.o DynamicURLList.o ImageContainer.o + HTMLTemplate.o LanguageContainer.o DynamicURLList.o ImageContainer.o \ + TrickleBuffer.o perlxsi.o VirusScanner.o LIBS = /usr/lib/libz.a PROG = dansguardian INSTALLFILES = dansguardian dansguardian.conf dansguardian.sysv \ @@ -23,6 +24,11 @@ PASSVARS += -DLOGLOCATION=\"${LOGLOCATION}access.log\" PASSVARS += -DCONFFILELOCATION=\"${CONFFILELOCATION}dansguardian.conf\" PASSVARS += -DPIDDIR=\"${PIDDIR}\" +PASSVARS += -DCONFFILEDIR=\"${CONFFILELOCATION}\" + +PERLCC = `perl -MExtUtils::Embed -e ccopts` +PERLLD = `perl -MExtUtils::Embed -e ldopts` +PERL = $(PERLCC) $(PERLLD) #Some advanced options: WARNING = -Wall @@ -34,11 +40,14 @@ CFLAGS = $(OPTIMISE) $(WARNING) DGCFLAGS = $(OPTIMISE) $(PASSVARS) -lz $(STATIC) +.c.o: + $(CPP) $(DEBUG) $(CFLAGS) $(PERLCC) -c $< + .cpp.o: - $(CPP) $(DEBUG) $(CFLAGS) -c $< + $(CPP) $(DEBUG) $(CFLAGS) $(PERLCC) -c $< all: $(OBJ) - $(CPP) -o dansguardian $(DGCFLAGS) $(DEBUG) $(OBJ) $(LIBS) dansguardian.cpp + $(CPP) -o dansguardian $(DGCFLAGS) $(DEBUG) $(OBJ) $(LIBS) $(PERL) dansguardian.cpp install: @test -d $I$(SYSVLOCATION) || install -d $I$(SYSVLOCATION) @@ -68,11 +77,18 @@ cp -f ./exceptionurllist $I$(CONFFILELOCATION)exceptionurllist cp -f ./exceptionuserlist $I$(CONFFILELOCATION)exceptionuserlist cp -f ./exceptioniplist $I$(CONFFILELOCATION)exceptioniplist + cp -f ./exceptionvirusmimetypelist $I$(CONFFILELOCATION)exceptionvirusmimetypelist + cp -f ./exceptionvirusextensionlist $I$(CONFFILELOCATION)exceptionvirusextensionlist cp -f ./pics $I$(CONFFILELOCATION)pics cp -f ./transparent1x1.gif $I$(CONFFILELOCATION)transparent1x1.gif cp -f ./template.html $I$(CONFFILELOCATION)template.html cp -f ./messages $I$(CONFFILELOCATION)messages cp -f ./logrotation $I$(CONFFILELOCATION)logrotation + cp -f ./virusscanner $I$(CONFFILELOCATION)virusscanner + cp -f ./virusscanner.conf $I$(CONFFILELOCATION)virusscanner.conf + cp -f ./logger.pl $I$(CONFFILELOCATION)logger.pl + cp -f ./config.pl $I$(CONFFILELOCATION)config.pl + cp -f ./sweep.pl $I$(CONFFILELOCATION)sweep.pl cp -f ./dansguardian.bsdv $I$(SYSVLOCATION)dansguardian.sh cp -f ./dansguardian.pl $I$(CGIBINLOCATION)dansguardian.pl chmod o+x $I$(CGIBINLOCATION)dansguardian.pl diff -urN --exclude=CVS --exclude=pcx dansguardian-2.7.1-0/autoconf/linux.in dansguardian2_7/autoconf/linux.in --- dansguardian-2.7.1-0/autoconf/linux.in Wed May 14 11:30:24 2003 +++ dansguardian2_7/autoconf/linux.in Wed May 14 15:02:09 2003 @@ -11,8 +11,9 @@ OBJ = String.o OptionContainer.o FDTunnel.o ConnectionHandler.o \ DataBuffer.o HTTPHeader.o NaughtyFilter.o RegExp.o Socket.o \ FatController.o UDSocket.o SysV.o ListContainer.o Ident.o \ - HTMLTemplate.o LanguageContainer.o DynamicURLList.o ImageContainer.o - + HTMLTemplate.o LanguageContainer.o DynamicURLList.o ImageContainer.o \ + TrickleBuffer.o perlxsi.o VirusScanner.o + LIBS = /usr/lib/libz.a PROG = dansguardian INSTALLFILES = dansguardian dansguardian.conf dansguardian.sysv \ @@ -28,6 +29,11 @@ PASSVARS += -DLOGLOCATION=\"${LOGLOCATION}access.log\" PASSVARS += -DCONFFILELOCATION=\"${CONFFILELOCATION}dansguardian.conf\" PASSVARS += -DPIDDIR=\"${PIDDIR}\" +PASSVARS += -DCONFFILEDIR=\"${CONFFILELOCATION}\" + +PERLCC = `perl -MExtUtils::Embed -e ccopts` +PERLLD = `perl -MExtUtils::Embed -e ldopts` +PERL = $(PERLCC) $(PERLLD) #Some advanced options: WARNING = -Wall @@ -39,11 +45,14 @@ CFLAGS = $(OPTIMISE) $(WARNING) DGCFLAGS = $(OPTIMISE) $(PASSVARS) -lz $(STATIC) +.c.o: + $(CPP) $(DEBUG) $(CFLAGS) $(PERLCC) -c $< + .cpp.o: - $(CPP) $(DEBUG) $(CFLAGS) -c $< + $(CPP) $(DEBUG) $(CFLAGS) $(PERLCC) -c $< all: $(OBJ) - $(CPP) -o dansguardian $(DGCFLAGS) $(DEBUG) $(OBJ) $(LIBS) dansguardian.cpp + $(CPP) -o dansguardian $(DGCFLAGS) $(DEBUG) $(OBJ) $(LIBS) $(PERL) dansguardian.cpp install: @test -d $I$(SYSVLOCATION) || install -d $I$(SYSVLOCATION) @@ -72,11 +81,18 @@ cp -f ./exceptionurllist $I$(CONFFILELOCATION)exceptionurllist cp -f ./exceptionuserlist $I$(CONFFILELOCATION)exceptionuserlist cp -f ./exceptioniplist $I$(CONFFILELOCATION)exceptioniplist + cp -f ./exceptionvirusmimetypelist $I$(CONFFILELOCATION)exceptionvirusmimetypelist + cp -f ./exceptionvirusextensionlist $I$(CONFFILELOCATION)exceptionvirusextensionlist cp -f ./pics $I$(CONFFILELOCATION)pics cp -f ./transparent1x1.gif $I$(CONFFILELOCATION)transparent1x1.gif cp -f ./template.html $I$(CONFFILELOCATION)template.html cp -f ./messages $I$(CONFFILELOCATION)messages cp -f ./logrotation $I$(CONFFILELOCATION)logrotation + cp -f ./virusscanner $I$(CONFFILELOCATION)virusscanner + cp -f ./virusscanner.conf $I$(CONFFILELOCATION)virusscanner.conf + cp -f ./logger.pl $I$(CONFFILELOCATION)logger.pl + cp -f ./config.pl $I$(CONFFILELOCATION)config.pl + cp -f ./sweep.pl $I$(CONFFILELOCATION)sweep.pl cp -f ./dansguardian.sysv $I$(SYSVLOCATION)dansguardian cp -f ./dansguardian.pl $I$(CGIBINLOCATION)dansguardian.pl chmod o+x $I$(CGIBINLOCATION)dansguardian.pl diff -urN --exclude=CVS --exclude=pcx dansguardian-2.7.1-0/autoconf/nbsd.in dansguardian2_7/autoconf/nbsd.in --- dansguardian-2.7.1-0/autoconf/nbsd.in Wed May 14 11:30:24 2003 +++ dansguardian2_7/autoconf/nbsd.in Wed May 14 15:02:25 2003 @@ -7,7 +7,8 @@ OBJ = String.o OptionContainer.o FDTunnel.o ConnectionHandler.o \ DataBuffer.o HTTPHeader.o NaughtyFilter.o RegExp.o Socket.o \ FatController.o UDSocket.o SysV.o ListContainer.o Ident.o \ - HTMLTemplate.o LanguageContainer.o DynamicURLList.o ImageContainer.o + HTMLTemplate.o LanguageContainer.o DynamicURLList.o ImageContainer.o \ + TrickleBuffer.o perlxsi.o VirusScanner.o LIBS = /usr/lib/libz.a PROG = dansguardian INSTALLFILES = dansguardian dansguardian.conf dansguardian.sysv \ @@ -24,6 +25,10 @@ PASSVARS += -DCONFFILELOCATION=\"${CONFFILELOCATION}dansguardian.conf\" PASSVARS += -DPIDDIR=\"${PIDDIR}\" +PERLCC = `perl -MExtUtils::Embed -e ccopts` +PERLLD = `perl -MExtUtils::Embed -e ldopts` +PERL = $(PERLCC) $(PERLLD) + #Some advanced options: WARNING = -Wall OPTIMISE = -O2 @@ -34,11 +39,14 @@ CFLAGS = $(OPTIMISE) $(WARNING) DGCFLAGS = $(OPTIMISE) $(PASSVARS) -lz $(STATIC) +.c.o: + $(CPP) $(DEBUG) $(CFLAGS) $(PERLCC) -c $< + .cpp.o: - $(CPP) $(DEBUG) $(CFLAGS) -c $< + $(CPP) $(DEBUG) $(CFLAGS) $(PERLCC) -c $< all: $(OBJ) - $(CPP) -o dansguardian $(DGCFLAGS) $(DEBUG) $(OBJ) $(LIBS) dansguardian.cpp + $(CPP) -o dansguardian $(DGCFLAGS) $(DEBUG) $(OBJ) $(LIBS) $(PERL) dansguardian.cpp install: @test -d $I$(SYSVLOCATION) || install -d $I$(SYSVLOCATION) @@ -68,11 +76,18 @@ cp -f ./exceptionurllist $I$(CONFFILELOCATION)exceptionurllist cp -f ./exceptionuserlist $I$(CONFFILELOCATION)exceptionuserlist cp -f ./exceptioniplist $I$(CONFFILELOCATION)exceptioniplist + cp -f ./exceptionvirusmimetypelist $I$(CONFFILELOCATION)exceptionvirusmimetypelist + cp -f ./exceptionvirusextensionlist $I$(CONFFILELOCATION)exceptionvirusextensionlist cp -f ./pics $I$(CONFFILELOCATION)pics cp -f ./transparent1x1.gif $I$(CONFFILELOCATION)transparent1x1.gif cp -f ./template.html $I$(CONFFILELOCATION)template.html cp -f ./messages $I$(CONFFILELOCATION)messages cp -f ./logrotation $I$(CONFFILELOCATION)logrotation + cp -f ./virusscanner $I$(CONFFILELOCATION)virusscanner + cp -f ./virusscanner.conf $I$(CONFFILELOCATION)virusscanner.conf + cp -f ./logger.pl $I$(CONFFILELOCATION)logger.pl + cp -f ./config.pl $I$(CONFFILELOCATION)config.pl + cp -f ./sweep.pl $I$(CONFFILELOCATION)sweep.pl cp -f ./dansguardian.bsdv $I$(SYSVLOCATION)dansguardian.sh cp -f ./dansguardian.pl $I$(CGIBINLOCATION)dansguardian.pl chmod o+x $I$(CGIBINLOCATION)dansguardian.pl diff -urN --exclude=CVS --exclude=pcx dansguardian-2.7.1-0/autoconf/obsd.in dansguardian2_7/autoconf/obsd.in --- dansguardian-2.7.1-0/autoconf/obsd.in Wed May 14 11:30:24 2003 +++ dansguardian2_7/autoconf/obsd.in Wed May 14 15:02:36 2003 @@ -7,7 +7,8 @@ OBJ = String.o OptionContainer.o FDTunnel.o ConnectionHandler.o \ DataBuffer.o HTTPHeader.o NaughtyFilter.o RegExp.o Socket.o \ FatController.o UDSocket.o SysV.o ListContainer.o Ident.o \ - HTMLTemplate.o LanguageContainer.o DynamicURLList.o ImageContainer.o + HTMLTemplate.o LanguageContainer.o DynamicURLList.o ImageContainer.o \ + TrickleBuffer.o perlxsi.o VirusScanner.o LIBS = /usr/lib/libz.a PROG = dansguardian INSTALLFILES = dansguardian dansguardian.conf dansguardian.sysv \ @@ -23,6 +24,11 @@ PASSVARS += -DLOGLOCATION=\"${LOGLOCATION}access.log\" PASSVARS += -DCONFFILELOCATION=\"${CONFFILELOCATION}dansguardian.conf\" PASSVARS += -DPIDDIR=\"${PIDDIR}\" +PASSVARS += -DCONFFILEDIR=\"${CONFFILELOCATION}\" + +PERLCC = `perl -MExtUtils::Embed -e ccopts` +PERLLD = `perl -MExtUtils::Embed -e ldopts` +PERL = $(PERLCC) $(PERLLD) #Some advanced options: WARNING = -Wall @@ -36,11 +42,14 @@ .SUFFIXES: .cpp +.c.o: + $(CPP) $(DEBUG) $(CFLAGS) $(PERLCC) -c $< + .cpp.o: - $(CPP) $(DEBUG) $(CFLAGS) -c $< + $(CPP) $(DEBUG) $(CFLAGS) $(PERLCC) -c $< all: $(OBJ) - $(CPP) -o dansguardian $(DGCFLAGS) $(DEBUG) $(OBJ) $(LIBS) dansguardian.cpp + $(CPP) -o dansguardian $(DGCFLAGS) $(DEBUG) $(OBJ) $(LIBS) $(PERL) dansguardian.cpp install: @test -d $I$(SYSVLOCATION) || install -d $I$(SYSVLOCATION) @@ -70,11 +79,18 @@ cp -f ./exceptionurllist $I$(CONFFILELOCATION)exceptionurllist cp -f ./exceptionuserlist $I$(CONFFILELOCATION)exceptionuserlist cp -f ./exceptioniplist $I$(CONFFILELOCATION)exceptioniplist + cp -f ./exceptionvirusmimetypelist $I$(CONFFILELOCATION)exceptionvirusmimetypelist + cp -f ./exceptionvirusextensionlist $I$(CONFFILELOCATION)exceptionvirusextensionlist cp -f ./pics $I$(CONFFILELOCATION)pics cp -f ./transparent1x1.gif $I$(CONFFILELOCATION)transparent1x1.gif cp -f ./template.html $I$(CONFFILELOCATION)template.html cp -f ./messages $I$(CONFFILELOCATION)messages cp -f ./logrotation $I$(CONFFILELOCATION)logrotation + cp -f ./virusscanner $I$(CONFFILELOCATION)virusscanner + cp -f ./virusscanner.conf $I$(CONFFILELOCATION)virusscanner.conf + cp -f ./logger.pl $I$(CONFFILELOCATION)logger.pl + cp -f ./config.pl $I$(CONFFILELOCATION)config.pl + cp -f ./sweep.pl $I$(CONFFILELOCATION)sweep.pl cp -f ./dansguardian.bsdv $I$(SYSVLOCATION)dansguardian.sh cp -f ./dansguardian.pl $I$(CGIBINLOCATION)dansguardian.pl chmod o+x $I$(CGIBINLOCATION)dansguardian.pl diff -urN --exclude=CVS --exclude=pcx dansguardian-2.7.1-0/autoconf/osx.in dansguardian2_7/autoconf/osx.in --- dansguardian-2.7.1-0/autoconf/osx.in Wed May 14 11:30:24 2003 +++ dansguardian2_7/autoconf/osx.in Wed May 14 15:02:46 2003 @@ -7,7 +7,8 @@ OBJ = String.o OptionContainer.o FDTunnel.o ConnectionHandler.o \ DataBuffer.o HTTPHeader.o NaughtyFilter.o RegExp.o Socket.o \ FatController.o UDSocket.o SysV.o ListContainer.o Ident.o \ - HTMLTemplate.o LanguageContainer.o DynamicURLList.o ImageContainer.o + HTMLTemplate.o LanguageContainer.o DynamicURLList.o ImageContainer.o \ + TrickleBuffer.o perlxsi.o VirusScanner.o LIBS = /usr/lib/libgcc.a PROG = dansguardian INSTALLFILES = dansguardian dansguardian.conf dansguardian.sysv \ @@ -24,6 +25,10 @@ PASSVARS += -DCONFFILELOCATION=\"${CONFFILELOCATION}dansguardian.conf\" PASSVARS += -DPIDDIR=\"${PIDDIR}\" +PERLCC = `perl -MExtUtils::Embed -e ccopts` +PERLLD = `perl -MExtUtils::Embed -e ldopts` +PERL = $(PERLCC) $(PERLLD) + #Some advanced options: WARNING = -Wall OPTIMISE = -O2 @@ -34,11 +39,14 @@ CFLAGS = $(OPTIMISE) $(WARNING) DGCFLAGS = $(OPTIMISE) $(PASSVARS) -lz $(STATIC) +.c.o: + $(CPP) $(DEBUG) $(CFLAGS) $(PERLCC) -c $< + .cpp.o: - $(CPP) $(DEBUG) $(CFLAGS) -c $< + $(CPP) $(DEBUG) $(CFLAGS) $(PERLCC) -c $< all: $(OBJ) - $(CPP) -o dansguardian $(DGCFLAGS) $(DEBUG) $(OBJ) $(LIBS) dansguardian.cpp + $(CPP) -o dansguardian $(DGCFLAGS) $(DEBUG) $(OBJ) $(LIBS) $(PERL) dansguardian.cpp installx: @test -d $I$(SYSVLOCATION) || install -d $I$(SYSVLOCATION) @@ -68,11 +76,18 @@ cp -f ./exceptionurllist $I$(CONFFILELOCATION)exceptionurllist cp -f ./exceptionuserlist $I$(CONFFILELOCATION)exceptionuserlist cp -f ./exceptioniplist $I$(CONFFILELOCATION)exceptioniplist + cp -f ./exceptionvirusmimetypelist $I$(CONFFILELOCATION)exceptionvirusmimetypelist + cp -f ./exceptionvirusextensionlist $I$(CONFFILELOCATION)exceptionvirusextensionlist cp -f ./pics $I$(CONFFILELOCATION)pics cp -f ./transparent1x1.gif $I$(CONFFILELOCATION)transparent1x1.gif cp -f ./template.html $I$(CONFFILELOCATION)template.html cp -f ./messages $I$(CONFFILELOCATION)messages cp -f ./logrotation $I$(CONFFILELOCATION)logrotation + cp -f ./virusscanner $I$(CONFFILELOCATION)virusscanner + cp -f ./virusscanner.conf $I$(CONFFILELOCATION)virusscanner.conf + cp -f ./logger.pl $I$(CONFFILELOCATION)logger.pl + cp -f ./config.pl $I$(CONFFILELOCATION)config.pl + cp -f ./sweep.pl $I$(CONFFILELOCATION)sweep.pl cp -f ./dansguardian.bsdv $I$(SYSVLOCATION)dansguardian.sh cp -f ./dansguardian.pl $I$(CGIBINLOCATION)dansguardian.pl chmod o+x $I$(CGIBINLOCATION)dansguardian.pl diff -urN --exclude=CVS --exclude=pcx dansguardian-2.7.1-0/autoconf/solaris.in dansguardian2_7/autoconf/solaris.in --- dansguardian-2.7.1-0/autoconf/solaris.in Wed May 14 11:30:24 2003 +++ dansguardian2_7/autoconf/solaris.in Wed May 14 15:02:55 2003 @@ -7,7 +7,8 @@ OBJ = String.o OptionContainer.o FDTunnel.o ConnectionHandler.o \ DataBuffer.o HTTPHeader.o NaughtyFilter.o RegExp.o Socket.o \ FatController.o UDSocket.o SysV.o ListContainer.o Ident.o \ - HTMLTemplate.o LanguageContainer.o DynamicURLList.o ImageContainer.o + HTMLTemplate.o LanguageContainer.o DynamicURLList.o ImageContainer.o \ + TrickleBuffer.o perlxsi.o VirusScanner.o LIBS = -lstdc++ -lsocket -lnsl -lz -lresolv PROG = dansguardian INSTALLFILES = dansguardian dansguardian.conf dansguardian.sysv \ @@ -23,6 +24,11 @@ PASSVARS += -DLOGLOCATION=\"${LOGLOCATION}access.log\" PASSVARS += -DCONFFILELOCATION=\"${CONFFILELOCATION}dansguardian.conf\" PASSVARS += -DPIDDIR=\"${PIDDIR}\" +PASSVARS += -DCONFFILEDIR=\"${CONFFILELOCATION}\" + +PERLCC = `perl -MExtUtils::Embed -e ccopts` +PERLLD = `perl -MExtUtils::Embed -e ldopts` +PERL = $(PERLCC) $(PERLLD) #Some advanced options: WARNING = -Wall @@ -34,11 +40,14 @@ CFLAGS = $(OPTIMISE) $(WARNING) DGCFLAGS = $(OPTIMISE) $(PASSVARS) $(STATIC) +.c.o: + $(CPP) $(DEBUG) $(CFLAGS) $(PERLCC) -c $< + .cpp.o: - $(CPP) $(DEBUG) $(CFLAGS) -c $< + $(CPP) $(DEBUG) $(CFLAGS) $(PERLCC) -c $< all: $(OBJ) - $(CPP) -o dansguardian $(DGCFLAGS) $(DEBUG) $(OBJ) $(LIBS) dansguardian.cpp + $(CPP) -o dansguardian $(DGCFLAGS) $(DEBUG) $(OBJ) $(LIBS) $(PERL) dansguardian.cpp install: @test -d $I$(SYSVLOCATION) || /usr/sbin/install -d $I$(SYSVLOCATION) @@ -68,11 +77,18 @@ cp -f ./exceptionurllist $I$(CONFFILELOCATION)exceptionurllist cp -f ./exceptionuserlist $I$(CONFFILELOCATION)exceptionuserlist cp -f ./exceptioniplist $I$(CONFFILELOCATION)exceptioniplist + cp -f ./exceptionvirusmimetypelist $I$(CONFFILELOCATION)exceptionvirusmimetypelist + cp -f ./exceptionvirusextensionlist $I$(CONFFILELOCATION)exceptionvirusextensionlist cp -f ./pics $I$(CONFFILELOCATION)pics cp -f ./transparent1x1.gif $I$(CONFFILELOCATION)transparent1x1.gif cp -f ./template.html $I$(CONFFILELOCATION)template.html cp -f ./messages $I$(CONFFILELOCATION)messages cp -f ./logrotation $I$(CONFFILELOCATION)logrotation + cp -f ./virusscanner $I$(CONFFILELOCATION)virusscanner + cp -f ./virusscanner.conf $I$(CONFFILELOCATION)virusscanner.conf + cp -f ./logger.pl $I$(CONFFILELOCATION)logger.pl + cp -f ./config.pl $I$(CONFFILELOCATION)config.pl + cp -f ./sweep.pl $I$(CONFFILELOCATION)sweep.pl cp -f ./dansguardian.solv $I$(SYSVLOCATION)dansguardian cp -f ./dansguardian.pl $I$(CGIBINLOCATION)dansguardian.pl chmod o+x $I$(CGIBINLOCATION)dansguardian.pl diff -urN --exclude=CVS --exclude=pcx dansguardian-2.7.1-0/config.pl dansguardian2_7/config.pl --- dansguardian-2.7.1-0/config.pl Wed Dec 31 18:00:00 1969 +++ dansguardian2_7/config.pl Wed May 14 14:32:37 2003 @@ -0,0 +1,184 @@ +# MailScanner - SMTP E-Mail Virus Scanner +# Copyright (C) 2002 Julian Field +# +# 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 +# +# The author, Julian Field, can be contacted by email at +# Jules@JulianField.net +# or by paper mail at +# Julian Field +# Dept of Electronics & Computer Science +# University of Southampton +# Southampton +# SO17 1BJ +# United Kingdom +# + +# 07/23/2002 +# James A. Pattie (james@pcxperience.com) - updated to only have the +# options needed for virus scanning configuration. + +# Read the configuration file and set globals accordingly. + +use strict; +package Config; + +my $prefix = '/etc/dansguardian'; + +# Default values +$Config::Debugging = 0; +$Config::Sweep = "/usr/local/sophos/bin/sophoswrapper"; +$Config::PidFile = "/var/run/virus.pid"; +$Config::DefaultConfig = "$prefix/virusscanner.conf"; +$Config::QuarantineAction = 'store'; +$Config::LocalPostmaster = 'postmaster'; +$Config::QuarantineDir = "/var/www/html/dgvirus/quarantine"; +$Config::VirusScanner = 'sophos'; +$Config::LockType = ''; +$Config::SweepTimeout = 300; +$Config::CodeStatus = 'supported'; +$Config::LockDir = '/tmp'; +$Config::SyslogFacility = 'user'; +$Config::MailDomain = 'your.domain.com'; +$Config::MailServer = '127.0.0.1'; +$Config::MailType = 'text/plain'; +$Config::DownloadDir = '/tmp/dgvirus'; +$Config::StatusGroupedBy = 'uname'; + +sub ReadConfig { + my($Config) = @_; + my($key, $value, $whole); + local(*CONF); + + $Config = $Config::DefaultConfig unless defined $Config; + + open(CONF, $Config) + or Log::DieLog("Could not open config file $Config, %s", $!); + while() { + chomp; + s/#.*$//; # Trim comments + s/^\s*//g; # Trim leading space + # Extract everything after the =\s* for inline signatures + /^[^=]*=\s*(.*)$/; + $whole = $1; + s/\s*$//g; # Trim trailing space + next if /^$/; # Ignore comments + +# This was +# ($key, $value) = split(/\s*=\s*/, $_, 2); +# The following should be equivalent but with untainting +# (we trust the admin to put sensible things in the config file). +# -- nwp, 14/01/02 + /^(.*?)\s*=\s*(.*)$/; + ($key,$value) = ($1,$2); + + $key = lc($key); + $key =~ s/[^a-z]//g; # Delete everything except letters + $Config::PidFile = $value if $key =~ /^pid/; + $Config::Sweep = $value if $key =~ /^sweep/; + $Config::LocalPostmaster = $value if $key =~ /^localpostmaster/; + $Config::Debugging = $value if $key =~ /^debug/; + $Config::QuarantineAction = $value if $key =~ /^action/; + $Config::QuarantineDir = $value if $key =~ /^quarantinedir/; + $Config::VirusScanner = lc($value) if $key =~ /^virusscanner$/i; + $Config::LockType = lc($value) if $key =~ /^locktype/i; + $Config::SweepTimeout = $value if $key =~ /^virusscannertimeout/i; + $Config::CodeStatus = $value if $key =~ /^minimumcodestatus/i; + $Config::LockDir = $value if $key =~ /^lockfiledir/i; + $Config::SyslogFacility = $value if $key =~ /^logfacility$/i; + $Config::MailDomain = $value if $key =~ /^maildomain$/i; + $Config::MailServer = $value if $key =~ /^mailserver$/i; + $Config::MailType = $value if $key =~ /^mailtype$/i; + $Config::DownloadDir = $value if $key =~ /^downloaddir$/i; + $Config::StatusGroupedBy = $value if $key =~ /^statusinfogroupedby$/i; + + } + close CONF; + + # Start logging here so we can set the log facility first + Log::Start($Config::MailScannerProcessName, $Config::SyslogFacility); + Log::InfoLog("Virus Scanner $Config::DGVirusVersion in DansGuardian $Config::DGVersion from MailScanner $Config::MailScannerVersion starting."); + + # Sanitise values of QuarantineAction + $Config::QuarantineAction = 'store' + if $Config::QuarantineAction =~ /quar|keep|stor|save/i; + $Config::QuarantineAction = 'delete' + if $Config::QuarantineAction =~ /dele|wipe/i; + + # make sure we have a valid MailType + Log::DieLog("MailType = '$Config::MailType' is invalid! Needs to be 'text/plain' or 'text/html'.") if ($Config::MailType !~ /^(text\/(plain|html))$/); + + # make sure we have a valid MailDomain value + Log::DieLog("MailDomain = '$Config::MailDomain' is invalid! You need to specify your own mail domain!") if ($Config::MailDomain eq "your.domain.com"); + + # make sure we have a valid StatusGroupedBy value + Log::DieLog("Status Info Grouped By = '$Config::StatusGroupedBy' is invalid! Needs to be 'uname' or 'ip'.") if ($Config::StatusGroupedBy !~ /^(uname|ip)$/); + + # Remove any trailing slashes from the directory names, they will cause + # trouble later if left in. + $Config::QuarantineDir =~ s/\/$//; + $Config::LockDir =~ s/\/$//; + $Config::DownloadDir =~ s/\/$//; + + # Check to ensure all the directories and files we need actually exist + DirExists($Config::QuarantineDir) if ($Config::QuarantineAction eq "store"); + DirExists($Config::LockDir); + + # Check that the script at the start of the "Sweep" command exists + my($command); + $command = (split(/[,\s]/, $Config::Sweep, 2))[0]; + if ($command eq "none") + { + DieLog("You must specify a virus engine to work with other than none!"); + } + FileReadable($command); + +} + +# +# Check a configuration file exists and is readable. +# Produce an error if it is not. +# +sub FileReadable { + my($name) = @_; + return 1 unless $name; + return 1 if -r $name; + Log::DieLog("Configuration file $name could not be opened for reading!"); +} + +# Check a directory exists and is writeable. +# Produce an error if it is not. +# +sub DirExists { + my($name) = @_; + return 1 unless $name; + return 1 if -d $name; + Log::DieLog("Directory $name does not exist!"); +} + +# Check two files/directories are on the same partition. +# Produce an error if it is not. +# Can't do this here as it breaks "require" by changing directory. + +# +# Set a Config variable to 0 or 1 depending on its current contents +# +sub ZeroOrOne { + my($current) = @_; + return 1 if $current =~ /yes|true|on|1/i; + return 0; +} + +1; diff -urN --exclude=CVS --exclude=pcx dansguardian-2.7.1-0/configure dansguardian2_7/configure --- dansguardian-2.7.1-0/configure Wed May 14 11:30:22 2003 +++ dansguardian2_7/configure Wed May 14 16:18:50 2003 @@ -3022,6 +3022,51 @@ echo "" >>dansguardian.conf echo "" >>dansguardian.conf +echo "# Antivirus settings" >>dansguardian.conf +echo "" >>dansguardian.conf +echo "# If on, we scan all downloaded content using the virus engines specified " >>dansguardian.conf +echo "# in virusscanner.conf" >>dansguardian.conf +echo "# If off, we don't scan any downloaded content." >>dansguardian.conf +echo "# See http://www.pcxperience.org/dgvirus/ for more details." >>dansguardian.conf +echo "#" >>dansguardian.conf +echo "virusscan = on" >>dansguardian.conf +echo "" >>dansguardian.conf + +# Included by Aecio F. Neto (afn@harvest.com.br) - Harvest Consultoria (http://www.harvest.com.br) +echo "# If off (value = -1), the scanner will send 1 byte per delay period" >>dansguardian.conf +echo "# to the client to keep a download connection alive." >>dansguardian.conf +echo "# When the whole file is downloaded and scanned, the client will receive " >>dansguardian.conf +echo "# all remaining bytes, if the file is clean." >>dansguardian.conf +echo "# Set to a positive integer value to enable immediate delivery to the client. " >>dansguardian.conf +echo "# Value set means minimum number of bytes of the downloaded file " >>dansguardian.conf +echo "# that will be held and delivered after virus scan." >>dansguardian.conf +echo "# If clean, the remaining bytes will be sent to the client." >>dansguardian.conf +echo "# If infected, file downloaded will be incomplete and a warning message " >>dansguardian.conf +echo "# will be sent to the postmaster and possibly the user." >>dansguardian.conf +echo "# Recommended value: 4096 (4 kbytes)" >>dansguardian.conf +echo "#" >>dansguardian.conf +echo "tricklelength = -1" >>dansguardian.conf +echo "" >>dansguardian.conf + +echo "# Delay in seconds to deliver the first byte to the client." >>dansguardian.conf +echo "# This option only applies if tricklelength is set to -1 (off)." >>dansguardian.conf +echo "#" >>dansguardian.conf +echo "firsttrickledelay = 30" >>dansguardian.conf +echo "" >>dansguardian.conf +echo "# Delay in seconds to deliver subsequent bytes to the client." >>dansguardian.conf +echo "# This option only applies if tricklelength is set to -1 (off)." >>dansguardian.conf +echo "#" >>dansguardian.conf +echo "followingtrickledelay = 60" >>dansguardian.conf +echo "" >>dansguardian.conf + +echo "# The following files allow you to define mime types and file extensions" >>dansguardian.conf +echo "# that should not be virus scanned." >>dansguardian.conf +$extendedecho "exceptionvirusmimetypelist = '$prefixdir$sysconfdir\c" >>dansguardian.conf +echo "exceptionvirusmimetypelist'" >>dansguardian.conf +$extendedecho "exceptionvirusextensionlist = '$prefixdir$sysconfdir\c" >>dansguardian.conf +echo "exceptionvirusextensionlist'" >>dansguardian.conf +echo "" >>dansguardian.conf + echo "# Username identification methods (used in logging)" >>dansguardian.conf echo "# You can have as many methods as you want and not just one. The first one" >>dansguardian.conf echo "# will be used then if no username is found, the next will be used." >>dansguardian.conf diff -urN --exclude=CVS --exclude=pcx dansguardian-2.7.1-0/dansguardian.cpp dansguardian2_7/dansguardian.cpp --- dansguardian-2.7.1-0/dansguardian.cpp Wed May 14 11:30:23 2003 +++ dansguardian2_7/dansguardian.cpp Wed May 14 14:32:37 2003 @@ -17,6 +17,8 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// Modified by James A. Pattie (james@pcxperience.com) Xperience, Inc. (www.pcxperience.com) + #include "autoconf/platform.h" #include #include @@ -30,7 +32,6 @@ #include "dansguardian.hpp" #include "FatController.hpp" #include -#include #include #include #include "Socket.hpp" @@ -46,12 +47,20 @@ #ifndef FD_SETSIZE #define FD_SETSIZE 256 #endif +#include +#include +#include +#undef getpwnam +#include +EXTERN_C void xs_init(pTHX); OptionContainer o; bool isDaemonised; +static PerlInterpreter *myPerl; /* The Perl Interpreter */ + int main(int argc, char* argv[]) { isDaemonised = false; std::string configfile = __CONFFILE; @@ -62,6 +71,7 @@ if ((argc > 1) && (*argv[1] == '-')) { switch (argv[1][1]) { case 'P': + std::cout << "AntiVirus Plugin 2.1" << std::endl; return 0; case 'q': return sysv.kill(pidfile); @@ -215,6 +225,34 @@ FatController f; // Thomas The Tank Engine + // setup the perl environment so that we have access to the + // virus scanning routines ported over from MailScanner. + + char *args[] = { NULL }; + if (o.virus_scan) + { + myPerl = perl_alloc(); + perl_construct(myPerl); + + // point to the main perl program that does all our work for us (provides methods and pulls in + // the needed modules, etc.) + + char* tmpString; + tmpString = new char[strlen(CONFFILEDIR)+strlen("virusscanner")+1]; + strcpy(tmpString, CONFFILEDIR); + strcat(tmpString, "virusscanner"); + argv[1] = tmpString; + + argc = 2; + perl_parse(myPerl, xs_init, argc, argv, (char **)NULL); + delete[] tmpString; // can now get rid of the temp string. + + // uncomment if using perl 5.8.0 or later. Will not work on 5.6.1. + // PL_exit_flags |= PERL_EXIT_DESTRUCT_END; + + // setup the virusscanner + perl_call_argv("setup", G_DISCARD | G_NOARGS, args); + } // *** Uncomment this section if you want to do benchmarking *** @@ -289,6 +327,15 @@ continue; } + if (o.virus_scan) + { + // cleanup the Perl Interperter. + perl_call_argv("shutdown", G_DISCARD | G_NOARGS, args); + + perl_destruct(myPerl); + perl_free(myPerl); + } + if (rc > 0) { if (!isDaemonised) { std::cerr << "Exiting with error" << std::endl; diff -urN --exclude=CVS --exclude=pcx dansguardian-2.7.1-0/exceptionvirusextensionlist dansguardian2_7/exceptionvirusextensionlist --- dansguardian-2.7.1-0/exceptionvirusextensionlist Wed Dec 31 18:00:00 1969 +++ dansguardian2_7/exceptionvirusextensionlist Wed May 14 14:32:37 2003 @@ -0,0 +1,28 @@ +#Exception Virus extension list +# The Virus scanning code will ignore files with these extensions. + +# File extensions with executable code + + +# Files which one normally things as non-executable but +# can contain harmful macros and viruses + + +# Other files which may contain files with executable code + + +# Time/bandwidth wasting files + +.mp3 # Music file +.mpeg # Movie file +.mpg # Movie file +.avi # Movie file +.ra # Real Audio +.ram # " +.rm # " + +# Image files not to scan +.gif +.jpg +.jpeg +.png diff -urN --exclude=CVS --exclude=pcx dansguardian-2.7.1-0/exceptionvirusmimetypelist dansguardian2_7/exceptionvirusmimetypelist --- dansguardian-2.7.1-0/exceptionvirusmimetypelist Wed Dec 31 18:00:00 1969 +++ dansguardian2_7/exceptionvirusmimetypelist Wed May 14 14:32:37 2003 @@ -0,0 +1,21 @@ +# MIME types the virus scanning code ignores. + +audio/mpeg +audio/x-mpeg +audio/x-pn-realaudio +audio/x-wav +audio/x-realaudio +audio/x-pn-realaudio +audio/vnd.rn-realaudio +video/mpeg +video/x-mpeg2 +video/acorn-replay +video/quicktime +video/x-msvideo +video/msvideo +video/vnd.rn-realvideo + +image/png +image/gif +image/jpeg +image/tiff diff -urN --exclude=CVS --exclude=pcx dansguardian-2.7.1-0/logger.pl dansguardian2_7/logger.pl --- dansguardian-2.7.1-0/logger.pl Wed Dec 31 18:00:00 1969 +++ dansguardian2_7/logger.pl Wed May 14 14:32:37 2003 @@ -0,0 +1,89 @@ +# MailScanner - SMTP E-Mail Virus Scanner +# Copyright (C) 2002 Julian Field +# +# $Id: logger.pl,v 1.1 2002/12/02 21:18:32 james Exp $ +# +# 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 +# +# The author, Julian Field, can be contacted by email at +# Jules@JulianField.net +# or by paper mail at +# Julian Field +# Dept of Electronics & Computer Science +# University of Southampton +# Southampton +# SO17 1BJ +# United Kingdom +# + +# 07/23/2002 +# James A. Pattie (james@pcxperience.com) - Updated to work in +# the DansGuardian project. + +########################################################### +# Syslog library calls +########################################################### + +use strict; +use Sys::Syslog; + +package Log; + +sub Start { + my($name, $facility) = @_; + # Do this in an eval so it can fail quietly if setlogsock + # is not supported in the installed version of Sys::Syslog + eval { Sys::Syslog::setlogsock('unix'); }; # Doesn't need syslogd -r + Sys::Syslog::openlog($name, 'pid, nowait', $facility); +} + +sub Stop { + Sys::Syslog::closelog(); +} + +sub DieLog { + # Bug fix here thanks to Nick Phillips + # (closelog changes $! in @_) + my(@x) = @_; + + my $logmessage = sprintf shift @x, @x; + + foreach(split /\n/,$logmessage) { + Sys::Syslog::syslog('err', $_); + } + + Sys::Syslog::closelog(); + die $logmessage; +} + +sub WarnLog { + my(@x) = @_; + Sys::Syslog::syslog('warning', @_); + #warn sprintf shift @x, @x; +} + +sub InfoLog { + my(@x) = @_; + Sys::Syslog::syslog('info', @_); + #print "@x\n"; +} + +sub DebugLog { + my(@x) = @_; + Sys::Syslog::syslog('debug', @_) if $Config::Debugging; + #print "@x\n"; +} + +1; diff -urN --exclude=CVS --exclude=pcx dansguardian-2.7.1-0/perlxsi.c dansguardian2_7/perlxsi.c --- dansguardian-2.7.1-0/perlxsi.c Wed Dec 31 18:00:00 1969 +++ dansguardian2_7/perlxsi.c Wed May 14 14:32:37 2003 @@ -0,0 +1,16 @@ +#include +#include + +EXTERN_C void xs_init (pTHX); + +EXTERN_C void boot_DynaLoader (pTHX_ CV* cv); + +EXTERN_C void +xs_init(pTHX) +{ + char *file = __FILE__; + dXSUB_SYS; + + /* DynaLoader is a special case */ + newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, file); +} diff -urN --exclude=CVS --exclude=pcx dansguardian-2.7.1-0/sweep.pl dansguardian2_7/sweep.pl --- dansguardian-2.7.1-0/sweep.pl Wed Dec 31 18:00:00 1969 +++ dansguardian2_7/sweep.pl Wed May 14 14:32:37 2003 @@ -0,0 +1,1509 @@ +# MailScanner - SMTP E-Mail Virus Scanner +# Copyright (C) 2002 Julian Field +# +# $Id: sweep.pl,v 1.7 2003/04/04 19:23:29 james Exp $ +# +# 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 +# +# The author, Julian Field, can be contacted by email at +# Jules@JulianField.net +# or by paper mail at +# Julian Field +# Dept of Electronics & Computer Science +# University of Southampton +# Southampton +# SO17 1BJ +# United Kingdom +# + +# 07/23/2002 +# Updated by James A. Pattie (james@pcxperience.com) for inclusion in +# DansGuardian as the virus checking modules for scanning a downloaded +# file. + +# Works with a specific file only (not a directory of files). +# Parse the output and find all the viruses. +# Passed in a file name.. +# Return a array. index 0 is the bool of clean=0/infected=1, index 1 contains +# the report of any found viruses. + +use strict; +use POSIX qw(:signal_h setsid); # For Solaris 9 SIG bug workaround +package Sweep; + +my($LOCK_SH) = 1; +my($LOCK_EX) = 2; +my($LOCK_NB) = 4; +my($LOCK_UN) = 8; + +# Sophos SAVI Library object and ide directory modification time +my($SAVI, $SAVIidedirmtime, $SAVIlibdirmtime, $SAVIinuse); +$SAVIidedirmtime = 0; +$SAVIlibdirmtime = 0; +$SAVIinuse = 0; + +# +# Virus scanner definitions table +# +my ( + $S_NONE, # Not present + $S_UNSUPPORTED, # Present but you're on your own + $S_ALPHA, # Present but not tested -- we hope it works! + $S_BETA, # Present and tested to some degree -- we think it works! + $S_SUPPORTED, # People use this; it'd better work! + ) = (0,1,2,3,4); + +my %Scanners = ( + sophos => { + Lock => 'SophosBusy.lock', + # In next line, '-ss' makes it work nice and quietly + CommonOptions => '-sc -f -all -rec -ss -archive -loopback --no-follow-symlinks --no-reset-atime -TNEF', + DisinfectOptions => '-di', + ScanOptions => '', + InitParser => \&InitSophosParser, + ProcessOutput => \&ProcessSophosOutput, + SupportScanning => $S_SUPPORTED, + SupportDisinfect => $S_SUPPORTED, + }, + mcafee => { + Lock => 'McAfeeBusy.lock', + CommonOptions => '--recursive --ignore-links --analyze --mime --secure --noboot', + DisinfectOptions => '--clean', + ScanOptions => '', + InitParser => \&InitMcAfeeParser, + ProcessOutput => \&ProcessMcAfeeOutput, + SupportScanning => $S_SUPPORTED, + SupportDisinfect => $S_SUPPORTED, + }, + command => { + Lock => 'CommandBusy.lock', + CommonOptions => '-packed -archive', + DisinfectOptions => '-disinf', + ScanOptions => '', + InitParser => \&InitCommandParser, + ProcessOutput => \&ProcessCommandOutput, + SupportScanning => $S_SUPPORTED, + SupportDisinfect => $S_SUPPORTED, + }, + inoculate => { + Lock => 'InoculateBusy.lock', + CommonOptions => '-nex -arc -mod reviewer -spm h ', + DisinfectOptions => '-act cure -sca mf', + ScanOptions => '', + InitParser => \&InitInoculateParser, + ProcessOutput => \&ProcessInoculateOutput, + SupportScanning => $S_SUPPORTED, + SupportDisinfect => $S_SUPPORTED, + }, + inoculan => { + Lock => 'InoculanBusy.lock', + CommonOptions => '-nex -rev ', + DisinfectOptions => '-nex -cur', + ScanOptions => '', + InitParser => \&InitInoculanParser, + ProcessOutput => \&ProcessInoculanOutput, + SupportScanning => $S_SUPPORTED, + SupportDisinfect => $S_SUPPORTED, + }, + kaspersky => { + Lock => 'KasperskyBusy.lock', + CommonOptions => '', + DisinfectOptions => '-- -I2', + ScanOptions => '-I0', + InitParser => \&InitKasperskyParser, + ProcessOutput => \&ProcessKasperskyOutput, + SupportScanning => $S_BETA, + SupportDisinfect => $S_BETA, + }, + kavdaemonclient => { + Lock => 'KavDaemonClientBusy.lock', + CommonOptions => '', + DisinfectOptions => '-- -I2', + ScanOptions => '', + InitParser => \&InitKavDaemonClientParser, + ProcessOutput => \&ProcessKavDaemonClientOutput, + SupportScanning => $S_BETA, + SupportDisinfect => $S_NONE, + }, + "f-secure" => { + Lock => 'FSecureBusy.lock', + CommonOptions => '--dumb --archive', + DisinfectOptions => '--auto --disinf', + ScanOptions => '', + InitParser => \&InitFSecureParser, + ProcessOutput => \&ProcessFSecureOutput, + SupportScanning => $S_SUPPORTED, + SupportDisinfect => $S_SUPPORTED, + }, + "f-prot" => { + Lock => 'FProtBusy.lock', + CommonOptions => '-old -archive -dumb', + DisinfectOptions => '-disinf -auto', + ScanOptions => '', + InitParser => \&InitFProtParser, + ProcessOutput => \&ProcessFProtOutput, + SupportScanning => $S_SUPPORTED, + SupportDisinfect => $S_SUPPORTED, + }, + nod32 => { + Lock => 'Nod32Busy.lock', + CommonOptions => '-log-', + DisinfectOptions => '-clean -delete', + ScanOptions => '', + InitParser => \&InitNOD32Parser, + ProcessOutput => \&ProcessNOD32Output, + SupportScanning => $S_SUPPORTED, + SupportDisinfect => $S_SUPPORTED, + }, + "nod32-1.99" => { + Name => 'Nod32', + Lock => 'Nod32Busy.lock', + CommonOptions => '--arch --all --log-brief', + DisinfectOptions => '--action clean --action-uncl none', + ScanOptions => '', + InitParser => \&InitNOD32Parser, + ProcessOutput => \&ProcessNOD32Output, + SupportScanning => $S_SUPPORTED, + SupportDisinfect => $S_SUPPORTED, + }, + "antivir" => { + Lock => 'AntiVirBusy.lock', + CommonOptions => '-allfiles -s -noboot -rs -z', + DisinfectOptions => '-e -ren', + ScanOptions => '', + InitParser => \&InitAntiVirParser, + ProcessOutput => \&ProcessAntiVirOutput, + SupportScanning => $S_SUPPORTED, + SupportDisinfect => $S_SUPPORTED, + }, + "panda" => { + Lock => 'PandaBusy.lock', + CommonOptions => '-AEX -CMP -AUT -NSO -ESP', + DisinfectOptions => '-CLV', + ScanOptions => '-HEU', + InitParser => \&InitPandaParser, + ProcessOutput => \&ProcessPandaOutput, + SupportScanning => $S_UNSUPPORTED, + SupportDisinfect => $S_UNSUPPORTED, + }, + "rav" => { + Lock => 'RavBusy.lock', + CommonOptions => '--all --mail --archive', + DisinfectOptions => '--clean', + ScanOptions => '', + InitParser => \&InitRavParser, + ProcessOutput => \&ProcessRavOutput, + SupportScanning => $S_SUPPORTED, + SupportDisinfect => $S_SUPPORTED, + }, + "clamav" => { + Lock => 'ClamAVBusy.lock', + CommonOptions => '-r --disable-summary --stdout', + DisinfectOptions => '', + ScanOptions => '', + InitParser => \&InitClamAVParser, + ProcessOutput => \&ProcessClamAVOutput, + SupportScanning => $S_SUPPORTED, + SupportDisinfect => $S_NONE, + }, + "trend" => { + Lock => 'TrendBusy.lock', + CommonOptions => '-a -za -r', + DisinfectOptions => '-c', + ScanOptions => '', + InitParser => \&InitTrendParser, + ProcessOutput => \&ProcessTrendOutput, + SupportScanning => $S_UNSUPPORTED, + SupportDisinfect => $S_UNSUPPORTED, + }, + "none" => { + Lock => 'NoneBusy.lock', + CommonOptions => '', + DisinfectOptions => '', + ScanOptions => '', + InitParser => \&NeverHappens, + ProcessOutput => \&NeverHappens, + SupportScanning => $S_NONE, + SupportDisinfect => $S_NONE, + }, +); + +sub VirusScan { + my($fileToScan, $InfectionTypes) = @_; + + my(%infections, $NumInfections); + + $NumInfections = CallCommercialChecking($fileToScan, \%infections, $InfectionTypes); + + Log::InfoLog("Found $NumInfections viruses in file '$fileToScan'") if $NumInfections; + + return \%infections; +} + +sub CallCommercialChecking { + my($fileToScan, $infections, $inftypes) = @_; + + #local(*BASEDIR); + my($NumInfections, $success, $id); + + if (! -e $fileToScan) { die "$fileToScan does not exist, $!"; } + + $success = TryCommercial($fileToScan, $infections, $inftypes, \$NumInfections); + unless ($success) { + # Virus checking the file timed out + Log::WarnLog("Denial Of Service attack detected in file '$fileToScan'!"); + $infections->{"$fileToScan"}{""} = "Denial Of Service attack in file!"; + $inftypes->{"$fileToScan"}{""} .= "d"; + } + + # Return value is the number of infections we found + return $NumInfections; +} + +# Try all the installed commercial virus scanners +sub TryCommercial { + my($fileToScan, $infections, $inftypes, $rCounter) = @_; + + my($number, @scanners, @commands, $result); + + $Config::VirusScanner =~ s/^[\s,]*//; # Remove any leading or trailing + $Config::VirusScanner =~ s/[\s,]*$//; # list separators + $Config::Sweep =~ s/^[\s,]*//; # Remove any leading or trailing + $Config::Sweep =~ s/[\s,]*$//; # list separators + + @scanners = split(/[\s,]+/, $Config::VirusScanner); + @commands = split(/[\s,]+/, $Config::Sweep); + $result = 0; + + foreach $number (0..$#scanners) { + $result += TryOneCommercial($scanners[$number], $commands[$number], $fileToScan, + $infections, $inftypes, $rCounter); + } + + return $result; +} + +# Try one of the commercial virus scanners +sub TryOneCommercial { + my($scanner, $sweepcommand, $fileToScan, $infections, $inftypes, $rCounter) = @_; + + local(*CHECKER, *LOCK, *KID); + my ($rScanner, $VirusLock, $voptions); + my($Counter, $TimedOut, $PipeReturn, $pid); + + Log::DieLog("Never heard of scanner '$scanner'!") + unless exists $Scanners{$scanner}; + + $rScanner = $Scanners{$scanner}; + + if ($rScanner->{"SupportScanning"} == $S_NONE){ + Log::DebugLog("Scanning using scanner \"$scanner\" not supported; not scanning"); + return 1; + } + + CheckCodeStatus($rScanner->{"SupportScanning"}) + or Log::DieLog("Bad return code from CheckCodeStatus - should it have quit?"); + + $VirusLock = $Config::LockDir . "/" . $rScanner->{"Lock"}; # lock file + $voptions = $rScanner->{"CommonOptions"}; # Set common command line options + $voptions .= " " . $rScanner->{"ScanOptions"}; # Add command line options to "scan only" + &{$$rScanner{"InitParser"}}(); # Initialise scanner-specific parser + + + # Check that the virus checker files aren't currently being updated, + # and wait if they are. + + # JAP 07/25/2002 - commenting out since I want to be able to scan multiple files simultaneously + # that are not able to wait for others to get done scanning. Will try to find a solution in the + # future. Also, there was a DOS possibility if you were running MailScanner and DansGuardian on the + # same machine, using the same virus programs and trying to scan files at the same time. + + #open(LOCK, ">$VirusLock") + # or Log::DieLog("Cannot create $VirusLock, $!"); + #flock(LOCK, $LOCK_SH); + #print LOCK "Virus checker locked for scanning by $scanner $$\n"; + + Log::DebugLog("Commencing scanning by $scanner..."); + + $TimedOut = 0; + eval { + die "Can't fork: $!" unless defined($pid = open(KID, "-|")); + if ($pid) { + # In the parent + local $SIG{ALRM} = sub { $TimedOut = 1; die "Command Timed Out" }; + alarm $Config::SweepTimeout; + while() { + $Counter += &{$$rScanner{"ProcessOutput"}}($_, $infections, $inftypes, $fileToScan); + } + close KID; + $PipeReturn = $?; + $pid = 0; # 2.54 + alarm 0; + } else { + # In the child + POSIX::setsid(); + exec "$sweepcommand $voptions $fileToScan" + or die "Can't run commercial checker $scanner (\"$sweepcommand\"): $!"; + } + }; + alarm 0; # 2.53 + + # Note to self: I only close the KID in the parent, not in the child. + Log::DebugLog("Completed scanning by $scanner"); + + # Catch failures other than the alarm + Log::DieLog("Commercial virus checker failed with real error: %s", $@) + if $@ and $@ !~ /Command Timed Out/; + + #print STDERR "pid = $pid and \@ = $@\n"; + + # In which case any failures must be the alarm + if ($@ or $pid>0) { + # Kill the running child process + my($i); + kill -15, $pid; + # Wait for up to 5 seconds for it to die + for ($i=0; $i<5; $i++) { + sleep 1; + ($pid=0),last unless kill(0, $pid); + kill -15, $pid; + } + # And if it didn't respond to 11 nice kills, we kill -9 it + kill -9, $pid if $pid; + wait; # 2.53 + } + + #flock(LOCK, $LOCK_UN); + #close LOCK; + $$rCounter = $Counter; # Set up output value + + # Return failure if the command timed out, otherwise return success + Log::WarnLog("Commercial scanner $scanner timed out!") if $TimedOut; + return 0 if $TimedOut; + return 1; +} + +# Initialise any state variables the Sophos output parser uses +sub InitSophosParser { + ; +} + +# Initialise any state variables the McAfee output parser uses +my($currentline); +sub InitMcAfeeParser { + $currentline = ''; +} + +# Initialise any state variables the Command (CSAV) output parser uses +sub InitCommandParser { + ; +} + +# Initialise any state variables the Inoculate-IT output parser uses +sub InitInoculateParser { + ; +} + +# Initialise any state variables the Inoculan 4.x output parser uses +sub InitInoculanParser { + ; +} + +# Initialise any state variables the Kaspersky output parser uses +my ($kaspersky_CurrentObject); +sub InitKasperskyParser { + $kaspersky_CurrentObject = ""; +} + +# Initialise any state variables the Kaspersky Daemon Client output parser uses +sub InitKavDaemonClientParser { + ; +} + +# Initialise any state variables the F-Secure output parser uses +my ($fsecure_InHeader, $fsecure_Version, %fsecure_Seen); +sub InitFSecureParser { + $fsecure_InHeader=(-1); + $fsecure_Version = 0; + %fsecure_Seen = (); +} + +# Initialise any state variables the F-Prot output parser uses +my ($fprot_InCruft); +sub InitFProtParser { + $fprot_InCruft=(-3); +} + +# Initialise any state variables the Nod32 output parser uses +my ($NOD32Version, $NOD32InHeading); +sub InitNOD32Parser { + $NOD32Version = undef; + $NOD32InHeading = 1; +} + +# Initialise any state variables the AntiVir output parser uses +sub InitAntiVirParser { + ; +} + +# Initialise any state variables the Panda output parser uses +sub InitPandaParser { + ; +} + +# Initialise any state variables the RAV output parser uses +sub InitRavParser { + ; +} + +# Initialise any state variables the ClamAV output parser uses +my ($clamav_archive); +sub InitClamAVParser { + $clamav_archive = ""; +} + +# Initialise any state variables the Vscan output parser uses +my ($trend_prevline); +sub InitTrendParser { + $trend_prevline = ""; +} + + +# These functions must be called with, in order: +# * The line of output from the scanner +# * A reference to the hash containing problem details +# * A reference to the hash containing types of problem +# * The file to scan. +# +# These functions must return with: +# * return code 0 if no problem, 1 if problem. +# * type of problem (currently only "v" for virus) +# appended to $types{filename}{""} +# * problem report from scanner appended to +# $infections{filename}{""} -- don't +# forget the terminating newline. +# +# If the scanner may refer to the same file multiple times, +# you should consider appending to the $infections rather +# than just setting it, I guess. +# + +sub ProcessSophosOutput { + my($line, $infections, $types, $fileToScan) = @_; + my($report, $infected, $dot, $id, $part, @rest); + my($logout); + + #print "$line"; + chomp $line; + $logout = $line; + $logout =~ s/%/%%/g; + Log::InfoLog($logout) if $line =~ /error/i; + # JKF Improved to handle multi-part split archives, + # JKF which Sophos whinges about + return 0 unless $line =~ /(virus.*found)|(could not check)/i; + Log::InfoLog($logout); + $report = $line; + $infected = $line; + $infected =~ s/^.*found\s*in\s*file\s*//i; + # Catch the extra stuff on the end of the line as well as the start + $infected =~ s/^Could not check\s*(.+) \(([^)]+)\)$/$1/i; + my $error = $2; + + # James: need to port over the sophosallowederrors option. + + #$infected =~ s/^Could not check\s*//i; + # JKF 10/08/2000 Used to split into max 3 parts, but this doesn't handle + # viruses in zip files in attachments. Now pull out first 3 parts instead. + ($dot, $id, $part, @rest) = split(/\//, $infected); + $infections->{"$fileToScan"}{""} .= $report . "\n"; + $types->{"$fileToScan"}{""} .= "v"; # it's a real virus + return 1; +} + +sub ProcessMcAfeeOutput { + my($line, $infections, $types, $fileToScan) = @_; + + my($lastline, $report, $dot, $id, $part, @rest); + my($logout); + + chomp $line; + $lastline = $currentline; + $currentline = $line; + + Log::InfoLog("McAfee said \"$line\""); + + # SEP: need to add code to log warnings + return 0 unless $line =~ /Found/; + + # McAfee prints the whole path as opposed to + # ./messages/part so make it the same + # JAP 07/23/2002 - not sure if we need this? + #$lastline =~ s/$fileToScan//; + + # make an equivalent report line from the last 2 + $report = "$lastline$currentline"; + $logout = $report; + $logout =~ s/%/%%/g; + Log::InfoLog($logout); + # note: '$dot' does not become '.' + ($dot, $id, $part, @rest) = split(/\//, $lastline); + $infections->{"$fileToScan"}{""} .= $report . "\n"; + $types->{"$fileToScan"}{""} .= "v"; + return 1; +} + +# This next function originally contributed in its entirety by +# "Richard Brookhuis" +# +sub ProcessCommandOutput { + my($line, $infections, $types, $fileToScan) = @_; + #my($line) = @_; + + my($report, $infected, $dot, $id, $part, @rest); + my($logout); + + #print "$line"; + chomp $line; + $logout = $line; + $logout =~ s/%/%%/g; + Log::InfoLog($logout) if $line =~ /error/i; + if ($line =~ /(is|could be) a (security risk|virus construction|joke program)/) { + # Reparse the rest of the line to turn it into an infection report + $line =~ s/(is|could be) a (security risk|virus construction|joke program).*$/Infection: /; + } + + return 0 unless $line =~ /Infection:/i; + Log::InfoLog($logout); + $report = $line; + $infected = $line; + $infected =~ s/\s+Infection:.*$//i; + # JKF 10/08/2000 Used to split into max 3 parts, but this doesn't handle + # viruses in zip files in attachments. Now pull out first 3 parts instead. + $line =~ s/-\>/\//; # JKF Handle archives rather better + ($dot, $id, $part, @rest) = split(/\//, $infected); + $infections->{"$fileToScan"}{""} .= $report . "\n"; + $types->{"$fileToScan"}{""} .= "v"; # it's a real virus + #print "ID: $id PART: $part REPORT: $report\n"; + return 1; +} + +# This next function contributed in its entirety by +# +# +sub ProcessInoculateOutput { + my($line, $infections, $types, $fileToScan) = @_; + my($report, $infected, $dot, $id, $part, @rest); + my($logout); + + #print "$line"; + + chomp $line; + $logout = $line; + $logout =~ s/%/%%/g; + Log::WarnLog($logout) if $line =~ /error/i; + #Log::WarnLog($logout) if $line =~ /Error/i; + return 0 unless $line =~ /is infected by virus:/i; + Log::InfoLog($logout); + + # Ino prints the whole path as opposed to + # ./messages/part so make it the same + # Scott Farrell's system definitely requires the extra / + # Output looks like this: + # File: /var/spool/MailScanner/incoming/./message-id/filename + # JAP 07/23/2002 - not sure if we need this? + #$line =~ s/$fileToScan\///; + + # ino uses instead of /files.ext/ in archives + $line =~ s//\//; + + $report = $line; + $infected = $line; +# $infected =~ s/^.*found\s*in\s*file\s*//i; + # Next 2 lines added on advice from . + $infected =~ s/File //; + $infected =~ s/ is infected by virus:.*//; + # JKF 10/08/2000 Used to split into max 3 parts, but this doesn't handle + # viruses in zip files in attachments. Now pull out first 3 parts instead. + ($dot, $id, $part, @rest) = split(/\//, $infected); + $infections->{"$fileToScan"}{""} .= $report . "\n"; + $types->{"$fileToScan"}{""} .= "v"; # so we know what to tell sender + return 1; +} + +# Inoculan 4.x parser, contributed in its entirety by +# +# Comment from : +# This next function is the modified version of sfarrell@icconsulting.com.au's +# inoculateit 6.0 section by gabor.funk@hunetkft.hu - 2002.03.01 - v1.0 +# It works with Inoculan 4.x inocucmd which is a beta/test/unsupported version +# Can be downloaded from: ftp://ftp.ca.com/getbbs/linux.eng/inoctar.LINUX.Z +# This package is rarely modified but you can download virsig.dat from other +# 4.x package such as the NetWare package (smallest and non MS compressed) +# It can be found at: ftp://ftp.ca.com/pub/InocuLAN/il0156.zip +# wget it; unzip il0156.zip VIRSIG.DAT; mv VIRSIG.DAT virsig.dat +# and since the last engine was "corrected" not to accept newer signature +# files, you have to patch the major version number to the same or below as +# the one which come with the inoctar.LINUX.Z (currently 34.19) otherwise +# it would refuse to run and misleadingly report the following: +# "Error during Initialization. Please check configuration." +# In virsig.dat the major version number is located at address 10h, for +# virsig.dat version 35.15 this would be 35h. You simply have to change it +# to 34h and it should work. Note: using a higher version virsig.dat with a +# lower version engine is highly discouraged by CA and can result not to +# recognize newer viruses. Automatic procedure for this: Get bview (bvi) from +# http://bvi.sourceforge.net, create a file called "patch" containing the +# following: "16 c h[LF]34[LF].[LF]w[LF]q[LF]" where [LF] means linefeed +# and of course without the quotes. Run "bvi -f patch virsig.dat" to change +# major version number automatically to 34 in virsig.dat. +# inocucmd needs libstdc++-libc6.1-1.so.2 so you need to link it to your +# closest one (it was libstdc++-3-libc6.2-2-2.10.0.so on my debian testing). +# location: inocucmd and virsig.dat (the two required files) should be at +# /opt/CA, /usr/local/bin or other location specified in $CAIGLBL0000 +# test: inocucmd . (inocucmd without argument can report bogus virsig.dat +# version number but it's ok if it scans the file with no error) +# I like inocucmd because it needs 2 file alltogether, requires no building +# and/or "installation" so is very ideal for testing. +# +# [text updated and expanded at 2002. April 22.] + +sub ProcessInoculanOutput { + my($line, $infections, $types, $fileToScan) = @_; + my($report, $infected, $dot, $id, $part, @rest); + my($logout); + + chomp $line; + $logout = $line; + $logout =~ s/%/%%/g; + Log::WarnLog($logout) if $line =~ /error/i; + #Log::WarnLog($line) if $line =~ /Error/i; + return 0 unless $line =~ /was infected by virus/i; + Log::InfoLog($logout); + + # Sample outputs for an unpacked and a packed virus + # "[././cih-sfl.exe] was infected by virus [Win95/CIH.1003]" + # "[././w95.arj:SLIDER10.EXE] was infected by virus [Win95/Slider 1.0.Trojan]" + + $report = $line; + $infected = $line; + $infected =~ s/^\[\.\///i; + $infected =~ s/([:\]]).*//i; + + ($dot, $id, $part, @rest) = split(/\//, $infected); + $infections->{"$fileToScan"}{""} .= $report . "\n"; + $types->{"$fileToScan"}{""} .= "v"; # so we know what to tell sender + return 1; +} + +# If you use Kaspersky, look at this code carefully +# and then be very grateful you didn't have to write it. +# Note that Kaspersky will now change long paths so they have "..." +# in the middle of them, removing the middle of the path. +# *WHY* do people have to do dumb things like this? +# +sub ProcessKasperskyOutput { + my($line, $infections, $types, $fileToScan) = @_; + #my($line) = @_; + + my($report, $infected, $dot, $id, $part, @rest); + my($logout); + + # Don't know what kaspersky means by "object" yet... + + # Lose trailing cruft + return 0 unless defined $kaspersky_CurrentObject; + + if ($line =~ /^Current\sobject:\s(.*)$/) { + $kaspersky_CurrentObject = $1; + } + elsif ($kaspersky_CurrentObject eq "") { + # Lose leading cruft + return 0; + } + else { + chomp $line; + $line =~ s/^\r//; + # We can rely on fileToScan not having trailing slash. + # Prefer s/// to m// as less likely to do unpredictable things. + if ($line =~ / infected: /) { + $line =~ s/.* \.\.\. (.*)/\.$1/; # Kav will now put ... in long paths + $report = $line; + $logout = $line; + $logout =~ s/%/%%/g; + Log::InfoLog($logout); + # JAP 07/23/2002 - not sure if we need this? + #$line =~ s/^$fileToScan//; + $line =~ s/(.*) infected:.*/\.$1/; # To handle long paths again + ($dot,$id,$part,@rest) = split(/\//, $line); + $infections->{"$fileToScan"}{""} .= $report . "\n"; + $types->{"$fileToScan"}{""} .= "v"; # so we know what to tell sender + return 1; + } + # see commented code below if you think this regexp looks fishy + if ($line =~ /^([\r ]*)Scan\sprocess\scompleted\.\s*$/) { + undef $kaspersky_CurrentObject; + # uncomment this to see just one reason why I hate kaspersky AVP -- nwp + # foreach(split //, $1) { + # print ord($_) . "\n"; + # } + } + } + return 0; +} + +# It uses AvpDaemonClient from /opt/AVP/DaemonClients/Sample +# or AvpTeamDream from /opt/AVP/DaemonClients/Sample2. +# This was contributed in its entirety by +# Nerijus Baliunas . +# +sub ProcessKavDaemonClientOutput { + my($line, $infections, $types, $fileToScan) = @_; + #my($line) = @_; + + my($report, $infected, $dot, $id, $part, @rest); + my($logout); + + chomp $line; + $line =~ s/^\r//; + # We can rely on fileToScan not having trailing slash. + # Prefer s/// to m// as less likely to do unpredictable things. + if ($line =~ /infected: /) { + $line =~ s/.* \.\.\. (.*)/\.$1/; # Kav will now put ... in long paths + $report = $line; + $logout = $line; + $logout =~ s/%/%%/g; + Log::InfoLog($logout); + # JAP 07/23/2002 - not sure if we need this? + #$line =~ s/^$fileToScan//; + $line =~ s/(.*) infected:.*/\.$1/; # To handle long paths again + ($dot,$id,$part,@rest) = split(/\//, $line); + $infections->{"$fileToScan"}{""} .= $report . "\n"; + $types->{"$fileToScan"}{""} .= "v"; # so we know what to tell sender + return 1; + } + return 0; +} + +# Sample output from version 4.50 of F-Secure: +# [./eicar2/eicar.zip] eicar.com: Infected: EICAR-Test-File [AVP] +# ./eicar2/eicar.co: Infected: EICAR_Test_File [F-Prot] +sub ProcessFSecureOutput { + my($line, $infections, $types, $fileToScan) = @_; + #my($line) = @_; + + my($report, $infected, $dot, $id, $part, @rest); + my($logout, $virus, $BeenSeen); + + chomp $line; + print STDERR "$line\n"; + print STDERR "InHeader $fsecure_InHeader\n"; + #system("echo -n '$line' | od -c"); + + # Lose header + if ($fsecure_InHeader < 0 && $line =~ /version ([\d.]+)/i) { + $fsecure_Version = $1 + 0.0; + #MailScanner::Log::InfoLog("Found F-Secure version $1=$fsecure_Version\n"); + return 0; + } + if ($line eq "") { + $fsecure_InHeader++; + return 0; + } + $fsecure_InHeader == 0 or return 0; + + $report = $line; + $logout = $line; + $logout =~ s/%/%%/g; + Log::InfoLog($logout); + + # If we are running the new version then there's a totally new parser here + if ($fsecure_Version >= 4.50) { + + #./g4UFLJR23090/Keld Jrn Simonsen: Infected: EICAR_Test_File [F-Prot] + #./g4UFLJR23090/Keld Jrn Simonsen: Infected: EICAR-Test-File [AVP] + #./g4UFLJR23090/cokegift.exe: Infected: is a joke program [F-Prot] + + return 0 unless $line =~ /: Infected: /; + # The last 3 words are "Infected:" + name of virus + name of scanner + $line =~ s/: Infected: +(.+) \[.*?\]$//; + #print STDERR "Line is \"$line\"\n"; + Log::InfoLog("Virus Scanning: F-Secure found virus %s", $1); + $virus = $1; + # We are now left with the filename, or + # then archive name followed by the filename within the archive. + $line =~ s/^\[(.*?)\] .*$/$1/; # Strip signs of an archive + + # We now just have the filename + ($dot,$id,$part,@rest) = split(/\//, $line); + $infections->{$fileToScan}{""} .= $virus . "\n"; + $types->{$fileToScan}{""} .= "v"; # so we know what to tell sender + # Only report results once for each file + return 0 if $fsecure_Seen{$line}; + $fsecure_Seen{$line} = 1; + return 1; + } else { + # We are running the old version, so use the old parser + # Prefer s/// to m// as less likely to do unpredictable things. + # We hope. + if ($line =~ /\tinfection:\s/) { + # Get to relevant filename in a reasonably but not + # totally robust manner (*impossible* to be totally robust + # if we have square brackets and spaces in filenames) + # Strip archive bits if present + $line =~ s/^\[(.*?)\] .+(\tinfection:.*)/$1$2/; + + # Get to the meat or die trying... + $line =~ s/\tinfection:([^:]*).*$// + or Log::DieLog("Dodgy things going on in F-Secure output:\n$report\n"); + $virus = $1; + $virus =~ s/^\s*(\S+).*$/$1/; # 1st word after Infection: is the virus + Log::InfoLog("Virus Scanning: F-Secure found virus %s",$virus); + + $infections->{"$fileToScan"}{""} .= $virus . "\n"; + $types->{"$fileToScan"}{""} .= "v"; # so we know what to tell sender + return 1; + } + Log::DieLog("Either you've found a bug in MailScanner's F-Secure output parser, or F-Secure's output format has changed! Please mail the author of MailScanner!\n"); + } +} + +sub ProcessFProtOutput { + my($line, $infections, $types, $fileToScan) = @_; + #my($line) = @_; + + my($report, $infected, $dot, $id, $part, $virus, @rest); + my($logout); + + #print STDERR $line; + + chomp $line; + + # Look for the "Program version: 4...." line which shows we are running + # version 4 and therefore have different headers at the start of the + # scan output. + if ($fprot_InCruft==-2) { + my $version = $1 if $line =~ /program\s+version:\s*([\d.]+)/i; + if ($version > 3.12) { + $fprot_InCruft -= 1; + return 0; + } + } + return 0 if $fprot_InCruft > 0; # Return if we are still in headers + # One header paragraph has finished, count it + if ($line eq "") { + $fprot_InCruft += 1; + return 0; + } + $fprot_InCruft == 0 or return 0; + + # Prefer s/// to m// as less likely to do unpredictable things. + # We hope. + # JKF 5+11/1/2002 Make "security risk" and "joke program" lines look like + # virus infections for easier parsing. + # JKF 25/02/2002 Add all sorts of patterns gleaned from a coredump of F-Prot + # JKF 24/07/2002 Reparse the lines to turn them into infection reports + $report = $line; + $logout = $line; + $logout =~ s/%/%%/g; + Log::InfoLog($logout); + if ($line =~ /(is|could be) a (security risk|virus construction)/) { + $line =~ s/(is|could be) a (security risk|virus construction).*$/Infection: /; + } + if ($line =~ /(is|could be) a mass-mailing worm/) { + $line =~ s/(is|could be) a mass-mailing worm.*$/Infection: /; + } elsif ($line =~ /(is|could be) a( boot sector)? virus dropper/) { + $line =~ s/(is|could be) a( boot sector)? virus dropper.*$/Infection: /; + } elsif ($line =~ /(is|could be) a corrupted or intended/) { + $line =~ s/(is|could be) a corrupted or intended.*$/Infection: /; + } elsif ($line =~ /(is|could be) a (joke|destructive) program/) { + $line =~ s/(is|could be) a (joke|destructive) program.*$/Infection: /; + } elsif ($line =~ /(is|could be) infected with an unknown virus/) { + $line =~ s/(is|could be) infected with an unknown virus.*$/Infection: /; + } elsif ($line =~ /contains.*\(non-working\)/) { + $line =~ s/contains /Infection: /; + } + if ($line =~ /\s\sInfection:\s/) { + # Get to relevant filename in a reasonably but not + # totally robust manner (*impossible* to be totally robust + # if we have slashes, spaces and "->" in filenames) + $line =~ s/^(.*?)->.+(\s\sInfection:.*)/$1$2/; # strip archive bits if present + $line =~ s/^.*(\/.*\/.*)\s\sInfection:([^:]*)$/$1/ # get to the meat or die trying + or Log::DieLog("Dodgy things going on in F-Prot output:\n$report\n"); + #print STDERR "**$line\n"; + $virus = $2; + $virus =~ s/^\s*(\S+).*$/$1/; # 1st word after Infection: is the virus + Log::InfoLog("Virus Scanning: F-Prot found virus %s", $virus); + $infections->{"$fileToScan"}{""} .= $virus . "\n"; + $types->{"$fileToScan"}{""} .= "v"; # so we know what to tell sender + return 1; + } + + # Have now seen F-Prot produce infection lines without Infection: in them! + # Look for W32 in the last word of the line + if ($line =~ /W32\/\S+$/) { + # Get to relevant filename in a reasonably but not + # totally robust manner (*impossible* to be totally robust + # if we have slashes, spaces and "->" in filenames) + $line =~ s/^(.*?)->.+(\sW32\/\S+)/$1$2/; # strip archive bits if present + $line =~ s/^.*(\/.*\/.*)\s(W32\/\S+)$/$1/ # get to the meat or die trying + or MailScanner::Log::DieLog("Dodgy things going on in F-Prot output2:\n$report\n"); + #print STDERR "**$line\n"; + $virus = $2; + Log::InfoLog("Virus Scanning: F-Prot found problem %s", + $virus); + $infections->{"$fileToScan"}{""} .= $virus . "\n"; + $types->{"$fileToScan"}{""} .= "v"; # so we know what to tell sender + return 1; + } + + # Ignore files we couldn't scan as they were encrypted + if ($line =~ /\s\sNot scanned \(encrypted\)/ || + $line =~ /\s\sNot scanned \(unsupported compression method\)/ || + $line =~ /Virus-infected files in archives cannot be deleted\./) { + return 0; + } + + Log::WarnLog("Either you've found a bug in MailScanner's F-Prot output parser, or F-Prot's output format has changed! F-Prot said this \"%s\". Please mail the author of MailScanner", $line); + return 0; +} + +# This function provided in its entirety by Ing. Juraj Hanták +# +sub ProcessNOD32Output { + my($line, $infections, $types, $fileToScan) = @_; + my($report, $infected, $dot, $id, $part, @rest); + my($logout); + + chomp $line; + $logout = $line; + $logout =~ s/%/%%/g; + Log::WarnLog($logout) if $line =~ /error/i; + if (!$NOD32Version && $NOD32InHeading && /^NOD32.*Version.*([\d.]+)/) { + $NOD32Version = $1; + $NOD32InHeading = 0; + return 0; + } + $NOD32InHeading = 0 if /^$/; + + return 0 unless $line =~ /\s-\s/i; + + if ($NOD32Version >= 1.990) { + # New NOD32 output parser + $line =~ /(.*) - (.*)$/; + my($file, $virus) = ($1, $2); + return 0 if $virus =~ /not an archive file|is OK/; + return 0 if $file =~ /^ /; + Log::InfoLog("%s", $line); + $infections->{"$fileToScan"}{""} .= $virus . "\n"; + $types->{"$fileToScan"}{""} .= "v"; # it's a real virus + return 1; + } else { + my ($part1,$part2,$part3,$part4,@ostatne)=split(/\.\//,$line); + $line="./".$part4; + $logout = $line; + $logout =~ s/%/%%/g; + Log::InfoLog($logout); + $report = $line; + $infected = $line; + $infected =~ s/^.*\s*-\s*//i; + + # JKF 10/08/2000 Used to split into max 3 parts, but this doesn't handle + # viruses in zip files in attachments. Now pull out first 3 parts instead. + ($dot, $id, $part, @rest) = split(/[\/,-]/, $report); + $part =~ s/\s$//g; + $infections->{"$fileToScan"}{""} .= $report . "\n"; + $types->{"$fileToScan"}{""} .= "v"; # it's a real virus + + return 1; + } +} + +# This function originally contributed by Cornelius Kölbel +# +sub ProcessAntiVirOutput { + my($line, $infections, $types, $fileToScan) = @_; + # my($line) = @_; + my($report, $infected, $dot, $id, $part, @rest); + my($logout); + + # From CK's mail: + # + # checking drive/path (list): /etc/mail + # !Virus! /etc/mail/eicar.com Eicar-Test-Signatur (exact) + # !Virus! /etc/mail/eicar file.com Eicar-Test-Signatur (exact) + # + # And I am not very sure, where I should to the sepeartion, if the file + # (attachment) has a space in it (as in the second case). + # So I took this regular expression: + # $part=~ s/^(.*)\s\S*\s\S*$/$1/g; + # + # Since I asume, that the output has always a column with the Name of the + # Virus (Eicar-Test-Signatur) and something, that says "exact". + + # Open questions: + # - Does it *always* say "!Virus!" or can it sometimes say, for example, + # "!Trojan!" or "!Joke!"?? + # - I am assuming that they are not braindead and therefore never have + # spaces in their virus names... + # - What does the output of antivir look like when invoked on "." (does + # it report relative paths? + # + # -- nwp 6/5/02 + + # Code updated by G.H.J. Dorssers - September 17, 2002 + # Added to DansGuardian AntiVirus Plugin on 2002/10/02 + # Rewriting was needed due to the changed output of AntiVir + # It seems to be working although I'm sure it can be greatly improved. :-) + + # Now produces output like this: + # ALERT: [Eicar-Test-Signatur virus] ./eicar1.zip --> eicar.com <<< Contains code of the Eicar-Test-Signatur virus + # ALERT: [Eicar-Test-Signatur virus] ./eicar2.com <<< Contains code of the Eicar-Test-Signatur virus + + + chomp $line; + $report = $line; + + if ($line =~ /.*!Virus!.*/) { + $logout = $line; + $logout =~ s/%/%%/g; + Log::InfoLog($logout); + ($dot,$id,$part,@rest) = split(/\//, $line); + # The Filename is all, except the last two comma seperated elements + $part =~ s/^(.*)\s\S*\s\S*$/$1/g; + $infections->{"$fileToScan"}{""} .= $report . "\n"; + $types->{"$fileToScan"}{""} .= "v"; # so we know what to tell sender + #print STDERR "dot: $dot, id: $id, part: $part, rest: @rest\n"; + return 1; + #print STDERR "dot: $dot, id: $id, part: $part, rest: @rest\n"; + # dot: , id: g28C22m03310, part: eicar.com, rest: + } + + # New output format? + if ($line =~ /^ALERT:/) { + $logout = $line; + $logout =~ s/%/%%/g; + MailScanner::Log::InfoLog($logout); + # Get rid of the virus name + $line =~ s/^ALERT: \[[^\]]+\] //; + if ($line =~ / --\> .*\<\<\ .*\<\<\<.*$//; + } else { + # Line describes a normal file + $line =~ s/ \<\<\<.*$//; + } + ($dot,$id,$part,@rest) = split(/\//, $line); + chomp $part; + #print STDERR "ID = $id and PART = $part\n"; + $infections->{"$fileToScan"}{""} .= $report . "\n"; + $types->{"$fileToScan"}{""} .= "v"; + return 1; + } + + + # Don't warn any more, new output format includes other gumph we aren't + # interested in. + #Log::WarnLog("Either you've found a bug in MailScanner's AntiVir output parser, or AntiVir's output format has changed! AntiVir said this \"$line\". Please mail the author of MailScanner"); + return 0; +} + +# This function originally contributed by Héctor García Álvarez +# +# From comment (now removed), it looks to be based on Sophos parser at +# some point in its history. +# +sub ProcessPandaOutput { + my($line, $infections, $types, $fileToScan) = @_; + my($report, $infected, $dot, $id, $part, @rest); + my($logout); + + return 0 if $line =~ /^Virus: 0/i; + + $logout = $line; + $logout =~ s/%/%%/g; + Log::InfoLog($logout); + + # EXAMPLE OUTPUT PLEASE? -- nwp 6/5/02 + + $line =~ /Base: (.*?)##/; + $fileToScan =~ $1; + my $temp = $line; + while ( $temp =~ /\d+: \'(.*?)\/(.*?)\' => (.*?)##/ ) + { + $id = $1; + $part = $2; + $report = $3; + $infections->{$fileToScan}{''} .= "$report\n"; + $types->{$fileToScan}{''} .= "v"; # it's a real virus + $temp = $'; + } + #$infected = "$id/$part $report"; + return 1; + +} + +# This function originally contributed by Luigino Masarati +# Looks like it's based on F-Secure function... +# +sub ProcessRavOutput { + my($line, $infections, $types, $fileToScan) = @_; + + my($report, $infected, $dot, $id, $part, @rest); + my($logout); + + # Sample output: + # + # [root@pico /tmp]# /usr/local/rav8/ravwrapper --all --mail --archive ./eicar + # RAV AntiVirus command line for Linux i686. + # Version: 8.3.0. + # Copyright (c) 1996-2001 GeCAD The Software Company. All rights reserved. + # + # Scan engine 8.5 for i386. + # Last update: Wed May 1 16:57:02 2002 + # Scanning for 66321 malwares (viruses, trojans and worms). + # + # Scan started on Wed May 8 08:52:55 2002 + # + # ./eicar/eicarcom2.zip->eicar_com.zip->eicar.com Infected: EICAR_Test_File + # ./eicar/eicar.com Infected: EICAR_Test_File + # ./eicar/eicar.com.txt Infected: EICAR_Test_File + # ./eicar/eicar_com.zip->eicar.com Infected: EICAR_Test_File + # + # Scan ended on Wed May 8 08:52:55 2002 + # + # Objects scanned: 7. + # Infected: 4. + # Warnings: 0. + # Time: 0 second(s). + # [root@pico /tmp]# + + #print STDERR ">>$line"; + # + # This is the original code contributed. It's not perfect. + # + #chomp $line; + + #$report = $line; + #if ($line =~ /\s+Infected:/i) { + # # Get to relevant filename in a reasonably but not + # # totally robust manner (*impossible* to be totally robust + # # if we have slashes, spaces and "->" in filenames) + # $line =~ s/^(.*?)\-\>.+(\s+Infected:.*)/$1$2/; # strip archive bits if present + # $line =~ s/^.*(\/.*\/.*)\s+Infected:[^:]*$/$1/ # get to the meat or die trying + # or Log::DieLog("Dodgy things going on in Rav output:\n%s\n", $report); + # #print STDERR "**$line\n"; + # ($dot,$id,$part,@rest) = split(/\//, $line); + # $infections->{"$fileToScan"}{""} .= $report . "\n"; + # $types->{"$fileToScan"}{""} .= "v"; # so we know what to tell sender + # return 1; + #} + #return 0; + # + # This is my rewritten code (JKF). Now supporting RAV officially. + # + # Syntax of infection report lines is like this: + # pathname->zippart\tInfected: virusname + # pathname->zippart\tSuspicious: virusname + # + chomp $line; + + $report = $line; + if ($line =~ /\t+(Infected|Suspicious): /i) { + $logout = $line; + $logout =~ s/%/%%/g; + Log::InfoLog($logout); + # Get to relevant filename in a reasonably but not + # totally robust manner (*impossible* to be totally robust + # if we have slashes, spaces and "->" in filenames) + + # Strip the infection report off the end, leaves us with the path + # and the archive element name + $line =~ s/\t(Infected|Suspicious): \S+$//; + # Strip any archive elements so we should just have the path and filename + $line =~ s/^(.*?)\-\>.*$/$1/; + $line =~ /\-\>/ + and Log::DieLog("Dodgy things going on in Rav " . + "output:\n%s\n", $report); + #print STDERR "**$line\n"; + ($dot,$id,$part,@rest) = split(/\//, $line); + $infections->{"$fileToScan"}{""} .= $report . "\n"; + $types->{"$fileToScan"}{""} .= "v"; # so we know what to tell sender + return 1; + } + return 0; +} + +# Process ClamAV (v0.22) output +# This code contributed in its entirety by +# Adrian Bridgett . +# Please contact him with any support questions. +sub ProcessClamAVOutput { + my($line, $infections, $types, $fileToScan) = @_; + + my($logline); + + if ($line =~ /^ERROR:/ or $line =~ /^execv\(p\):/ or + $line =~ /^Autodetected \d+ CPUs/) + { + chomp $line; + $logline = $line; + $logline =~ s/%/%%/g; + Log::WarnLog($logline); + return 0; + } + + # clamscan currently stops as soon as one virus is found + # therefore there is little point saying which part + # it's still a start mind! + + # Only tested with --unzip since only windows boxes get viruses ;-) + + if (/^Archive: (.*)$/) + { + $clamav_archive = $1; + return 0; + } + return 0 if /Empty file.$/; + # Normally means you just havn't asked for it + if (/: (\S+ module failure\.)/) + { + Log::InfoLog("ProcessClamAVOutput: %s", $1); + return 0; + } + return 0 if /^ /; # " inflating", " deflating.." from --unzip + if ($clamav_archive && /^$clamav_archive:/) + { + $clamav_archive = ""; + return 0; + } + + return 0 if /OK$/; + + Log::InfoLog("%s", $line); + + if (/^(.*?): (.*) FOUND$/) + { + my ($file, $subfile, $virus, $report); + $virus = $2; + if ($clamav_archive) + { + $file = $clamav_archive; + ($subfile = $1) =~ s/^.*\///; # get basename of file + $report = "in $subfile (possibly others)"; + } + else + { + $file = $1; + } + + $file =~ s/^(.\/)?$fileToScan\/?//; + $file =~ s/^\.\///; + my ($id,$part) = split /\//, $file, 2; + + $infections->{"$fileToScan"}{""} .= "$virus\n"; + $types->{"$fileToScan"}{""} .= "v"; + return 1; + } + + chomp $line; + $logline = $line; + $logline =~ s/%/%%/g; + Log::WarnLog("ProcessClamAVOutput: unrecognised " . + "line \"$logline\". Please contact the authors!"); + return 0; +} + +# Parse the output of the Trend VirusWall vscan output. +# Contributed in its entirety by Martin Lorensen +sub ProcessTrendOutput { + my($line, $infections, $types, $fileToScan) = @_; + chomp $line; + + return if $line =~ /^\s*(=====|Directory:|Searched :|File:|Searched :|Scan :|Infected :|Time:|Start :|Stop :|Used :|Configuration:|$)/; + + # Next line didn't work with zip (and other) archives + #$line =~ y/\t//d and $trend_prevline = $line; + $line =~ s/^\t+\././ and $trend_prevline = $line; + + Log::InfoLog("%s", $line); + + # Sample output: + # + # Scanning 2 messages, 1944 bytes + # Virus Scanner v3.1, VSAPI v5.500-0829 + # Trend Micro Inc. 1996,1997 + # ^IPattern version 329 + # ^IPattern number 46849 + # Configuration: -e'{* + # Directory . + # Directory ./g72CdVd6018935 + # Directory ./g72CdVd7018935 + # ^I./g72CdVd7018935/eicar.com + # *** Found virus Eicar_test_file in file /var/spool/MailScanner/incoming_virus/g72CdVd7018935/eicar.com + + if ( $line =~ /Found virus (\S+) in file/i ) + { + my($virus ) = $1; # Name of virus found + + # Unfortunately vscan shows the full filename even though it was given + # a relative name to scan. The previous line is relative, though. + # So use that instead. + + my($dot, $id, $part, @rest) = split(/\//, $trend_prevline); + + $infections->{$fileToScan}{""} .= "$virus\n"; + $types->{$fileToScan}{""} .= "v"; # so we know what to tell sender + return 1; + } + return 0; +} + +# Call the commercial scanners, but to disinfect this time. +# Ignore all output from them. +# Re-check the files later to see if the disinfection was successful. +sub CallDisinfector { + my($fileToScan) = @_; + + my($number, @scanners, @commands); + + # No need to do this here, will have already been done. + #$Config::VirusScanner =~ s/^[\s,]*//; # Remove any leading or trailing + #$Config::VirusScanner =~ s/[\s,]*$//; # list separators + #$Config::Sweep =~ s/^[\s,]*//; # Remove any leading or trailing + #$Config::Sweep =~ s/[\s,]*$//; # list separators + + @scanners = split(/[\s,]+/, $Config::VirusScanner); + @commands = split(/[\s,]+/, $Config::Sweep); + + foreach $number (0..$#scanners) { + CallOneDisinfector($scanners[$number], $commands[$number], $fileToScan); + } +} + +# Call one of the commercial scanners, but to disinfect. +# Ignore all output from it this time. +sub CallOneDisinfector { + my($scanner, $sweepcommand, $fileToScan) = @_; + + local(*SWEEP, *LOCK); + my($rScanner, $VirusLock, $voptions, $TimedOut, $pid, $PipeReturn); + + Log::DieLog("Never heard of scanner '$scanner'!") + unless exists $Scanners{$scanner}; + + $rScanner = $Scanners{$scanner}; + + if ($rScanner->{"SupportDisinfect"} == $S_NONE){ + Log::DebugLog("Disinfection using scanner \"$scanner\" not supported; not disinfecting"); + return 1; + } + + CheckCodeStatus($rScanner->{"SupportDisinfect"}) + or Log::DieLog("Bad return code from CheckCodeStatus - should it have quit?"); + + $VirusLock = $Config::LockDir . "/" . $rScanner->{"Lock"}; # lock file + $voptions = $rScanner->{"CommonOptions"}; # Set common command line options + $voptions .= " " . $rScanner->{"DisinfectOptions"}; # Add command line options to disinfect + + if (! -e $fileToScan) { die "$fileToScan does not exist, $!"; } + + # Check that Sophos IDE files aren't currently being updated, + # and wait if they are. + open(LOCK, ">$VirusLock") or Log::DieLog("Cannot create $VirusLock, $!"); + flock(LOCK, $LOCK_SH); + print LOCK "Sophos locked for scanning by $scanner $$\n"; + + $TimedOut = 0; + eval { + die "Can't fork: $!" unless defined($pid = open(KID, "|-")); + if ($pid) { + # In the parent + local $SIG{ALRM} = sub { $TimedOut = 1; die "Command Timed Out" }; + alarm $Config::SweepTimeout; + print KID "A\n" if $scanner eq 'sophos'; # Tell sweep to disinfect all + close KID; + $PipeReturn = $?; + $pid = 0; + alarm 0; + } else { + # In the child + POSIX::setsid(); + exec "$sweepcommand $voptions $fileToScan" + or die "Can't run commercial disinfector $scanner (\"$sweepcommand\"): $!"; + } + }; + alarm 0; # 2.53 + + # Note to self: I only close the KID in the parent, not in the child. + + # Catch failures other than the alarm + Log::DieLog("Commercial virus checker failed with real error: %s", $@) + if $@ and $@ !~ /Command Timed Out/; + + # In which case any failures must be the alarm + if ($@ or $pid>0) { + # Kill the running child process + my($i); + kill -15, $pid; + # Wait for up to 5 seconds for it to die + for ($i=0; $i<5; $i++) { + sleep 1; + ($pid=0),last unless kill(0, $pid); + kill -15, $pid; + } + # And if it didn't respond to 11 nice kills, we kill -9 it + kill -9, $pid if $pid; + wait; # 2.53 + } + + # Don't care about return code in this case, we ignore it anyway + Log::InfoLog("Commercial disinfector $scanner returned $PipeReturn") if $PipeReturn; + flock(LOCK, $LOCK_UN); + close LOCK; +} + +sub NeverHappens { + Log::DieLog("THIS SHOULD NEVER HAPPEN.\nPlease report this as a bug to the authors."); +} + +# Should be called when we're about to try to run some code to +# scan or disinfect (after checking that code is present) +sub CheckCodeStatus { + my ($codestatus) = @_; + + my $allowedlevel = $S_SUPPORTED; + + $Config::CodeStatus =~ /^beta/i and $allowedlevel = $S_BETA; + $Config::CodeStatus =~ /^alpha/i and $allowedlevel = $S_ALPHA; + $Config::CodeStatus =~ /^unsup/i and $allowedlevel = $S_UNSUPPORTED; + $Config::CodeStatus =~ /^none/i and $allowedlevel = $S_NONE; + + $codestatus >= $allowedlevel and return 1; + + Log::WarnLog("Looks like a problem... dumping status information"); + Log::WarnLog("Minimum acceptable stability = $allowedlevel ($Config::CodeStatus)"); + Log::WarnLog("Using Scanner \"$Config::VirusScanner\""); + foreach (keys %Scanners) { + my $statusinfo = "Scanner \"$_\": scanning code status "; + $statusinfo .= $Scanners{$_}{"SupportScanning"}; + $statusinfo .= " - disinfect code status "; + $statusinfo .= $Scanners{$_}{"SupportDisinfect"}; + Log::WarnLog($statusinfo); + } + Log::WarnLog("FATAL: Encountered code that does not meet configured acceptable stability"); + Log::DieLog("FATAL: *Please go and READ* http://www.sng.ecs.soton.ac.uk/mailscanner/install/codestatus.shtml"); +} + +1; diff -urN --exclude=CVS --exclude=pcx dansguardian-2.7.1-0/virusscanner dansguardian2_7/virusscanner --- dansguardian-2.7.1-0/virusscanner Wed Dec 31 18:00:00 1969 +++ dansguardian2_7/virusscanner Wed May 14 14:32:37 2003 @@ -0,0 +1,408 @@ +#!/usr/bin/perl +# +# MailScanner - SMTP E-Mail Virus Scanner +# Copyright (C) 2002 Julian Field +# +# $Id: virusscanner,v 1.9 2003/04/04 20:19:35 james Exp $ +# +# 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 +# +# The author, Julian Field, can be contacted by email at +# Jules@JulianField.net +# or by paper mail at +# Julian Field +# Dept of Electronics & Computer Science +# University of Southampton +# Southampton +# SO17 1BJ +# United Kingdom +# + +# 07/23/2002 +# James A. Pattie (james@pcxperience.com) - updated to be the starting +# point for DansGuardian. + +# Main program.... + +use strict; +use POSIX; +use Mail::Sender; +use Fcntl qw(:DEFAULT :flock); +require 5.005; + +my $autoinstalled=0; + +sub setup +{ + # To detect whether we've been auto-configured & installed + # -- $autoinstalled will be set to 1 if so. + #@@$autoinstalled=1; + + # Needed for Sys::Syslog, as Debian Potato (at least) doesn't + # appear to have "gethostname" syscall as used (indirectly) by Sys::Syslog + # So it uses `hostname` instead, which it can't do if PATH is tainted. + # It's good to have this anyway, although we may need to modify it for + # other OS when we find that something we need isn't here -- nwp 14/01/02 + $ENV{PATH}="/sbin:/bin:/usr/sbin:/usr/bin"; + + # We *really* should clear *all* environment bar what we *know* we + # need here. It will avoid surprises (like bash running BASH_ENV or + # SpamAssassin using $ENV{HOME} rather than getpwnam to decide where + # to drop its load. + + # Needed for -T: + delete $ENV{'BASH_ENV'}; # Don't run things on bash startup + + # Remember to update this before producing new version of MailScanner + $Config::MailScannerVersion = '3.26'; + $Config::DGVirusVersion = "4.0"; + $Config::DGVersion = "2.7.0-1"; + $Config::OSName = `/bin/uname -s`; + chomp $Config::OSName; + + my($dir); + $dir = $0; + # can't use s/// as it doesn't untaint $dir -- nwp 14/01/02 (just) + $dir =~ m#^(.*)/([^/]+)$#; + $dir = $1; + $Config::MailScannerProcessName = $2; + # Add my directory onto the front of the include path + unless ($autoinstalled) { + unshift @INC, $dir; + } + + require 'logger.pl'; + require 'sweep.pl'; + require 'config.pl'; + + # Specify config file location on command line if you like + $Config::ConfigFile = $ARGV[0] if defined $ARGV[0]; + + Config::ReadConfig($Config::ConfigFile); + umask 0077; # Set nice and safe to no-one else can access anything! + + # also make sure the /tmp/dgvirus directory exists or whatever the user specified. + if ($Config::OSName =~ /OpenBSD/) + { + `/bin/mkdir -p -m 0700 $Config::DownloadDir`; + } + else + { + `/bin/mkdir -p --mode=0700 $Config::DownloadDir`; + } +} + +sub scan +{ + my ($fileToScan, $Bytes, $url, $user, $userIP) = @_; + + # Check all the attachments for viruses and work out + # the cleanliness of each message. + my $Start = time; + my($InfectionReports, %InfectionTypes); + $InfectionReports = Sweep::VirusScan($fileToScan, \%InfectionTypes); + my $Duration = time - $Start; + + $user = "no-user" if ($user eq "-"); # fixup the username if none was specified. + + if (scalar keys %{$InfectionReports} == 0) + { + Log::InfoLog("Scanned $fileToScan, $Bytes bytes, url $url in $Duration seconds for user '$user'"); + return 0; + } + else + { + if ($InfectionTypes{$fileToScan}{""} eq "d") + { + Log::InfoLog("Failed Scanning $fileToScan, $Bytes bytes, url $url in $Duration seconds for user '$user' - Denial of Service Attack"); + } + else + { + Log::InfoLog("Scanned $fileToScan, $Bytes bytes, url $url in $Duration seconds for user '$user' is Infected with: $InfectionReports->{$fileToScan}{''}"); + + my $dateString = `/bin/date +%Y%m%d`; + chomp $dateString; + + (my $urlDir = $url) =~ s/(&)/\&/g; # make sure any &'s are properly escaped so the shell doesn't try anything funny. + + (my $fileName = $fileToScan) =~ s/^($Config::DownloadDir\/)//; + + my $fromUser = "DansGuardian Anti-Virus "; + my $subject = "Virus Found by DansGuardian Anti-Virus"; + my $ourURL = "http://www.pcxperience.org/dgvirus/"; + my $seperatorString = ($Config::MailType eq "text/html" ? "
\n" : "\n"); + my $infoString = $seperatorString x 2 . "This message generated by DansGuardian Anti-Virus Plugin " . $Config::DGVirusVersion . ". " . ($Config::MailType eq "text/plain" ? $ourURL : "$ourURL"); + + # quarantine if needed + if ($Config::QuarantineAction eq "store") + { + if ($Config::OSName =~ /OpenBSD/) + { + `/bin/mkdir -p -m 0755 $Config::QuarantineDir/$dateString/$user/$urlDir`; + } + else + { + `/bin/mkdir -p --mode=0755 $Config::QuarantineDir/$dateString/$user/$urlDir`; + } + `/bin/cp $Config::DownloadDir/$fileName $Config::QuarantineDir/$dateString/$user/$urlDir`; + } + + chomp $InfectionReports->{$fileToScan}{''}; # get rid of the extra newline. + + my $message; + if ($Config::MailType eq "text/plain") + { + $message = "Virii" . " " x 11 . ": " . $InfectionReports->{$fileToScan}{''} . $seperatorString; + $message .= "User" . " " x 12 . ": $user" . $seperatorString; + $message .= "URL" . " " x 13 . ": $url" . $seperatorString; + if ($Config::QuarantineAction eq "store") + { + $message .= "Quarantined" . " " x 5 . ": Yes" . $seperatorString; + $message .= "Quarantine File : $Config::QuarantineDir/$dateString/$urlDir/$fileName" . $seperatorString; + } + else + { + $message .= "Quarantined" . " " x 5 . ": No" . $seperatorString; + $message .= "Quarantine File : $fileName" . $seperatorString; + } + $message .= "Users IP" . " " x 8 . ": $userIP" . $seperatorString; + } + else + { + $message .= "\n"; + $message .= " \n"; + $message .= " \n"; + $message .= " \n"; + $message .= " \n"; + $message .= " \n"; + $message .= " \n"; + $message .= " \n"; + $message .= " \n"; + $message .= " \n"; + if ($Config::QuarantineAction eq "store") + { + $message .= " \n"; + $message .= " \n"; + $message .= " \n"; + $message .= " \n"; + $message .= " \n"; + $message .= " \n"; + } + else + { + $message .= " \n"; + $message .= " \n"; + $message .= " \n"; + $message .= " \n"; + $message .= " \n"; + $message .= " \n"; + } + $message .= " \n"; + $message .= " \n"; + $message .= " \n"; + $message .= "
Virii$InfectionReports->{$fileToScan}{''}
User$user
URL$url
QuarantinedYes
Quarantine File$Config::QuarantineDir/$dateString/$urlDir/$fileName
QuarantinedNo
Quarantine File$fileName
Users IP$userIP
\n"; + } + $message .= $infoString; # common footer. + + # email the LocalPostmaster user + sendEmail(to => $Config::LocalPostmaster, from => $fromUser, subject => $subject, + body => $message, mailServer => $Config::MailServer, + ctype => $Config::MailType); + + # email the specified user + if ($user ne "no-user") + { + $message = "The file you were downloading from " . ($Config::MailType eq "text/plain" ? $url : "$url") . " was found to be Infected with: $InfectionReports->{$fileToScan}{''}"; + $message .= $seperatorString x 2; + if ($Config::QuarantineAction eq "store") + { + $message .= "It was Quarantined on the server. File = $fileName"; + $message .= $seperatorString x 2 . "Please contact the Administrator to see if they can recover this file for you."; + } + else + { + $message .= "It was NOT Quarantined on the server."; + } + $message .= $infoString; + + sendEmail(to => "$user\@$Config::MailDomain", from => $fromUser, subject => $subject, + body => $message, mailServer => $Config::MailServer, + ctype => $Config::MailType); + } + } + return 1; + } +} + +sub shutdown +{ + Log::InfoLog("Virus Scanner $Config::DGVirusVersion in DansGuardian $Config::DGVersion from MailScanner $Config::MailScannerVersion stopping."); + + # Don't want to leave connections to 514/udp open... + Log::Stop(); +} + +sub tempFileName +{ + # return a temp file name without any leading directories. + my $fileName = tmpnam(); + $fileName =~ s/^(\/tmp\/)//; + + # also make sure the /tmp/dgvirus/status directory exists or whatever the user specified. + my $dirName = $Config::DownloadDir . "/status"; + if ($Config::OSName =~ /OpenBSD/) + { + `/bin/mkdir -p -m 0700 $dirName`; + } + else + { + `/bin/mkdir -p --mode=0700 $dirName`; + } + + return $fileName; +} + +sub tempDownloadDir +{ + return $Config::DownloadDir; +} + +# takes: to, from, subject, body, mailServer, ctype as name => value pairs. +sub sendEmail +{ + my %args = ( to => "", from => "", subject => "", body => "", mailServer => "127.0.0.1", ctype => "text/plain", @_ ); + my $to = $args{to}; + my $from = $args{from}; + my $subject = $args{subject}; + my $body = $args{body}; + my $mailServer = $args{mailServer}; + my $ctype = $args{ctype}; + my $errorOutput = ""; + + my $sender = new Mail::Sender { smtp => "$mailServer" }; + if (!ref ($sender)) + { + $errorOutput .= "Error initializing e-mail connection with server via Mail::Sender Module!\n"; + $errorOutput .= "Error = \"" . $Mail::Sender::Error . "\"\n\n"; + $errorOutput .= "Note to Administrator:  dansguardian-virus was sending a $ctype e-mail.\n"; + Log::WarnLog($errorOutput); + return; + } + $Mail::Sender::NO_X_MAILER = 1; # turn off the X_Mailer entry that has the info about this module. + if ($sender->Open( { from => "$from", to => "$to", subject => "$subject", headers => "MIME-Version: 1.0\r\nContent-type: $ctype\r\nContent-Transfer-Encoding: 7bit" }) < 0) + { + $errorOutput .= "Error opening e-mail connection with server via Mail::Sender Module!\n"; + $errorOutput .= "Error = \"" . $Mail::Sender::Error . "\"\n\n"; + $errorOutput .= "Note to Administrator:  dansguardian-virus was sending a $ctype e-mail.\n"; + Log::WarnLog($errorOutput); + return; + } + if ($sender->Send($body) < 0) + { + $errorOutput .= "Error sending e-mail via Mail::Sender Module!\n"; + $errorOutput .= "Error = \"" . $Mail::Sender::Error . "\"\n\n"; + $errorOutput .= "Note to Administrator:  dansguardian-virus was sending a $ctype e-mail.\n"; + Log::WarnLog($errorOutput); + return; + } + if ($sender->Close() < 0) + { + $errorOutput .= "Error sending e-mail via Mail::Sender Module!\n"; + $errorOutput .= "Error = \"" . $Mail::Sender::Error . "\"\n\n"; + $errorOutput .= "Note to Administrator:  dansguardian-virus was sending a $ctype e-mail.\n"; + Log::WarnLog($errorOutput); + return; + } +} + +sub updateStatusFile +{ + my ($startTime, $currTime, $file, $url, $fileLength, $bytesDownloaded, $user, $userIP) = @_; + + $startTime =~ s/\n$//; + $currTime =~ s/\n$//; + + $user = "no-user" if ($user eq "-"); # fixup the username if none was specified. + + my $sortBy = ($Config::StatusGroupedBy eq "uname" ? $user : $userIP); + my $fileName = "$sortBy/$file"; + + # output to the status file the specified info. + + if (! -d "$Config::DownloadDir/status/$sortBy") + { + if ($Config::OSName =~ /OpenBSD/) + { + `/bin/mkdir -p -m 0700 $Config::DownloadDir/status/$sortBy`; + } + else + { + `/bin/mkdir -p --mode=0700 $Config::DownloadDir/status/$sortBy`; + } + } + + if (!open(STATUS, ">$Config::DownloadDir/status/$fileName")) + { + Log::WarnLog("updateStatusFile: Error opening status file '$fileName'! $!"); + return 0; + } + if (!flock(STATUS, LOCK_EX)) # get an exclusive lock on the file + { + Log::WarnLog("updateStatusFile: Error trying to write-lock '$fileName'! $!"); + return 0; + } + + print STATUS << "END_OF_STATUS"; +started: $startTime +last: $currTime +url: $url +file: $file +fileLength: $fileLength +bytesDownloaded: $bytesDownloaded +user: $user +userIP: $userIP +END_OF_STATUS + + close(STATUS); + + return 1; # signal success. +} + +sub removeStatusFile +{ + my ($file, $user, $userIP) = @_; + + $user = "no-user" if ($user eq "-"); # fixup the username if none was specified. + + my $sortBy = ($Config::StatusGroupedBy eq "uname" ? $user : $userIP); + my $fileName = "$sortBy/$file"; + + if (-e "$Config::DownloadDir/status/$fileName") + { + if (!open(STATUS, "$Config::DownloadDir/status/$fileName")) + { + Log::WarnLog("removeStatusFile: Error opening status file '$fileName'! $!"); + return 0; + } + if (!flock(STATUS, LOCK_EX)) # get an exclusive lock on the file + { + Log::WarnLog("removeStatusFile: Error trying to write-lock '$fileName'! $!"); + return 0; + } + + unlink("$Config::DownloadDir/status/$fileName"); + close(STATUS); + } +} diff -urN --exclude=CVS --exclude=pcx dansguardian-2.7.1-0/virusscanner.conf dansguardian2_7/virusscanner.conf --- dansguardian-2.7.1-0/virusscanner.conf Wed Dec 31 18:00:00 1969 +++ dansguardian2_7/virusscanner.conf Wed May 14 14:32:37 2003 @@ -0,0 +1,122 @@ +# Configuration file for MailScanner E-Mail Virus Scanner in DansGuardian +# This file assumes everything is in the default locations provided +# by the MailScanner and RedHat 6.2 and upwards. +# +# Note: If your directories are symlinked (soft-linked) in any way, +# please put their *real* location in here, not a path that +# includes any links. You may get some very strange error +# messages from some of the virus scanners if you don't. + +# Set where to store infected files (if they are kept) +Quarantine Dir = /var/www/html/dgvirus/quarantine + +# Set where the files are downloaded to before they are scanned +Download Dir = /tmp/dgvirus + +# Store status info by IP Address (ip) or User Name (uname) +Status Info Grouped By = uname +#Status Info Grouped By = ip + +# What syslog "facility" should we use for logging? +# If that means nothing to you, then leave this option alone or +# else read "man syslog.conf" before making any changes. +Log Facility = user + +# Which Virus Scanning package to use: +# sophos from www.sophos.com, or +# mcafee from www.mcafee.com, or +# command from www.command.co.uk, or +# kaspersky from www.kaspersky.com, or +# kavdaemonclient from www.kaspersky.com, or +# inoculate from www.cai.com/products/inoculateit.htm, or +# inoculan from ftp.ca.com/getbbs/linux.eng/inoctar.LINUX.Z, or +# nod32 from www.nod32.com, or +# f-secure from www.f-secure.com, or +# f-prot from www.f-prot.com, or +# panda from www.pandasoftware.com, or +# rav from www.ravantivirus.com, or +# antivir from www.antivir.de, or +# clamav from clamav.elektrapro.com, or +# trend from www.trendmicro.com +# +# Note: If you want to use multiple virus scanners, then this should be a +# comma-separated list of virus scanners. For example: +# Virus Scanner = sophos, f-prot +# +Virus Scanner = f-prot + +# Where the Virus scanner is installed. This is the command needed to run it. +# Look in /usr/lib/DGVirus for a list of available wrapper scripts. +# +# Note: If you want to use multiple virus scanners, then this should be a +# comma-separated list of commands, **in the same order** as they are listed +# in the "Virus Scanner" keyword just above. For example: +# Sweep = /usr/lib/DGVirus/sophos-wrapper, /usr/lib/DGVirus/f-prot-wrapper +# +Sweep = /usr/lib/DGVirus/f-prot-wrapper + +# The maximum length of time the commercial virus scanner is allowed to run +# for 1 batch of messages (in seconds). +Virus Scanner Timeout = 300 + +# Set email address of who to notify about any infections found. +# Should put your full domain name here too, +# e.g. postmaster@your.domain.com +Local Postmaster = postmaster + +# Set email domain to use when notifying users of an infected file. +# This is just the domain name part, after the @ +# e.g. your.domain.com +Mail Domain = your.domain.com + +# Set the address of the Mail Server to send notifications through. +# e.g. mail.xyz.com +Mail Server = 127.0.0.1 + +# Specify the type of notification email you want to get. +# text or html (text/plain or text/html) +Mail Type = text/plain + +# Set what to do with infected files. +# keep ==> Store under the "Quarantine Dir" +# delete ==> Just delete them +#Action = delete +Action = keep + +# +# Advanced Features +# ================= +# +# Don't bother changing anything below this unless you really know what +# you are doing. +# + +# Set Debug to 1 to stop it running as a daemon +# and produce more verbose output +Debug = 0 + +# Where to put the virus scanning engine lock files. +# These lock files are used between MailScanner and the virus signature +# "autoupdate" scripts, to ensure that they aren't both working at the +# same time (which could cause MailScanner to let a virus through). +Lock File Dir = /tmp + +# Minimum acceptable code stability status -- if we come across code +# that's not at least as stable as this, we barf. +# This is currently only used to check that you don't end up using untested +# virus scanner support code without realising it. +# Levels used are: +# none - there may not even be any code. +# unsupported - code may be completely untested, a contributed dirty hack, +# anything, really. +# alpha - code is pretty well untested. Don't assume it will work. +# beta - code is tested a bit. It should work. +# supported - code *should* be reliable. +# +# Don't even *think* about setting this to anything other than "beta" or +# "supported" on a system that receives real mail until you have tested it +# yourself and are happy that it is all working as you expect it to. +# Don't set it to anything other than "supported" on a system that could +# ever receive important mail. +Minimum Code Status = supported +