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 "builtin-modes.h"

ModeHandler::ModeHandler(Module* Creator, const std::string& Name, char modeletter, ParamSpec Params, ModeType type)
	: ServiceProvider(Creator, Name, SERVICE_MODE), m_paramtype(TR_TEXT), parameters_taken(Params),
	m_type(type), levelrequired(HALFOP_VALUE), mode(modeletter), prefix(0), oper(false),
	fixed_letter(true), list(false), disabled(false)
{
}

CullResult ModeHandler::cull()
{
	if (ServerInstance->Modes)
		ServerInstance->Modes->DelMode(this);
	return classbase::cull();
}

ModeHandler::~ModeHandler()
{
	if (ServerInstance && ServerInstance->Modes && ServerInstance->Modes->FindMode(mode, m_type) == this)
		ServerInstance->Logs->Log("MODE", DEFAULT, "ERROR: Destructor for mode %c called while still registered", mode);
}

bool ModeHandler::IsListMode()
{
	return list;
}

unsigned int ModeHandler::GetPrefixRank()
{
	return 0;
}

void ModeHandler::AdjustModeChar(char proposed_letter)
{
	if (fixed_letter)
		return;
	mode = proposed_letter;
}

int ModeHandler::GetNumParams(bool adding)
{
	switch (parameters_taken)
	{
		case PARAM_ALWAYS:
			return 1;
		case PARAM_SETONLY:
			return adding ? 1 : 0;
		case PARAM_NONE:
			break;
	}
	return 0;
}

std::string ModeHandler::GetUserParameter(User* user)
{
	return "";
}

void ModeHandler::AccessCheck(ModePermissionData&)
{
}

void ModeHandler::DisplayList(User*, Channel*)
{
}

void ModeHandler::DisplayEmptyList(User*, Channel*)
{
}

void ModeHandler::OnParameterMissing(User* user, User* dest, Channel* channel, std::string& param)
{
}

bool ModeHandler::ParamOrder(const std::string& theirs, const std::string& ours)
{
	return (theirs < ours);
}

ModeAction SimpleUserModeHandler::OnModeChange(User* source, User* dest, Channel* channel, std::string &parameter, bool adding)
{
	if (adding)
	{
		if (!dest->IsModeSet(this->GetModeChar()))
		{
			dest->SetMode(this->GetModeChar(),true);
			return MODEACTION_ALLOW;
		}
	}
	else
	{
		if (dest->IsModeSet(this->GetModeChar()))
		{
			dest->SetMode(this->GetModeChar(),false);
			return MODEACTION_ALLOW;
		}
	}

	return MODEACTION_DENY;
}


ModeAction SimpleChannelModeHandler::OnModeChange(User* source, User* dest, Channel* channel, std::string &parameter, bool adding)
{
	if (adding)
	{
		if (!channel->IsModeSet(this))
		{
			channel->SetMode(this,true);
			return MODEACTION_ALLOW;
		}
	}
	else
	{
		if (channel->IsModeSet(this))
		{
			channel->SetMode(this,false);
			return MODEACTION_ALLOW;
		}
	}

	return MODEACTION_DENY;
}

ModeAction ParamChannelModeHandler::OnModeChange(User* source, User* dest, Channel* channel, std::string &parameter, bool adding)
{
	if (adding)
	{
		std::string now = channel->GetModeParameter(this);
		if (parameter == now)
			return MODEACTION_DENY;
		if (!ParamValidate(parameter))
			return MODEACTION_DENY;
		channel->SetModeParam(this, parameter);
	}
	else
		channel->SetModeParam(this, "");
	return MODEACTION_ALLOW;
}

bool ParamChannelModeHandler::ParamValidate(std::string& parameter)
{
	return true;
}

ModeWatcher::ModeWatcher(Module* Creator, char modeletter, ModeType type)
	: mode(modeletter), m_type(type), creator(Creator)
{
}

ModeWatcher::~ModeWatcher()
{
}

char ModeWatcher::GetModeChar()
{
	return mode;
}

ModeType ModeWatcher::GetModeType()
{
	return m_type;
}

bool ModeWatcher::BeforeMode(User*, User*, Channel*, std::string&, bool, ModeType)
{
	return true;
}

void ModeWatcher::AfterMode(User*, User*, Channel*, const std::string&, bool, ModeType)
{
}

irc::modechange::modechange(const std::string& name, const std::string& param, bool add)
	: adding(add), value(param)
{
	ModeHandler* mh = ServerInstance->Modes->FindMode(name);
	if (mh)
		mode = mh->id;
}

irc::modechange::modechange(char modechar, ModeType type, const std::string& param, bool add)
	: adding(add), value(param)
{
	ModeHandler* mh = ServerInstance->Modes->FindMode(modechar, type);
	if (mh)
		mode = mh->id;
}

