/**
 * LOGINCommand.cc
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
 * USA.
 *
 * $Id: LOGINCommand.cc,v 1.67 2009/06/09 15:40:29 mrbean_ Exp $
 */

#include	<string>
#include	<sstream>
#include	<iostream>
#include	<iomanip>
#include	<inttypes.h>

#include	"StringTokenizer.h"
#include	"ELog.h"
#include	"cservice.h"
#include	"md5hash.h"
#include	"responses.h"
#include	"levels.h"
#include	"networkData.h"
#include	"cservice_config.h"
#include	"Network.h"
#include	"ip.h"
#include	"sqlIncompleteChannel.h"

const char LOGINCommand_cc_rcsId[] = "$Id: LOGINCommand.cc,v 1.67 2009/06/09 15:40:29 mrbean_ Exp $" ;

namespace gnuworld
{
struct autoOpData {
	unsigned int channel_id;
	unsigned int flags;
	time_t suspend_expires;
} aOp;

using std::string ;
using std::endl ;
using std::ends ;
using std::stringstream ;
using namespace gnuworld;

bool LOGINCommand::Exec( iClient* theClient, const string& Message )
{
bot->incStat("COMMANDS.LOGIN");

StringTokenizer st( Message ) ;
if( st.size() < 3 )
	{
	Usage(theClient);
	return true;
	}

/*
 * Are we allowing logins yet?
 */
unsigned int useLoginDelay = bot->getConfigVar("USE_LOGIN_DELAY")->asInt();
unsigned int loginTime = bot->getUplink()->getStartTime() + bot->loginDelay;
if ( (useLoginDelay == 1) && (loginTime >= (unsigned int)bot->currentTime()) )
	{
	bot->Notice(theClient, "AUTHENTICATION FAILED as %s. (Unable "
		"to login during reconnection, please try again in "
		"%i seconds)",
		st[1].c_str(), (loginTime - bot->currentTime()));
	return false;
	}

/*
 * Check theClient isn't already logged in, if so, tell
 * them they shouldn't be.
 */

sqlUser* tmpUser = bot->isAuthed(theClient, false);
if (tmpUser)
	{
	bot->Notice(theClient,
		bot->getResponse(tmpUser, language::already_authed).c_str(),
		tmpUser->getUserName().c_str());
	return false;
	}

/*
 * Check if this client has exceeded their max logins
 */

unsigned int maxFailedLogins = bot->getConfigVar("MAX_FAILED_LOGINS")->asInt();
unsigned int failedLogins = bot->getFailedLogins(theClient);
if ((maxFailedLogins > 0) && (failedLogins >= maxFailedLogins))
{
	/* exceeded maximum failed logins */
	bot->Notice(theClient,
		bot->getResponse(tmpUser,
		language::max_failed_logins,
		string("AUTHENTICATION FAILED as %s (Exceeded maximum login failures for this session)")).c_str(),
		st[1].c_str());
	return false;
}

/*
 * Find the user record, confirm authorisation and attach the record
 * to this client.
 */

if(st[1][0] == '#')
	{
	bot->setFailedLogins(theClient, failedLogins+1);
	bot->Notice(theClient, "AUTHENTICATION FAILED as %s.", st[1].c_str());
	return false;
	}

// TODO: Force a refresh of the user's info from the db
sqlUser* theUser = bot->getUserRecord(st[1]);
if( !theUser )
	{
	bot->setFailedLogins(theClient, failedLogins+1);
	bot->Notice(theClient,
		bot->getResponse(tmpUser,
			language::not_registered,
			string("AUTHENTICATION FAILED as %s.")).c_str(),
		st[1].c_str());
	return false;
	}

if ((theUser->getFlag(sqlUser::F_GLOBAL_SUSPEND)) && (!theUser->getFlag(sqlUser::F_POWER)))
	{
	bot->setFailedLogins(theClient, failedLogins+1);
	bot->Notice(theClient, "AUTHENTICATION FAILED as %s. (Suspended)",
	st[1].c_str());
	return false;
	}
int pass_end = st.size();

#ifdef TOTP_AUTH_ENABLED
bool totp_enabled = false;
if(bot->totpAuthEnabled && theUser->getFlag(sqlUser::F_TOTP_ENABLED)) {
        if(st.size() < 4) {
                bot->Notice(theClient,"AUTHENTICATION FAILED as %s. (Missing TOTP token)",st[1].c_str());
                return false;
        }
        pass_end = st.size()-1;
        totp_enabled = true;
}
#endif

/*
 * Check password, if its wrong, bye bye.
 */

unsigned int max_failed_logins = bot->getConfigVar("FAILED_LOGINS")->asInt();
unsigned int failed_login_rate = bot->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 ((!bot->isPasswordRight(theUser, st.assemble(2,pass_end)))
		&& (theUser->getRecoveryPassword() != st.assemble(2,pass_end)))
	{
	bot->setFailedLogins(theClient, failedLogins+1);
	bot->Notice(theClient,
		bot->getResponse(theUser,
			language::auth_failed,
			string("AUTHENTICATION FAILED as %s.")).c_str(),
		theUser->getUserName().c_str());
	/* increment failed logins counter */
	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));
		bot->logPrivAdminMessage("%d failed logins for %s (last attempt by %s, checksum %d).",
			theUser->getFailedLogins(),
			theUser->getUserName().c_str(),
			theClient->getRealNickUserHost().c_str(),
			checksum);
	}
	return false;
	}
