/* +------------------------------------+
* | Inspire Internet Relay Chat Daemon |
* +------------------------------------+
*
* InspIRCd: (C) 2002-2011 InspIRCd Development Team
* See: http://wiki.inspircd.org/Credits
*
* This program is free but copyrighted software; see
* the file COPYING for details.
*
* ---------------------------------------------------
*/
#include "inspircd.h"
#include "protocol.h"
#include "account.h"
#include "hash.h"
/* $ModDesc: Store account information and allow logging in to them ircd-side */
static dynamic_reference<AccountProvider> account("account");
class AccountDBEntryImpl : public AccountDBEntry
{
public:
AccountDBEntryImpl(const irc::string& nameref, time_t ourTS, std::string h = "", std::string p = "", time_t h_p_ts = 0, std::string cc = "", time_t cc_ts = 0) : AccountDBEntry(nameref, ourTS, h, p, h_p_ts, cc, cc_ts)
{
}
virtual CullResult cull()
{
return Extensible::cull();
}
virtual ~AccountDBEntryImpl()
{
}
};
class AccountDBProviderImpl : public AccountDBProvider
{
public:
AccountDB db;
AccountDBProviderImpl(Module* parent) : AccountDBProvider(parent) {}
AccountDBEntry* AddAccount(bool send, const irc::string& nameref, time_t ourTS, std::string h = "", std::string p = "", time_t h_p_ts = 0, std::string cc = "", time_t cc_ts = 0)
{
if(db.find(nameref) != db.end())
return NULL;
AccountDBEntry* entry = new AccountDBEntryImpl(nameref, ourTS, h, p, h_p_ts, cc, cc_ts);
db.insert(std::make_pair(nameref, entry));
if(send)
SendAccount(entry);
return entry;
}
AccountDBEntry* GetAccount(irc::string acctname, bool alias) const
{
if(alias)
{
GetAccountByAliasEvent e(creator, acctname);
if(e.entry)
return e.entry;
}
AccountDB::const_iterator res = db.find(acctname);
return res != db.end() ? res->second : NULL;
}
void RemoveAccount(bool send, AccountDBEntry* entry)
{
db.erase(entry->name);
if(account)
for (user_hash::const_iterator i = ServerInstance->Users->clientlist->begin(); i != ServerInstance->Users->clientlist->end(); ++i)
{
if(!IS_LOCAL(i->second))
continue;
if(entry->name == account->GetAccountName(i->second))
{
account->DoLogin(i->second, "", "");
i->second->WriteServ("NOTICE %s :Account %s has been dropped", i->second->nick.c_str(), entry->name.c_str());
}
}
if(send)
SendRemoval(entry->name, entry->ts);
entry->cull();
delete entry;
}
const AccountDB& GetDB() const
{
return db;
}
void SendAccount(const AccountDBEntry* entry) const
{
std::vector<std::string> params;
params.push_back("*");
params.push_back("SVSACCOUNT");
params.push_back("ADD");
params.push_back(entry->name);
params.push_back(":" + ConvToStr(entry->ts));
ServerInstance->PI->SendEncapsulatedData(params);
params.clear();
params.push_back("*");
params.push_back("SVSACCOUNT");
params.push_back("SET");
params.push_back(entry->name);
params.push_back(ConvToStr(entry->ts));
params.push_back("hash_password");
params.push_back(ConvToStr(entry->hash_password_ts));
params.push_back(":" + entry->hash + " " + entry->password);
ServerInstance->PI->SendEncapsulatedData(params);
for(Extensible::ExtensibleStore::const_iterator it = entry->GetExtList().begin(); it != entry->GetExtList().end(); ++it)
{
ExtensionItem* item = it->first;
std::string value = item->serialize(FORMAT_NETWORK, entry, it->second);
if (!value.empty())
{
params.clear();
params.push_back("*");
params.push_back("SVSACCOUNT");
params.push_back("SET");
params.push_back(entry->name);
params.push_back(ConvToStr(entry->ts));
params.push_back(item->name);
params.push_back(value);
ServerInstance->PI->SendEncapsulatedData(params);
}
}
AccountDBModifiedEvent(creator, entry->name, entry).Send();
}
void SendUpdate(const AccountDBEntry* entry, std::string field) const
{
std::vector<std::string> params;
params.push_back("*");
params.push_back("SVSACCOUNT");
params.push_back("SET");
params.push_back(entry->name);
params.push_back(ConvToStr(entry->ts));
params.push_back(field);
if(field == "hash_password")
{
params.push_back(ConvToStr(entry->hash_password_ts));
params.push_back(":" + entry->hash + " " + entry->password);
}
else
{
ExtensionItem* ext = ServerInstance->Extensions.GetItem(field);
if(!ext)
return;
Extensible::ExtensibleStore::const_iterator it = entry->GetExtList().find(ext);
if(it == entry->GetExtList().end())
return;
std::string value = ext->serialize(FORMAT_NETWORK, entry, it->second);
if(value.empty())
return;
params.push_back(value);
}
ServerInstance->PI->SendEncapsulatedData(params);
AccountDBModifiedEvent(creator, entry->name, entry).Send();
}
void SendRemoval(irc::string acctname, time_t ts) const
{
std::vector<std::string> params;
params.push_back("*");
params.push_back("SVSACCOUNT");
params.push_back("DEL");
params.push_back(acctname);
params.push_back(ConvToStr(ts));
ServerInstance->PI->SendEncapsulatedData(params);
AccountDBModifiedEvent(creator, acctname, NULL).Send();
}
};
/** Handle /SVSACCOUNT
*/
class CommandSvsaccount : public Command
{
public:
AccountDBProviderImpl prov;
CommandSvsaccount(Module* Creator) : Command(Creator,"SVSACCOUNT", 3, 6), prov(Creator)
{
flags_needed = FLAG_SERVERONLY; syntax = "ADD|SET|DEL <account name> <account TS> [key] [value TS] [value]";
}
CmdResult Handle (const std::vector<std::string>& parameters, User *user)
{
GetAccountByAliasEvent e(creator, parameters[1]);
AccountDB::iterator iter = prov.db.find(parameters[1]);
time_t ts = atol(parameters[2].c_str());
if(parameters[0] == "SET")
{
if(parameters.size() < 5)
return CMD_INVALID; /* this form of the command needs at least 5 parameters */
if(iter == prov.db.end())
return CMD_FAILURE; /* if this ever happens, we're desynced */
if(e.entry)
{
if(e.alias_ts < ts)
return CMD_FAILURE;
e.RemoveAlias();
}
if(iter->second->ts < ts)
return CMD_FAILURE; /* we have an older account with the same name */
if(iter->second->ts > ts)
{
/* Nuke the entry. */
prov.RemoveAccount(false, iter->second);
AccountDBEntry* entry = new AccountDBEntryImpl(parameters[1], ts);
iter = prov.db.insert(std::make_pair(parameters[1], entry)).first;
}
ExtensionItem* ext = ServerInstance->Extensions.GetItem(parameters[3]);
if (ext)
{
if(parameters.size() > 5)
ext->unserialize(FORMAT_NETWORK, iter->second, parameters[4] + " " + parameters[5]);
else
ext->unserialize(FORMAT_NETWORK, iter->second, parameters[4]);
}
if(parameters[3] == "hash_password")
{
time_t theirTS = atol(parameters[4].c_str());
if(iter->second->hash_password_ts > theirTS)
return CMD_FAILURE;
std::string theirhash, theirpassword;
std::string::size_type delim;
if(parameters.size() > 5 && (delim = parameters[5].find_first_of(' ')) != std::string::npos)
{
theirhash = parameters[5].substr(0, delim);
theirpassword = parameters[5].substr(delim + 1);
}
else
theirhash = theirpassword = "";
if(iter->second->hash_password_ts < theirTS || iter->second->hash < theirhash || iter->second->password < theirpassword)
{
iter->second->hash = theirhash;
iter->second->password = theirpassword;
iter->second->hash_password_ts = theirTS;
}
}
AccountDBModifiedEvent(creator, iter->second->name, iter->second).Send();
}
else if(parameters[0] == "ADD")
{
if(e.entry)
{
if(e.alias_ts < ts)
return CMD_FAILURE;
e.RemoveAlias();
}
if(iter == prov.db.end() || iter->second->ts > ts)
{
if(iter != prov.db.end())
prov.RemoveAccount(false, iter->second);
AccountDBEntry* entry = new AccountDBEntryImpl(parameters[1], ts);
iter = prov.db.insert(std::make_pair(parameters[1], entry)).first;
}
else if(iter->second->ts < ts)
return CMD_FAILURE;
AccountDBModifiedEvent(creator, iter->second->name, iter->second).Send();
}
else if(parameters[0] == "DEL")
{
if(iter != prov.db.end())
{
if(iter->second->ts < ts)
return CMD_FAILURE;
prov.RemoveAccount(false, iter->second);
AccountDBModifiedEvent(creator, parameters[1], NULL).Send();
}
}
else
return CMD_FAILURE;
return CMD_SUCCESS;
}
};
/** Handle /IDENTIFY
*/
class CommandIdentify : public Command
{
AccountDB& db;
public:
CommandIdentify(Module* Creator, AccountDB& db_ref) : Command(Creator,"IDENTIFY", 1, 2), db(db_ref)
{
syntax = "[account name] <password>";
}
bool TryLogin (User* user, irc::string username, std::string password)
{
AccountDBEntry* entry;
GetAccountByAliasEvent e(creator, username);
if(e.entry)
entry = e.entry;
else
{
AccountDB::const_iterator iter = db.find(username);
if(iter == db.end())
return false;
entry = iter->second;
}
if(entry->password.empty() || ServerInstance->PassCompare(user, entry->password, password, entry->hash))
return false;
if(account)
account->DoLogin(user, entry->name, "");
return true;
}
CmdResult Handle (const std::vector<std::string>& parameters, User *user)
{
bool result;
if(parameters.size() == 1)
result = TryLogin(user, user->nick, parameters[0]);
else
result = TryLogin(user, parameters[0], parameters[1]);
if(!result)
{
user->WriteServ("NOTICE %s :Invalid username or password", user->nick.c_str());
return CMD_FAILURE;
}
return CMD_SUCCESS;
}
};
/** Handle /LOGOUT
*/
class CommandLogout : public Command
{
public:
CommandLogout(Module* Creator) : Command(Creator,"LOGOUT", 0, 0)
{
}
CmdResult Handle (const std::vector<std::string>& parameters, User *user)
{
if(account)
{
if(account->IsRegistered(user))
{
account->DoLogin(user, "", "");
user->WriteServ("NOTICE %s :Logout successful", user->nick.c_str());
}
else
user->WriteServ("NOTICE %s :You are not logged in", user->nick.c_str());
}
return CMD_SUCCESS;
}
};
class ModuleAccount : public Module
{
private:
CommandSvsaccount cmd;
CommandIdentify cmd_identify;
CommandLogout cmd_logout;
public:
ModuleAccount() : cmd(this), cmd_identify(this, cmd.prov.db), cmd_logout(this)
{
}
void init()
{
ServerInstance->SNO->EnableSnomask('u', "ACCOUNT");
ServerInstance->Modules->AddService(cmd);
ServerInstance->Modules->AddService(cmd.prov);
ServerInstance->Modules->AddService(cmd_identify);
ServerInstance->Modules->AddService(cmd_logout);
Implementation eventlist[] = { I_OnUserRegister, I_OnSyncNetwork, I_OnUnloadModule };
ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation));
}
virtual ~ModuleAccount()
{
for(AccountDB::iterator iter = cmd.prov.db.begin(); iter != cmd.prov.db.end(); ++iter)
{
iter->second->cull();
delete iter->second;
}
cmd.prov.db.clear();
}
void OnUserRegister(LocalUser* user)
{
if (account && account->IsRegistered(user))
return;
std::string::size_type sep = user->password.find_first_of(':');
if(sep != std::string::npos &&
cmd_identify.TryLogin(user, user->password.substr(0, sep), user->password.substr(sep + 1)))
return;
if(cmd_identify.TryLogin(user, user->nick, user->password))
return;
cmd_identify.TryLogin(user, user->ident, user->password);
}
void OnSyncNetwork(SyncTarget* target)
{
for (AccountDB::const_iterator i = cmd.prov.db.begin(); i != cmd.prov.db.end(); ++i)
{
std::string name = i->first, ts = ConvToStr(i->second->ts);
target->SendCommand("ENCAP * SVSACCOUNT ADD " + name + " :" + ts);
target->SendCommand("ENCAP * SVSACCOUNT SET " + name + " " + ts + " hash_password "
+ ConvToStr(i->second->hash_password_ts) + " :" + i->second->hash + " " + i->second->password);
for(Extensible::ExtensibleStore::const_iterator it = i->second->GetExtList().begin(); it != i->second->GetExtList().end(); ++it)
{
ExtensionItem* item = it->first;
std::string value = item->serialize(FORMAT_NETWORK, i->second, it->second);
if (!value.empty())
target->SendCommand("ENCAP * SVSACCOUNT SET " + name + " " + ts + " " + item->name + " " + value);
}
}
}
void OnUnloadModule(Module* mod)
{
std::vector<reference<ExtensionItem> > acct_exts;
ServerInstance->Extensions.BeginUnregister(mod, EXTENSIBLE_ACCOUNT, acct_exts);
for(AccountDB::const_iterator iter = cmd.prov.db.begin(); iter != cmd.prov.db.end(); ++iter)
{
mod->OnCleanup(TYPE_OTHER, iter->second);
iter->second->doUnhookExtensions(acct_exts);
AccountDBModifiedEvent(this, iter->second->name, iter->second).Send();
}
}
Version GetVersion()
{
return Version("Store account information and allow logging in to them ircd-side", VF_VENDOR|VF_OPTCOMMON);
}
};
MODULE_INIT(ModuleAccount)