Browse Source

setup

git-svn-id: https://svn.microneil.com/svn/SNFMilter/trunk@1 2c985dca-31e6-41a4-b4a2-d8f5b7f8e074
master
madscientist 16 years ago
commit
5399b9b1be
8 changed files with 3413 additions and 0 deletions
  1. 103
    0
      MailHeaders.cpp
  2. 102
    0
      MailHeaders.hpp
  3. 34
    0
      Makefile.am
  4. 48
    0
      ProductionQueue.hpp
  5. 1382
    0
      SNFMilter.cpp
  6. 1315
    0
      SNFMilter.cpp.new
  7. 258
    0
      SNFMilter.hpp
  8. 171
    0
      main.cpp

+ 103
- 0
MailHeaders.cpp View File

@@ -0,0 +1,103 @@
// MailHeaders.cpp
// Copyright (C) 2008 ARM Research Labs, LLC.
// See www.armresearch.com for the copyright terms.
//
// This file contains the functions to extract the mail header and
// body from the string returned by SNFMilterEngine::XHeaders().

#include <stdexcept>
#include <sstream>
#include "MailHeaders.hpp"

using namespace std;

const string DefaultSMTPENDL = "\r\n"; ///< Default SMTP endline.
const string DefaultLibmilterENDL = "\n "; ///< Default libmilter endline.
const string DefaultPrefix = "X-"; ///< Default start of header line.

MailHeaders::MailHeaders() :
SMTPENDL(DefaultSMTPENDL),
LibMilterENDL(DefaultLibmilterENDL) {
}

MailHeaders::~MailHeaders() {
}

void
MailHeaders::loadHeaders(const string &HeaderBuffer) {
Buffer = HeaderBuffer;
}

bool
MailHeaders::getHeaderName(string *HeaderName) {
if (Buffer.empty()) { // No header if the buffer is empty.
return false;
}
string::size_type endIndex = Buffer.find(SMTPENDL);
if (string::npos == endIndex) { // No SMPENDL.
ostringstream Temp;
Temp << "Invalid header line (no SMTP end-of-line): '"
<< Buffer << "'.";
throw std::invalid_argument(Temp.str());
}
string Line(Buffer, 0, endIndex); // First line of the header.
string::size_type colonIndex = Line.find(":");
if (string::npos == colonIndex) { // No ":".
ostringstream Temp;
Temp << "Invalid header line (no ':'): '"
<< Buffer << "'.";
throw std::invalid_argument(Temp.str());
}
HeaderName->assign(Line, 0, colonIndex);
if (0 == HeaderName->size()) { // Null header name.
ostringstream Temp;
Temp << "Invalid header line (no name): '"
<< Buffer << "'.";
throw std::invalid_argument(Temp.str());
}
Buffer.erase(0, colonIndex + 1); // Erase the header name and ":".
return true;
}

bool
MailHeaders::getHeaderLine(string *HeaderLine) {
if (Buffer.empty()) { // No line if the buffer is empty.
return false;
}
string::size_type EndIndex = Buffer.find(SMTPENDL);
if (string::npos == EndIndex) { // No SMPENDL.
ostringstream Temp;
Temp << "Invalid header line (no SMTP end-of-line): '"
<< Buffer << "'.";
throw std::invalid_argument(Temp.str());
}
HeaderLine->assign(Buffer, 0, EndIndex); // Tentative line of the body.
string::size_type XIndex = HeaderLine->find("X-");
if (XIndex == 0) { // Does it begin with "X-"?
return false; // Yes; it's not the body line.
}

Buffer.erase(0, EndIndex + SMTPENDL.size()); // Erase line and SMTPENDL.
return true;
}

bool
MailHeaders::getNameBody(string *HeaderName, string *FormattedHeaderBody) {
if (!getHeaderName(HeaderName)) { // Get the header.
return false; // No more headers.
}

FormattedHeaderBody->clear();
string HeaderLine; // One line of the body.
bool FirstTime = true;
while (getHeaderLine(&HeaderLine)) { // While this header has lines...
if (FirstTime) {
*FormattedHeaderBody = HeaderLine;
FirstTime = false;
} else {
*FormattedHeaderBody += LibMilterENDL;
*FormattedHeaderBody += HeaderLine;
}
}
return true;
}