#ifdef TOTP_AUTH_ENABLED
	if (totp_enabled)
	{
		OathResult::OATH_RESULT_TYPE response = theUser->validateTOTP(st[st.size()-1].c_str());
		if (response != OathResult::OK)
		{
			if (response == OathResult::ERROR)
			{
				bot->Notice(theClient, "AUTHENTICATION FAILED as %s due to an error, please contact a CService representative", st[1].c_str());
			}
			if (response == OathResult::INVALID_TOKEN)
			{
				bot->setFailedLogins(theClient, failedLogins+1);
					bot->Notice(theClient,
							bot->getResponse(theUser,
									language::auth_failed_token,
									string("AUTHENTICATION FAILED as %s. (Invalid Token)")).c_str(),
								theUser->getUserName().c_str());
					/* increment failed logins counter */
					theUser->incFailedLogins();
			}
			return false;
		}
	}
#endif

/*
 * Check if this is a privileged user, if so check against IP restrictions
 */
if ((bot->getAdminAccessLevel(theUser, true) > 0) && (!theUser->getFlag(sqlUser::F_ALUMNI)))
{
	/* ok, they have "*" access (excluding alumni's) */
	if (!bot->checkIPR(theClient, theUser))
	{
		bot->setFailedLogins(theClient, failedLogins+1);
		bot->Notice(theClient, "AUTHENTICATION FAILED as %s. (IPR)",  
			st[1].c_str());
		/* notify the relay channel */
		bot->logAdminMessage("%s (%s) failed IPR check.",
			theClient->getNickName().c_str(),
			st[1].c_str());
		/* increment failed logins counter */
		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 */
			theUser->setLastFailedLoginTS(time(NULL));
			bot->logPrivAdminMessage("%d failed logins for %s (last attempt by %s).",
				theUser->getFailedLogins(),
				theUser->getUserName().c_str(),
				theClient->getRealNickUserHost().c_str());
		}
		return false;
	}
}

/*
 * Don't exceed MAXLOGINS.
 */

bool iploginallow = false;
if(theUser->networkClientList.size() + 1 > theUser->getMaxLogins())
	{
	/* They have exceeded their maxlogins setting, but check if they
	   are allowed to login from the same IP - only applies if their
	   maxlogins is set to ONE */
	uint32_t iplogins = bot->getConfigVar("LOGINS_FROM_SAME_IP")->asInt();
	uint32_t iploginident = bot->getConfigVar("LOGINS_FROM_SAME_IP_AND_IDENT")->asInt();
	if ((theUser->getMaxLogins() == 1) && (iplogins > 1))
	{
		/* ok, we're using the multi-logins feature (0=disabled) */
		if (theUser->networkClientList.size() + 1 <= iplogins)
		{
			/* Check their IP from previous session against
			   current IP.  If it matches, allow the login.
			   As this only applies if their maxlogin is 1, we
			   know there is only 1 entry in their clientlist */
			if (theClient->getIP() == theUser->networkClientList.front()->getIP())
			{
				if (iploginident==1)
				{
					/* need to check ident here */
					string oldident = theUser->networkClientList.front()->getUserName();
					string newident = theClient->getUserName();
					if ((oldident[0]=='~') || (oldident==newident))
					{
						/* idents match (or they are unidented) - allow this login */
						iploginallow = true;
					}
				} else {
					/* don't need to check ident, this login is allowed */
					iploginallow = true;
				}
			}
		}
	}
	if (!iploginallow)
	{
		bot->setFailedLogins(theClient, failedLogins+1);
		bot->Notice(theClient, "AUTHENTICATION FAILED as %s. (Maximum "
			"concurrent logins exceeded).",
			theUser->getUserName().c_str());

		string clientList;
		for( sqlUser::networkClientListType::iterator ptr = theUser->networkClientList.begin() ;
			ptr != theUser->networkClientList.end() ; )
			{
			clientList += (*ptr)->getNickUserHost();
			++ptr;
			if (ptr != theUser->networkClientList.end())
				{
				clientList += ", ";
				}
			} // for()

		bot->Notice(theClient, "Current Sessions: %s", clientList.c_str());
		return false;
		} // for()
	}

