Index About us
Index Setup & configLogs & bandwidth usage SPAMDownloadDevelopers  
 

Developers

Greetings, fellow developers!

Moxy was written with the idea that extending it's basic functionality should be easy, especially in terms of logging and domain lookups. Here you will find a description of the project layout as well as some specific tips to help you make Moxy do what you want.

I have put a number of hours into this project, using the Psychogenic.com resources, and have released the code under the GNU GPL. Please contact me and send me your modifications so that the project may improve and we all can profit.

General layout

Moxy was written in C++. As described on the home page, it runs as a daemon listening for connections and forks a process for incoming connections, which handles the entire transaction with the sender.

Here's a simplified graphical description of the various Moxy components

Note that not all classes, relationships, methods or parameters have been preserved in order to get a semblance of clarity in 640 pixels. As you can see, all the log subclasses implement a common interface inherited from the (abstract base) Log class. The same holds for the DomainDB subclasses. Extending the Moxy logging or lookup facilities is therefore a simple matter of creating an appropriate subclass. Of course there are some extra details involved (like instantiating the new class in the driver) that I shall describe here.

Adding a new domain database type

The DomainDB derived objects used to lookup domains and translate them to a list of destinations (hosts and ports) all share a common interface. To implement a new DB type, for instance one that looks up the domains in a postgres table, follow these 4 steps.

  • Derive a new class from DomainDB (eg PostgresDB). Implement all the DomainDB methods (open(), close(), etc) in a manner that reflects the type of db you are dealing with. The method that you'll probably need to focus on most is:

    bool get(const string & domain, DestList & returnList);

    In the get() method, do your magic to find the hosts (or IPs) and ports that match the domain string. For each of these host/port combos, simply call

    returnList.add(destHost, destPort);

    (part of the DestList interface found in destlist.hh). If at least one host/port combination is found for the domain, return true, else return false.

  • Define your DBTYPE in domaindb.hh (eg DBTYPE_PGRES) and include your header file (eg pgresdb.hh) in main.cc.

    Also in main.cc, modify open_new_DB() so it will instantiate an appropriate DB object when configured for you new type.

  • Make your additions portable. Please give your new class some thought so that others will be able to use it without modifying the source code. For instance, instead of hard coding:

    "SELECT host,port from clienttable where domain='%s'"

    in your PostgresDB::get() implementation, use something like:

    "SELECT %s from %s where %s"

    and fill in the query using variables from the config file. Adding a value to the config file is a simple as inserting lines in the .conf, so inserting:

    dbselect	"host, port"
    dbtable		clienttable
    dbwhere		"domain='%domain'"
    
    

    and using these options in your code (see config.hh for methods used to fetch configuration parameters) should be very easy. A note on the example dbwhere above: in order to ensure maximum flexibility, a scheme using magic substrings (like %domain) was implemented. It would be nice if a similar technique could be used in cases like this one, so have a look at the SysLog (syslog.cc) or FileLog (filelog.cc) log() implementations. Both use the utils SearchReplace() function to allow users to set the exact string formatting for SysLog and Filelog logs and are quite straightforward.

  • Test your code then make sure you send it back to us so it can be incorporated into the main Moxy tree. This will avoid code forks, allowing you to upgrade later without having to reintegrate your additions, and allow others to benefit as well.

You may add debugging output throughout your implementation by enclosing it with DBDEBUG conditionals, like:

#ifdef DBDEBUG
    cerr << "Creating new PostgresDB object." << endl;
#endif

Take a look at the domaindb.hh and the other  *db.hh and *db.cc files before you start.

Adding a new logging facility