+ 102
- 0
MailHeaders.hpp View File

@@ -0,0 +1,102 @@
// MailHeaders.hpp
// Copyright (C) 2008 ARM Research Labs, LLC.
// See www.armresearch.com for the copyright terms.
//
// Header file for MailHeaders.cpp.
#ifndef MailHeadershpp_included
#define Mailheadershpp_included
#include <string>
using namespace std;
/// Class to extract mail header name/body pairs.
//
// This class extracts mail header name/body pairs from a string
// containing a series of mail headers.
//
class MailHeaders {
private:
string Buffer; ///< Contains the mail headers.
const string SMTPENDL; ///< STMP end-of-line string (e.g. "\r\n).
const string LibMilterENDL; ///< libmilter end-of-line string.
const string Prefix; ///< Start of header name (e.g. "X-").
/// Get the next header name and body.
//
// This method inputs a string containing zero or more RFC
// 2822-compliant headers, and returns the header name and body in
// a format suitable for adding the header using the libmilter
// library. The header name, ":", body, and the trailing SMTPENDL
// are removed from the input string.
//
// \param[out] HeaderName contains the name of the header.
//
// \param[in,out] Buffer on input contains the headers. On output,
// the name of the first header, the ":", header body, and trailing
// SMTPENDL are removed from the beginning.
//
// \returns true if a header is found, false if Buffer is "".
//
// \throws std::invalid_argument if Buffer does not have the correct
// format.
//
// \see getHeaderLine.
//
// \see getHeaderBody.
//
bool getHeaderName(std::string *HeaderName);
/// Get the next line of a mail header.
//
// This method inputs a string containing zero or more lines of
// the body of a RFC 2822-compliant header, and returns the next
// line of the body of the header. The line and the SMTPENDL are
// removed from the input string Buffer.
//
// \param[out] HeaderLine contains the line of the header.
//
// \returns true if a line is found, false if Buffer is "".
//
// \throws std::invalid_argument if the first line of Buffer is not
// "", and does not end with SMTPENDL.
//
bool getHeaderLine(std::string *HeaderLine);
public:
MailHeaders(); ///< Constructer for MailHeaders.
~MailHeaders(); ///< Destructor for MailHeaders.
/// Load mail headers.
//
// This method loads the object with the headers.
//
// \param[in] HeaderBuffer contains the headers.
//
void loadHeaders(const string &HeaderBuffer);
/// Get the next header name and body.
//
// This method returns the next header name and body. The body is
// formatted so that it can be used as input for the libmilter
// smfi_addheader() function.
//
// \param[out] HeaderName contains the header name.
//
// \param[out] FormattedHeaderBody contains the formatted header
// body.
//
// \returns true if a header/body is found, false if the buffer is
// "".
//
// \throws exceptions thrown by getHeaderName and getHeaderLine.
//
// \see getHeaderName.
//
// \see getHeaderLine.
//
bool getNameBody(string *HeaderName, string *FormattedHeaderBody);
};
#endif

+ 34
- 0
Makefile.am View File

@@ -0,0 +1,34 @@
## Process this file with automake to produce Makefile.in
##
## $Id$
##
## automake input for the MicroNeil SNFMilter application (SNFMilter directory).
##
## Author: Alban Deniz
##
## Copyright (C) 2009 ARM Research Labs, LLC.
## See www.armresearch.com for the copyright terms.
##
##

LIBS = @SNF_LIBS@ -L../SNFMulti -L../CodeDweller -lSNFMulti -lCodeDweller @LIBS@
CXXFLAGS = $(SNF_CXXFLAGS) -I@top_srcdir@/SNFMulti -I@top_srcdir@/CodeDweller

sbin_PROGRAMS = \
SNFMilter

SNFMilter_SOURCES = \
@top_srcdir@/SNFMilter/main.cpp \
@top_srcdir@/SNFMilter/SNFMilter.cpp \
@top_srcdir@/SNFMilter/MailHeaders.cpp

noinst_HEADERS = \
@top_srcdir@/SNFMilter/MailHeaders.hpp \
@top_srcdir@/SNFMilter/ProductionQueue.hpp \
@top_srcdir@/SNFMilter/SNFMilter.hpp

