aboutsummaryrefslogtreecommitdiff
/*       +------------------------------------+
 *       | 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 "account.h"

static dynamic_reference<AccountProvider> accounts("account");
static dynamic_reference<AccountDBProvider> db("accountdb");

typedef std::map<irc::string, AccountDBEntry*> NickMap;

static NickMap nickinfo;

struct NickTSItem
{
	irc::string nick;
	time_t ts;
	NickTSItem(const irc::string& newnick, time_t newTS) : nick(newnick), ts(newTS)
	{
	}
};

typedef std::vector<NickTSItem> NicksOwned;

class NicksOwnedExtItem : public TSGenericExtItem<NicksOwned>
{
	bool CheckCollision(irc::string accountname, irc::string nick, time_t ts)
	{
		AccountDBEntry* owner = db->GetAccount(nick, false);
		if(owner)
		{
			if(owner->ts > ts)
			{
				db->RemoveAccount(false, owner);
				return false;
			}
			return true;
		}
		NickMap::iterator i = nickinfo.find(nick);
		if(i == nickinfo.end()) return false;
		AccountDBEntry* entry = i->second;
		NicksOwned* ext = get_value(entry);
		if(ext)
			for(NicksOwned::iterator iter = ext->begin(); iter != ext->end(); ++iter)
				if(iter->nick == nick)
				{
					if(iter->ts < ts || (iter->ts == ts && entry->name < accountname))
						return true;
					ext->erase(iter);
					nickinfo.erase(i);
					return false;
				}

		throw ModuleException("An entry in nickinfo is incorrect");
	}

 protected:
	virtual std::string value_serialize(SerializeFormat format, const NicksOwned* value) const
	{
		std::ostringstream str;
		if(format == FORMAT_USER)
		{
			for(NicksOwned::const_iterator i = value->begin(); i != value->end(); ++i)
			{
				str << i->nick << " ";
			}
			std::string retval = str.str();
			return retval.empty() ? "-none-" : retval;
		}
		for(NicksOwned::const_iterator i = value->begin(); i != value->end(); ++i)
		{
			str << i->nick << "," << i->ts << " ";
		}
		return str.str();
	}

 private:
	virtual NicksOwned* value_unserialize(SerializeFormat, const std::string&)
	{
		throw ModuleException("Called NicksOwnedExtItem::value_unserialize");
	}

	virtual void value_resolve_conflict(NicksOwned*, NicksOwned*)
	{
		throw ModuleException("Called NicksOwnedExtItem::value_resolve_conflict");
	}

 public:
	NicksOwnedExtItem(const std::string& Key, Module* parent) : TSGenericExtItem<NicksOwned>(Key, NicksOwned(), parent)
	{
	}

	virtual void unserialize(SerializeFormat format, Extensible* container, const std::string& value)
	{
		std::string::size_type delim = value.find_first_of(' ');
		time_t ts = atol(value.substr(0, delim).c_str());
		std::string item;
		if(delim != std::string::npos)
			item = value.substr(delim + 1);
		value_pair* p = get(container);
		if(!p || ts > p->first || (ts == p->first && item < value_serialize(format, p->second)))
		{
			if(p)
				for(NicksOwned::const_iterator i = p->second->begin(); i != p->second->end(); ++i)
					nickinfo.erase(i->nick);

			std::string token;
			irc::string nick;
			time_t nickTS;
			irc::spacesepstream sep(item);
			std::vector<std::pair<irc::string, AccountDBEntry*> > newinfo;
			NicksOwned* ptr = new NicksOwned;
			while(sep.GetToken(token))
			{
				delim = token.find_first_of(',');
				if(delim == std::string::npos)
					continue;
				nick = token.substr(0, delim);
				nickTS = atol(token.substr(delim + 1).c_str());
				if(!CheckCollision(static_cast<AccountDBEntry*>(container)->name, nick, nickTS))
				{
					newinfo.push_back(std::make_pair(nick, static_cast<AccountDBEntry*>(container)));
					ptr->push_back(NickTSItem(nick, nickTS));
				}
			}
			set(container, ts, ptr);
			nickinfo.insert(newinfo.begin(), newinfo.end());
		}
	}

	virtual void free(void* item)
	{
		if(!item) return;
		NicksOwned* value = static_cast<value_pair*>(item)->second;
		for(NicksOwned::const_iterator i = value->begin(); i != value->end(); ++i)
			nickinfo.erase(i->nick);
		this->TSGenericExtItem<NicksOwned>::free(item);
	}
};

static NicksOwnedExtItem* nicks_ext;

static void RemoveNick(const irc::string& nick)
{
	NickMap::iterator i = nickinfo.find(nick);
	NicksOwned* ext = nicks_ext->get_value(i->second);
	if(ext)
		for(NicksOwned::iterator iter = ext->begin(); iter != ext->end(); ++iter)
			if(iter->nick == nick)
			{
				ext->erase(iter);
				nickinfo.erase(i);
				return;
			}

	throw ModuleException("An entry in nickinfo is incorrect");
}

class CommandAddnick : public Command
{
 public:
	unsigned int limit;
	CommandAddnick(Module* parent) : Command(parent, "ADDNICK", 0, 0)
	{
	}

	CmdResult Handle(const std::vector<std::string>& parameters, User* user)
	{
		AccountDBEntry* entry;
		if(!accounts || !accounts->IsRegistered(user) || !(entry = db->GetAccount(accounts->GetAccountName(user), false)))
		{
			user->WriteServ("NOTICE %s :You are not logged in", user->nick.c_str());
			return CMD_FAILURE;
		}
		if (!ServerInstance->IsNick(user->nick.c_str(), ServerInstance->Config->Limits.NickMax))
		{
			user->WriteServ("NOTICE %s :You may not register your UID", user->nick.c_str());
			return CMD_FAILURE;
		}
		if(db->GetAccount(user->nick, true))
		{
			user->WriteServ("NOTICE %s :Nick %s is already registered", user->nick.c_str(), user->nick.c_str());
			return CMD_FAILURE;
		}
		NicksOwnedExtItem::value_pair* p = nicks_ext->get(entry);
		if(!p)
		{
			NicksOwned* value = new NicksOwned;
			value->push_back(NickTSItem(user->nick, ServerInstance->Time()));
			nicks_ext->set(entry, value);
		}
		else if(limit && p->second->size() >= limit)
		{
			user->WriteServ("NOTICE %s :You already have the maximum number of nicks registered", user->nick.c_str());
			return CMD_FAILURE;
		}
		else
		{
			p->first = ServerInstance->Time();
			p->second->push_back(NickTSItem(user->nick, ServerInstance->Time()));
		}
		nickinfo.insert(std::make_pair(user->nick, entry));
		db->SendUpdate(entry, "Nicks_owned");
		ServerInstance->SNO->WriteGlobalSno('u', "%s used ADDNICK to register their nick to %s", user->nick.c_str(), entry->name.c_str());
		user->WriteServ("NOTICE %s :Nick %s has been registered to account %s", user->nick.c_str(), user->nick.c_str(), entry->name.c_str());
		return CMD_SUCCESS;
	}
};

class CommandDelnick : public Command
{
 public:
	CommandDelnick(Module* parent) : Command(parent, "DELNICK", 0, 1)
	{
		syntax = "[nick]";
	}

	CmdResult Handle(const std::vector<std::string>& parameters, User* user)
	{
		AccountDBEntry* entry;
		irc::string nick = parameters.size() ? parameters[0] : user->nick;
		if(!accounts || !accounts->IsRegistered(user) || !(entry = db->GetAccount(accounts->GetAccountName(user), false)))
		{
			user->WriteServ("NOTICE %s :You are not logged in", user->nick.c_str());
			return CMD_FAILURE;
		}
		AccountDBEntry* owner = db->GetAccount(nick, false);
		NickMap::iterator iter = nickinfo.find(nick);
		if(owner == entry)
		{
			user->WriteServ("NOTICE %s :Nick %s is your primary nick and may not be deleted", user->nick.c_str(), nick.c_str());
			return CMD_FAILURE;
		}
		else if(owner || iter == nickinfo.end() || iter->second != entry)
		{
			user->WriteServ("NOTICE %s :Nick %s is not registered to you", user->nick.c_str(), nick.c_str());
			return CMD_FAILURE;
		}
		NicksOwnedExtItem::value_pair* p = nicks_ext->get(entry);
		if(p)
			for(NicksOwned::iterator i = p->second->begin(); i != p->second->end(); ++i)
				if(i->nick == nick)
				{
					p->first = ServerInstance->Time();
					p->second->erase(i);
					nickinfo.erase(iter);
					db->SendUpdate(entry, "Nicks_owned");
					ServerInstance->SNO->WriteGlobalSno('u', "%s used DELNICK to unregister nick %s from %s", user->nick.c_str(), nick.c_str(), entry->name.c_str());
					user->WriteServ("NOTICE %s :Nick %s has been unregistered", user->nick.c_str(), nick.c_str());
					return CMD_SUCCESS;
				}

		throw ModuleException("An entry in nickinfo is incorrect");
	}
};

class CommandFdelnick : public Command
{
 public:
	CommandFdelnick(Module* Creator) : Command(Creator,"FDELNICK", 2, 2)
	{
		flags_needed = 'o'; syntax = "<account> <nick>";
	}

	CmdResult Handle (const std::vector<std::string>& parameters, User *user)
	{
		AccountDBEntry* entry = db->GetAccount(parameters[0], false);
		if(!entry)
		{
			user->WriteServ("NOTICE %s :No such account", user->nick.c_str());
			return CMD_FAILURE;
		}
		if(entry->name == parameters[1])
		{
			user->WriteServ("NOTICE %s :Nick %s is %s's primary nick and may not be deleted", user->nick.c_str(), entry->name.c_str(), entry->name.c_str());
			return CMD_FAILURE;
		}
		NickMap::iterator iter = nickinfo.find(parameters[1]);
		if(iter == nickinfo.end() || iter->second != entry)
		{
			user->WriteServ("NOTICE %s :Nick %s is not registered to them", user->nick.c_str(), parameters[1].c_str());
			return CMD_FAILURE;
		}
		NicksOwnedExtItem::value_pair* p = nicks_ext->get(entry);
		if(p)
			for(NicksOwned::iterator i = p->second->begin(); i != p->second->end(); ++i)
				if(i->nick == parameters[1])
				{
					p->first = ServerInstance->Time();
					p->second->erase(i);
					nickinfo.erase(iter);
					db->SendUpdate(entry, "Nicks_owned");
					ServerInstance->SNO->WriteGlobalSno('a', "%s used FDELNICK to unregister nick %s from account %s", user->nick.c_str(), parameters[1].c_str(), entry->name.c_str());
					user->WriteServ("NOTICE %s :Nick %s has been unregistered from account %s", user->nick.c_str(), parameters[1].c_str(), entry->name.c_str());
					return CMD_SUCCESS;
				}

		throw ModuleException("An entry in nickinfo is incorrect");
	}
};

/** Handle /SETENFORCE
 */