std::string irc::modestacker::popModeLine(SerializeFormat format, int maxlen, int maxmodes)
{
	char pm_now = '\0';
	std::string modeline;
	std::stringstream params;
	int nmodes = 0, linelen = 0;
	if (maxmodes == 0)
		maxmodes = ServerInstance->Config->Limits.MaxModes;

	std::vector<modechange>::iterator iter = sequence.begin();
	for(; iter != sequence.end() && nmodes < maxmodes; ++iter)
	{
		char pm = iter->adding ? '+' : '-';
		ModeHandler* mh = ServerInstance->Modes->FindMode(iter->mode);
		if (!mh)
			continue;
		char modechar = mh->GetModeChar();
		std::string value = iter->value;
		if (mh->GetTranslateType() == TR_NICK)
		{
			User* u = ServerInstance->FindNick(value);
			if (u && format == FORMAT_USER)
				value = u->nick;
			else if (u && (format == FORMAT_NETWORK || format == FORMAT_INTERNAL))
				value = u->uuid;
			else
				value.clear();
		}
		else if (mh->GetTranslateType() == TR_CUSTOM)
		{
			mh->TranslateMode(value, iter->adding, format);
		}

		if (mh->GetNumParams(iter->adding) && value.empty())
		{
			// value is empty when we want a param - failed conversion?
			continue;
		}

		if (format == FORMAT_PERSIST || !modechar)
		{
			// this will only happen if we already allowed NameOnlyModes, no need to check
			modechar = 'Z';
			if (mh->GetNumParams(iter->adding))
				value = mh->name + "=" + value;
			else
				value = mh->name;
		}
		else if (!mh->GetNumParams(iter->adding))
			value.clear();

		int mylen = (pm == pm_now) ? 1 : 2;
		if (!value.empty())
			mylen += 1 + value.length();

		if (mylen + linelen > maxlen)
			break;

		linelen += mylen;
		if (pm != pm_now)
		{
			modeline.push_back(pm);
			pm_now = pm;
		}
		modeline.push_back(modechar);

		if (!value.empty())
			params << " " << value;
	}

	sequence.erase(sequence.begin(), iter);
	if (format == FORMAT_PERSIST)
	{
		std::string rv = params.str();
		if (rv.empty())
			return rv;
		// drop the initial space
		return rv.substr(1);
	}
	if (modeline.empty())
		return "+";
	return modeline + params.str();
}

void ModeParser::DisplayCurrentModes(User *user, User* targetuser, Channel* targetchannel, const char* text)
{
	if (targetchannel)
	{
		irc::modestacker ms;
		targetchannel->ChanModes(ms, targetchannel->HasUser(user) ? MODELIST_SHORT : MODELIST_PUBLIC);
		/* Display channel's current mode string */
		user->WriteNumeric(RPL_CHANNELMODEIS, "%s %s %s", user->nick.c_str(),
			targetchannel->name.c_str(), ms.popModeLine(FORMAT_USER, 400, INT_MAX).c_str());
		user->WriteNumeric(RPL_CHANNELCREATED, "%s %s %lu", user->nick.c_str(), targetchannel->name.c_str(), (unsigned long)targetchannel->age);
		return;
	}
	else
	{
		if (targetuser == user || user->HasPrivPermission("users/auspex"))
		{
			/* Display user's current mode string */
			user->WriteNumeric(RPL_UMODEIS, "%s :+%s",targetuser->nick.c_str(),targetuser->FormatModes());
			if (IS_OPER(targetuser))
				user->WriteNumeric(RPL_SNOMASKIS, "%s +%s :Server notice mask", targetuser->nick.c_str(), targetuser->FormatNoticeMasks());
			return;
		}
		else
		{
			user->WriteNumeric(ERR_USERSDONTMATCH, "%s :Can't view modes for other users", user->nick.c_str());
			return;
		}
	}
}