EXTRA_DIST = \
Makefile.am

clean-local:
rm -f *.gcno *.gcov *.gcda *~ $(CONFDATA)

+ 48
- 0
ProductionQueue.hpp View File

@@ -0,0 +1,48 @@
// ProductionQueue.hpp
// Copyright (C) 2007 MicroNeil Research Corporation
// See www.armresearch.com for the copyright terms.
//
// A ProductionQueue is a templated, thread safe mechanism for implementing
// a producer/consumer relationship. The objects in the queue should be simple
// data so that they can be created, destroyed, and copied without trouble. Put
// another way - the objects in the ProductionQueue should be lightweight
// handles for other things. Those things should be created and destroyed
// elsewhere.
#include <queue>
#include "threading.hpp"
using namespace std;
template<typename T> // Templatized
class ProductionQueue { // Production Queue Class
private:
Mutex myMutex; // Contains a mutex and
volatile int LatestSize; // a volatile (blinking light) size
ProductionGateway myGateway; // integrated with a production
queue<T> myQueue; // gateway and a queue.
public:
ProductionQueue() : LatestSize(0) {} // The size always starts at zero.
T take() { // To consume a queued object
myGateway.consume(); // we wait on the production gateway
ScopeMutex OneAtATimePlease(myMutex); // and when we get through we lock
T O = myQueue.front(); // the mutext, take the object on the
myQueue.pop(); // front of the queue, pop it out,
LatestSize = myQueue.size(); // and rest our size (blinking light).
return O; // Then return the object we got.
}
void give(T O) { // To produce a queued object
ScopeMutex OneAtATimePlease(myMutex); // we wait on the mutex. When we
myQueue.push(O); // get through we push our object
LatestSize = myQueue.size(); // into the queue, reset our size
myGateway.produce(); // indicator and tell the gateway.
} // When we're done it can be grabbed.
int size() { // To check the size we look at
return LatestSize; // the blinking light.
}
};

+ 1382
- 0
SNFMilter.cpp
File diff suppressed because it is too large
View File


+ 1315
- 0
SNFMilter.cpp.new
File diff suppressed because it is too large
View File


+ 258
- 0
SNFMilter.hpp View File