class CommandSetenforce : public Command
{
 public:
	TSBoolExtItem enforce;
	CommandSetenforce(Module* Creator) : Command(Creator,"SETENFORCE", 1, 1), enforce("Automatically_enforce_nicks", false, true, Creator)
	{
		syntax = "OFF|ON";
	}

	CmdResult Handle (const std::vector<std::string>& parameters, User *user)
	{
		AccountDBEntry* entry;
		if(!accounts || !accounts->IsRegistered(user) || !(entry = db->GetAccount(accounts->GetAccountName(user), false)))
		{
			user->WriteServ("NOTICE %s :You are not logged in", user->nick.c_str());
			return CMD_FAILURE;
		}
		bool newsetting;
		if(irc::string(parameters[0]) == "ON")
			newsetting = true;
		else if(irc::string(parameters[0]) == "OFF")
			newsetting = false;
		else
		{
			user->WriteServ("NOTICE %s :Unknown setting", user->nick.c_str());
			return CMD_FAILURE;
		}
		enforce.set(entry, newsetting);
		db->SendUpdate(entry, "Automatically_enforce_nicks");
		user->WriteServ("NOTICE %s :Nick enforcement for account %s %s successfully", user->nick.c_str(), entry->name.c_str(), newsetting ? "enabled" : "disabled");
		return CMD_SUCCESS;
	}
};