ModeAction ModeParser::TryMode(User* user, User* targetuser, Channel* chan, irc::modechange& mc, bool SkipACL)
{
	bool adding = mc.adding;
	ModeID modeid = mc.mode;
	ModeType type = chan ? MODETYPE_CHANNEL : MODETYPE_USER;

	ModeHandler *mh = FindMode(modeid);
	int pcnt = mh->GetNumParams(adding);

	// crop mode parameter size to 250 characters
	if (mc.value.length() > 250)
		mc.value = mc.value.substr(0, 250);

	ModResult MOD_RESULT;
	FIRST_MOD_RESULT(OnRawMode, MOD_RESULT, (user, chan, mc));

	if (IS_LOCAL(user) && (MOD_RESULT == MOD_RES_DENY))
		return MODEACTION_DENY;

	if (chan && !SkipACL && (MOD_RESULT != MOD_RES_ALLOW))
	{
		User* targ = (mh->GetTranslateType() == TR_NICK) ? ServerInstance->FindNick(mc.value) : NULL;
		ModePermissionData perm(user, "mode/" + mh->name, chan, targ, mc);
		mh->AccessCheck(perm);
		FOR_EACH_MOD(OnPermissionCheck, (perm));
		perm.DoRankCheck();

		if (perm.result == MOD_RES_DENY)
		{
			if (!perm.reason.empty())
				user->SendText(perm.reason);
			return MODEACTION_DENY;
		}
	}

	std::pair<ModeWatcherMap::iterator,ModeWatcherMap::iterator> watchers = modewatchers.equal_range(modeid);
	for (ModeWatcherMap::iterator watcher = watchers.first; watcher != watchers.second; watcher++)
	{
		if (watcher->second->BeforeMode(user, targetuser, chan, mc.value, adding, type) == false)
			return MODEACTION_DENY;
		/* A module whacked the parameter completely, and there was one. abort. */
		if (pcnt && mc.value.empty())
			return MODEACTION_DENY;
	}

	if (!SkipACL && (mh->disabled || (adding && mh->NeedsOper())) && !user->HasModePermission(mh->id))
	{
		/* It's an oper only mode, and they don't have access to it. */
		if (IS_OPER(user))
		{
			user->WriteNumeric(ERR_NOPRIVILEGES, "%s :Permission Denied - Oper type %s does not have access to set the %s mode",
					user->nick.c_str(), user->oper->NameStr(), mh->name.c_str());
		}
		else if (mh->disabled)
		{
			user->WriteNumeric(ERR_NOPRIVILEGES, "%s :Permission Denied - mode %s has been locked by the administrator",
				user->nick.c_str(), mh->name.c_str());
		}
		else
		{
			user->WriteNumeric(ERR_NOPRIVILEGES, "%s :Permission Denied - Only operators may set the %s mode",
					user->nick.c_str(), mh->name.c_str());
		}
		return MODEACTION_DENY;
	}

	if (mh->GetTranslateType() == TR_NICK)
	{
		User* targ = ServerInstance->FindNick(mc.value);
		if(!targ)
		{
			user->WriteNumeric(ERR_NOSUCHNICK, "%s %s :No such nick/channel", user->nick.c_str(), mc.value.c_str());
			return MODEACTION_DENY;
		}
		else
			mc.value = targ->uuid;
	}

	if (mh->GetPrefixRank() && chan)
	{
		User* user_to_prefix = ServerInstance->FindNick(mc.value);
		if (!user_to_prefix)
			return MODEACTION_DENY;
		if (!chan->SetPrefix(user_to_prefix, mh->GetModeChar(), adding))
			return MODEACTION_DENY;
	}

	/* Call the handler for the mode */
	ModeAction ma = mh->OnModeChange(user, targetuser, chan, mc.value, adding);

	if (pcnt && mc.value.empty())
		return MODEACTION_DENY;

	if (ma != MODEACTION_ALLOW)
		return ma;

	for (ModeWatcherMap::iterator watcher = watchers.first; watcher != watchers.second; watcher++)
		watcher->second->AfterMode(user, targetuser, chan, mc.value, adding, type);

	return MODEACTION_ALLOW;
}

void ModeParser::Parse(const std::vector<std::string>& parameters, User *user, Extensible*& target, irc::modestacker& modes)
{
	std::string targetstr = parameters[0];
	Channel* targetchannel = ServerInstance->FindChan(targetstr);
	User* targetuser = ServerInstance->FindNick(targetstr);
	target = targetuser ? (Extensible*)targetuser : targetchannel;
	ModeType type = targetchannel ? MODETYPE_CHANNEL : MODETYPE_USER;

	if (!targetchannel && !targetuser)
	{
		user->WriteNumeric(ERR_NOSUCHNICK, "%s %s :No such nick/channel",user->nick.c_str(),targetstr.c_str());
		return;
	}
	if (parameters.size() == 1)
	{
		this->DisplayCurrentModes(user, targetuser, targetchannel, targetstr.c_str());
		return;
	}
	if (targetchannel && parameters.size() == 2)
	{
		/* Special case for displaying the list for listmodes,
		 * e.g. MODE #chan b, or MODE #chan +b without a parameter
		 */
		this->DisplayListModes(user, targetchannel, parameters[1]);
	}

	unsigned int param_at = 2;
	bool adding = true;

	bool listednamedmodes = false;

	for (std::string::const_iterator letter = parameters[1].begin(); letter != parameters[1].end(); letter++)
	{
		unsigned char modechar = *letter;
		// skip s2s mode-merge indicator
		if (modechar == '=')
			continue;
		if (modechar == '+' || modechar == '-')
		{
			adding = (modechar == '+');
			continue;
		}
		if (modechar == 'Z' && ServerInstance->Config->NameOnlyModes)
		{
			// special case: Z is the named mode change interface
			std::string name, value;
			if (param_at == parameters.size())
			{
				/* Ensure the user doesnt request the same mode twice,
				 * so they cant flood themselves off out of idiocy.
				 */
				if (listednamedmodes)
					continue;

				listednamedmodes = true;
				for(ModeIDIter id; id; id++)
				{
					ModeHandler* mh = ServerInstance->Modes->FindMode(id);
					if (!mh || mh->IsListMode() || mh->GetModeType() != type)
						continue;
					if (targetchannel && !targetchannel->IsModeSet(mh))
						continue;
					if (targetuser && !targetuser->IsModeSet(mh->GetModeChar()))
						continue;

					if (mh->GetNumParams(true))
						user->WriteNumeric(963, "%s %s %s=%s", user->nick.c_str(), targetchannel->name.c_str(), mh->name.c_str(), targetchannel->GetModeParameter(mh).c_str());
					else
					{
						if(targetchannel)
							user->WriteNumeric(963, "%s %s %s", user->nick.c_str(), targetchannel->name.c_str(), mh->name.c_str());
						else
							user->WriteNumeric(963, "%s %s %s", user->nick.c_str(), targetuser->nick.c_str(), mh->name.c_str());
					}
				}
				if(targetchannel)
					user->WriteNumeric(962, "%s :End of Mode List", targetchannel->name.c_str());
				else
					user->WriteNumeric(962, "%s :End of Mode List", targetuser->nick.c_str());
				continue;
			}

			name = parameters[param_at++];
			std::string::size_type eq = name.find('=');
			if (eq != std::string::npos)
			{
				value = name.substr(eq + 1);
				name = name.substr(0, eq);
			}
			ModeHandler *mh = FindMode(name);
			if (mh && mh->GetModeType() == type)
			{
				if (mh->GetNumParams(adding) && value.empty())
				{
					if (mh->IsListMode() && targetchannel && adding)
					{
						/* Special case for displaying the list for listmodes,
						 * e.g. MODE #chan +Z ban, or MODE #chan +Z ban=
						 */
						mh->DisplayList(user, targetchannel);
						continue;
					}
					/* No parameter, continue to the next mode unless OnParameterMissing fills one in */
					mh->OnParameterMissing(user, targetuser, targetchannel, value);
					if (value.empty())
						continue;
				}
				modes.push(irc::modechange(mh->id, value, adding));
			}
			else
			{
				user->WriteServ("%d %s %s :is unknown mode string to me", type == MODETYPE_CHANNEL ? 472 : 501, user->nick.c_str(), name.c_str());
			}
			continue;
		}

		ModeHandler *mh = this->FindMode(modechar, type);
		if (!mh)
		{
			/* No mode handler? Unknown mode character then. */
			user->WriteServ("%d %s %c :is unknown mode char to me", type == MODETYPE_CHANNEL ? 472 : 501, user->nick.c_str(), modechar);
			continue;
		}

		std::string parameter = "";
		int pcnt = mh->GetNumParams(adding);
		if (pcnt && param_at == parameters.size())
		{
			/* No parameter, continue to the next mode unless OnParameterMissing fills one in */
			mh->OnParameterMissing(user, targetuser, targetchannel, parameter);
			if (parameter.empty())
				continue;
		}
		else if (pcnt)
		{
			parameter = parameters[param_at++];
		}
		modes.push(irc::modechange(mh->id, parameter, adding));
	}
}