@@ -0,0 +1,258 @@
// SNFMilter.hpp
// Copyright (C) 2007 ARM Research Labs, LLC.
// See www.armresearch.com for the copyright terms.
//
// This file defines the SNFMilter configuration data structures and interface.
#ifndef SNFMilterhpp_included
#define SNFMilterhpp_included
#include <signal.h>
#include <libmilter/mfapi.h>
#include <vector>
#include <string>
#include "ProductionQueue.hpp"
#include "MailHeaders.hpp"
#include "timing.hpp"
#include "threading.hpp"
using namespace std;
class snf_EngineHandler; // We must know that these exist.
class snf_RulebaseHandler;
// Connection types.
enum SNFMilterSocketType {
TCPMilterSocket = 1,
UNIXMilterSocket = 2,
NOMilterSocket = 3
};
// SNFMilterAction - What we can do.
enum SNFMilterAction { // SNFMilter Actions
Error = -1, // Error result.
Allow = 0, // Process the message.
Accept = 1, // White-List the message.
Retry = 2, // Return Try Again (tmp fail)
Reject = 3, // Reject the message.
Discard = 4, // Silently discard.
Quarantine = 5, // Store in quarantine.
NoAction = 6, // Take no action.
NMilterActions
};
//
// Configuration.
//
const string AllowActionMnemonic = "0";
const string AcceptActionMnemonic = "1";
const string RetryActionMnemonic = "2";
const string RejectActionMnemonic = "3";
const string DiscardActionMnemonic = "4";
const string QuarantineActionMnemonic = "5";
const string NoActionMnemonic = "6";
const string TCPMilterSocketMnemonic = "1";
const string UNIXMilterSocketMnemonic = "2";
const int ResultCodesCount = 64; // Number of valid result codes.
const int ConfigurationLifetime = 1000; // Config life time in ms.
const bool PrependLocalReceivedHeader = true; // True to prepend local received headers
// to the buffer to scan.
const sfsistat FailSafeMilterResponse = SMFIS_CONTINUE; // Return value to libmilter
// in case of error.
const string::size_type MailBufferReserveSize = 65536; // Maximum amount of email message to scan.
const string SMTPENDL = "\r\n"; // SMTP endline.
const int ShutdownPeriods = 5; // Number of time periods to wait while
// shutting down.
const int ShutdownWaitPeriod_ms = 1000; // Time period duration for shutting down.
const string UnknownExceptionMessage = "Unknown exception occurred."; // Logged when an unknown exception occurs.
const int ShutdownSignal = SIGTERM; // Signal for shutdown.
//
// End of configuration.
//
// The SNFMilterEngine class provides up to date configuration data and
// a scanning engine for the milter. It is presumed that at one of these objects
// will be used in each message scan. They can be created, used, and destroyed,
// or for a more optimized approach they can be created, used, stored in a pool,
// and refreshed, and used again for a new scan. They should all be properly
// destroyed before runLibMilter() returns.
class SNFMilterEngine { // Milter config objec. One per scan.
private:
Mutex ConfigMutex; // Configuration lock mutex.
snf_RulebaseHandler* myRulebase; // Where is my rulebase?
snf_EngineHandler* myEngine; // Where is my engine?
// Configuration data.
// IP scan actions
SNFMilterAction WhiteAction; // Action for White IP.
SNFMilterAction CautionAction; // Action for Caution IP.
SNFMilterAction BlackAction; // Action for Black IP.
SNFMilterAction TruncateAction; // Action for Truncate IP.
SNFMilterAction NonZeroAction; // Action for nonzero scans.
// Message scan actions
SNFMilterAction ResultActions[ResultCodesCount]; // Array of possible scan actions.
Timeout ConfigurationCheckTime; // Timer for checking the configuration.
string RunningConfiguration; // The current running config string.
void readConfiguration(); // Parses the configuration.
void checkConfiguration(); // Reload the config if it is old.
public:
SNFMilterEngine(snf_RulebaseHandler* R); // Construct the configuration.
~SNFMilterEngine(); // Destroy the configuration.
void setResultAction(int Result, SNFMilterAction Action); // Set a result / action pair.
SNFMilterAction scanIP(unsigned long int IP); // Scans an IP.
SNFMilterAction scanMessage(const unsigned char* bfr, int length); // Scans a message.
string XHeaders(); // Return X headers from last scan.
};
// The SNFMilterContext class maintains the context for a connection
// from the MTA. There is one SNFMilterContext object for each
// connection from the MTA. They can be created, used, and destroyed,
// or for a more optimized approach they can be created, used, stored
// in a pool, and refreshed, and used again for a new scan. They
// should all be properly destroyed before runLibMilter() returns.
class SNFMilterContext { // Milter connection context object.
public:
// Object states. The object transitions to the corresponding state
// when a libmilter callback is invoked. The object is in the
// Pooled state when the object is not being used.
enum SNFMilterState {
Pooled,
Connect,
Helo,
EnvFrom,
EnvRcpt,
Data,
Header,
EOH,
Body,
EOM,
Close,
NMilterStates
} State;
SNFMilterContext(snf_RulebaseHandler *);
~SNFMilterContext();
sfsistat SkipReturn; // libmilter return value when further
// callbacks of the same type are to be skipped.
// Map the scan result to the libmilter return value.
sfsistat
smfisReturn(SNFMilterAction);
// Scanning engine.
SNFMilterEngine milterEngine;
string getLocalReceivedHeader();
// Connection data.
struct SNFMilterConnectionData {
string HostName;
IP4Address HostIP;
string HostHelo;
// Clear the object.
void clear() {
HostName.clear();
HostIP = (long unsigned )0;
HostHelo.clear();
}
} ConnectionData;
// Message buffer.
struct SNFMilterMessageData {
// Buffer to hold the message.
string MessageBuffer;
// Sender address.
string SenderAddress;
// Constructor reserves memory to hold the message.
SNFMilterMessageData(string::size_type reserveSize) {
MessageBuffer.reserve(reserveSize);
}
// Clear the object.
void clear() {
MessageBuffer.clear();
SenderAddress.clear();
}
} MessageData;
};
class SNFMilterContextPool { // SNFMilter Pool Manager
private:
Mutex ContextAllocationControl; // Protects context allocation.
vector<SNFMilterContext*> ContextPool; // Contains all created contexts.
ProductionQueue<SNFMilterContext*> AvailableContexts; // Contains all available contexts.
snf_RulebaseHandler* myRulebase; // Rulebase handler.
// Connection info.
SNFMilterSocketType MilterSocketType;
string MilterSocketPath;
string MilterSocketGroup;
string MilterSocketIP;
int MilterSocketPort;
public:
SNFMilterContextPool(snf_RulebaseHandler* Rulebase); // Ctor needs a live rulebase handler.
~SNFMilterContextPool(); // Dtor gracefully discards contexts.
SNFMilterSocketType getSocketType(); // Named pipe or TCP socket specified
// in the configuration.
string getSocketPath(); // Path of named pipe specified in
// the configuration
string getSocketGroup(); // Group specified for named pipe
// in the conofiguration.
string getSocketIP(); // IP (host) specified in the
// configuration.
int getSocketPort(); // TCP port specified in the
// configuration
SNFMilterContext* grab(); // Get an context to use.
void drop(SNFMilterContext* E); // Drop an context after use.
bool allUnused(); // Return true if no contexts are in use.
void logThisError(string ContextName, int Code, string Text); // Log an error message.
void logThisInfo(string ContextName, int Code, string Text); // Log an informational message.
};
// The runLibMilter function establishes the appropriate libmilter call backs and
// accepts calls from the MTA via libmilter until it is told to quit. When it
// is told to quit it gracefully closes down, reclaims any memory it allocated,
// and returns.
// The actual code for the runLibMilter() function call is found in SNFMilter.cpp.
void runLibMilter(SNFMilterContextPool* Contexts, bool DebugMode); // Run the milter 'til it's done.
#endif