/** Handle /ENFORCE
 */
class CommandEnforce : public Command
{
 public:
	CommandEnforce(Module* Creator) : Command(Creator,"ENFORCE", 1, 1)
	{
		syntax = "<nick>";
	}

	CmdResult Handle (const std::vector<std::string>& parameters, User *user)
	{
		if(IS_LOCAL(user))
		{
			AccountDBEntry* entry;
			if(!accounts || !accounts->IsRegistered(user) || !(entry = db->GetAccount(accounts->GetAccountName(user), false)))
			{
				user->WriteServ("NOTICE %s :You are not logged in", user->nick.c_str());
				return CMD_FAILURE;
			}
			NickMap::iterator iter = nickinfo.find(parameters[0]);
			if(entry->name != parameters[0] && (iter == nickinfo.end() || iter->second != entry))
			{
				user->WriteServ("NOTICE %s :Nick %s is not registered to you", user->nick.c_str(), parameters[0].c_str());
				return CMD_FAILURE;
			}
		}
		User* target = ServerInstance->FindNick(parameters[0]);
		if(!target)
		{
			user->WriteNumeric(ERR_NOSUCHNICK, "%s %s :No such nick/channel", user->nick.c_str(), parameters[0].c_str());
			return CMD_FAILURE;
		}
		if(IS_LOCAL(user))
			user->WriteServ("NOTICE %s :Nick %s enforced successfully", user->nick.c_str(), parameters[0].c_str());
		if(IS_LOCAL(target))
		{
			target->WriteNumeric(433, "%s %s :Nickname overruled: ENFORCE command used",
				target->nick.c_str(), target->nick.c_str());
			target->ChangeNick(target->uuid, true);
			if(user->registered != REG_ALL)
				user->registered &= ~REG_NICK;
		}
		return CMD_SUCCESS;
	}

	RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters)
	{
		User* dest = ServerInstance->FindNick(parameters[0]);
		if (dest)
			return ROUTE_OPT_UCAST(dest->server);
		return ROUTE_LOCALONLY;
	}
};

class ModuleAccountNickOwnership : public Module
{
 public:
	NicksOwnedExtItem nicks;
	CommandAddnick cmd_addnick;
	CommandDelnick cmd_delnick;
	CommandFdelnick cmd_fdelnick;
	CommandSetenforce cmd_setenforce;
	CommandEnforce cmd_enforce;

	ModuleAccountNickOwnership() : nicks("Nicks_owned", this), cmd_addnick(this), cmd_delnick(this), cmd_fdelnick(this), cmd_setenforce(this), cmd_enforce(this)
	{
		nicks_ext = &nicks;
	}

	void init()
	{
		ServerInstance->Modules->AddService(nicks);
		ServerInstance->Modules->AddService(cmd_addnick);
		ServerInstance->Modules->AddService(cmd_delnick);
		ServerInstance->Modules->AddService(cmd_fdelnick);
		ServerInstance->Modules->AddService(cmd_setenforce);
		ServerInstance->Modules->AddService(cmd_setenforce.enforce);
		ServerInstance->Modules->AddService(cmd_enforce);
		Implementation eventlist[] = { I_OnUserPreNick, I_OnCheckReady, I_OnUserConnect, I_OnEvent };
		ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation));
	}

	void ReadConfig(ConfigReadStatus&)
	{
		ConfigTag *tag = ServerInstance->Config->GetTag ("nickownership");
		cmd_addnick.limit = tag->getInt ("limit", 5);
	}

	ModResult OnUserPreNick(User* user, const std::string& nick)
	{
		if (ServerInstance->NICKForced.get(user))
			return MOD_RES_PASSTHRU;

		// check the new nick
		AccountDBEntry* owner = db->GetAccount(nick, true);
		if(!owner || (accounts && accounts->GetAccountName(user) == owner->name))
			return MOD_RES_PASSTHRU;
		bool* enforce = cmd_setenforce.enforce.get_value(owner);
		if (!enforce || !*enforce)
		{
			if(user->registered == REG_ALL)
				user->WriteServ("NOTICE %s :Nick %s is registered to the account '%s'", user->nick.c_str(), nick.c_str(), owner->name.c_str());
			return MOD_RES_PASSTHRU;
		}
		else if (user->registered == REG_ALL)
		{
			user->WriteNumeric(433, "%s %s :You must be identified to the account '%s' to use this nick",
				user->nick.c_str(), nick.c_str(), owner->name.c_str());
			return MOD_RES_DENY;
		}
		else
		{
			// allow through to give things like SASL or SQLauth time to return before denying
			// sending a 437 here makes both irssi and xchat auto-select a different nick, so we'll send a NOTICE instead
			user->WriteServ("NOTICE %s :Nick %s requires you to identify to the account '%s'", user->nick.c_str(), nick.c_str(), owner->name.c_str());
			return MOD_RES_PASSTHRU;
		}
	}

	ModResult OnCheckReady(LocalUser* user)
	{
		AccountDBEntry* owner = db->GetAccount(user->nick, true);
		if(!owner)
			return MOD_RES_PASSTHRU;
		bool* enforce = cmd_setenforce.enforce.get_value(owner);
		if (enforce && *enforce && (!accounts || owner->name != accounts->GetAccountName(user)))
		{
			user->WriteNumeric(433, "%s %s :Nickname overruled: requires login to the account '%s'",
				user->nick.c_str(), user->nick.c_str(), owner->name.c_str());
			user->ChangeNick(user->uuid, true);
			user->registered &= ~REG_NICK;
			return MOD_RES_DENY;
		}
		return MOD_RES_PASSTHRU;
	}

	virtual void OnUserConnect(LocalUser* user)
	{
		AccountDBEntry* owner = db->GetAccount(user->nick, true);
		if(!owner)
			return;
		bool* enforce = cmd_setenforce.enforce.get_value(owner);
		if ((!enforce || !*enforce) && (!accounts || owner->name != accounts->GetAccountName(user)))
			user->WriteServ("NOTICE %s :Nick %s is registered to the account '%s'", user->nick.c_str(), user->nick.c_str(), owner->name.c_str());
	}

	void OnEvent(Event& event)
	{
		if(event.id == "get_account_by_alias"){
			GetAccountByAliasEvent& e = static_cast<GetAccountByAliasEvent&>(event);
			if(e.entry)
				return; // Some other module already populated the event
			NickMap::iterator iter = nickinfo.find(e.account);
			if(iter != nickinfo.end())
			{
				e.entry = iter->second;
				NicksOwned* ext = nicks.get_value(iter->second);
				if(ext)
					for(NicksOwned::const_iterator i = ext->begin(); i != ext->end(); ++i)
						if(i->nick == e.account)
						{
							e.alias_ts = i->ts;
							e.RemoveAliasImpl = &RemoveNick;
							return;
						}

				throw ModuleException("An entry in nickinfo is incorrect");
			}
		}
	}

	void Prioritize()
	{
		ServerInstance->Modules->SetPriority(this, I_ModuleInit, PRIORITY_AFTER, ServerInstance->Modules->Find("m_account.so"));
		// need to be after all modules that might deny the ready check
		ServerInstance->Modules->SetPriority(this, I_OnCheckReady, PRIORITY_LAST);
	}

	Version GetVersion()
	{
		return Version("Nick registration tracking", VF_VENDOR | VF_OPTCOMMON);
	}
};

MODULE_INIT(ModuleAccountNickOwnership)