void ModeParser::Send(User *src, Extensible* target, irc::modestacker modes)
{
	Channel* targetchannel = IS_CHANNEL(target);
	User* targetuser = IS_USER(target);
	if (targetchannel)
	{
		while (!modes.empty())
			targetchannel->WriteChannel(src, "MODE %s %s", targetchannel->name.c_str(), modes.popModeLine(FORMAT_USER).c_str());
	}
	else if (targetuser)
	{
		while (!modes.empty())
			targetuser->WriteFrom(src, "MODE %s %s", targetuser->nick.c_str(), modes.popModeLine(FORMAT_USER).c_str());
	}
}

void ModeParser::Process(User *src, Extensible* target, irc::modestacker& modes, bool merge, bool SkipAccessChecks)
{
	Channel* targetchannel = IS_CHANNEL(target);
	User* targetuser = IS_USER(target);

	if (!targetuser && !targetchannel)
		return;

	ModResult MOD_RESULT;
	FIRST_MOD_RESULT(OnPreMode, MOD_RESULT, (src, target, modes));

	if (!IS_LOCAL(src) || MOD_RESULT == MOD_RES_ALLOW)
		SkipAccessChecks = true;
	else if (MOD_RESULT == MOD_RES_DENY)
		return;

	if (targetuser && src != targetuser && !SkipAccessChecks)
	{
		src->WriteNumeric(ERR_USERSDONTMATCH, "%s :Can't change mode for other users", src->nick.c_str());
		return;
	}

	std::vector<irc::modechange>::iterator mc = modes.sequence.begin();
	while (mc != modes.sequence.end())
	{
		ModeHandler* mh = FindMode(mc->mode);
		if (!mh)
			throw CoreException("Invalid mode sequence passed to ModeParser::Process!");
		if (mc->value.c_str()[0] == ':' || mc->value.find(' ') != std::string::npos)
		{
			// you can't do that in a mode value, sorry
			mc = modes.sequence.erase(mc);
			continue;
		}
		if (merge && targetchannel && targetchannel->IsModeSet(mh) && mc->adding && !mh->IsListMode())
		{
			std::string ours = targetchannel->GetModeParameter(mh);
			if (mc->value == ours || !mh->ParamOrder(mc->value, ours))
			{
				/* local side won the mode merge, don't apply this mode */
				mc = modes.sequence.erase(mc);
				continue;
			}
		}

		ModeAction ma = TryMode(src, targetuser, targetchannel, *mc, SkipAccessChecks);

		if (ma == MODEACTION_ALLOW)
		{
			mc++;
		}
		else
		{
			/* mode change was denied */
			mc = modes.sequence.erase(mc);
			continue;
		}
	}

	FOREACH_MOD(I_OnMode, OnMode(src, target, modes));
}