/*
 * If this user account is already authed against, send a notice to the other
 * users warning them that someone else has logged in too.
 */
if(theUser->isAuthed())
{
	bot->noticeAllAuthedClients(theUser,
	"%s has just authenticated as you (%s). "
	"If this is not you, your account may have been compromised. "
	"If you wish to suspend all your access as a precautionary measure, "
	"type '\002/msg %s@%s suspendme <password>'\002 and contact a "
	"CService representative to resolve the problem."
	" \002** Note: You will NOT be able to use your account after "
	"you issue this command **\002",
		theClient->getNickUserHost().c_str(),
		theUser->getUserName().c_str(),
		bot->getNickName().c_str(),
		bot-> getUplinkName().c_str());
}

/* 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="
		<< theUser->getID()
		<< ends;
#ifdef LOG_SQL
	elog	<< "LOGIN::sqlQuery> "
		<< queryString.str().c_str()
		<< endl;
#endif

if( bot->SQLDb->Exec(queryString, true ) )
//if (PGRES_TUPLES_OK == status)
{
	if (bot->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("
				<< theUser->getID()
				<< ",date_part('epoch', CURRENT_TIMESTAMP)::int,date_part('epoch', CURRENT_TIMESTAMP)::int)"
				<< ends;

#ifdef LOG_SQL
		elog	<< "LOGIN::sqlQuery> "
			<< updateQuery.str().c_str()
			<< endl;
#endif
		bot->SQLDb->Exec(updateQuery);
	}
}
/* update their details */
theUser->setLastSeen(bot->currentTime(), theClient->getNickUserHost(),
	theClient->getNickName() + "!" + theClient->getUserName() + "@" + xIP( theClient->getIP()).GetNumericIP());
theUser->setFlag(sqlUser::F_LOGGEDIN);
theUser->addAuthedClient(theClient);

networkData* newData =
	static_cast< networkData* >( theClient->getCustomData(bot) ) ;
if( NULL == newData )
	{
	bot->Notice( theClient,
		"Internal error." ) ;
	elog	<< "LOGINCommand> newData is NULL for: "
		<< theClient
		<< endl ;
	return false ;
	}

// Pointer back to the sqlUser from this iClient.
newData->currentUser = theUser;

bot->Notice(theClient,
	bot->getResponse(theUser, language::auth_success).c_str(),
	theUser->getUserName().c_str());

/* set failed logins counter back to zero after alerting the user (if required) */
if (bot->getConfigVar("ALERT_FAILED_LOGINS")->asInt()==1 &&
	theUser->getFailedLogins() > 0)
{
	/* ok, alert the user */
	bot->Notice(theClient,
		bot->getResponse(theUser,
			language::auth_failed_logins,
			string("There were %d failed login attempts since your last successful login.")).c_str(),
			theUser->getFailedLogins());
}
theUser->setFailedLogins(0);
theUser->setLastFailedLoginTS(0);

int tmpLevel = bot->getAdminAccessLevel(theUser);
if (tmpLevel > 0)
	{
		/* this is a privileged user, send a notice to _info */
		if (tmpLevel < 900)
		{
			/* regular admin, report to normal relay channel */
			bot->logAdminMessage("%s (%s) has authenticated (level %d)",
				theClient->getNickName().c_str(),
				theUser->getUserName().c_str(),
				tmpLevel);
		} else {
			/* if this is a high level admin, report to privileged relay channel */
			bot->logPrivAdminMessage("%s (%s) has authenticated (level %d)",
				theClient->getNickName().c_str(),
				theUser->getUserName().c_str(),
				tmpLevel);
		}
	} else {
	string greeting = bot->getResponse(theUser, language::greeting);
	if (!greeting.empty())
		{
		bot->Notice(theClient, greeting.c_str());
		}
	}

