// SNFMultiDLL.cpp // Copyright (C) ARM Research Labs, LLC // // Provides a full SNFMulti engine with dynamic scanner allocation. #ifdef WIN32 // Required because threading.hpp includes windows.h. #include #endif #include #include #include #include "../SNFMulti/snfmulti.hpp" using namespace std; const char* SNF_DLL_VERSION = "SNFMulti DLL Version 3.0 Build: " __DATE__ " " __TIME__; const int snf_ERROR_NO_HANDLE = -1; const int snf_ERROR_SCAN_FAILED = -2; const int snf_ERROR_EXCEPTION = -3; const double snf_ReputationMehResult = 0.0; /* Don't know or don't care */ const double snf_ReputationBadResult = 1.0; /* IP is pure evil */ const double snf_ReputationGoodResult = -1.0; /* IP is pure good */ //////////////////////////////////////////////////////////////////////////////// // Here we define the interface provided by this DLL. #define EXP __declspec(dllexport) extern "C" { BOOL WINAPI DllMain ( HINSTANCE hInst, DWORD wDataSeg, LPVOID lpvReserved ); // Main DLL setup function. EXP int setThrottle(int Threads); // Set scan thread limit. EXP int startupSNF(char* Path); // Start SNF with configuration. EXP int startupSNFAuthenticated(char* Path, char* Lic, char* Auth); // Start SNF with conf & auth. EXP int shutdownSNF(); // Shutdown SNF. EXP int testIP(unsigned long int IPToCheck); // Test the IP for a GBUdb range. EXP double getIPReputation(unsigned long int IPToCheck); // Get reputation figure for IP. EXP int scanBuffer(unsigned char* Bfr, int Length, char* Name, int Setup); // Scan msgBuffer, name, setup time. EXP int scanFile(char* FilePath, int Setup); // Scan msgFile, setup time. EXP int getScanXHeaders(int ScanHandle, char** Bfr, int* Length); // Get result & XHeaders. EXP int getScanXMLLog(int ScanHandle, char** Bfr, int* Length); // Get result & XML Log. EXP int getScanClassicLog(int ScanHandle, char** Bfr, int* Length); // Get result & Classic Log. EXP int getScanResult(int ScanHandle); // Get just the scan result. EXP int closeScan(int ScanHandle); // Close the scan result. } //////////////////////////////////////////////////////////////////////////////// // Here are the guts of the engine. // // A ScannerInstance encapsulates an snf_EngineHandler and buffers for the // scan results. The buffers can safely be passed back to the calling app. class ScannerInstance : public snf_EngineHandler { // Scanner Instance. private: int Ordinal; // Instance ordinal. int ScanResult; // Current scan result. string ScanXHeaders; // Buffered X-Headers. char* ScanXHeadersPtr; // Buffered X-Headers pointer. int ScanXHeadersLength; // Buffered X-Headers length. string ScanXMLLog; // Buffered XML Log. char* ScanXMLLogPtr; // Buffered XML Log pointer. int ScanXMLLogLength; // Buffered XML Log length. string ScanClassicLog; // Buffered Classic Log. char* ScanClassicLogPtr; // Buffered Classic Log pointer. int ScanClassicLogLength; // Buffered Classic Log length. void clearBuffers(); // Clears the buffers & result. public: ScannerInstance(snf_RulebaseHandler* RBH, int N); // Initialize w/ Rulebase & Ordinal ~ScannerInstance(); // Cleanup before going away. int scanBuffer(unsigned char* Bfr, int Length, char* Name, int Setup); // Scan message buffer, give handle. int scanFile(char* FilePath, int Setup); // Scan message file, give handle. int getScanXHeaders(char** Bfr, int* Length); // Return scan result & X headers. int getScanXMLLog(char** Bfr, int* Length); // Return scan result & XML log. int getScanClassicLog(char** Bfr, int* Length); // Return scan result & Classic log. int getScanResult(); // Return scan result. }; const int XHeaderReserve = 4096; // Space to reserve for X headers. const int XMLLogReserve = 4096; // Space to reserve for XML log data. const int ClassicReserve = 4096; // Space to reserve for Classic log data. ScannerInstance::ScannerInstance(snf_RulebaseHandler* RBH, int N) : // Initialize. Ordinal(N), // Ordinal as assigned. ScanResult(snf_ERROR_UNKNOWN), // No good result yet. ScanXHeaders(XHeaderReserve, '\0'), // Pre-allocate some space for our ScanXMLLog(XMLLogReserve, '\0'), // result caching buffers (strings) ScanClassicLog(ClassicReserve, '\0') { // so we might not have to later. clearBuffers(); // Clear the buffers. open(RBH); // Open the engine handler. } ScannerInstance::~ScannerInstance() { // When shutting down close(); // close the engine handler clearBuffers(); // and clear the buffers. Ordinal = -1; // Invalidate our handle. } // In the C world, 0 cannot be a handle, so when we return a "handle" from // our scanXX() methods we must convert the scanner's ordinal into a "handle" // by adding 1. Later on when we get a scanner by it's handle, that function // reverses the conversion by subtracting 1 from the handle to get the ordinal. int ScannerInstance:: scanBuffer(unsigned char* Bfr, int Length, char* Name, int Setup) { // Scan a message buffer. try { // Prepare to capture exceptions. clearBuffers(); // Clear the buffers first. ScanResult = scanMessage(Bfr, Length, Name, Setup); // Scan and capture the result. } // Translate exceptions into catch(snf_EngineHandler::FileError& e) { // result codes and capture them. ScanResult = snf_ERROR_MSG_FILE; } catch(snf_EngineHandler::XHDRError& e) { ScanResult = snf_ERROR_MSG_FILE; } catch(snf_EngineHandler::BadMatrix& e) { ScanResult = snf_ERROR_BAD_MATRIX; } catch(snf_EngineHandler::MaxEvals& e) { ScanResult = snf_ERROR_MAX_EVALS; } catch(snf_EngineHandler::AllocationError& e) { ScanResult = snf_ERROR_ALLOCATION; } catch(...) { ScanResult = snf_ERROR_UNKNOWN; } return (Ordinal + 1); // Return our handle. } int ScannerInstance:: scanFile(char* FilePath, int Setup) { // Scan a message file. try { // Prepare to capture exceptions. clearBuffers(); // Clear the buffers first. ScanResult = scanMessageFile(FilePath, Setup); // Scan and capture the result. } // Translate exceptions into catch(snf_EngineHandler::FileError& e) { // result codes and capture them. ScanResult = snf_ERROR_MSG_FILE; } catch(snf_EngineHandler::XHDRError& e) { ScanResult = snf_ERROR_MSG_FILE; } catch(snf_EngineHandler::BadMatrix& e) { ScanResult = snf_ERROR_BAD_MATRIX; } catch(snf_EngineHandler::MaxEvals& e) { ScanResult = snf_ERROR_MAX_EVALS; } catch(snf_EngineHandler::AllocationError& e) { ScanResult = snf_ERROR_ALLOCATION; } catch(...) { ScanResult = snf_ERROR_UNKNOWN; } return (Ordinal + 1); // Return our handle. } void ScannerInstance::clearBuffers() { // Clear the buffers. ScanXHeaders.clear(); // Clear the buffer strings ScanXHeadersPtr = 0; // zero their pointers and ScanXHeadersLength = 0; // zero their lengths. ScanXMLLog.clear(); ScanXMLLogPtr = 0; ScanXMLLogLength = 0; ScanClassicLog.clear(); ScanClassicLogPtr = 0; ScanClassicLogLength = 0; ScanResult = snf_ERROR_UNKNOWN; } int ScannerInstance::getScanXHeaders(char** Bfr, int* Length) { // Return the result & X headers. if(0 == ScanXHeadersPtr) { // If we haven't already then ScanXHeaders = getXHDRs(); // capture the data. ScanXHeadersLength = ScanXHeaders.length(); ScanXHeadersPtr = const_cast(ScanXHeaders.c_str()); } *Bfr = ScanXHeadersPtr; // Pass the pointer and *Length = ScanXHeadersLength; // length back to the caller. return ScanResult; // Return the scan result. } int ScannerInstance::getScanXMLLog(char** Bfr, int* Length) { // Return the result & XML log. if(0 == ScanXMLLogPtr) { // If we haven't already then ScanXMLLog = getXMLLog(); // capture the data. ScanXMLLogPtr = const_cast(ScanXMLLog.c_str()); ScanXMLLogLength = ScanXMLLog.length(); } *Bfr = ScanXMLLogPtr; // Pass the pointer and *Length = ScanXMLLogLength; // length back to the caller. return ScanResult; // Return the scan result. } int ScannerInstance::getScanClassicLog(char** Bfr, int* Length) { // Return the result & Classic log. if(0 == ScanClassicLogPtr) { // If we haven't already then ScanClassicLog = getClassicLog(); // capture the data. ScanClassicLogPtr = const_cast(ScanClassicLog.c_str()); ScanClassicLogLength = ScanClassicLog.length(); } *Bfr = ScanClassicLogPtr; // Pass the pointer and *Length = ScanClassicLogLength; // length back to the caller. return ScanResult; // Return the scan result. } int ScannerInstance::getScanResult() { // Return the scan result only. return ScanResult; // Just like that. } // The snfScannerPool starts up and shuts down the rulebase engine and keeps // track of the ScannerInstances. class snfScannerPool { // Dynamically allocated scanner pool. private: codedweller::Mutex myMutex; // Protect the state of the pool. snf_RulebaseHandler* myRulebaseHandler; // This is our rulebase manager. vector ScannerPool; // This is a collection of scanners. codedweller::ProductionQueue AvailableScanners; // This is the available scanners queue. unsigned int ScanThreadLimit; // Maximum number of concurrent threads. public: snfScannerPool(); // This is how we are constructed. ~snfScannerPool(); // This is how we are destroyed. void Throttle(int ThreadLimit); // Set a Scan Thread Limit. void startup(char* ConfigurationPath); // Startup the engine w/ config. void startupAuthenticated(char* Path, char* Lic, char* Auth); // Startup engine w/ full auth. void shutdown(); // Shutdown the engine. int testIP(unsigned long int IPToCheck); // Test an IP against gbudb. double getIPReputation(unsigned long int IPToCheck); // This is how we get IP reps. ScannerInstance* grabScanner(); // Get a new (available) scanner. ScannerInstance* getScanner(int ScanHandle); // What scanner goes with this handle? void dropScanner(ScannerInstance* S); // When done with a scanner, drop it. }; snfScannerPool Scanners; // Call our local pool "Scanners". snfScannerPool::snfScannerPool() : // When created, our myRulebaseHandler(0), // rulebase handler is 0. ScanThreadLimit(0) {} // There is no limit. snfScannerPool::~snfScannerPool() { // When destroyed we must shutdown(); // shutdown if needed. } void snfScannerPool::Throttle(int ThreadLimit) { // Set a scanner thread limit. ThreadLimit = (0 > ThreadLimit) ? 0 : ThreadLimit; // Throttle will be 0 or above. ScanThreadLimit = ThreadLimit; } void snfScannerPool::startup(char* ConfigurationPath) { // Start the engine with this config. codedweller::ScopeMutex StateIsInFlux(myMutex); // Protect our state data. if(myRulebaseHandler) return; // If we are started, do nothing. myRulebaseHandler = new snf_RulebaseHandler(); // Create a new rulebase handler. myRulebaseHandler->PlatformVersion(SNF_DLL_VERSION); // Record our version identification. myRulebaseHandler->open(ConfigurationPath, "", ""); // Open the rulebase with the config. } void snfScannerPool::startupAuthenticated(char* Path, char* Lic, char* Auth) { // Startup engine w/ full auth. codedweller::ScopeMutex StateIsInFlux(myMutex); // Protect our state data. if(myRulebaseHandler) return; // If we are started, do nothing. myRulebaseHandler = new snf_RulebaseHandler(); // Create a new rulebase handler. myRulebaseHandler->PlatformVersion(SNF_DLL_VERSION); // Record our version identification. myRulebaseHandler->open(Path, Lic, Auth); // Open rulebase with full auth. } void snfScannerPool::shutdown() { // Shutdown the engine. codedweller::ScopeMutex StateIsInFlux(myMutex); // Protect our state data. if( 0 == ScannerPool.size() && 0 == AvailableScanners.size() && 0 == myRulebaseHandler ) return; // If we are down don't bother. if(ScannerPool.size() != AvailableScanners.size()) throw false; // If some scanners are in use throw! for( vector::iterator iS = ScannerPool.begin(); // Destroy all of the scanner iS != ScannerPool.end(); // instances in the pool. iS++) delete (*iS); ScannerPool.clear(); // Empty the pool of pointers. while(0 < AvailableScanners.size()) AvailableScanners.take(); // Empty the available scanners. if(myRulebaseHandler) { // Safely destroy the rulebase handler. myRulebaseHandler->close(); // Close it first, delete myRulebaseHandler; // then delete it, myRulebaseHandler = 0; // then forget about it. } } int snfScannerPool::testIP(unsigned long int IPToCheck) { // This is how we test IPs. codedweller::ScopeMutex StayAsYouAre(myMutex); // Hold things steady while we do this. if(0 == myRulebaseHandler) return snf_ERROR_NO_HANDLE; // If we have no engine, punt! IPTestRecord Tester(IPToCheck); // Make up a test record for this IP. myRulebaseHandler->performIPTest(Tester); // Run it past the engine. return static_cast(Tester.R); // IPRange is an enum, so use as an int. } double snfScannerPool::getIPReputation(unsigned long int IPToCheck) { // This is how we get IP reps. codedweller::ScopeMutex StayAsYouAre(myMutex); // Hold things steady while we do this. if(0 == myRulebaseHandler) return snf_ERROR_NO_HANDLE; // If we have no engine, punt! IPTestRecord Tester(IPToCheck); // Make up a test record for this IP. myRulebaseHandler->performIPTest(Tester); // Run it past the engine. // Now that we have a result from the engine let's translate the result. double Reputation = snf_ReputationMehResult; // Reputation figures are precise. switch(Tester.G.Flag()) { // Flags get priority in our translation. case Good: Reputation = snf_ReputationGoodResult; break; // A good flag means the IP is pure good. case Bad: Reputation = snf_ReputationBadResult; break; // A bad flag means the IP is pure evil. case Ignore: Reputation = snf_ReputationMehResult; break; // An ignore flag means we don't care. default: { // Ugly means we calculate the reputation Reputation = // figure from the statistics. Start by sqrt(fabs(Tester.G.Probability() * Tester.G.Confidence())); // combining the c & p figures then if(0 > Tester.G.Probability()) Reputation *= -1.0; // flip the sign if p is negative. } } return Reputation; // This is what we say about this IP. } // In order to balance off speed and safety we control access when starting // up, shutting down, or getting a new scanner. The grabScanner operation is // usually very fast. // // The the calling application behaves properly it will never attempt a scan // while attempting to shutdown, and will never attempt to shutdown while it // is performing a scan. If this conflict does occur then one of two cases // arise: // // The shutdown() thread grabs the mutex before the grabScanner() thread, // the engine is shutdown, then the grabScanner() thread finds the engine // is down and cannot return a scanner. // // The grabScanner() thread gets the mutex before the shutdown() thread, // a scanner is allocated, and the shutdown() thread discovers an imabalance // in the number of Available scanners and the number of scanners in the pool. // When it discovers that it will throw false and the C interface function will // return an error. ScannerInstance* snfScannerPool::grabScanner() { // Get the next available scanner. codedweller::ScopeMutex FreezeOurState(myMutex); // Don't change while I'm doing this. if(0 == myRulebaseHandler) return 0; // If we're not alive return nothing. if(1 > AvailableScanners.size()) { // No scanners? See about making one. if( 0 == ScanThreadLimit || // If there is no limit OR ScanThreadLimit > ScannerPool.size() // we have room for more scanners ) { // then we can make another one: ScannerInstance* NewScanner = new ScannerInstance(myRulebaseHandler, ScannerPool.size()); // Start up a new scanner and ScannerPool.push_back(NewScanner); // add it to the pool. AvailableScanners.give(NewScanner); // Make the new scanner available. } // If we have reached the limit on } // scanners we will wait for one. return AvailableScanners.take(); // Get a scanner from the pool. } // Since handles are one bigger than the ordinal (so 0 isn't a handle) // we must convert the ScanHandle we're given into a proper ordinal so // we can match the scanner's position in the pool. The reverse happens // upon scan requestes. ScannerInstance* snfScannerPool::getScanner(int ScanHandle) { // Get the scanner for this handle. ScannerInstance* S = 0; // We're gonna get a scanner. try { S = ScannerPool.at(ScanHandle - 1); } // Safely get the scanner at Ordinal. catch(...) { return 0; } // If something goes wrong, no scanner. return S; // Return the scanner we got. } void snfScannerPool::dropScanner(ScannerInstance* S) { // When done with a scanner, drop it. if( // If : 0 != S && // - we have a good scanner pointer and 0 != myRulebaseHandler // - we are not dead yet ) AvailableScanners.give(S); // then make the scanner available. } //////////////////////////////////////////////////////////////////////////////// // Interface Functions BOOL WINAPI DLLMain ( HINSTANCE hInst, DWORD wDataSeg, LPVOID lpvReserved ) { try { switch(wDataSeg) { // Handle setup & teardown msgs. case DLL_PROCESS_ATTACH: // If the DLL is being loaded return true; // return true. Nothing to do yet. break; case DLL_PROCESS_DETACH: // If the DLL is being dropped Scanners.shutdown(); // be sure we are shut down first. break; default: // For all other messages (thread stuff) return true; // we'll just return ok because break; // we don't have any of that here. } } catch(...) {}; // Do not throw exceptions. // We won't actually get here but we return false; // want to make the compiler happy :-) } EXP int setThrottle(int Threads) { // Set scan thread limit. try { Scanners.Throttle(Threads); // Set the throttle limit } catch(...) { return snf_ERROR_EXCEPTION; } return Threads; // and return the value. } EXP int startupSNF(char* Path) { // Start the engine. try { // Use a try block to translate Scanners.startup(Path); // exceptions into result codes. } catch(snf_RulebaseHandler::AuthenticationError &e) { // Catch and translage exceptions. return snf_ERROR_RULE_AUTH; } catch(snf_RulebaseHandler::AllocationError& e) { return snf_ERROR_ALLOCATION; } catch(snf_RulebaseHandler::FileError& e) { return snf_ERROR_RULE_FILE; } catch(...) { return snf_ERROR_EXCEPTION; } return snf_SUCCESS; // No exceptions, All happy :-) } EXP int startupSNFAuthenticated(char* Path, char* Lic, char* Auth) { // Start SNF with conf & auth. try { // Use a try block to translate Scanners.startupAuthenticated(Path, Lic, Auth); // exceptions into result codes. } catch(snf_RulebaseHandler::AuthenticationError &e) { // Catch and translage exceptions. return snf_ERROR_RULE_AUTH; } catch(snf_RulebaseHandler::AllocationError& e) { return snf_ERROR_ALLOCATION; } catch(snf_RulebaseHandler::FileError& e) { return snf_ERROR_RULE_FILE; } catch(...) { return snf_ERROR_EXCEPTION; } return snf_SUCCESS; // No exceptions, All happy :-) } EXP int shutdownSNF() { // Shut down the engine. try { // Use a try block to translate Scanners.shutdown(); // exceptions into result codes. } catch(snf_RulebaseHandler::AuthenticationError &e) { // Catch and translage exceptions. return snf_ERROR_RULE_AUTH; } catch(snf_RulebaseHandler::AllocationError& e) { return snf_ERROR_ALLOCATION; } catch(snf_RulebaseHandler::FileError& e) { return snf_ERROR_RULE_FILE; } catch(...) { return snf_ERROR_EXCEPTION; } return snf_SUCCESS; // No exceptions, All happy :-) } EXP int testIP(unsigned long int IPToCheck) { // Test the IP for a GBUdb range. int Result = snf_ERROR_EXCEPTION; // On panics return this. try { Result = Scanners.testIP(IPToCheck); // Testing an IP is easy. } catch(...) {} // Do not throw exceptions. return Result; // Return the result. } EXP double getIPReputation(unsigned long int IPToCheck) { // Get reputation figure for IP. double Result = snf_ReputationMehResult; // On panics return this. try { Result = Scanners.getIPReputation(IPToCheck); // Getting an IP rep is easy. } catch(...) {} // Do not throw exceptions. return Result; // Return the result. } EXP int scanBuffer(unsigned char* Bfr, int Length, char* Name, int Setup) { // Scan a message. ScannerInstance* myScanner = 0; // We need a scanner. try { myScanner = Scanners.grabScanner(); } // Safely get a scanner. catch(...) { return snf_ERROR_NO_HANDLE; } // If that fails, we're done. int ScannerHandle = snf_ERROR_NO_HANDLE; // We will return a handle. try { ScannerHandle = myScanner->scanBuffer(Bfr, Length, Name, Setup); } // Perform the scan. catch(...) { // If we get an unhandled Scanners.dropScanner(myScanner); // exception then drop the return snf_ERROR_SCAN_FAILED; // scanner and return the failure. } // If all goes well then return ScannerHandle; // return our scanner's handle. } EXP int scanFile(char* FilePath, int Setup) { ScannerInstance* myScanner = 0; // We need a scanner. try { myScanner = Scanners.grabScanner(); } // Safely get a scanner. catch(...) { return snf_ERROR_NO_HANDLE; } // If that fails, we're done. int ScannerHandle = snf_ERROR_NO_HANDLE; // We will return a handle. try { ScannerHandle = myScanner->scanFile(FilePath, Setup); } // Perform the scan. catch(...) { // If we get an unhandled Scanners.dropScanner(myScanner); // exception then drop the return snf_ERROR_SCAN_FAILED; // scanner and return the failure. } // If all goes well then return ScannerHandle; // return our scanner's handle. } EXP int getScanXHeaders(int ScanHandle, char** Bfr, int* Length) { // Return the XHeaders for a scan. ScannerInstance* myScanner = 0; // We need the scanner. try { myScanner = Scanners.getScanner(ScanHandle); } // Try to get the scanner by handle. catch(...) { return snf_ERROR_NO_HANDLE; } // If we can't, we're broken. if(0 == myScanner) return snf_ERROR_NO_HANDLE; // A zero pointer is also broken. int Result = snf_ERROR_EXCEPTION; // On panic return this error code. try { Result = myScanner->getScanXHeaders(Bfr, Length); // If we can, get our data. } catch(...) {} // Do not throw exceptions. return Result; // Return our result. } EXP int getScanXMLLog(int ScanHandle, char** Bfr, int* Length) { // Return the XML Log for a scan. ScannerInstance* myScanner = 0; // We need the scanner. try { myScanner = Scanners.getScanner(ScanHandle); } // Try to get the scanner by handle. catch(...) { return snf_ERROR_NO_HANDLE; } // If we can't, we're broken. if(0 == myScanner) return snf_ERROR_NO_HANDLE; // A zero pointer is also broken. int Result = snf_ERROR_EXCEPTION; // On panic return this error code. try { Result = myScanner->getScanXMLLog(Bfr, Length); // If we can, get our data. } catch(...) {} // Do not throw exceptions. return Result; // Return our result. } EXP int getScanClassicLog(int ScanHandle, char** Bfr, int* Length) { // Return the classic log for a scan. ScannerInstance* myScanner = 0; // We need the scanner. try { myScanner = Scanners.getScanner(ScanHandle); } // Try to get the scanner by handle. catch(...) { return snf_ERROR_NO_HANDLE; } // If we can't we're broken. if(0 == myScanner) return snf_ERROR_NO_HANDLE; // A zero pointer is also broken. int Result = snf_ERROR_EXCEPTION; // On panic return this error code. try { Result = myScanner->getScanClassicLog(Bfr, Length); // If we can, get our data. } catch(...) {} // Do not throw exceptions. return Result; // Return our result. } EXP int getScanResult(int ScanHandle) { // Return just the scan result. ScannerInstance* myScanner = 0; // We need the scanner. try { myScanner = Scanners.getScanner(ScanHandle); } // Try to get the scanner by handle. catch(...) { return snf_ERROR_NO_HANDLE; } // If we can't we're broken. if(0 == myScanner) return snf_ERROR_NO_HANDLE; // A zero pointer is also broken. return myScanner->getScanResult(); // If we can, get our data. } EXP int closeScan(int ScanHandle) { // Return the scanner to the pool. ScannerInstance* myScanner = 0; // We need the scanner. try { myScanner = Scanners.getScanner(ScanHandle); } // Try to get the scanner by handle. catch(...) { return snf_ERROR_NO_HANDLE; } // If we can't we're broken. if(0 == myScanner) return snf_ERROR_NO_HANDLE; // A zero pointer is also broken. try { Scanners.dropScanner(myScanner); } // If we can then drop the scanner. catch(...) { return snf_ERROR_EXCEPTION; } // Convert exceptions to a panic code. return snf_SUCCESS; // If all ok then return success. }