void ModeParser::DisplayListModes(User* user, Channel* chan, const std::string &mode_sequence)
{
	std::bitset<MODE_ID_MAX> sent;

	for (std::string::const_iterator letter = mode_sequence.begin(); letter != mode_sequence.end(); letter++)
	{
		unsigned char mletter = *letter;
		if (mletter == '+')
			continue;

		ModeHandler *mh = this->FindMode(mletter, MODETYPE_CHANNEL);

		if (!mh || !mh->IsListMode())
			return;

		/* Ensure the user doesnt request the same mode twice,
		 * so they cant flood themselves off out of idiocy.
		 */
		if (sent[mh->id.GetID()])
			continue;

		sent[mh->id.GetID()] = true;

		irc::modechange mc(mh->id);
		ModResult MOD_RESULT;
		FIRST_MOD_RESULT(OnRawMode, MOD_RESULT, (user, chan, mc));
		if (MOD_RESULT == MOD_RES_DENY)
			continue;

		bool display = true;
		if (!user->HasPrivPermission("channels/auspex") && ServerInstance->Config->HideModeLists[mletter] && (chan->GetAccessRank(user) < HALFOP_VALUE))
		{
			user->WriteNumeric(ERR_CHANOPRIVSNEEDED, "%s %s :You do not have access to view the +%c list",
				user->nick.c_str(), chan->name.c_str(), mletter);
			display = false;
		}

		std::pair<ModeWatcherMap::iterator,ModeWatcherMap::iterator> watchers = modewatchers.equal_range(mh->id);
		for(ModeWatcherMap::iterator watcher = watchers.first; watcher != watchers.second; watcher++)
		{
			std::string dummyparam;

			if (!watcher->second->BeforeMode(user, NULL, chan, dummyparam, true, MODETYPE_CHANNEL))
				display = false;
		}
		if (display)
			mh->DisplayList(user, chan);
		else
			mh->DisplayEmptyList(user, chan);
	}
}

void ModeParser::CleanMask(std::string &mask)
{
	std::string::size_type pos_of_pling = mask.find_first_of('!');
	std::string::size_type pos_of_at = mask.find_first_of('@');
	std::string::size_type pos_of_dot = mask.find_first_of('.');
	std::string::size_type pos_of_colons = mask.find("::"); /* Because ipv6 addresses are colon delimited -- double so it treats extban as nick */

	if (mask.length() >= 2 && mask[1] == ':')
		return; // if it's an extban, don't even try guess how it needs to be formed.

	if ((pos_of_pling == std::string::npos) && (pos_of_at == std::string::npos))
	{
		/* Just a nick, or just a host - or clearly ipv6 (starting with :) */
		if ((pos_of_dot == std::string::npos) && (pos_of_colons == std::string::npos) && mask[0] != ':')
		{
			/* It has no '.' in it, it must be a nick. */
			mask.append("!*@*");
		}
		else
		{
			/* Got a dot in it? Has to be a host */
			mask = "*!*@" + mask;
		}
	}
	else if ((pos_of_pling == std::string::npos) && (pos_of_at != std::string::npos))
	{
		/* Has an @ but no !, its a user@host */
		 mask = "*!" + mask;
	}
	else if ((pos_of_pling != std::string::npos) && (pos_of_at == std::string::npos))
	{
		/* Has a ! but no @, it must be a nick!ident */
		mask.append("@*");
	}
}

void ModeParser::AddMode(ModeHandler* mh)
{
	/* A mode prefix of ',' is not acceptable, it would fuck up server to server.
	 * A mode prefix of ':' will fuck up both server to server, and client to server.
	 * A mode prefix of '#' will mess up /whois and /privmsg
	 */
	if ((mh->GetPrefix() == ',') || (mh->GetPrefix() == ':') || (mh->GetPrefix() == '#'))
		throw ModuleException("Invalid prefix defined in mode");

	std::string myletter = ServerInstance->Config->GetTag("modeletters")->getString(mh->name, std::string(1,mh->GetModeChar()));
	mh->AdjustModeChar(myletter.c_str()[0]);

	if (FindMode(mh->name))
		throw ModuleException("Duplicate mode name found");

	// prefixes can't be duplicated
	if (mh->GetPrefix() && FindPrefix(mh->GetPrefix()))
		throw ModuleException("Duplicate channel prefix found");

	char mc = mh->GetModeChar();

	// user modes have to be alpha (can't be null either)
	if (mh->GetModeType() == MODETYPE_USER && !isalpha(mc))
		throw ModuleException("Invalid mode character");

	// all modes have to be alphanumeric or null
	if (mc && !isalnum(mc))
		throw ModuleException("Invalid mode character");

	if (mh->GetPrefixRank() && !mc)
		throw ModuleException("Prefix modes must define a mode character");

	if (mc && FindMode(mc, mh->GetModeType()))
		throw ModuleException("Duplicate mode character '" + std::string(1, mc) + "' found");

	if (!mc && !ServerInstance->Config->NameOnlyModes)
		throw ModuleException("<options:nameonlymodes> must be enabled to clear a mode character");

	// find a free ID, and add it
	for(int id = 1; id < MODE_ID_MAX; id++)
	{
		if (handlers[id])
			continue;
		mh->id.SetID(id);
		handlers[id] = mh;
		return;
	}
	// whoops, you need to increase MODE_ID_MAX
	throw ModuleException("Out of Mode IDs!");
}