/*
 * Send out AC token onto the network.
 * The AC Token (Account) is read by ircu2.10.11 and any other
 * service on the net that cares and added to the Client structure.
 * Format:
 * [Source Server] AC [Authenticated User's Numeric] [Authenticated UserName]
 * Eg: AX AC APAFD gte
 */
server->UserLogin(theClient, theUser->getUserName(), bot);
if (!theUser->getHostName().empty())
	server->SendOutFakeHost(theClient, theUser->getHostName().c_str(), bot);

/*
 * If the user account has been suspended, make sure they don't get
 * auto-opped.
 */

if (theUser->getFlag(sqlUser::F_GLOBAL_SUSPEND))
	{
	bot->Notice(theClient,
		"..however your account has been suspended by a CService administrator."
		" You will be unable to use any channel access you may have.");
	return true;
	}

cservice::incompleteChanRegsType::const_iterator theApp =  bot->incompleteChanRegs.find(theUser->getID());
if (theApp != bot->incompleteChanRegs.end())
{
	sqlIncompleteChannel *chanApp = theApp->second;
	string chanName = bot->getPendingChanName(chanApp->chanId);
	bot->Notice(theClient, "You have an incomplete channel application of %s", chanName.c_str());
	if (!chanApp->RealName.empty())
	{
		bot->Notice(theClient, "So far you've enumerated the following:");
		bot->Notice(theClient, "Your Real Name: %s", chanApp->RealName.c_str());
	}
	else
	{
		bot->Notice(theClient,"Now specify your Real Name by typing /msg %s REGISTER %s REALNAME <Your RealName>", bot->getNickName().c_str(), chanName.c_str());
		bot->Notice(theClient,"To cancel/abort your application any time, type /msg %s CANCEL %s YES", bot->getNickName().c_str(), chanName.c_str());
	}
	if ((!chanApp->RealName.empty()) && (chanApp->Description.empty()))
	{
		bot->Notice(theClient,"Now specify your channel's description by typing /msg %s REGISTER %s DESCRIPTION <description>", bot->getNickName().c_str(),chanName.c_str());
		if (chanApp->supps.empty())
			bot->Notice(theClient,"To cancel/abort your application any time, type /msg %s CANCEL %s YES", bot->getNickName().c_str(),chanName.c_str());
	}
	if (!chanApp->Description.empty()) bot->Notice(theClient, "Description: %s", chanApp->Description.c_str());
	if ((!chanApp->RealName.empty()) && (!chanApp->Description.empty()))
	{
		bot->Notice(theClient,"Now enumerate your supporters by typing /msg %s REGISTER %s SUPPORTERS <supporter1 supporter2 supporter3 ...>", bot->getNickName().c_str(),chanName.c_str());
		bot->Notice(theClient,"To cancel/abort your application any time, type /msg %s CANCEL %s YES", bot->getNickName().c_str(),chanName.c_str());
	}
}

/*
 * The fun part! For all channels this user has access on, and has
 * AUTOP set, and isn't already op'd on - do the deed.
 */

stringstream theQuery;
theQuery	<< "SELECT channel_id,flags,suspend_expires FROM "
		<< "levels WHERE user_id = "
		<< theUser->getID()
		<< ends;

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

if( !bot->SQLDb->Exec(theQuery, true ) )
//if( PGRES_TUPLES_OK != status )
	{
	elog	<< "LOGIN> SQL Error: "
		<< bot->SQLDb->ErrorMessage()
		<< endl ;
	return false ;
	}

typedef vector < autoOpData > autoOpVectorType;
autoOpVectorType autoOpVector;

for(unsigned int i = 0; i < bot->SQLDb->Tuples(); i++)
	{
	autoOpData current;

	current.channel_id = atoi(bot->SQLDb->GetValue(i, 0));
	current.flags = atoi(bot->SQLDb->GetValue(i, 1));
	current.suspend_expires = atoi(bot->SQLDb->GetValue(i, 2));

	autoOpVector.push_back( autoOpVectorType::value_type(current) );
	}

