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 "protocol.h"
#include "account.h"
#include "opflags.h"
#include "u_listmode.h"

/* $ModDesc: Provides support for the +w channel mode, autoop list */

static dynamic_reference<AccountDBProvider> db("accountdb");

/** Handles +n user mode
 */
class NoAutoopMode : public SimpleUserModeHandler
{
 public:
	NoAutoopMode(Module* Creator) : SimpleUserModeHandler(Creator, "noautoop", 'n') { }
};

/** Handles +w channel mode
 */
class AutoOpList : public ListModeBase
{
 public:
	AutoOpList(Module* Creator) : ListModeBase(Creator, "autoop", 'w', "End of Channel Access List", 910, 911, true)
	{
		levelrequired = OP_VALUE;
		fixed_letter = false;
		tidy = false;
	}

	void AccessCheck(ModePermissionData& perm)
	{
		std::string::size_type pos = perm.mc.value.find(':');
		if (pos == 0 || pos == std::string::npos)
		{
			if (perm.mc.adding)
				perm.result = MOD_RES_DENY;
			return;
		}
		unsigned int mylevel = perm.chan->GetAccessRank(perm.source);
		irc::commasepstream modes(perm.mc.value.substr(0, pos));
		std::string mid;
		while (modes.GetToken(mid))
		{
			ModeHandler* mh = mid.length() == 1 ?
				ServerInstance->Modes->FindMode(mid[0], MODETYPE_CHANNEL) :
				ServerInstance->Modes->FindMode(mid);

			if (perm.mc.adding && mh && mh->GetLevelRequired() > mylevel)
			{
				perm.ErrorNumeric(482, "%s :You must be able to set mode '%s' to include it in an autoop",
					perm.chan->name.c_str(), mid.c_str());
				perm.result = MOD_RES_DENY;
			}
		}
	}
};

class ModuleAutoOp : public Module
{
	AutoOpList mh;
	NoAutoopMode mh2;
	dynamic_reference<OpFlagProvider> opflags;
	bool checkonlogin, checkonhostchange, allow_selfup;

public:
	ModuleAutoOp() : mh(this), mh2(this), opflags("opflags")
	{
	}

	void init()
	{
		mh.init();
		ServerInstance->Modules->AddService(mh);
		ServerInstance->Modules->AddService(mh2);

		Implementation list[] = { I_OnPostJoin, I_OnEvent, I_OnChangeHost, I_OnPermissionCheck, I_OnGarbageCollect };
		ServerInstance->Modules->Attach(list, this, sizeof(list)/sizeof(Implementation));
	}

	void PrepareAutoop(Membership* memb, irc::modestacker& ms, std::set<std::string>& flags)
	{
		modelist* list = mh.extItem.get(memb->chan);
		if (!list)
			return;
		for (modelist::const_iterator it = list->begin(); it != list->end(); ++it)
		{
			std::string::size_type colon = it->mask.find(':');
			if (colon == std::string::npos)
				continue;
			if (!memb->chan->CheckBan(memb->user, it->mask.substr(colon+1)))
				continue;
			irc::commasepstream modes(it->mask.substr(0, colon));
			std::string mflag;
			while (modes.GetToken(mflag))
			{
				ModeHandler* given = mflag.length() == 1 ?
					ServerInstance->Modes->FindMode(mflag[0], MODETYPE_CHANNEL) :
					ServerInstance->Modes->FindMode(mflag);
				if (given && given->GetPrefixRank())
					ms.push(irc::modechange(given->id, memb->user->nick, true));
				else if (!given)
				{
					// it's a flag
					flags.insert(mflag);
				}
			}
		}
	}

	void DoAutoop(Membership* memb)
	{
		irc::modestacker ms;
		std::set<std::string> flags;
		PrepareAutoop(memb, ms, flags);
		if (!ms.empty())
		{
			User* src = ServerInstance->Config->CycleHostsFromUser ? memb->user : ServerInstance->FakeClient;
			ServerInstance->Modes->Process(src, memb->chan, ms, false, true);
			ServerInstance->Modes->Send(src, memb->chan, ms);
			ServerInstance->PI->SendMode(src, memb->chan, ms);
		}
		if (opflags && !flags.empty())
		{
			std::string message = ServerInstance->Config->ServerName + " set " + memb->user->nick + " opflags to " + opflags->SetFlags(memb, flags, true);
			memb->chan->WriteChannelWithServ(ServerInstance->Config->ServerName.c_str(), "NOTICE %s :%s", memb->chan->name.c_str(), message.c_str());
			ServerInstance->PI->SendChannelNotice(memb->chan, 0, message);
		}
	}