+ 171
- 0
main.cpp View File

@@ -0,0 +1,171 @@
// main.cpp
//
// Copyright (C) 2007, ARM Research Labs, LLC.
// See www.armresearch.com for the copyright terms.
//
// This is the entry point for the SNF Milter.
// The main() function acquires the configuration file path, sets up the
// SNF engine, and passes control to the SNFMilter module via runSNFMilter().
//
// SNFMilter() makes use of the SNF engine until it is dismissed by the MTA via
// the appropriate libmilter call.
//
// While SNFMilter() is running, it will get new configuration data for each
// scan it is asked to perform. A configuration packet will be produced on
// demand by the main module. The main module will check the configuration once
// every second or so to make sure it has the correct current data. The
// configuration data structure is defined in SNFMilter.hpp.
//
// When the SNFMilter() function returns, the main() function closes down the
// SNF Engine and exits the program.
#include <errno.h>
#include <string.h>
#include <exception>
#include <iostream>
#include "SNFMulti.hpp"
#include "SNFMilter.hpp"
#include "config.h"
using namespace std;
const char* SNF_MILTER_VERSION = "SNFMilter " PACKAGE_VERSION " Build: " __DATE__ " " __TIME__;
static const string XCIShutdownResponse =
"<snf><xci><server><response message=\'shutdown in progress\' code=\'1\'/></server></xci></snf>\n";
class XCIShutdownWatcher : public snfXCIServerCommandHandler { // Shutdown watcher.
public:
XCIShutdownWatcher(){}
string processXCIRequest(snf_xci& X) { // Here is how we process requests.
if(0 == X.xci_server_command.find("shutdown")) { // If we find shutdown then
smfi_stop(); // Stop processing messages.
return XCIShutdownResponse; // respond with a message.
} // If we get some other request
return XCIErrorResponse; // return the error response.
}
};
class SignalCatcher : public Thread { // Class to catch a signal.
private:
sigset_t *MySignalSet;
snf_RulebaseHandler *MyRulebase;
public:
SignalCatcher(sigset_t *Signal_Set, snf_RulebaseHandler* Rulebase) : // Construct.
MySignalSet(Signal_Set), // Set of signals to catch.
MyRulebase(Rulebase) { // Used for error logging.
run(); // Start the thread.
}
void myTask() {
int ReceivedSignal;
int Status = sigwait(MySignalSet, &ReceivedSignal);
if (0 != Status) {
// Log error.
ostringstream Temp;
Temp << "Error waiting for signal: " << strerror(Status) << ".";
MyRulebase->logThisError("Signal Catcher", 1, Temp.str());
return;
}
smfi_stop(); // Stop processing.
}
};
const int CorrectARGc = 2; // How many arguments we expect.
void displayHelp() { // Display some help.
cout
<< SNF_MILTER_VERSION << endl
<< "Copyright (C) 2007, ARM Research Labs, LLC (www.armresearch.com)" << endl
<< endl
<< "Use snfmilter <full path to configuration file>" << endl
<< "Example: /home/snfmilter/snfmilter /home/snfmilter/snf_milter.xml" << endl;
};
int main(int argc, char* argv[]) {
// Get and check the command line arguments.
if(CorrectARGc != argc) { // If our command line arguments
displayHelp(); // don't look right then display
return 0; // our help screen.
}
bool DebugMode = false; // This will be our debug mode.
string argv0(argv[0]); // Capture how we were called.
if(
string::npos != argv0.find("Debug") || // If we find "Debug" or
string::npos != argv0.find("debug") // "debug" in our command path
) { // then we are in DebugMode.
DebugMode = true; // Set the flag and tell the
cout << "Debug Mode" << endl; // watchers.
}
sigset_t SignalSet; // Set of signals to mask.
SignalCatcher *SigCatcher; // Object to catch signal.
try { // Catch anything that breaks loose.
int PthreadStatus;
if ( (0 != sigemptyset(&SignalSet)) || // Mask signal for this thread and
// all threads to be created.
(0 != sigaddset(&SignalSet, ShutdownSignal)) ) {
ostringstream Temp;
Temp << "Error masking signal on startup: " << strerror(errno) << ".";
throw runtime_error(Temp.str());
}
PthreadStatus = pthread_sigmask(SIG_BLOCK, &SignalSet, 0);
if (0 != PthreadStatus) {
ostringstream Temp;
Temp << "Error masking signal on startup (pthread_sigmask): " << strerror(PthreadStatus) << ".";
throw runtime_error(Temp.str());
}
snf_RulebaseHandler* MilterRulebase = new snf_RulebaseHandler(); // Allocate a rulebase handler.
MilterRulebase->PlatformVersion(SNF_MILTER_VERSION); // Record our version identification.
XCIShutdownWatcher ShutdownWatcher; // Make a server shutdown processor
MilterRulebase->XCIServerCommandHandler(ShutdownWatcher); // and register it with the engine.
MilterRulebase->open(argv[1], "", ""); // Open the rulebase.
SNFMilterContextPool* MilterContexts =
new SNFMilterContextPool(MilterRulebase); // Create the Milter Context Pool.
SigCatcher = new SignalCatcher(&SignalSet, MilterRulebase); // Create thread object to catch the signal.
runLibMilter(MilterContexts, DebugMode); // Run the milter.
ThreadState const &SigCatcherState = SigCatcher->MyState();
if (Thread::ThreadStarted.Name == SigCatcherState.Name) { // Is the signal catcher thread running?
PthreadStatus = pthread_kill(SigCatcher->getMyThread(), ShutdownSignal); // Yes. Send it a signal.
if (0 == PthreadStatus) {
SigCatcher->join(); // Wait for signal catcher to complete.
} else {
ostringstream Temp;
Temp << "Error terminating signal catcher: " << strerror(PthreadStatus) << ".";
throw runtime_error(Temp.str());
}
}
delete MilterContexts; // Destroy the context pool.
MilterContexts = 0; // Forget it.
MilterRulebase->close(); // Close down the rulebase handler.
delete MilterRulebase; // Destroy the rulebase.
MilterRulebase = 0; // Forget it.
delete SigCatcher; // Destroy the signal catcher.
SigCatcher = 0; // Forget it.
} // That's all folks.
catch(exception& e) { // Report any normal exceptions.
cerr << "SNFMilter Exception: " << e.what() << endl;
}
catch(...) { // Report any unexpected exceptions.
cerr << "SNFMilter Panic! Unknown Exception!" << endl;
}
return 0; // Normally we return zero.
}

Loading…
Cancel
Save