for (autoOpVectorType::const_iterator resultPtr = autoOpVector.begin();
	resultPtr != autoOpVector.end(); ++resultPtr)
	{

	/* If the autoop flag isn't set in this record */
	if (!(resultPtr->flags & sqlLevel::F_AUTOOP) &&
		!(resultPtr->flags & sqlLevel::F_AUTOHALFOP) &&
		!(resultPtr->flags & sqlLevel::F_AUTOVOICE) &&
		!(resultPtr->flags & sqlLevel::F_AUTOINVITE))
		{
		continue;
		}

	sqlChannel* theChan = bot->getChannelRecord(resultPtr->channel_id);
	if (!theChan)
		{
		continue;
		}

	/*
	 * Make sure the channel isn't suspended..
	 */

	if (theChan->getFlag(sqlChannel::F_SUSPEND))
		{
		continue;
		}


	/*
	 * Check if they're already opped.
	 */

	Channel* netChan = Network->findChannel(theChan->getName());
	if (!netChan)
		{
		continue;
		}


	/*
	 * Don't attempt to op/invite if we're not in the channel, or not op'd.
	 */

	ChannelUser* tmpBotUser = netChan->findUser(bot->getInstance());
	if (!tmpBotUser)
		{
		continue;
		}

	if (!theChan->getInChan() || !tmpBotUser->getMode(ChannelUser::MODE_O))
		{
		continue;
		}

	/*
	 *  Would probably be wise to check they're not suspended too :)
	 *  (*smack* Ace)
	 */

	if(resultPtr->suspend_expires > bot->currentTime() )
		{
		continue;
		}

	ChannelUser* tmpChanUser = netChan->findUser(theClient) ;
	if(!tmpChanUser) {
		//The user is not in the channel, lets see if their autoinvite is on and act upon it
		if (resultPtr->flags & sqlLevel::F_AUTOINVITE)  {
			int level = bot->getEffectiveAccessLevel(theUser, theChan, true);
			if(level >= level::invite) {
				sqlBan* tmpBan = bot->isBannedOnChan(theChan, theClient);
				if (tmpBan && tmpBan->getLevel() >= 75) {
					bot->Notice(theClient, "Can't invite you to channel %s, you are banned", theChan->getName().c_str());
					continue;
				}
				bot->Invite(theClient,netChan);
			}
		}
		continue;
	}

	/*
	 * Check if the channel is NOOP.
	 * N.B: If the channel is strictop, we op them.
	 * They've just logged in! :P
	 */

	if(theChan->getFlag(sqlChannel::F_NOOP))
		{
		continue;
		}

	/*
	 * Check they aren't banned < 75 in the chan.
	 */

	sqlBan* tmpBan = bot->isBannedOnChan(theChan, theClient);
	if( tmpBan) {
		if (tmpBan->getLevel() < 75)  {
			continue;
		} else {
			bot->Kick(netChan,theClient,tmpBan->getReason());
			continue;
		}
	}

	/*
 	 *  If its AUTOOP, check for op's and do the deed.
	 *  Otherwise, its just AUTOVOICE :)
	 */

	if (resultPtr->flags & sqlLevel::F_AUTOOP)
		{
		if(!tmpChanUser->getMode(ChannelUser::MODE_O))
			{
			bot->Op(netChan, theClient);
			}
		}
	else if (resultPtr->flags & sqlLevel::F_AUTOHALFOP)
		{
		if(!tmpChanUser->getMode(ChannelUser::MODE_H))
			{
			bot->HalfOp(netChan, theClient);
			}
		}
	else if ((resultPtr->flags & sqlLevel::F_AUTOVOICE) && (!theChan->getFlag(sqlChannel::F_NOVOICE)))
		{
		if(!tmpChanUser->getMode(ChannelUser::MODE_V))
			{
			bot->Voice(netChan, theClient);
			}
		}

	}

/*
 *  And last but by no means least, see if we have been nominated as
 *  a supporter for a channel.
 */

stringstream supporterQuery;
supporterQuery	<< "SELECT channels.name FROM"
			<< " supporters,channels,pending WHERE"
			<< " supporters.channel_id = channels.id"
			<< " AND pending.channel_id = channels.id"
			<< " AND channels.registered_ts = 0"
			<< " AND supporters.support = '?'"
			<< " AND pending.status = 0"
			<< " AND user_id = "
			<< theUser->getID()
			<< ends;

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

if( !bot->SQLDb->Exec(supporterQuery, true ) )
//if( PGRES_TUPLES_OK != status )
	{
	elog	<< "LOGIN> SQL Error: "
		<< bot->SQLDb->ErrorMessage()
		<< endl ;
	return false ;
	}