	void OnPostJoin(Membership* memb)
	{
		if (!IS_LOCAL(memb->user) || memb->user->IsModeSet('n'))
			return;
		DoAutoop(memb);
	}

	void OnEvent(Event& event)
	{
		if(checkonlogin && event.id == "account_login"){
			AccountEvent& acct_event = static_cast<AccountEvent&>(event);
			if(!IS_LOCAL(acct_event.user) || acct_event.user->IsModeSet('n')) return;
			for (UCListIter v = acct_event.user->chans.begin(); v != acct_event.user->chans.end(); ++v)
				DoAutoop(&*v);
		}
	}

	void OnChangeHost(User* u, const std::string&)
	{
		if(checkonhostchange && !u->IsModeSet('n'))
			for (UCListIter v = u->chans.begin(); v != u->chans.end(); ++v)
				DoAutoop(&*v);
	}

	void OnPermissionCheck(PermissionData& perm)
	{
		if (!allow_selfup || !perm.chan || perm.user != perm.source ||
				perm.result != MOD_RES_PASSTHRU)
			return;
		Membership* memb = perm.chan->GetUser(perm.user);
		if (!memb)
			return;
		irc::modestacker ms;
		std::set<std::string> flags;
		PrepareAutoop(memb, ms, flags);

		if (perm.name.substr(0,5) == "mode/")
		{
			ModePermissionData& mpd = static_cast<ModePermissionData&>(perm);
			if (!mpd.mc.adding)
				return;
			for(std::vector<irc::modechange>::const_iterator i = ms.sequence.begin(); i != ms.sequence.end(); ++i)
			{
				if (i->mode == mpd.mc.mode)
				{
					mpd.result = MOD_RES_ALLOW;
					return;
				}
			}
		}
		else if (perm.name == "opflags")
		{
			OpFlagPermissionData& opd = static_cast<OpFlagPermissionData&>(perm);
			irc::commasepstream deltaflags(opd.delta);
			std::string flag;
			while (deltaflags.GetToken(flag))
			{
				if (flag[0] == '-' || flag[0] == '=')
					return;
				if (flag[0] == '+')
					flag = flag.substr(1);
				if (flags.find(flag) == flags.end())
					return;
			}
			// all flags mentioned in this change are in the autoop list
			opd.result = MOD_RES_ALLOW;
		}
	}

	void ReadConfig(ConfigReadStatus&)
	{
		mh.DoRehash();
		ConfigTag* tag = ServerInstance->Config->GetTag("autoop");
		allow_selfup = tag->getBool("selfop", true);
		checkonlogin = tag->getBool("checkonlogin", true);
		checkonhostchange = tag->getBool("checkonhostchange", true);
	}

	void Prioritize()
	{
		ServerInstance->Modules->SetPriority(this, I_OnGarbageCollect, PRIORITY_AFTER, ServerInstance->Modules->Find("m_account_register.so"));
	}

	void OnGarbageCollect ()
	{
		if(!db) return;
		irc::modestacker ms;
		for (chan_hash::const_iterator iter = ServerInstance->chanlist->begin(); iter != ServerInstance->chanlist->end(); ++iter)
		{
			modelist* list = mh.extItem.get(iter->second);
			if (!list)
				continue;
			ms.sequence.clear();
			for (modelist::const_iterator it = list->begin(); it != list->end(); ++it)
			{
				std::string::size_type colon = it->mask.find_first_of(':');
				if (colon == std::string::npos)
					continue;
				std::string mask = it->mask.substr(colon + 1);
				if(mask.length() < 2 || mask[0] != 'r' || mask[1] != ':')
					continue;
				if(!db->GetAccount(mask.substr(2), false))
					ms.sequence.push_back(irc::modechange(mh.id, it->mask, false));
			}
			if(!ms.sequence.empty())
				ServerInstance->SendMode(ServerInstance->FakeClient, iter->second, ms, true);
		}
	}

	Version GetVersion()
	{
		return Version("Provides support for the +w channel mode", VF_VENDOR);
	}
};

MODULE_INIT(ModuleAutoOp)