bool ModeParser::DelMode(ModeHandler* mh)
{
	if (handlers[mh->id.GetID()] != mh)
		return false;

	/* Note: We can't stack here, as we have modes potentially being removed across many different channels.
	 * To stack here we have to make the algorithm slower. Discuss.
	 */
	switch (mh->GetModeType())
	{
		case MODETYPE_USER:
			for (user_hash::iterator i = ServerInstance->Users->clientlist->begin(); i != ServerInstance->Users->clientlist->end();)
			{
				mh->RemoveMode(i++->second);
			}
		break;
		case MODETYPE_CHANNEL:
			for (chan_hash::iterator i = ServerInstance->chanlist->begin(); i != ServerInstance->chanlist->end();)
			{
				mh->RemoveMode(i++->second);
			}
		break;
	}

	handlers[mh->id.GetID()] = NULL;
	mh->id.SetID(0);

	return true;
}

ModeHandler* ModeParser::FindMode(const std::string& name)
{
	for(int id = 1; id < MODE_ID_MAX; id++)
	{
		if (handlers[id] && handlers[id]->name == name)
			return handlers[id];
	}
	return NULL;
}

ModeHandler* ModeParser::FindMode(unsigned const char modeletter, ModeType mt)
{
	for(int id = 1; id < MODE_ID_MAX; id++)
	{
		if (handlers[id] && handlers[id]->GetModeChar() == modeletter && handlers[id]->GetModeType() == mt)
			return handlers[id];
	}
	return NULL;
}

std::string ModeParser::UserModeList()
{
	char modestr[256];
	int pointer = 0;

	for(ModeIDIter id; id; id++)
	{
		ModeHandler* mh = FindMode(id);
		if(mh && mh->GetModeType() == MODETYPE_USER && mh->GetModeChar())
			modestr[pointer++] = mh->GetModeChar();
	}
	std::sort(modestr, modestr + pointer);
	modestr[pointer++] = 0;
	return modestr;
}

std::string ModeParser::ChannelModeList()
{
	char modestr[256];
	int pointer = 0;

	for(ModeIDIter id; id; id++)
	{
		ModeHandler* mh = FindMode(id);
		if (mh && mh->GetModeType() == MODETYPE_CHANNEL && mh->GetModeChar())
			modestr[pointer++] = mh->GetModeChar();
	}
	if (ServerInstance->Config->NameOnlyModes)
		modestr[pointer++] = 'Z';
	modestr[pointer] = 0;
	std::sort(modestr, modestr + pointer);
	return modestr;
}

std::string ModeParser::ParaModeList()
{
	char modestr[256];
	int pointer = 0;

	for(ModeIDIter id; id; id++)
	{
		ModeHandler* mh = FindMode(id);
		if (mh && mh->GetModeType() == MODETYPE_CHANNEL && mh->GetNumParams(true) && mh->GetModeChar())
			modestr[pointer++] = mh->GetModeChar();
	}
	if (ServerInstance->Config->NameOnlyModes)
		modestr[pointer++] = 'Z';
	modestr[pointer] = 0;
	std::sort(modestr, modestr + pointer);
	return modestr;
}

ModeHandler* ModeParser::FindPrefix(unsigned const char pfxletter)
{
	for(ModeIDIter id; id; id++)
	{
		ModeHandler* mh = FindMode(id);
		if (mh && mh->GetPrefix() == pfxletter)
			return mh;
	}
	return NULL;
}

std::string ModeParser::GiveModeList(ModeType m)
{
	std::string type1;	/* Listmodes EXCEPT those with a prefix */
	std::string type2;	/* Modes that take a param when adding or removing */
	std::string type3;	/* Modes that only take a param when adding */
	std::string type4;	/* Modes that dont take a param */
	if (m == MODETYPE_CHANNEL && ServerInstance->Config->NameOnlyModes)
		type1.push_back('Z');

	for(ModeIDIter id; id; id++)
	{
		ModeHandler* mh = FindMode(id);
		if (mh && mh->GetModeType() == m && mh->GetModeChar())
		{
			if (mh->GetNumParams(true))
			{
				if ((mh->IsListMode()) && (!mh->GetPrefix()))
				{
					type1 += mh->GetModeChar();
				}
				else
				{
					/* ... and one parameter when removing */
					if (mh->GetNumParams(false))
					{
						/* But not a list mode */
						if (!mh->GetPrefix())
						{
							type2 += mh->GetModeChar();
						}
					}
					else
					{
						/* No parameters when removing */
						type3 += mh->GetModeChar();
					}
				}
			}
			else
			{
				type4 += mh->GetModeChar();
			}
		}
	}

	std::sort(type1.begin(), type1.end());
	std::sort(type2.begin(), type2.end());
	std::sort(type3.begin(), type3.end());
	std::sort(type4.begin(), type4.end());
	return type1 + "," + type2 + "," + type3 + "," + type4;
}