The Log class derived objects which are used to log bandwidth usage all share a common interface. The procedure to follow to implement a new logging facility (eg one that updates a MySQL database) involves little more than creating a new class derived from Log and resembles the method used to add a new lookup database described above. To do so:

  • Derive a new class from Log (eg MysqlLog). Implement all the Log methods (open(), close(), log()) in a manner appropriate to your circumstances. Most of the code will be spent implementing the log() methods:

    bool log(const string & message);
    
    bool log(const string & senderHost, const string & senderEmail,
    		const string & destHost, const string & recipients, long msgsize);
    

    If logging arbitrary strings is not applicable to your type (as in the case of a MySQL logging facility), you may safely ignore the log(const string & message) method by simply returning true and dropping the message.

    The other log() method needs to be implemented in a manner consistent with your facility. It is called by the MTA after sending mail to a host. The function parameters are:

    senderHost	The sender's IP	(eg "216.182.45.14")
    	
    	senderEmail	The sender's email address (eg "pat@hisdomain.com")
    	
    	destHost	The host (or IP) as listed in the lookup db and the port number 
    			(eg "psychogenic.com:3500")
    	
    	recipients	A comma delimited list of the recipient email addresses for 
    			this domain (eg "bill@microsoft.com,ralph@msrulez.com")
    	
    	msgsize		The length of the message, in bytes (eg 244302)
    

    and you may use any of these in your logs, the two most important being destHost and msgsize.

  • Define you new LOGTYPE in log.hh (eg LOGTYPE_MYSQL) and include your header file (mysqllog.hh) in main.cc. Also in main.cc, modify open_new_Log() so that it will instantiate an appropriate Log object when configured for you new type.

  • Make your additions portable. Please give your new class some thought so that others will be able to use it without modifying the source code. For instance, instead of hard coding:

    "INSERT INTO bwtable (host, size) VALUES(%s, %s)"

    in your MysqlLog::log() implementation, use something like:

    "INSERT INTO %s (%s) VALUES(%s)"

    and fill in the query using variables from the config file. Adding a value to the config file is a simple as inserting lines in the .conf, so inserting:

    logTable	bwtable
    logTableCols	"host, size"
    logTableVals	"%dhost, %msize"
    

    and using these options in your code (see config.hh for methods used to fetch configuration parameters) should be very easy. A note on the example logTableVals above: in order to ensure maximum flexibility, a scheme using magic substrings (like %dhost) was implemented. It would be nice if a similar technique could be used in cases like this one, so have a look at the SysLog (syslog.cc) or FileLog (filelog.cc) implementations. Both use the utils SearchReplace() function to allow users to set the exact string formatting for SysLog and Filelog logs and are quite straightforward.

  • Test your code then make sure you send it back to us so it can be incorporated into the main Moxy tree. This will avoid code forks, allowing you to upgrade later without having to reintegrate your additions, and allow others to benefit as well.

You may add debugging output throughout your implementation by enclosing it with LOGDEBUG conditionals, like:

#ifdef LOGDEBUG
    cerr << "MysqlLog opening " << dbname << endl;
#endif

Take a look at the log.hh and the other  *log.hh and *log.cc files before you start.

Notes on using the Config class

The Config class is used extensively in Moxy. To get access to the program configuration in your class implementations, simply add

#include "config.hh"
extern Config MyConf;

to your .cc file. Yes, I know, it's an ugly global but it was the best way for everyone to have access to the configuration without forcing classes to pass around unnecessary parameters all the time and, anyway, the configuration is (logically) global in scope - there is only one config per program.

Take a look at config.hh to see the class' interface. All you normally need to worry about is the

string get(string attrib, const string & default = "")

member function. attrib is the attribute you are requesting and default is (an optional) default value to return if attrib is not found. The get() returns a string containing the value found in the configuration file or an empty string ("") if the parameter was not present and default was not set.

Note that the Config class may be set to be case insensitive or not but, in Moxy, the parameters names (the key values, attribs) are case insensitive. In the config file, dbname, DBName and DbNamE are all equivalent - so when calling get(), use the all lowercase version. The values returned by Config::get() are case sensitive in the sense that their case is not affected by the class - the values are returned as they are found in the configuration file.


Debugging

When first testing your installation, you might want to try starting moxy in a terminal without the -d option so you will see it log any errors. If you really need to debug moxy get the source code and define DEBUG (-DDEBUG) while compiling -- you will get lotsa output. You might want to check the defaults.hh file if you are debugging something specific and only want that module to be verbose, there are many flags you can set such as CONFDEBUG, LOGDEBUG and OPTDEBUG.

 
 
 
© 2001-2002 Psychogenic inc.
 
SourceForge Logo