2037 lines
54 KiB
C++
2037 lines
54 KiB
C++
/**
|
|
* dronescan.cc
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
|
|
* USA.
|
|
*/
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
#include <sstream>
|
|
#include <iostream>
|
|
#include <list>
|
|
#include <cstdarg> /* va_list */
|
|
#include <cstdio> /* *printf() */
|
|
#include "dbHandle.h"
|
|
|
|
#include "gnuworld_config.h"
|
|
#include "EConfig.h"
|
|
#include "Network.h"
|
|
#include "server.h"
|
|
#include "StringTokenizer.h"
|
|
|
|
#include "activeChannel.h"
|
|
#include "clientData.h"
|
|
#include "constants.h"
|
|
#include "dronescan.h"
|
|
#include "dronescanCommands.h"
|
|
#include "dronescanTests.h"
|
|
#include "sqlFakeClient.h"
|
|
#include "sqlUser.h"
|
|
#include "Timer.h"
|
|
#include "ip.h"
|
|
|
|
#ifdef ENABLE_LOG4CPLUS
|
|
#include <log4cplus/logger.h>
|
|
#endif
|
|
|
|
namespace gnuworld {
|
|
|
|
namespace ds {
|
|
|
|
using std::string ;
|
|
using std::endl;
|
|
|
|
/*
|
|
* Exported function to be used by moduleLoader to gain an
|
|
* instance of this module.
|
|
*/
|
|
extern "C"
|
|
{
|
|
xClient* _gnuwinit(const string& args)
|
|
{
|
|
return new dronescan(args);
|
|
}
|
|
}
|
|
|
|
/***************************
|
|
** C O N S T R U C T O R **
|
|
***************************/
|
|
|
|
/**
|
|
* This constructor calls the base class constructor. The xClient
|
|
* constructor will open the configuration file given and retrieve
|
|
* basic client info (nick/user/host/etc).
|
|
* Any additional processing must be done here.
|
|
*/
|
|
dronescan::dronescan( const string& configFileName )
|
|
: xClient( configFileName )
|
|
{
|
|
/* Load the config file */
|
|
dronescanConfig = new (std::nothrow) EConfig(configFileName);
|
|
assert(dronescanConfig != 0);
|
|
|
|
/* Do config file processing here */
|
|
consoleChannel = dronescanConfig->Require("consoleChannel")->second;
|
|
|
|
/* Set our initial state */
|
|
//elog << "dronescan.start> getUplink()->getStartTime()) = " << getUplink()->getStartTime() << endl;
|
|
int i = 0;
|
|
for (xNetwork::serverIterator Itr = Network->servers_begin(); Itr != Network->servers_end(); Itr++)
|
|
{
|
|
i++;
|
|
}
|
|
elog << "droneScan.start> i = " << i << endl;
|
|
if (i == 1) //if i == 1, it means i'm not connectd to a hub. If i > 1, it means the RELOAD command was sent
|
|
currentState = BURST;
|
|
averageEntropy = 0;
|
|
totalNicks = 0;
|
|
|
|
/* What is the voting cutoff? */
|
|
voteCutoff = atoi(dronescanConfig->Require("voteCutoff")->second.c_str());
|
|
|
|
/*
|
|
* Set up initial margins.
|
|
* These, particularly nickMargin, will require tweaking to make them work well.
|
|
* It is probably wise to not run these too close to false positive since
|
|
* the average entropy and such will vary over time. Two decimal places is
|
|
* probably the furthest I would push it for now.
|
|
*/
|
|
channelMargin = atof(dronescanConfig->Require("channelMargin")->second.c_str());
|
|
nickMargin = atof(dronescanConfig->Require("nickMargin")->second.c_str());
|
|
channelCutoff = atoi(dronescanConfig->Require("channelCutoff")->second.c_str());
|
|
|
|
RegisterTest(new ABNORMALSTest(this, "ABNORMALS", "Checks for the percentage of normal clients.", 10));
|
|
RegisterTest(new COMMONREALTest(this, "COMMONREAL", "Checks for common realnames", 10));
|
|
RegisterTest(new HASALLOPTest(this, "HASALLOP", "Checks if a channel has all ops.", 10));
|
|
RegisterTest(new HASOPTest(this, "HASOP", "Checks if a channel has no ops.", 10));
|
|
RegisterTest(new MAXCHANSTest(this, "MAXCHANS", "Checks the max channel membership of a channel.", 10));
|
|
RegisterTest(new RANGETest(this, "RANGE", "Checks the entropy range.", 10));
|
|
|
|
/* Set up core options */
|
|
dcInterval = atoi(dronescanConfig->Require("dcInterval")->second.c_str());
|
|
consoleLevel = atoi(dronescanConfig->Require("consoleLevel")->second.c_str());
|
|
jcInterval = atoi(dronescanConfig->Require("jcInterval")->second.c_str());
|
|
jcCutoff = atoi(dronescanConfig->Require("jcCutoff")->second.c_str());
|
|
//pcCutoff = atoi(dronescanConfig->Require("pcCutoff")->second.c_str());
|
|
ncInterval = atoi(dronescanConfig->Require("ncInterval")->second.c_str());
|
|
ncCutoff = atoi(dronescanConfig->Require("ncCutoff")->second.c_str());
|
|
rcInterval = atoi(dronescanConfig->Require("rcInterval")->second.c_str());
|
|
jcMinJoinToGline = atoi(dronescanConfig->Require("jcMinJoinToGline")->second.c_str());
|
|
jcMinJoinToGlineJOnly = atoi(dronescanConfig->Require("jcMinJoinToGlineJOnly")->second.c_str());
|
|
jcMinJoinsPerIPToGline = atoi(dronescanConfig->Require("jcMinJoinsPerIPToGline")->second.c_str());
|
|
jcJoinsPerIPTime = atoi(dronescanConfig->Require("jcJoinsPerIPTime")->second.c_str());
|
|
jcMinJFSizeToGline = atoi(dronescanConfig->Require("jcMinJFSizeToGline")->second.c_str());
|
|
jcMinJFJOnlySizeToGline = atoi(dronescanConfig->Require("jcMinJFJOnlySizeToGline")->second.c_str());
|
|
jcGlineEnable = atoi(dronescanConfig->Require("jcGlineEnable")->second.c_str()) == 1 ? true : false;
|
|
jcGlineEnableConf = jcGlineEnable;
|
|
jcGlineReason = dronescanConfig->Require("jcGlineReason")->second.c_str();
|
|
jcGlineLength = atoi(dronescanConfig->Require("jcGlineLength")->second.c_str());
|
|
gbCount = atoi(dronescanConfig->Require("gbCount")->second.c_str());
|
|
gbInterval = atoi(dronescanConfig->Require("gbInterval")->second.c_str());
|
|
/* Set up variables that our tests will need */
|
|
typedef std::vector<string> testVarsType;
|
|
testVarsType testVars;
|
|
|
|
/* Cycle over tests to get configuration variable name */
|
|
for( testMapType::const_iterator itr = testMap.begin() ;
|
|
itr != testMap.end() ; ++itr) {
|
|
testVars.push_back( itr->second->getVariable() );
|
|
}
|
|
|
|
for( testVarsType::const_iterator itr = testVars.begin() ;
|
|
itr != testVars.end() ; ++itr) {
|
|
if(*itr == "") continue;
|
|
|
|
string theValue = dronescanConfig->Require(*itr)->second;
|
|
|
|
if(Test *theTest = setTestVariable(string_upper(*itr), theValue)) {
|
|
elog << "dronescan> Test " << theTest->getName()
|
|
<< " accepted parameter " << *itr
|
|
<< " @ " << theValue
|
|
<< std::endl;
|
|
} else {
|
|
elog << "dronescan> No test accepted variable: "
|
|
<< *itr << " @ " << theValue
|
|
<< std::endl;
|
|
::exit(1);
|
|
}
|
|
}
|
|
|
|
/* Set up database */
|
|
string sqlHost(dronescanConfig->Require("sqlHost")->second);
|
|
string sqlPort(dronescanConfig->Require("sqlPort")->second);
|
|
string sqlDB(dronescanConfig->Require("sqlDB")->second);
|
|
string sqlUsername(dronescanConfig->Require("sqlUser")->second);
|
|
string sqlPass(dronescanConfig->Require("sqlPass")->second);
|
|
|
|
std::stringstream connectString;
|
|
connectString << "host=" << sqlHost << " "
|
|
<< "port=" << sqlPort << " "
|
|
<< "dbname=" << sqlDB << " "
|
|
<< "user=" << sqlUsername << " "
|
|
<< "password=" << sqlPass
|
|
;
|
|
elog << "dronescan::dronescan> Connecting to SQL Server." << std::endl;
|
|
|
|
//SQLDb = new PgDatabase(connectString.str().c_str());
|
|
SQLDb = new dbHandle( sqlHost,
|
|
atoi( sqlPort ),
|
|
sqlDB,
|
|
sqlUsername,
|
|
sqlPass ) ;
|
|
if(SQLDb->ConnectionBad()) {
|
|
elog << "dronescan> Failed to connect to SQL server: "
|
|
<< SQLDb->ErrorMessage()
|
|
<< std::endl;
|
|
::exit(0);
|
|
}
|
|
|
|
elog << "dronescan> Established connection to SQL server." << std::endl;
|
|
|
|
/* Preload caches */
|
|
preloadFakeClientCache();
|
|
preloadUserCache();
|
|
if (preloadExceptionalChannels() == false) {
|
|
//If exceptional channels can't be loaded, we don't want glines to be turned on
|
|
jcGlineEnable = false;
|
|
}
|
|
|
|
/* Initialise statistics */
|
|
customDataCounter = 0;
|
|
|
|
/* Setup fake oper record if necessary */
|
|
unsigned short fakeOperLevel = atoi(dronescanConfig->Require("fakeOperLevel")->second.c_str());
|
|
if( fakeOperLevel > 0 ) {
|
|
/* Create a fake record for opers to use */
|
|
fakeOperUser = new sqlUser(0);
|
|
fakeOperUser->setAccess(fakeOperLevel);
|
|
fakeOperUser->setFlags(0);
|
|
fakeOperUser->setLastUpdated(::time(0));
|
|
fakeOperUser->setLastUpdatedBy("Internal");
|
|
} else {
|
|
fakeOperUser = 0;
|
|
}
|
|
|
|
/* Set up our timer. */
|
|
theTimer = new Timer();
|
|
|
|
/* Register commands available to users */
|
|
RegisterCommand(new ACCESSCommand(this, "ACCESS", "() (<user>)"));
|
|
RegisterCommand(new ADDUSERCommand(this, "ADDUSER", "<user> <access>"));
|
|
RegisterCommand(new ADDEXCEPTIONALCHANNELCommand(this, "ADDEXCEPTIONALCHANNEL","<channel name>"));
|
|
RegisterCommand(new ANALYSECommand(this, "ANALYSE", "<#channel>"));
|
|
RegisterCommand(new CHECKCommand(this, "CHECK", "(<#channel>) (<user>)"));
|
|
RegisterCommand(new FAKECommand(this, "FAKE", "(activate)"));
|
|
RegisterCommand(new LISTCommand(this, "LIST", "(active|fakeclients|joinflood|users)"));
|
|
RegisterCommand(new MODUSERCommand(this, "MODUSER", "(ACCESS <user> <level>"));
|
|
RegisterCommand(new QUOTECommand(this, "QUOTE", "<string>"));
|
|
RegisterCommand(new REMEXCEPTIONALCHANNELCommand(this, "REMEXCEPTIONALCHANNEL","<channel name>"));
|
|
RegisterCommand(new REMUSERCommand(this, "REMUSER", "<user>"));
|
|
RegisterCommand(new STATUSCommand(this, "STATUS", ""));
|
|
RegisterCommand(new RELOADCommand(this, "RELOAD", ""));
|
|
|
|
// in case RELOAD command is used, have to iterate the userlist to know how many clients per IP are online for the gline count.
|
|
clientsIPMap.clear();
|
|
for( xNetwork::const_clientIterator cItr = Network->clients_begin() ; cItr != Network->clients_end() ; ++cItr )
|
|
{
|
|
string IP = xIP(cItr->second->getIP()).GetNumericIP();
|
|
/* Store usercount per IPs to a map */
|
|
if (clientsIPMap.find(IP) == clientsIPMap.end())
|
|
clientsIPMap.insert(std::make_pair(IP, 1));
|
|
else
|
|
clientsIPMap[IP]++;
|
|
}
|
|
|
|
} // dronescan::dronescan(const string&)
|
|
|
|
|
|
/*************************
|
|
** D E S T R U C T O R **
|
|
*************************/
|
|
dronescan::~dronescan()
|
|
{
|
|
}
|
|
|
|
|
|
/*****************************************************
|
|
** X C L I E N T M E M B E R F U N C T I O N S **
|
|
*****************************************************/
|
|
|
|
/**
|
|
* Here we run whatever needs doing as soon as we have attached to
|
|
* the xServer. We are not attached to the network at this point.
|
|
*/
|
|
void dronescan::OnAttach()
|
|
{
|
|
/* Register for global network events */
|
|
MyUplink->RegisterEvent( EVT_BURST_CMPLT, this );
|
|
MyUplink->RegisterEvent( EVT_NETJOIN, this );
|
|
MyUplink->RegisterEvent( EVT_NETBREAK, this );
|
|
MyUplink->RegisterEvent( EVT_NICK, this );
|
|
MyUplink->RegisterEvent( EVT_KILL, this );
|
|
MyUplink->RegisterEvent( EVT_QUIT, this );
|
|
|
|
/* Register for all channel events */
|
|
MyUplink->RegisterChannelEvent( xServer::CHANNEL_ALL, this );
|
|
|
|
/* Set up clearing active channels */
|
|
time_t theTime = time(0) + dcInterval;
|
|
tidClearActiveList = MyUplink->RegisterTimer(theTime, this, 0);
|
|
|
|
/* Set up our JC counter */
|
|
theTime = time(0) + jcInterval;
|
|
tidClearJoinCounter = MyUplink->RegisterTimer(theTime, this, 0);
|
|
|
|
/* Set up cache refresh timer */
|
|
theTime = time(0) + rcInterval;
|
|
tidRefreshCaches = MyUplink->RegisterTimer(theTime, this, 0);
|
|
|
|
theTime = time(0) + ncInterval;
|
|
tidClearNickCounter = MyUplink->RegisterTimer(theTime, this, 0);
|
|
|
|
theTime = time(0) + gbInterval;
|
|
tidGlineQueue = MyUplink->RegisterTimer(theTime,this,0);
|
|
|
|
xClient::OnAttach() ;
|
|
} // dronescan::OnAttach()
|
|
|
|
/**
|
|
* Here we decide what channels our xClient needs to burst in to.
|
|
* The only channel of any interest to us is our console channel, which is
|
|
* loaded from the configuration file.
|
|
*/
|
|
void dronescan::BurstChannels()
|
|
{
|
|
MyUplink->JoinChannel(this, consoleChannel,
|
|
dronescanConfig->Require("consoleChannelModes")->second);
|
|
|
|
/* We need to assign a clientData to all xClients on this xServer */
|
|
xNetwork::const_localClientIterator itr = Network->localClient_begin();
|
|
for( ; itr != Network->localClient_end(); ++itr )
|
|
{
|
|
xClient *theXClient = itr->second;
|
|
iClient *theClient = theXClient->getInstance();
|
|
|
|
clientData *newData = new clientData();
|
|
newData->setState(NORMAL);
|
|
|
|
theClient->setCustomData(this, newData);
|
|
customDataCounter++;
|
|
}
|
|
|
|
/* Set the topic */
|
|
setConsoleTopic();
|
|
|
|
return xClient::BurstChannels();
|
|
} // dronescan::BurstChannels()
|
|
|
|
void dronescan::OnCTCP( iClient* theClient, const string& CTCP,
|
|
const string& Message, bool Secure )
|
|
{
|
|
StringTokenizer st(CTCP);
|
|
|
|
if(st.empty()) return ;
|
|
|
|
string Command = string_upper(st[0]);
|
|
|
|
if("DCC" == Command) {
|
|
DoCTCP(theClient, CTCP, "REJECT");
|
|
} else if("PING" == Command) {
|
|
DoCTCP(theClient, CTCP, Message);
|
|
} else if("VERSION" == Command) {
|
|
DoCTCP(theClient, CTCP, "GNUWorld DroneScan v0.0.9.2");
|
|
}
|
|
|
|
xClient::OnCTCP(theClient, CTCP, Message, Secure);
|
|
}
|
|
|
|
/**
|
|
* Here we receive network events that we are registered for.
|
|
*/
|
|
void dronescan::OnEvent( const eventType& theEvent,
|
|
void *Data1, void *Data2, void *Data3, void *Data4)
|
|
{
|
|
switch( theEvent )
|
|
{
|
|
case EVT_BURST_CMPLT :
|
|
{
|
|
log(DBG, "Caught EOB. Resetting frequencies.");
|
|
updateState();
|
|
break;
|
|
} // EVT_BURST_CMPLT
|
|
case EVT_NETJOIN :
|
|
{
|
|
changeState(BURST);
|
|
break;
|
|
}
|
|
case EVT_NETBREAK :
|
|
{
|
|
updateState();
|
|
break;
|
|
}
|
|
case EVT_NICK :
|
|
{
|
|
handleNewClient( static_cast< iClient* >( Data1 ) );
|
|
break;
|
|
} // EVT_NICK
|
|
case EVT_CHNICK:
|
|
{
|
|
handleNickChange( static_cast< iClient* >( Data1 ) );
|
|
}
|
|
|
|
case EVT_KILL : /* Intentional drop through */
|
|
case EVT_QUIT :
|
|
{
|
|
iClient *theClient = static_cast< iClient* >
|
|
( theEvent == EVT_KILL ?
|
|
Data2 :
|
|
Data1 );
|
|
|
|
/* Store usercount per IPs to a map */
|
|
string IP = xIP(theClient->getIP()).GetNumericIP();
|
|
if (clientsIPMap[IP] <= 1)
|
|
clientsIPMap.erase(IP);
|
|
else
|
|
clientsIPMap[IP]--;
|
|
|
|
clientData *theData = static_cast< clientData* >
|
|
( theClient->removeCustomData(this) );
|
|
|
|
delete(theData);
|
|
--customDataCounter;
|
|
break;
|
|
}
|
|
} // switch( theEvent )
|
|
|
|
xClient::OnEvent( theEvent, Data1, Data2, Data3, Data4);
|
|
}
|
|
|
|
/** Receive channel events. */
|
|
void dronescan::OnChannelEvent( const channelEventType& theEvent,
|
|
Channel *theChannel, void *Data1, void *Data2, void *Data3, void *Data4 )
|
|
{
|
|
/* If we are bursting, we don't want to be checking joins. */
|
|
if(currentState == BURST) return ;
|
|
|
|
/* If this is not a join and not part, we don't care. */
|
|
if(theEvent != EVT_JOIN && theEvent != EVT_PART) return ;
|
|
|
|
iClient* theClient = static_cast< iClient* > (Data1);
|
|
if(theEvent == EVT_JOIN)
|
|
{
|
|
handleChannelJoin(theChannel,theClient);
|
|
}
|
|
else if (theEvent == EVT_PART)
|
|
{
|
|
handleChannelPart(theChannel,theClient);
|
|
}
|
|
xClient::OnChannelEvent( theEvent, theChannel,
|
|
Data1, Data2, Data3, Data4 ) ;
|
|
}
|
|
|
|
/**
|
|
* Here we receive private messages from iClients.
|
|
*/
|
|
void dronescan::OnPrivateMessage( iClient* theClient,
|
|
const string& Message, bool )
|
|
{
|
|
sqlUser *theUser = getSqlUser(theClient->getAccount());
|
|
|
|
/* If the client is opered, we might have a fake account */
|
|
if( !theUser && theClient->isOper() && fakeOperUser ) {
|
|
theUser = fakeOperUser;
|
|
|
|
fakeOperUser->setUserName(theClient->getNickName());
|
|
fakeOperUser->setCreated(::time(0));
|
|
fakeOperUser->setLastSeen(::time(0));
|
|
}
|
|
|
|
if(!theUser) return ;
|
|
if (!theClient->isOper())
|
|
return;
|
|
|
|
/* We have now seen this user! */
|
|
theUser->setLastSeen(::time(0));
|
|
theUser->commit();
|
|
|
|
/* If we are currently in BURST, we don't accept commands */
|
|
/*if(BURST == currentState) {
|
|
Reply(theClient, "Sorry, I do not accept commands during a burst.");
|
|
return ;
|
|
}*/
|
|
|
|
StringTokenizer st(Message);
|
|
|
|
if(st.size() < 1) return ;
|
|
|
|
string Command = string_upper(st[0]);
|
|
commandMapType::iterator commandHandler = commandMap.find(Command);
|
|
|
|
if(commandHandler != commandMap.end())
|
|
{
|
|
commandHandler->second->Exec(theClient, Message, theUser);
|
|
return ;
|
|
}
|
|
|
|
if("INVITE" == Command)
|
|
{
|
|
Invite(theClient, consoleChannel);
|
|
return ;
|
|
}
|
|
|
|
/* This is commented out because it doesn't work at the moment */
|
|
/* if("RELOAD" == Command)
|
|
{
|
|
getUplink()->UnloadClient(this, "Reloading...");
|
|
getUplink()->LoadClient("libdronescan.la", getConfigFileName());
|
|
return ;
|
|
}
|
|
*/
|
|
if("STATS" == Command)
|
|
{
|
|
Reply(theClient, "Allocated custom data: %d", customDataCounter);
|
|
|
|
Reply(theClient, "CM/NM/CC: %0.2lf/%0.2lf/%u",
|
|
channelMargin,
|
|
nickMargin,
|
|
channelCutoff
|
|
);
|
|
|
|
Reply(theClient, "jcI/jcC : %u/%u",
|
|
jcInterval,
|
|
jcCutoff
|
|
);
|
|
Reply(theClient, "ncI/ncC : %u/%u",
|
|
ncInterval,
|
|
ncCutoff
|
|
);
|
|
return ;
|
|
}
|
|
|
|
if("RESET" == Command)
|
|
{
|
|
resetAndCheck();
|
|
return ;
|
|
}
|
|
|
|
if(st.size() < 2) return ;
|
|
|
|
if("INFO" == Command)
|
|
{
|
|
string nick = st[1];
|
|
iClient *targetClient = Network->findNick(nick);
|
|
if(!targetClient) return ;
|
|
Reply(theClient, "Status of %s: %s",
|
|
targetClient->getNickName().c_str(),
|
|
(isNormal(targetClient) ? "Normal" : "Abnormal" )
|
|
);
|
|
return ;
|
|
}
|
|
|
|
if(st.size() < 3) return ;
|
|
|
|
if("SET" == Command)
|
|
{
|
|
string Option = string_upper(st[1]);
|
|
|
|
//Glines on/off
|
|
if("GL" == Option) {
|
|
if ("ON" == string_upper(st[2])) {
|
|
if (jcGlineEnable) {
|
|
Reply(theClient, "I am already setting glines - no change");
|
|
return ;
|
|
}
|
|
else {
|
|
if (jcGlineEnableConf) {
|
|
jcGlineEnable = true;
|
|
Reply(theClient, "Glines activated");
|
|
log(INFO, "%s is activating auto-glines", theClient->getNickName().c_str());
|
|
}
|
|
else {
|
|
Reply(theClient, "You have to enable auto-glines in the conf first");
|
|
}
|
|
return ;
|
|
}
|
|
}
|
|
else if ("OFF" == string_upper(st[2])) {
|
|
if (!jcGlineEnable) {
|
|
Reply(theClient, "I'm not setting glines - no change");
|
|
return ;
|
|
}
|
|
else {
|
|
jcGlineEnable = false;
|
|
Reply(theClient, "Glines deactivated");
|
|
log(INFO, "%s is deactivating auto-glines", theClient->getNickName().c_str());
|
|
return ;
|
|
}
|
|
}
|
|
else {
|
|
Reply(theClient, "Syntax: set GL <ON|OFF>");
|
|
return ;
|
|
}
|
|
}
|
|
|
|
/* Global entropy options */
|
|
if("CC" == Option)
|
|
{
|
|
unsigned int newCC = atoi(st[2].c_str());
|
|
channelCutoff = newCC;
|
|
resetAndCheck();
|
|
return ;
|
|
}
|
|
if("CM" == Option)
|
|
{
|
|
double newCM = atof(st[2].c_str());
|
|
if(newCM < 0 || newCM > 1) return ;
|
|
channelMargin = newCM;
|
|
resetAndCheck();
|
|
return ;
|
|
}
|
|
if("NM" == Option)
|
|
{
|
|
double newNM = atof(st[2].c_str());
|
|
if(newNM < 0 || newNM > 1) return ;
|
|
nickMargin = newNM;
|
|
resetAndCheck();
|
|
return ;
|
|
}
|
|
|
|
/* None of the hardcoded options have hit. Try dynamic. */
|
|
if(Test *theTest = setTestVariable(Option, st[2])) {
|
|
log(INFO, "%s set %s to %s in %s",
|
|
theClient->getNickName().c_str(),
|
|
Option.c_str(),
|
|
st[2].c_str(),
|
|
theTest->getName().c_str()
|
|
);
|
|
|
|
resetAndCheck();
|
|
return ;
|
|
} else {
|
|
Reply(theClient, "No test accepted the variable %s",
|
|
Option.c_str()
|
|
);
|
|
return ;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/** Clean up after ourselves */
|
|
void dronescan::OnDetach()
|
|
{
|
|
/* We need to delete() anything we have new()d
|
|
* Currently this is:
|
|
* dronescanConfig
|
|
* Tests
|
|
* Commands
|
|
* clientData for every client on the net
|
|
* theTimer
|
|
*
|
|
* It is important this is kept up to date so that reload() does not leak.
|
|
* We also need to unregister any timers we have so GNUworld doesn't segfault :)
|
|
*/
|
|
|
|
/* Delete our config */
|
|
delete dronescanConfig;
|
|
|
|
/* Delete our timer */
|
|
delete theTimer;
|
|
|
|
/* Delete commands */
|
|
for(commandMapType::iterator itr = commandMap.begin() ;
|
|
itr != commandMap.end() ; ++itr) {
|
|
delete itr->second;
|
|
}
|
|
commandMap.clear();
|
|
|
|
/* Delete tests */
|
|
for(testMapType::iterator itr = testMap.begin() ;
|
|
itr != testMap.end() ; ++itr) {
|
|
delete itr->second;
|
|
}
|
|
testMap.clear();
|
|
|
|
/* Iterate over clients to delete clientData */
|
|
for(xNetwork::const_clientIterator ptr = Network->clients_begin() ;
|
|
ptr != Network->clients_end() ; ++ptr) {
|
|
iClient *theClient = ptr->second;
|
|
clientData *theData = static_cast< clientData* > (theClient->removeCustomData(this));
|
|
delete theData;
|
|
}
|
|
|
|
/* Unregister the join counting timer */
|
|
if(!MyUplink->UnRegisterTimer(tidClearJoinCounter, 0) ||
|
|
!MyUplink->UnRegisterTimer(tidClearNickCounter, 0) ||
|
|
!MyUplink->UnRegisterTimer(tidClearActiveList, 0)) {
|
|
elog << "dronescan::~dronescan> "
|
|
<< "Could not unregister timer. Expect problems shortly."
|
|
<< std::endl;
|
|
}
|
|
|
|
/* Done! */
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Receive our own timed events. */
|
|
void dronescan::OnTimer( const xServer::timerID& theTimer , void *)
|
|
{
|
|
time_t theTime;
|
|
|
|
if(theTimer == tidClearActiveList)
|
|
{
|
|
droneChannelsType::iterator dcitr, next_dcitr;
|
|
time_t joinSince = ::time(0) - dcInterval;
|
|
|
|
for( dcitr = droneChannels.begin() ;
|
|
dcitr != droneChannels.end() ; dcitr = next_dcitr ) {
|
|
next_dcitr = dcitr;
|
|
++next_dcitr;
|
|
|
|
if( dcitr->second->getLastJoin() <= joinSince ) {
|
|
log(DBG, "Removing %s.",
|
|
dcitr->first.c_str()
|
|
);
|
|
|
|
delete dcitr->second;
|
|
droneChannels.erase(dcitr);
|
|
}
|
|
}
|
|
|
|
theTime = time(0) + dcInterval;
|
|
tidClearActiveList = MyUplink->RegisterTimer(theTime, this, 0);
|
|
}
|
|
|
|
if(theTimer == tidClearJoinCounter)
|
|
{
|
|
processJoinPartChannels();
|
|
}
|
|
|
|
if(theTimer == tidClearNickCounter)
|
|
{
|
|
for(ncChanMapType::const_iterator itr = ncChanMap.begin() ;
|
|
itr != ncChanMap.end() ; ++itr) {
|
|
Channel* theChan = Network->findChannel(
|
|
itr->first ) ;
|
|
if( 0 == theChan )
|
|
{
|
|
elog << "dronescan::OnTimer> Unable "
|
|
<< "to find channel: "
|
|
<< itr->first
|
|
<< std::endl ;
|
|
continue ;
|
|
}
|
|
|
|
if(itr->second >= ncCutoff)
|
|
log(WARN, "Nick flood over in %s. Total joins: %u. Total size: %d",
|
|
itr->first.c_str(),
|
|
itr->second,
|
|
theChan->size()
|
|
);
|
|
}
|
|
|
|
log(DBG, "Clearing %u records from the join counter.",
|
|
ncChanMap.size()
|
|
);
|
|
ncChanMap.clear();
|
|
|
|
theTime = time(0) + ncInterval;
|
|
tidClearNickCounter = MyUplink->RegisterTimer(theTime, this, 0);
|
|
}
|
|
|
|
if(theTimer == tidRefreshCaches) {
|
|
log(DBG, "Refreshing caches");
|
|
|
|
preloadUserCache();
|
|
preloadExceptionalChannels();
|
|
|
|
theTime = time(0) + rcInterval;
|
|
tidRefreshCaches = MyUplink->RegisterTimer(theTime, this, 0);
|
|
}
|
|
|
|
if(theTimer == tidGlineQueue) {
|
|
processGlineQueue();
|
|
|
|
theTime = time(0) + gbInterval;
|
|
tidGlineQueue = MyUplink->RegisterTimer(theTime, this , 0);
|
|
}
|
|
}
|
|
|
|
/*******************************************
|
|
** D R O N E S C A N F U N C T I O N S **
|
|
*******************************************/
|
|
|
|
|
|
void dronescan::processJoinPartChannels()
|
|
{
|
|
static time_t jcFCInterval = ::time(0);
|
|
for(jcChanMapType::const_iterator itr = jcChanMap.begin() ;
|
|
itr != jcChanMap.end() ; ++itr)
|
|
{
|
|
Channel* theChan = Network->findChannel(
|
|
itr->first ) ;
|
|
if( 0 == theChan )
|
|
{
|
|
elog << "dronescan::OnTimer> Unable "
|
|
<< "to find channel: "
|
|
<< itr->first
|
|
<< std::endl ;
|
|
/* The join/flood is now reported/glined even if the channel is empty.
|
|
We now have to make sure theChan != 0 below.
|
|
*/
|
|
}
|
|
jfChannel* jChannel = itr->second;
|
|
|
|
if(jChannel->getNumOfJoins() >= jcCutoff)
|
|
{
|
|
log(WARN, "Join flood over in %s. Total joins: %u. Total parts: %u. Total size: %d",
|
|
itr->first.c_str(),
|
|
jChannel->getNumOfJoins(),
|
|
jChannel->getNumOfParts(),
|
|
(theChan == 0 ? 0 : theChan->size())
|
|
);
|
|
std::list<std::pair<glineData*,std::list<string> > > glined;
|
|
jfChannel::joinPartMapIterator joinPartIt = jChannel->joinPartBegin();
|
|
jfChannel::joinPartMapIterator joinPartEnd = jChannel->joinPartEnd();
|
|
std::stringstream names;
|
|
std::stringstream excluded;
|
|
std::stringstream tempNames;
|
|
int clientcount = 0;
|
|
|
|
bool isoktogline = false;
|
|
bool isoktogline2 = false;
|
|
for(;joinPartIt != joinPartEnd; ++joinPartIt )
|
|
{
|
|
isoktogline = ((::time(0) - lastBurstTime) > 25 && jcGlineEnable && jChannel->getNumOfJoins() > jcMinJFSizeToGline && (jChannel->getNumOfParts() > jcMinJFSizeToGline || (joinPartIt->second.numOfJoins >= jcMinJoinToGlineJOnly && jChannel->getNumOfJoins() >= jcMinJFJOnlySizeToGline))) ? true : false;
|
|
if (isoktogline)
|
|
isoktogline2 = true;
|
|
int numOfUsernames = 0;
|
|
if((joinPartIt->second.numOfJoins >= jcMinJoinToGline &&
|
|
joinPartIt->second.numOfParts >= jcMinJoinToGline) || (joinPartIt->second.numOfJoins >= jcMinJoinToGlineJOnly && jChannel->getNumOfJoins() >= jcMinJFJOnlySizeToGline))
|
|
{
|
|
if(!joinPartIt->second.seenOper && !joinPartIt->second.seenLoggedInUser)
|
|
{
|
|
std::list<string>::const_iterator numericsIt = joinPartIt->second.numerics.begin();
|
|
tempNames << joinPartIt->first.c_str() << std::string("[")
|
|
<< joinPartIt->second.numOfJoins
|
|
<< std::string("]<");
|
|
std::list<std::string> clients;
|
|
for(;numericsIt != joinPartIt->second.numerics.end();++numericsIt)
|
|
{
|
|
iClient* theClient = Network->findClient(*numericsIt);
|
|
if(theClient && !strcmp(xIP(theClient->getIP()).GetNumericIP().c_str()
|
|
,joinPartIt->first.c_str()))
|
|
{
|
|
tempNames << theClient->getNickName()
|
|
<< ",";
|
|
if (isoktogline == true)
|
|
{
|
|
clients.push_back(theClient->getNickName() + "!" + theClient->getUserName() +"@"
|
|
+ xIP(theClient->getIP()).GetNumericIP() + " " + theClient->getDescription());
|
|
|
|
}
|
|
}
|
|
}
|
|
tempNames << ">";
|
|
if(names.str().size() + tempNames.str().size() > 400)
|
|
{
|
|
outputNames(itr->first,names,false,isoktogline);
|
|
}
|
|
names << " " << tempNames.str();
|
|
tempNames.str("");
|
|
if (isoktogline == true)
|
|
{
|
|
glineData* theGline = new (std::nothrow) glineData("*@" +joinPartIt->first,jcGlineReason,jcGlineLength);
|
|
assert(theGline != 0);
|
|
glined.push_back(std::pair<glineData*,std::list<std::string> >(theGline,clients));
|
|
clientcount += clients.size();
|
|
}
|
|
} else {
|
|
excluded << joinPartIt->first.c_str()
|
|
<< "[";
|
|
if(joinPartIt->second.seenOper)
|
|
{
|
|
excluded << "O";
|
|
}
|
|
if(joinPartIt->second.seenLoggedInUser)
|
|
{
|
|
excluded << "L";
|
|
}
|
|
excluded << "],";
|
|
if(excluded.str().size() > 400)
|
|
{
|
|
outputNames(itr->first,excluded,true,isoktogline);
|
|
}
|
|
|
|
#ifdef ENABLE_LOG4CPLUS
|
|
std::list<std::pair<std::string,std::string > >::const_iterator userNamesIt = joinPartIt->second.userNames.begin();
|
|
for(;userNamesIt != joinPartIt->second.userNames.end(); ++ userNamesIt)
|
|
{
|
|
iClient* theClient = Network->findClient(userNamesIt->first);
|
|
if(theClient && !strcmp(xIP(theClient->getIP()).GetNumericIP().c_str()
|
|
,joinPartIt->first.c_str()))
|
|
{
|
|
if(numOfUsernames++ == 0)
|
|
{
|
|
log(JF_CSERVICE, "Join flood over in %s. Total joins: %u. Total parts: %u. Total size: %d",
|
|
itr->first.c_str(),
|
|
jChannel->getNumOfJoins(),
|
|
jChannel->getNumOfParts(),
|
|
(theChan == 0 ? 0 : theChan->size())
|
|
);
|
|
}
|
|
/*log(JF_CSERVICE,"%s %s!%s@%s %s",userNamesIt->second.c_str(),
|
|
theClient->getNickName().c_str(),
|
|
theClient->getUserName().c_str(),
|
|
xIP(theClient->getIP()).GetNumericIP().c_str(),
|
|
theClient->getDescription().c_str());*/
|
|
log(JF_CSERVICE,"(%s) %s!%s@%s.users.undernet.org %s",xIP(theClient->getIP()).GetNumericIP().c_str(),
|
|
theClient->getNickName().c_str(),
|
|
theClient->getUserName().c_str(),
|
|
userNamesIt->second.c_str(),
|
|
theClient->getDescription().c_str());
|
|
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
isoktogline = isoktogline2;
|
|
|
|
if(names.str().size() > 0)
|
|
{
|
|
outputNames(itr->first,names,false,isoktogline);
|
|
}
|
|
if(excluded.str().size() > 0)
|
|
{
|
|
outputNames(itr->first,excluded,true,isoktogline);
|
|
}
|
|
if (isoktogline == true)
|
|
{
|
|
if ((glined.size() < 3) && (clientcount < 8))
|
|
{
|
|
if (glined.size() != 0) {
|
|
log(WARN,"Aborting glines for channel %s because only %d flooding clients from %d addresses were found",
|
|
itr->first.c_str(),clientcount,glined.size());
|
|
}
|
|
}
|
|
#ifdef ENABLE_LOG4CPLUS
|
|
else {
|
|
log(JF_GLINED,"Join flood over in %s. Total joins: %u. Total parts: %u. Total size: %d. Total addresses glined %d.",
|
|
itr->first.c_str(),
|
|
jChannel->getNumOfJoins(),
|
|
jChannel->getNumOfParts(),
|
|
(theChan == 0 ? 0 : theChan->size()),
|
|
glined.size());
|
|
}
|
|
#endif
|
|
}
|
|
std::list<std::pair<glineData*,std::list<string> > >::iterator glinesIt = glined.begin();
|
|
for(; glinesIt != glined.end();++glinesIt)
|
|
{
|
|
glineData* curGline = glinesIt->first;
|
|
if ((glined.size() >= 3) || (clientcount >= 8))
|
|
{
|
|
/*
|
|
string IP = xIP(theClient->getIP()).GetNumericIP();
|
|
clientsIPFloodMapType::iterator tmpItr = clientsIPFloodMap.find(IP);
|
|
if (tmpItr != clientsIPFloodMap.end()) {
|
|
jcFloodClients* aptr = tmpItr->second;
|
|
clientsIPFloodMap.erase(tmpItr);
|
|
delete aptr;
|
|
}
|
|
IPJQueueType::iterator tmpItr2 = IPJQueue.begin();
|
|
for (; tmpItr2 != IPJQueue.end(); tmpItr2++) {
|
|
if (*tmpItr2 == IP) {
|
|
IPJQueue.erase(tmpItr2);
|
|
break;
|
|
}
|
|
}
|
|
*/
|
|
std::list<string>::iterator clientsIt = glinesIt->second.begin();
|
|
#ifdef ENABLE_LOG4CPLUS
|
|
for(; clientsIt != glinesIt->second.end(); ++ clientsIt)
|
|
{
|
|
log(JF_GLINED,(*clientsIt).c_str());
|
|
}
|
|
#endif
|
|
//Checking if the gline is already present in the queue
|
|
glineQueueType::iterator git = glineQueue.begin();
|
|
bool found_gline_in_queue = false;
|
|
for (; git != glineQueue.end(); ++git) {
|
|
if (strcasecmp((*git)->getHost().c_str(),curGline->getHost().c_str()) == 0)
|
|
found_gline_in_queue = true;
|
|
}
|
|
//skipping findGline() check, cpu expensive.
|
|
//if ((MyUplink->findGline(curGline->getHost()) == 0) && (found_gline_in_queue == false)) {
|
|
if (found_gline_in_queue == false) {
|
|
glineQueue.push_back(curGline);
|
|
}
|
|
}
|
|
else {
|
|
delete curGline;
|
|
}
|
|
}
|
|
|
|
if ((glined.size() >= 3) || (clientcount >= 8))
|
|
log(WARN, "Glining %d floodbots from %d different ips", clientcount, glined.size());
|
|
}
|
|
|
|
delete itr->second;
|
|
|
|
}
|
|
|
|
|
|
IPJQueueType::iterator Itr2 = IPJQueue.begin();
|
|
std::stringstream tempNames;
|
|
int clientcount = 0;
|
|
for (; Itr2 != IPJQueue.end(); Itr2++) {
|
|
glineQueueType::iterator git = glineQueue.begin();
|
|
bool found_gline_in_queue = false;
|
|
for (; git != glineQueue.end(); ++git) {
|
|
std::string gmask = "*@" + *Itr2;
|
|
if (strcasecmp((*git)->getHost().c_str(),gmask.c_str()) == 0)
|
|
found_gline_in_queue = true;
|
|
}
|
|
if (found_gline_in_queue == true) {
|
|
continue;
|
|
}
|
|
#ifdef ENABLE_LOG4CPLUS
|
|
if (clientcount == 0)
|
|
log(JF_GLINED,"--- Multiple join floods without parts ---");
|
|
#endif
|
|
clientcount++;
|
|
if (tempNames.str().size() > 380) {
|
|
std::stringstream s;
|
|
s << "Glining the following clients:" << tempNames.str();
|
|
log(WARN, "%s", s.str().c_str());
|
|
tempNames.str("");
|
|
}
|
|
tempNames << " ";
|
|
jcFloodClients* jcFC = clientsIPFloodMap[*Itr2];
|
|
std::list<string>::const_iterator Itr3 = jcFC->nicks.begin();
|
|
tempNames << *Itr2 << std::string("[")
|
|
<< jcFC->count
|
|
<< std::string("]<");
|
|
for (; Itr3 != jcFC->nicks.end(); Itr3++) {
|
|
tempNames << *Itr3 << ",";
|
|
}
|
|
tempNames << ">(";
|
|
Itr3 = jcFC->chans.begin();
|
|
for (; Itr3 != jcFC->chans.end(); Itr3++) {
|
|
tempNames << *Itr3 << ",";
|
|
}
|
|
tempNames << ")";
|
|
|
|
glineData* theGline = new (std::nothrow) glineData("*@" +*Itr2,jcGlineReason,jcGlineLength);
|
|
assert(theGline != 0);
|
|
glineQueue.push_back(theGline);
|
|
#ifdef ENABLE_LOG4CPLUS
|
|
Itr3 = jcFC->log.begin();
|
|
for (; Itr3 != jcFC->log.end(); Itr3++) {
|
|
log(JF_GLINED,"%s",(*Itr3).c_str());
|
|
}
|
|
#endif
|
|
|
|
}
|
|
if (clientcount > 0) {
|
|
#ifdef ENABLE_LOG4CPLUS
|
|
log(JF_GLINED,"--- End of multiple join floods without parts ---");
|
|
#endif
|
|
std::string astr = "Glining the following clients:" + tempNames.str();
|
|
log(WARN, "%s", astr.c_str());
|
|
log(WARN, "Glining %d different ips for multiple joins during join floods", clientcount);
|
|
}
|
|
if (IPJQueue.size() > 0)
|
|
IPJQueue.clear();
|
|
if ((unsigned int) (jcFCInterval - ::time(0)) >= jcJoinsPerIPTime) {
|
|
jcFloodClients* jcFC;
|
|
jcFCInterval = ::time(0);
|
|
clientsIPFloodMapType::iterator anItr = clientsIPFloodMap.begin();
|
|
for (; anItr != clientsIPFloodMap.end(); anItr++) {
|
|
jcFC = anItr->second;
|
|
if ((unsigned int) (::time(0) - jcFC->ctime) > jcJoinsPerIPTime) {
|
|
clientsIPFloodMap.erase(anItr);
|
|
delete jcFC;
|
|
}
|
|
}
|
|
}
|
|
|
|
log(DBG, "Clearing %u records from the join counter.",
|
|
jcChanMap.size()
|
|
);
|
|
jcChanMap.clear();
|
|
|
|
time_t theTime = time(0) + jcInterval;
|
|
tidClearJoinCounter = MyUplink->RegisterTimer(theTime, this, 0);
|
|
|
|
}
|
|
|
|
void dronescan::outputNames(const std::string& chanName,std::stringstream& names,bool exclude,bool isoktogline)
|
|
{
|
|
names << "\r\n";
|
|
//std::string gString = exclude ? "Excluding" : jcGlineEnable ? "Glining" : "Suppose to gline";
|
|
std::string gString = exclude ? "Excluding" : isoktogline ? "Glining" : "Suppose to gline";
|
|
log(WARN,"%s the following clients from %s: %s",gString.c_str(),
|
|
chanName.c_str(),names.str().c_str());
|
|
names.str("");
|
|
}
|
|
|
|
void dronescan::processGlineQueue() {
|
|
if(glineQueue.size() > 0)
|
|
{
|
|
unsigned int count = 0;
|
|
int userCount;
|
|
glineData* curGline;
|
|
char us[100];
|
|
log(DBG,"Processing gline queue\r\n");
|
|
for(; count < gbCount && glineQueue.size() > 0;)
|
|
{
|
|
curGline = glineQueue.front();
|
|
glineQueue.pop_front();
|
|
//userCount = Network->countMatchingRealUserHost(curGline->getHost());
|
|
StringTokenizer st(curGline->getHost(),'@');
|
|
|
|
recentlyGlinedIpsType::iterator rItr = recentlyGlinedIps.begin();
|
|
bool alreadyGlined = false;
|
|
for (; rItr != recentlyGlinedIps.end(); rItr++) {
|
|
if ((rItr->first == st[1]) && ((::time(0) - rItr->second) < 120)) {
|
|
alreadyGlined = true;
|
|
}
|
|
}
|
|
if (alreadyGlined) {
|
|
delete curGline;
|
|
continue;
|
|
}
|
|
|
|
userCount = clientsIPMap[st[1]];
|
|
us[0] = '\0';
|
|
sprintf(us,"%d",userCount);
|
|
std::string glineReason = string("AUTO [") + us + string("] ") + curGline->getReason();
|
|
MyUplink->setGline(nickName,curGline->getHost(),
|
|
glineReason,curGline->getExpires(),::time(0),this);
|
|
|
|
recentlyGlinedIps.push_front(std::pair<std::string,int>(st[1],::time(0)));
|
|
if (recentlyGlinedIps.size() > RecentlyGlinedIpsSize) {
|
|
recentlyGlinedIps.pop_back();
|
|
}
|
|
delete curGline;
|
|
count++;
|
|
}
|
|
|
|
log(DBG,"Processed %d glines from the gline queue, %d glines are left in the queue",count,glineQueue.size());
|
|
}
|
|
}
|
|
/** Report a SQL error as necessary. */
|
|
void dronescan::doSqlError(const string& theQuery, const string& theError)
|
|
{
|
|
/* First, log it to error out */
|
|
elog << "SQL> Whilst executing: "
|
|
<< theQuery
|
|
<< std::endl;
|
|
elog << "SQL> "
|
|
<< theError
|
|
<< std::endl;
|
|
}
|
|
|
|
/** This function allows us to change our current state. */
|
|
void dronescan::changeState(DS_STATE newState)
|
|
{
|
|
if(currentState == newState) return;
|
|
|
|
/* Instantiate our own timer so we don't interfere with anyone elses */
|
|
Timer stateTimer;
|
|
stateTimer.Start();
|
|
|
|
/* First, do what we need to exit our current state */
|
|
switch( currentState )
|
|
{
|
|
case BURST :
|
|
{
|
|
calculateEntropy();
|
|
setNickStates();
|
|
elog << "*** DroneScan: Exiting state BURST"
|
|
<< std::endl;
|
|
lastBurstTime = ::time(0);
|
|
break;
|
|
}
|
|
case RUN :
|
|
{
|
|
elog << "*** DroneScan: Exiting state RUN"
|
|
<< std::endl;
|
|
}
|
|
}
|
|
|
|
currentState = newState;
|
|
|
|
switch( currentState )
|
|
{
|
|
case BURST :
|
|
{
|
|
charMap.clear();
|
|
elog << "*** DroneScan: Entering state BURST"
|
|
<< std::endl;
|
|
break;
|
|
}
|
|
case RUN :
|
|
{
|
|
elog << "*** DroneScan: Entering state RUN"
|
|
<< std::endl;
|
|
checkChannels();
|
|
break;
|
|
}
|
|
}
|
|
|
|
log(INFO, "Changed state in: %u ms", stateTimer.stopTimeMS());
|
|
}
|
|
|
|
void dronescan::updateState()
|
|
{
|
|
std::list< iServer* > burstingServers = Network->getAllBurstingServers();
|
|
std::list<iServer*>::iterator ptr = burstingServers.begin();
|
|
for(;ptr != burstingServers.end();++ptr)
|
|
{
|
|
log(DBG,"Server %s is bursting",(*ptr)->getName().c_str());
|
|
}
|
|
changeState(burstingServers.size() == 0 ? RUN : BURST);
|
|
}
|
|
|
|
void dronescan::handleChannelJoin( Channel* theChannel, iClient* theClient)
|
|
{
|
|
/* If this channel is too small, don't test it. */
|
|
if(theChannel->size() < channelCutoff) return ;
|
|
|
|
/* If this is an exceptional channel, dont test it. */
|
|
if(isExceptionalChannel(theChannel->getName())) return;
|
|
|
|
/* Iterate over our available tests, checking this channel */
|
|
if(droneChannels.find(theChannel->getName()) == droneChannels.end()) {
|
|
/* This channel is not currently listed as active */
|
|
// Check the channel for abnormalities, if enough
|
|
// are found then it will be added to the
|
|
// droneChannels structure by checkChannel()
|
|
checkChannel( theChannel );
|
|
}
|
|
|
|
/* Reset lastjoin on the active channel */
|
|
droneChannelsType::iterator droneChanItr =
|
|
droneChannels.find( theChannel->getName() ) ;
|
|
|
|
// If the channel is still not in the droneChannels
|
|
// structure then it is a "normal" channel
|
|
if( droneChanItr != droneChannels.end() )
|
|
{
|
|
droneChanItr->second->setLastJoin( ::time( 0 ) ) ;
|
|
}
|
|
|
|
/* Do join count processing if applicable */
|
|
const string& channelName = theChannel->getName();
|
|
|
|
jcChanMapIterator jcChanIt = jcChanMap.find(channelName);
|
|
jfChannel* channel;
|
|
if( jcChanIt != jcChanMap.end() )
|
|
{
|
|
channel = jcChanIt->second;
|
|
}
|
|
else
|
|
{
|
|
channel = new (std::nothrow) jfChannel(channelName);
|
|
assert(channel != NULL);
|
|
jcChanMap[channelName] = channel;
|
|
}
|
|
unsigned int joinCount = channel->advanceChannelJoin();
|
|
|
|
if(joinCount == jcCutoff)
|
|
{
|
|
log(WARN, "%s is being join flooded.",
|
|
channelName.c_str()
|
|
);
|
|
//if the channel was not in flooded state, clear its join record
|
|
channel->setJoinFlooded(true);
|
|
}
|
|
if(channel->getJoinFlooded())
|
|
{
|
|
channel->addJoin(theClient);
|
|
if (joinCount >= jcCutoff) {
|
|
string IP = xIP(theClient->getIP()).GetNumericIP();
|
|
jcFloodClients* jcFC;
|
|
if ((::time(0) - lastBurstTime) > 25 && jcGlineEnable) {
|
|
clientsIPFloodMapType::const_iterator Itr = clientsIPFloodMap.find(IP);
|
|
if (Itr != clientsIPFloodMap.end()) {
|
|
jcFC = Itr->second;
|
|
if ((unsigned int) (::time(0) - jcFC->ctime) > jcJoinsPerIPTime) {
|
|
jcFC->ctime = ::time(0);
|
|
jcFC->count = 0;
|
|
jcFC->chans.clear();
|
|
jcFC->nicks.clear();
|
|
jcFC->log.clear();
|
|
}
|
|
}
|
|
else {
|
|
jcFC = new (std::nothrow) jcFloodClients;
|
|
assert(jcFC != NULL);
|
|
jcFC->count = 0;
|
|
jcFC->ctime = ::time(0);
|
|
clientsIPFloodMap[IP] = jcFC;
|
|
}
|
|
jcFC->count++;
|
|
std::list< string >::iterator sItr;
|
|
bool isMatchFound = false;
|
|
for (sItr = jcFC->chans.begin(); sItr != jcFC->chans.end(); sItr++) {
|
|
if (*sItr == channelName) {
|
|
isMatchFound = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!isMatchFound)
|
|
jcFC->chans.push_back(channelName);
|
|
isMatchFound = false;
|
|
for (sItr = jcFC->nicks.begin(); sItr != jcFC->nicks.end(); sItr++) {
|
|
if (*sItr == theClient->getNickName()) {
|
|
isMatchFound = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!isMatchFound)
|
|
jcFC->nicks.push_back(theClient->getNickName());
|
|
|
|
std::stringstream s;
|
|
s << theClient->getNickName()
|
|
<< "!" << theClient->getUserName()
|
|
<< "@" << xIP(theClient->getIP()).GetNumericIP()
|
|
<< " " << theClient->getDescription()
|
|
<< " " << channelName << " " << ::time(0);
|
|
jcFC->log.push_back(s.str());
|
|
|
|
|
|
if ((unsigned int) jcFC->count >= jcMinJoinsPerIPToGline) {
|
|
IPJQueue.push_back(IP);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
void dronescan::handleChannelPart(Channel* theChan,iClient* theClient)
|
|
{
|
|
|
|
/* If this is an exceptional channel, dont test it. */
|
|
if(isExceptionalChannel(theChan->getName())) return;
|
|
|
|
jcChanMapIterator jcChanIt = jcChanMap.find(theChan->getName());
|
|
if(jcChanIt != jcChanMap.end() && jcChanIt->second->getJoinFlooded())
|
|
{
|
|
jcChanIt->second->advanceChannelParts();
|
|
// if(partCount == pcCutOff)
|
|
// {
|
|
// log(DEBUG,"%s is being part flooded.",
|
|
// channelName.c_str());
|
|
// jcChanIt->second->setPartFlooded(true);
|
|
// }
|
|
// if(jcChanIt->second->getPartFlooded())
|
|
// {
|
|
jcChanIt->second->addPart(theClient);
|
|
// }
|
|
}
|
|
}
|
|
|
|
/** Here we handle new clients as they connect to the network. */
|
|
void dronescan::handleNewClient( iClient* theClient )
|
|
{
|
|
/* First, all new clients must be assigned a clientData */
|
|
clientData* theData = new clientData();
|
|
assert(theClient->setCustomData(this, theData));
|
|
++customDataCounter;
|
|
|
|
string IP = xIP(theClient->getIP()).GetNumericIP();
|
|
/* Store usercount per IPs to a map */
|
|
if (clientsIPMap.find(IP) == clientsIPMap.end())
|
|
clientsIPMap.insert(std::make_pair(IP, 1));
|
|
else
|
|
clientsIPMap[IP]++;
|
|
|
|
|
|
|
|
/* If we are still bursting, calculate letter frequencies */
|
|
if(currentState == BURST)
|
|
{
|
|
/* Do nothing - this will all get done later */
|
|
}
|
|
else if(currentState == RUN)
|
|
{
|
|
setClientState(theClient);
|
|
}
|
|
}
|
|
|
|
void dronescan::handleNickChange( iClient* theClient )
|
|
{
|
|
for(iClient::const_channelIterator it=theClient->channels_begin();
|
|
it!=theClient->channels_end();
|
|
++it) {
|
|
Channel *theChannel=*it;
|
|
/* If this channel is too small, don't test it. */
|
|
if(theChannel->size() < channelCutoff) return ;
|
|
|
|
/* Iterate over our available tests, checking this channel */
|
|
if(droneChannels.find(theChannel->getName()) == droneChannels.end()) {
|
|
/* This channel is not currently listed as active */
|
|
// Check the channel for abnormalities, if enough
|
|
// are found then it will be added to the
|
|
// droneChannels structure by checkChannel()
|
|
checkChannel( theChannel );
|
|
}
|
|
|
|
/* Reset lastnick on the active channel */
|
|
droneChannelsType::iterator droneChanItr =
|
|
droneChannels.find( theChannel->getName() ) ;
|
|
|
|
// If the channel is still not in the droneChannels
|
|
// structure then it is a "normal" channel
|
|
if( droneChanItr != droneChannels.end() )
|
|
{
|
|
droneChanItr->second->setLastNick( ::time( 0 ) ) ;
|
|
}
|
|
|
|
/* Do nick count processing if applicable */
|
|
const string& channelName = theChannel->getName();
|
|
|
|
ncChanMap[channelName]++;
|
|
|
|
unsigned int nickCount = ncChanMap[channelName];
|
|
|
|
if(nickCount == ncCutoff)
|
|
{
|
|
log(WARN, "%s is being nick flooded.",
|
|
channelName.c_str()
|
|
);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/** Calculate the global entropy. */
|
|
void dronescan::calculateEntropy()
|
|
{
|
|
/* Zero out everything */
|
|
charMap.clear();
|
|
averageEntropy = 0;
|
|
totalNicks = 0;
|
|
|
|
theTimer->Start();
|
|
|
|
elog << "dronescan::calculateEntropy> Calculating frequencies."
|
|
<< std::endl;
|
|
/* First, learn the entropy from all nicks */
|
|
for( xNetwork::const_clientIterator ptr = Network->clients_begin() ;
|
|
ptr != Network->clients_end() ; ++ptr)
|
|
{
|
|
const char *nickPtr = ptr->second->getNickName().c_str();
|
|
for( ; *nickPtr ; ++nickPtr)
|
|
{
|
|
charMap[*nickPtr]++;
|
|
}
|
|
++totalNicks;
|
|
}
|
|
|
|
log(DBG, "Calculated frequencies in: %u ms", theTimer->stopTimeMS());
|
|
|
|
theTimer->Start();
|
|
|
|
elog << "dronescan::calculateEntropy> Normalising frequencies."
|
|
<< std::endl;
|
|
for( charMapType::iterator itr = charMap.begin() ;
|
|
itr != charMap.end(); ++itr)
|
|
itr->second /= totalNicks;
|
|
|
|
log(DBG, "Normalised frequencies in: %u ms", theTimer->stopTimeMS());
|
|
|
|
elog << "dronescan::calculateEntropy> Calculating average entropy."
|
|
<< std::endl;
|
|
|
|
double totalEntropy = 0;
|
|
|
|
theTimer->Start();
|
|
|
|
for( xNetwork::const_clientIterator ptr = Network->clients_begin() ;
|
|
ptr != Network->clients_end() ; ++ptr)
|
|
{
|
|
if( ptr->second->isModeK() ) continue;
|
|
totalEntropy += calculateEntropy(ptr->second->getNickName());
|
|
}
|
|
|
|
log(DBG, "Total entropy : %lf", totalEntropy);
|
|
log(DBG, "Total nicks : %u", totalNicks);
|
|
|
|
averageEntropy = totalEntropy / totalNicks;
|
|
log(DBG, "Average entropy: %lf ", averageEntropy);
|
|
|
|
log(DBG, "Found entropy in: %u ms", theTimer->stopTimeMS());
|
|
}
|
|
|
|
|
|
/** Return the entropy of a given client. */
|
|
double dronescan::calculateEntropy( const iClient *theClient )
|
|
{
|
|
clientData *theData = static_cast< clientData* > ( theClient->getCustomData(this) );
|
|
if( 0 == theData ) return 0.0 ;
|
|
|
|
return theData->getEntropy();
|
|
}
|
|
|
|
|
|
/** Calculate state of all nicks. */
|
|
void dronescan::setNickStates()
|
|
{
|
|
elog << "dronescan::setNickStates> Finding states of all nicks."
|
|
<< std::endl;
|
|
|
|
theTimer->Start();
|
|
|
|
/* Now we must assign a state to each nick we see */
|
|
for( xNetwork::const_clientIterator ptr = Network->clients_begin()
|
|
; ptr != Network->clients_end() ; ++ptr)
|
|
{
|
|
setClientState( ptr->second );
|
|
}
|
|
|
|
log(DBG, "Set all nick states in: %u ms",
|
|
theTimer->stopTimeMS()
|
|
);
|
|
}
|
|
|
|
/** Check global channels for drones. */
|
|
void dronescan::checkChannels()
|
|
{
|
|
log(INFO, "Checking channels for drones:");
|
|
|
|
unsigned int noChannels = 0;
|
|
unsigned int noDrones = 0;
|
|
unsigned int failure = 0;
|
|
|
|
theTimer->Start();
|
|
|
|
xNetwork::const_channelIterator ptr = Network->channels_begin();
|
|
|
|
for( ; ptr != Network->channels_end() ; ++ptr )
|
|
{
|
|
++noChannels;
|
|
|
|
if(ptr->second->size() < channelCutoff) continue;
|
|
|
|
if(!checkChannel( ptr->second )) {
|
|
++failure;
|
|
noDrones += ptr->second->size();
|
|
}
|
|
}
|
|
|
|
log(INFO, "Finished checking %u channels. %u/%u total possible channels/clients. Duration: %u ms",
|
|
noChannels,
|
|
failure,
|
|
noDrones,
|
|
theTimer->stopTimeMS()
|
|
);
|
|
}
|
|
|
|
|
|
/** Check a channel for drones. */
|
|
bool dronescan::checkChannel( const Channel *theChannel , const iClient *theClient )
|
|
{
|
|
unsigned short int failed = 0;
|
|
|
|
/* Iterate over the tests. */
|
|
for(testMapType::iterator testItr = testMap.begin() ;
|
|
testItr != testMap.end() ; ++testItr )
|
|
{
|
|
bool hasPassed = testItr->second->isNormal(theChannel);
|
|
|
|
if(theClient)
|
|
{
|
|
Reply(theClient, "%20s: %s",
|
|
testItr->second->getName().c_str(),
|
|
hasPassed ? "PASSED" : "FAILED"
|
|
);
|
|
}
|
|
|
|
if(!hasPassed) failed += testItr->second->getWeight();
|
|
}
|
|
|
|
/* If we were checking for a client, don't output to console. */
|
|
if(theClient) return true;
|
|
|
|
/* If the failure count is over or equal to the vote cutoff
|
|
* report this channel as abnormal. */
|
|
|
|
if(failed >= voteCutoff)
|
|
{
|
|
/* This channel is voted abnormal. */
|
|
std::stringstream chanStat, chanParams;
|
|
if(theChannel->getMode(Channel::MODE_I)) chanStat << "i";
|
|
if(theChannel->getMode(Channel::MODE_R)) chanStat << "r";
|
|
|
|
if(theChannel->getMode(Channel::MODE_K)) {
|
|
chanStat << "k";
|
|
chanParams << theChannel->getKey();
|
|
}
|
|
if(theChannel->getMode(Channel::MODE_L)) {
|
|
chanStat << "l";
|
|
if(theChannel->getMode(Channel::MODE_K)) chanParams << " ";
|
|
chanParams << theChannel->getLimit();
|
|
}
|
|
|
|
log(WARN, "[%u] (%4u) %s +%s %s",
|
|
failed,
|
|
theChannel->size(),
|
|
theChannel->getName().c_str(),
|
|
chanStat.str().c_str(),
|
|
chanParams.str().c_str()
|
|
);
|
|
|
|
/* Add this channel to the actives list */
|
|
activeChannel *newActive = new activeChannel(theChannel->getName(), ::time(0));
|
|
droneChannels[theChannel->getName()] = newActive;
|
|
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
/* This channel is voted normal. */
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/** Calculate the entropy of a given string. */
|
|
double dronescan::calculateEntropy( const string& theString )
|
|
{
|
|
/* If we have not yet calibrated, we cannot calculate an entropy */
|
|
assert(!charMap.empty());
|
|
|
|
double entropy = 0;
|
|
const char* ptr = theString.c_str();
|
|
|
|
for( ; *ptr ; ++ptr )
|
|
entropy += charMap[*ptr];
|
|
|
|
return entropy / theString.length();
|
|
}
|
|
|
|
|
|
/** Check whether an iClient's nick is `normal'. */
|
|
bool dronescan::isNormal( const iClient* theClient )
|
|
{
|
|
/* We should never see this called during BURST */
|
|
assert(currentState != BURST);
|
|
|
|
clientData *theData = static_cast< clientData* > (theClient->getCustomData(this));
|
|
assert( theData != 0 ) ;
|
|
|
|
/* We should never see an unknown state for an initialised client */
|
|
assert(!theData->isUnknown());
|
|
|
|
return theData->isNormal();
|
|
}
|
|
|
|
|
|
/** Set the iClient's state depending on certain features. */
|
|
CLIENT_STATE dronescan::setClientState( iClient *theClient )
|
|
{
|
|
clientData* theData = static_cast< clientData* > (theClient->getCustomData(this));
|
|
assert(theData != 0);
|
|
|
|
double userEntropy = calculateEntropy(theClient->getNickName());
|
|
theData->setEntropy(userEntropy);
|
|
|
|
/* Make sure our services don't skew anything */
|
|
if(theClient->isModeK()) return theData->setState(NORMAL);
|
|
|
|
/*
|
|
* First, check if the first 8 chars of the realname are the same as the
|
|
* first 8 chars of the nickname.
|
|
*/
|
|
if( strncmp( theClient->getNickName().c_str(),
|
|
theClient->getDescription().c_str(),
|
|
8 ) == 0)
|
|
{
|
|
return theData->setState(ABNORMAL);
|
|
}
|
|
|
|
/*
|
|
* Second, check whether the nickname itself is abnormal.
|
|
*/
|
|
if( userEntropy > averageEntropy * (1 + nickMargin) ||
|
|
userEntropy < averageEntropy * (1 - nickMargin) )
|
|
return theData->setState(ABNORMAL);
|
|
|
|
/*
|
|
* It has passed the checks. It is therefore normal.
|
|
*/
|
|
return theData->setState(NORMAL);
|
|
}
|
|
|
|
|
|
/** Log a message. */
|
|
void dronescan::log(LOG_TYPE logType, const char *format, ...)
|
|
{
|
|
if(logType < consoleLevel) return;
|
|
|
|
std::stringstream newMessage;
|
|
|
|
switch(logType) {
|
|
case DBG : newMessage << "[D] "; break;
|
|
case INFO : newMessage << "[I] "; break;
|
|
case WARN : newMessage << "[W] "; break;
|
|
case ERR : newMessage << "[E] "; break;
|
|
default : newMessage << "[U] "; break;
|
|
}
|
|
|
|
char buffer[512] = {0};
|
|
va_list _list;
|
|
|
|
va_start(_list, format);
|
|
vsnprintf(buffer, 512, format, _list);
|
|
va_end(_list);
|
|
|
|
newMessage << buffer;
|
|
|
|
Message(consoleChannel, "%s",newMessage.str().c_str());
|
|
}
|
|
|
|
#ifdef ENABLE_LOG4CPLUS
|
|
void dronescan::log(char* cat,const char* format, ...)
|
|
{
|
|
char buffer[512] = {0};
|
|
va_list _list;
|
|
|
|
va_start(_list, format);
|
|
vsnprintf(buffer, 512, format, _list);
|
|
va_end(_list);
|
|
|
|
log4cplus::Logger::getInstance(cat).log(log4cplus::INFO_LOG_LEVEL,buffer);
|
|
|
|
}
|
|
#endif
|
|
|
|
/** Set the topic of the console channel. */
|
|
void dronescan::setConsoleTopic()
|
|
{
|
|
std::stringstream setTopic;
|
|
setTopic << getCharYYXXX() << " T "
|
|
<< consoleChannel << " :"
|
|
;
|
|
|
|
setTopic << " ||"
|
|
<< " channelMargin: " << channelMargin
|
|
<< " nickMargin: " << nickMargin
|
|
<< " channelCutoff: " << channelCutoff
|
|
;
|
|
|
|
setTopic << " ||"
|
|
<< " jcInterval: " << jcInterval
|
|
<< " jcCutoff: " << jcCutoff
|
|
<< " ncInterval: " << ncInterval
|
|
<< " ncCutoff: " << ncCutoff
|
|
;
|
|
|
|
Write(setTopic);
|
|
}
|
|
|
|
|
|
/** Reply to an iClient. */
|
|
void dronescan::Reply(const iClient *theClient, const char *format, ...)
|
|
{
|
|
char buffer[512] = {0};
|
|
va_list _list;
|
|
|
|
va_start(_list, format);
|
|
vsnprintf(buffer, 512, format, _list);
|
|
va_end(_list);
|
|
|
|
Message(theClient, buffer);
|
|
}
|
|
|
|
|
|
sqlUser *dronescan::getSqlUser(const string& theNick)
|
|
{
|
|
userMapType::const_iterator itr = userMap.find(theNick);
|
|
|
|
if(itr != userMap.end()) {
|
|
return itr->second;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/** Are we due an update? */
|
|
bool dronescan::updateDue(string _table)
|
|
{
|
|
std::stringstream check;
|
|
check << "SELECT max(last_updated) FROM " << _table;
|
|
|
|
if( !SQLDb->Exec(check, true ) )
|
|
// if( PGRES_TUPLES_OK != status )
|
|
{
|
|
doSqlError(check.str(), SQLDb->ErrorMessage());
|
|
return false;
|
|
}
|
|
|
|
time_t maxUpdated = atoi(SQLDb->GetValue(0, 0));
|
|
|
|
if( maxUpdated > lastUpdated[_table] ) return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/** Preload the fake clients cache */
|
|
void dronescan::preloadFakeClientCache()
|
|
{
|
|
/* Are we due to update? */
|
|
if(!updateDue("FAKECLIENTS")) return;
|
|
|
|
std::stringstream theQuery;
|
|
theQuery << sql::fakeclients ;
|
|
|
|
if(!SQLDb->Exec(theQuery, true)) {
|
|
doSqlError(theQuery.str(), SQLDb->ErrorMessage());
|
|
return;
|
|
}
|
|
|
|
for(fcMapType::iterator itr = fakeClients.begin() ;
|
|
itr != fakeClients.end(); ++itr) {
|
|
delete itr->second;
|
|
}
|
|
|
|
fakeClients.clear();
|
|
|
|
/*
|
|
string yyxxx( MyUplink->getCharYY() + "]]]" );
|
|
*/
|
|
|
|
for(unsigned int i = 0; i < SQLDb->Tuples(); ++i) {
|
|
sqlFakeClient *newFake = new sqlFakeClient(SQLDb);
|
|
assert(newFake != 0);
|
|
|
|
newFake->setAllMembers(i);
|
|
fakeClients.insert(fcMapType::value_type(newFake->getId(), newFake));
|
|
|
|
/*
|
|
iClient *fakeClient = new iClient(
|
|
MyUplink->getIntYY(),
|
|
yyxxx,
|
|
newFake->getNickName(),
|
|
newFake->getUserName(),
|
|
"AKAQEK",
|
|
newFake->getHostName(),
|
|
newFake->getHostName(),
|
|
"+i",
|
|
string(),
|
|
0,
|
|
newFake->getRealName(),
|
|
::time(0)
|
|
);
|
|
|
|
assert( fakeClient != 0 );
|
|
|
|
MyUplink->AttachClient( fakeClient );
|
|
*/
|
|
}
|
|
|
|
elog << "dronescan::preloadFakeClientCache> Loaded "
|
|
<< fakeClients.size()
|
|
<< " fake clients."
|
|
<< std::endl;
|
|
log(INFO, "Loaded %u fake clients.", fakeClients.size());
|
|
}
|
|
|
|
|
|
/** Preload the users cache */
|
|
void dronescan::preloadUserCache()
|
|
{
|
|
/* Are we due to update? */
|
|
if(!updateDue("USERS")) return;
|
|
|
|
std::stringstream theQuery;
|
|
theQuery << "SELECT user_name,last_seen,last_updated_by,last_updated,flags,access,created "
|
|
<< "FROM users"
|
|
;
|
|
|
|
if( SQLDb->Exec(theQuery,true ) )
|
|
// if(PGRES_TUPLES_OK == status)
|
|
{
|
|
/* First we need to clear the current cache. */
|
|
for(userMapType::iterator itr = userMap.begin() ;
|
|
itr != userMap.end() ; ++itr) {
|
|
delete itr->second;
|
|
}
|
|
userMap.clear();
|
|
|
|
for(unsigned int i = 0; i < SQLDb->Tuples(); ++i) {
|
|
sqlUser *newUser = new sqlUser(SQLDb);
|
|
assert(newUser != 0);
|
|
|
|
newUser->setAllMembers(i);
|
|
userMap.insert(userMapType::value_type(newUser->getUserName(), newUser));
|
|
}
|
|
} else {
|
|
elog << "dronescan::preloadUserCache> "
|
|
<< SQLDb->ErrorMessage();
|
|
}
|
|
|
|
elog << "dronescan::preloadUserCache> Loaded "
|
|
<< userMap.size()
|
|
<< " users."
|
|
<< std::endl ;
|
|
}
|
|
|
|
bool dronescan::preloadExceptionalChannels()
|
|
{
|
|
std::stringstream theQuery;
|
|
theQuery << "SELECT name FROM exceptionalChannels";
|
|
|
|
//if(PGRES_TUPLES_OK == status) {
|
|
if( SQLDb->Exec(theQuery, true ) )
|
|
{
|
|
/* First we need to clear the current cache. */
|
|
exceptionalChannels.clear();
|
|
for(unsigned int i = 0; i < SQLDb->Tuples(); ++i)
|
|
{
|
|
exceptionalChannels.push_back(SQLDb->GetValue(i,0));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
elog << "dronescan::preloadExceptionalChannels> "
|
|
<< SQLDb->ErrorMessage();
|
|
return false;
|
|
}
|
|
|
|
elog << "dronescan::preloadExceptionalChannels> Loaded "
|
|
<< exceptionalChannels.size()
|
|
<< " exceptional channels."
|
|
<< std::endl ;
|
|
return true;
|
|
}
|
|
|
|
/** Register a new command. */
|
|
bool dronescan::RegisterCommand( Command *theCommand )
|
|
{
|
|
return commandMap.insert( commandPairType(theCommand->getName(), theCommand)).second;
|
|
}
|
|
|
|
|
|
/** Register a new test. */
|
|
bool dronescan::RegisterTest( Test *theTest )
|
|
{
|
|
return testMap.insert( testPairType(theTest->getName(), theTest) ).second;
|
|
}
|
|
|
|
/** Unregister a test. */
|
|
bool dronescan::UnRegisterTest( const string& testName )
|
|
{
|
|
testMapType::iterator ptr = testMap.find( testName );
|
|
if( ptr == testMap.end() ) return false;
|
|
|
|
delete ptr->second;
|
|
testMap.erase(ptr);
|
|
|
|
return true;
|
|
}
|
|
|
|
/** Set a test variable. */
|
|
Test *dronescan::setTestVariable( const string& var, const string& value )
|
|
{
|
|
for( testMapType::iterator testItr = testMap.begin() ;
|
|
testItr != testMap.end() ; ++testItr ) {
|
|
if(testItr->second->setVariable(var, value))
|
|
return testItr->second;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool dronescan::isExceptionalChannel(const string& chanName)
|
|
{
|
|
exceptionalChannelsType::iterator it = exceptionalChannels.begin();
|
|
for(;it != exceptionalChannels.end();++it)
|
|
{
|
|
if( !strcasecmp((*it).c_str(),chanName.c_str()))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
bool dronescan::addExceptionalChannel(const string& chanName)
|
|
{
|
|
std::stringstream insertQ;
|
|
insertQ << "INSERT into exceptionalChannels(name) VALUES('"
|
|
<< chanName << "');" << std::ends;
|
|
|
|
//ExecStatusType status = SQLDb->Exec(insertQ.str().c_str());
|
|
if( !SQLDb->Exec(insertQ) )
|
|
//if (PGRES_COMMAND_OK != status)
|
|
{
|
|
elog << "ERROR while adding exceptionalChannel: " << SQLDb->ErrorMessage() << std::endl;
|
|
return false;
|
|
}
|
|
exceptionalChannels.push_back(chanName);
|
|
return true;
|
|
}
|
|
|
|
bool dronescan::remExceptionalChannel(const string& chanName)
|
|
{
|
|
std::stringstream insertQ;
|
|
insertQ << "DELETE from exceptionalChannels where name='"
|
|
<< chanName << "';" << std::ends;
|
|
|
|
//ExecStatusType status = SQLDb->Exec(insertQ.str().c_str());
|
|
if( !SQLDb->Exec(insertQ) )
|
|
//if (PGRES_COMMAND_OK != status)
|
|
{
|
|
elog << "ERROR while removing exceptionalChannel: " << SQLDb->ErrorMessage() << std::endl;
|
|
return false;
|
|
}
|
|
exceptionalChannelsType::iterator it = exceptionalChannels.begin();
|
|
for(;it != exceptionalChannels.end();++it)
|
|
{
|
|
if( !strcasecmp((*it).c_str(),chanName.c_str()))
|
|
{
|
|
exceptionalChannels.erase(it);
|
|
return true;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/** Return usage information for a client */
|
|
void Command::Usage( const iClient *theClient )
|
|
{
|
|
bot->Reply(theClient, "SYNTAX: %s", getInfo().c_str());
|
|
}
|
|
|
|
} // namespace ds
|
|
|
|
} // namespace gnuworld
|