for(unsigned int i = 0; i < bot->SQLDb->Tuples(); i++)
	{
	string channelName = bot->SQLDb->GetValue(i, 0);
	bot->Notice(theClient, "You have been named as a supporter in a "
		"new channel application for %s. You may visit the "
		"website to register your support or to make an "
		"objection. Alternatively, you can"
		" type '\002/msg %s support %s YES\002' or '\002/msg %s "
		"support %s NO\002' to confirm or deny your support.",
		channelName.c_str(),
		bot->getNickName().c_str(),
		channelName.c_str(),
		bot->getNickName().c_str(),
		channelName.c_str());
	}

/*
 * Check for modinfo notices.
 */
#ifdef USE_NOTICES
	stringstream noticeQuery;
	noticeQuery	<< "SELECT message FROM notices "
			<< "WHERE user_id = "
			<< theUser->getID()
			<< " ORDER BY last_updated ASC"
			<< ends;

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

	if (!bot->SQLDb->Exec(noticeQuery, true))
	{
#ifdef LOG_SQL
		elog	<< "LOGINNoteAllAuthedClients::commit> Something went wrong: "
			<< bot->SQLDb->ErrorMessage()
			<< endl;
#endif
		bot->logDebugMessage("LOGINNoteAllAuthedClients",bot->SQLDb->ErrorMessage().c_str());
	return false;
	}
	
	unsigned int count = bot->SQLDb->Tuples();
	if(count)
	    for (unsigned int i=0; i<count; i++)
		bot->Notice(theClient,bot->SQLDb->GetValue(i,0).c_str());

 /* Delete automatically the notices */

	noticeQuery.str("");
	noticeQuery	<< "DELETE FROM notices WHERE user_id = "
			<< theUser->getID()
			<< ends;

	if (!bot->SQLDb->Exec(noticeQuery, true))
	{
#ifdef LOG_SQL
	elog	<< "LOGIN::deleteNotices::sqlQuery> "
		<< noticeQuery.str().c_str()
		<< endl;
#endif
		bot->logDebugMessage("LOGIN-DeleteNoteAllAuthedClients",bot->SQLDb->ErrorMessage().c_str());
	return false;
	}

#endif

/*
 * See if they have any notes, but first check if Notes are enabled.
 */

#ifdef USE_NOTES
   #ifndef USE_USERS_NOTES
	if (theUser->getAdminAccessLevel() == 0) 
		return true;
   #endif
if(!theUser->getFlag(sqlUser::F_NONOTES))
{
	stringstream noteQuery;
	noteQuery.str("");
	noteQuery	<< "SELECT message_id FROM notes "
			<< "WHERE user_id = "
			<< theUser->getID()
			<< ends;

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

	bot->SQLDb->Exec(noteQuery, true) ;

	count = bot->SQLDb->Tuples();
	if(count)
		{
		bot->Notice(theClient, bot->getResponse(theUser,language::have_notes,string("You have %i note(s). To read them type /msg %s notes read all")).c_str(), count, bot->getNickName().c_str());
		}
	}

#endif

if (theUser->getFlag(sqlUser::F_AUTONICK))
{
	iClient* tmpClient = Network->findNick(theUser->getNickName());
	if (tmpClient)
	{
		if (!tmpClient->getMode(iClient::MODE_SERVICES))
		{
			sqlUser* tmpUser2 = bot->isAuthed(tmpClient, false);
			if (!tmpUser2)
			{
				bot->generateNickName(tmpClient);
			}
			else if (tmpUser2->getUserName() != theUser->getUserName())
			{
				bot->generateNickName(tmpClient);
			}
		}
	}
	else
		bot->Write("%s SN %s %s", bot->getCharYY().c_str(), theClient->getCharYYXXX().c_str(), theUser->getNickName().c_str());
}

// If we have a login with recovery password, replace the actual password with the recovery password
if (theUser->getRecoveryPassword() == st.assemble(2, pass_end))
{
	string cryptpass = bot->CryptPass(theUser->getRecoveryPassword().c_str());
	theUser->setPassword(cryptpass.c_str());
	theUser->setRecoveryPassword(string());
	theUser->commit(theClient);
}

// If successful login reached, the recovery password has to be cleared all the time
theUser->setRecoveryPassword(string());

return true;
}

} // namespace gnuworld.

