// main.cpp // SNF MDaemon Plugin // Copyright (C) 2007-2008, ARM Research Labs, LLC. #include #include "mdconfiguration.hpp" #include "../SNF_Service/SNFMulti.hpp" #define DLL_EXPORT __declspec(dllexport) //////////////////////////////////////////////////////////////////////////////// // Constants And Tweaks //////////////////////////////////////////////////////////////////////////////// const int MDPLUGIN_MSG = 22000; const int MDPLUGIN_DISPLAY = 22001; const char* PLUGIN_VERSION_INFO = "SNF MDaemon Plugin Version 3.0 Build: " __DATE__ " " __TIME__; //////////////////////////////////////////////////////////////////////////////// // SNF MDaemon Plugin DLL Interface Setup. //////////////////////////////////////////////////////////////////////////////// extern "C" { // All of these "C" functions for export. BOOL WINAPI DllMain ( HINSTANCE hInst, DWORD wDataSeg, LPVOID lpvReserved ); // The DllMain starup/shutdown function. DLL_EXPORT void _stdcall Startup(HWND Parent); // Startup Function. DLL_EXPORT void _stdcall ConfigFunc(HWND Parent); // Configuration function. DLL_EXPORT void _stdcall MessageFunc(HWND Parent, const char* File); // Message Scan Function. DLL_EXPORT void _stdcall MessageIPFunc(HWND Parent, const char* File); // IP Scan Function. DLL_EXPORT void _stdcall Shutdown(HWND Parent); // Shutdown Function. } BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { // Thread attach/detach functions. switch (fdwReason) { // Switch on the reason for the call. case DLL_PROCESS_ATTACH: // Attach to process. /*** Startup function moved to API ***/ return true; // We will assume it worked. break; case DLL_PROCESS_DETACH: // Detach from process. /*** Shutdown function moved to API ***/ return true; // We will assume that worked. break; case DLL_THREAD_ATTACH: // Attach to thread break; // Just be happy. case DLL_THREAD_DETACH: // Detach from thread break; // Just be happy. } return true; // Be happy by default. } //////////////////////////////////////////////////////////////////////////////// // SNF MDaemon Plugin Engine Code //////////////////////////////////////////////////////////////////////////////// // Handy Debug Functions. This is how we can display critical events on screen // and emit entries into the MDaemon logs. HWND LatestParent = 0; // Handle MDaemon log operations. void sayToScreen(const string StuffToSay) { // Display a modal system message. MessageBox ( // Use a message box. GetFocus(), // Take the user's focus. StuffToSay.c_str(), // This is what to say. "Message Sniffer Plug-In", // This is how to title the box. MB_OK|MB_SYSTEMMODAL ); // This makes it modal with a button. } void sayToLog(const string Msg) { // Emit message into MDaemon log. if(0 != LatestParent) { // If we have a handle: COPYDATASTRUCT Packet; // Create a packet for the log. Packet.dwData = MDPLUGIN_DISPLAY; Packet.cbData = Msg.length(); Packet.lpData = reinterpret_cast( const_cast(Msg.c_str())); SendMessage(LatestParent, MDPLUGIN_MSG, MDPLUGIN_DISPLAY, // Send the packet. (LPARAM)(PCOPYDATASTRUCT)&Packet); } } // The configuration file path is theoretically in a fixed location based on // the installation directory of MDaemon. We read the installation directory // from the registry and derive the path from that. If we get what we want // then we return the full path to the SNFMDPlugin.xml file - which is derived // from snf_engine.xml. If we don't get what we want then we return nothing. string ConfigurationFilePath() { // Get the config file path. char data[256]; // Create a buffer. DWORD dwType = 0; // Type holder. DWORD dwCount = sizeof(data); // Data counter. HKEY rkey; // Key handle. string KeyLocation("SOFTWARE\\Alt-N Technologies\\MDaemon"); // Key location. string KeyValueName("AppPath"); // Value name. // Open the key and if successful read it's value. string ReadValue = ""; // We'll put the answer here. if(ERROR_SUCCESS == // Try to open the registry RegOpenKeyEx(HKEY_LOCAL_MACHINE, KeyLocation.c_str(), 0, KEY_READ, &rkey) ) { // If it opened successfully: RegQueryValueEx( // Read the MDaemon AppPath key. rkey, KeyValueName.c_str(), NULL, &dwType, (LPBYTE)&data, &dwCount ); ReadValue = data; // Convert the results to a string. RegCloseKey(rkey); // Close the registry key. // Now we must convert the APP path to our SNFMDPlugin path. int PathEnd = ReadValue.length(); // Grab the length. if(PathEnd > 0) { ReadValue.erase(PathEnd-1,1); } // Eat the stroke at the end. PathEnd = ReadValue.find_last_of("\\"); // Find the next stroke in. int PathLength = ReadValue.length(); // Grab the path length. int EraseSpan = PathLength - (PathEnd+1); // Calculate the amount to erase. ReadValue.erase(PathEnd+1,EraseSpan); // Erase it to get the root path. ReadValue.append("SNF\\SNFMDPlugin.xml"); } // Return either the empty string or the configuration file path. return ReadValue; // Return what we have. } // Here are out engine components, startup, and shutdown logic. The actual // engine components are built as the DLL is loaded. They are "lit up" by the // startup() function - which is called when an application attaches to this // DLL; and they are closed down when an application detaches from this DLL. // 20080311 _M Added configuration interpreter for and linked it // to the ConfigFunc() and MessageIPFunc() functions. volatile bool EngineIsGood = false; // True when things are ready to go. snf_RulebaseHandler* Rulebase = 0; // Our Rulebase Handler. snf_EngineHandler* ScanEngine = 0; // Our Scan Engine. int Generation = -1; // Configuration generation tag. string Configuration; // Configuration file path. MDConfiguration* PlatformConfig = 0; // Platform config interpreter. DLL_EXPORT void _stdcall Startup(HWND Parent) { // How to get all this started. LatestParent = Parent; EngineIsGood = false; // Start off pessimistically. try { // Be sure to catch any exceptions. Rulebase = new snf_RulebaseHandler(); // Create our rulebase handler. ScanEngine = new snf_EngineHandler(); // Create our engine scanner. Configuration = ConfigurationFilePath(); // Get the configuration path. PlatformConfig = new MDConfiguration(*Rulebase, Configuration); // Set up our configuration monitor. Rulebase->open(Configuration.c_str(), "", ""); // Open a configured rulebase. Rulebase->PlatformVersion(PLUGIN_VERSION_INFO); // Set the Platform version string. ScanEngine->open(Rulebase); // Open the scanning engine. EngineIsGood = true; // If all went well, we're up! sayToLog(Rulebase->EngineVersion()); // version info to show we're ok. sayToLog(Rulebase->PlatformVersion()); // Log our platform and engine string ConfigInfo = "SNF Config: "; // Build a configuration info ConfigInfo.append(Configuration); // message and display that sayToLog(ConfigInfo); // too. } catch(snf_RulebaseHandler::ConfigurationError) { // Can't work with config file! string ErrorMessage = "Unable to Configure with: "; // Tell them about it and give ErrorMessage.append(Configuration); // them a hint where we looked. sayToScreen(ErrorMessage); } catch(snf_RulebaseHandler::FileError) { // Can't load the rulebase file! string ErrorMessage = "Unable to Load Rulebase in: "; // Tell them about it and give ErrorMessage.append(Configuration); // them a hint where we looked. sayToScreen(ErrorMessage); } catch(snf_RulebaseHandler::AllocationError) { // Can't allocate memory! sayToScreen("Unable to Allocate Enough Memory!"); // Tell them about it. } catch(snf_RulebaseHandler::IgnoreListError) { // Can't load ignore list! sayToScreen("Unable to Load Ingore List!"); // Tell them about it. } catch(snf_RulebaseHandler::AuthenticationError) { // Can't authenticate! sayToScreen("Unable to Authenticate Rulebase!"); // Tell them about it. } catch(snf_RulebaseHandler::Busy) { // Busy?!! That's weird. sayToScreen("Busy Exception?!!"); // Tell them about it. } catch(snf_RulebaseHandler::Panic) { // Panic?!! That's weird. sayToScreen("Panic Exception?!!"); // Tell them about it. } catch(exception& e) { // Some other runtime error? sayToScreen(e.what()); // Tell them what it was. } catch(...) { // Catch the unknown exception. sayToScreen("Unexpected Exception!"); // Tell them things didn't work. } return; // All done. } /****** THE FOLLOWING IS DEFUNCT, BUT USEFUL INFO SO WE KEEP IT! ******/ /**** *Note: Why do we always return true even when we fail??? ***** Because, in a DLL that is being loaded, none of the threads that ***** get created in the rulebase object will get any cycles until the ***** DLL load sequence is complete _AND_SUCCESSFUL_! ***** ***** The same is true when we return false -- because then the OS KNOWS ***** that it's not safe to run any of the threads we've created. ***** ***** As a result, if we try to destroy our rulebase manager after an ***** unsuccessful load then all of the stop() calls to our threads will ***** block waiting for the initialized threads to see their stop bits and ***** end. Since none of these threads actually get any love until the ***** DLL is successfully loaded, we end up waiting happily for ever! ***** ***** Instead of stepping into this world of hurt, we've decided to leave ***** the internal flag set to indicate that the engine is Not-Good so that ***** the scanning functions remain inert, and then we wait patiently for ***** the DLL to be unloaded. ***** ***** When that happens, since the OS KNOWS the DLL was loaded happily ***** before, the objects can go through their normal destruction without ***** deadlocking on their stop() functions (join()). ****/ DLL_EXPORT void _stdcall Shutdown(HWND Parent) { // How to get all this stopped. LatestParent = Parent; if(EngineIsGood) { // If the engine is good: try { EngineIsGood = false; // Stop using the engine now. LatestParent = 0; // No more logging calls. if(ScanEngine) { // Close the scanner if it exists: ScanEngine->close(); // Close it. delete ScanEngine; // Delete it. ScanEngine = 0; // Forget it. } if(Rulebase) { // Close the rulebase if it exists. Rulebase->close(); // Close it. delete Rulebase; // Delete it. Rulebase = 0; // Forget it. } if(PlatformConfig) { // Drop the PlatformConfig if it be. delete PlatformConfig; // Delete it. PlatformConfig = 0; // Forget it. } Generation = -1; // Reset our config gen tag. sayToLog("SNF: Plugin Shutdown."); // Tell them we're shut down. } catch(exception& e) { // If we have a runtime exception string InShutdown = "SNF, Shutdown: "; // make a human friendly message InShutdown.append(e.what()); // and package the exception for sayToScreen(InShutdown); // a screen pop-up. } catch(...) { // If we see some other kind of sayToScreen("SNF, Shutdown: Unknown Exception"); // exception then show that. } } return; // All done. } // Now we get to the business end of the plugin. Three functions are exported. // ConfigFunc() launches an editor for the configuration file. // 20080311 _M Adjusted ConfigFunc to use Platform Configuration Parameters. DLL_EXPORT void _stdcall ConfigFunc(HWND Parent) { // Configuration function. LatestParent = Parent; // Capture the parent for logging. string ConfiguratorString = PlatformConfig->ConfiguratorCommand(); // Get Configurator launch command. if(0 < ConfiguratorString.length()) system(ConfiguratorString.c_str()); // If we have a launch command use it. } // MessageFunc() scans a message file and injects headers. DLL_EXPORT void _stdcall MessageFunc(HWND Parent, const char* File) { // Message Scan Function. LatestParent = Parent; // Capture the parent for logging. string LogMessage = "SNF MessageScan: "; // Start a plugin log entry. LogMessage.append(File); // add the file name. if(false == EngineIsGood) { // If the engine is not up then LogMessage.append(", Engine Not Ready!"); // report that in the plug-in log. sayToLog(LogMessage); // Post our entry to the plug-in log. return; // We're done. } // We are ready to begin our scan. int ResultCode = 0; // Keep track of the scan result. try { // Be sure to catch any exceptions. ResultCode = ScanEngine->scanMessageFile(File); } catch(snf_EngineHandler::FileError& e) { // Exception when a file won't open. LogMessage.append(", File Error!"); sayToLog(LogMessage); string DebugData = "SNF Debug: "; DebugData.append(e.what()); sayToLog(DebugData); return; } catch(snf_EngineHandler::XHDRError& e) { // Exception when XHDR Inject/File fails. LogMessage.append(", X-Header Error!"); sayToLog(LogMessage); string DebugData = "SNF Debug: "; DebugData.append(e.what()); sayToLog(DebugData); return; } catch(snf_EngineHandler::BadMatrix& e) { // Exception out of bounds of matrix. LogMessage.append(", Bad Matrix!"); sayToLog(LogMessage); string DebugData = "SNF Debug: "; DebugData.append(e.what()); sayToLog(DebugData); return; } catch(snf_EngineHandler::MaxEvals& e) { // Exception too many evaluators. LogMessage.append(", Too Many Evaluators!"); sayToLog(LogMessage); string DebugData = "SNF Debug: "; DebugData.append(e.what()); sayToLog(DebugData); return; } catch(snf_EngineHandler::AllocationError& e) { // Exception when we can't allocate something. LogMessage.append(", Allocation Error!"); sayToLog(LogMessage); string DebugData = "SNF Debug: "; DebugData.append(e.what()); sayToLog(DebugData); return; } catch(snf_EngineHandler::Busy& e) { // Exception when there is a collision. LogMessage.append(", Engine Busy!"); sayToLog(LogMessage); string DebugData = "SNF Debug: "; DebugData.append(e.what()); sayToLog(DebugData); return; } catch(snf_EngineHandler::Panic& e) { // Exception when something else happens. LogMessage.append(", Engine Panic!"); sayToLog(LogMessage); string DebugData = "SNF Debug: "; DebugData.append(e.what()); sayToLog(DebugData); return; } catch(exception& e) { // Some unexpected exception. LogMessage.append(", Unexpected Exception!"); sayToLog(LogMessage); string DebugData = "SNF Debug: "; DebugData.append(e.what()); sayToLog(DebugData); return; } catch(...) { // We should never get one of these, but LogMessage.append(", Non-Std Exception!"); // if we do we have something to say. sayToLog(LogMessage); return; } // At this point we know our scan worked ok. So, let's post the result. ostringstream O; // A stringstream makes it easy to O << LogMessage << ", Result=" << ResultCode; // format our plug-in log entry. sayToLog(O.str()); // Then we post it to the plug-in log. } // ControlFilePath(MessageFilePath) Converts .msg path to .ctl path. string ControlFilePath(string MessageFilePath) { // Convert .msg/.tmp to .ctl path. const string ControlFileExt(".ctl"); // Control file extension. const string EmptyString(""); // The empty string. string ControlFilePath(MessageFilePath); // Start with the message path. string::size_type CFExtPosition = ControlFilePath.find_last_of('.'); // Find the extension. if(string::npos == CFExtPosition) return EmptyString; // If we didn't find it return "" ControlFilePath.replace( // Replace the message file CFExtPosition, ControlFileExt.length(), // extension with the control ControlFileExt // file extension. ); return ControlFilePath; } // MessageIPFunc() looks at the control file for a message, extracts the // source IP, and deletes the message if the IP is in the truncate range. DLL_EXPORT void _stdcall MessageIPFunc(HWND Parent, const char* File) { // IP Scan Function. LatestParent = Parent; // Capture the parent for logging. if(false == PlatformConfig->MessageIPFuncOn()) { // If the IP func is off: return; // We're done. } string LogMessage = "SNF IPScan: "; // Start a plugin log entry. LogMessage.append(File); // add the file name. if(false == EngineIsGood) { // If the engine is not up then LogMessage.append(", Engine Not Ready!"); // report that in the plug-in log. sayToLog(LogMessage); // Post our entry to the plug-in log. return; // We're done. } // Open the control file and find the RemoteIP const string CtlRemoteIP("RemoteIP="); // This is what we're looking for. string RemoteIP = ""; // This is where it will go. try { // Do this safely. ifstream ControlFile(ControlFilePath(File).c_str()); // Open the control file. while(ControlFile) { // While the file is good... string Line; // Read one line at a time getline(ControlFile, Line); // into a string. string::size_type TagPos = Line.find(CtlRemoteIP); // Look for the RemoteIP tag. if(string::npos != TagPos) { // If we find the RemoteIP tag: RemoteIP = Line.substr(TagPos + CtlRemoteIP.length()); // Get the string after the tag break; // We're done with the ctl file! } } ControlFile.close(); // Be nice and close our file. } catch(...) {} // Eat any exceptions. if(0 == RemoteIP.length()) { // If we didn't get the IP then LogMessage.append(", Didn't Get Remote IP!"); // make that the rest of our log sayToLog(LogMessage); // entry and post it. return; // We're done. } // At this point we have the remote IP to check. LogMessage.append(", "); LogMessage.append(RemoteIP); // Add the IP to the log entry. IPTestRecord IPAnalysis(RemoteIP); // Set up a test record. try { // Safely, Rulebase->performIPTest(IPAnalysis); // check the IP w/ GBUdb. } catch(...) { // If we encountered a problem LogMessage.append(", Analysis Failed!"); // then we need to report that sayToLog(LogMessage); // our analysis failed. return; // We're done. } // At this point we've got a good analysis, so we take action (if needed) // and produce a helpful log entry. ostringstream Happy; // A stringstream for easy formatting. Happy << LogMessage << ", {"; // Start the analysis report. switch(IPAnalysis.G.Flag()) { // Identify the flag data for this IP. case Good: Happy << "Good, "; break; case Bad: Happy << "Bad, "; break; case Ugly: Happy << "Ugly, "; break; case Ignore: Happy << "Ignore, "; break; } Happy << "p=" << IPAnalysis.G.Probability() << ", " // Probability and Confidence. << "c=" << IPAnalysis.G.Confidence() << ", "; switch(IPAnalysis.R) { // The Range analysis. case Unknown: { Happy << " Unknown}"; break; } // Unknown - not defined. case White: { Happy << " White}"; break; } // This is a good guy. case Normal: { Happy << " Normal}"; break; } // Benefit of the doubt. case New: { Happy << " New}"; break; } // It is new to us. case Caution: { Happy << " Caution}"; break; } // This is suspicious. case Black: { Happy << " Black}"; break; } // This is bad. case Truncate: { Happy << " Truncate}"; break; } // This is bad unless we ignore it. } string WhatWeDid; // So, what do we do? if( // Are we in Truncate land? (Truncate == IPAnalysis.R && Ugly == IPAnalysis.G.Flag()) || // If the IP is Truncate & Ugly, or Bad == IPAnalysis.G.Flag() // If the IP is just plain bad then ) { // we are in truncate land so we Happy << " Rejected!"; WhatWeDid = "Rejected"; // mark the message Rejected! and remove(File); // remove the file to reject it! } else { // If we are not rejecting the Happy << " Allowed."; WhatWeDid = "Allowed"; // message tell them it's allowed. } Rulebase->logThisIPTest(IPAnalysis, WhatWeDid); // Log the event. sayToLog(Happy.str()); // Post to the plug-in log. }