/**
 * cservice.cc
 * Author: Greg Sikorski
 * Purpose: Overall control client.
 *
 * 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.
 *
 * $Id: cservice.cc,v 1.300 2012/06/03 16:14:46 Seven Exp $
 */

#include	<new>
#include	<vector>
#include	<iostream>
#include	<sstream>
#include	<string>
#include	<iomanip>
#include	<utility>

#include	<ctime>
#include	<cstdlib>
#include	<cstring>
#include	<cstdarg>

#include	"client.h"
#include  	"cservice.h"
#include	"EConfig.h"
#include	"events.h"
#include	"ip.h"
#include	"Network.h"
#include	"StringTokenizer.h"
#include	"misc.h"
#include	"ELog.h"
#include	"dbHandle.h"
#include	"constants.h"
#include	"networkData.h"
#include	"levels.h"
#include	"cservice_config.h"
#include	"match.h"
#include	"md5hash.h"
#include	"responses.h"
#include	"banMatcher.h"

namespace gnuworld
{

using std::pair ;
using std::vector ;
using std::endl ;
using std::stringstream ;
using std::ends ;
using std::string ;
using std::clog ;

/*
 *  Exported function used by moduleLoader to gain an
 *  instance of this module.
 */

extern "C"
{
  xClient* _gnuwinit(const string& args)
  {
    return new cservice( args );
  }
}

bool cservice::RegisterCommand( Command* newComm )
{
UnRegisterCommand( newComm->getName() ) ;
return commandMap.insert( pairType( newComm->getName(), newComm ) ).second ;
}

bool cservice::UnRegisterCommand( const string& commName )
{
commandMapType::iterator ptr = commandMap.find( commName ) ;
if( ptr == commandMap.end() )
        {
        return false ;
        }
delete ptr->second ;
commandMap.erase( ptr ) ;
return true ;
}

void cservice::OnAttach()
{
for( commandMapType::iterator ptr = commandMap.begin() ;
	ptr != commandMap.end() ; ++ptr )
	{
	ptr->second->setServer( MyUplink ) ;
	}

// Start the Db checker timer rolling.
time_t theTime = time(NULL) + connectCheckFreq;
dBconnection_timerID = MyUplink->RegisterTimer(theTime, this, NULL);

// Start the Db update/Reop timer rolling.
theTime = time(NULL) + updateInterval;
update_timerID = MyUplink->RegisterTimer(theTime, this, NULL);

// Start the ban suspend/expire timer rolling.
theTime = time(NULL) + expireInterval;
expire_timerID = MyUplink->RegisterTimer(theTime, this, NULL);

// Start the users DB expire timer rolling.
theTime = time(NULL) + expireDBUserPeriod;
users_expire_timerID = MyUplink->RegisterTimer(theTime, this, NULL);

// Start the channels DB expire timer rolling.
theTime = time(NULL) + expireDBChannelPeriod;
channels_expire_timerID = MyUplink->RegisterTimer(theTime, this, NULL);

// Start the cache expire timer rolling.
theTime = time(NULL) + cacheInterval;
cache_timerID = MyUplink->RegisterTimer(theTime, this, NULL);

// Start the pending chan timer rolling.
theTime = time(NULL) + pendingChanPeriod;
pending_timerID = MyUplink->RegisterTimer(theTime, this, NULL);

// Start the floating Limit timer rolling.
theTime = time(NULL) + limitCheckPeriod;
limit_timerID = MyUplink->RegisterTimer(theTime, this, NULL);

/* Start the web relay timer rolling.
 * First, empty out any old notices that may be present.
 */
#ifdef LOG_SQL
	elog	<< "cservice::OnAttach::sqlQuery> DELETE FROM "
			<< "webnotices WHERE created_ts < "
			<< "date_part('epoch', CURRENT_TIMESTAMP)::int - 600)"
			<< endl;
#endif
if (SQLDb->Exec("DELETE FROM webnotices WHERE created_ts < (date_part('epoch', CURRENT_TIMESTAMP)::int - 600)"))
{
	/* only register the timer if the query is ok.
	 * if the query fails, we most likely don't have
	 * the table setup, so pointless checking it.
	 */
	theTime = time(NULL) + webrelayPeriod;
	webrelay_timerID = MyUplink->RegisterTimer(theTime, this, NULL);
} else {
	/* log the error */
	elog	<< "cservice::OnAttach> Unable to empty webnotices "
			<< "table, not checking webnotices."
			<< endl;
}

if (SQLDb->Exec("SELECT date_part('epoch', CURRENT_TIMESTAMP)::int;",true))
	{
	// Set our "Last Refresh" timers to the current database system time.
	time_t serverTime = atoi(SQLDb->GetValue(0,0).c_str());
	lastChannelRefresh = serverTime;
	lastUserRefresh = serverTime;
	lastLevelRefresh = serverTime;
	lastBanRefresh = serverTime;

	/*
	 * Calculate the current time offset from the DB server.
	 * We always want to talk in DB server time.
	 */

	dbTimeOffset = serverTime - ::time(NULL);
	elog	<< "*** [CMaster::ImplementServer]:  Current DB server time: "
			<< currentTime()
			<< endl;
	}
else
	{
 	elog	<< "Unable to retrieve time from postgres server!"
 			<< endl;
	::exit(0);
	}

/* Register our interest in recieving some Network events from gnuworld. */

MyUplink->RegisterEvent( EVT_KILL, this );
MyUplink->RegisterEvent( EVT_QUIT, this );
MyUplink->RegisterEvent( EVT_NICK, this );
MyUplink->RegisterEvent( EVT_CHNICK, this );
MyUplink->RegisterEvent( EVT_ACCOUNT, this );
MyUplink->RegisterEvent( EVT_BURST_ACK, this );
MyUplink->RegisterEvent( EVT_XQUERY, this );
MyUplink->RegisterEvent( EVT_GLINE , this );
MyUplink->RegisterEvent( EVT_REMGLINE , this );
MyUplink->RegisterEvent( EVT_SERVERMODE , this );

xClient::OnAttach() ;
}

void cservice::OnShutdown(const std::string& reason)
{
	/* handle client shutdown */
	MyUplink->UnloadClient(this, reason);
}

cservice::cservice(const string& args)
 : xClient( args )
{

#ifdef USE_COMMAND_LOG
commandLog.open(commandlogPath.c_str());
#endif
/*
 *  Register command handlers.
 */

RegisterCommand(new SHOWCOMMANDSCommand(this, "SHOWCOMMANDS", "[#channel]", 3));
RegisterCommand(new LOGINCommand(this, "LOGIN", "<username> <password>", 30));
RegisterCommand(new ACCESSCommand(this, "ACCESS", "<channel> <username> [-min n] [-max n] [-op] [-voice] [-none] [-modif]", 5));
RegisterCommand(new CHANINFOCommand(this, "CHANINFO", "<#channel>", 3));
RegisterCommand(new CHANINFOCommand(this, "INFO", "<username>", 3));
RegisterCommand(new ISREGCommand(this, "ISREG", "<#channel>", 4));
RegisterCommand(new VERIFYCommand(this, "VERIFY", "<nick>", 3));
//RegisterCommand(new SEARCHCommand(this, "SEARCH", "[-min <amount>] <keywords>", 5));
RegisterCommand(new MOTDCommand(this, "MOTD", "", 4));
RegisterCommand(new HELPCommand(this, "HELP", "[command]", 4));
RegisterCommand(new RANDOMCommand(this, "RANDOM", "", 4));
RegisterCommand(new SHOWIGNORECommand(this, "SHOWIGNORE", "", 3));
RegisterCommand(new SUPPORTCommand(this, "SUPPORT", "#channel <YES|NO>", 15));
RegisterCommand(new NOTECommand(this, "NOTE", "send <username> <message>, read all, erase <all|message id>", 10));
RegisterCommand(new NOTECommand(this, "NOTES", "send <username> <message>, read all, erase <all|message id>", 10));
RegisterCommand(new OBJECTCommand(this, "OBJECT", "<#channel> <objection>", 8));
RegisterCommand(new CANCELCommand(this, "CANCEL", "<#channel> <YES|NO>", 8));

RegisterCommand(new OPCommand(this, "OP", "<#channel> [nick] [nick] ..", 3));
RegisterCommand(new DEOPCommand(this, "DEOP", "<#channel> [nick] [nick] ..", 3));
#ifdef USING_NEFARIOUS
  #ifdef USE_HALFOPS
    RegisterCommand(new HALFOPCommand(this, "HALFOP", "<#channel> [nick] [nick] ..", 3));
    RegisterCommand(new DEHALFOPCommand(this, "DEHALFOP", "<#channel> [nick] [nick] ..", 3));
  #endif
#endif
RegisterCommand(new VOICECommand(this, "VOICE", "<#channel> [nick] [nick] ..", 3));
RegisterCommand(new DEVOICECommand(this, "DEVOICE", "<#channel> [nick] [nick] ..", 3));
RegisterCommand(new ADDUSERCommand(this, "ADDUSER", "<#channel> <username> <access>", 8));
RegisterCommand(new REMUSERCommand(this, "REMUSER", "<#channel> <username>", 4));
RegisterCommand(new MODINFOCommand(this, "MODINFO", "<#channel> [ACCESS <username> <level>] [AUTOMODE <username> <NONE|OP|VOICE>] [INVITE <ON|OFF>]", 6));
RegisterCommand(new SETCommand(this, "SET", "[#channel] <variable> <value> or, SET <invisible> <ON|OFF> or, SET LANG <language> or, SET MAXLOGINS <max-logins>.", 6));
RegisterCommand(new INVITECommand(this, "INVITE", "<#channel> <#channel> <#channel> ... ", 2));
RegisterCommand(new TOPICCommand(this, "TOPIC", "<#channel> <topic>", 4));
RegisterCommand(new BANLISTCommand(this, "BANLIST", "<#channel>", 3));
RegisterCommand(new KICKCommand(this, "KICK", "<#channel> <nick> [reason]", 4));
RegisterCommand(new STATUSCommand(this, "STATUS", "<#channel>", 4));
RegisterCommand(new SUSPENDCommand(this, "SUSPEND", "<#channel> <username> [duration] [level] [reason]", 5));
RegisterCommand(new UNSUSPENDCommand(this, "UNSUSPEND", "<#channel> <username> [reason]", 5));
RegisterCommand(new BANCommand(this, "BAN", "<#channel> <nick | *!*user@*.host> [duration] [level] [reason]", 5));
RegisterCommand(new UNBANCommand(this, "UNBAN", "<#channel> <*!*user@*.host>", 5));
RegisterCommand(new LBANLISTCommand(this, "LBANLIST", "<#channel> <banmask>", 5));
RegisterCommand(new NEWPASSCommand(this, "NEWPASS", "<new passphrase|username>", 8));
RegisterCommand(new JOINCommand(this, "JOIN", "<#channel>", 8));
RegisterCommand(new PARTCommand(this, "PART", "<#channel>", 8));
RegisterCommand(new OPERJOINCommand(this, "OPERJOIN", "<#channel>", 8));
RegisterCommand(new OPERPARTCommand(this, "OPERPART", "<#channel>", 8));
RegisterCommand(new CLEARMODECommand(this, "CLEARMODE", "<#channel>", 4));
RegisterCommand(new SUSPENDMECommand(this, "SUSPENDME", "<password>", 15));

RegisterCommand(new WHITELISTCommand(this, "WHITELIST", "<ADD|REM|VIEW> <IP> [duration] [reason]", 10));
RegisterCommand(new SCANCommand(this, "SCAN", "NICK|NICKNAME <nick>", 10));
RegisterCommand(new SCANHOSTCommand(this, "SCANHOST", "<mask> [-all]", 10));
RegisterCommand(new SCANUNAMECommand(this, "SCANUNAME", "<mask> [-all]", 10));
RegisterCommand(new SCANEMAILCommand(this, "SCANEMAIL", "<mask> [-all]", 10));
RegisterCommand(new REMIGNORECommand(this, "REMIGNORE", "<mask>", 5));
RegisterCommand(new REGISTERCommand(this, "REGISTER", "<#channel>", 8));
RegisterCommand(new REMOVEALLCommand(this, "REMOVEALL", "<#channel>", 15));
RegisterCommand(new PURGECommand(this, "PURGE", "<username | #channel> [-noop] <reason>", 8));
RegisterCommand(new ACCEPTCommand(this, "ACCEPT", "<#channel> <decision>", 8));
RegisterCommand(new REJECTCommand(this, "REJECT", "<#channel> <decision>", 8));
RegisterCommand(new RENAMECommand(this, "RENAME", "<old_username> <new_username>", 5));
RegisterCommand(new FORCECommand(this, "FORCE", "<#channel>", 8));
RegisterCommand(new UNFORCECommand(this, "UNFORCE", "<#channel>", 8));
RegisterCommand(new SERVNOTICECommand(this, "SERVNOTICE", "<#channel> <text>", 5));
RegisterCommand(new SAYCommand(this, "SAY", "<#channel> <text>", 5));
RegisterCommand(new SAYCommand(this, "DO", "<#channel> <text>", 5));
RegisterCommand(new QUOTECommand(this, "QUOTE", "<text>", 5));
RegisterCommand(new REHASHCommand(this, "REHASH", "[translations | help | config | motd]", 5));
RegisterCommand(new STATSCommand(this, "STATS", "", 8));
RegisterCommand(new ADDCOMMENTCommand(this, "ADDCOMMENT", "<username | #channel> <comment>", 10));
RegisterCommand(new SHUTDOWNCommand(this, "SHUTDOWN", "<reason>", 10));

#ifdef ALLOW_HELLO
  RegisterCommand( new HELLOCommand( this,
	"HELLO", "<username> <email> <email> <1-3> <verification answer>", 10 ) ) ;
#endif // ALLOW_HELLO

cserviceConfig = new (std::nothrow) EConfig( args ) ;
assert( cserviceConfig != 0 ) ;

confSqlHost = cserviceConfig->Require( "sql_host" )->second;
confSqlPass = cserviceConfig->Require( "sql_pass" )->second;
confSqlDb = cserviceConfig->Require( "sql_db" )->second;
confSqlPort = cserviceConfig->Require( "sql_port" )->second;
confSqlUser = cserviceConfig->Require( "sql_user" )->second;

string Query = "host=" + confSqlHost + " dbname=" + confSqlDb + " port=" + confSqlPort + " user=" + confSqlUser
			   + " password=" + confSqlPass;

elog	<< "*** [CMaster]: Attempting to make PostgreSQL connection to: "
		<< confSqlHost
		<< "; Database Name: "
		<< confSqlDb
		<< endl;

//SQLDb = new (std::nothrow) cmDatabase( Query.c_str() ) ;
SQLDb = new (std::nothrow) dbHandle( confSqlHost,
	atoi( confSqlPort.c_str() ),
	confSqlDb,
	confSqlUser,
	confSqlPass ) ;
assert( SQLDb != 0 ) ;

if (SQLDb->ConnectionBad ())
	{
	elog	<< "*** [CMaster]: Unable to connect to SQL server."
			<< endl
			<< "*** [CMaster]: PostgreSQL error message: "
			<< SQLDb->ErrorMessage()
			<< endl ;

	::exit( 0 ) ;
	}
else
	{
	elog	<< "*** [CMaster]: Connection established to SQL server. "
			<< endl ;
	}

// The program will exit if these variables are not defined in the
// configuration file.
relayChan = cserviceConfig->Require( "relay_channel" )->second ;
privrelayChan = cserviceConfig->Require( "priv_relay_channel" )->second ;
debugChan = cserviceConfig->Require( "debug_channel" )->second ;
coderChan = cserviceConfig->Require( "coder_channel" )->second ;
pendingPageURL = cserviceConfig->Require( "pending_page_url" )->second ;
updateInterval = atoi((cserviceConfig->Require( "update_interval" )->second).c_str());
expireInterval = atoi((cserviceConfig->Require( "expire_interval" )->second).c_str());
cacheInterval = atoi((cserviceConfig->Require( "cache_interval" )->second).c_str());
webrelayPeriod = atoi((cserviceConfig->Require( "webrelay_interval" )->second).c_str());
input_flood = atoi((cserviceConfig->Require( "input_flood" )->second).c_str());
output_flood = atoi((cserviceConfig->Require( "output_flood" )->second).c_str());
flood_duration = atoi((cserviceConfig->Require( "flood_duration" )->second).c_str());
topic_duration = atoi((cserviceConfig->Require( "topic_duration" )->second).c_str());
pendingChanPeriod = atoi((cserviceConfig->Require( "pending_duration" )->second).c_str());
connectCheckFreq = atoi((cserviceConfig->Require( "connection_check_frequency" )->second).c_str());
connectRetry = atoi((cserviceConfig->Require( "connection_retry_total" )->second).c_str());
limitCheckPeriod = atoi((cserviceConfig->Require( "limit_check" )->second).c_str());
loginDelay = atoi((cserviceConfig->Require( "login_delay" )->second).c_str());
noteDuration = atoi((cserviceConfig->Require( "note_duration" )->second).c_str());
noteLimit = atoi((cserviceConfig->Require( "note_limit" )->second).c_str());
preloadUserDays = atoi((cserviceConfig->Require( "preload_user_days" )->second).c_str());
partIdleChan = atoi((cserviceConfig->Require( "part_idle_chan" )->second).c_str());
expireDBUserPeriod = atoi((cserviceConfig->Require( "users_db_idle" )->second).c_str());
expireDBChannelPeriod = atoi((cserviceConfig->Require( "channels_db_idle" )->second).c_str());
UsersExpireDBDays = atoi((cserviceConfig->Require( "users_expire_days" )->second).c_str());
startMIA = atoi((cserviceConfig->Require( "MIA_start_days" )->second).c_str());
endMIA = atoi((cserviceConfig->Require( "MIA_end_days" )->second).c_str());
hourSeconds = atoi((cserviceConfig->Require( "hour_seconds" )->second).c_str());
daySeconds = atoi((cserviceConfig->Require( "day_seconds" )->second).c_str());
MIAStartDesc = cserviceConfig->Require( "MIA_start_desc" )->second;
MIAURL = cserviceConfig->Require( "MIA_URL" )->second;
MIAEndDesc = cserviceConfig->Require( "MIA_end_desc" )->second;
MAXnotes = atoi((cserviceConfig->Require( "max_notes" )->second).c_str());
RequiredSupporters = atoi((cserviceConfig->Require( "required_supporters" )->second).c_str());
JudgeDaySeconds = atoi((cserviceConfig->Require( "judge_day_seconds" )->second).c_str());
MinDaysBeforeReg = atoi((cserviceConfig->Require( "min_days_before_reg" )->second).c_str());
MinDaysBeforeSupport = atoi((cserviceConfig->Require( "min_days_before_support" )->second).c_str());
MaxConcurrentSupports = atoi((cserviceConfig->Require( "max_concurrent_supports" )->second).c_str());
NoRegDaysOnNOSupport = atoi((cserviceConfig->Require( "noreg_days_on_nosupport" )->second).c_str());
RejectAppOnUserFraud = atoi((cserviceConfig->Require( "reject_app_on_userfraud" )->second).c_str());
//int AcceptOnTrafficPass = 1;
RewievOnObject = atoi((cserviceConfig->Require( "rewiev_on_object" )->second).c_str());
RewievsExpireTime = atoi((cserviceConfig->Require( "rewievs_expire_time" )->second).c_str());
PendingsExpireTime = atoi((cserviceConfig->Require( "pendings_expire_time" )->second).c_str());
MaxDays = atoi((cserviceConfig->Require( "max_days" )->second).c_str());
Joins = atoi((cserviceConfig->Require( "joins" )->second).c_str());
UniqueJoins = atoi((cserviceConfig->Require( "unique_joins" )->second).c_str());
MinSupporters = atoi((cserviceConfig->Require( "min_supporters" )->second).c_str());
MinSupportersJoin = atoi((cserviceConfig->Require( "min_supporters_joins" )->second).c_str());
NotifyDays = atoi((cserviceConfig->Require( "notify_days" )->second).c_str());
SupportDays = atoi((cserviceConfig->Require( "support_days" )->second).c_str());
ReviewerId = atoi((cserviceConfig->Require( "reviewer_id" )->second).c_str());

welcomeNewChanMessage = cserviceConfig->Require("welcome_newchan_message")->second ;
welcomeNewChanTopic = cserviceConfig->Require("welcome_newchan_topic")->second ;

#ifdef USE_COMMAND_LOG
commandlogPath = cserviceConfig->Require( "command_logfile" )->second ;
#endif
/* adminlogPath = cserviceConfig->Require( "admin_logfile" )->second ; */

#ifdef ALLOW_HELLO
  helloBlockPeriod = atoi(cserviceConfig->Require("hello_block_period")->second.c_str());
  helloSendmailEnabled = atoi((cserviceConfig->Require("hello_sendmail_enabled")->second).c_str()) == 1;
#endif // ALLOW_HELLO

  sendmailFrom = cserviceConfig->Require("sendmail_from")->second;

#ifdef TOTP_AUTH_ENABLED
  totpAuthEnabled = atoi((cserviceConfig->Require( "enable_totp" )->second).c_str()) == 1; 
#endif

reservedHostsList.push_back(hostName);
reservedHostsList.push_back("*" + iClient::getHiddenHostSuffix());

EConfig::const_iterator ptr = cserviceConfig->Find("reservedHost");
while (ptr != cserviceConfig->end() && ptr->first == "reservedHost")
{
	reservedHostsList.push_back(ptr->second);
	ptr++;
}

loadConfigData();

userHits = 0;
userCacheHits = 0;
channelHits = 0;
channelCacheHits = 0;
levelHits = 0;
levelCacheHits = 0;
banHits = 0;
banCacheHits = 0;
dbErrors = 0;
joinCount = 0;
connectRetries = 0;
totalCommands = 0;
if (hourSeconds < 1) hourSeconds = 1;
if (daySeconds < 1) daySeconds = 1;
if (expireDBUserPeriod < 1) expireDBUserPeriod = 8;
if (expireDBChannelPeriod < 1) expireDBChannelPeriod = 3;
if (MAXnotes == 0) MAXnotes = 3;
expireDBUserPeriod *= hourSeconds;
expireDBChannelPeriod *= hourSeconds;
UsersExpireDBDays *= daySeconds;
startMIA *= daySeconds;
endMIA *= daySeconds;

/* Load our translation tables. */
loadTranslationTable();

/* Load help messages */
loadHelpTable();

/* Preload the Channel Cache */
preloadChannelCache();

/* Preload the Ban Cache */
preloadBanCache();

/* Preload the Level cache */
preloadLevelsCache();

/* Preload any user accounts we want to */
preloadUserCache();

/* Load the glines from db */
loadGlines();

/*
 * Init the admin log.
 */

/* adminLog.open( adminlogPath.c_str() ) ;
if( !adminLog.is_open() )
	{
	clog	<< "*** Unable to open CMaster admin log file: "
			<< adminlogPath
			<< endl ;
	::exit( 0 ) ;
	} */

loadIncompleteChanRegs();

}

cservice::~cservice()
{
delete cserviceConfig ;	cserviceConfig = 0 ;
delete SQLDb ; SQLDb = 0 ;

for( commandMapType::iterator ptr = commandMap.begin() ;
	ptr != commandMap.end() ; ++ptr )
	{
	delete ptr->second ;
	}

commandMap.clear() ;

for (incompleteChanRegsType::iterator ptr = incompleteChanRegs.begin();
		ptr != incompleteChanRegs.end(); ++ptr )
{
	delete ptr->second;
}
incompleteChanRegs.clear();
reservedHostsList.clear();
}

void cservice::BurstChannels()
{
	/*
	 *   Need to join every channel with AUTOJOIN set. (But not * ;))
 	 *   Various other things must be done, such as setting the topic if AutoTopic
 	 *   is on.
 	 */

	Channel* tmpChan;
	sqlChannelHashType::iterator ptr = sqlChannelCache.begin();
	while (ptr != sqlChannelCache.end())
	{
	sqlChannel* theChan = (ptr)->second;

	/* we're now interested in registered channels even if we're not in it... */
	if(theChan->getName() == "*")
	{	//Do not do anything for the admins channel
		++ptr;
		continue;
	}
	
	MyUplink->RegisterChannelEvent( theChan->getName(), this ) ;

	tmpChan = Network->findChannel(theChan->getName());
	
	if (tmpChan && tmpChan->getCreationTime() < theChan->getChannelTS())
			theChan->setChannelTS(tmpChan->getCreationTime());	

	if (theChan->getFlag(sqlChannel::F_AUTOJOIN)) 
		{
		string tempModes = theChan->getChannelMode();
		if (tempModes.empty())
			tempModes = "+";
		if (tempModes.find('R') == string::npos)
			tempModes += 'R';
		MyUplink->JoinChannel( this,
			theChan->getName(),
			tempModes,
			theChan->getChannelTS(),
			true );

		theChan->setInChan(true);
		DoTheRightThing(tmpChan);
		joinCount++;

			if (getConfigVar("BAN_CHECK_ON_BURST")->asInt() == 1)
			{
				/* check current inhabitants of the channel against our banlist */
				tmpChan = Network->findChannel(theChan->getName());
				for (Channel::userIterator chanUsers = tmpChan->userList_begin();
					chanUsers != tmpChan->userList_end(); ++chanUsers)
				{
					ChannelUser* tmpUser = chanUsers->second;
					/* check if this user is banned */
					(void)checkBansOnJoin(tmpChan, theChan, tmpUser->getClient());
				}
			}
		} else {
			/* although AUTOJOIN isn't set, set the channel to +R if
			 * it exists on the Network.
			 */
			if (tmpChan)
			{
				stringstream tmpTS;
				tmpTS << theChan->getChannelTS();
				string channelTS = tmpTS.str();
				MyUplink->Mode(NULL, tmpChan, string("+R"), channelTS );
			}
		}
	++ptr;
	}

	logDebugMessage("Channel join complete.");

	return xClient::BurstChannels();
}

void cservice::OnConnect()
{
// TODO: I changed this from return 0
xClient::OnConnect() ;
}

unsigned short cservice::getFloodPoints(iClient* theClient)
{
/*
 * This function gets an iClient's flood points.
 */

networkData* tmpData =
	static_cast< networkData* >( theClient->getCustomData(this) ) ;

if(!tmpData)
	{
	return 0;
	}
//assert(tmpData != NULL);

return tmpData->flood_points;
}

void cservice::setFloodPoints(iClient* theClient, unsigned short amount)
{
networkData* tmpData =
	static_cast< networkData* >( theClient->getCustomData(this) ) ;

if (!tmpData)
	{
	return;
	}
//assert(tmpData != NULL);

tmpData->flood_points = amount;
}

/**
 * This method sets a timestamp for when we last recieved
 * a message from this iClient.
 */
void cservice::setLastRecieved(iClient* theClient, time_t last_recieved)
{
networkData* tmpData =
	static_cast< networkData* >( theClient->getCustomData(this) ) ;

if(!tmpData)
	{
	return;
	}
//assert(tmpData != NULL);

tmpData->messageTime = last_recieved;
}

/**
 * This method gets a timestamp from this iClient
 * for flood control.
 */
time_t cservice::getLastRecieved(iClient* theClient)
{
networkData* tmpData =
	static_cast< networkData* >( theClient->getCustomData(this) ) ;

if(!tmpData)
	{
	return 0;
	}
//assert(tmpData != NULL);

return tmpData->messageTime;
}

bool cservice::isIgnored(iClient* theClient)
{

networkData* tmpData =
	static_cast< networkData* >( theClient->getCustomData(this) ) ;

if(!tmpData)
	{
	return 0;
	}

return tmpData->ignored;
}

void cservice::setIgnored(iClient* theClient, bool _ignored)
{

networkData* tmpData =
	static_cast< networkData* >( theClient->getCustomData(this) ) ;

if(!tmpData)
	{
	return;
	}

tmpData->ignored = _ignored;
}

bool cservice::hasFlooded(iClient* theClient, const string& type)
{
if( (getLastRecieved(theClient) + flood_duration) <= ::time(NULL) )
	{
	/*
	 *  Reset a few things, they're out of the flood period now.
	 *  Or, this is the first message from them.
	 */

	setFloodPoints(theClient, 0);
	setOutputTotal(theClient, 0);
	setLastRecieved(theClient, ::time(NULL));
	ipFloodMap[theClient->getIP()]=0;
	}
else
	{
	/*
	 *  Inside the flood period, check their points..
	 */

	if(getFloodPoints(theClient) > input_flood)
		{
		/*
		 *  Check admin access, if present then
		 *  don't trigger.
		 */

		sqlUser* theUser = isAuthed(theClient, false);
		if (theUser && getAdminAccessLevel(theUser))
			{
			return false;
			}

		// Bad boy!
		setFloodPoints(theClient, 0);
		setLastRecieved(theClient, ::time(NULL));
		Notice(theClient,
			"Flood me will you? I'm not going to listen to "
			"you anymore.");

		// Send a silence numeric target, and mask to ignore
		// messages from this user.
		string silenceMask = string( "*!*" )
			+ theClient->getUserName()
			+ "@"
			+ theClient->getInsecureHost();

		stringstream s;
		s	<< getCharYYXXX()
			<< " SILENCE "
			<< theClient->getCharYYXXX()
			<< " "
			<< silenceMask
			<< ends;
		Write( s );

		time_t expireTime = currentTime() + 3600;
		silenceList.insert(silenceListType::value_type(silenceMask,
			std::make_pair(expireTime, theClient->getCharYYXXX())));

		setIgnored(theClient, true);

		string floodComment;
		StringTokenizer st(type) ;

		if( st.size() >= 2 )
		{
			floodComment = st[0] + ' ' + st[1];
		} else {
			floodComment = st[0];
		}

		if (getConfigVar("FLOOD_MESSAGES")->asInt()==1)
		{
			logAdminMessage("MSG-FLOOD from %s (%s)",
				theClient->getNickUserHost().c_str(),
				floodComment.c_str());
		}
		return true;
		} // if()

		if (ipFloodMap[theClient->getIP()]>input_flood*5)
		{
			setLastRecieved(theClient, ::time(NULL));

			string silenceMask = string( "*!*" )
				+ theClient->getUserName()
				+ "@"
				+ theClient->getInsecureHost();

			stringstream s;
			s	<< getCharYYXXX()
				<< " SILENCE "
				<< theClient->getCharYYXXX()
				<< " "
				<< silenceMask
				<< ends;
			Write( s );

			Notice(theClient,
				"Flood me will you? I'm not going to listen to "
				"you or your friends anymore.");

			time_t expireTime = currentTime() + 3600;
			silenceList.insert(silenceListType::value_type(silenceMask,
				std::make_pair(expireTime, theClient->getCharYYXXX())));

			setIgnored(theClient, true);

			string floodComment;
			StringTokenizer st(type);

			if (st.size() >= 2)
			{
				floodComment = st[0] + ' ' + st[1];
			} else {
				floodComment = st[0];
			}

			if (getConfigVar("FLOOD_MESSAGES")->asInt()==1)
			{
				logAdminMessage("IP-FLOOD from %s (%s)",
					theClient->getNickUserHost().c_str(),
					floodComment.c_str());
			}
			return true;
		}
	} // else()

return false;
}

string cservice::NickIsRegisteredTo(const string& Nick)
{
	stringstream theQuery;
	theQuery	<< "SELECT user_name FROM users WHERE lower(nickname) = '"
				<< escapeSQLChars(string_lower(Nick))
				<< "'"
				<< ends;
	if (!SQLDb->Exec(theQuery, true))
	{
		logDebugMessage("Error on cservice::NickIsRegisteredTo::nicknameQuery");
	#ifdef LOG_SQL
		//elog << "sqlQuery> " << theQuery.str().c_str() << endl;
		elog << "cservice::NickIsRegisteredTo::nicknameQuery> SQL Error: "
			<< SQLDb->ErrorMessage()
			<< endl ;
	#endif
		return string();
	}
	else if (SQLDb->Tuples() > 0)
		return (string)SQLDb->GetValue(0,0);
	return string();
}

void cservice::generateNickName(iClient* theClient)
{
	string genNick = theClient->getNickName();
	string baseNick = genNick;
	if (baseNick.length() > 25)
		baseNick = baseNick.substr(20);
	do
	{
		srand(time(0));
		int rNum = rand() % 9000 + 1000;
		genNick = baseNick + itoa(rNum);
	} while (Network->findNick(genNick));
	stringstream s;
	s	<< getCharYY()
		<< " SN "
		<< theClient->getCharYYXXX()
		<< " "
		<< genNick
		<< ends
		<< endl;
	Write( s );
	//Write("%s SN %s %s", getCharYY().c_str(), theClient->getCharYYXXX().c_str(), genNick.c_str());
	return ;
}

void cservice::validateNickName(iClient* theClient)
{
	if (theClient->getMode(iClient::MODE_SERVICES))
		return;
	bool isRegNick = false;
	string ownerUser = NickIsRegisteredTo(theClient->getNickName());
	//elog << "cservice::validateNickName " << theClient->getNickName() <<"'s owner user is '" << ownerUser << "'" << endl;
	if (!ownerUser.empty())
		isRegNick = true;

	sqlUser* theUser = isAuthed(theClient, false);
	if (isRegNick)
	{
		if (!theUser)
		{
			generateNickName(theClient);
			Notice(theClient, "You don't own that nick, your nick has been changed by force.");
			return;
		}
		else if ((string_lower(theUser->getNickName()) != string_lower(theClient->getNickName())))
		{
			generateNickName(theClient);
			Notice(theClient, "You don't own that nick, your nick has been changed by force.");
		}
	}
	return;
}

void cservice::setOutputTotal(const iClient* theClient, unsigned int count)
{
/*
 * This function sets the current output total in bytes
 * for this client.
 */

networkData* tmpData =
	static_cast< networkData* >( theClient->getCustomData(this) );

if (!tmpData)
	{
	return;
	}
//assert(tmpData != NULL);

tmpData->outputCount = count;
}

unsigned int cservice::getOutputTotal(const iClient* theClient)
{
networkData* tmpData =
	static_cast< networkData* >( theClient->getCustomData(this) );

if (!tmpData)
	{
	return 0;
	}
//assert(tmpData != NULL);

return tmpData->outputCount;
}

bool cservice::hasOutputFlooded(iClient* theClient)
{

if( (getLastRecieved(theClient) + flood_duration) <= ::time(NULL) )
	{
	/*
	 *  Reset a few things, they're out of the flood period now.
	 *  Or, this is the first message from them.
	 */

	setOutputTotal(theClient, 0);
	setFloodPoints(theClient, 0);
	setLastRecieved(theClient, ::time(NULL));
	}
else
	{
	/*
	 *  Inside the flood period, check their output count.
	 */

	if(getOutputTotal(theClient) > output_flood)
		{
		/*
		 *  Check admin access, if present then
		 *  don't trigger.
		 */

		sqlUser* theUser = isAuthed(theClient, false);
		if (theUser && getAdminAccessLevel(theUser))
			{
			return false;
 			}

		setOutputTotal(theClient, 0);
		setLastRecieved(theClient, ::time(NULL));
		Notice(theClient, "I think I've sent you a little "
			"too much data, I'm going to ignore you "
			"for a while.");

		// Send a silence numeric target, and mask to ignore
		// messages from this user.
		string silenceMask = string( "*!*" )
			+ theClient->getUserName()
			+ "@"
			+ theClient->getInsecureHost();

		stringstream s;
		s	<< getCharYYXXX()
			<< " SILENCE "
			<< theClient->getCharYYXXX()
			<< " "
			<< silenceMask
			<< ends;
		Write( s );

		time_t expireTime = currentTime() + 3600;

		silenceList.insert(silenceListType::value_type(silenceMask,
			std::make_pair(expireTime, theClient->getCharYYXXX())));

		setIgnored(theClient, true);

		if (getConfigVar("FLOOD_MESSAGES")->asInt()==1)
		{
			logAdminMessage("OUTPUT-FLOOD from %s",
				theClient->getNickUserHost().c_str());
		}
		return true;
		}
	}

return false;
}

void cservice::OnPrivateMessage( iClient* theClient,
	const string& Message,
	bool secure )
{
/*
 * Private message handler. Pass off the command to the relevant
 * handler.
 */

if (isIgnored(theClient)) return ;

StringTokenizer st( Message ) ;
if( st.empty() )
	{
	Notice( theClient, "Incomplete command");
	return ;
	}

#ifdef USE_COMMAND_LOG
commandLog << (secure ? "[" : "<") << theClient->getNickUserHost() << (secure ? "] " : "> ")  << Message << endl;
#endif

/*
 * Do flood checking - admins at 750 or above are excempt.
 * N.B: Only check that *after* someone has flooded ;)
 */
const string Command = string_upper( st[ 0 ] ) ;

/*
 *  Just quickly, abort if someone tries to LOGIN or NEWPASS
 *  unsecurely.
 */

if (!secure && ((Command == "LOGIN") || (Command == "NEWPASS") || (Command == "SUSPENDME")) )
	{
	Notice(theClient, "To use %s, you must /msg %s@%s",
		Command.c_str(),
		nickName.c_str(),
		getUplinkName().c_str());
	return ;
	}

/*
 * If the person issuing this command is an authenticated admin, we need to log
 * it to the admin log for accountability purposes.
 *
 * Moved this logging further down the function (to SQL) so that only recognised
 * commands are logged.  This stops logging of passwords etc
 *
		sqlUser* theUser = isAuthed(theClient, false);
		if (theUser && getAdminAccessLevel(theUser))
			{
			adminLog << ::time(NULL) << " " << theClient->getRealNickUserHost() << " " << st.assemble() << endl;
 			}
 */

/* Attempt to find a handler for this method. */

commandMapType::iterator commHandler = commandMap.find( Command ) ;
if( commHandler == commandMap.end() )
	{
	/* Don't reply to unknown commands, but add to their flood
	 * total :)
	 */
	if (hasFlooded(theClient, "PRIVMSG"))
		{
		return ;
		}

	if (hasOutputFlooded(theClient))
		{
		return ;
		}

	// Why use 3 here?  Should be in config file
	// (Violation of "rule of numbers")
	setFloodPoints(theClient, getFloodPoints(theClient) + 3);
	ipFloodMap[theClient->getIP()] += 3;
	}
else
	{
	/*
	 *  Check users flood limit, if exceeded..
	 */

	if (hasFlooded(theClient, Message))
		{
		return ;
		}

	if (hasOutputFlooded(theClient))
		{
		return ;
		}

	setFloodPoints(theClient, getFloodPoints(theClient)
		+ commHandler->second->getFloodPoints() );
	ipFloodMap[theClient->getIP()] += commHandler->second->getFloodPoints();

	totalCommands++;

	sqlUser* theUser = isAuthed(theClient, false);

	/* Check IP restriction (if admin level) - this is in case you get added as admin AFTER logging in */
	if (theUser && needIPRcheck(theUser) && (Command != "LOGIN"))
	{
		/* ok, they have a valid user and are listed as admin (and this is not a login request) */
		if (!passedIPR(theClient))
		{
			/* not passed IPR yet, do it */
			if (!checkIPR(theClient, theUser))
			{
				/* failed IPR! */
				Notice(theClient, "Insufficient access for this command. (IPR)");
				return;
			}
		}
	}
	/* Log command to SQL here, if Admin */
	if (theUser && getAdminAccessLevel(theUser) && (Command != "LOGIN") && (Command != "NEWPASS") && (Command != "SUSPENDME"))
	{
		stringstream theLog;
		theLog  << "INSERT INTO adminlog (user_id,cmd,args,timestamp,issue_by)"
			<< " VALUES("
			<< theUser ->getID()
			<< ","
			<< "'" << escapeSQLChars(Command) << "'"
			<< ","
			<< "'" << escapeSQLChars(st.assemble(1)) << "'"
			<< ","
			<< currentTime()
			<< ","
			<< "'" << escapeSQLChars(theClient->getRealNickUserHost()) << "'"
			<< ")"
			<< ends;

		if( !SQLDb->Exec(theLog ) )
//		if( PGRES_COMMAND_OK != status )
		{
			elog    << "cservice::adminlog> Something went wrong: "
				<< theLog.str().c_str()
				<< " "
				<< SQLDb->ErrorMessage()
				<< endl;
		}
	}

	commHandler->second->Exec( theClient, Message ) ;
	}

xClient::OnPrivateMessage( theClient, Message ) ;
}

void cservice::OnCTCP( iClient* theClient, const string& CTCP,
                    const string& Message, bool )
{
/**
 * CTCP hander. Deal with PING, GENDER and VERSION.
 * Hit users with a '5' flood score for CTCP's.
 * This should be in the config file.
 */

incStat("CORE.CTCP");

if (isIgnored(theClient)) return ;

if (hasFlooded(theClient, "CTCP"))
	{
	return ;
	}

setFloodPoints(theClient, getFloodPoints(theClient) + 5 );
ipFloodMap[theClient->getIP()]+=5;

StringTokenizer st( CTCP ) ;
if( st.empty() )
	{
	return ;
	}

const string Command = string_upper(st[0]);

if(Command == "PING" || Command=="ECHO")
	{
	xClient::DoCTCP(theClient, CTCP, Message);
	}
else if(Command == "GENDER")
	{
	xClient::DoCTCP(theClient, CTCP,
		"Tried to be a man again - there was a slip - now I'm an IT");
	}
else if(Command == "VERSION")
	{
	xClient::DoCTCP(theClient, CTCP,
		"UnderChat Channel Services III ["
		__DATE__ " " __TIME__
		"] Release 2.1.6");
	}
else if(Command == "PROBLEM?")
	{
	xClient::DoCTCP(theClient, CTCP.c_str(), "Blame Bleep!");
	}
else if(Command == "PUSHER")
	{
	xClient::DoCTCP(theClient, CTCP.c_str(), "Pak, Chooie, Unf.");
	}
else if(Command == "SOUND")
	{
	xClient::DoCTCP(theClient, CTCP.c_str(), "I'm deaf, remember?");
	}
else if(Command == "DCC")
	{
	xClient::DoCTCP(theClient, CTCP.c_str(), "REJECT");
	}
else if(Command == "FLOOD")
	/* Ignored */;
else if(Command == "PAGE")
	{
	xClient::DoCTCP(theClient, CTCP.c_str(), "I'm always here, no need to page");
	}
else if(Command == "USERINFO")
	{
	xClient::DoCTCP(theClient, CTCP.c_str(), "I'm a user, not an abuser");
	}
else if(Command == "TIME")
	{
	xClient::DoCTCP(theClient, CTCP.c_str(), "Time you got a watch?");
	}
else
	{
	xClient::DoCTCP(theClient, "ERRMSG", CTCP.c_str());
	}
}

/**
 * Check to see if this user is 'forced' onto this channel.
 */
unsigned short cservice::isForced(sqlChannel* theChan, sqlUser* theUser)
{
if (!theChan->forceMap.empty())
	{
	sqlChannel::forceMapType::iterator ptr = theChan->forceMap.find(theUser->getID());
	if(ptr != theChan->forceMap.end())
		{
		/* So they do, return their forced level. */
		return ptr->second.first;
		}
	}

return 0;
}

/**
 *  Confirms a user is logged in by returning a pointer to
 *  the sqlUser record.
 *  If 'alert' is true, send a notice to the user informing
 *  them that they must be logged in.
 */
sqlUser* cservice::isAuthed(iClient* theClient, bool alert)
{
networkData* tmpData =
	static_cast< networkData* >( theClient->getCustomData(this) ) ;

if(!tmpData)
	{
	return 0;
	}

//assert( tmpData != 0 ) ;

sqlUser* theUser = tmpData->currentUser;

if( theUser )
	{
	return theUser;
	}

if( alert )
	{
	Notice(theClient,
		"Sorry, You must be logged in to use this command.");
	}
return 0;
}

/**
 *  Locates a cservice user record by 'id', the username of this user.
 */
sqlUser* cservice::getUserRecord(const string& id)
{
/*
 *  Check if this is a lookup by nick
 */
if (id[0]=='=')
	{
	const char* theNick = id.c_str();
	// Skip the '='
	++theNick;

	iClient *client = Network->findNick(theNick);
	if (client) return isAuthed(client,false);

	return NULL;
	}
/*
 *  Check if this record is already in the cache.
 */

sqlUserHashType::iterator ptr = sqlUserCache.find(id);
if(ptr != sqlUserCache.end())
	{
	// Found something!
	#ifdef LOG_CACHE_HITS
		elog	<< "cmaster::getUserRecord> Cache hit for "
			<< id
			<< endl;
	#endif

	ptr->second->setLastUsed(currentTime());
	userCacheHits++;
	return ptr->second ;
	}

/*
 *  We didn't find anything in the cache, fetch the data from
 *  the backend and create a new sqlUser object.
 */

sqlUser* theUser = new (std::nothrow) sqlUser(SQLDb);
assert( theUser != 0 ) ;

if (theUser->loadData(id))
	{
 	sqlUserCache.insert(sqlUserHashType::value_type(id, theUser));

	#ifdef LOG_SQL
		elog	<< "cmaster::getUserRecord> There are "
			<< sqlUserCache.size()
			<< " elements in the cache."
		<< endl;
	#endif

	userHits++;

	// Return the new user to the caller
	theUser->setLastUsed(currentTime());
	return theUser;
	}
else
	{
	delete theUser ;
	}

return NULL;
}

/**
 *  Locates a cservice user record by 'id', the userId number of this user. (Seven)
 */
sqlUser* cservice::getUserRecord(int Id)
{
	stringstream theQuery;
	theQuery	<< "SELECT user_name FROM users WHERE id = "
				<< Id
			<< ends;
	if (!SQLDb->Exec(theQuery, true))
	{	logDebugMessage("Error on getUserRecordUserIdQuery");
	#ifdef LOG_SQL
		//elog << "sqlQuery> " << theQuery.str().c_str() << endl;
		elog << "getUserRecordUserIdQuery> SQL Error: "
		     << SQLDb->ErrorMessage()
		     << endl ;
	#endif
		return NULL;
	} else if (SQLDb->Tuples() == 0)
	{
		logDebugMessage("getUserRecordUserIdQuery = 0 !");
		return NULL;
	}
	string id = SQLDb->GetValue(0,0);
/*
 *  Check if this record is already in the cache.
 */

sqlUserHashType::iterator ptr = sqlUserCache.find(id);
if(ptr != sqlUserCache.end())
	{
	// Found something!
	#ifdef LOG_CACHE_HITS
		elog	<< "cmaster::getUserRecord> Cache hit for "
			<< id
			<< endl;
	#endif

	ptr->second->setLastUsed(currentTime());
	userCacheHits++;
	return ptr->second ;
	}

/*
 *  We didn't find anything in the cache, fetch the data from
 *  the backend and create a new sqlUser object.
 */

sqlUser* theUser = new (std::nothrow) sqlUser(SQLDb);
assert( theUser != 0 ) ;

if (theUser->loadData(id))
	{
 	sqlUserCache.insert(sqlUserHashType::value_type(id, theUser));

	#ifdef LOG_SQL
		elog	<< "cmaster::getUserRecord> There are "
			<< sqlUserCache.size()
			<< " elements in the cache."
		<< endl;
	#endif

	userHits++;

	// Return the new user to the caller
	theUser->setLastUsed(currentTime());
	return theUser;
	}
else
	{
	delete theUser ;
	}

return NULL;
}

void cservice::updateUserCacheUserName(sqlUser* updateUser, const string& newUserName)
{
sqlUserHashType::iterator ptr = sqlUserCache.find(updateUser->getUserName());
if(ptr != sqlUserCache.end())
{
	// Found something!
	#ifdef LOG_CACHE_HITS
		elog	<< "cmaster::getUserRecord> Cache hit for "
			<< updateUser->getUserName()
			<< endl;
	#endif

	ptr->second->setLastUsed(currentTime());
	userCacheHits++;
	//(*ptr)->first = string(newUserName);
	//(*ptr).swap(sqlUserHashType::value_type(newUserName,updateUser));	
	//sqlUserCache[ptr->first].swap(sqlUserHashType::value_type(newUserName,updateUser));
	updateUser->setUserName(newUserName);
	sqlUserCache.erase(ptr);
	sqlUserCache.insert(sqlUserHashType::value_type(newUserName, updateUser));	
	return;
}

sqlUser* theUser = new (std::nothrow) sqlUser(SQLDb);
assert( theUser != 0 ) ;

if (theUser->loadData(updateUser->getID()))
{
	sqlUserCache.insert(sqlUserHashType::value_type(updateUser->getUserName(), theUser));
	#ifdef LOG_SQL
		elog	<< "cmaster::getUserRecord> There are "
			<< sqlUserCache.size()
			<< " elements in the cache."
		<< endl;
	#endif
	userHits++;

	// Return the new user to the caller
	theUser->setLastUsed(currentTime());
	theUser->setUserName(newUserName);
	return;
}
	logDebugMessage("Failed to update userCache. User %s (%i)", updateUser->getUserName().c_str(), updateUser->getID());
	return;
}

/**
 *  Locates a channel record by 'id', the channel name.
 */
sqlChannel* cservice::getChannelRecord(const string& id)
{

/*
 *  Check if this record is already in the cache.
 */

sqlChannelHashType::iterator ptr = sqlChannelCache.find(id);
if(ptr != sqlChannelCache.end())
	{
	channelCacheHits++;
	ptr->second->setLastUsed(currentTime());

	// Return the channel to the caller
	return ptr->second ;
	}

/*
 *  We didn't find anything in the cache.
 */

return 0;
}

/**
 *  Loads a channel from the cache by 'id'.
 */
sqlChannel* cservice::getChannelRecord(int id)
{

/*
 *  Check if this record is already in the cache.
 */

sqlChannelIDHashType::iterator ptr = sqlChannelIDCache.find(id);
if(ptr != sqlChannelIDCache.end())
	{
	channelCacheHits++;
	ptr->second->setLastUsed(currentTime());

	// Return the channel to the caller
	return ptr->second ;
	}

/*
 *  We didn't find anything in the cache.
 */

return 0;
}


sqlLevel* cservice::getLevelRecord( sqlUser* theUser, sqlChannel* theChan )
{
// Check if the record is already in the cache.
pair<int, int> thePair( theUser->getID(), theChan->getID() );

sqlLevelHashType::iterator ptr = sqlLevelCache.find(thePair);
if(ptr != sqlLevelCache.end())
	{
	// Found something!
	#ifdef LOG_CACHE_HITS
		elog	<< "cmaster::getLevelRecord> Cache hit for "
			<< "user-id:chan-id "
			<< theUser->getID() << ":"
			<< theChan->getID()
			<< endl;
	#endif

	levelCacheHits++;
	ptr->second->setLastUsed(currentTime());
	return ptr->second ;
	}

/*
 *  We didn't find anything in the cache.
 */

return 0;
}

/**
 * Check if a user has already passed IPR checks
 */
bool cservice::passedIPR(iClient* theClient)
{
	networkData* tmpData = static_cast< networkData* >(theClient->getCustomData(this));

	/* if the user has no network data, the haven't passed IPR */
	if (!tmpData)
		return false;

	if (tmpData->ipr_ts > 0)
		return true;

	return false;
}

/**
 * Set the client's IPR timestamp (can also be used to clear it)
 */

void cservice::setIPRts(iClient* theClient, unsigned int _ipr_ts)
{
	networkData* tmpData = static_cast< networkData* >(theClient->getCustomData(this));

	if (!tmpData)
		return;

	/* set the timestamp */
	tmpData->ipr_ts = _ipr_ts;

	return;
}

/**
 *  Check a user against IP restrictions
 */
bool cservice::checkIPR( iClient* theClient, sqlUser* theUser )
{
	stringstream theQuery;
	theQuery	<< "SELECT allowmask,allowrange1,allowrange2,added FROM "
			<< "ip_restrict WHERE user_id = "
			<< theUser->getID()
			<< ends;
#ifdef LOG_SQL
	elog	<< "cservice::checkIPR::sqlQuery> "
		<< theQuery.str().c_str()
		<< endl;
#endif

	if( !SQLDb->Exec(theQuery, true ) )
//	if (PGRES_TUPLES_OK != status)
	{
		/* SQL error, fail them */
		elog    << "cservice::checkIPR> SQL Error: "
			<< SQLDb->ErrorMessage()
			<< endl;
		return false;
        }
	if (SQLDb->Tuples() < 1)
	{
#ifdef IPR_DEFAULT_REJECT
		/* no entries, fail them */
		return false;
#else
		/* no entries, allow them */
		return true;
#endif
	}
	/* cycle through results to find a match */
	bool ipr_match = false;
	unsigned int ipr_ts = 0;
	unsigned int tmpIP = xIP(theClient->getIP()).GetLongIP();
	for (unsigned int i=0; i < SQLDb->Tuples(); i++)
	{
		/* get some variables out of the db row */
		std::string ipr_allowmask = SQLDb->GetValue(i, 0);
		unsigned int ipr_allowrange1 = atoi(SQLDb->GetValue(i, 1).c_str());
		unsigned int ipr_allowrange2 = atoi(SQLDb->GetValue(i, 2).c_str());
		ipr_ts = atoi(SQLDb->GetValue(i, 3).c_str());

		/* is this an IP range? */
		if (ipr_allowrange2 > 0)
		{
			/* yes it is, is the client IP between range1 and range2? */
			if ((tmpIP >= ipr_allowrange1) && (tmpIP <= ipr_allowrange2))
			{
				ipr_match = true;
				break;
			}
		} else {
			/* no, is it a single IP? */
			if (ipr_allowrange1 > 0)
			{
				/* yes it is, does the IP match range1? */
				if (tmpIP == ipr_allowrange1)
				{
					ipr_match = true;
					break;
				}
			} else {
				/* no, is it a hostmask? */
				if (ipr_allowmask.size() > 0)
				{
					/* yes it is, does it match our hostname? */
					if (!match(ipr_allowmask, theClient->getRealInsecureHost()) ||
						!match(ipr_allowmask, xIP(theClient->getIP()).GetNumericIP()))
					{
						ipr_match = true;
						break;
					}
				} else {
					/* no, fail */
				}
			}
		}
	}
	/* check if we found a match yet */
	if (!ipr_match)
	{
		/* no match, fail them */
		return false;
	} else {
		/* IP restriction check passed - mark it against this user */
		setIPRts(theClient, ipr_ts);
		return true;
	}
}

/**
 * Fetch the number of failed logins for a client
 */
unsigned int cservice::getFailedLogins(iClient* theClient)
{
	networkData* tmpData = static_cast< networkData* >(theClient->getCustomData(this));

	/* if the user has no network data, they have zero failed logins */
	if (!tmpData)
		return 0;

	return tmpData->failed_logins;
}

/**
 * Set the number of failed logins for a client
 */
void cservice::setFailedLogins(iClient* theClient, unsigned int _failed_logins)
{
	networkData* tmpData = static_cast< networkData* >(theClient->getCustomData(this));

	/* if the user has no network data, we cant set it */
	if (!tmpData)
		return;

	/* set the failed login counter */
	tmpData->failed_logins = _failed_logins;

	return;
}

/**
 *  Returns true or false to whether IPR checks are required.
 */
bool cservice::needIPRcheck(sqlUser* theUser)
{
	/* don't need to check in the case of alumni */
	if (theUser->getFlag(sqlUser::F_ALUMNI))
		return 0;
	/* check if they have access to '*' */
	sqlChannel* theChan = getChannelRecord("*");
	if (!theChan)
	{
		elog	<< "cservice::needIPRcheck> Unable to "
			<< "locate channel '*'!"
			<< endl ;
		::exit(0);
	}
	sqlLevel* theLevel = getLevelRecord(theUser, theChan);
	if (theLevel)
	{
		if (theLevel->getAccess() > 0)
			return true;
	}
	/* if we reach here, no IPR checks are needed */
	return false;
}

/**
 *  Returns the admin access level a particular user has.
 */
short int cservice::getAdminAccessLevel( sqlUser* theUser, bool verify )
{

/*
 *  First thing, check if this ACCOUNT has been globally
 *  suspended.
 */
if (theUser->getFlag(sqlUser::F_GLOBAL_SUSPEND))
	{
	return 0;
	}

if (theUser->getFlag(sqlUser::F_NOADMIN))
	{
	return 0;
	}

/* Are they an alumni (check except for 'verify' command) ? */
if (!verify && (theUser->getFlag(sqlUser::F_ALUMNI)))
	return 0;

sqlChannel* theChan = getChannelRecord("*");
if (!theChan)
	{
	elog	<< "cservice::getAdminAccessLevel> Unable to "
		<< "locate channel '*'! Sorry, I can't continue.."
		<< endl;
	::exit(0);
	}

sqlLevel* theLevel = getLevelRecord(theUser, theChan);
if(theLevel)
	{
	/* check if they have been suspended! */
	if (theLevel->getSuspendExpire() > currentTime())
		return 0;
	return theLevel->getAccess();
	}

// By default, users have level 0 admin access.
return 0;
}

/**
 *  Returns the coder access level a particular user has.
 */
short int cservice::getCoderAccessLevel( sqlUser* theUser )
{
if (theUser->getFlag(sqlUser::F_GLOBAL_SUSPEND))
	{
	return 0;
	}

sqlChannel* theChan = getChannelRecord(coderChan);
if (!theChan)
	{
	elog	<< "cservice::getAdminAccessLevel> Unable to "
		<< "locate channel '"
		<< coderChan.c_str()
		<< "'! Sorry, I can't continue.."
		<< endl;
	::exit(0);
	}

sqlLevel* theLevel = getLevelRecord(theUser, theChan);
if(theLevel)
	{
	return theLevel->getAccess();
	}

// By default, users have level 0 admin access.
return 0;
}

/**
 * Returns the access level a particular user has on a particular
 * channel taking into account channel & user level suspensions.
 * Also used to return the level of access granted to a forced access.
 *
 * Usage: When determining if we should grant a permission to a user
 * to access a particular command/function.
 * To determine the effect access level of a target user.
 */
short int cservice::getEffectiveAccessLevel( sqlUser* theUser,
	sqlChannel* theChan, bool notify )
{

/*
 *  First thing, check if this ACCOUNT has been globally
 *  suspended.
 */

if (theUser->getFlag(sqlUser::F_GLOBAL_SUSPEND))
	{
	if (theUser->isAuthed() && notify)
		{
		noticeAllAuthedClients(theUser, "Your account has been suspended.");
		}
	return 0;
	}

/*
 *  Have a look to see if this user has forced some access.
 */

unsigned short forcedAccess = isForced(theChan, theUser);
if (forcedAccess)
	{
	return forcedAccess;
	}

sqlLevel* theLevel = getLevelRecord(theUser, theChan);
if( !theLevel )
	{
	return 0 ;
	}

/* Then, check to see if the channel has been suspended. */

if (theChan->getFlag(sqlChannel::F_SUSPEND))
	{
	/* Send them a notice to let them know they've been bad? */
	if (theUser->isAuthed() && notify)
		{
		noticeAllAuthedClients(theUser, "The channel %s has been suspended by a cservice administrator.",
			theChan->getName().c_str());
		}
	return 0;
	}

/*
 *  Check to see if this particular access record has been
 *  suspended too.
 */

if (theLevel->getSuspendExpire() > currentTime())
	{
	// Send them a notice.
	if (theUser->isAuthed() && notify)
		{
		//TODO Do this with getresponse
		noticeAllAuthedClients(theUser, getResponse(theUser,
			language::acc_susp).c_str(),theChan->getName().c_str());
		}
	return 0;
	}

/*  We need to use getAdminAccessLevel in case the check is made against *,
 *  otherwise userflags (like ALUMNI) are not taken into account.
 */

sqlChannel* adminChan = getChannelRecord("*");
if (theChan == adminChan)
	{
	return getAdminAccessLevel(theUser, false);
	}


return theLevel->getAccess();
}

/**
 *  Returns the access level a particular user has on a particular
 *  channel. Plain and simple. If the user has 500 in the channel
 *  record, this function returns 500.
 */
short int cservice::getAccessLevel( sqlUser* theUser,
	sqlChannel* theChan )
{
sqlLevel* theLevel = getLevelRecord(theUser, theChan);
if(theLevel)
	{
	return theLevel->getAccess();
 	}

/* By default, users have level 0 access on a channel. */
return 0;
}

/**
 * Returns the help message for the specified topic in
 * this user's prefered language
 */
const string cservice::getHelpMessage(sqlUser* theUser, string topic)
{
	int lang_id = 1;

	if (theUser)
		lang_id = theUser->getLanguageId();

	pair <int, string> thePair(lang_id, topic);
	helpTableType::iterator ptr = helpTable.find(thePair);
	if (ptr != helpTable.end())
		return ptr->second;

	if (lang_id != 1)
		return getHelpMessage(NULL, topic);

	return string("");
}

/**
 *  Execute an SQL query to retrieve all the help messages.
 */
void cservice::loadHelpTable()
{
if( SQLDb->Exec("SELECT language_id,topic,contents FROM help", true ) )
//if (PGRES_TUPLES_OK == status)
	for (unsigned int i = 0; i < SQLDb->Tuples(); i++)
		helpTable.insert(helpTableType::value_type(std::make_pair(
			atoi(SQLDb->GetValue(i, 0).c_str()),
			SQLDb->GetValue(i, 1)),
			SQLDb->GetValue(i, 2)));

#ifdef LOG_SQL
	elog	<< "*** [CMaster::loadHelpTable]: Loaded "
			<< helpTable.size()
			<< " help messages."
			<< endl;
#endif
}

/**
 * Returns response id "response_id" for this user's prefered
 * language.
 */
const string cservice::getResponse( sqlUser* theUser, int response_id,
	string msg )
{

// Language defaults to English
int lang_id = 1;

if (theUser)
	{
	lang_id = theUser->getLanguageId();
	}

pair<int, int> thePair( lang_id, response_id );

translationTableType::iterator ptr = translationTable.find(thePair);
if(ptr != translationTable.end())
	{
	/* Found something! */
	return ptr->second ;
	}

/*
 * Can't find this response Id within a valid language.
 * Realistically we should bomb here, however it might be wise
 * to 'fallback' to a lower language ID and try again, only bombing if we
 * can't find an english variant. (Carrying on here could corrupt
 * numerous varg lists, and will most likely segfault anyway).
 */
if (lang_id != 1)
	{
	pair<int, int> thePair( 1, response_id );
	translationTableType::iterator ptr = translationTable.find(thePair);
	if(ptr != translationTable.end())
		return ptr->second ;
	}

if( !msg.empty() )
	{
	return msg;
	}

return string( "Unable to retrieve response. Please contact a cservice "
	"administrator." ) ;
}

/**
 *  Execute an SQL query to retrieve all the translation data.
 */
void cservice::loadTranslationTable()
{
if( SQLDb->Exec("SELECT id,code,name FROM languages", true ) )
//if (PGRES_TUPLES_OK == status)
	for (unsigned int i = 0; i < SQLDb->Tuples(); i++)
		languageTable.insert(languageTableType::value_type(SQLDb->GetValue(i, 1),
			std::make_pair(atoi(SQLDb->GetValue(i, 0).c_str()),
				SQLDb->GetValue(i, 2))));

#ifdef LOG_SQL
	elog	<< "*** [CMaster::loadTranslationTable]: Loaded "
			<< languageTable.size()
			<< " languages."
			<< endl;
#endif

if( SQLDb->Exec(
	"SELECT language_id,response_id,text FROM translations", true ) )
//if( PGRES_TUPLES_OK == status )
	{
	for (unsigned int i = 0 ; i < SQLDb->Tuples(); i++)
		{
		/*
		 *  Add to our translations table.
		 */

		int lang_id = atoi(SQLDb->GetValue( i, 0 ).c_str());
		int resp_id = atoi(SQLDb->GetValue( i, 1 ).c_str());

		pair<int, int> thePair( lang_id, resp_id ) ;

		translationTable.insert(
			translationTableType::value_type(
				thePair, SQLDb->GetValue( i, 2 )) );
		}
	}

#ifdef LOG_SQL
	elog	<< "*** [CMaster::loadTranslationTable]: Loaded "
		<< translationTable.size()
		<< " translations."
		<< endl;
#endif

}

int cservice::rehashMOTD() {
/* 
 * Trying to rehash the MOTD.
 * If it succeeds, we return when it was last adjusted. 
 */

int lang_id = 1;
pair<int, int> thePair( lang_id, language::motd );
translationTableType::iterator ptr = translationTable.find(thePair);
if(ptr != translationTable.end())
	{
	translationTable.erase(thePair);
	stringstream selectQuery;
	selectQuery	<< "SELECT text,last_updated FROM "
        	        << "translations WHERE language_id = "
			<< lang_id
			<< "AND response_id = "
			<< language::motd
			<< ends;
	if( SQLDb->Exec( selectQuery, true ) ) 
		{
		translationTable.insert(translationTableType::value_type(thePair, SQLDb->GetValue(0,0)) );
		int last_updated = atoi(SQLDb->GetValue(0,1));
		return last_updated;			
		}
	}
return 0;
}

bool cservice::isOnChannel( const string& /* chanName */ ) const
{
return true;
}

/**
 * This member function executes an SQL query to return all
 * suspended access level records, and unsuspend them.
 * TODO
 */
void cservice::expireSuspends()
{
#ifdef LOG_DEBUG
elog	<< "cservice::expireSuspends> Checking for expired Suspensions.."
	<< endl;
#endif
time_t expiredTime = currentTime();
stringstream expireQuery;
expireQuery	<< "SELECT user_id,channel_id FROM levels "
		<< "WHERE suspend_expires <= "
		<< expiredTime
		<< " AND suspend_expires <> 0"
		<< ends;

#ifdef LOG_SQL
	elog	<< "expireSuspends::sqlQuery> "
		<< expireQuery.str().c_str()
		<< endl;
#endif

if( !SQLDb->Exec(expireQuery, true ) )
//if( PGRES_TUPLES_OK != status )
	{
	elog	<< "cservice::expireSuspends> SQL Error: "
		<< SQLDb->ErrorMessage()
		<< endl ;
	return ;
	}

/*
 *  Loops over the results set, and attempt to locate
 *  this level record in the cache.
 */

#ifdef LOG_SQL
	elog	<< "cservice::expireSuspends> Found "
		<< SQLDb->Tuples()
		<< " expired suspensions."
		<< endl;
#endif

/*
 *  Place our query results into temporary storage, because
 *  we might have to execute other queries inside the
 *  loop which will invalidate our results set.
 */

typedef vector < pair < string, string > > expireVectorType;
expireVectorType expireVector;

for (unsigned int i = 0 ; i < SQLDb->Tuples(); i++)
	{
	expireVector.push_back(expireVectorType::value_type(
		SQLDb->GetValue(i, 0),
		SQLDb->GetValue(i, 1) )
		);
	}

for (expireVectorType::const_iterator resultPtr = expireVector.begin();
	resultPtr != expireVector.end(); ++resultPtr)
		{
		/*
		 * Attempt to find this level record in the cache.
		 */
		pair<int, int> thePair( atoi(resultPtr->first.c_str()),
			atoi(resultPtr->second.c_str()) );

		sqlLevelHashType::iterator Lptr = sqlLevelCache.find(thePair);
		if(Lptr != sqlLevelCache.end())
			{
			/* Found it in the cache, remove suspend. */
#ifdef LOG_CACHE_HITS
			elog	<< "cservice::expireSuspends> "
				<< "Found level record in cache: "
				<< resultPtr->first
				<< ":"
				<< resultPtr->second
				<< endl;
#endif
			(Lptr->second)->setSuspendExpire(0);
			(Lptr->second)->setSuspendLevel(0);
			(Lptr->second)->setSuspendBy(string());
			(Lptr->second)->setSuspendReason(string());
			}

		/*
		 *  Execute a query to update the status in the db.
		 */

		} // for()

stringstream updateQuery;
updateQuery << "UPDATE levels SET suspend_expires = 0, suspend_level = 0, suspend_by = '', suspend_reason = ''"
		<< " WHERE suspend_expires <= "
		<< expiredTime
		<< " AND suspend_expires <> 0";

#ifdef LOG_SQL
	elog	<< "expireSuspends::sqlQuery> "
		<< updateQuery.str().c_str()
		<< endl;
#endif

if( !SQLDb->Exec(updateQuery ) )
//if( status != PGRES_COMMAND_OK)
	{
	elog	<< "cservice::expireSuspends> Unable to "
		<< "update record while unsuspending."
		<< endl;
	}
}

/**
 * This function removes any ignores that have expired.
 */
void cservice::expireSilence()
{

silenceListType::iterator ptr = silenceList.begin();
while (ptr != silenceList.end())
	{
	if ( ptr->second.first < currentTime() )
		{
		string theMask = ptr->first;
		stringstream s;
		s	<< getCharYYXXX()
			<< " SILENCE "
			<< "*"
			<< " -"
			<< theMask
			<< ends;
		Write( s );

		/*
		 * Locate this user by numeric.
		 * If the numeric is still in use, clear the ignored flag.
		 * If someone else has inherited this numeric, no prob,
		 * its cleared anyway.
		 */

		iClient* theClient = Network->findClient(ptr->second.second);
		if (theClient)
			{
				setIgnored(theClient, false);
			}

		++ptr;
		silenceList.erase(theMask);
		} else {
			++ptr;
		}

	} // while()

}

/**
 * This member function executes an SQL query to return all
 * bans that have expired. It removes them from the internal
 * cache as well as the database.
 */
void cservice::expireBans()
{
#ifdef LOG_DEBUG
elog	<< "cservice::expireBans> Checking for expired bans.."
	<< endl;
#endif
time_t expiredTime = currentTime();
stringstream expireQuery;
expireQuery	<< "SELECT channel_id,id FROM bans "
		<< "WHERE expires <= "
		<< expiredTime
		<< ends;

#ifdef LOG_SQL
	elog	<< "sqlQuery> "
		<< expireQuery.str().c_str()
		<< endl;
#endif

if( !SQLDb->Exec(expireQuery, true ) )
//if( PGRES_TUPLES_OK != status )
	{
	elog	<< "cservice::expireBans> SQL Error: "
		<< SQLDb->ErrorMessage()
		<< endl ;
	return ;
	}

/*
 *  Loops over the results set, and attempt to locate
 *  this ban in the cache.
 */

#ifdef LOG_SQL
	elog	<< "cservice::expireBans> Found "
		<< SQLDb->Tuples()
		<< " expired bans."
		<< endl;
#endif

/*
 *  Place our query results into temporary storage, because
 *  we might have to execute other queries inside the
 *  loop which will invalidate our results set.
 */
typedef vector < pair < unsigned int, unsigned int > > expireVectorType;
expireVectorType expireVector;

for (unsigned int i = 0 ; i < SQLDb->Tuples(); i++)
	{
	expireVector.push_back(expireVectorType::value_type(
		atoi(SQLDb->GetValue(i, 0).c_str()),
		atoi(SQLDb->GetValue(i, 1).c_str()) )
		);
	}

for (expireVectorType::const_iterator resultPtr = expireVector.begin();
	resultPtr != expireVector.end(); ++resultPtr)
	{
	sqlChannel* theChan = getChannelRecord( resultPtr->first );
	if (!theChan)
		{
		// TODO: Debuglog.
		continue;
		}

	#ifdef LOG_DEBUG
		elog	<< "Checking bans for "
			<< theChan->getName()
			<< endl;
	#endif

	/* Attempt to find the ban according to its id */
	map< int,sqlBan* >::iterator ptr =  
		theChan->banList.find(resultPtr->second);

	/* Was a ban found ? */
	if (ptr != theChan->banList.end())
		{
		sqlBan* theBan = ptr->second;
		theChan->banList.erase(ptr);

		Channel* tmpChan = Network->findChannel(
			theChan->getName());
		if (tmpChan)
			{
			UnBan(tmpChan, theBan->getBanMask());
			}

		#ifdef LOG_DEBUG
			elog	<< "Cleared Ban "
				<< theBan->getBanMask()
				<< " from cache"
				<< endl;
		#endif

		delete(theBan);
		}
	else
		{
		// BUG: Correct me if Im wrong, but this will crash, 
		// right?
		sqlBan* theBan = ptr->second;
		elog << "Unable to find ban "
		     << theBan->getBanMask()
		     << " with id " 
		     << theBan->getID()
		     << endl;
		}     
	} /* Forall results in set */

stringstream deleteQuery;
deleteQuery	<< "DELETE FROM bans "
		<< "WHERE expires <= "
		<< expiredTime
		<< ends;

#ifdef LOG_SQL
	elog	<< "sqlQuery> "
		<< deleteQuery.str()
		<< endl;
#endif

if( !SQLDb->Exec(deleteQuery ) )
//if( PGRES_COMMAND_OK != status )
	{
	elog	<< "cservice::expireBans> SQL Error: "
		<< SQLDb->ErrorMessage()
		<< endl ;
	return ;
	}

}

/**
 * This function iterates over the usercache, and removes
 * accounts not accessed in a certain timeframe.
 * N.B: do *NOT* expire those with a networkClient set.
 * (Ie: those currently logged in).
 */
void cservice::cacheExpireUsers()
{
	logDebugMessage("Beginning User cache cleanup:");
	sqlUserHashType::iterator ptr = sqlUserCache.begin();
	sqlUser* tmpUser;
	clock_t startTime = ::clock();
	clock_t endTime = 0;
	int purgeCount = 0;
	int updateCount = 0;
	string removeKey;

	while (ptr != sqlUserCache.end())
	{
		tmpUser = ptr->second;
		/*
		 * Have a quick look if this person has been logged in more than 24 hrs.
		 * If so, update the last-seen so their account doesn't expire after xx days. ;)
		 */
		if ( tmpUser->isAuthed() && ((tmpUser->getInstantiatedTS() + 86400) < ::time(NULL))  )
		{
			/* check to see if we have a last seen time (bug workaround) - if not, make one */
       
			stringstream queryString;
			queryString	<< "SELECT last_seen FROM users_lastseen WHERE user_id="
					<< tmpUser->getID()
					<< ends;
#ifdef LOG_SQL
			elog	<< "cservice::cacheExpireUsers::sqlQuery> "
				<< queryString.str().c_str()
				<< endl; 
#endif

			if( SQLDb->Exec(queryString, true ) )
//			if (PGRES_TUPLES_OK == status)
			{
				if (SQLDb->Tuples() < 1)
				{
					/* no rows returned - create a dummy record that will be updated
					 * by setLastSeen after this loop
					 */
					stringstream updateQuery;
					updateQuery	<< "INSERT INTO users_lastseen (user_id,"
							<< "last_seen,last_updated) VALUES("
							<< tmpUser->getID()
							<< ",date_part('epoch', CURRENT_TIMESTAMP)::int,date_part('epoch', CURRENT_TIMESTAMP)::int)"
							<< ends;

#ifdef LOG_SQL
					elog	<< "cservice::cacheExpireUsers::sqlQuery> "
						<< updateQuery.str()
						<< endl;
#endif
					SQLDb->Exec(updateQuery.str());
				}
			}
			/* update their details */
			tmpUser->setLastSeen(currentTime());
			tmpUser->setInstantiatedTS(::time(NULL));
			updateCount++;
		}

		/*
	 	 *  If this user has been idle one hour, and currently
		 *  isn't logged in, boot him out the window.
		 */
		if ( ((tmpUser->getLastUsed() + 3600) < currentTime()) &&
			!tmpUser->isAuthed() )
		{
#ifdef LOG_DEBUG
			elog << "cservice::cacheExpireUsers> "
			<< tmpUser->getUserName()
			<< "; last used: "
			<< tmpUser->getLastUsed()
			<< endl;
#endif
			purgeCount++;
			removeKey = ptr->first;
			delete(ptr->second);
			/* Advance the iterator past the soon to be
			 * removed element. */
			++ptr;
			sqlUserCache.erase(removeKey);
		}
			else
		{
			++ptr;
		}
	}
	endTime = ::clock();
	logDebugMessage("User cache cleanup complete; Removed %i user records in %i ms.",
		purgeCount, (endTime - startTime) /  CLOCKS_PER_SEC);
	logDebugMessage("I also updated %i last_seen records for people logged in for >24 hours.",
		updateCount);
}

void cservice::fixUsersLastSeen()
{
	typedef vector <std::pair < unsigned int, string > > uidVectorType;
	uidVectorType uidVector;
	vector <unsigned int> toFixVector;

	stringstream queryString;
	queryString	<< "SELECT id,user_name FROM users"
				<< ends;

	if (!SQLDb->Exec(queryString, true))
	{
		logDebugMessage("Error on cservice::fixUsersLastSeenIDQuery");
#ifdef LOG_SQL
		//elog << "sqlQuery> " << theQuery.str().c_str() << endl;
		elog 	<< "cservice::fixUsersLastSeenIDQuery> SQL Error: "
				<< SQLDb->ErrorMessage()
				<< endl ;
#endif
		return;
	}
	for (unsigned int i = 0; i < SQLDb->Tuples(); i++)
		uidVector.push_back(uidVectorType::value_type(atoi(SQLDb->GetValue(i,0).c_str()),SQLDb->GetValue(i,1).c_str()));

	if (uidVector.empty()) return;

	logDebugMessage("Total usercount to check fixUsersLastSeen: %i",(int)uidVector.size());

	for (size_t i=0; i<uidVector.size(); i++)
	{
		queryString.str("");
		queryString	<< "SELECT last_seen FROM users_lastseen WHERE user_id="
					<< uidVector.at(i).first
					<< ends;

		if (!SQLDb->Exec(queryString, true))
		{
#ifdef LOG_SQL
			//elog << "sqlQuery> " << theQuery.str().c_str() << endl;
			elog 	<< "cservice::fixUsersLastSeenLastSeenQuery> SQL Error: "
					<< SQLDb->ErrorMessage()
					<< endl ;
			logDebugMessage("Error on cservice::fixUsersLastSeenLastSeenQuery");
#endif
			return;
		}
		if (SQLDb->Tuples() == 0)
		//logDebugMessage("Found %i result(s) for userid (%i)",SQLDb->Tuples(),uidVector.at(i));
		{
			sqlUser* wipeUser;
		    sqlUserHashType::iterator usrItr = sqlUserCache.find(uidVector.at(i).second);
		    if (usrItr != sqlUserCache.end())
		    	wipeUser = usrItr->second;
		    else
		    	wipeUser = getUserRecord((int)uidVector.at(i).first);
			if (wipeUser)
			{
				// Somehow the user has no creation time, so set it now, and leave alone
				if (wipeUser->getCreatedTS() == 0)
				{
					wipeUser->setCreatedTS(currentTime());
					wipeUser->commit(this->getInstance());
				}
				else 	//only at least 1 day old users can expire
					if ((wipeUser->getCreatedTS() + 86400 < currentTime()) && (wipeUser->getLastSeen() == 0))
					{
						toFixVector.push_back(uidVector.at(i).first);
					}
			}
		}
	}
	logDebugMessage("Beginning to wipe %i users:",(int)toFixVector.size());
	for (size_t i=0; i<toFixVector.size(); i++)
		wipeUser(toFixVector.at(i),false);
	logDebugMessage("Completed.");
}

void cservice::cacheExpireLevels()
{
	logDebugMessage("Beginning Channel Level-cache cleanup:");

	/*
	 *  While we are at this, we'll clear out any FORCE'd access's
	 *  in channels.
	 */

	sqlChannelHashType::iterator ptr = sqlChannelCache.begin();
	while (ptr != sqlChannelCache.end())
	{
		sqlChannel* theChan = (ptr)->second;
		if(theChan->forceMap.size() > 0)
		{
			logDebugMessage("Clearing out %i FORCE(s) from channel %s",
						theChan->forceMap.size(), theChan->getName().c_str());
			theChan->forceMap.clear();
		}
		/*
		 * While we're here, lets see if this channel has been idle for a while.
		 * If so, we might want to part and turn off autojoin.. etc.
		 */

		if ( ((currentTime() - theChan->getLastUsed()) >= partIdleChan)
			&& theChan->getInChan()
			&& !theChan->getFlag(sqlChannel::F_SPECIAL) )
		{
			/*
			 * So long! and thanks for all the fish.
			 */

			theChan->setInChan(false);
			theChan->removeFlag(sqlChannel::F_AUTOJOIN);
			theChan->commit();
			joinCount--;
			writeChannelLog(theChan, me, sqlChannel::EV_IDLE, "");
			logDebugMessage("I've just left %s because its too quiet.",
					theChan->getName().c_str());
			Part(theChan->getName(), "So long! (And thanks for all the fish)");
		}

		++ptr;
	}
	logDebugMessage("Channel Level cache-cleanup complete.");
}

bool cservice::deleteUserFromTable(unsigned int userId, const string& table)
{
	/* We can safely do this, because the user is so long seen
	 * that is *very probably* is not in the users cache
	 */
	string userIdStr = "user_id";
	if (table == "pending") userIdStr = "manager_id";
	if (table == "users") userIdStr = "id";
	if (table == "pending_mgrchange") userIdStr = "new_manager_id";
	stringstream queryString;
	queryString << "DELETE FROM " << table << " WHERE " << userIdStr << " = "
    	 		<< userId
        		<< endl;
    if (!SQLDb->Exec(queryString,true))
    {
    	logDebugMessage("wipeUser FAILED to delete user %i from %s",userId,table.c_str());
#ifdef LOG_SQL
			elog 	<< "cservice::wipeUser> SQL Error: "
					<< SQLDb->ErrorMessage()
					<< endl ;
#endif
		logDebugMessage(SQLDb->ErrorMessage().c_str());
    	return false;
    }
    return true;
}

bool cservice::wipeUser(unsigned int userId, bool expired)
{
	sqlUser* tmpUser = getUserRecord(userId);
	assert(tmpUser != 0);
	string removeKey;
	//Don't expire any newly created, never logged user sooner than 10 minutes
	if (((int)(currentTime() - tmpUser->getCreatedTS()) < 600) && (expired)) goto cacheclean;
    if (tmpUser->getFlag(sqlUser::F_NOPURGE))
    {
    	//logDebugMessage("User %s (%i) had NOPURGE flag, but Purging It anyway!",tmpUser->getUserName().c_str(),userId);
    	if (!tmpUser->getFlag(sqlUser::F_GLOBAL_SUSPEND))
		{
   			logDebugMessage("User %s (%i) has NOPURGE flag, so Not Purging It!",tmpUser->getUserName().c_str(),userId);
   			goto cacheclean;
		}
		else
			logDebugMessage("User %s (%i) is NOPURGE but SUSPENDED, so Purging It!",tmpUser->getUserName().c_str(),userId);
    	//return true;
    }
	deleteUserFromTable(userId,"acl");
	deleteUserFromTable(userId,"levels");
	deleteUserFromTable(userId,"notices");
	deleteUserFromTable(userId,"notes");
	deleteUserFromTable(userId,"pending");
	deleteUserFromTable(userId,"pending_emailchanges");
	deleteUserFromTable(userId,"pending_pwreset");
	deleteUserFromTable(userId,"pending_mgrchange");
	deleteUserFromTable(userId,"supporters");
	deleteUserFromTable(userId,"objections");
	deleteUserFromTable(userId,"userlog");
	deleteUserFromTable(userId,"fraud_list_data");
	deleteUserFromTable(userId,"users_lastseen");
	deleteUserFromTable(userId,"users");

	if (expired) logAdminMessage("User %s (%s) has expired",tmpUser->getUserName().c_str(), tmpUser->getEmail().c_str());
	else logDebugMessage("Deleted(wipeUser) %s (%i) from the database.", tmpUser->getUserName().c_str(),userId);

	//sqlUser* tmpUser = getUserRecord(userId) inserted in the sqlUserCache, so we clean from chache all the time
	//if (!expired) //so we wipe by force even from UserCache
    //{
	cacheclean:
        sqlUserHashType::iterator usrItr = sqlUserCache.find(tmpUser->getUserName());
    	if (usrItr != sqlUserCache.end())
    	{
    		removeKey = usrItr->first;
    		delete(usrItr->second);
    		sqlUserCache.erase(removeKey);
    	}
    //}

	return true;
}

void cservice::ExpireUsers() 
{
	if (UsersExpireDBDays == 0) return;	
	logDebugMessage("Performing Database Users Expire");
    int usersCount=0;
	stringstream queryString;
	queryString	<< "SELECT user_id FROM users_lastseen WHERE last_seen<="
				<< currentTime()-UsersExpireDBDays
				<< ends;
	if( !SQLDb->Exec(queryString, true ))
	{
	   logDebugMessage("An Error occured while retrieve database information on USERS-EXPIRE query");
	   return;
	}
	if (SQLDb->Tuples() < 1)
	{
		logDebugMessage("Removed 0 users from the database");
		return;
	}
	vector <unsigned int> UserIDs;
	for (unsigned int i = 0 ; i < SQLDb->Tuples(); i++) 
		UserIDs.push_back(atoi(SQLDb->GetValue(i,0).c_str())); 
	for (unsigned int i = 0 ; i < UserIDs.size(); i++)
	    if (wipeUser(UserIDs.at(i),true)) ++usersCount;
    UserIDs.clear();
    logDebugMessage("Removed %i users from the database",usersCount);
    return;
}

void cservice::DeleteChannel(sqlChannel* theChan) 
{
   stringstream theQuery;
	/* iterate over the channel userlist */
vector< iClient* > opList;
Channel* tmpChan = Network->findChannel(theChan->getName());
/* only parse the following if the channel exists on the network
 * if there is nobody in the channel, there is nobody to op.
 */
if (tmpChan)
{
	for (Channel::userIterator chanUsers = tmpChan->userList_begin();
		chanUsers != tmpChan->userList_end(); ++chanUsers)
	{
		ChannelUser* tmpUser = chanUsers->second;
		iClient* tmpClient = tmpUser->getClient();
		sqlUser* tUser = isAuthed(tmpClient, false);
		if (!tUser)
			continue;
		/* are they globally suspended? */
		if (tUser->getFlag(sqlUser::F_GLOBAL_SUSPEND))
			continue;
		sqlLevel* theLevel = getLevelRecord(tUser, theChan);
		/* check if they have access (and not suspended) */
		if (theLevel)
		{
			if (theLevel->getSuspendExpire() > currentTime())
				continue;
			if (theLevel->getAccess() >= 100)
			{
				/* they're 100+, op them */                        
				opList.push_back(tmpClient);
			}
		}
	}
	/* actually do the ops */
	if (!opList.empty())
	{
		/* check we are in the channel, and opped */
		ChannelUser* tmpBotUser = tmpChan->findUser(getInstance());
		if (tmpBotUser)
		{
			if (!tmpBotUser->getMode(ChannelUser::MODE_O))
			{
				/* op ourselves so that we can do the reops */
				stringstream s;
				s	<< getCharYY()
					<< " M "
					<< theChan->getName()
					<< " +o "
					<< getCharYYXXX()
					<< ends;
				Write( s );
				/* update the channel state */
				tmpBotUser->setMode(ChannelUser::MODE_O);
			}
		}
		/* do the ops - if we were not in the channel before, this will
		 * auto-join and op the bot anyway.
		 */
		Op(tmpChan, opList);
	}
}

/* Reset everything back to nice default values. */

theChan->clearFlags();
theChan->setMassDeopPro(3);
theChan->setFloodPro(0);
theChan->setNoTake(0);
theChan->setURL("");
theChan->setDescription("");
theChan->setComment("");
theChan->setKeywords("");
theChan->setRegisteredTS(0);
theChan->setChannelMode("+tn");
theChan->commit();

/*
 * Permanently delete all associated Level records for this channel.
 */

theQuery.str("");
theQuery	<< "DELETE FROM levels WHERE channel_id = "
			<< theChan->getID()
			<< ends;

#ifdef LOG_SQL
elog	<< "sqlQuery> "
		<< theQuery.str().c_str()
		<< endl;
#endif

if( !SQLDb->Exec(theQuery ) )
//if( status != PGRES_COMMAND_OK )
	{
	elog	<< "PURGE> SQL Error: "
		<< SQLDb->ErrorMessage()
		<< endl ;
	return;
	}

/*
 * Bin 'em all.
 */

cservice::sqlLevelHashType::const_iterator ptr = sqlLevelCache.begin();
cservice::sqlLevelHashType::key_type thePair;

while(ptr != sqlLevelCache.end())
{
	sqlLevel* tmpLevel = ptr->second;
	unsigned int channel_id = ptr->first.second;

	if (channel_id == theChan->getID())
	{
		thePair = ptr->first;

#ifdef LOG_DEBUG
		elog << "Purging Level Record for: " << thePair.second << " (UID: " << thePair.first << ")" << endl;
#endif

		++ptr;
		sqlLevelCache.erase(thePair);

		delete(tmpLevel);
	} else
	{
		++ptr;
	}
}

/* Remove from cache.. part channel. */
sqlChannelCache.erase(theChan->getName());
sqlChannelIDCache.erase(theChan->getID());
/* no longer interested in this channel */
getUplink()->UnRegisterChannelEvent( theChan->getName(), this ) ;
/* remove mode 'R' (no longer registered) */
//Channel* tmpChan = Network->findChannel(theChan->getName());
if (tmpChan) getUplink()->Mode(NULL, tmpChan, string("-R"), string() );
Part(theChan->getName());
joinCount--;

delete(theChan);
}

void cservice::ExpireChannels() 
{ 
if ((startMIA == 0) || (endMIA == 0)) return;
logDebugMessage("Performing Channel Database Purge"); 
bool userIsAuthed = false; 
unsigned int mngrID; 
unsigned int mngrLastSeen; 
unsigned int _startMIA = currentTime()-startMIA; 
unsigned int _endMIA = currentTime()-endMIA; 
unsigned int purgeCount=0; 
string manager = "No Manager"; 
string managerEmail = "No Email Address"; 
stringstream managerQuery; 
stringstream theQuery; 
//sqlUser* theUser;
vector <sqlChannel*> delChanList; 
sqlChannelHashType::iterator ptr = sqlChannelCache.begin(); 
while (ptr != sqlChannelCache.end()) 
{ 
   sqlChannel* theChan = (ptr)->second; 
   if((theChan->getName() == "*") || (theChan->getFlag(sqlChannel::F_NOPURGE))) 
   { 
	 ++ptr; 
	 continue; 
   } 
   managerQuery.str(""); 
   managerQuery << "SELECT users.user_name,users.email,users.id " 
			    << "FROM users,levels " 
			    << "WHERE levels.user_id = users.id " 
			    << "AND levels.access = 500 " 
			    << "AND levels.channel_id = " 
			    << theChan->getID() 
			    << " LIMIT 1" 
			    << ends; 

   if( !SQLDb->Exec(managerQuery, true ) ) 
   //if( status != PGRES_TUPLES_OK ) 
   { 
#ifdef LOG_SQL
	 elog << "PURGE> SQL Error: " 
		  << SQLDb->ErrorMessage() 
		  << endl ;
#endif 
	 return ; 
   } 
   if (SQLDb->Tuples() == 0) 
   { 
      //Channel already purged 
		logDebugMessage("Channel %s has NO Manager,purging it:",theChan->getName().c_str()); 
		delChanList.push_back(theChan); 
		//logDebugMessage("Channel %s should be purged",theChan->getName().c_str());
		logAdminMessage("Channel %s has expired",theChan->getName().c_str());
		writeChannelLog(theChan, me, sqlChannel::EV_PURGE, "has purged " + theChan->getName() + " (automatic channel expire), " +
	"Manager was " + manager + " (" + managerEmail + ")" );
		purgeCount++; 
		++ptr; 
		continue; 
   } else { 
		manager = SQLDb->GetValue(0,0); 
		managerEmail = SQLDb->GetValue(0,1);
		mngrID = atoi(SQLDb->GetValue(0,2).c_str());
		sqlUserHashType::iterator ptr2 = sqlUserCache.find(manager); 
		if(ptr2 != sqlUserCache.end()) 
		{ // Found something! 
			#ifdef LOG_CACHE_HITS 
			elog << "cmaster::getUserRecord> Cache hit for " 
				 << manager 
				 << endl; 
			#endif 
			ptr2->second->setLastUsed(currentTime()); 
			userCacheHits++; 
			sqlUser* theUser = ptr2->second;
			if (theUser->isAuthed()) userIsAuthed=true; else userIsAuthed=false; 
			//if (ptr2->second->isAuthed()) userIsAuthed=true; 
		} 
		theQuery.str(""); 
		theQuery << "SELECT last_seen FROM users_lastseen WHERE user_id = "
				 << mngrID
				 << endl;
		if( !SQLDb->Exec(theQuery, true ) ) 
		{ 
			elog << "PURGE> SQL Error: " 
				 << SQLDb->ErrorMessage() 
				 << endl; 
			return; 
		} 				
		if (SQLDb->Tuples() != 0) { 
			mngrLastSeen = atoi(SQLDb->GetValue(0,0).c_str());
		} else { logDebugMessage("Warning: missing lastseen data for managerUser %s",manager.c_str()); }
		if (((mngrLastSeen<=_endMIA) && (theChan->getFlag(sqlChannel::F_MIA))) || ((mngrLastSeen<=_startMIA) && (theChan->getFlag(sqlChannel::F_CAUTION)) && (!userIsAuthed)))
		{	
			delChanList.push_back(theChan); 
			//logDebugMessage("Channel %s should be purged",theChan->getName().c_str());
			logAdminMessage("Channel %s has expired",theChan->getName().c_str());
			writeChannelLog(theChan, me, sqlChannel::EV_PURGE, "has purged " + theChan->getName() + " (automatic channel expire), " +
	"Manager was " + manager + " (" + managerEmail + ")" );
			manager = "No Manager"; 
			managerEmail = "No Email Address"; 
			purgeCount++; 
		} 
		else
		{ 
		   if ((mngrLastSeen<=_startMIA) && (!theChan->getFlag(sqlChannel::F_MIA)) && (!userIsAuthed)) 
		   {		//MIA procedure 
			theChan->setFlag(sqlChannel::F_MIA); 
			theChan->setFlag(sqlChannel::F_LOCKED); 
			theChan->setFlag(sqlChannel::F_AUTOTOPIC); 
			theChan->setDescription(MIAStartDesc);			
			theChan->setURL(MIAURL); 
			theChan->commit(); 
			if (theChan->getInChan()) doAutoTopic(theChan); 
			logDebugMessage("Applied MIA procedure for channel: %s",theChan->getName().c_str()); 
		   }
		   if ((mngrLastSeen>_startMIA) && (theChan->getFlag(sqlChannel::F_MIA)) && (userIsAuthed)) 
		   { 
			theChan->removeFlag(sqlChannel::F_MIA); 
			theChan->removeFlag(sqlChannel::F_LOCKED); 
			theChan->setFlag(sqlChannel::F_CAUTION); 
			theChan->setFlag(sqlChannel::F_AUTOTOPIC); 
			theChan->setDescription(MIAEndDesc); 
			theChan->setURL(""); 
			theChan->commit(); 
			if (theChan->getInChan()) doAutoTopic(theChan); 
		  } 
		}
   }
	++ptr; 
} //while 
for (unsigned int i=0; i<delChanList.size(); i++) 
{ 
   DeleteChannel(delChanList[i]); 
} 
   delChanList.clear(); 
   logDebugMessage("Removed %i channels from the database",purgeCount); 
}


/**
 * Check the database to see if anything has changed.
 * TODO: Lots.
 */
void cservice::processDBUpdates()
{
	logDebugMessage("[DB-UPDATE]: Looking for changes:");
	updateChannels();
	updateUsers();
	updateLevels();
	updateBans();
	logDebugMessage("[DB-UPDATE]: Complete.");
}

void cservice::updateChannels()
{
stringstream theQuery ;

theQuery	<< "SELECT "
			<< sql::channel_fields
			<< ",date_part('epoch', CURRENT_TIMESTAMP)::int as db_unixtime FROM "
			<< "channels WHERE last_updated >= "
			<< lastChannelRefresh
			<< " AND registered_ts <> 0"
			<< ends;

#ifdef LOG_SQL
elog	<< "*** [CMaster::processDBUpdates]:sqlQuery: "
		<< theQuery.str().c_str()
		<< endl;
#endif

if( !SQLDb->Exec(theQuery, true ) )
//if (status != PGRES_TUPLES_OK)
	{
	elog	<< "*** [CMaster::updateChannels]: SQL error: "
			<< SQLDb->ErrorMessage()
			<< endl;
	return;
	}

if (SQLDb->Tuples() <= 0)
	{
	/* Nothing to see here.. */
	return;
	}

/* Update our time offset incase things drift.. */
dbTimeOffset = atoi(SQLDb->GetValue(0,"db_unixtime").c_str()) - ::time(NULL);
unsigned int updates = 0;
unsigned int newchans = 0;

for (unsigned int i = 0 ; i < SQLDb->Tuples(); i++)
	{
	sqlChannelHashType::iterator ptr =
		sqlChannelCache.find(SQLDb->GetValue(i, 1));

	if(ptr != sqlChannelCache.end())
		{
		/* Found something! Update it. */
		(ptr->second)->setAllMembers(i);
		updates++;
		}
	else
		{
		/*
		 * Not in the cache.. must be a new channel.
		 * Create new channel record, insert in cache.
		 */

		sqlChannel* newChan = new (std::nothrow) sqlChannel(SQLDb);
		assert( newChan != 0 ) ;

		newChan->setAllMembers(i);

        time_t channel_ts = 0;
        Channel* tmpChan = Network->findChannel(newChan->getName());
        channel_ts = tmpChan ? tmpChan->getCreationTime() : ::time(NULL);

        newChan->setChannelTS(channel_ts);
        newChan->setFlag(sqlChannel::F_AUTOJOIN);
        newChan->setLastUsed(currentTime());
        newChan->commit();

        stringstream tmpTS;
        tmpTS << channel_ts;
        string channelTS = tmpTS.str();

        if (tmpChan)
			getUplink()->Mode(NULL, tmpChan, string("+R"), channelTS);

		sqlChannelCache.insert(sqlChannelHashType::value_type(newChan->getName(), newChan));
		sqlChannelIDCache.insert(sqlChannelIDHashType::value_type(newChan->getID(), newChan));
		MyUplink->RegisterChannelEvent(newChan->getName(), this);

		Join(newChan->getName(), string("+tnR"), newChan->getChannelTS(), true);
		newChan->setInChan(true);
		joinCount++;

		//Send a welcome notice to the channel
		if (!welcomeNewChanMessage.empty())
			Notice(newChan->getName(), TokenStringsParams(welcomeNewChanMessage.c_str(), newChan->getName().c_str()).c_str());

			//Set a welcome topic of the new channel, only if the actual topic is empty
	#ifdef TOPIC_TRACK
		if (!welcomeNewChanTopic.empty())
			if (tmpChan && tmpChan->getTopic().empty())
				Topic(tmpChan, welcomeNewChanTopic);
	#endif

		logDebugMessage("[DB-UPDATE]: Found new channel: %s", newChan->getName().c_str());
		newchans++;
		}

	}

logDebugMessage("[DB-UPDATE]: Refreshed %i channel records, loaded %i new channel(s).",
	updates, newchans);

/* Set the "Last refreshed from channels table" timestamp. */
lastChannelRefresh = atoi(SQLDb->GetValue(0,"db_unixtime").c_str());
}

//Upde One specified user's level record (used for global unsuspension)
void cservice::updateUserLevels(sqlUser* theUser)
{
	stringstream theQuery ;

	theQuery	<< "SELECT "
				<< sql::level_fields
				<< " FROM levels WHERE user_id = "
				<< theUser->getID()
				<< ends;

	#ifdef LOG_SQL
	elog	<< "updateUserLevelsQuery: "
			<< theQuery.str().c_str()
			<< endl;
	#endif

	if( !SQLDb->Exec(theQuery, true ) )
	//if (status != PGRES_TUPLES_OK)
		{
		elog	<< "updateUserLevelsQuery error: "
				<< SQLDb->ErrorMessage()
				<< endl;
		return;
		}

	if (SQLDb->Tuples() <= 0)
		{
		/* Nothing to see here.. */
		return;
		}

	for (unsigned int i = 0 ; i < SQLDb->Tuples(); i++)
		{
		unsigned int channel_id = atoi(SQLDb->GetValue(i, 0).c_str());
		unsigned int user_id = atoi(SQLDb->GetValue(i, 1).c_str());
		sqlChannel* theChan = getChannelRecord(channel_id);

		/*
		 * If we don't have the channel cached, its not registered so
		 * we aren't interested in this level record.
		 */

		if (!theChan) continue;

		pair<int, int> thePair( user_id, channel_id );

		sqlLevelHashType::iterator ptr = sqlLevelCache.find(thePair);

		if(ptr != sqlLevelCache.end())
			{
			/* Found something! Update it. */
			(ptr->second)->setAllMembers(i);
			} else
			{
			/*
			 * Must be a new level record, add it.
			 */

			sqlLevel* newLevel = new (std::nothrow) sqlLevel(SQLDb);
			newLevel->setAllMembers(i);
			sqlLevelCache.insert(sqlLevelHashType::value_type(thePair, newLevel));
			}
		}
	return;
}

/*
 * Check the levels table for recent updates.
 */
void cservice::updateLevels()
{
stringstream theQuery ;

theQuery	<< "SELECT "
			<< sql::level_fields
			<< ",date_part('epoch', CURRENT_TIMESTAMP)::int as db_unixtime FROM "
			<< "levels WHERE last_updated >= "
			<< lastLevelRefresh
			<< ends;

#ifdef LOG_SQL
elog	<< "*** [CMaster::updateLevels]: sqlQuery: "
		<< theQuery.str().c_str()
		<< endl;
#endif

if( !SQLDb->Exec(theQuery, true ) )
//if (status != PGRES_TUPLES_OK)
	{
	elog	<< "*** [CMaster::updateLevels]: SQL error: "
			<< SQLDb->ErrorMessage()
			<< endl;
	return;
	}

if (SQLDb->Tuples() <= 0)
	{
	/* Nothing to see here.. */
	return;
	}

/* Update our time offset incase things drift.. */
dbTimeOffset = atoi(SQLDb->GetValue(0,"db_unixtime").c_str()) - ::time(NULL);
unsigned int updates = 0;
unsigned int newlevs = 0;

for (unsigned int i = 0 ; i < SQLDb->Tuples(); i++)
	{
	unsigned int channel_id = atoi(SQLDb->GetValue(i, 0).c_str());
	unsigned int user_id = atoi(SQLDb->GetValue(i, 1).c_str());
	sqlChannel* theChan = getChannelRecord(channel_id);

	/*
	 * If we don't have the channel cached, its not registered so
	 * we aren't interested in this level record.
	 */

	if (!theChan) continue;

	pair<int, int> thePair( user_id, channel_id );

	sqlLevelHashType::iterator ptr = sqlLevelCache.find(thePair);

	if(ptr != sqlLevelCache.end())
		{
		/* Found something! Update it. */
		(ptr->second)->setAllMembers(i);
		updates++;
		} else
		{
		/*
		 * Must be a new level record, add it.
		 */

		sqlLevel* newLevel = new (std::nothrow) sqlLevel(SQLDb);
		newLevel->setAllMembers(i);
		sqlLevelCache.insert(sqlLevelHashType::value_type(thePair, newLevel));
		newlevs++;
		}
	}

logDebugMessage("[DB-UPDATE]: Refreshed %i level record(s), loaded %i new level record(s).",
	updates, newlevs);

/* Set the "Last refreshed from levels table" timestamp. */
lastLevelRefresh = atoi(SQLDb->GetValue(0,"db_unixtime").c_str());
}

/*
 * Check the users table to see if there have been any updates since we last looked.
 */
void cservice::updateUsers()
{
	stringstream theQuery ;

	theQuery	<< "SELECT "
				<< sql::user_fields
				<< ",date_part('epoch', CURRENT_TIMESTAMP)::int as db_unixtime FROM "
				<< "users WHERE last_updated >= "
				<< lastUserRefresh
				<< ends;

	#ifdef LOG_SQL
	elog	<< "*** [CMaster::updateUsers]: sqlQuery: "
			<< theQuery.str().c_str()
			<< endl;
	#endif

	if( !SQLDb->Exec(theQuery, true ) )
//	if (status != PGRES_TUPLES_OK)
		{
		elog	<< "*** [CMaster::updateUsers]: SQL error: "
				<< SQLDb->ErrorMessage()
				<< endl;
		return;
		}

	if (SQLDb->Tuples() <= 0)
		{
		/* Nothing to see here.. */
		return;
		}

	/* Update our time offset incase things drift.. */
	dbTimeOffset = atoi(SQLDb->GetValue(0,"db_unixtime").c_str()) - ::time(NULL);
	unsigned int updates = 0;

	for (unsigned int i = 0 ; i < SQLDb->Tuples(); i++)
		{
		sqlUserHashType::iterator ptr =
			sqlUserCache.find(SQLDb->GetValue(i, 1));

		if(ptr != sqlUserCache.end())
			{
			/* Found something! Update it */
			(ptr->second)->setAllMembers(i);
			updates++;
			}
		}

	logDebugMessage("[DB-UPDATE]: Refreshed %i user record(s).",
		updates);

	/* Set the "Last refreshed from Users table" timestamp. */
	lastUserRefresh = atoi(SQLDb->GetValue(0,"db_unixtime").c_str());
}

void cservice::updateBans()
{
/* Todo */
}

/**
 * Timer handler.
 * This member handles a number of timers, dispatching
 * control to the relevant member for the timer
 * triggered.
 */
void cservice::OnTimer(const xServer::timerID& timer_id, void*)
{
if (timer_id == limit_timerID)
	{
	updateLimits();

	/* Refresh Timers */
	time_t theTime = time(NULL) + limitCheckPeriod;
	limit_timerID = MyUplink->RegisterTimer(theTime, this, NULL);
	}

if (timer_id == dBconnection_timerID)
	{
	checkDbConnectionStatus();

	/* Refresh Timers */
	time_t theTime = time(NULL) + connectCheckFreq;
	dBconnection_timerID = MyUplink->RegisterTimer(theTime, this, NULL);
	}

if (timer_id ==  update_timerID)
	{
	processDBUpdates();

	/* Refresh Timer */
	time_t theTime = time(NULL) + updateInterval;
	update_timerID = MyUplink->RegisterTimer(theTime, this, NULL);
	}

if (timer_id == expire_timerID)
	{
	expireBans();
	expireSuspends();
	expireSilence();
	expireGlines();
	expireWhitelist();

	/* Refresh Timer */
	time_t theTime = time(NULL) + expireInterval;
	expire_timerID = MyUplink->RegisterTimer(theTime, this, NULL);
	}

if (timer_id == users_expire_timerID)
	{
	ExpireUsers();
	/* Refresh Timer */
	time_t theTime = time(NULL) + expireDBUserPeriod;
	users_expire_timerID = MyUplink->RegisterTimer(theTime, this, NULL);
	}

if (timer_id == channels_expire_timerID)
	{
	ExpireChannels();
	/* Refresh Timer */
	time_t theTime = time(NULL) + expireDBChannelPeriod;
	channels_expire_timerID = MyUplink->RegisterTimer(theTime, this, NULL);
	}

if (timer_id == cache_timerID)
	{
	cacheExpireUsers();
	cacheExpireLevels();

	fixUsersLastSeen();

	/* Refresh Timer */
	time_t theTime = time(NULL) + cacheInterval;
	cache_timerID = MyUplink->RegisterTimer(theTime, this, NULL);
	}

if (timer_id == webrelay_timerID)
{
	/* Check for new webrelay messages */
	string webrelayQuery;

	webrelayQuery = "SELECT created_ts,contents FROM webnotices WHERE ";
	webrelayQuery += "created_ts <= date_part('epoch', CURRENT_TIMESTAMP)::int ";
	webrelayQuery += "ORDER BY created_ts";
#ifdef LOG_SQL
	elog	<< "cservice::OnTimer::sqlQuery> "
		<< webrelayQuery.c_str()
		<< endl;
#endif
	int webrelay_messagecount = 0;

	if( SQLDb->Exec(webrelayQuery, true ) )
//	if (PGRES_TUPLES_OK == status)
	{
		/* process messages */
		webrelay_messagecount = SQLDb->Tuples();
		string webrelay_msg;
		unsigned long webrelay_ts = 0;
		for (int i=0; i < webrelay_messagecount; i++)
		{
			webrelay_ts = atoi(SQLDb->GetValue(i,0).c_str());
			webrelay_msg = SQLDb->GetValue(i,1);

			logAdminMessage("%s", webrelay_msg.c_str());
		}
		/* delete old messages */
		if (webrelay_messagecount > 0)
		{
			char web_ts[15];
			sprintf(web_ts, "%li", webrelay_ts);
			webrelayQuery = "DELETE FROM webnotices WHERE created_ts <= ";
			webrelayQuery += web_ts;
#ifdef LOG_SQL
			elog	<< "cservice::OnTimer::sqlQuery> "
				<< webrelayQuery.c_str()
				<< endl;
#endif
			if (!SQLDb->Exec(webrelayQuery) )
// PGRES_COMMAND_OK)
			{
				/* log error */
				elog	<< "cservice::webrelay> SQL Query Error: "
					<< SQLDb->ErrorMessage()
					<< endl;
			}
		}
	}

	/* Refresh Timer */
	time_t theTime = time(NULL) + webrelayPeriod;
	webrelay_timerID = MyUplink->RegisterTimer(theTime, this, NULL);
}

if (timer_id == pending_timerID)
	{
	/*
	 * Load the list of pending channels and calculate/save some stats.
	 */
	checkValidUsersAndChannelsState();
	checkIncomings();
	loadPendingChannelList();
	checkTrafficPass();
	checkObjections();
	checkAccepts();
	checkRewievs();
	cleanUpPendings();

	/*
	 * Load a list of channels in NOTIFICATION stage and send them
	 * a notice.
	 */

	stringstream theQuery;
	theQuery	<<  "SELECT channels.name,channels.id,pending.created_ts"
				<< " FROM pending,channels"
				<< " WHERE channels.id = pending.channel_id"
				<< " AND pending.status = 2;"
				<< ends;

#ifdef LOG_SQL
		elog	<< "cmaster::loadPendingChannelList> "
			<< theQuery.str().c_str()
			<< endl;
#endif

	unsigned int noticeCount = 0;
	if( SQLDb->Exec(theQuery, true ) )
//	if( PGRES_TUPLES_OK == status )
		{
		for (unsigned int i = 0 ; i < SQLDb->Tuples(); i++)
			{
			noticeCount++;
			string channelName = SQLDb->GetValue(i,0);
			unsigned int channel_id =
				atoi(SQLDb->GetValue(i, 1).c_str());
			unsigned int created_ts =
				atoi(SQLDb->GetValue(i, 2).c_str());
			Channel* tmpChan = Network->findChannel(channelName);

			if (tmpChan)
				{
				serverNotice(tmpChan,
				"This channel is currently being processed for registration. "
				"If you wish to view the details of the application or to object, please visit: "
				"%s?id=%i-%i OR use INFO and/or OBJECT commands.", pendingPageURL.c_str(), created_ts, channel_id);
				}
			}
		}

	logDebugMessage("Loaded Pending Channels notification list, I have just notified %i channels that they are under registration.",
		noticeCount);

	/* Refresh Timer */

	time_t theTime = time(NULL) +pendingChanPeriod;
	pending_timerID = MyUplink->RegisterTimer(theTime, this, NULL);
	}
}

/**
 * Send a notice to a channel from the server.
 * TODO: Move this method to xServer.
 */
bool cservice::serverNotice( Channel* theChannel, const char* format, ... )
{
char buf[ 1024 ] = { 0 } ;
va_list _list ;

va_start( _list, format ) ;
vsnprintf( buf, 1024, format, _list ) ;
va_end( _list ) ;

stringstream s;
s	<< MyUplink->getCharYY()
	<< " O "
	<< theChannel->getName()
	<< " :"
	<< buf
	<< ends;

Write( s );

return false;
}

/**
 * Send a notice to a channel from the server.
 * TODO: Move this method to xServer.
 */
bool cservice::serverNotice( Channel* theChannel, const string& Message)
{
stringstream s;
s	<< MyUplink->getCharYY()
	<< " O "
	<< theChannel->getName()
	<< " :"
	<< Message
	<< ends;

Write( s );

return false;
}

/**
 *  Log a message to the admin channel and the logfile.
 */
bool cservice::logAdminMessage(const char* format, ... )
{

char buf[ 1024 ] = { 0 } ;
va_list _list ;

va_start( _list, format ) ;
vsnprintf( buf, 1024, format, _list ) ;
va_end( _list ) ;

// Try and locate the relay channel.
//Channel* tmpChan = Network->findChannel(getConfigVar("CMASTER.RELAY_CHAN")->asString());
Channel* tmpChan = Network->findChannel(relayChan);
if (!tmpChan)
	{
	elog	<< "cservice::logAdminMessage> Unable to locate relay "
		<< "channel on network!"
		<< endl;
	return false;
	}

string message = string( "[" ) + nickName + "] " + buf ;
serverNotice(tmpChan, message);
return true;
}

/**
 * Log a privileged admin channel message
 */
bool cservice::logPrivAdminMessage(const char* format, ... )
{
	char buf[1024] = { 0 };
	va_list _list ;

	va_start(_list, format);
	vsnprintf(buf, 1024, format, _list);
	va_end(_list);

	/* try to locate the privileged relay channel */
	Channel* tmpChan = Network->findChannel(privrelayChan);
	if (!tmpChan)
	{
		elog	<< "cservice::logPrivAdminMessage> Unable to locate "
			<< "prileved relay channel on network!"
			<< endl;
		return false;
	}

	string message = string("[") + nickName + "] " + buf;
	serverNotice(tmpChan, message);
	return true;
}

bool cservice::logDebugMessage(const char* format, ... )
{

char buf[ 1024 ] = { 0 } ;
va_list _list ;

va_start( _list, format ) ;
vsnprintf( buf, 1024, format, _list ) ;
va_end( _list ) ;

// Try and locate the debug channel.
//Channel* tmpChan = Network->findChannel(getConfigVar("CMASTER.DEBUG_CHAN")->asString());
Channel* tmpChan = Network->findChannel(debugChan);
if (!tmpChan)
	{
	elog	<< "cservice::logAdminMessage> Unable to locate debug "
		<< "channel on network!"
		<< endl;
	return false;
	}

string message = string( "[" ) + nickName + "] " + buf ;
serverNotice(tmpChan, message);
return true;
}

bool cservice::sqlLogCommand(iClient* theClient, const char* format, ... )
{
	unsigned int userId = 0;
	sqlUser* theUser = isAuthed(theClient, false);
	if (theUser) userId = theUser->getID(); //return false; //I have no better idea for the moment

	string message = TokenStringsParams(format);
	StringTokenizer st( message ) ;
	if( st.empty() )
	{
		elog << "cservice::sqlLogCommand> Incomplete command" << endl;
		return false;
	}

	const string Command = string_upper( st[ 0 ] ) ;

	/* Log command to SQL */
	if ((Command != "LOGIN") && (Command != "NEWPASS") && (Command != "SUSPENDME"))
	{
		stringstream theLog;
		theLog  << "INSERT INTO adminlog (user_id,cmd,args,timestamp,issue_by)"
				<< " VALUES("
				<< userId //theUser->getID()
				<< ","
				<< "'" << escapeSQLChars(Command) << "'"
				<< ","
				<< "'" << escapeSQLChars(st.assemble(1)) << "'"
				<< ","
				<< currentTime()
				<< ","
				<< "'" << escapeSQLChars(theClient->getRealNickUserHost()) << "'"
				<< ")"
				<< ends;

		elog << theLog.str().c_str() << endl;

		if( !SQLDb->Exec(theLog ) )
//		if( PGRES_COMMAND_OK != status )
		{
			elog    << "cservice::adminlog> Something went wrong: "
					<< theLog.str().c_str()
					<< " "
					<< SQLDb->ErrorMessage()
					<< endl;

			return false;
		}
	}
	return true;
}

/* *** The Judge *** */

void cservice::AddToValidResponseString(const string& resp)
{
	if (validResponseString == "") validResponseString = resp;
	else validResponseString += "," + resp;
	return;
}

unsigned int cservice::getPendingChanId(const string& chanName)
{
	stringstream theQuery;
    theQuery	<< "SELECT id FROM channels WHERE name = '"
       		    << escapeSQLChars(chanName)
                << "' AND registered_ts = 0"
                << ends;
    if (!SQLDb->Exec(theQuery, true))
    {
    	logDebugMessage("Error on getPendingChanId");
        #ifdef LOG_SQL
        //elog << "sqlQuery> " << theQuery.str().c_str() << endl;
        elog	<< "getPendingChanId> SQL Error: "
                << SQLDb->ErrorMessage()
                << endl ;
        #endif
        logDebugMessage("getPendingChanId> SQL Error: %s", SQLDb->ErrorMessage().c_str());
        return 0;
    }
    else if (SQLDb->Tuples() > 0) return atoi(SQLDb->GetValue(0,0)) ;
	return 0;
}

const string cservice::getPendingChanName(unsigned int chanId)
{
	stringstream theQuery;
    theQuery	<< "SELECT name FROM channels WHERE id = "
       		    << chanId
                << ends;
    if (!SQLDb->Exec(theQuery, true))
    {
    	logDebugMessage("Error on getPendingChanName");
        #ifdef LOG_SQL
        //elog << "sqlQuery> " << theQuery.str().c_str() << endl;
        elog	<< "getPendingChanName> SQL Error: "
                << SQLDb->ErrorMessage()
                << endl ;
        #endif
        logDebugMessage("getPendingChanName> SQL Error: %s", SQLDb->ErrorMessage().c_str());
        return("");
    }
    else if (SQLDb->Tuples() > 0) return SQLDb->GetValue(0,0) ;
	return("");
}

bool cservice::isValidUser(const string& userName)
{
	validResponseString = "";
	sqlUser* tmpUser = getUserRecord(userName);
	if (!tmpUser)
	{
		validResponseString = "INEXISTENT";
		return false;
	}

	/* Check if user is NOREG/LOCKED/locked werification answer
	 * if user noreg type is 0, then it is NOREG, if type=5 then it is LOCKED, if type = 6 then user_name is referring to the locked verification answer.
	 */
	stringstream theQuery;
	theQuery	<< "SELECT user_name,email,type FROM noreg WHERE user_name IS NOT NULL OR email IS NOT NULL"
				<< ends;
	if (!SQLDb->Exec(theQuery, true))
	{
	#ifdef LOG_SQL
		elog 	<< "JUDGE.EmailQuery> SQL Error: "
	     		<< SQLDb->ErrorMessage()
	     		<< endl ;
	#endif
		return false;
	} else if (SQLDb->Tuples() != 0)
	{
		string matchString;
		for (unsigned int i = 0 ; i < SQLDb->Tuples(); i++)
		{
			if (atoi(SQLDb->GetValue(i,2)) == 6)
			{
				matchString = SQLDb->GetValue(i,0);
				if (matchString[0] == '!')
				{
					matchString.erase(0,1);
					if (!match(matchString,tmpUser->getVerifData()))
					{
						AddToValidResponseString("INVALID VERIF");
					}
				}
				else
				{   /* This should be the matchcase metod - TODO: need to find a solution */
					if (!casematch(matchString,tmpUser->getVerifData()))
					//if (0 != strcmp(matchString,tmpUser->getVerifData()))
					{
						AddToValidResponseString("INVALID VERIF");
					}
				}
			}
			matchString = SQLDb->GetValue(i,0);
			if (!match(matchString,tmpUser->getUserName()) && (matchString != "*"))
			{
				if (atoi(SQLDb->GetValue(i,2)) < 4)
				{
					AddToValidResponseString("NOREG");
				}
				//This case is taken account by the webinterface
				//We skip this because we only look after *existing* usernames
				//if (atoi(SQLDb->GetValue(i,2)) == 4)
				//	AddToValidResponseString("FRAUD");
				if (atoi(SQLDb->GetValue(i,2)) == 5)
					AddToValidResponseString("LOCKED");
			}
			matchString = SQLDb->GetValue(i,1);
			if (!match(matchString,tmpUser->getEmail()) && (matchString != "*"))
			{
				if (atoi(SQLDb->GetValue(i,2)) < 4)
				{
					AddToValidResponseString("INV.E-MAIL");
				}
				//This case is taken account by the webinterface
				//TODO: probably we should handle this case too
				//if (atoi(SQLDb->GetValue(i,2)) == 4)
				//	AddToValidResponseString("FRAUD");
				if (atoi(SQLDb->GetValue(i,2)) == 5)
					AddToValidResponseString("LOCKED E-MAIL");
			}
		}
	}
	bool isValid = true;
	if (tmpUser->getFlag(sqlUser::F_FRAUD))
	{
		AddToValidResponseString("FRAUD");
		isValid = false;
	}
	if (tmpUser->getFlag(sqlUser::F_GLOBAL_SUSPEND))
	{
		AddToValidResponseString("SUSPEND");
		isValid = false;
	}
	if (!isValid) return false;
	return true;
}

bool cservice::isValidUser(const string& userName, iClient* theClient)
{
	if (!isValidUser(userName))
	{
		Notice(theClient,"Invalid username (%s)",validResponseString.c_str());
		return false;
	}
	return true;
}

bool cservice::isValidSupporter(const string& suppUser)
{
	sqlUser* theUser = getUserRecord(suppUser);
	if (!theUser) return false;
	//{
	//	validResponseString = "INEXISTENT";
	//	return false;
	//}
	//Check ever logged on IRC
	if (theUser->getLastIP().size() == 0)
	{
		validResponseString = "NEVER_LOGGED";
		return false;
	}
	//Check MIN_DAYS_BEFORE_SUPPORT
	if ((currentTime()-theUser->getCreatedTS()) < (MinDaysBeforeSupport * JudgeDaySeconds))
	{
		validResponseString = "TOO_NEW";
		return false;
	}
	//Check last_seen <> 21 days
	if ((currentTime()-theUser->getLastSeen()) > (21 * JudgeDaySeconds))
	{
		validResponseString = "SEEN_LONG_AGO";
		return false;
	}
	//Check MAX_CONCURRENT_SUPPORTS
	stringstream theQuery;
	theQuery	<< "SELECT COUNT(*) FROM users,channels,supporters,pending WHERE "
				<< "lower(users.user_name) = '"
				<< escapeSQLChars(string_lower(suppUser))
				<< "' AND pending.channel_id = supporters.channel_id AND users.id = supporters.user_id "
				<< " AND channels.registered_ts = 0"
				<< ends;
	if (!SQLDb->Exec(theQuery, true))
	{	logDebugMessage("Error on Judge.SupportingCountQuery");
#ifdef LOG_SQL
	//elog << "sqlQuery> " << theQuery.str().c_str() << endl;
	elog 	<< "Jude.SupportingCountQuery> SQL Error: "
			<< SQLDb->ErrorMessage()
			<< endl ;
#endif
	return false;
	}
	if ((SQLDb->Tuples() != 0) && (atoi(SQLDb->GetValue(0,0)) > (int)MaxConcurrentSupports))
	{
		validResponseString = "TOO_MANY_SUPPORTS";
		return false;
	}
	return true;
}

bool cservice::isValidApplicant(sqlUser* theUser)
{
	//sqlUser* theUser = getUserRecord(userName);
	if (!theUser) return false;

	/* Check if the user has ever logged in on IRC. */
	if (theUser->getLastIP().size() == 0)
	{
		validResponseString = "Target user must login to X on IRC to own a channel.";
		return false;
	}

	// Check theUser has passed suffucient days after it's user registration
	if ((currentTime()-theUser->getCreatedTS()) < (MinDaysBeforeReg * JudgeDaySeconds))
	{
		validResponseString = "TOO_NEW";
		return false;
	}

	/* Check if already has a channel */
	stringstream theQuery;
	theQuery	<< "SELECT * FROM levels,channels WHERE channels.id=levels.channel_id AND channels.registered_ts>0 AND levels.user_id="
				<< theUser->getID()
				<< " AND levels.access = '500'"
				<< ends;
	if (!SQLDb->Exec(theQuery, true))
	{	logDebugMessage("Error on Judge.REGISTER.500Query");
#ifdef LOG_SQL
	//elog << "sqlQuery> " << theQuery.str().c_str() << endl;
	elog 	<< "Jude.REGISTER.500Query> SQL Error: "
			<< SQLDb->ErrorMessage()
			<< endl ;
#endif
	return false;
	}
	if (SQLDb->Tuples() != 0)
	{
		validResponseString = "ALREADY_HAVE_CHAN";
		return false;
	}

	/* Check if has a pending application */
	theQuery.str("");
	theQuery	<< "SELECT * FROM pending WHERE status<3 AND manager_id="
				<< theUser->getID()
				<< ends;
	if (!SQLDb->Exec(theQuery, true))
	{	logDebugMessage("Error on Judge.REGISTER.PendingQuery");
#ifdef LOG_SQL
	//elog << "sqlQuery> " << theQuery.str().c_str() << endl;
	elog 	<< "Judge.REGISTER.PendingQuery> SQL Error: "
			<< SQLDb->ErrorMessage()
			<< endl ;
#endif
	return false;
	}
	if (SQLDb->Tuples() != 0)
	{
		validResponseString = "ALREADY_HAVE_PENDINGCHAN";
		return false;
	}
	return true;
}

bool cservice::isValidChannel(const string& chName)
{
	/* Check if channelname is locked */
	stringstream theQuery;
	theQuery	<< "SELECT channel_name,type FROM noreg WHERE channel_name IS NOT NULL"
				<< ends;
	if (!SQLDb->Exec(theQuery, true))
	{
		logDebugMessage("Error on Judge.isValidChannelchannel_nameQuery");
	#ifdef LOG_SQL
		//elog << "sqlQuery> " << theQuery.str().c_str() << endl;
		elog 	<< "Judge.isValidChannelchannel_nameQuery> SQL Error: "
	     		<< SQLDb->ErrorMessage()
	     		<< endl ;
	#endif
		return false;
	} else if (SQLDb->Tuples() != 0)
	{
		for (unsigned int i = 0 ; i < SQLDb->Tuples(); i++)
			if (!match(string_lower(SQLDb->GetValue(i,0)),string_lower(chName)))
			{
				if (atoi(SQLDb->GetValue(i,1)) < 4)
					validResponseString = "NOREG";
				if (atoi(SQLDb->GetValue(i,1)) == 5)
					validResponseString = "LOCKED";
				return false;
			}
	}
	return true;
}

bool cservice::isValidChannel(const string& chName, iClient* theClient)
{
	validResponseString = "";
	sqlUser* theUser = isAuthed(theClient,true);
	if (!theUser) return false;
	/*
	 *  First, check the channel isn't already registered.
	 */
	sqlChannel* theChan;
	theChan = getChannelRecord(chName);
	if (theChan) 
	{
		validResponseString = TokenStringsParams(getResponse(theUser,
				language::chan_already_reg,
				string("%s is already registered with me.")).c_str(),
			chName.c_str());
		Notice(theClient,validResponseString);
		return false;
	}

	/* Check if channelname is locked */
	stringstream theQuery;
	theQuery	<< "SELECT channel_name,type FROM noreg WHERE channel_name IS NOT NULL"
			<< ends;
	if (!SQLDb->Exec(theQuery, true))
	{
		logDebugMessage("Error on Judge.channel_nameQuery");
	#ifdef LOG_SQL
		//elog << "sqlQuery> " << theQuery.str().c_str() << endl;
		elog 	<< "Judge.channel_nameQuery> SQL Error: "
	     		<< SQLDb->ErrorMessage()
	     		<< endl ;
	#endif
		return false;
	} else if (SQLDb->Tuples() != 0)
	{
		for (unsigned int i = 0 ; i < SQLDb->Tuples(); i++)
			if (!match(string_lower(SQLDb->GetValue(i,0)),string_lower(chName)))
			{
				if (atoi(SQLDb->GetValue(i,1)) < 4)
					validResponseString = "Cannot register, channel is NOREG.";
				if (atoi(SQLDb->GetValue(i,1)) == 5)
					validResponseString = "Cannot register, channel is LOCKED.";
				Notice(theClient,validResponseString);
				return false;
			}
	}
	return true;
}

bool cservice::RejectChannel(unsigned int chanId, const string& reason)
{
	stringstream theQuery;
	theQuery	<< "UPDATE pending SET status = '9',"
				<< " last_updated = date_part('epoch', CURRENT_TIMESTAMP)::int,"
				<< " decision_ts = date_part('epoch', CURRENT_TIMESTAMP)::int,"
				<< " decision = 'by The Judge: "
				<< reason
				<< "', reviewed = 'Y', reviewed_by_id = "
				<< ReviewerId
				<< " WHERE channel_id = " << chanId
				<< ends;

	if (!SQLDb->Exec(theQuery, true))
	{
		logDebugMessage("Error on Judge.RejectChannelQuery");
	#ifdef LOG_SQL
		//elog << "sqlQuery> " << theQuery.str().c_str() << endl;
		elog 	<< "Judge.RejectChannelQuery> SQL Error: "
	     		<< SQLDb->ErrorMessage()
	     		<< endl ;
	#endif
		return false;
	} else if (SQLDb->Tuples() != 0) return true; else return false;
}

bool cservice::RewievChannel(unsigned int chanId)
{
	stringstream theQuery;
	theQuery	<< "UPDATE pending SET status = '8',"
				<< "last_updated = date_part('epoch', CURRENT_TIMESTAMP)::int,"
				<< "check_start_ts = date_part('epoch', CURRENT_TIMESTAMP)::int "
				<< "WHERE channel_id = " << chanId
				<< ends;

	if (!SQLDb->Exec(theQuery, true))
	{
		logDebugMessage("Error on Judge.RewievtChannelQuery");
	#ifdef LOG_SQL
		//elog << "sqlQuery> " << theQuery.str().c_str() << endl;
		elog 	<< "Judge.RewievChannelQuery> SQL Error: "
	     		<< SQLDb->ErrorMessage()
	     		<< endl ;
	#endif
		return false;
	} else if (SQLDb->Tuples() != 0) return true; else return false;
}

bool cservice::AcceptChannel(unsigned int chanId, const string& reason)
{
	stringstream theQuery;
	theQuery	<< "UPDATE pending SET status = '3',"
				<< " last_updated = date_part('epoch', CURRENT_TIMESTAMP)::int,"
				<< " decision_ts = date_part('epoch', CURRENT_TIMESTAMP)::int,"
				<< " decision = 'by The Judge: "
				<< reason
				<< "', reviewed = 'Y', reviewed_by_id = "
				<< ReviewerId
				<< " WHERE channel_id = " << chanId
				<< ends;

	if (!SQLDb->Exec(theQuery, true))
	{
		logDebugMessage("Error on Judge.AcceptChannelQuery");
	#ifdef LOG_SQL
		//elog << "sqlQuery> " << theQuery.str().c_str() << endl;
		elog 	<< "Judge.AcceptChannelQuery> SQL Error: "
	     		<< SQLDb->ErrorMessage()
	     		<< endl ;
	#endif
		return false;
	} else if (SQLDb->Tuples() != 0) return true; else return false;
}

bool cservice::sqlRegisterChannel(iClient* theClient, sqlUser* mngrUsr, const string& chanName)
{

		/*
         * Create the new channel and insert it into the cache.
         * If the channel exists on IRC, grab the creation timestamp
         * and use this as the channel_ts in the Db.
         */

        unsigned int channel_ts = 0;
        Channel* tmpChan = Network->findChannel(chanName);
        channel_ts = tmpChan ? tmpChan->getCreationTime() : ::time(NULL);

        sqlChannel* newChan = new (std::nothrow) sqlChannel(SQLDb);
        newChan->setName(chanName);
        newChan->setChannelTS(channel_ts);
        newChan->setRegisteredTS(currentTime());
        newChan->setChannelMode("+tnR");
    	newChan->setFlag(sqlChannel::F_AUTOJOIN);
        newChan->setLastUsed(currentTime());

        sqlUser* theUser = isAuthed(theClient, false);

        /*
         *  If this channel exists in the database (without a registered_ts set),
         *  then it is currently unclaimed. This register command will
         *  update the timestamp, and proceed to adduser.
         */

        stringstream checkQuery;
        checkQuery      << "SELECT id FROM channels WHERE "
                        << "registered_ts = 0 AND lower(name) = '"
                        << escapeSQLChars(string_lower(chanName))
                        << "'"
                        << ends;

#ifdef LOG_SQL
        elog << "sqlRegisterChannelQuery> " << checkQuery.str().c_str() << endl;
#endif

        bool isUnclaimed = false;
        if (SQLDb->Exec(checkQuery, true))
//      if ((status = bot->SQLDb->Exec(checkQuery.str().c_str())) == PGRES_TUPLES_OK)
        {
                if (SQLDb->Tuples() > 0) isUnclaimed = true;
        }

        if (isUnclaimed)
        {
                /*
                 *  Quick query to set registered_ts back for this chan.
                 */

                stringstream reclaimQuery;

                reclaimQuery    << "UPDATE channels SET registered_ts = date_part('epoch', CURRENT_TIMESTAMP)::int,"
                                << " last_updated = date_part('epoch', CURRENT_TIMESTAMP)::int, "
                                << " flags = 0, description = '', url = '', comment = '', keywords = '', channel_mode = '+tn' "
                                << " WHERE lower(name) = '"
                                << escapeSQLChars(string_lower(chanName))
                                << "'"
                                << ends;
#ifdef LOG_SQL
                elog << "sqlRegChannReclaimQuery> " << reclaimQuery.str().c_str() << endl;
#endif

                if (!SQLDb->Exec(reclaimQuery)) return false;
        }
                else /* We perform a normal registration. */
        {
                newChan->insertRecord();
        }

        /*
         *  Now add the target chap at 500 in the new channel. To do this, we need to know
         *  the db assigned channel id of the newly created channel :/
         */
        stringstream idQuery;
        idQuery         << "SELECT id FROM channels WHERE "
                        << "lower(name) = '"
                        << escapeSQLChars(string_lower(chanName))
                        << "'"
                        << ends;

#ifdef LOG_SQL
        elog << "sqlRegChannidQuery> " << idQuery.str().c_str() << endl;
#endif

        unsigned int theId = 0;

        if (SQLDb->Exec(idQuery, true))
//      if ((status = bot->SQLDb->Exec(idQuery.str().c_str())) ==
//PGRES_TUPLES_OK)
        {
                if (SQLDb->Tuples() > 0)
                {
                        theId = atoi(SQLDb->GetValue(0, 0));
                        newChan->setID(theId);

                        sqlChannelCache.insert(cservice::sqlChannelHashType::value_type(newChan->getName(), newChan));
                        sqlChannelIDCache.insert(cservice::sqlChannelIDHashType::value_type(newChan->getID(), newChan));
                } else
                {
                        /*
                         * If we can't find the channel in the db, something has gone
                         * horribly wrong.
                         */
                        return false;
                }

        } else
        {
                return false;
        }
        newChan->commit();
        /*
         * Create the new manager.
         */

        sqlLevel* newManager = new (std::nothrow) sqlLevel(SQLDb);
        newManager->setChannelId(newChan->getID());
        newManager->setUserId(mngrUsr->getID());
        newManager->setAccess(500);
        newManager->setAdded(currentTime());
        if (theClient == getInstance())
        {
        	newManager->setAddedBy("(" + getNickName() + ") " + getInstance()->getNickUserHost());
        	newManager->setLastModifBy("*** The Judge ***");
        }
        else
        {
        	newManager->setAddedBy("(" + theUser->getUserName() + ") " + theClient->getNickUserHost());
        	newManager->setLastModifBy("(" + theUser->getUserName() + ") " + theClient->getNickUserHost());
        }
		newManager->setLastModif(currentTime());
        if (!newManager->insertRecord())
        {
		validResponseString = "Couldn't automatically add the level 500 Manager, check if doesn't already exist.";
		logDebugMessage(validResponseString.c_str());
                delete(newManager);
                return false;
        }

        /*
         * Insert this new 500 into the level cache.
         */

        pair<int, int> thePair( newManager->getUserId(), newManager->getChannelId());
        sqlLevelCache.insert(cservice::sqlLevelHashType::value_type(thePair, newManager));

        /* set channel mode R - tmpChan is created further above */
        stringstream tmpTS;
        tmpTS << channel_ts;
        string channelTS = tmpTS.str();

        if (tmpChan)
        	getUplink()->Mode(NULL, tmpChan, string("+R"), channelTS );
        getUplink()->RegisterChannelEvent(chanName.c_str(),this);
        writeChannelLog(newChan, theClient, sqlChannel::EV_REGISTER, "to " + mngrUsr->getUserName());

    	Join(newChan->getName(), string("+tnR"), newChan->getChannelTS(), true);
    	newChan->setInChan(true);
    	joinCount++;

        //Send a welcome notice to the channel
        if (!welcomeNewChanMessage.empty())
        	Notice(newChan->getName(), TokenStringsParams(welcomeNewChanMessage.c_str(), newChan->getName().c_str()).c_str());

        //Set a welcome topic of the new channel, only if the actual topic is empty
	#ifdef TOPIC_TRACK
        if (!welcomeNewChanTopic.empty())
        	if (tmpChan && tmpChan->getTopic().empty())
        		Topic(tmpChan, welcomeNewChanTopic);
	#endif

        return true;
}

bool cservice::wipeChannel(unsigned int id)
{
	stringstream theQuery;
	theQuery	<< "DELETE FROM pending WHERE channel_id = " << id << ends;
	if (!SQLDb->Exec(theQuery, true))
	{
		logDebugMessage("Error on Judge.WipePendingQuery");
	    #ifdef LOG_SQL
	            //elog << "sqlQuery> " << theQuery.str().c_str() << endl;
	            elog    << "Judge.WipePendingQuery> SQL Error: "
	                    << SQLDb->ErrorMessage()
	                    << endl ;
	    #endif
	            return false;
	}
	theQuery.str("");
	theQuery	<< "DELETE FROM objections WHERE channel_id = " << id << ends;
	if (!SQLDb->Exec(theQuery, true))
	{
		logDebugMessage("Error on Judge.WipeObjectionsQuery");
	    #ifdef LOG_SQL
	            //elog << "sqlQuery> " << theQuery.str().c_str() << endl;
	            elog    << "Judge.WipeObjectionsQuery> SQL Error: "
	                    << SQLDb->ErrorMessage()
	                    << endl ;
	    #endif
	            return false;
	}
	theQuery.str("");
	theQuery	<< "DELETE FROM supporters WHERE channel_id = " << id << ends;
	if (!SQLDb->Exec(theQuery, true))
	{
		logDebugMessage("Error on Judge.WipeSupportersQuery");
	    #ifdef LOG_SQL
	            //elog << "sqlQuery> " << theQuery.str().c_str() << endl;
	            elog    << "Judge.WipeSupportersQuery> SQL Error: "
	                    << SQLDb->ErrorMessage()
	                    << endl ;
	    #endif
	            return false;
	}
/*
	// This has to be deleted !!!
	theQuery.str("");
	theQuery	<< "DELETE FROM levels WHERE channel_id = " << id << ends;
	if (!SQLDb->Exec(theQuery, true))
	{
		logDebugMessage("Error on Judge.WipeLevelsQuery");
	    #ifdef LOG_SQL
	            //elog << "sqlQuery> " << theQuery.str().c_str() << endl;
	            elog    << "Judge.WipeLevelsQuery> SQL Error: "
	                    << SQLDb->ErrorMessage()
	                    << endl ;
	    #endif
	            return false;
	}
	theQuery.str("");
	theQuery	<< "DELETE FROM bans WHERE channel_id = " << id << ends;
	if (!SQLDb->Exec(theQuery, true))
	{
		logDebugMessage("Error on Judge.WipeBansQuery");
	    #ifdef LOG_SQL
	            //elog << "sqlQuery> " << theQuery.str().c_str() << endl;
	            elog    << "Judge.WipeBansQuery> SQL Error: "
	                    << SQLDb->ErrorMessage()
	                    << endl ;
	    #endif
	            return false;
	}
	*/
	return true;
}

void cservice::checkValidUsersAndChannelsState()
{
	//First we check out channel<--> manager validity
	typedef std::vector <std::pair<unsigned int,string> > pendingChanListType;
	pendingChanListType chanList, managerList;
	unsigned int currentChanId = 0;
	stringstream theQuery;
	theQuery	<< "SELECT channel_id,channels.name,manager_id,users.user_name FROM channels,pending,users "
				<< "WHERE channels.id = pending.channel_id AND users.id = pending.manager_id "
				<< "AND (pending.status <> 3 AND pending.status <> 9 AND pending.status <> 4)"
				<< ends;
	if (!SQLDb->Exec(theQuery, true))
	{
		logDebugMessage("Error on Judge.validChanAndMngrQuery");
	#ifdef LOG_SQL
		//elog << "sqlQuery> " << theQuery.str().c_str() << endl;
		elog 	<< "Judge.validChanAndMngrQuery> SQL Error: "
	     		<< SQLDb->ErrorMessage()
	     		<< endl ;
	#endif
		return;
	} else if (SQLDb->Tuples() != 0)
	{
		for (unsigned int i = 0 ; i < SQLDb->Tuples(); i++)
		{
			unsigned int chanId = atoi(SQLDb->GetValue(i,0));
			string chanName = SQLDb->GetValue(i,1);
			unsigned int mngrId = atoi(SQLDb->GetValue(i,2));
			string mngrUser = SQLDb->GetValue(i,3);
			//chanList.push_back(std::make_pair(chanId,chanName));
			//managerList.push_back(std::make_pair(mngrId,mngrUser));
			chanList.push_back(pendingChanListType::value_type(chanId,chanName));
			managerList.push_back(pendingChanListType::value_type(mngrId,mngrUser));
		}
	}
	if (!chanList.empty())
	{
		logDebugMessage("Checking all pending channels validity ...");
		for (size_t i = 0; i < chanList.size(); ++i)
		{
			//logDebugMessage("Checking channel %s's validity ...",chanList[i].second.c_str());
			if (!isValidChannel(chanList.at(i).second))
			{
				string rejectReason = "Invalid channel (" + validResponseString +")";
				validResponseString.clear();
				RejectChannel(chanList[i].first,rejectReason);
				sqlUser* mgrUsr = getUserRecord(managerList[i].first);
				NoteAllAuthedClients(mgrUsr,"Your channel application of %s has been rejected with reason: %s", chanList[i].second.c_str(),rejectReason.c_str());
				logDebugMessage(rejectReason.c_str());
			} //else logDebugMessage("VALID");
			//logDebugMessage("Checking channel %s's Manager validity ...",chanList[i].second.c_str());
			if (!isValidUser(managerList[i].second))
			{
				string rejectReason = "Invalid applicant (" + validResponseString +")";
				validResponseString.clear();
				RejectChannel(chanList[i].first,rejectReason);
				sqlUser* mgrUsr = getUserRecord(managerList[i].first);
				NoteAllAuthedClients(mgrUsr,"Your channel application of %s has been rejected with reason: %s", chanList[i].second.c_str(),rejectReason.c_str());
				logDebugMessage(rejectReason.c_str());
				logDebugMessage("Rejected channel %s",chanList[i].second.c_str());
			}// else logDebugMessage("VALID");
		} //end of if (chanList.size() > 0)
	}
	//Now we iterate through all the supporters, and looking for invalidity
	//And we don't forget that one supporter might be supporting for multiple channels
	theQuery.str("");
	theQuery	<< "SELECT user_id, user_name,channel_id,channels.name FROM supporters,users,channels "
				<< "WHERE channels.id = supporters.channel_id AND users.id = supporters.user_id"
				<< ends;
	if (!SQLDb->Exec(theQuery, true))
	{
		logDebugMessage("Error on Judge.validSuppsQuery");
	#ifdef LOG_SQL
		//elog << "sqlQuery> " << theQuery.str().c_str() << endl;
		elog 	<< "Judge.validSuppsQuery> SQL Error: "
	     		<< SQLDb->ErrorMessage()
	     		<< endl ;
	#endif
		return;
	} else if (SQLDb->Tuples() != 0)
	{
		logDebugMessage("Checking all supporters validity ...");
		//logDebugMessage("Found %i supporters,",SQLDb->Tuples());
		unsigned int suppUserId, chanId;
		string suppUserName,chanName;
		pendingChanListType suppChanList, suppList;
		for (unsigned int i = 0 ; i < SQLDb->Tuples(); i++)
		{
			suppUserId = atoi(SQLDb->GetValue(i,0));
			suppUserName = SQLDb->GetValue(i,1);
			chanId = atoi(SQLDb->GetValue(i,2));
			chanName = SQLDb->GetValue(i,3);
			suppChanList.push_back(pendingChanListType::value_type(chanId,chanName));
			suppList.push_back(pendingChanListType::value_type(suppUserId,suppUserName));
			//logDebugMessage("Processed %i",i);
		}
		for (size_t i=0; i<suppList.size(); i++)
		{
			suppUserName = suppList[i].second;
			chanName = suppChanList[i].second;
			chanId = suppChanList[i].first;
			string mngrUsr(""); // = managerList.find(chanId);
			//logDebugMessage("Checking validity of suporter %s supporting channel %s",suppUserName.c_str(),chanName.c_str());
			if (!isValidUser(suppUserName))
			{
				string rejectReason = "Invalid supporter " + suppUserName + " (" + validResponseString +")";
				validResponseString.clear();
				logDebugMessage(rejectReason.c_str());
				for (unsigned int j=0; j < chanList.size(); j++ )
				{
					if (chanList[j].first == chanId)
					{
						mngrUsr = managerList[j].second;
						//logDebugMessage("FOUND MANAGER %s",mngrUsr.c_str());
						break;
					}
				}
				if (mngrUsr.empty())
				{
					//logDebugMessage("Not Found the Manager");
					//logDebugMessage("Because the channel is or Rejected Or Accepted !");
					return;
				}
				sqlUser* mgrUsr = getUserRecord(mngrUsr);
				NoteAllAuthedClients(mgrUsr,rejectReason.c_str());
				if (currentChanId != chanId)
				{
					RejectChannel(chanId,rejectReason);
					logDebugMessage("Rejected channel %s",chanName.c_str());
					NoteAllAuthedClients(mgrUsr,"Your channel application of %s has been rejected with reason: %s", chanName.c_str(),rejectReason.c_str());
				}
				currentChanId = chanId;
			} //isValidUser
		} //for
		suppChanList.clear();
		suppList.clear();
	} //Tuples != 0
	else if (!chanList.empty()) logDebugMessage("WARNING: Not found any supporter!");
	chanList.clear();
	managerList.clear();
	return;
}

void cservice::checkIncomings()
{
	string chanName;
	unsigned int chanId;
	//int mngrId;
	string mngrUser;
	string reason = "Failed supporters confirmation.";
	std::vector<std::pair<std::pair<int,string>, string> > rejectList;
	unsigned int pendingTime = SupportDays * JudgeDaySeconds; //currentTime()
	stringstream theQuery;
//	theQuery	<< "SELECT channels.name,channels.id,manager_id,users.user_name FROM channels,pending,users WHERE channels.id = pending.channel_id "
	theQuery	<< "SELECT channels.name,channels.id,users.user_name FROM channels,pending,users WHERE channels.id = pending.channel_id "
				<< "AND pending.status = 0 AND (pending.created_ts + "
				<< pendingTime
				<< ") < date_part('epoch', CURRENT_TIMESTAMP)::int "
				<< " AND users.id = manager_id"
				<< ends;
	if (!SQLDb->Exec(theQuery, true))
	{
		logDebugMessage("Error on Judge.IncomingQuery");
	#ifdef LOG_SQL
		//elog << "sqlQuery> " << theQuery.str().c_str() << endl;
		elog 	<< "Judge.IncomingQuery> SQL Error: "
	     		<< SQLDb->ErrorMessage()
	     		<< endl ;
	#endif
		return;
	} else if (SQLDb->Tuples() != 0)
	{
		logDebugMessage("List of expiring Incoming applications:");
		for (unsigned int i = 0 ; i < SQLDb->Tuples(); i++)
		{
			chanName = SQLDb->GetValue(i,0);
			chanId = atoi(SQLDb->GetValue(i,1));
			mngrUser = SQLDb->GetValue(i,2);
			rejectList.push_back(std::make_pair(std::make_pair(chanId,chanName),mngrUser));
			logDebugMessage("Rejected channel application %s with reason: %s",chanName.c_str(),reason.c_str());
		}
		if (!rejectList.empty())
		{
			for (unsigned int i=0; i<rejectList.size(); i++ )
			{
				sqlUser* mgrUsr = getUserRecord(rejectList[i].second);
				incompleteChanRegsType::iterator theApp = incompleteChanRegs.find(mgrUsr->getID());
				reason = "Failed supporters confirmation.";
				if (theApp != incompleteChanRegs.end())
				{
					delete(theApp->second);
					incompleteChanRegs.erase(theApp);
					reason = "Failed to enumerate all required paramaters for channel application";
				}
				RejectChannel(rejectList[i].first.first,reason);
				NoteAllAuthedClients(mgrUsr,"Your channel application of %s has been rejected with reason: %s", rejectList[i].first.second.c_str(),reason.c_str());
			}
			rejectList.clear();
		}
	}// else logDebugMessage("No Expiring Incoming Applications Found!");
	return;
}

void cservice::checkTrafficPass()
{
	if (pendingChannelList.size() == 0)
	{
		//logDebugMessage("No Expiring pending channel application found");
		return;
	}

	pendingChannelListType::iterator ptr = pendingChannelList.begin();
	while (ptr != pendingChannelList.end())
	{
		sqlPendingChannel* pendingChan = ptr->second;

		//Let's begin with optimism
		bool JoinsPass = true;
		bool uniqueJoinsPass = true;
		bool minSupportersPass = true;
		bool minSupportersJoinPass = true;
		string rejectReason = "";

		unsigned int trafficTime = MaxDays * JudgeDaySeconds;
		time_t elapsedDays = pendingChan->checkStart + time_t(trafficTime);
		if (elapsedDays < currentTime())
		{
			//Satisfied or not surely we can stop listening channel events
			MyUplink->UnRegisterChannelEvent(ptr->first, this);
			//Check if the channel was visited at least at ONCE by the required MinSupporters count supporters
			unsigned int actualMinSupporters = pendingChan->uniqueSupporterList.size();
			if (actualMinSupporters < MinSupporters)
			{
				//rejectReason = "Insuffucient number of supporters that visited the channel";
				minSupportersPass = false;
				logDebugMessage("Insufficient number of supporters that visited the pending channel %s (%i/%i)",ptr->first.c_str(),actualMinSupporters,MinSupporters);
			}
			else // <- if yes, we check if one of the supporters has an insufficient MinSupportersJoin joincount
			{
				sqlPendingChannel::trafficListType::iterator ptr2 = pendingChan->uniqueSupporterList.begin();
				while (ptr2 != pendingChan->uniqueSupporterList.end())
				{
					if (ptr2->second->join_count < MinSupportersJoin)
					{
						unsigned int supporterUserId = ptr2->second->ip_number;
						unsigned int actualMinSupportersJoin = ptr2->second->join_count;
						minSupportersJoinPass = false;
						logDebugMessage("Insufficient supporter joincount of supporter userid %i (%i/%i) on pending channel %s",supporterUserId,actualMinSupportersJoin,MinSupportersJoin,ptr->first.c_str());
					}
					++ptr2;
				} //end while
			} //end else

			//Next, we check after general channel activity:
			//(total) join_count, and the unique_join_count
			if (pendingChan->unique_join_count < UniqueJoins)
			{
				uniqueJoinsPass = false;
				logDebugMessage("Insufficient number of IP's (%i/%i) that visited the pending channel %s",pendingChan->unique_join_count,UniqueJoins,ptr->first.c_str());
			}
			if (pendingChan->join_count < Joins)
			{
				JoinsPass = false;
				logDebugMessage("Insufficient number of joincounts (%i/%i) that visited the pending channel %s",pendingChan->join_count,Joins,ptr->first.c_str());
			}
			//We all passed, so we move the channel to the next Notification phase
			if ((JoinsPass) && (uniqueJoinsPass) && (minSupportersPass) && (minSupportersJoinPass))
			{
				stringstream theQuery;
				theQuery 	<< "UPDATE pending SET status = '2',"
							<< "check_start_ts = date_part('epoch', CURRENT_TIMESTAMP)::int,"
							<< "last_updated = date_part('epoch', CURRENT_TIMESTAMP)::int "
							<< "WHERE channel_id = " << pendingChan->channel_id
							<< ends;
				#ifdef LOG_SQL
					elog	<< "cservice::checkTrafficPass.moveToState2Notification> "
							<< theQuery.str().c_str()
							<< endl;
				#endif
				if( !SQLDb->Exec(theQuery, true ) )
				//if( PGRES_TUPLES_OK == status )
				logDebugMessage("Error on update pending trafficCheck -> notification");
				else
				logDebugMessage("Channel %s has passed traffic checking, successfully moved to Notification stage",ptr->first.c_str());
			}
		} //(elapsedDays < currentTime())
		if ((!uniqueJoinsPass) || (!JoinsPass))
			rejectReason = "Insufficient channel activity";
		if ((!minSupportersPass) || (!minSupportersJoinPass))
			rejectReason = "Insufficient suporter activity";
		if (!rejectReason.empty())
		{
			RejectChannel(pendingChan->channel_id,rejectReason);
			logDebugMessage("Rejecting channel %s with reason: %s",ptr->first.c_str(),rejectReason.c_str());
			ptr->second = NULL;
			delete(pendingChan);
			pendingChannelList.erase(ptr);
		}
		++ptr;
	} /* while() */
}

void cservice::checkObjections()
{
	if (!RewievOnObject) return;
	std::vector<std::pair<std::pair<int,string>, string> > objectList;
	unsigned int notifTime = NotifyDays * JudgeDaySeconds;
	int actualChan = 0;
    stringstream theQuery;
    theQuery	<< "SELECT channels.name,channels.id,users.user_name FROM channels,pending,users,objections "
    			<< "WHERE channels.id = pending.channel_id "
    			<< "AND pending.status = 2 AND (pending.check_start_ts + "
    			<< notifTime
    			<< ") < date_part('epoch', CURRENT_TIMESTAMP)::int "
    			<< " AND users.id = manager_id"
    			<< " AND objections.channel_id = pending.channel_id"
    			//<< " LIMIT 1"
    			<< ends;

    if (!SQLDb->Exec(theQuery, true))
    {
            logDebugMessage("Error on Judge.ObjectionkQuery");
    #ifdef LOG_SQL
            //elog << "sqlQuery> " << theQuery.str().c_str() << endl;
            elog    << "Judge.ObjectionkQuery> SQL Error: "
                    << SQLDb->ErrorMessage()
                    << endl ;
    #endif
            return;
    } else if (SQLDb->Tuples() != 0)
    {
            logDebugMessage("List of applications moved to Ready to rewiev:");
            for (unsigned int i = 0 ; i < SQLDb->Tuples(); i++)
            {
                    string chanName = SQLDb->GetValue(i,0);
                    int chanId = atoi(SQLDb->GetValue(i,1));
                    string mngrUser = SQLDb->GetValue(i,2);
                    if (chanId != actualChan) //more than one objection per channel lead to multiple db hit, so we need to limit to one result
                    {
                    	actualChan = chanId;
                        objectList.push_back(std::make_pair(std::make_pair(chanId,chanName),mngrUser));
                        logDebugMessage(chanName.c_str());
                    }
            }
    }
    if (!objectList.empty())
    	for (unsigned int i=0; i<objectList.size(); i++ )
    	{
    		RewievChannel(objectList[i].first.first);
            logDebugMessage("Channel %s has been moved to Ready to review",objectList[i].first.second.c_str());
            sqlUser* mgrUsr = getUserRecord(objectList[i].second.c_str());
    		NoteAllAuthedClients(mgrUsr,"Your channel application of %s is now at state Ready to rewiev", objectList[i].first.second.c_str());
    	}
    objectList.clear();
return;
}

void cservice::checkAccepts()
{
	std::vector<std::pair<std::pair<int,string>, string> > acceptList;
	unsigned int notifTime = NotifyDays * JudgeDaySeconds;
	stringstream theQuery;
	theQuery	<< "SELECT channels.name,channels.id,users.user_name FROM channels,pending,users "
	   			<< "WHERE channels.id = pending.channel_id "
	   			<< "AND pending.status = 2 AND (pending.check_start_ts + "
	   			<< notifTime
	   			<< ") < date_part('epoch', CURRENT_TIMESTAMP)::int "
	   			<< " AND users.id = manager_id"
	   			<< ends;

	if (!SQLDb->Exec(theQuery, true))
	{
		logDebugMessage("Error on Judge.AcceptQuery");
	    #ifdef LOG_SQL
	            //elog << "sqlQuery> " << theQuery.str().c_str() << endl;
	            elog    << "Judge.AcceptQuery> SQL Error: "
	                    << SQLDb->ErrorMessage()
	                    << endl ;
	    #endif
	            return;
	} else if (SQLDb->Tuples() != 0)
	{
		logDebugMessage("List of applications moved to ACCEPT:");
		for (unsigned int i = 0 ; i < SQLDb->Tuples(); i++)
		{
			string chanName = SQLDb->GetValue(i,0);
			int chanId = atoi(SQLDb->GetValue(i,1));
			string mngrUser = SQLDb->GetValue(i,2);
			acceptList.push_back(std::make_pair(std::make_pair(chanId,chanName),mngrUser));
			logDebugMessage(chanName.c_str());
		}
	}
	if (!acceptList.empty())
		for (unsigned int i=0; i<acceptList.size(); i++ )
	    {
	    	AcceptChannel(acceptList[i].first.first,"ACCEPTED");
	    	sqlUser* mgrUsr = getUserRecord(acceptList[i].second.c_str());
	    	if (sqlRegisterChannel(getInstance(), mgrUsr, acceptList[i].first.second.c_str()))
	    	{
	            logAdminMessage("%s (The Judge) has registered %s to %s", getInstance()->getNickName().c_str(),
	            		acceptList[i].first.second.c_str(), mgrUsr->getUserName().c_str());
	            NoteAllAuthedClients(mgrUsr,"Your channel application of %s is Accepted", acceptList[i].first.second.c_str());
	    	} else
	    		logDebugMessage("(The Judge) FAILED to sqlRegisterChannel");
	    }
	    acceptList.clear();
}

/**
 * After a time we clenup any "never" reviewed channel
 */
void cservice::checkRewievs()
{
	if (!RewievsExpireTime) return;
	std::vector<std::pair<std::pair<int,string>, string> > rewievList;
	unsigned int rewievTime = RewievsExpireTime * JudgeDaySeconds;
	stringstream theQuery;
	theQuery	<< "SELECT channels.name,channels.id,users.user_name FROM channels,pending,users "
	   			<< "WHERE channels.id = pending.channel_id "
	   			<< "AND pending.status = 8 AND (pending.check_start_ts + "
	   			<< rewievTime
	   			<< ") < date_part('epoch', CURRENT_TIMESTAMP)::int "
	   			<< " AND users.id = manager_id"
	   			<< ends;

	if (!SQLDb->Exec(theQuery, true))
	{
		logDebugMessage("Error on Judge.rewievQuery");
	    #ifdef LOG_SQL
	            //elog << "sqlQuery> " << theQuery.str().c_str() << endl;
	            elog    << "Judge.rewievQuery> SQL Error: "
	                    << SQLDb->ErrorMessage()
	                    << endl ;
	    #endif
	            return;
	} else if (SQLDb->Tuples() != 0)
	{
		logDebugMessage("List of Wiped applications:");
		for (unsigned int i = 0 ; i < SQLDb->Tuples(); i++)
		{
			string chanName = SQLDb->GetValue(i,0);
			int chanId = atoi(SQLDb->GetValue(i,1));
			string mngrUser = SQLDb->GetValue(i,3);
			rewievList.push_back(std::make_pair(std::make_pair(chanId,chanName),mngrUser));
			logDebugMessage(chanName.c_str());
		}
	}
	if (!rewievList.empty())
		for (unsigned int i=0; i<rewievList.size(); i++ )
	    {
	    	if (wipeChannel(rewievList[i].first.first))
	    		logDebugMessage("Expired and wiped Ready to Review channel application %s",rewievList[i].first.second.c_str());
	    	 else
	    		logDebugMessage("(The Judge) Failed to wipeChannel(ready to Review) %s",rewievList[i].first.second.c_str());
	    }
	    rewievList.clear();
}

// After a time, we cleanup de databes from old application data's: pending channels, supporters, etc
//But this is valuable *only* for channels Accepted OR Rejected !!!
void cservice::cleanUpPendings()
{
	//If PendingsExpireTime == 0 than feature is disabled
	if (!PendingsExpireTime) return;
	unsigned int expireTime = PendingsExpireTime * JudgeDaySeconds;
	stringstream theQuery;
		theQuery	<< "SELECT channel_id FROM pending "
					<< "WHERE (pending.status = 3 OR pending.status = 9 OR pending.status = 4) "
					<< "AND (pending.last_updated + "
					<< expireTime
					<< ") < date_part('epoch', CURRENT_TIMESTAMP)::int "
					<< ends;
		if (!SQLDb->Exec(theQuery, true))
		{
			logDebugMessage("Error on Judge.checkPendingCleanupsQuery");
		#ifdef LOG_SQL
			//elog << "sqlQuery> " << theQuery.str().c_str() << endl;
			elog 	<< "Judge.checkPendingCleanupsQuery> SQL Error: "
		     		<< SQLDb->ErrorMessage()
		     		<< endl ;
		#endif
			return;
		} else if (SQLDb->Tuples() != 0)
		{
			logDebugMessage("Found %i channel(s) to pendingCleanup",SQLDb->Tuples());
			vector <unsigned int> wipeChanList;
			for (unsigned int i = 0 ; i < SQLDb->Tuples(); i++)
				wipeChanList.push_back(atoi(SQLDb->GetValue(i,0)));
			for (unsigned int i = 0; i < wipeChanList.size(); i++)
			{
				wipeChannel(wipeChanList[i]);
				logDebugMessage("Wiped channel %i",wipeChanList[i]);
			}
			wipeChanList.clear();
		} //else logDebugMessage("No pendingCleanup found!");
	return;
}

void cservice::loadIncompleteChanRegs()
{
	// This is a 2 phase process ...
	// 1. We're iterate over all channels in pending table, checking after 0 status,
	// description, real name.
	// 2. Iterating all over the supporters table finding which chanId's has supporters
	// If the found supporters count == RequiredSupporters we skip
	stringstream theQuery;
	theQuery 	<< "SELECT channel_id,channels.name,manager_id,managername,pending.description FROM pending,channels "
				<< "WHERE channels.id=pending.channel_id AND status = 0"
				<< ends;

	#ifdef LOG_SQL
		elog	<< "loadIncomplChanRegs> "
				<< theQuery.str().c_str()
				<< endl;
	#endif

	if( !SQLDb->Exec(theQuery ) )
	//if( PGRES_COMMAND_OK != status )
	{
		elog	<< "loadIncomplChanRegs> Something went wrong: "
				<< SQLDb->ErrorMessage()
				<< endl;
		logDebugMessage("loadIncomplChanRegs ERROR: %s", SQLDb->ErrorMessage().c_str());
		return;
	}
	if (SQLDb->Tuples() == 0) return;

	for (unsigned int i = 0; i < SQLDb->Tuples(); i++)
	{
		sqlIncompleteChannel* newApp = new (std::nothrow) sqlIncompleteChannel(SQLDb);
		assert(newApp != 0);
		newApp->chanId = atoi(SQLDb->GetValue(i,0));
		//elog << "chanId=" << newApp->chanId << endl;
		newApp->chanName = SQLDb->GetValue(i,1);
		//elog << "chanName=" << newApp->chanName << endl;
		unsigned int mngrId = atoi(SQLDb->GetValue(i,2));
		//elog << "mngrId=" << mngrId << endl;
		newApp->RealName = SQLDb->GetValue(i,3);
		//elog << "RealName=" << newApp->RealName << endl;
		newApp->Description = SQLDb->GetValue(i,4);
		//elog << "Description=" << newApp->Description << endl;

		incompleteChanRegs.insert(cservice::incompleteChanRegsType::value_type(mngrId,newApp));
	}

	// 2nd part
	/*
	incompleteChanRegsType::iterator ptr = incompleteChanRegs.begin();
	while (ptr != incompleteChanRegs.end())
	{
		sqlIncompleteChannel* theApp = (ptr)->second;

		if (theApp->Description.empty())
		{	//Do not do anything if not completed until description
			++ptr;
			continue;
		}
		theQuery.str("");
		theQuery 	<< "SELECT user_id FROM supporters WHERE channel_id = "
					<< theApp->chanId
					<< " AND support = '?'"
					<< ends;

		#ifdef LOG_SQL
			elog	<< "loadIncomplChanRegs.loadSupporters> "
					<< theQuery.str().c_str()
					<< endl;
		#endif

		if( !SQLDb->Exec(theQuery ) )
		//if( PGRES_COMMAND_OK != status )
		{
			elog	<< "loadIncomplChanRegs.loadSupporters> Something went wrong: "
					<< SQLDb->ErrorMessage()
					<< endl;
			logDebugMessage("loadIncomplChanRegs.loadSupporters ERROR: %s", SQLDb->ErrorMessage().c_str());
			return;
		}
		unsigned int rCount = SQLDb->Tuples();
		if ((rCount > 0) && (rCount < RequiredSupporters))
		{
			for (unsigned int i=0; i < rCount; ++i)
			{
				unsigned int suppId = atoi(SQLDb->GetValue(i,0));
				theApp->supps.insert(sqlIncompleteChannel::supporterListType::value_type(suppId,NULL));
			}
			for (sqlIncompleteChannel::supporterListType::iterator itr = theApp->supps.begin();
					itr != theApp->supps.end(); ++itr)
				itr->second = getUserRecord(itr->first);
		}
		++ptr;
	}
	*/
	return;
}

string cservice::userStatusFlags( const string& theUser )
{

string flagString = "";

sqlUserHashType::iterator ptr = sqlUserCache.find(theUser);

if(ptr != sqlUserCache.end())
	{
	flagString = 'L';
	/* (depreciated)
	 * if(tmpUser->getFlag(sqlUser::F_LOGGEDIN)) flagString += 'U';
	 */
	}

return flagString;
}

bool cservice::validUserMask(const string& userMask) const
{

// Check that a '!' exists, and that the nickname
// is no more than 15 characters //50 ;-)
StringTokenizer st1( userMask, '!' ) ;
if( (st1.size() != 2) || (st1[ 0 ].size() > 50) )
	{
	return false ;
	}

// Check that a '@' exists and that the username is
// no more than 12 characters //25 ;-)
StringTokenizer st2( st1[ 1 ], '@' ) ;

if( (st2.size() != 2) || (st2[ 0 ].size() > 25) )
	{
	return false ;
	}

// Be sure that the hostname is no more than 128 characters
if( st2[ 1 ].size() > 128 )
	{
	return false ;
	}

// Tests have passed
return true ;
}

void cservice::OnChannelMode( Channel* theChan, ChannelUser* sourceUser,
		const xServer::modeVectorType& modeVector )
{
#ifdef CATCH_OPMODES
	sqlChannel* reggedChan = getChannelRecord(theChan->getName());
	if (!reggedChan) return;
	if (!reggedChan->getInChan())
	{
		//Do not monitor channels i am not in
		return;
	}
	string modeString, antimode;
	if (sourceUser == NULL) return; //mode made by a server!
	if (sourceUser->getClient()->getMode(iClient::MODE_SERVICES)) return;
	string chanName = string_lower(theChan->getName());
	bool currentPolarity = true;

	if (currentPolarity) modeString = "+"; else modeString = "-";

	for (xServer::modeVectorType::const_iterator mItr = modeVector.begin() ;
		mItr != modeVector.end() ; ++mItr)
	{
		bool polarity = (*mItr).first;
		if (polarity != currentPolarity)
		{
			currentPolarity = polarity;
			if (modeString.size() < 2) modeString = "";
			if (polarity) modeString += "+"; else modeString += "-";
		}
		if ((*mItr).second == Channel::MODE_M) modeString += "m";
		if ((*mItr).second == Channel::MODE_I) modeString += "i";
		if ((*mItr).second == Channel::MODE_R) modeString += "r";
		//if ((*mItr).second == Channel::MODE_K) modeString += "k"; //nope, it's with parameters, handled by OnChannelModeK
		//if ((*mItr).second == Channel::MODE_L) modeString += "l"; //nope, it's with parameters, handled by OnChannelModeL
		if ((*mItr).second == Channel::MODE_S) modeString += "s";
		if ((*mItr).second == Channel::MODE_P) modeString += "p";
		if ((*mItr).second == Channel::MODE_T) modeString += "t";
		if ((*mItr).second == Channel::MODE_N) modeString += "n";
		if ((*mItr).second == Channel::MODE_D) modeString += "D";
	} // for()
	if (sourceUser->isMadeOpMode())
	{
		string logModeString = theChan->getName() + " " + modeString;
		logAdminMessage("*** OPMODE by %s MODE %s",sourceUser->getClient()->getNickName().c_str(),logModeString.c_str());
		logModeString = "OPMODE " + logModeString;
		sqlLogCommand(sourceUser->getClient(),logModeString.c_str());
	}
#endif
	return;
}

void cservice::OnChannelModeV( Channel* theChan, ChannelUser* theChanUser,
	const xServer::voiceVectorType& theTargets)
{
	sqlChannel* reggedChan = getChannelRecord(theChan->getName());
	if(!reggedChan)
		{
	//	elog	<< "cservice::OnChannelModeV> WARNING, unable to "
	//		<< "locate channel record"
	//		<< " for registered channel event: "
	//		<< theChan->getName()
	//		<< endl;
		return;
		}

	if(!reggedChan->getInChan())
		{
		//Do not monitor channels i am not in
		return;
		}
	// List of clients to devoice.
	vector< iClient* > deVoiceList;

	bool currentPolarity = true;
	string logModeString;
	string logModeClientList;

	if (currentPolarity) logModeString = "+"; else logModeString = "-";

	for( xServer::voiceVectorType::const_iterator ptr = theTargets.begin() ;
		ptr != theTargets.end() ; ++ptr )
		{
		ChannelUser* tmpUser = ptr->second;
		bool polarity = ptr->first;

		if (polarity != currentPolarity)
		{
			currentPolarity = polarity;
			if (logModeString.size() < 2) logModeString = "";
			if (polarity) logModeString += "+"; else logModeString += "-";
		}
		logModeString += "v";
		logModeClientList += " " + tmpUser->getClient()->getNickName();

		if (polarity)
			{
			// If somebody is being voiced.
			// If the channel is NOVOICE, devoice everyone who tries to get voiced!
			if (reggedChan->getFlag(sqlChannel::F_NOVOICE))
				{
				if ( !tmpUser->getClient()->getMode(iClient::MODE_SERVICES) )
					deVoiceList.push_back(tmpUser->getClient());
				}
			}
		} // for()

	/*
	 *  Send notices and perform the devoice's. (But don't deop anything thats +k).
	 */

	if( !deVoiceList.empty() )
	{
		if ((theChanUser) && (reggedChan->getFlag(sqlChannel::F_NOVOICE)) )
		{
			Notice( theChanUser->getClient(),
				"The NOVOICE flag is set on %s",
				reggedChan->getName().c_str());
		}
		if (theChanUser->getMode(ChannelUser::MODE_O))
			if (!theChanUser->getClient()->getMode(iClient::MODE_SERVICES))
				DeOp(theChan,theChanUser->getClient());
		DeVoice(theChan, deVoiceList);
	}
#ifdef CATCH_OPMODES
	if (theChanUser && theChanUser->isMadeOpMode())
	{
		logModeString = theChan->getName() + " " + logModeString + logModeClientList;
		logAdminMessage("*** OPMODE by %s MODE %s",theChanUser->getClient()->getNickName().c_str(),logModeString.c_str());
		//For now if the client isn't authed will NOT log ...
		logModeString = "OPMODE " + logModeString;
		sqlLogCommand(theChanUser->getClient(),logModeString.c_str());
	}
#endif
	return;
}

void cservice::OnChannelModeO( Channel* theChan, ChannelUser* theChanUser,
	const xServer::opVectorType& theTargets)
{
/*
 * There are a number of things to do when we recieve a mode O for
 * a channel/targets.
 * Firstly, we check the status of certain channel flags is it set
 * NOOP? is it set STRICTOP?
 * We will only ever recieve events for channels that are registered.
 */

sqlChannel* reggedChan = getChannelRecord(theChan->getName());
if(!reggedChan)
	{
//	elog	<< "cservice::OnChannelModeO> WARNING, unable to "
//		<< "locate channel record"
//		<< " for registered channel event: "
//		<< theChan->getName()
//		<< endl;
	return;
	}

if(!reggedChan->getInChan()) 
	{
	//Do not monitor channels i am not in
	return;
	}

// List of clients to deop.
vector< iClient* > deopList;

// If we find a situation where we need to deop the person who has
// performed the mode, do so.
bool sourceHasBeenBad = false;

int deopCounter = 0;
bool currentPolarity = true;
string logModeString;
string logModeClientList;

if (currentPolarity) logModeString = "+"; else logModeString = "-";

for( xServer::opVectorType::const_iterator ptr = theTargets.begin() ;
	ptr != theTargets.end() ; ++ptr )
	{
	ChannelUser* tmpUser = ptr->second;
	bool polarity = ptr->first;

	if (polarity != currentPolarity)
	{
		currentPolarity = polarity;
		if (logModeString.size() < 2) logModeString = "";
		if (polarity) logModeString += "+"; else logModeString += "-";
	}
	logModeString += "o";
	logModeClientList += " " + tmpUser->getClient()->getNickName();

	if (polarity)
		{
		// If somebody is being opped.

		// If the channel is NOOP, deop everyone who tries to
		// get opped!
		if (reggedChan->getFlag(sqlChannel::F_NOOP))
			{
			if ( !tmpUser->getClient()->getMode(iClient::MODE_SERVICES) )
			deopList.push_back(tmpUser->getClient());
			}

		sqlUser* authUser = isAuthed(tmpUser->getClient(), false);

		// Has the target user's account been suspended?
		if (authUser && authUser->getFlag(sqlUser::F_GLOBAL_SUSPEND))
		{
			if (theChanUser) Notice(theChanUser->getClient(), "The user %s (%s) has been suspended by a CService Administrator.",
				authUser->getUserName().c_str(), tmpUser->getClient()->getNickName().c_str());
			deopList.push_back(tmpUser->getClient());
			sourceHasBeenBad = true;
		}

		// If the channel is STRICTOP, deop everyone who isn't
		// authenticated or and doesn't have access on the
		// channel.

		if (reggedChan->getFlag(sqlChannel::F_STRICTOP))
			{
			if (!authUser)
				{
				// Not authed, deop.
				if ( !tmpUser->getClient()->getMode(iClient::MODE_SERVICES) )
				deopList.push_back(tmpUser->getClient());
				sourceHasBeenBad = true;
				// Authed but doesn't have access... deop.
				}
			else if (!(getEffectiveAccessLevel(authUser,reggedChan, false) >= level::op))
				{
				if ( !tmpUser->getClient()->getMode(iClient::MODE_SERVICES) )
				deopList.push_back(tmpUser->getClient());
				sourceHasBeenBad = true;
				}
			}

		/*
		 *  The 'Fun' Part. Scan through channel bans to see if this hostmask
		 *  is 'banned' at 75 or below.
		 */

		sqlBan* theBan = isBannedOnChan(reggedChan, tmpUser->getClient());
		if( theBan && (theBan->getLevel() <= 75) )
			{
				if ( !tmpUser->getClient()->getMode(iClient::MODE_SERVICES) )
					{
					deopList.push_back(tmpUser->getClient());
					sourceHasBeenBad = true;

					/* Tell the person bein op'd that they can't */
					Notice(tmpUser->getClient(),
						"You are not allowed to be opped on %s",
						reggedChan->getName().c_str());

					/* Tell the person doing the op'ing this is bad */
					if (theChanUser)
						{
						Notice(theChanUser->getClient(),
							"%s isn't allowed to be opped on %s",
							tmpUser->getClient()->getNickName().c_str(),
							reggedChan->getName().c_str());
						}
					}
			}

		} // if()
	else
		{
		/* Somebody is being deopped? */
		deopCounter++;

		/* What if someone deop'd us?! */
		if (tmpUser->getClient() == me)
			{
			logAdminMessage("I've been DEOPped on %s!",
				reggedChan->getName().c_str());
			xClient::Op(theChan, me);
			}
		}
	} // for()

/*
 *  Send notices and perform the deop's. (But don't deop anything thats +k).
 */

if (theChanUser && sourceHasBeenBad && !theChanUser->getClient()->getMode(iClient::MODE_SERVICES))
	deopList.push_back(theChanUser->getClient());

if( !deopList.empty() )
	{
	if ((theChanUser) && (reggedChan->getFlag(sqlChannel::F_NOOP)) )
		{
		Notice( theChanUser->getClient(),
			"The NOOP flag is set on %s",
			reggedChan->getName().c_str());
		}

	if ((theChanUser) && (reggedChan->getFlag(sqlChannel::F_STRICTOP)) )
		{
		Notice( theChanUser->getClient(),
			"The STRICTOP flag is set on %s",
			reggedChan->getName().c_str());
		}

	DeOp(theChan, deopList);
	}

/*
 *  Have more than 'maxdeoppro' been deopped?
 *  If so, suspend and kick 'em. (Unless they're +k of course ;)
 */

if ((theChanUser) && (deopCounter >= reggedChan->getMassDeopPro())
	&& (reggedChan->getMassDeopPro() > 0) && !theChanUser->getClient()->getMode(iClient::MODE_SERVICES))
	{
		doInternalBanAndKick(reggedChan, theChanUser->getClient(),
			"### MASSDEOPPRO TRIGGERED! ###");
	}
#ifdef CATCH_OPMODES
if (theChanUser && theChanUser->isMadeOpMode())
{
	logModeString = theChan->getName() + " " + logModeString + logModeClientList;
	logAdminMessage("*** OPMODE by %s MODE %s",theChanUser->getClient()->getNickName().c_str(),logModeString.c_str());
	logModeString = "OPMODE " + logModeString;
	sqlLogCommand(theChanUser->getClient(),logModeString.c_str());
}
#endif

}

void cservice::OnChannelModeL( Channel* theChan, bool polarity,
		ChannelUser* theChanUser, const unsigned int& lim)
{
#ifdef CATCH_OPMODES
	sqlChannel* reggedChan = getChannelRecord(theChan->getName());
	if (!reggedChan) return;
	if (!reggedChan->getInChan())
	{
		//Do not monitor channels i am not in
		return;
	}
	string modeString;
	if (theChanUser == NULL) return; //mode made by a server!
	if (theChanUser->getClient()->getMode(iClient::MODE_SERVICES)) return;
	if (theChanUser->isMadeOpMode())
	{
		string logModeString;
		if (polarity) logModeString = "+l"; else logModeString = "-l";
		logModeString = theChan->getName() + " " + logModeString;
		if (lim > 0) logModeString = TokenStringsParams("%s %i",logModeString.c_str(),lim);
		logAdminMessage("*** OPMODE by %s MODE %s",theChanUser->getClient()->getNickName().c_str(),logModeString.c_str());
		logModeString = "OPMODE " + logModeString;
		sqlLogCommand(theChanUser->getClient(),logModeString.c_str());
	}
#endif
	return;
}

void cservice::OnChannelModeK( Channel* theChan, bool polarity,
			ChannelUser* theChanUser, const std::string& keyStr)
{
#ifdef CATCH_OPMODES
	sqlChannel* reggedChan = getChannelRecord(theChan->getName());
	if (!reggedChan) return;
	if (!reggedChan->getInChan())
	{
		//Do not monitor channels i am not in
		return;
	}
	string modeString;
	if (theChanUser == NULL) return; //mode made by a server!
	if (theChanUser->getClient()->getMode(iClient::MODE_SERVICES)) return;
	if (theChanUser->isMadeOpMode())
	{
		string logModeString;
		if (polarity) logModeString = "+k "; else logModeString = "-k ";
		logModeString = theChan->getName() + " " + logModeString + keyStr;
		logAdminMessage("*** OPMODE by %s MODE %s",theChanUser->getClient()->getNickName().c_str(),logModeString.c_str());
		logModeString = "OPMODE " + logModeString;
		sqlLogCommand(theChanUser->getClient(),logModeString.c_str());
	}
#endif
	return;
}

void cservice::OnChannelModeB( Channel* theChan, ChannelUser* theChanUser,
		const xServer::banVectorType& addresses)
{
#ifdef CATCH_OPMODES
	sqlChannel* reggedChan = getChannelRecord(theChan->getName());
	if(!reggedChan)
		{
	//	elog	<< "cservice::OnChannelModeV> WARNING, unable to "
	//		<< "locate channel record"
	//		<< " for registered channel event: "
	//		<< theChan->getName()
	//		<< endl;
		return;
		}

	if(!reggedChan->getInChan())
		{
		//Do not monitor channels i am not in
		return;
		}

	bool currentPolarity = true;
	string logModeString;
	string logModeBaddresses;

	if (currentPolarity) logModeString = "+"; else logModeString = "-";

	for( xServer::banVectorType::const_iterator ptr = addresses.begin() ;
		ptr != addresses.end() ; ++ptr )
	{

		bool polarity = ptr->first;

		if (polarity != currentPolarity)
		{
			currentPolarity = polarity;
			if (logModeString.size() < 2) logModeString = "";
			if (polarity) logModeString += "+"; else logModeString += "-";
		}
		logModeString += "b";
		logModeBaddresses += " " + ptr->second;
	}
	if (theChanUser && theChanUser->isMadeOpMode())
	{
		logModeString = theChan->getName() + " " + logModeString + logModeBaddresses;
		logAdminMessage("*** OPMODE by %s MODE %s",theChanUser->getClient()->getNickName().c_str(),logModeString.c_str());
		logModeString = "OPMODE " + logModeString;
		sqlLogCommand(theChanUser->getClient(),logModeString.c_str());
	}
#endif
	return;
}

void cservice::OnEvent( const eventType& theEvent,
	void* data1, void* data2, void* data3, void* data4 )
{
switch( theEvent )
	{
	case EVT_XQUERY:
		{
		iServer* theServer = static_cast< iServer* >( data1 );
		const char* Routing = reinterpret_cast< char* >( data2 );
		const char* Message = reinterpret_cast< char* >( data3 );
		elog << "CSERVICE.CC: " << theServer->getName() << " " << Routing << " " << Message << endl;
		//As it is possible to run multiple GNUWorld clients on one server, first parameter should be a nickname.
		//If it ain't us, ignore the message, the message is probably meant for another client here.
		StringTokenizer st( Message ) ;
		if( st.size() < 2 )
        		{
			//No command or no nick supplied
        		break;
       			}
		if (st[0] == getNickName()) 
			{
			string Command = string_upper(st[1]);
			if (Command == "LOGIN")
				{
				doXQLogin(theServer, Routing, Message);		
				}
			}
		break;
		}
	case EVT_ACCOUNT:
		{
		iClient* tmpUser = static_cast< iClient* >( data1 ) ;
		networkData* tmpData = static_cast< networkData* >(tmpUser->getCustomData(this) ) ;
		/* Lookup this user account, if its not there.. trouble */
		sqlUser* theUser = getUserRecord(tmpUser->getAccount());
		if (theUser)
			{
			tmpData->currentUser = theUser;
			theUser->addAuthedClient(tmpUser);
			}
		break;
		}
	case EVT_BURST_ACK:
		{
#ifdef USING_NEFARIOUS
		iServer* theServer = static_cast< iServer* >( data1 );
		if ( theServer == MyUplink->getUplink() )
		{
			//If WE were in burst, it means need to iterate through all the servers, and validate all the nick
			// in short ALL the Nicknames, regardless of the server
			xNetwork::const_clientIterator cItr = Network->clients_begin();
			for ( ; cItr != Network->clients_end(); ++cItr)
			{
				iClient* tmpClient = cItr->second;
				validateNickName(tmpClient);
			}
		}
		else
		{
			//only the theServer's clients lists need to be validated
			xNetwork::const_clientIterator cItr = Network->clients_begin();
			for ( ; cItr != Network->clients_end(); ++cItr)
			{
				iClient* tmpClient = cItr->second;
				if (tmpClient->getIntYY() == theServer->getIntYY())
					validateNickName(tmpClient);
			}
		}
#endif
			break;
		}
	case EVT_QUIT:
	case EVT_KILL:
		{
		/*
		 *  We need to deauth this user if they're authed.
		 *  Also, clean up their custom data memory.
		 */

		iClient* tmpUser = (theEvent == EVT_QUIT) ?
			static_cast< iClient* >( data1 ) :
			static_cast< iClient* >( data2 ) ;

		sqlUser* tmpSqlUser = isAuthed(tmpUser, false);
		if (tmpSqlUser)
			{
			tmpSqlUser->removeAuthedClient(tmpUser);
			tmpSqlUser->removeFlag(sqlUser::F_LOGGEDIN);
#ifdef LOG_DEBUG
			elog	<< "cservice::OnEvent> Deauthenticated "
				<< "client: " << tmpUser << " from "
				<< "user: "
				<< tmpSqlUser->getUserName()
				<< endl;
#endif
			}

		// Clear up the custom data structure we appended to
		// this iClient.
		networkData* tmpData = static_cast< networkData* >(
			tmpUser->getCustomData(this) ) ;
		tmpUser->removeCustomData(this);

		delete(tmpData);
		customDataAlloc--;

		break ;
		} // case EVT_KILL/case EVT_QUIT

	case EVT_NICK:
		{
		/*
		 *  Give this new user a custom data structure!
		 */

		iClient* tmpUser =
			static_cast< iClient* >( data1 );
		networkData* newData = new (std::nothrow) networkData();
		assert( newData != 0 ) ;

		customDataAlloc++;

		// Not authed.. (yet!)
		newData->currentUser = NULL;
		tmpUser->setCustomData(this,
			static_cast< void* >( newData ) );

		/*
		 * Well.. they might be already auth'd.
		 * In which case, we'll receieve mode r and accountname for
		 * this person.
		 */
		if (tmpUser->isModeR())
			{
				/* Lookup this user account, if its not there.. trouble */
				sqlUser* theUser = getUserRecord(tmpUser->getAccount());
				if (theUser)
					{
					newData->currentUser = theUser;
					theUser->addAuthedClient(tmpUser);
					}
			}

#ifdef USING_NEFARIOUS
		iServer* tmpServer = Network->findServer(tmpUser->getIntYY());
		if ((!this->getUplink()->isBursting()) && (!tmpServer->isBursting()))
			validateNickName(tmpUser);
#endif
		break;
		} // case EVT_NICK
	case EVT_CHNICK:
	{
#ifdef USING_NEFARIOUS
		validateNickName(static_cast< iClient* >( data1 ));
#endif
		break;
	} // case EVT_CHNICK
	case EVT_GLINE:
		{
                if(!data1) //TODO: find out how we get this (Do we even ever get this?)
                        {
                        return ;
                        }
                Gline* newG = static_cast< Gline* >(data1);

                csGline* newGline = findGline(newG->getUserHost());
                if(!newGline)
                        {
                        newGline = new (std::nothrow) csGline(SQLDb);
                        assert (newGline != NULL);
                        iServer* serverAdded = Network->findServer(newG->getSetBy());
                        if(serverAdded)
                                newGline->setAddedBy(serverAdded->getName());
                        else
                                newGline->setAddedBy("Unknown");
                        }
                else
                        {
                        if(newGline->getLastUpdated() >= newG->getLastmod())
                                {
                                return ;
                                }

                        }
                newGline->setAddedOn(::time(0));
                newGline->setHost(newG->getUserHost());
                newGline->setReason(newG->getReason());
                newGline->setExpires(newG->getExpiration());
                        
		newGline->Insert();
                newGline->loadData(newGline->getHost());
                
		addGline(newGline);

                break;               
		} // case EVT_GLINE
	case EVT_REMGLINE:
		{
                if(!data1)
                        {
                        return ;
                        }

                Gline* newG = static_cast< Gline* >(data1);
                csGline* newGline = findGline(newG->getUserHost());
                if(newGline)
                        {
                        remGline(newGline);
                        newGline->Delete();
                        delete newGline;
                        }
                break;
		} // case EVT_REMGLINE
	} // switch()
xClient::OnEvent( theEvent,
	data1, data2, data3, data4 ) ;
}

/**
 * Support function to deVoice all voiced users on a channel.
 */
void cservice::deVoiceAllOnChan(Channel* theChan)
{
if( !theChan )
	{
	/* Don't try this on a null channel. */
	return;
	}

sqlChannel* reggedChan = getChannelRecord(theChan->getName());

if (!reggedChan)
	{
	return;
	}

if (!reggedChan->getInChan())
	{
	return;
	}

/* Check we're actually opped first.. */

ChannelUser* tmpBotUser = theChan->findUser(getInstance());
if( !tmpBotUser || !tmpBotUser->getMode(ChannelUser::MODE_O) )
	{
	return;
	}

vector< iClient* > deVoiceList;

for( Channel::const_userIterator ptr = theChan->userList_begin();
	ptr != theChan->userList_end() ; ++ptr )
	{
		if( ptr->second->getMode(ChannelUser::MODE_V))
		{
			deVoiceList.push_back( ptr->second->getClient());
		}
	}

if( !deVoiceList.empty() )
	{
	DeVoice(theChan, deVoiceList);
	}
}

/**
 * Support function to deop all opped users on a channel.
 */
void cservice::deopAllOnChan(Channel* theChan)
{
if( !theChan )
	{
	/* Don't try this on a null channel. */
	return;
	}

sqlChannel* reggedChan = getChannelRecord(theChan->getName());

if (!reggedChan)
	{
	return;
	}

if (!reggedChan->getInChan())
	{
	return;
	}

/* Check we're actually opped first.. */

ChannelUser* tmpBotUser = theChan->findUser(getInstance());
if( !tmpBotUser || !tmpBotUser->getMode(ChannelUser::MODE_O) )
	{
	return;
	}

vector< iClient* > deopList;

for( Channel::const_userIterator ptr = theChan->userList_begin();
	ptr != theChan->userList_end() ; ++ptr )
	{
	if( ptr->second->getMode(ChannelUser::MODE_O))
		{

		/* Don't deop +k things */
		if ( !ptr->second->getClient()->getMode(iClient::MODE_SERVICES) )
			deopList.push_back( ptr->second->getClient() );

		} // If opped.
	}

if( !deopList.empty() )
	{
	DeOp(theChan, deopList);
	}

}

size_t cservice::countChanOps(const Channel* theChan)
{
if( !theChan )
	{
	/* Don't try this on a null channel. */
	return 0;
	}

size_t chanOps = 0;

for( Channel::const_userIterator ptr = theChan->userList_begin();
	ptr != theChan->userList_end() ; ++ptr )
	{
	if( ptr->second->getMode(ChannelUser::MODE_O))
		{
		chanOps++;
		} // If opped.
	}

return chanOps;
}

/**
 * Support function to deop all non authed opped users on a channel.
 */
void cservice::deopAllUnAuthedOnChan(Channel* theChan)
{
// TODO: assert( theChan != 0 ) ;

if( !theChan )
	{
	/* Don't try this on a null channel. */
	return;
	}

sqlChannel* reggedChan = getChannelRecord(theChan->getName());

if( !reggedChan || !reggedChan->getInChan() )
	{
	return;
	}

/* Check we're actually opped first.. */

ChannelUser* tmpBotUser = theChan->findUser(getInstance());
if(! tmpBotUser || !tmpBotUser->getMode(ChannelUser::MODE_O))
	{
	return;
	}

vector< iClient* > deopList;

for( Channel::const_userIterator ptr = theChan->userList_begin();
	ptr != theChan->userList_end() ; ++ptr )
	{
	if( ptr->second->getMode(ChannelUser::MODE_O))
		{
		/* Are they authed? */
		sqlUser* authUser = isAuthed(ptr->second->getClient(), false);

		if (!authUser)
			{
			/* Not authed, deop this guy + Don't deop +k things */
			if ( !ptr->second->getClient()->getMode(iClient::MODE_SERVICES) )
				{
				deopList.push_back( ptr->second->getClient() );
				}

		/* Authed but no access? Tough. :) */
			}
		else if ((reggedChan) && !(getEffectiveAccessLevel(authUser, reggedChan, false) >= level::op))
			{
			/* Don't deop +k things */
			if ( !ptr->second->getClient()->getMode(iClient::MODE_SERVICES) )
				{
				deopList.push_back( ptr->second->getClient() );
				}
			}

		} // if opped.
	} // forall users in channel.

if( !deopList.empty() )
	{
	DeOp(theChan, deopList);
	}

}

void cservice::deopSuspendedOnChan(Channel* theChan, sqlUser* theUser)
{
	// TODO: assert( theChan != 0 ) ;

	if( !theChan )
		{
		/* Don't try this on a null channel. */
		return;
		}

	sqlChannel* reggedChan = getChannelRecord(theChan->getName());

	if( !reggedChan || !reggedChan->getInChan() )
		{
		return;
		}

	/* Check we're actually opped first.. */

	ChannelUser* tmpBotUser = theChan->findUser(getInstance());
	if(! tmpBotUser || !tmpBotUser->getMode(ChannelUser::MODE_O))
		{
		return;
		}

	vector< iClient* > deopList;
	if (!theUser->networkClientList.empty())
		for( sqlUser::networkClientListType::iterator cliPtr = theUser->networkClientList.begin() ;
			cliPtr != theUser->networkClientList.end() ; ++cliPtr )
		{
			iClient* tmpClient = (*cliPtr);
			ChannelUser* tmpUser = theChan->findUser(tmpClient);
			if ((tmpUser) && (tmpUser->getMode(ChannelUser::MODE_O) && (!tmpClient->getMode(iClient::MODE_SERVICES))))
				deopList.push_back(tmpClient);
		}

	if( !deopList.empty() )
		DeOp(theChan, deopList);
	return;
}

void cservice::DoTheRightThing(Channel* tmpChan)
{
		if (!tmpChan) return;

		ChannelUser* tmpBotUser = tmpChan->findUser(getInstance());
		if (!tmpBotUser || !tmpBotUser->getMode(ChannelUser::MODE_O))
			return;

		sqlChannel* theChan = getChannelRecord(tmpChan->getName());
		if(theChan->getFlag(sqlChannel::F_NOOP))
		{
			deopAllOnChan(tmpChan);
		}
		if(theChan->getFlag(sqlChannel::F_STRICTOP))
		{
			deopAllUnAuthedOnChan(tmpChan);
		}
		if(theChan->getFlag(sqlChannel::F_NOVOICE))
		{
			deVoiceAllOnChan(tmpChan);
		}
	return;
}

void cservice::DoTheRightThing()
{
	sqlChannelHashType::iterator ptr = sqlChannelCache.begin();
	while (ptr != sqlChannelCache.end())
	{
		sqlChannel* theChan = (ptr)->second;
		if (!theChan->getInChan())
		{
			++ptr;
			continue;
		}
		//* Check we're actually opped first..
		Channel* tmpChan = Network->findChannel(theChan->getName());
		//*** Alternatively ***
		//Channel* tmpChan = Network->findChannel(ptr->first);
		//sqlChannel* theChan = getChannelRecord(tmpChan->getName());
		if (!tmpChan)
		{
			++ptr;
			continue;
		}
		ChannelUser* tmpBotUser = tmpChan->findUser(getInstance());
		if( !tmpBotUser || !tmpBotUser->getMode(ChannelUser::MODE_O) )
		{
			++ptr;
			continue;
		}
		if(theChan->getFlag(sqlChannel::F_NOOP))
		{
			deopAllOnChan(tmpChan);
		}
		if(theChan->getFlag(sqlChannel::F_STRICTOP))
		{
			deopAllUnAuthedOnChan(tmpChan);
		}
		if(theChan->getFlag(sqlChannel::F_NOVOICE))
		{
			deVoiceAllOnChan(tmpChan);
		}
		++ptr;
	} //While
	return;
}
/**
 * Handler for registered channel events.
 * Performs a number of functions, autoop, autovoice, bankicks, etc.
 */
void cservice::OnChannelEvent( const channelEventType& whichEvent,
	Channel* theChan,
	void* data1, void* data2, void* data3, void* data4 )
{
iClient* theClient = 0 ;

switch( whichEvent )
	{
	case EVT_CREATE:
	case EVT_JOIN:
		{
		/*
		 * We should only ever recieve events for registered channels, or those
		 * that are 'pending'. If we do get past the pending check, there must be
		 * some kind of database inconsistancy.
		 */

		theClient = static_cast< iClient* >( data1 ) ;

		pendingChannelListType::iterator ptr = pendingChannelList.find(theChan->getName());

		if(ptr != pendingChannelList.end())
			{
			/*
			 * Firstly, is this join a result of a server bursting onto the network?
			 * If this is the case, its not a manual /join.
			 */

			iServer* theServer = Network->findServer( theClient->getIntYY() ) ;
			if (!theServer->isBursting())
				{
				/*
				 *  Yes, this channel is pending registration, update join count
				 *  and check out this user joining.
				 */

				ptr->second->join_count++;

				/*
				 *  Now, has this users IP joined this channel before?
				 *  If not - we keep a record of it.
				 */

				sqlPendingChannel::trafficListType::iterator Tptr =
					ptr->second->trafficList.find(theClient->getIP());

				sqlPendingTraffic* trafRecord;

				/*
				 * If we have more than 50 unique IP's join, we don't bother
				 * recording anymore.
				 */

				if (ptr->second->unique_join_count < 50)
					{
					if(Tptr == ptr->second->trafficList.end())
						{
						/* New IP, create and write the record. */

						trafRecord = new sqlPendingTraffic(SQLDb);
						trafRecord->ip_number = theClient->getIP();
						trafRecord->join_count = 1;
						trafRecord->channel_id = ptr->second->channel_id;
						trafRecord->insertRecord();

						ptr->second->trafficList.insert(sqlPendingChannel::trafficListType::value_type(
							theClient->getIP(), trafRecord));
#ifdef LOG_DEBUG
						logDebugMessage("Created a new IP traffic record for IP#%u (%s) on %s",
							theClient->getIP(), theClient->getNickUserHost().c_str(),
							theChan->getName().c_str());
#endif
						} else
						{
						/* Already cached, update and save. */
						trafRecord = Tptr->second;
						trafRecord->join_count++;
						//trafRecord->commit();
						}

						ptr->second->unique_join_count = ptr->second->trafficList.size();

						//logDebugMessage("New total for IP#%u on %s is %i",
						//	theClient->getIP(), theChan->getName().c_str(),
						//	trafRecord->join_count);
					}

				sqlUser* theUser = isAuthed(theClient, false);
				if (!theUser)
					{
					/*
					 *  If this user isn't authed, he can't possibly be flagged
					 *  as one of the valid supporters, so we drop out.
					 */

					xClient::OnChannelEvent( whichEvent, theChan,
						data1, data2, data3, data4 );
					return ;
					}

					/*
					 * Now, if this guy is a supporter, we bump his join count up.
					 */

					sqlPendingChannel::supporterListType::iterator Supptr = ptr->second->supporterList.find(theUser->getID());
					if (Supptr != ptr->second->supporterList.end())
						{
							Supptr->second++;
							ptr->second->commitSupporter(Supptr->first, Supptr->second);
#ifdef LOG_DEBUG
							logDebugMessage("New total for Supporter #%i (%s) on %s is %i.", theUser->getID(),
								theUser->getUserName().c_str(), theChan->getName().c_str(), Supptr->second);
#endif
						}

				xClient::OnChannelEvent( whichEvent, theChan,
					data1, data2, data3, data4 );
				return ;

				} /* Is server bursting? */
			} /* Is channel on pending list */

		sqlChannel* reggedChan = getChannelRecord(theChan->getName());
		if(!reggedChan)
			{
//			elog	<< "cservice::OnChannelEvent> WARNING, "
//				<< "unable to locate channel record"
//				<< " for registered channel event: "
//				<< theChan->getName()
//				<< endl;
			return ;
			}

		/* This is a registered channel, check it is set +R.
		 * If not, set it to +R (channel creation)
		 */
		if (!theChan->getMode(Channel::MODE_REG)) {
			stringstream tmpTS;
			tmpTS << reggedChan->getChannelTS();
			string channelTS = tmpTS.str();
			MyUplink->Mode(NULL, theChan, string("+R"), channelTS );
		}

		/* If this is a registered channel, but we're not in it -
		 * then we're not interested in the following commands!
		 */
		if (!reggedChan->getInChan())
		{
			break;
		}

		/*
		 * First thing we do - check if this person is banned.
		 * If so, they're booted out.
		 */

		if (checkBansOnJoin(theChan, reggedChan, theClient))
			{
			break;
			}

	#ifdef USE_WELCOME
		if (strlen(reggedChan->getWelcome().c_str()) > 0)
		{
			Notice(theClient, "(%s) %s",
				   theChan->getName().c_str(),
				   reggedChan->getWelcome().c_str());
		}
	#endif

		/* Is it time to set an autotopic? */
		if (reggedChan->getFlag(sqlChannel::F_AUTOTOPIC) &&
			(reggedChan->getLastTopic()
			+ topic_duration <= currentTime()))
			{
			doAutoTopic(reggedChan);
			}

		/* Deal with auto-op first - check this users access level. */
		sqlUser* theUser = isAuthed(theClient, false);
		if (!theUser)
			{
			/* If not authed, bye. */
			break;
			}

		/* Check access in this channel. */
		int accessLevel = getEffectiveAccessLevel(theUser, reggedChan, false);
		if (!accessLevel)
			{
			/* No access.. */
			break;
 			}

		sqlLevel* theLevel = getLevelRecord(theUser, reggedChan);
		if(!theLevel)
			{
			break;
			}

		/* Auto voice? */
		if (theLevel->getFlag(sqlLevel::F_AUTOVOICE))
			{
			if (!(accessLevel >= level::voice)) {
				theLevel->removeFlag(sqlLevel::F_AUTOVOICE);
				break;
			}
			if (!reggedChan->getFlag(sqlChannel::F_NOVOICE))
				Voice(theChan, theClient);
				break;
			}

		/* Check noop isn't set */
		if (reggedChan->getFlag(sqlChannel::F_NOOP))
			{
			break;
			}

		/* Check strictop isn't on, and this user is < 100 */
		if (reggedChan->getFlag(sqlChannel::F_STRICTOP))
			{
			if (!(accessLevel >= level::op))
				{
				break;
				}
			}

		/* Next, see if they have auto op set. */
		if (theLevel->getFlag(sqlLevel::F_AUTOOP))
			{
			if (!(accessLevel >= level::op)) {
				theLevel->removeFlag(sqlLevel::F_AUTOOP);
				break;
			}
			Op(theChan, theClient);
			break;
			}

		if (theLevel->getFlag(sqlLevel::F_AUTOHALFOP))
			{
			if (!(accessLevel >= level::halfop)) {
				theLevel->removeFlag(sqlLevel::F_AUTOHALFOP);
				break;
			}
			HalfOp(theChan, theClient);
			break;
			}


		break;
		}
	case EVT_SERVERMODE:
	{
        //if(!data1) return;
		elog << "mod.cservice: GOT SERVER MODE EVENT!" << std::endl;
        logDebugMessage("GOT SERVER MODE EVENT!");
        break;
	}
	default:
		break;
	} // switch()

xClient::OnChannelEvent( whichEvent, theChan,
	data1, data2, data3, data4 );
}

/**
 *  This function matches a client against the bans stored in the
 *  database for this channel.
 *  Returns an sqlBan if it matches, false otherwise.
 *  'theChan' and 'theClient' must _not_ be null.
 */
sqlBan* cservice::isBannedOnChan(sqlChannel* theChan, iClient* theClient)
{
map < int,sqlBan* >::const_iterator ptr = theChan->banList.begin();

for( ; ptr != theChan->banList.end() ; ++ptr )
	{
	// NOTE: This is a band-aid, it does not correct the actual
	// problem..
	sqlBan* theBan = ptr->second;
	if( 0 == theBan )
		{
		elog	<< "cservice::isBannedOnChan> Invalid ban!"
			<< endl ;
		continue ;
		}
	if(theBan->getMatcher()->matches(theClient))
		{
		return theBan;
		}
	} /* for() */

return NULL;
}

/**
 * This function compares a client with any active bans set in the DB.
 * If matched, the ban is applied and the user is kicked.
 * Returns true if matched, false if not.
 * N.B: Called from OnChannelEvent, theClient is guarantee'd to be in the
 * channel and netChan will exist.
 *--------------------------------------------------------------------------*/
bool cservice::checkBansOnJoin( Channel* netChan, sqlChannel* theChan,
	iClient* theClient )
{

sqlBan* theBan = isBannedOnChan(theChan, theClient);

// TODO: Don't reapply this ban to the network if it already is set in the channel object.
//       (ircu banlist could be full).
// TODO: Ban through the server.
// TODO: Violation of rule of numbers
/* If we found a matching ban */
if( theBan && (theBan->getLevel() >= 75) )
{
	stringstream s;
	s	<< getCharYYXXX()
		<< " M "
		<< theChan->getName()
		<< " +b "
		<< theBan->getBanMask()
		<< ends;

	Write( s );

	/* remove the ban (even if it doesnt exist, it will return false anyway) */
	netChan->removeBan( theBan->getBanMask() ) ;
	/* set the ban */
	netChan->setBan( theBan->getBanMask() ) ;

    string kickReason = "(" + theBan->getSetBy() + ") " + theBan->getReason();
    if ((theBan->getSetBy().empty()) || (theBan->getSetBy() == nickName)) kickReason = theBan->getReason();
    /* Don't kick banned +k bots */
    if ( !theClient->getMode(iClient::MODE_SERVICES) )
    {
            Kick(netChan, theClient, kickReason);
            //      string( "("
            //      + theBan->getSetBy()
            //      + ") "
            //      + theBan->getReason()) );

    }
    return true;
} /* Matching Ban > 75 */

/*
 * If they're banned < 75, return true, but don't
 * do anything.
 */
if( theBan && (theBan->getLevel() < 75) )
	{
	return true;
	}

return false;
}

void cservice::OnWhois( iClient* sourceClient,
			iClient* targetClient )
{
	/*
	 *  Return info about 'targetClient' to 'sourceClient'
	 */
// TODO: Only use one stringstream here

stringstream s;
s	<< getCharYY()
	<< " 311 "
	<< sourceClient->getCharYYXXX()
	<< " " << targetClient->getNickName()
	<< " " << targetClient->getUserName()
	<< " " << targetClient->getInsecureHost()
	<< " * :"
	<< ends;
Write( s );

stringstream s4;
s4	<< getCharYY()
	<< " 317 "
	<< sourceClient->getCharYYXXX()
	<< " " << targetClient->getNickName()
	<< " 0 " << targetClient->getConnectTime()
	<< " :seconds idle, signon time"
	<< ends;
Write( s4 );

if (targetClient->isOper())
	{
	stringstream s5;
	s5	<< getCharYY()
		<< " 313 "
		<< sourceClient->getCharYYXXX()
		<< " " << targetClient->getNickName()
		<< " :is an IRC Operator"
		<< ends;
	Write( s5 );
	}

sqlUser* theUser = isAuthed(targetClient, false);

if (theUser)
	{
	stringstream s6;
	s6	<< getCharYY()
		<< " 316 "
		<< sourceClient->getCharYYXXX()
		<< " " << targetClient->getNickName()
		<< " :is Logged in as "
		<< theUser->getUserName()
		<< ends;
	Write( s6 );
	}

if (isIgnored(targetClient))
	{
	stringstream s7;
	s7	<< getCharYY()
		<< " 316 "
		<< sourceClient->getCharYYXXX()
		<< " " << targetClient->getNickName()
		<< " :is currently being ignored. "
		<< ends;
	Write( s7 );
	}

stringstream s3;
s3	<< getCharYY()
	<< " 318 "
	<< sourceClient->getCharYYXXX()
	<< " " << targetClient->getNickName()
	<< " :End of /WHOIS list."
	<< ends;
Write( s3 );
}

void cservice::updateLimits()
{
	/*
	 * Forall cached channel records, perform an update of the
	 * channel limit.
	 */
	 sqlChannelHashType::iterator ptr = sqlChannelCache.begin();
	 	while (ptr != sqlChannelCache.end())
	 	{
		sqlChannel* theChan = (ptr)->second;

		/*
		 * Don't have the Floating Limit flag set?
		 */
		if (!theChan->getFlag(sqlChannel::F_FLOATLIM))
			{
			++ptr;
			continue;
			}
		/*
		 * X isn't even in the channel?
		 */
		if (!theChan->getInChan())
			{
			++ptr;
			continue;
			}

		Channel* tmpChan = Network->findChannel(theChan->getName());

		/*
		 * For some magical reason the channel doesn't even exist?
		 */
		if (!tmpChan)
			{
			++ptr;
			continue;
			}

		/*
		 * If its not time to update the limit for this channel yet.
		 */
		if (theChan->getLastLimitCheck() + theChan->getLimitPeriod() > currentTime())
			{
			++ptr;
			continue;
			}

		doFloatingLimit(theChan, tmpChan);

		++ptr;
		}
}

void cservice::doFloatingLimit(sqlChannel* reggedChan, Channel* theChan)
{
/*
 * This event is triggered when its "time" to do autolimits.
 */
 	unsigned int newLimit = theChan->size() + reggedChan->getLimitOffset();

 	/* Don't bother if the new limit is the same as the old one. */
 	if (newLimit == theChan->getLimit()) return;

	/* Also don't bother if the difference between the old limit and
	 * the new limit is < 'grace' */
	int currentDif = abs((int)theChan->getLimit() - (int)newLimit);
	if (currentDif <= (int)reggedChan->getLimitGrace()) return;

	/*
	 * If the new limit is above our max limit, don't bother
	 * either.
	 */

	if (reggedChan->getLimitMax() && (newLimit >= reggedChan->getLimitMax())) return;

	/*
 	 * Check we're actually opped.
	 */

	ChannelUser* tmpBotUser = theChan->findUser(getInstance());
	if (!tmpBotUser) return;
	if (!tmpBotUser->getMode(ChannelUser::MODE_O)) return;

	theChan->setMode(Channel::MODE_L);
	theChan->setLimit(newLimit);
	reggedChan->setLastLimitCheck(currentTime());

	incStat("CORE.FLOATLIM.ALTER");

	stringstream s;
	s	<< getCharYYXXX()
		<< " M "
		<< theChan->getName()
		<< " +l "
		<< newLimit
		<< ends;

	Write( s );

	incStat("CORE.FLOATLIM.ALTER.BYTES", strlen(s.str().c_str()));
}

/*--doAutoTopic---------------------------------------------------------------
 *
 * This support function sets the autotopic in a particular channel.
 */
void cservice::doAutoTopic(sqlChannel* theChan)
{

/* Quickly drop out if nothing is set.. */
if ( theChan->getDescription().empty() && theChan->getURL().empty() )
	{
	return;
	}

string extra ;
if( !theChan->getURL().empty() )
	{
	extra = " ( " + theChan->getURL() + " )" ;
	}

stringstream s;
s	<< getCharYYXXX()
	<< " T "
	<< theChan->getName()
	<< " :"
	<< theChan->getDescription()
	<< extra
	<< ends;

Write( s );

theChan->setLastTopic(currentTime());
}

/**
 * Bans a user via IRC and the database with 'theReason',
 * and then kicks. theChan cannot be null.
 */
bool cservice::doInternalBanAndKick(sqlChannel* theChan,
	iClient* theClient, const string& theReason)
{
/*
 *  Check to see if this banmask already exists in the
 *  channel. (Ugh, and overlapping too.. hmm).
 */

/* Create a new Ban record */
sqlBan* newBan = new (std::nothrow) sqlBan(SQLDb);
assert( newBan != 0 ) ;

string banTarget = Channel::createBan(theClient);

// TODO: Build a suitable constructor in sqlBan
newBan->setChannelID(theChan->getID());
newBan->setBanMask(banTarget);
newBan->setSetBy(nickName);
newBan->setSetTS(currentTime());
newBan->setLevel(25);

/* Move 360 to config */
newBan->setExpires( 300 + currentTime());
newBan->setReason(theReason);

/*
 *  Check for duplicates, if none found -
 *  add to internal list and commit to the db.
 */

map< int,sqlBan* >::const_iterator ptr = theChan->banList.begin();
while (ptr != theChan->banList.end())
	{
	const sqlBan* theBan = ptr->second;

	if(string_lower(banTarget) == string_lower(theBan->getBanMask()))
		{
			/*
			 * If this mask is already banned, we're just getting
			 * lagged info.
			 */
			return true;
		}
	++ptr;
	}

//theChan->banList[newBan->getID()] = newBan;

/* Insert this new record into the database. */
newBan->insertRecord();

/* Insert to our internal List. */
theChan->banList.insert(std::map<int,sqlBan*>::value_type(newBan->getID(),newBan));

/*
 * Finally, if this guy is auth'd.. suspend his account.
 */

sqlUser* theUser = isAuthed(theClient, false);
if (theUser)
{
  sqlLevel* accessRec = getLevelRecord(theUser, theChan);
  if (accessRec && (accessRec->getSuspendExpire() < (currentTime() + 300)))
  {
    int susLev = accessRec->getAccess() + 1;
    if (accessRec->getSuspendLevel() < susLev)
      accessRec->setSuspendLevel(susLev);
    accessRec->setSuspendExpire(currentTime() + 300);
    accessRec->setSuspendBy(nickName);
    accessRec->commit();
  }
}

Channel* netChan = Network->findChannel(theChan->getName());

// Oh dear?
if (!netChan)
	{
	return true;
	}

Kick( netChan, theClient, theReason ) ;

return true ;
}

bool cservice::doInternalBanAndKick(sqlChannel* theChan,
    iClient* theClient, unsigned short banLevel, unsigned int banExpire, const string& theReason)
{
/*
 *  Check to see if this banmask already exists in the
 *  channel. (Ugh, and overlapping too.. hmm).
 */

/* Create a new Ban record */
sqlBan* newBan = new (std::nothrow) sqlBan(SQLDb);
assert( newBan != 0 ) ;

string banTarget = Channel::createUniformBan(theClient);

// TODO: Build a suitable constructor in sqlBan
newBan->setChannelID(theChan->getID());
newBan->setBanMask(banTarget);
newBan->setSetBy(getNickName());
newBan->setSetTS(currentTime());
newBan->setLevel(banLevel);

newBan->setExpires(banExpire + currentTime());
newBan->setReason(theReason);

/*
 *  Check for duplicates, if none found -
 *  add to internal list and commit to the db.
 */

map< int,sqlBan* >::const_iterator ptr = theChan->banList.begin();
while (ptr != theChan->banList.end())
{
	const sqlBan* theBan = ptr->second;

	if(string_lower(banTarget) == string_lower(theBan->getBanMask()))
	{
		/*
		 * If this mask is already banned, we're just getting
		 * lagged info.
		 */
		return true;
	}
    ++ptr;
}

//theChan->banList[newBan->getID()] = newBan;

/* Insert this new record into the database. */
newBan->insertRecord();

/* Insert to our internal List. */
theChan->banList.insert(std::map<int,sqlBan*>::value_type(newBan->getID(),newBan));
Channel* netChan = Network->findChannel(theChan->getName());

// Oh dear?
if (!netChan)
{
	return true;
}

stringstream s;
s	<< getCharYYXXX()
	<< " M "
	<< netChan->getName()
	<< " +b "
	<< newBan->getBanMask()
	<< ends;

Write( s );

/* remove the ban (even if it doesnt exist, it will return false anyway) */
netChan->removeBan( newBan->getBanMask() ) ;
/* set the ban */
netChan->setBan( newBan->getBanMask() ) ;

vector <iClient*> toBoot;

for (Channel::userIterator chanUsers = netChan->userList_begin(); chanUsers != netChan->userList_end(); ++chanUsers)
{
	ChannelUser* tmpUser = chanUsers->second;
		if (tmpUser->getClient()->getIP() == theClient->getIP())
		{
			/* Don't kick +k things */
			if (!tmpUser->getClient()->getMode(iClient::MODE_SERVICES))
			{
				toBoot.push_back(tmpUser->getClient());
			}
		}
}
Kick( netChan, toBoot, theReason ) ;

return true ;
}

bool cservice::doInternalBanAndKick(sqlChannel* theChan,
    unsigned int IP, unsigned short banLevel, unsigned int banExpire, const string& theReason)
{
/*
 *  Check to see if this banmask already exists in the
 *  channel. (Ugh, and overlapping too.. hmm).
 */

/* Create a new Ban record */
sqlBan* newBan = new (std::nothrow) sqlBan(SQLDb);
assert( newBan != 0 ) ;

string banTarget = "*!*@" + xIP(IP).GetNumericIP();

// TODO: Build a suitable constructor in sqlBan
newBan->setChannelID(theChan->getID());
newBan->setBanMask(banTarget);
newBan->setSetBy(getNickName());
newBan->setSetTS(currentTime());
newBan->setLevel(banLevel);

newBan->setExpires(banExpire + currentTime());
newBan->setReason(theReason);

/*
 *  Check for duplicates, if none found -
 *  add to internal list and commit to the db.
 */

map< int,sqlBan* >::const_iterator ptr = theChan->banList.begin();
while (ptr != theChan->banList.end())
{
	const sqlBan* theBan = ptr->second;

	if(string_lower(banTarget) == string_lower(theBan->getBanMask()))
	{
		/*
		 * If this mask is already banned, we're just getting
		 * lagged info.
		 */
		return true;
	}
    ++ptr;
}

//theChan->banList[newBan->getID()] = newBan;

/* Insert this new record into the database. */
newBan->insertRecord();

/* Insert to our internal List. */
theChan->banList.insert(std::map<int,sqlBan*>::value_type(newBan->getID(),newBan));
Channel* netChan = Network->findChannel(theChan->getName());

// Oh dear?
if (!netChan)
{
	return true;
}

stringstream s;
s	<< getCharYYXXX()
	<< " M "
	<< netChan->getName()
	<< " +b "
	<< newBan->getBanMask()
	<< ends;

Write( s );

/* remove the ban (even if it doesnt exist, it will return false anyway) */
netChan->removeBan( newBan->getBanMask() ) ;
/* set the ban */
netChan->setBan( newBan->getBanMask() ) ;

vector <iClient*> toBoot;

for (Channel::userIterator chanUsers = netChan->userList_begin(); chanUsers != netChan->userList_end(); ++chanUsers)
{
	ChannelUser* tmpUser = chanUsers->second;
		if (tmpUser->getClient()->getIP() == IP)
		{
			/* Don't kick +k things */
			if (!tmpUser->getClient()->getMode(iClient::MODE_SERVICES))
			{
				toBoot.push_back(tmpUser->getClient());
			}
		}
}
Kick( netChan, toBoot, theReason ) ;

return true ;
}

bool cservice::doInternalSuspend(sqlChannel* theChan,
    iClient* theClient, unsigned short suspLevel, unsigned int suspExpire, const string& theReason)
{
	sqlUser* theUser = isAuthed(theClient, false);
	if (theUser)
	{
		sqlLevel* accessRec = getLevelRecord(theUser, theChan);
		if (accessRec && (accessRec->getSuspendExpire() < (currentTime() + suspExpire)))
		{
			if (accessRec->getSuspendLevel() < suspLevel)
				accessRec->setSuspendLevel(suspLevel);
			accessRec->setSuspendExpire(currentTime() + suspExpire);
			accessRec->setSuspendBy(getNickName());
			accessRec->setLastModif(currentTime());
			accessRec->setLastModifBy(getInstance()->getNickUserHost());
			accessRec->setSuspendReason(theReason);
			accessRec->commit();
		}
	} else return false;
	return true;
}

bool cservice::doInternalGline(iClient* theClient, const time_t& thePeriod, const string& theReason)
{
	string UserHost = /*theClient->getUserName() + "@"*/ "*@" + theClient->getRealInsecureHost();
	csGline *theGline = findGline(UserHost);
	bool Up = false;
	if(theGline)
		Up =  true;
	else { theGline = new (std::nothrow) csGline(SQLDb);
		assert(theGline != NULL); }
	theGline->setHost(UserHost);
	theGline->setExpires(unsigned(::time(0) + thePeriod));
	theGline->setAddedBy(getUplinkName());
	theGline->setReason(theReason);
	theGline->setAddedOn(::time(0));
	theGline->setLastUpdated(::time(0));
	addGlineToUplink(theGline);
	if (Up)
		theGline->Update();
	else
	{
		theGline->Insert();
		//We need to update the Id
		theGline->loadData(theGline->getHost());
		addGline(theGline);
	}
	return true ;
}

bool cservice::doInternalGline(unsigned int IP, const time_t& thePeriod, const string& theReason)
{
	string UserHost = "*@" + xIP(IP).GetNumericIP();
	csGline *theGline = findGline(UserHost);
	bool Up = false;
	if(theGline)
		Up =  true;
	else { theGline = new (std::nothrow) csGline(SQLDb);
		assert(theGline != NULL); }
	theGline->setHost(UserHost);
	theGline->setExpires(unsigned(::time(0) + thePeriod));
	theGline->setAddedBy(getUplinkName());
	theGline->setReason(theReason);
	theGline->setAddedOn(::time(0));
	theGline->setLastUpdated(::time(0));
	addGlineToUplink(theGline);
	if (Up)
		theGline->Update();
	else
	{
		theGline->Insert();
		//We need to update the Id
		theGline->loadData(theGline->getHost());
		addGline(theGline);
	}
	return true ;
}

/**
 * This method writes a 'channellog' record, recording an event that has
 * occured in this channel.
 */

void cservice::writeChannelLog(sqlChannel* theChannel, iClient* theClient,
	unsigned short eventType, const string& theMessage)
{
sqlUser* theUser = isAuthed(theClient, false);
string userExtra = theUser ? theUser->getUserName() : "Not Logged In";

stringstream theLog;
theLog	<< "INSERT INTO channellog (ts, channelID, event, message, "
	<< "last_updated) VALUES "
	<< "("
	<< currentTime()
	<< ", "
	<< theChannel->getID()
	<< ", "
	<< eventType
	<< ", "
 	<< "'["
	<< nickName
	<< "]: "
	<< theClient->getNickUserHost()
	<< " (" << userExtra << ") "
	<< escapeSQLChars(theMessage)
	<< "', "
	<< currentTime()
	<< ")"
	<< ends;

#ifdef LOG_SQL
	elog	<< "cservice::writeChannelLog> "
		<< theLog.str().c_str()
		<< endl;
#endif

// TODO: Is this right?
SQLDb->Exec(theLog);
//SQLDb->ExecCommandOk(theLog.str().c_str());
}

/**
 *  This function returns the last channel event of type 'eventType'
 *  (up to 'eventTime') for the channel given.
 *  It returns a blank string if none found.
 */
const string cservice::getLastChannelEvent(sqlChannel* theChannel,
	unsigned short eventType, unsigned int& eventTime)
{
	unsigned int ts;
	stringstream queryString;

	if (eventTime == 0)
		ts = currentTime();
	else
		ts = eventTime;

	queryString	<< "SELECT message FROM channellog WHERE "
			<< "channelid = "
			<< theChannel->getID()
			<< " AND event = "
			<< eventType
			<< " AND ts <= "
			<< ts
			<< " ORDER BY ts DESC LIMIT 1"
			<< ends;

#ifdef LOG_SQL
	elog	<< "cservice::getLastChannelEvent> "
		<< queryString.str().c_str()
		<< endl;
#endif

	if (SQLDb->Exec(queryString, true))
	{
		if (SQLDb->Tuples() < 1)
			return "";
		string reason = SQLDb->GetValue(0, 0);
		return reason;
	}
	return "";
}

/**
 * Global method to replace ' with \' in strings for safe placement in
 * SQL statements.
 */
const string escapeSQLChars(const string& theString)
{
string retMe ;

for( string::const_iterator ptr = theString.begin() ;
	ptr != theString.end() ; ++ptr )
	{
	if( *ptr == '\'' )
		{
		retMe += "\\\047" ;
		}
	else if ( *ptr == '\\' )
		{
		retMe += "\\\134" ;
		}
	else
		{
		retMe += *ptr ;
		}
	}
return retMe ;
}

const string searchSQL(const string& theString)
{
string retMe ;

for( string::const_iterator ptr = theString.begin() ;
        ptr != theString.end() ; ++ptr )
        {
        if( *ptr == '*' )
                {
                retMe += "%" ;
                }
        else if ( *ptr == '?' )
		{
		retMe += "_" ;
		}
        else
		{
                retMe += *ptr ;
                }
        }
return retMe ;
}

time_t cservice::currentTime() const
{
/* Returns the current time according to the postgres server. */
return dbTimeOffset + ::time(NULL);
}

bool cservice::Notice( const iClient* Target, const string& Message )
{
bool returnMe = false ;
if( Connected && MyUplink )
	{
	setOutputTotal( Target, getOutputTotal(Target) + Message.size() );
	char buffer[512] = { 0 };
	char *b = buffer ;
	const char *m = 0 ;

	// TODO: This should be fixed.
	// A walking timebomb.
	for (m=Message.c_str();*m!=0;m++)
		{
		if (*m == '\n' || *m == '\r')
			{
			*b='\0';
			MyUplink->Write( "%s O %s :%s\r\n",
				getCharYYXXX().c_str(),
				Target->getCharYYXXX().c_str(),
				buffer ) ;
			b=buffer;
			}
		else
			{
			if (b<buffer+509)
			  *(b++)=*m;
			}

		}
        *b='\0';
	returnMe = MyUplink->Write( "%s O %s :%s\r\n",
		getCharYYXXX().c_str(),
		Target->getCharYYXXX().c_str(),
		buffer ) ;
	}
return returnMe ;
}

bool cservice::Notice(const string& Channel, const char* Message, ...)
{
	return xClient::Notice(Channel, Message);
}

string cservice::HostIsRegisteredTo(const string& theHost)
{
	string theUser = string();
	stringstream theQuery;
	theQuery	<< "SELECT user_name FROM users WHERE "
				<< "lower(users.hostname) = '"
				<< escapeSQLChars(string_lower(theHost))
				<< "'"
				<< ends;
	if (!SQLDb->Exec(theQuery, true))
	{
		logDebugMessage("Error on cservice::HostIsRegisteredToQuery");
#ifdef LOG_SQL
	//elog << "sqlQuery> " << theQuery.str().c_str() << endl;
	elog 	<< "cservice::HostIsRegisteredToQuery> SQL Error: "
			<< SQLDb->ErrorMessage()
			<< endl ;
#endif
	return theUser;
	}
	if (SQLDb->Tuples() > 0)
		theUser = SQLDb->GetValue(0,0);
	return theUser;
}

bool cservice::HostIsReserved(const string& theHost)
{
	for (reservedHostsListType::iterator itr = reservedHostsList.begin(); itr != reservedHostsList.end(); itr++)
	{
		if (!match((*itr), theHost))
			return true;
	}
	return false;
}

bool cservice::Notice( const iClient* Target, const char* Message, 
	... )
{
if( Connected && MyUplink && Message && Message[ 0 ] != 0 )
	{
	char buffer[ 512 ] = { 0 } ;
	va_list list;

	va_start(list, Message);
	vsnprintf(buffer, 512, Message, list);
	va_end(list);

	setOutputTotal( Target, getOutputTotal(Target) + strlen(buffer) );
	return MyUplink->Write("%s O %s :%s\r\n",
		getCharYYXXX().c_str(),
		Target->getCharYYXXX().c_str(),
		buffer ) ;
	}
return false ;
}

void cservice::dbErrorMessage(iClient* theClient)
{
Notice(theClient,
	"An error occured while performing this action, "
	"the database may be unavailable. Please try again later.");
dbErrors++;
}

void cservice::loadPendingChannelList()
{
/*
 *  First thing first, if the list has something in it, we want to dump it
 *  out to the database.
 *  Then, clear the list free'ing up memory and finally emptying the list.
 */

if (pendingChannelList.size() > 0)
{
	pendingChannelListType::iterator ptr = pendingChannelList.begin();

	if( !SQLDb->Exec("BEGIN;" ) )
//	if( PGRES_COMMAND_OK != begnew sqlPendingChannel(SQLDb);
	{
		elog << "Error starting transaction." << endl;
	}

#ifdef LOG_SQL
	elog << "BEGIN" << endl;
#endif

	while (ptr != pendingChannelList.end())
		{
		sqlPendingChannel* pendingChan = ptr->second;

		/* Commit the record */
		pendingChan->commit();

		/* Stop listening on this channels events for now. */
		MyUplink->UnRegisterChannelEvent(ptr->first, this);

		ptr->second = NULL;
		delete(pendingChan);
		++ptr;
		} /* while() */

	if( !SQLDb->Exec("END;") )
//	if( PGRES_COMMAND_OK != endStatus )
	{
		elog << "Error Ending transaction." << endl;
	}

#ifdef LOG_SQL
	elog << "END" << endl;
#endif

	pendingChannelList.clear();
}

/*
 * For simplicity, we assume that if a pending channel is in state "1", then it has 10 valid
 * supporters who have said "Yes" and we're looking at them.
 */

stringstream theQuery;
theQuery	<<  "SELECT channels.name, pending.channel_id, user_id, pending.join_count, supporters.join_count, pending.unique_join_count, pending.check_start_ts"
			<< " FROM pending,supporters,channels"
			<< " WHERE pending.channel_id = supporters.channel_id"
			<< " AND channels.id = pending.channel_id"
			<< " AND pending.status = 1;"
			<< ends;

#ifdef LOG_SQL
elog	<< "*** [CMaster::loadPendingChannelList]: Loading pending channel details."
	<< theQuery.str().c_str()
	<< endl;
#endif

if( SQLDb->Exec(theQuery, true ) )
//if( PGRES_TUPLES_OK == status )
	{
	for (unsigned int i = 0 ; i < SQLDb->Tuples(); i++)
		{
		string chanName = SQLDb->GetValue(i,0);
		sqlPendingChannel* newPending;

		/*
		 *  Is this channel already in our pending list? If so, simply
		 *  lookup and append this supporter to its list.
		 *  If not, create a new 'pending' channel entry.
		 */

		pendingChannelListType::iterator ptr = pendingChannelList.find(chanName);

		if(ptr != pendingChannelList.end())
			{
			// It already exists.
			newPending = ptr->second;
			}
				else
			{
			newPending = new sqlPendingChannel(SQLDb);
			newPending->channel_id =
				atoi(SQLDb->GetValue(i,1).c_str());
			newPending->join_count =
				atoi(SQLDb->GetValue(i,3).c_str());
			newPending->unique_join_count =
				atoi(SQLDb->GetValue(i,5).c_str());
			newPending->checkStart = atoi(SQLDb->GetValue(i,6).c_str());
			//time_t checkStart = atoi(SQLDb->GetValue(i,5).c_str());
			//unsigned int traffickTime = MaxDays * JudgeDaySeconds; //currentTime()
			//if ((checkStart + traffickTime) > currentTime())
			pendingChannelList.insert( pendingChannelListType::value_type(chanName, newPending) );
			//else logDebugMessage("")

			/*
			 *  Lets register our interest in listening on JOIN events for this channel.
			 */
			MyUplink->RegisterChannelEvent(chanName, this);
			}

		/*
		 *  Next, update the internal supporters list.
		 */
		newPending->supporterList.insert( sqlPendingChannel::supporterListType::value_type(
			atoi(SQLDb->GetValue(i, 2).c_str()), 
			atoi(SQLDb->GetValue(i, 4).c_str()) )  );
		}
	}

	logDebugMessage("Loaded Pending Channels, there are currently %i channels being traffic monitored.",
		pendingChannelList.size());

#ifdef LOG_DEBUG
	elog	<< "Loaded pending channels, there are currently "
			<< pendingChannelList.size()
			<< " channels being notified and recorded."
			<< endl;
#endif

	/*
	 * For each pending channel, load up its IP traffic
	 * cache.
	 */

	pendingChannelListType::iterator ptr = pendingChannelList.begin();
	while (ptr != pendingChannelList.end())
		{
		sqlPendingChannel* pendingChan = ptr->second;
		pendingChan->loadTrafficCache();
		pendingChan->loadSupportersTraffic();
		++ptr;
		};

}

void cservice::checkDbConnectionStatus()
{
	if( SQLDb->ConnectionBad() )
//	if(SQLDb->Status() == CONNECTION_BAD)
	{
		logAdminMessage("\002WARNING:\002 Backend database connection has been lost, attempting to reconnect.");
		elog	<< "cmaster::cmaster> Attempting to reconnect to database." << endl;

		/* Remove the old database connection object. */
		delete(SQLDb);

		string Query = "host=" + confSqlHost + " dbname=" + confSqlDb + " port=" + confSqlPort + " user=" + confSqlUser
					 + " password=" + confSqlPass;

		SQLDb = new (std::nothrow) dbHandle( confSqlHost,
			atoi( confSqlPort.c_str() ),
			confSqlDb,
			confSqlUser,
			confSqlPass ) ;
//		SQLDb = new (std::nothrow) cmDatabase( Query.c_str() ) ;
		assert( SQLDb != 0 ) ;

		if (SQLDb->ConnectionBad())
		{
			elog	<< "cmaster::cmaster> Unable to connect to SQL server."
					<< endl
					<< "cmaster::cmaster> PostgreSQL error message: "
					<< SQLDb->ErrorMessage()
					<< endl ;

			connectRetries++;
			if (connectRetries >= 6)
			{
				logAdminMessage("Unable to contact database after 6 attempts, shutting down.");
				//MyUplink->flushBuffer();
				::exit(0);
			} else
			{
				logAdminMessage("Connection failed, retrying:");
			}


		} else
		{
// TODO: Is this ok?
				SQLDb->Exec("LISTEN channels_u; LISTEN users_u; LISTEN levels_u;");
//				SQLDb->ExecCommandOk("LISTEN channels_u; LISTEN users_u; LISTEN levels_u;");
				logAdminMessage("Successfully reconnected to database server. Panic over ;)");
		}
	}

}

void cservice::preloadChannelCache()
{
stringstream theQuery;
theQuery	<< "SELECT " << sql::channel_fields
			<< " FROM channels WHERE "
			<< "registered_ts <> 0"
			<< ends;

elog	<< "*** [CMaster::preloadChannelCache]: Loading all registered channel records: "
		<< endl;

if( SQLDb->Exec(theQuery, true ) )
//if( PGRES_TUPLES_OK == status )
	{
	for (unsigned int i = 0 ; i < SQLDb->Tuples(); i++)
		{
 		/* Add this information to the channel cache. */

		sqlChannel* newChan = new (std::nothrow) sqlChannel(SQLDb);
		assert( newChan != 0 ) ;

		newChan->setAllMembers(i);
		newChan->setLastUsed(currentTime());

		sqlChannelCache.insert(sqlChannelHashType::value_type(newChan->getName(), newChan));
		sqlChannelIDCache.insert(sqlChannelIDHashType::value_type(newChan->getID(), newChan));

		} // for()
	} // if()

elog	<< "*** [CMaster::preloadChannelCache]: Done. Loaded "
		<< SQLDb->Tuples()
		<< " registered channel records."
		<< endl;
}

void cservice::preloadBanCache()
{
/*
 * Execute a query to return all bans.
 */

stringstream theQuery;
theQuery	<< "SELECT " << sql::ban_fields
		<< " FROM bans;"
		<< ends;

elog	<< "*** [CMaster::preloadBanCache]: Precaching Bans table: "
	<< endl;

if( SQLDb->Exec(theQuery, true ) )
//if( PGRES_TUPLES_OK == status )
	{
	for (unsigned int i = 0 ; i < SQLDb->Tuples(); i++)
		{
		/*
		 * First, lookup this channel in the channel cache.
		 */

		unsigned int channel_id = atoi(SQLDb->GetValue(i, 1).c_str());
		sqlChannel* theChan = getChannelRecord(channel_id);

		/*
		 * If we don't have the channel cached, its not registered so
		 * we aren't interested in this ban.
		 */

		if (!theChan) continue;

		sqlBan* newBan = new (std::nothrow) sqlBan(SQLDb);
		newBan->setAllMembers(i);
		theChan->banList.insert(
			std::make_pair( newBan->getID(), newBan ) ) ;
//		theChan->banList[newBan->getID()] = newBan;

		} // for()
	} // if()

elog	<< "*** [CMaster::preloadBanCache]: Done. Loaded "
	<< SQLDb->Tuples()
	<< " bans."
	<< endl;
}

void cservice::preloadLevelsCache()
{
/*
 * Execute a query to return all level records.
 */

stringstream theQuery;
theQuery	<< "SELECT " << sql::level_fields
			<< " FROM levels"
			<< ends;

elog		<< "*** [CMaster::preloadLevelCache]: Precaching Level table: "
			<< endl;

unsigned int goodCount = 0;
if( SQLDb->Exec(theQuery, true ) )
//if( PGRES_TUPLES_OK == status )
	{
		for (unsigned int i = 0 ; i < SQLDb->Tuples(); i++)
		{
		/*
		 * First, lookup this channel in the channel cache.
		 */

		unsigned int channel_id = atoi(SQLDb->GetValue(i, 0).c_str());
		unsigned int user_id = atoi(SQLDb->GetValue(i, 1).c_str());
		sqlChannel* theChan = getChannelRecord(channel_id);

		/*
		 * If we don't have the channel cached, its not registered so
		 * we aren't interested in this level record.
		 */

		if (!theChan) continue;

		pair<int, int> thePair( user_id, channel_id );
		sqlLevel* newLevel = new (std::nothrow) sqlLevel(SQLDb);
		newLevel->setAllMembers(i);

		sqlLevelCache.insert(sqlLevelHashType::value_type(thePair, newLevel));
		goodCount++;

		} // for()
	} // if()

elog	<< "*** [CMaster::preloadLevelCache]: Done. Loaded "
		<< goodCount << " level records out of " << SQLDb->Tuples()
		<< "."
		<< endl;
}

/*
 * Preload all the users within the last 'x' days, to
 * save doing loads of lookups when we recieve 25,000 +r'd users
 * during net.merge.
 */

void cservice::preloadUserCache()
{
	stringstream theQuery;
	theQuery	<< "SELECT " << sql::user_fields
				<< " FROM users,users_lastseen WHERE "
				<< " users_lastseen.user_id = users.id AND "
				<< " users_lastseen.last_seen >= "
				<< currentTime() - (preloadUserDays * 86400)
				<< ends;

	elog		<< "*** [CMaster::preloadUserCache]: Loading users accounts logged in within "
				<< preloadUserDays
				<< " days : "
				<< endl;

	if( SQLDb->Exec(theQuery, true ) )
//	if( PGRES_TUPLES_OK == status )
	{
		for (unsigned int i = 0 ; i < SQLDb->Tuples(); i++)
			{
				sqlUser* newUser = new (std::nothrow) sqlUser(SQLDb);
				assert( newUser != 0 ) ;

				newUser->setAllMembers(i);
				newUser->setLastUsed(currentTime());
				sqlUserCache.insert(sqlUserHashType::value_type(newUser->getUserName(), newUser));
			}
	}

	elog	<< "*** [CMaster::preloadUserCache]: Done. Loaded "
			<< SQLDb->Tuples()
			<< " user accounts."
			<< endl;
}

void cservice::incStat(const string& name)
{
	statsMapType::iterator ptr = statsMap.find( name );

	if( ptr == statsMap.end() )
        {
        statsMap.insert(statsMapType::value_type(name, 1));
        }
        else
        {
			ptr->second++;
		}
}

void cservice::incStat(const string& name, unsigned int amount)
{
	statsMapType::iterator ptr = statsMap.find( name );

	if( ptr == statsMap.end() )
        {
        statsMap.insert(statsMapType::value_type(name, 1));
        }
        else
        {
			ptr->second += amount;
		}
}

void cservice::noticeAllAuthedClients(sqlUser* theUser, const char* Message, ... )
{
/*
 * Loop over everyone who is authed as this user and give them a message.
 */

if( Connected && MyUplink && Message && Message[ 0 ] != 0 )
	{
	char buffer[ 512 ] = { 0 } ;
	va_list list;

	va_start(list, Message);
	vsnprintf(buffer, 512, Message, list);
	va_end(list);

	/*
	 * Loop over all people auth'd as this user, and send them a
	 * message.
	 */

	for( sqlUser::networkClientListType::iterator ptr = theUser->networkClientList.begin() ;
		ptr != theUser->networkClientList.end() ; ++ptr )
		{
		iClient* Target = (*ptr);
		setOutputTotal( Target, getOutputTotal(Target) + strlen(buffer) );
		MyUplink->Write("%s O %s :%s\r\n",
			getCharYYXXX().c_str(),
			Target->getCharYYXXX().c_str(),
			buffer ) ;
		}
	}

}

void cservice::NoteAllAuthedClients(sqlUser* theUser, const char* Message, ... )
{
#ifdef USE_NOTICES
if( Connected && MyUplink && Message && Message[ 0 ] != 0 )
	{
	char buffer[ 512 ] = { 0 } ;
	va_list list;

	va_start(list, Message);
	vsnprintf(buffer, 512, Message, list);
	va_end(list);

	/*
	 * Loop over all people auth'd as this user, and send them a
	 * message.
	 */
	if (theUser->networkClientList.size() != 0)
	{
		for( sqlUser::networkClientListType::iterator ptr = theUser->networkClientList.begin() ;
			ptr != theUser->networkClientList.end() ; ++ptr )
			{
			iClient* Target = (*ptr);
			setOutputTotal( Target, getOutputTotal(Target) + strlen(buffer) );
			MyUplink->Write("%s O %s :%s\r\n",
				getCharYYXXX().c_str(),
				Target->getCharYYXXX().c_str(),
				buffer ) ;
			}
		return;
	}

	string noteMessage = string(buffer);

	stringstream queryString;
	queryString	<< "DELETE FROM notices WHERE last_updated IN "
			<< "(SELECT MIN(last_updated) FROM notices "
			<< "WHERE user_id = "
			<< theUser->getID()
			<< " HAVING count(last_updated) >= "
			<< MAXnotes 
			<< "3)"
			<< ends;
	#ifdef LOG_SQL
		elog	<< "cservice::NoteAllAuthedClients::DeleteNotice> "
				<< queryString.str().c_str()
				<< endl;
	#endif

	if( !SQLDb->Exec(queryString, true ) )
//	if( PGRES_COMMAND_OK != status )
		{
	#ifdef LOG_SQL
		elog	<< "NoteAllAuthedClients::commit> Something went wrong: "
				<< SQLDb->ErrorMessage()
				<< endl;
	#endif
		logDebugMessage("NoteAllAuthedClients::DELETEFROM: ", SQLDb->ErrorMessage().c_str());
		return;
		}
	static const char* queryHeader = "INSERT INTO notices (user_id,message,last_updated) VALUES (";
	queryString.str("");
	queryString	<< queryHeader
			<< theUser->getID() << ", '"
			<< escapeSQLChars(noteMessage) << "', "
			<< "date_part('epoch', CURRENT_TIMESTAMP)::int);"
			<< ends;

	#ifdef LOG_SQL
		elog	<< "cservice::NoteAllAuthedClients::Insert Note> "
				<< queryString.str().c_str()
				<< endl;
	#endif

	if( !SQLDb->Exec(queryString, true ) )
//	if( PGRES_COMMAND_OK != status )
		{
	#ifdef LOG_SQL
		elog	<< "NoteAllAuthedClients::commit> Something went wrong: "
				<< SQLDb->ErrorMessage()
				<< endl;
	#endif
		logDebugMessage("An unknown error occured delivering the note.");
		return;
		}
	} //if MyUpLink
#endif
   return;
}

/*
 * Checks if the password supplied correctly matches the password for
 * this user.
 */

bool cservice::isPasswordRight(sqlUser* theUser, const string& password)
{
/*
 *  Compare password with MD5 hash stored in user record.
 */

/* MD5 hash algorithm object. */
md5	hash;

/* MD5Digest algorithm object.*/
md5Digest digest;

/* check that we have a valid md5 hash to prevent coredumps */

if (theUser->getPassword().size() < 9)
{
	/* salt is 8 characters and there must be a password
	 * of some kind, so 9 is a 'safe' value.
	 *
	 * return a standard 'invalid password' to user
	 */
	return false;
}

string salt = theUser->getPassword().substr(0, 8);
string md5Part = theUser->getPassword().substr(8);
string guess = salt + password;

// Build a MD5 hash based on our salt + the guessed password.
hash.update( (const unsigned char *)guess.c_str(), guess.size() );
hash.report( digest );

// Convert the digest into an array of int's to output as hex for
// comparison with the passwords generated by PHP.
int data[ MD5_DIGEST_LENGTH ] = { 0 } ;

for( size_t ii = 0; ii < MD5_DIGEST_LENGTH; ii++ )
	{
	data[ii] = digest[ii];
	}

stringstream output;
output << std::hex;
output.fill('0');

for( size_t ii = 0; ii < MD5_DIGEST_LENGTH; ii++ )
	{
	output << std::setw(2) << data[ii];
	}
output << ends;

if(md5Part != output.str().c_str() ) // If the MD5 hash's don't match..
	{
	return false;
	}

return true;
}

/*
 * Return some slightly interesting stats as a result of a status *.
 */
void cservice::doCoderStats(iClient* theClient)
{
	float userTotal = userCacheHits + userHits;
	float userEf = userCacheHits ? ((float)userCacheHits / userTotal * 100) : 0;

	Notice(theClient, "CMaster Channel Services internal status:");

	Notice(theClient,"[User Record Stats] \002Cached Entries:\002 %i    \002DB Requests:\002 %i    \002Cache Hits:\002 %i    \002Efficiency:\002 %.2f%%",
		sqlUserCache.size(),
		userHits,
		userCacheHits,
		userEf);

	/*
	 * Count how many users are actually logged in right now.
	 */
	unsigned int authCount = 0;
	sqlUserHashType::iterator ptr = sqlUserCache.begin();
	sqlUser* tmpUser;

	while (ptr != sqlUserCache.end())
	{
		tmpUser = ptr->second;
		if (tmpUser->isAuthed()) authCount++;
		++ptr;
	}

	/*
	 * Iterate over all the clients on the network and
	 * see how many are +x.
	 */

	unsigned int plusXCount = 0;
	unsigned int plusWCount = 0;
	unsigned int plusDCount = 0;

	xNetwork::const_clientIterator ptr2 = Network->clients_begin();
	while(ptr2 != Network->clients_end())
	{
		iClient* tmpClient = ptr2->second;
		if (tmpClient->isModeX()) plusXCount++;
		if (tmpClient->isModeW()) plusWCount++;
		if (tmpClient->isModeD()) plusDCount++;
		++ptr2;
	}

	Notice(theClient, "--- Total clients : %i", Network->clientList_size());

	float authTotal = ((float)authCount / (float)Network->clientList_size()) * 100;
	Notice(theClient, "--- Total Auth'd  : %i (%.2f%% of total)",
		authCount, authTotal);

	float plusXTotal = ((float)plusXCount / (float)Network->clientList_size()) * 100;
	Notice(theClient, "--- Total umode +x: %i (%.2f%% of total)",
		plusXCount, plusXTotal);

	float plusWTotal = ((float)plusWCount / (float)Network->clientList_size()) * 100;
	Notice(theClient, "--- Total umode +w: %i (%.2f%% of total)",
		plusWCount, plusWTotal);

	float plusDTotal = ((float)plusDCount / (float)Network->clientList_size()) * 100;
	Notice(theClient, "--- Total umode +d: %i (%.2f%% of total)",
		plusDCount, plusDTotal);

	float joinTotal = ((float)joinCount / (float)Network->channelList_size()) * 100;
	Notice(theClient, "I am in %i channels out of %i on the network. (%.2f%%)",
		joinCount, Network->channelList_size(), joinTotal);

	unsigned int secs = (currentTime() - getUplink()->getStartTime());

	float cPerSec = (float)totalCommands / (float)secs;

	Notice(theClient, "I've received %i commands since I started (%.2f commands per second).",
		totalCommands, cPerSec);

	Notice(theClient,"\002Uptime:\002 %s",
		prettyDuration(getUplink()->getStartTime() + dbTimeOffset).c_str());

	/*
	 * Now, dump all the config settings.
	 */

	Notice(theClient, "-------------------------");
	Notice(theClient, "Configuration Variables: ");
	Notice(theClient, "-------------------------");

	for( cservice::configHashType::iterator ptr = configTable.begin() ;
	ptr != configTable.end() ; ++ptr )
	{
	Notice(theClient, "%s: %s", ptr->first.c_str(), ptr->second->asString().c_str());
	}
}

void cservice::showAdmins(iClient* theClient)
{
	Notice(theClient,"Status of currently logged '*' officials:");

	stringstream authQuery;
	authQuery	<< "SELECT users.user_name,levels.access FROM "
				<< "users,levels WHERE users.id = levels.user_id "
				<< "AND levels.channel_id = 1"
				<< " ORDER BY levels.access DESC"
				<< ends;

	#ifdef LOG_SQL
		elog	<< "sqlQuery> "
				<< authQuery.str().c_str()
				<< endl;
	#endif

	if( !SQLDb->Exec( authQuery, true ) )
	//if( PGRES_TUPLES_OK != status )
	{
		elog	<< "STATUS> SQL Error: "
				<< SQLDb->ErrorMessage()
				<< endl ;
		return;
	}
	string authList;
	string nextPerson;

	for (unsigned int i = 0; i < SQLDb->Tuples(); i++)
	{
		/*
		 *  Look up this username in the cache.
		 */

		sqlUserHashType::iterator ptr =	sqlUserCache.find(SQLDb->GetValue(i, 0));

		if (ptr != sqlUserCache.end())
		{
			sqlUser* currentUser = ptr->second;
			if (!currentUser->isAuthed())
			{
				continue ;
			}

			nextPerson += SQLDb->GetValue(i, 0);
			nextPerson += "/\002";

			/*
			 * Only show the online nickname if that person is in the target
			 * channel.
			 *
			 * Loop around all people auth'd as this nick and append their nicks
			 */
			for (sqlUser::networkClientListType::iterator ptr = currentUser->networkClientList.begin();
				ptr != currentUser->networkClientList.end(); ++ptr)
			{
				iClient* tmpClient = (*ptr);

				nextPerson += "";
				nextPerson += tmpClient->getNickName();
				nextPerson += " ";
			}

		nextPerson += "\002(";
		nextPerson += SQLDb->GetValue(i, 1);
		nextPerson += ") ";

		/*
		 *  Will this string overflow our Notice buffer?
		 *  If so, dump it now..
		 */
		if (nextPerson.size() + authList.size() > 400)
		{
			Notice(theClient, "Auth: %s", authList.c_str());
			authList.erase(authList.begin(), authList.end());
		}

		/*
		 * Add it on to our list.
		 */

		authList += nextPerson;
		nextPerson.erase( nextPerson.begin(), nextPerson.end() );
		}
	} // for()

	Notice(theClient, "Auth: %s", authList.c_str());
}

/*
 * Function to retrieve the specified config variable from the in-memory cache.
 */
ConfigData* cservice::getConfigVar(const string& variable)
{
	configHashType::iterator ptr = configTable.find(variable);

	if(ptr != configTable.end())
	{
		return ptr->second;
	} else {
		return &empty_config;
	}
}

/*
 * Pre-load the configuration information from the db.
 */
void cservice::loadConfigData()
{
	stringstream theQuery;
	theQuery	<< "SELECT var_name,contents FROM variables;"
				<< ends;

	if( SQLDb->Exec(theQuery, true ) )
//	if( PGRES_TUPLES_OK == status )
		{
		for (unsigned int i = 0 ; i < SQLDb->Tuples(); i++)
			{
			ConfigData* newConfig = new (std::nothrow) ConfigData();
			assert( newConfig != 0 ) ;

			newConfig->string_value = SQLDb->GetValue(i, 1);
			newConfig->int_value = atoi(newConfig->string_value.c_str());

			configTable.insert(configHashType::value_type(SQLDb->GetValue(i, 0), newConfig));
			}
		}

}


bool cservice::doXQLogin(iServer* theServer, const string& Routing, const string& Message)
{
//What's going to be in Message?
//<ournick> LOGIN <ip> <username> <password>
StringTokenizer st( Message );
if( st.size() < 5 )
        {
	return false;
        }

/*
 *  Are we allowing logins yet?
 **/
unsigned int useLoginDelay = getConfigVar("USE_LOGIN_DELAY")->asInt();
unsigned int loginTime = getUplink()->getStartTime() + loginDelay;
if ( (useLoginDelay == 1) && (loginTime >= (unsigned int)currentTime()) )
	{
	//TODO Send error back
	return false;
	}



sqlUser* theUser = getUserRecord(st[3]);
if( !theUser )
        {
	elog 	<< "doXQLogin: "
		<< "Couldn't find user data for accountname: "
		<< st[3]
		<< endl;
	//TODO Send error back
        return false;
        }

// IPR checks are performed against st[2], the IP passed to us by iauth

stringstream theQuery;
theQuery        << "SELECT allowmask,allowrange1,allowrange2,added FROM "
                << "ip_restrict WHERE user_id = "
                << theUser->getID()
                << ends;
#ifdef LOG_SQL
        elog    << "cservice::checkIPR::sqlQuery> "
                << theQuery.str().c_str()
                << endl;
#endif

if( !SQLDb->Exec(theQuery, true ) )
        {
        /* SQL error, fail them */
        elog    << "cservice::checkIPR> SQL Error: "
                << SQLDb->ErrorMessage()
                << endl;
                return false;
        }

bool userHasIPR = true;
if (SQLDb->Tuples() < 1)
        {
	userHasIPR = false;
#ifdef IPR_DEFAULT_REJECT
        /* no entries, fail them */
        return false;
#else
	/* no entries, allow them to pass through*/
#endif
        }

if (userHasIPR) 
	{
	/* cycle through results to find a match */
        bool ipr_match = false;
        unsigned int ipr_ts = 0;
        unsigned int tmpIP = xIP(st[2], false).GetLongIP();
        for (unsigned int i=0; i < SQLDb->Tuples(); i++)
        {
                /* get some variables out of the db row */
                std::string ipr_allowmask = SQLDb->GetValue(i, 0);
                unsigned int ipr_allowrange1 = atoi(SQLDb->GetValue(i, 1).c_str());
                unsigned int ipr_allowrange2 = atoi(SQLDb->GetValue(i, 2).c_str());
                ipr_ts = atoi(SQLDb->GetValue(i, 3).c_str());

                /* is this an IP range? */
                if (ipr_allowrange2 > 0)
                {
                        /* yes it is, is the client IP between range1 and range2? */
                        if ((tmpIP >= ipr_allowrange1) && (tmpIP <= ipr_allowrange2))
						                        {
                                ipr_match = true;
                                break;
                        }
                } else {
                        /* no, is it a single IP? */
                        if (ipr_allowrange1 > 0)
                        {
                                /* yes it is, does the IP match range1? */
                                if (tmpIP == ipr_allowrange1)
                                {
                                        ipr_match = true;
                                        break;
                                }
                        } 
                }
        }
        /* check if we found a match yet */
        if (!ipr_match)
        {
                /* no match, fail them */
		//TODO Send error back
                return false;
        } else {
                /* IP restriction check passed */
        }
}	

if (theUser->getFlag(sqlUser::F_GLOBAL_SUSPEND))
        {
        elog    << "doXQLogin: "
                << "Globally suspended account tried to auth: "
                << st[3]
                << endl;
	//TODO Send error back
        return false;
        }

unsigned int max_failed_logins = getConfigVar("FAILED_LOGINS")->asInt();
unsigned int failed_login_rate = getConfigVar("FAILED_LOGINS_RATE")->asInt();

/* if it's not configured, default to every 15 minutes */
if (failed_login_rate==0)
	failed_login_rate = 900;

if (!isPasswordRight(theUser, st.assemble(4)))
        {
	theUser->incFailedLogins();

	if ((max_failed_logins > 0) && (theUser->getFailedLogins() > max_failed_logins) &&
		(theUser->getLastFailedLoginTS() < (time(NULL) - failed_login_rate)))
	{
		/* we have exceeded our maximum - alert relay channel
		 * work out a checksum for the password.  Yes, I could have
  		 * just used a checksum of the original password, but this
  		 * means it's harder to 'fool' the check digit with a real
  		 * password - create MD5 from original salt stored */
		unsigned char	checksum;
		md5		hash;
		md5Digest	digest;

		if (theUser->getPassword().size() < 9)
		{
			checksum = 0;
		} else {
			string salt = theUser->getPassword().substr(0, 8);
			string guess = salt + st.assemble(2);

			hash.update( (const unsigned char *)guess.c_str(), guess.size() );
			hash.report( digest );

			checksum = 0;
			for (size_t i = 0; i < MD5_DIGEST_LENGTH; i++)
			{
				/* add ascii value to check digit */
				checksum += digest[i];
			}
		}

		theUser->setLastFailedLoginTS(time(NULL));
		logPrivAdminMessage("%d failed logins for %s (last attempt from iauth with IP %s), checksum %d).",
			theUser->getFailedLogins(),
			theUser->getUserName().c_str(),
			st[2].c_str(),
			checksum);
	
	}
        elog    << "doXQLogin: "
                << "Wrong password supplied by: "
                << st[3] << "PASS: "
		<< st.assemble(4)
                << endl;
	//TODO Send error back
        return false;
	}

/*
 * Don't exceed MAXLOGINS.
 */
//TODO

/*
 * If this user account is already authed against, send a notice to the other
 * users warning them that someone else has logged in too.
 */
//TODO



elog    << "doXQLogin: "
        << "Succesful auth for "
        << st[3] 
	<< endl;
//TODO Return confirmation
return true;
}



/*
 * Display a summary of channels "theUser" has access on to "theClient".
 * Only show those above or equal to "minlevel".
 */
void cservice::outputChannelAccesses(iClient* theClient, sqlUser* theUser, sqlUser* tmpUser, unsigned int minlevel)
{
	stringstream channelsQuery;
	string channelList ;

	channelsQuery	<< "SELECT channels.name,levels.access,levels.flags FROM levels,channels "
			<< "WHERE levels.channel_id = channels.id AND channels.registered_ts <> 0 AND levels.user_id = "
			<< theUser->getID()
			<< " AND levels.access >= "
			<< minlevel
			<< " ORDER BY levels.access DESC"
			<< ends;

	#ifdef LOG_SQL
		elog	<< "CHANINFO::sqlQuery> "
			<< channelsQuery.str().c_str()
			<< endl;
	#endif

	string chanName ;
	string chanAccess ;
	unsigned int flags;

	if( !SQLDb->Exec(channelsQuery, true ) )
//	if( PGRES_TUPLES_OK != status )
		{
		Notice( theClient,
			"Internal error: SQL failed" ) ;

		elog	<< "CHANINFO> SQL Error: "
			<< SQLDb->ErrorMessage()
			<< endl ;
		return  ;
		}

	for(unsigned int i = 0; i < SQLDb->Tuples(); i++)
		{
		flags = atoi(SQLDb->GetValue(i, 2));
		if(flags & sqlLevel::F_AUTOINVITE)
		{
			chanName = "\002" + SQLDb->GetValue(i,0) + "\002";
		} else {
			chanName = SQLDb->GetValue(i,0);
		}
		chanAccess = SQLDb->GetValue(i,1);
		
		// 4 for 2 spaces, 2 brackets + comma.
		if ((channelList.size() + chanName.size() + chanAccess.size() +5) >= 450)
			{
			Notice(theClient,
				getResponse(tmpUser,
					language::channels,
					string("Channels: %s")).c_str(),
				channelList.c_str());
			channelList.erase( channelList.begin(),
				channelList.end() ) ;
			}

		if (channelList.size() != 0)
				{
				channelList += ", ";
				}
		channelList += chanName;
		channelList += " (";
		channelList += chanAccess;
		channelList +=  ")";
		} // for()

	Notice(theClient,
		getResponse(tmpUser,
			language::channels,
			string("Channels: %s")).c_str(),
		channelList.c_str());
}

void Command::Usage( iClient* theClient )
{
bot->Notice( theClient, string( "SYNTAX: " ) + getInfo() ) ;
}

string cservice::CryptPass( const string& pass )
{
StringTokenizer st( pass ) ;

const char validChars[] = 
	"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.$*_";

string salt ;
for( unsigned short int i = 0 ; i < 8 ; ++i )
	{
	int randNo = 1 + (int) (64.0 * rand() / (RAND_MAX + 1.0) );
	salt += validChars[ randNo ] ;
	}

/* Work out a MD5 hash of our salt + password */
md5		hash; // MD5 hash algorithm object.
md5Digest	digest; // MD5Digest algorithm object.

stringstream output;
string newPass;
newPass = salt + st.assemble(0);

hash.update( (const unsigned char *)newPass.c_str(),
	newPass.size() );
hash.report( digest );

/* Convert to Hex */
int data[ MD5_DIGEST_LENGTH ] = { 0 } ;
for( size_t ii = 0; ii < MD5_DIGEST_LENGTH; ii++ )
        {
        data[ii] = digest[ii];
        }

output << std::hex;
output.fill('0');
for( size_t ii = 0; ii < MD5_DIGEST_LENGTH; ii++ )
        {
        output << std::setw(2) << data[ii];
        }
output << ends;

return string( salt + output.str()  );
}

bool cservice::SendMail(const string& address, const string& subject, const stringstream& mailtext)
{
	std::ofstream TMail;
	TMail.open("mailbody.txt", std::ios::out);
	if (!TMail)
	{
		logDebugMessage(" *** ERROR while sending email");
		return false;
	}
	TMail << mailtext.str().c_str();
	TMail.close();
	stringstream mailcommand;
	mailcommand << "mail -s " << "\"" << subject.c_str() << "\" -r \"" << this->sendmailFrom.c_str() << "\" " << address.c_str() << " < mailbody.txt" << endl << ends;
	elog << "mailcommand = '" << mailcommand.str() << endl << ends;
	//Devnote: alternate way of sending mail: echo "Mail body text here" | mail -s "Subject" -r "from@address" email@address.com
	system(mailcommand.str().c_str());
	return true;
}

bool cservice::addGline( csGline* TempGline)
{

glineIterator ptr;
if(TempGline->getHost().substr(0,1) == "$") //check if its a realname gline
        {
	return true;
        }
else
        {
        ptr = glineList.find(TempGline->getHost());
        if(ptr != glineList.end())
                {
                if(ptr->second != TempGline)
                        {
                        delete ptr->second;
                        glineList.erase(ptr);
                        }
                }
        glineList[TempGline->getHost()] = TempGline;

        }
return true;
}

bool cservice::remGline( csGline* TempGline)
{
if(TempGline->getHost().substr(0,1) == "$")
        {
	return true;
        }
else
        {
        glineList.erase(TempGline->getHost()) ;
        }
return true;
}

csGline* cservice::findGline( const string& HostName )
{
glineIterator ptr = glineList.find(HostName);
if(ptr != glineList.end())
        {
        return ptr->second;
        }

return NULL ;
}

bool cservice::loadGlines()
{
static const char *Main = "SELECT Id,Host,AddedBy,AddedOn,ExpiresAt,LastUpdated,Reason FROM glines";

stringstream eraseQuery;
eraseQuery	<< "DELETE FROM glines";
if( !SQLDb->Exec( eraseQuery, true ) )
	{
	elog    << "cservice::loadGlines::Erase> SQL Failure: "
		<< SQLDb->ErrorMessage()
		<< endl;
	return false;
	}

stringstream theQuery;
theQuery        << Main
                << ends;

#ifdef LOG_SQL
elog    << "cservice::loadGlines> "
        << theQuery.str().c_str()
        << endl;
#endif

if( !SQLDb->Exec( theQuery, true ) )
//if( PGRES_TUPLES_OK != status )
        {
        elog    << "cservice::loadGlines> SQL Failure: "
                << SQLDb->ErrorMessage()
                << endl ;

        return false;
        }

csGline *tempGline = NULL;

for( unsigned int i = 0 ; i < SQLDb->Tuples() ; i++ )
        {
        tempGline =  new (std::nothrow) csGline(SQLDb);
        assert( tempGline != NULL ) ;

        tempGline->setId(SQLDb->GetValue(i,0));
        tempGline->setHost(SQLDb->GetValue(i,1));
        tempGline->setAddedBy(SQLDb->GetValue(i,2)) ;
        tempGline->setAddedOn(static_cast< time_t >(
                atoi( SQLDb->GetValue(i,3).c_str() ) )) ;
        tempGline->setExpires(static_cast< time_t >(
                atoi( SQLDb->GetValue(i,4).c_str() ) )) ;
        tempGline->setLastUpdated(static_cast< time_t >(
                atoi( SQLDb->GetValue(i,5).c_str() ) )) ;
        tempGline->setReason(SQLDb->GetValue(i,6));
        addGline(tempGline);
        }
return true;
}

bool cservice::expireGlines()
{

int totalFound = 0;
csGline * tempGline;
list<string> remList;
list<string>::iterator remIterator;
for(glineIterator ptr = glineList.begin();ptr != glineList.end();++ptr)
        {
        tempGline = ptr->second;
        if((tempGline->getExpires() <= ::time(0))
            && ((tempGline->getHost().substr(0,1) != "#") ||
            (tempGline->getExpires() != 0)))

                {
                //remove the gline from the database
                tempGline->Delete();
                remList.push_back(ptr->first);
//              ptr = glineList.erase(ptr);
                delete tempGline;
                ++totalFound;
                }
        }

for(remIterator = remList.begin();remIterator != remList.end();)
        {
        glineList.erase(*remIterator);
        remIterator = remList.erase(remIterator);
        }

if(totalFound > 0)
     elog 	<< "[Refresh Glines]: "
     		<< totalFound << " expired"
		<< endl;
return true;

}

bool cservice::expireWhitelist()
{
stringstream whitelistQuery;
whitelistQuery	<< "DELETE FROM whitelist WHERE "
		<< "expiresat <= date_part('epoch', CURRENT_TIMESTAMP)::int AND "
		<< "expiresat != 0" << ends;

#ifdef LOG_SQL
        elog    << "sqlQuery> "
                << whitelistQuery.str().c_str()
                << endl;
#endif

if( !SQLDb->Exec(whitelistQuery, true ) )
	{
	elog	<< "cservice::expireBans> SQL Error: "
		<< SQLDb->ErrorMessage()
		<< endl ;
	return false;
	}
return true;
}

void cservice::addGlineToUplink(csGline* theGline)
{
	int Expires;
	if((theGline->getHost().substr(0,1) == "#")
			&& (theGline->getExpires() == 0))
	{
		Expires = 730*3600*24; //gline::PERM_TIME;
	}
	else
	{
		Expires = theGline->getExpires() - time(0);
	}
	MyUplink->setGline(theGline->getAddedBy()
    ,theGline->getHost(),theGline->getReason()
    ,Expires,theGline->getLastUpdated(),this);
}

} // namespace gnuworld