std::string ModeParser::BuildPrefixes(bool lettersAndModes)
{
	std::string mletters;
	std::string mprefixes;
	std::map<int,std::pair<char,char> > prefixes;

	for(ModeIDIter id; id; id++)
	{
		ModeHandler* mh = FindMode(id);

		if (mh && mh->GetPrefix())
		{
			prefixes[mh->GetPrefixRank()] = std::make_pair(mh->GetPrefix(), mh->GetModeChar());
		}
	}

	for(std::map<int,std::pair<char,char> >::reverse_iterator n = prefixes.rbegin(); n != prefixes.rend(); n++)
	{
		mletters = mletters + n->second.first;
		mprefixes = mprefixes + n->second.second;
	}

	return lettersAndModes ? "(" + mprefixes + ")" + mletters : mletters;
}

bool ModeParser::AddModeWatcher(ModeWatcher* mw)
{
	if (!mw)
		return false;

	ModeHandler* mh = FindMode(mw->GetModeChar(), mw->GetModeType());

	if (!mh)
		return false;

	modewatchers.insert(std::make_pair(mh->id,mw));

	return true;
}

bool ModeParser::DelModeWatcher(ModeWatcher* mw)
{
	if (!mw)
		return false;

	ModeHandler* mh = FindMode(mw->GetModeChar(), mw->GetModeType());

	if (!mh)
		return false;

	std::pair<ModeWatcherMap::iterator,ModeWatcherMap::iterator> watchers = modewatchers.equal_range(mh->id);

	while (watchers.first != watchers.second)
	{
		if (watchers.first->second == mw)
		{
			modewatchers.erase(watchers.first);
			return true;
		}
		watchers.first++;
	}

	return false;
}

/** This default implementation can remove simple user modes
 */
void ModeHandler::RemoveMode(User* user, irc::modestacker* stack)
{
	if (user->IsModeSet(this->GetModeChar()))
	{
		irc::modechange mc(id, "", false);
		if (stack)
		{
			stack->push(mc);
		}
		else
		{
			irc::modestacker tmp;
			tmp.push(mc);
			ServerInstance->SendMode(ServerInstance->FakeClient, user, tmp, false);
		}
	}
}

/** This default implementation can remove non-list modes
 */
void ModeHandler::RemoveMode(Channel* channel, irc::modestacker* stack)
{
	if (channel->IsModeSet(this))
	{
		irc::modechange mc(id, channel->GetModeParameter(this), false);
		if (stack)
		{
			stack->push(mc);
		}
		else
		{
			irc::modestacker ms;
			ms.push(mc);
			ServerInstance->SendMode(ServerInstance->FakeClient, channel, ms, false);
		}
	}
}

void PrefixModeHandler::RemoveMode(Channel* channel, irc::modestacker* stack)
{
	irc::modestacker loc_ms;
	if (!stack)
		stack = &loc_ms;
	for (UserMembIter i = channel->userlist.begin(); i != channel->userlist.end(); i++)
	{
		Membership* memb = i->second;
		if (memb->hasMode(mode))
			stack->push(irc::modechange(id, i->first->nick, false));
	}
	if (stack == &loc_ms)
		ServerInstance->SendMode(ServerInstance->FakeClient, channel, loc_ms, false);
}

void PrefixModeHandler::RemoveMode(User*, irc::modestacker*)
{
}

void ModeHandler::PopulateChanModes(Channel* channel, irc::modestacker& stack)
{
	irc::modechange mc(id, channel->GetModeParameter(this), true);
	if (channel->IsModeSet(this))
		stack.push(mc);
}

void ListExtItem::free(void* item)
{
	modelist* ml = static_cast<modelist*>(item);
	delete ml;
}

void ListModeBase::DisplayList(User* user, Channel* channel)
{
	modelist* el = extItem.get(channel);
	if (el)
	{
		for (modelist::reverse_iterator it = el->rbegin(); it != el->rend(); ++it)
		{
			user->WriteNumeric(listnumeric, "%s %s %s %s %ld", user->nick.c_str(), channel->name.c_str(), it->mask.c_str(), it->setter.c_str(), (long)it->time);
		}
	}
	user->WriteNumeric(endoflistnumeric, "%s %s :%s", user->nick.c_str(), channel->name.c_str(), endofliststring.c_str());
}

const modelist* ListModeBase::GetList(Channel* channel)
{
	return extItem.get(channel);
}

void ListModeBase::PopulateChanModes(Channel* channel, irc::modestacker& stack)
{
	modelist* ml = extItem.get(channel);
	if (!ml)
		return;
	for(modelist::const_iterator i = ml->begin(); i != ml->end(); i++)
	{
		stack.push(irc::modechange(id, i->mask, true));
	}
}

void ListModeBase::RemoveMode(Channel* channel, irc::modestacker* stack)
{
	modelist* el = extItem.get(channel);
	if (el)
	{
		irc::modestacker modestack;

		for (modelist::iterator it = el->begin(); it != el->end(); it++)
		{
			if (stack)
				stack->push(irc::modechange(id, it->mask, false));
			else
				modestack.push(irc::modechange(id, it->mask, false));
		}

		if (stack)
			return;

		ServerInstance->SendMode(ServerInstance->FakeClient, channel, modestack, false);
	}
}

