db.cpp

00001 /***************************************************************************
00002  *   Copyright (C) 2004 by Michael Moritz                                  *
00003  *   mimo@restoel.net                                                      *
00004  *                                                                         *
00005  *   This program is free software; you can redistribute it and/or modify  *
00006  *   it under the terms of the GNU General Public License as published by  *
00007  *   the Free Software Foundation; either version 2 of the License, or     *
00008  *   (at your option) any later version.                                   *
00009  *                                                                         *
00010  *   This program is distributed in the hope that it will be useful,       *
00011  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
00012  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
00013  *   GNU General Public License for more details.                          *
00014  *                                                                         *
00015  *   You should have received a copy of the GNU General Public License     *
00016  *   along with this program; if not, write to the                         *
00017  *   Free Software Foundation, Inc.,                                       *
00018  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
00019  ***************************************************************************/
00020 
00021 #ifdef HAVE_CONFIG_H
00022 #include <config.h>
00023 #endif
00024 
00025 #include "db.h"
00026 #include "defs.h"
00027 //#include <signal.h>
00028 #include <syslog.h>
00029 #include <iostream>
00030 #include <iomanip>
00031 #include "triplet.h"
00032 #include <sstream>
00033 #include <stdexcept>
00034 #include "cfg.h"
00035 #include "tmplwlmod.h"
00036 #include "wldb.h"
00037 #include "wlcacheddb.h"
00038 #include "dbdefs.h"
00039 #include "dbiquote.h"
00040 
00041 #define WEAKRVHOSTDBG   false
00042 
00043 using namespace std;
00044 // define the wl modules
00045 typedef TmplWlMod<WLDB> DbWlModule;
00046 typedef TmplWlMod<WlCachedDB> CachedDbWlModule;
00047 
00048 DB::DB() throw(std::exception)
00049         : _con(0),_graceful_quit(0)
00050 {
00051         if(dbi_initialize(0) <= 0)
00052                 throw runtime_error("DB: no dbi drivers installed, or none found");
00053 //      bool verbose = g_getCfg().isVerbose();
00054 //      bool initmode = g_getCfg().isModeInit();
00055         /*      
00056         const char* dbdriver = g_getCfg().getDbType().c_str();
00057         _con = dbi_conn_new(dbdriver);
00058         if(!_con) {
00059                 stringstream stm;
00060                 stm << "DB: connection/driver " << dbdriver << " could not be opened\nAvailable drivers:\n";
00061                 list_drivers(stm);
00062                 throw runtime_error(stm.str());
00063         } else {
00064                 if(verbose)
00065                         syslog(LOG_DEBUG,"connecting to DB, using driver %s",dbdriver);
00066         }
00067         const ParamVector& params = g_getCfg().getDbParamVector();
00068         for(ParamVector::const_iterator itr=params.begin(); itr != params.end();++itr) {
00069                 // note: dbi_conn_set_option 2nd arg is non-const
00070                 const char *key = (*itr).c_str();
00071                 ++itr;
00072                 if(itr != params.end()) {
00073                         if((*itr).length() < (tempCharBuf-1) ) {
00074                                 const char *value = (*itr).c_str();
00075                                 char szBuf[tempCharBuf];
00076                                 strcpy(szBuf,value);
00077                                 if(verbose) {
00078                                         if(string(key) == "password") 
00079                                                 syslog(LOG_DEBUG,"setting DB option: %s to: (hidden)",key);
00080                                         else
00081                                                 syslog(LOG_DEBUG,"setting DB option: %s to: %s",key,szBuf);
00082                                                 
00083                                 }
00084                                 if(dbi_conn_set_option(_con, key, szBuf) != 0)
00085                                         throw runtime_error( getConError("dbi_conn_set_option "+(*itr)) );
00086                         }
00087                 }
00088         }
00089         if(dbi_conn_connect(_con) != 0)
00090                 throw runtime_error( getConError("dbi_conn_connect") );
00091         if(verbose)
00092                 syslog(LOG_DEBUG,"connected to DB");
00093         */      
00094         open();
00095         // initialise triplets table
00096 //      if(initmode)
00097 //              createTable(g_getTripletDef());
00098 }
00099 void DB::open() throw (std::exception)
00100 {
00101         bool initmode = g_getCfg().isModeInit();
00102         bool verbose = g_getCfg().isVerbose();
00103         if(_con) 
00104                 throw runtime_error("already connected to db");
00105         const char* dbdriver = g_getCfg().getDbType().c_str();
00106         _con = dbi_conn_new(dbdriver);
00107         if(!_con) {
00108                 stringstream stm;
00109                 stm << "DB: connection/driver " << dbdriver << " could not be opened\nAvailable drivers:\n";
00110                 list_drivers(stm);
00111                 throw runtime_error(stm.str());
00112         } else {
00113                 if(verbose)
00114                         syslog(LOG_DEBUG,"connecting to DB, using driver %s",dbdriver);
00115         }
00116         const ParamVector& params = g_getCfg().getDbParamVector();
00117         for(ParamVector::const_iterator itr=params.begin(); itr != params.end();++itr) {
00118                 // note: dbi_conn_set_option 2nd arg is non-const
00119 //              const char *key = (*itr).c_str();
00120                 string strKey = (*itr);
00121                 ++itr;
00122                 if(itr != params.end()) {
00123                         if(verbose) {
00124                                 string strLog = (strKey=="password") ? "setting DB option: "+strKey+" to: (hidden)"
00125                                         : "setting DB option: "+strKey+" to: "+(*itr);
00126                                 syslog(LOG_DEBUG,ESC_LOG(strLog).c_str());
00127                         }
00129                         string strValue = (*itr);
00130                         if(dbi_conn_set_option(_con, strKey.c_str(), strValue.c_str()) != 0)
00131                                 throw runtime_error( getConError("dbi_conn_set_option "+(*itr)) );
00132                 }
00133         }
00134         if(dbi_conn_connect(_con) != 0)
00135                 throw runtime_error( getConError("dbi_conn_connect") );
00136         if(verbose)
00137                 syslog(LOG_DEBUG,"connected to DB");
00138         if(_wlMods.size() == 0)
00139                 init_wlMods();
00140         // initialise triplets table
00141         if(initmode)
00142                 createTable(g_getTripletDef());
00143 }
00144 
00145 void DB::close()
00146 {
00147 //      s_db_object = 0;
00148         for(WlModuleList::const_iterator itr=_wlMods.begin(); itr != _wlMods.end();++itr) 
00149                 delete (*itr);  
00150         _wlMods.clear();        
00151         if(_con) {
00152 //              signal (SIGTERM, SIG_DFL);
00153                 syslog(LOG_INFO,"disconnecting from DB");       
00154                 dbi_conn_close(_con);
00155                 _con = 0;
00156         }
00157         dbi_shutdown();
00158 }
00159 int DB::handle_signal(int signum) {
00160         syslog(LOG_DEBUG,"received signal %i",signum);
00161 //      assert (signum == SIGINT);
00162         this->_graceful_quit = 1;
00163         this->close();
00164         return 0;
00165 }
00166 void DB::init_wlMods() {
00167         bool verbose = g_getCfg().isVerbose();
00168         bool initmode = g_getCfg().isModeInit();
00169         try {
00170                 // initialise whitelisting modules      
00171                 const Cfg::WLMap& wlMap = g_getCfg().getWLMap();
00172                 for(Cfg::WLMap::const_iterator itr=wlMap.begin(); itr != wlMap.end(); ++itr) {
00173                         bool created = false;
00174                         const std::string& tableName = itr->first;
00175                         //cout << "tableName: " << tableName << endl;
00176                         const WLStaticDef& wldef = g_getWlDef(tableName); //throws if not found
00177                         switch(itr->second.getMode()) {
00178                                 case Cfg::WLEntry::WL_db:
00179                                         _wlMods.push_back(new DbWlModule (*this, initmode, wldef) );
00180                                         created = true;
00181                                         break;
00182                                 case Cfg::WLEntry::WL_dbcached:
00183                                         _wlMods.push_back(new CachedDbWlModule (*this, initmode, wldef) );
00184                                         created = true;
00185                                         break;
00186                                 case Cfg::WLEntry::WL_off:
00187                                         break;
00188                                 default:
00189                                         throw runtime_error("wl mode not supported for table: "+tableName+". Check your configuration file.");
00190                         }
00191                         if(verbose && created)
00192                                 syslog(LOG_DEBUG,"created wl module: %s, type: %s",
00193                                            tableName.c_str(),g_getCfg().getWlModeName(itr->second.getMode()).c_str());  
00194                 }
00195         } catch(runtime_error &e) { //catch exception, dont exit, wl failure is nonfatal, but log error
00196                 syslog(LOG_ERR,"Error while initialising wl module: %s",e.what());
00197         }
00198 }
00199 
00200 std::string DB::getConError(const std::string& pre) const
00201 {
00202         if(_con != 0) {
00203                 const char *errBuff;
00204                 dbi_conn_error(_con,&errBuff);
00205                 return "DB: "+pre+": "+string(errBuff)+" ";
00206         } else {
00207                 return "DB: "+pre+": no connection to db";
00208         }
00209 }
00210 void DB::list_drivers(ostream &os)
00211 {
00212         dbi_driver drv = 0;
00213         while((drv=dbi_driver_list(drv)) != 0) {
00214                 os << " " << dbi_driver_get_name(drv);
00215         }
00216 }
00217 const std::string DB::trimLastByte(std::string str)
00218 {
00219         string::size_type lastdotpos = str.rfind('.');
00220         if(lastdotpos != string::npos) {
00221                 str.erase(lastdotpos,string::npos);
00222                 str += '.';
00223         }
00224         return str;
00225 }
00226 //TODO optimise this sql query 
00227 //              nogo: REPLACE because =DELETE+INSERT
00228 //              maybe: http://dev.mysql.com/doc/mysql/en/HANDLER.html
00229 DB::RecordStatus DB::update(const Triplet& triplet) throw(std::exception)
00230 {
00231 // unlikely to happen
00232         if(!_con)
00233                 throw runtime_error(getConError("update"));
00234         if(dbi_conn_ping(_con) != 1) {
00235                 _con = 0;
00236                 syslog(LOG_INFO,"lost db connection, reconnecting");
00238                 this->open();
00239         }
00240         dbi_driver dbidrv = dbi_conn_get_driver(_con);
00241         DBIQuote sender(dbidrv, triplet.getSender().substr(0,MAX_LEN_SENDER));
00242         DBIQuote recipient(dbidrv, triplet.getRecipient().substr(0,MAX_LEN_RECIPIENT));
00243         DBIQuote client(dbidrv, triplet.getClientAddress().substr(0,MAX_LEN_CLIENT));
00244         string clientLoggable = client();
00245         string clientNumeric = triplet.getClientAddressNumeric();
00246         bool weak = g_getCfg().isModeWeak();
00247         bool reverse = g_getCfg().isModeReverse();
00248         bool verbose = g_getCfg().isVerbose();
00249         RecordStatus recStatus = unknown;
00250         time_t ltime;
00251         dbi_result result;
00252         
00253         if(clientNumeric.size() != IPADDR_NUM)
00254                 throw runtime_error("Invalid IP Address: "+triplet.getClientAddress()
00255                         + " parsed: "+triplet.getClientAddressNumericStr());
00256         // check triplet against wl modules
00257         if(!_wlMods.empty()) {
00258                 Triplet quotedTriplet(client(),sender(),recipient(),clientNumeric); 
00259                 for(WlModuleList::const_iterator itr=_wlMods.begin(); itr != _wlMods.end();++itr) 
00260                         if( (*itr)->check(quotedTriplet) )
00261                                 return registered;
00262         }
00263         //
00264         unsigned clientIpBytes = clientNumeric.size();
00265         stringstream sqlSelect;
00266         stringstream sqlWhere;
00267         //construct the sql select string
00268         sqlSelect << "SELECT count,uts FROM "TABLE_TRIPLETS" WHERE ( ";
00269         sqlWhere << ATTR_SENDER"=" << sender() << " AND "ATTR_RECIPIENT"=" << recipient();
00270 //      string sqlOrder;
00271         if(reverse) { //weak greylisting, reverse name lookups
00272                 //bDoReplace = true; //use replace instead of INSERT for similiar entries
00273                 string revClient = getWeakHost(triplet.getResolved());
00274                 if(revClient.size() > 0) {
00275                         transform(revClient.begin(),revClient.end(),revClient.begin(), ToLower());
00276                         client = DBIQuote(dbidrv,revClient);
00277                         clientLoggable = client();
00278                         sqlWhere << " AND "ATTR_CLIENT"=" << client();
00279                 } else {
00280                         // check for name resolution error 
00281                         // and fallback to IP based weak g-listing
00282                         if(verbose) {
00283                                 if(triplet.getResolveError().size() > 0) {
00284                                         //log the name resolution error
00285                                         syslog(LOG_DEBUG,"name resolve error: %s, %s",triplet.getResolveError().c_str(),client());
00286                                 } else {
00287                                         //log my name conversion's error
00288                                         syslog(LOG_INFO,"host name conversion failed: %s",client());
00289                                 }
00290                         }
00291                         reverse = false;
00292                         weak = true;
00293                         client.setNULL();
00294                 }
00295         }
00296         if(weak) { // weak greylisting
00297                 clientIpBytes = g_getCfg().getWeakBytes();
00298                 if( (clientIpBytes < 1) || (clientIpBytes > clientNumeric.size()) )
00299                         throw runtime_error("Invalid client IP bytes (weakbytes in config): "
00300                                 +numToStr(clientIpBytes)+" (1-"+numToStr(clientNumeric.size())+"): "+triplet.getClientAddress()
00301                                 + " parsed: "+triplet.getClientAddressNumericStr());
00302         }
00303         if(!reverse)
00304                 sqlWhere << " AND " << triplet.getClientAddressNumericStrSQL(" AND ","ip",64,"=",2,clientIpBytes);      
00305         sqlSelect << sqlWhere.str() << " )"; 
00306         
00307         //cout << clientIpBytes << " " << sqlSelect.str() << endl;
00308         //exit(0);
00309         //const char *pcz = sqlSelect.str().c_str();
00310         
00311         if(verbose)
00312                 syslog(LOG_DEBUG,ESC_LOG(sqlSelect.str()).c_str());
00313 //                      syslog(LOG_DEBUG,strEscape(sqlSelect.str(),'%','%',sqlSelect.str().size()).c_str());
00314         result = DBI_CON_QUERYF(_con, ESC_LOG(sqlSelect.str()).c_str());
00315         if(!result)
00316                 throw runtime_error(getConError("update select: ")+sqlSelect.str()+" " );
00317 //              throw runtime_error(getConError("update select: ")+sqlSelect.str()+" " );
00318 
00319         time(&ltime); //get ultime (unix timestamp) in ltime 
00320         unsigned long lNow = ltime;
00321         int rows = dbi_result_get_numrows(result);
00322         if( rows == 0) { // new triplet
00323                 dbi_result_free(result);
00324                 syslog(LOG_INFO,"new: %s -> %s, %s",sender(),recipient(),clientLoggable.c_str());
00325                 //since we dont name the fields anymore this has to be in sync with the dbdefs.cpp definition
00326                 stringstream sqlInsert;
00327                 /*
00328                 if(bDoReplace) //NOTE Postgresql doesnt have REPLACE!!
00329                         sqlInsert << "REPLACE";
00330                 else
00331                         sqlInsert << "INSERT"; 
00332                 sqlInsert << " INTO "TABLE_TRIPLETS" VALUES (" << client() << "," << sender() << ',' << recipient() << ',';
00333                 */
00341                 sqlInsert << "INSERT INTO "TABLE_TRIPLETS" VALUES (" << client() << "," << sender() << ',' << recipient() << ',';
00342                 sqlInsert << triplet.getClientAddressNumericStr(',') << ",0," << lNow << ")";
00343                 if(verbose)
00344                         syslog(LOG_DEBUG,ESC_LOG(sqlInsert.str()).c_str());
00345                 result = DBI_CON_QUERYF( _con, ESC_LOG(sqlInsert.str()).c_str() );
00346                 if(!result)
00347                         throw runtime_error(getConError("update insert: ")+sqlInsert.str() );
00348                 recStatus = unknown;
00349         } else { 
00350                 dbi_result_next_row(result); 
00351                 unsigned long count = dbi_result_get_ulong_idx(result,1);
00352                 unsigned long recTimeStamp = dbi_result_get_ulong_idx(result,2);
00353                 unsigned long lTimeDiff = 0;
00354                 
00355                 if(recTimeStamp < lNow)  // not in future
00356                         lTimeDiff = lNow - recTimeStamp;
00357                 if( (count > 0) || (lTimeDiff > g_getCfg().getTimeout()) ) {
00358                         syslog(LOG_INFO,"ok: %s -> %s, %s (%lu, %lu secs)",sender(),recipient(),clientLoggable.c_str(),
00359                                 count,lTimeDiff);
00360                         dbi_result_free(result);
00361                         recTimeStamp = lNow;
00362                         stringstream sqlUpdate;
00363                         sqlUpdate << "UPDATE "TABLE_TRIPLETS" SET count=count+1,uts=" << lNow;
00364                         sqlUpdate << " WHERE ( " << sqlWhere.str() << " )";
00365                         if(verbose)
00366                                 syslog(LOG_DEBUG,ESC_LOG(sqlUpdate.str()).c_str());
00367                         result = DBI_CON_QUERYF(_con,ESC_LOG(sqlUpdate.str()).c_str());
00368                         if(!result)
00369                                 throw runtime_error(getConError("update update: ")+sqlUpdate.str());
00370                         recStatus = registered;
00371                 } else { // waiting triplet
00372                         syslog(LOG_INFO,"wait: %s -> %s, %s (%lu, %lu secs)",sender(),recipient(),clientLoggable.c_str(),
00373                                    count,lTimeDiff);
00374                         recStatus = waiting;
00375                 }
00376         }
00377         if(result)
00378                 dbi_result_free(result);
00379         return recStatus; 
00380 }
00381 dbi_result DB::executeSQL(const std::string& sql) throw(std::exception)
00382 {
00383         if(!_con)
00384                 throw runtime_error(getConError("executeSQL"));
00385         dbi_result result = DBI_CON_QUERYF(_con, ESC_LOG(sql).c_str() );
00386         if(!result) {
00387                 syslog(LOG_INFO,"SQL error: %s",getConError("executeSQL").c_str());
00388 //              throw runtime_error( getConError("executeSQL") );
00389         }
00390         return result;
00391 }
00392 void DB::createTable(const WLStaticDef& staticDef) throw(std::exception)
00393 {       
00394         if(!_con)
00395                 throw runtime_error(getConError("createTable: no db connection"));
00396         bool verbose = g_getCfg().isVerbose();
00397         stringstream sqlTestTable;
00398         sqlTestTable << staticDef._testTable << " WHERE 1=0"; //more portable than " LIMIT 1";
00399         if(verbose)
00400                 syslog(LOG_DEBUG,sqlTestTable.str().c_str());
00401         dbi_result result = DBI_CON_QUERYF(_con, sqlTestTable.str().c_str() );
00402         if(!result) {
00403                 syslog(LOG_INFO,"testing table failed: %s : %s, creating table",
00404                         staticDef._tableName,getConError("").c_str());
00405                 unsigned iCT = 0;
00406                 while( staticDef._createTable[iCT] ) {
00407                         stringstream sqlCreateTable;
00408                         sqlCreateTable << staticDef._createTable[iCT];
00409                         if(staticDef._primaryKey)
00410                                 sqlCreateTable << " PRIMARY KEY (" << staticDef._primaryKey << " )";
00411                         sqlCreateTable << ")";
00412                         if(verbose)
00413                                 syslog(LOG_DEBUG,ESC_LOG(sqlCreateTable.str()).c_str());
00414                         result = DBI_CON_QUERYF(_con, sqlCreateTable.str().c_str());
00415                         if(!result && (g_getCfg().getDbType() == "sqlite") ) {
00416                                 //table created, sqlite driver currently returns empty result nevertheless
00417                                 result = DBI_CON_QUERYF(_con, sqlTestTable.str().c_str() );
00418                         }
00419                         if(!result) {
00420                                         throw runtime_error( getConError("create table: ")+staticDef._tableName
00421                                                 + string( verbose?" SQL: "+sqlCreateTable.str() : "") );
00422                         } else {
00423                                 dbi_result_free(result);
00424                                 result = 0;
00425                         }
00426                         iCT++;
00427                 }
00428                 syslog(LOG_INFO,"table %s created", staticDef._tableName );
00429         }
00430         if(result)
00431                 dbi_result_free(result);
00432 }
00433 std::string DB::getSqlCondition(const WLCompares& compares,const Triplet& triplet) const
00434 {
00435         string sql;
00436         int j = 0;
00437         while(j < MAX_WL_COMPARES) {
00438                 if(compares[j]._compareWith) {
00439                         if(compares[j]._compareBinder)
00440                                 sql += std::string(" ") + compares[j]._compareBinder + " ";
00441                         sql += compares[j]._compareWith;
00442                         sql += compares[j]._compareOp;
00443                         sql += triplet.getMember(compares[j]._tripletMember);
00444                         j++;
00445                 } else
00446                         break;
00447         }
00448         return sql;
00449 }
00450 bool DB::exactMatch(const std::string& logprefix,const WLStaticDef& staticDef,
00451         const Triplet& triplet) throw(std::exception)
00452 {
00453         bool verbose = g_getCfg().isVerbose();
00454         string sqlWlSelect = staticDef._searchTable;
00455         sqlWlSelect += " WHERE " + getSqlCondition(staticDef._compares,triplet);
00456         sqlWlSelect += " LIMIT 1";
00457         if(verbose)
00458                 syslog(LOG_DEBUG,ESC_LOG(sqlWlSelect).c_str());
00459         dbi_result result = DBI_CON_QUERYF(_con, ESC_LOG(sqlWlSelect).c_str());
00460         if(!result)
00461                 throw runtime_error(getConError("exactMatch")+staticDef._tableName );
00462         int rows = dbi_result_get_numrows(result);
00463         if( rows == 0) {
00464                 dbi_result_free(result);
00465                 return false;
00466         } else {
00467                 dbi_result_next_row(result); 
00468                 const char *comment = dbi_result_get_string_idx(result,1);
00469                 syslog(LOG_INFO,"%s %s: %s -> %s, %s: %s",
00470                         logprefix.c_str(),staticDef._tableName,triplet.getSender().c_str(),
00471                         triplet.getRecipient().c_str(),triplet.getClientAddress().c_str(),comment);
00472                 dbi_result_free(result);
00473                 return true;
00474         }
00475 }
00476 const std::string DB::numToStr(unsigned u) const
00477 {
00478         stringstream stm;
00479         stm << u;
00480         return stm.str();
00481 }
00482 std::string DB::getWeakHost(const std::string& host) const throw(std::exception)
00483 {
00484         if(WEAKRVHOSTDBG)       cout << host << " len:" << host.size() <<endl;
00485         if(host.size() == 0) // not resolved
00486                 return string(host);
00487         string::size_type pos = host.find('.');
00488         if(pos == string::npos) // no dot in name
00489                 return string(host);
00490         if(WEAKRVHOSTDBG)       cout << host.substr(pos) << " pos:" << pos << endl;
00491         if(WEAKRVHOSTDBG)       cout << host.substr(pos+1,string::npos) << endl;
00492         return host.substr(pos+1,string::npos);
00493 }
00494 
00495 
00496 

Generated on Tue Jul 24 16:36:53 2007 for gps by  doxygen 1.5.1