void ListModeBase::RemoveMode(User*, irc::modestacker* stack)
{
	// listmodes don't get set on users
}

void ListModeBase::DoRehash()
{
	ConfigTagList tags = ServerInstance->Config->GetTags(configtag);

	chanlimits.clear();

	for (ConfigIter i = tags.first; i != tags.second; i++)
	{
		// For each <banlist> tag
		ConfigTag* c = i->second;
		ListLimit limit;
		limit.mask = c->getString("chan");
		limit.limit = c->getInt("limit");

		if (limit.mask.size() && limit.limit > 0)
			chanlimits.push_back(limit);
	}
	if (chanlimits.size() == 0)
	{
		ListLimit limit;
		limit.mask = "*";
		limit.limit = 64;
		chanlimits.push_back(limit);
	}
}

ModeAction ListModeBase::OnModeChange(User* source, User*, Channel* channel, std::string &parameter, bool adding)
{
	// Try and grab the list
	modelist* el = extItem.get(channel);

	if (adding)
	{
		// If there was no list, make one
		if (!el)
			el = extItem.make(channel);

		// Clean the mask up
		if (this->tidy)
			ModeParser::CleanMask(parameter);

		// Check if the item already exists in the list
		for (modelist::iterator it = el->begin(); it != el->end(); it++)
		{
			if (parameter == it->mask)
			{
				/* Give a subclass a chance to error about this */
				TellAlreadyOnList(source, channel, parameter);

				// it does, deny the change
				return MODEACTION_DENY;
			}
		}

		unsigned int maxsize = 0;

		for (limitlist::iterator it = chanlimits.begin(); it != chanlimits.end(); it++)
		{
			if (InspIRCd::Match(channel->name, it->mask))
			{
				// We have a pattern matching the channel...
				maxsize = el->size();
				if (!IS_LOCAL(source) || (maxsize < it->limit))
				{
					/* Ok, it *could* be allowed, now give someone subclassing us
					 * a chance to validate the parameter.
					 * The param is passed by reference, so they can both modify it
					 * and tell us if we allow it or not.
					 *
					 * eg, the subclass could:
					 * 1) allow
					 * 2) 'fix' parameter and then allow
					 * 3) deny
					 */
					if (ValidateParam(source, channel, parameter))
					{
						// And now add the mask onto the list...
						BanItem e;
						e.mask = parameter;
						e.setter = source->nick;
						e.time = ServerInstance->Time();

						el->push_back(e);
						return MODEACTION_ALLOW;
					}
					else
					{
						/* If they deny it they have the job of giving an error message */
						return MODEACTION_DENY;
					}
				}
			}
		}

		/* List is full, give subclass a chance to send a custom message */
		if (!TellListTooLong(source, channel, parameter))
		{
			source->WriteNumeric(478, "%s %s %s :Channel ban/ignore list is full", source->nick.c_str(), channel->name.c_str(), parameter.c_str());
		}

		parameter = "";
		return MODEACTION_DENY;
	}
	else
	{
		// We're taking the mode off
		if (el)
		{
			for (modelist::iterator it = el->begin(); it != el->end(); it++)
			{
				if (parameter == it->mask)
				{
					el->erase(it);
					if (el->size() == 0)
					{
						extItem.unset(channel);
					}
					return MODEACTION_ALLOW;
				}
			}
			/* Tried to remove something that wasn't set */
			TellNotSet(source, channel, parameter);
			parameter = "";
			return MODEACTION_DENY;
		}
		else
		{
			/* Hmm, taking an exception off a non-existant list, DIE */
			TellNotSet(source, channel, parameter);
			parameter = "";
			return MODEACTION_DENY;
		}
	}
	return MODEACTION_DENY;
}


struct builtin_modes
{
	ModeChannelSecret s;
	ModeChannelPrivate p;
	ModeChannelModerated m;
	ModeChannelTopicOps t;

	ModeChannelNoExternal n;
	ModeChannelInviteOnly i;
	ModeChannelKey k;
	ModeChannelLimit l;

	ModeChannelBan b;
	ModeChannelOp o;
	ModeChannelVoice v;

	ModeUserWallops uw;
	ModeUserInvisible ui;
	ModeUserOperator uo;
	ModeUserServerNoticeMask us;

	void init(ModeParser* modes)
	{
		b.init();
		modes->AddMode(&s);
		modes->AddMode(&p);
		modes->AddMode(&m);
		modes->AddMode(&t);
		modes->AddMode(&n);
		modes->AddMode(&i);
		modes->AddMode(&k);
		modes->AddMode(&l);
		modes->AddMode(&b);
		modes->AddMode(&o);
		modes->AddMode(&v);
		modes->AddMode(&uw);
		modes->AddMode(&ui);
		modes->AddMode(&uo);
		modes->AddMode(&us);
	}
};

static builtin_modes static_modes;

ModeParser::ModeParser()
{
	/* Clear mode handler list */
	memset(handlers, 0, sizeof(handlers));

	static_modes.init(this);
}

ModeParser::~ModeParser()
{
}