anope

- supernets anope source code & configuration
git clone git://git.acid.vegas/anope.git
Log | Files | Refs | Archive | README

cs_xop.cpp (17726B)

      1 /* ChanServ core functions
      2  *
      3  * (C) 2003-2022 Anope Team
      4  * Contact us at team@anope.org
      5  *
      6  * Please read COPYING and README for further details.
      7  *
      8  * Based on the original code of Epona by Lara.
      9  * Based on the original code of Services by Andy Church.
     10  */
     11 
     12 #include "module.h"
     13 
     14 namespace
     15 {
     16 	std::vector<Anope::string> order;
     17 	std::map<Anope::string, std::vector<Anope::string> > permissions;
     18 }
     19 
     20 class XOPChanAccess : public ChanAccess
     21 {
     22  public:
     23 	Anope::string type;
     24 
     25 	XOPChanAccess(AccessProvider *p) : ChanAccess(p)
     26 	{
     27 	}
     28 
     29 	bool HasPriv(const Anope::string &priv) const anope_override
     30 	{
     31 		for (std::vector<Anope::string>::iterator it = std::find(order.begin(), order.end(), this->type); it != order.end(); ++it)
     32 		{
     33 			const std::vector<Anope::string> &privs = permissions[*it];
     34 			if (std::find(privs.begin(), privs.end(), priv) != privs.end())
     35 				return true;
     36 		}
     37 		return false;
     38 	}
     39 
     40 	Anope::string AccessSerialize() const anope_override
     41 	{
     42 		return this->type;
     43 	}
     44 
     45 	void AccessUnserialize(const Anope::string &data) anope_override
     46 	{
     47 		this->type = data;
     48 	}
     49 
     50 	static Anope::string DetermineLevel(const ChanAccess *access)
     51 	{
     52 		if (access->provider->name == "access/xop")
     53 		{
     54 			const XOPChanAccess *xaccess = anope_dynamic_static_cast<const XOPChanAccess *>(access);
     55 			return xaccess->type;
     56 		}
     57 		else
     58 		{
     59 			std::map<Anope::string, int> count;
     60 
     61 			for (std::map<Anope::string, std::vector<Anope::string> >::const_iterator it = permissions.begin(), it_end = permissions.end(); it != it_end; ++it)
     62 			{
     63 				int &c = count[it->first];
     64 				const std::vector<Anope::string> &perms = it->second;
     65 				for (unsigned i = 0; i < perms.size(); ++i)
     66 					if (access->HasPriv(perms[i]))
     67 						++c;
     68 			}
     69 
     70 			Anope::string max;
     71 			int maxn = 0;
     72 			for (std::map<Anope::string, int>::iterator it = count.begin(), it_end = count.end(); it != it_end; ++it)
     73 				if (it->second > maxn)
     74 				{
     75 					maxn = it->second;
     76 					max = it->first;
     77 				}
     78 
     79 			return max;
     80 		}
     81 	}
     82 };
     83 
     84 class XOPAccessProvider : public AccessProvider
     85 {
     86  public:
     87 	XOPAccessProvider(Module *o) : AccessProvider(o, "access/xop")
     88 	{
     89 	}
     90 
     91 	ChanAccess *Create() anope_override
     92 	{
     93 		return new XOPChanAccess(this);
     94 	}
     95 };
     96 
     97 class CommandCSXOP : public Command
     98 {
     99  private:
    100 	void DoAdd(CommandSource &source, ChannelInfo *ci, const std::vector<Anope::string> &params)
    101 	{
    102 		Anope::string mask = params.size() > 2 ? params[2] : "";
    103 
    104 		if (mask.empty())
    105 		{
    106 			this->OnSyntaxError(source, "ADD");
    107 			return;
    108 		}
    109 
    110 		if (Anope::ReadOnly)
    111 		{
    112 			source.Reply(_("Sorry, channel %s list modification is temporarily disabled."), source.command.c_str());
    113 			return;
    114 		}
    115 
    116 		AccessGroup access = source.AccessFor(ci);
    117 		const ChanAccess *highest = access.Highest();
    118 		bool override = false;
    119 		const NickAlias *na = NULL;
    120 
    121 		std::vector<Anope::string>::iterator cmd_it = std::find(order.begin(), order.end(), source.command.upper()),
    122 			access_it = highest ? std::find(order.begin(), order.end(), XOPChanAccess::DetermineLevel(highest)) : order.end();
    123 
    124 		if (!access.founder && (!access.HasPriv("ACCESS_CHANGE") || cmd_it <= access_it))
    125 		{
    126 			if (source.HasPriv("chanserv/access/modify"))
    127 				override = true;
    128 			else
    129 			{
    130 				source.Reply(ACCESS_DENIED);
    131 				return;
    132 			}
    133 		}
    134 
    135 		if (IRCD->IsChannelValid(mask))
    136 		{
    137 			if (Config->GetModule("chanserv")->Get<bool>("disallow_channel_access"))
    138 			{
    139 				source.Reply(_("Channels may not be on access lists."));
    140 				return;
    141 			}
    142 
    143 			ChannelInfo *targ_ci = ChannelInfo::Find(mask);
    144 			if (targ_ci == NULL)
    145 			{
    146 				source.Reply(CHAN_X_NOT_REGISTERED, mask.c_str());
    147 				return;
    148 			}
    149 			else if (ci == targ_ci)
    150 			{
    151 				source.Reply(_("You can't add a channel to its own access list."));
    152 				return;
    153 			}
    154 
    155 			mask = targ_ci->name;
    156 		}
    157 		else
    158 		{
    159 			na = NickAlias::Find(mask);
    160 			if (!na && Config->GetModule("chanserv")->Get<bool>("disallow_hostmask_access"))
    161 			{
    162 				source.Reply(_("Masks and unregistered users may not be on access lists."));
    163 				return;
    164 			}
    165 			else if (mask.find_first_of("!*@") == Anope::string::npos && !na)
    166 			{
    167 				User *targ = User::Find(mask, true);
    168 				if (targ != NULL)
    169 					mask = "*!*@" + targ->GetDisplayedHost();
    170 				else
    171 				{
    172 					source.Reply(NICK_X_NOT_REGISTERED, mask.c_str());
    173 					return;
    174 				}
    175 			}
    176 
    177 			if (na)
    178 				mask = na->nick;
    179 		}
    180 
    181 		for (unsigned i = 0; i < ci->GetAccessCount(); ++i)
    182 		{
    183 			const ChanAccess *a = ci->GetAccess(i);
    184 
    185 			if ((na && na->nc == a->GetAccount()) || mask.equals_ci(a->Mask()))
    186 			{
    187 				if ((!highest || *a >= *highest) && !access.founder && !source.HasPriv("chanserv/access/modify"))
    188 				{
    189 					source.Reply(ACCESS_DENIED);
    190 					return;
    191 				}
    192 
    193 				delete ci->EraseAccess(i);
    194 				break;
    195 			}
    196 		}
    197 
    198 		unsigned access_max = Config->GetModule("chanserv")->Get<unsigned>("accessmax", "1024");
    199 		if (access_max && ci->GetDeepAccessCount() >= access_max)
    200 		{
    201 			source.Reply(_("Sorry, you can only have %d access entries on a channel, including access entries from other channels."), access_max);
    202 			return;
    203 		}
    204 
    205 		ServiceReference<AccessProvider> provider("AccessProvider", "access/xop");
    206 		if (!provider)
    207 			return;
    208 		XOPChanAccess *acc = anope_dynamic_static_cast<XOPChanAccess *>(provider->Create());
    209 		acc->SetMask(mask, ci);
    210 		acc->creator = source.GetNick();
    211 		acc->type = source.command.upper();
    212 		acc->last_seen = 0;
    213 		acc->created = Anope::CurTime;
    214 		ci->AddAccess(acc);
    215 
    216 		Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to add " << mask;
    217 
    218 		FOREACH_MOD(OnAccessAdd, (ci, source, acc));
    219 		source.Reply(_("\002%s\002 added to %s %s list."), acc->Mask().c_str(), ci->name.c_str(), source.command.c_str());
    220 	}
    221 
    222 	void DoDel(CommandSource &source, ChannelInfo *ci, const std::vector<Anope::string> &params)
    223 	{
    224 		NickCore *nc = source.nc;
    225 		Anope::string mask = params.size() > 2 ? params[2] : "";
    226 
    227 		if (mask.empty())
    228 		{
    229 			this->OnSyntaxError(source, "DEL");
    230 			return;
    231 		}
    232 
    233 		if (Anope::ReadOnly)
    234 		{
    235 			source.Reply(_("Sorry, channel %s list modification is temporarily disabled."), source.command.c_str());
    236 			return;
    237 		}
    238 
    239 		if (!ci->GetAccessCount())
    240 		{
    241 			source.Reply(_("%s %s list is empty."), ci->name.c_str(), source.command.c_str());
    242 			return;
    243 		}
    244 
    245 		AccessGroup access = source.AccessFor(ci);
    246 		const ChanAccess *highest = access.Highest();
    247 		bool override = false;
    248 
    249 		if (!isdigit(mask[0]) && mask.find_first_of("#!*@") == Anope::string::npos && !NickAlias::Find(mask))
    250 		{
    251 			User *targ = User::Find(mask, true);
    252 			if (targ != NULL)
    253 				mask = "*!*@" + targ->GetDisplayedHost();
    254 			else
    255 			{
    256 				source.Reply(NICK_X_NOT_REGISTERED, mask.c_str());
    257 				return;
    258 			}
    259 		}
    260 
    261 		std::vector<Anope::string>::iterator cmd_it = std::find(order.begin(), order.end(), source.command.upper()),
    262 			access_it = highest ? std::find(order.begin(), order.end(), XOPChanAccess::DetermineLevel(highest)) : order.end();
    263 
    264 		if (!mask.equals_ci(nc->display) && !access.founder && (!access.HasPriv("ACCESS_CHANGE") || cmd_it <= access_it))
    265 		{
    266 			if (source.HasPriv("chanserv/access/modify"))
    267 				override = true;
    268 			else
    269 			{
    270 				source.Reply(ACCESS_DENIED);
    271 				return;
    272 			}
    273 		}
    274 
    275 		/* Special case: is it a number/list?  Only do search if it isn't. */
    276 		if (isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos)
    277 		{
    278 			class XOPDelCallback : public NumberList
    279 			{
    280 				CommandSource &source;
    281 				ChannelInfo *ci;
    282 				Command *c;
    283 				unsigned deleted;
    284 				Anope::string nicks;
    285 				bool override;
    286 			 public:
    287 				XOPDelCallback(CommandSource &_source, ChannelInfo *_ci, Command *_c, bool _override, const Anope::string &numlist) : NumberList(numlist, true), source(_source), ci(_ci), c(_c), deleted(0), override(_override)
    288 				{
    289 				}
    290 
    291 				~XOPDelCallback()
    292 				{
    293 					if (!deleted)
    294 						 source.Reply(_("No matching entries on %s %s list."), ci->name.c_str(), source.command.c_str());
    295 					else
    296 					{
    297 						Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, c, ci) << "to delete " << nicks;
    298 
    299 						if (deleted == 1)
    300 							source.Reply(_("Deleted one entry from %s %s list."), ci->name.c_str(), source.command.c_str());
    301 						else
    302 							source.Reply(_("Deleted %d entries from %s %s list."), deleted, ci->name.c_str(), source.command.c_str());
    303 					}
    304 				}
    305 
    306 				void HandleNumber(unsigned number) anope_override
    307 				{
    308 					if (!number || number > ci->GetAccessCount())
    309 						return;
    310 
    311 					ChanAccess *caccess = ci->GetAccess(number - 1);
    312 
    313 					if (caccess->provider->name != "access/xop" || this->source.command.upper() != caccess->AccessSerialize())
    314 						return;
    315 
    316 					++deleted;
    317 					if (!nicks.empty())
    318 						nicks += ", " + caccess->Mask();
    319 					else
    320 						nicks = caccess->Mask();
    321 
    322 					ci->EraseAccess(number - 1);
    323 					FOREACH_MOD(OnAccessDel, (ci, source, caccess));
    324 					delete caccess;
    325 				}
    326 			}
    327 			delcallback(source, ci, this, override, mask);
    328 			delcallback.Process();
    329 		}
    330 		else
    331 		{
    332 			for (unsigned i = 0; i < ci->GetAccessCount(); ++i)
    333 			{
    334 				ChanAccess *a = ci->GetAccess(i);
    335 
    336 				if (a->provider->name != "access/xop" || source.command.upper() != a->AccessSerialize())
    337 					continue;
    338 
    339 				if (a->Mask().equals_ci(mask))
    340 				{
    341 					Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to delete " << a->Mask();
    342 
    343 					source.Reply(_("\002%s\002 deleted from %s %s list."), a->Mask().c_str(), ci->name.c_str(), source.command.c_str());
    344 
    345 					ci->EraseAccess(i);
    346 					FOREACH_MOD(OnAccessDel, (ci, source, a));
    347 					delete a;
    348 
    349 					return;
    350 				}
    351 			}
    352 
    353 			source.Reply(_("\002%s\002 not found on %s %s list."), mask.c_str(), ci->name.c_str(), source.command.c_str());
    354 		}
    355 	}
    356 
    357 	void DoList(CommandSource &source, ChannelInfo *ci, const std::vector<Anope::string> &params)
    358 	{
    359 
    360 		const Anope::string &nick = params.size() > 2 ? params[2] : "";
    361 
    362 		AccessGroup access = source.AccessFor(ci);
    363 
    364 		if (!access.HasPriv("ACCESS_LIST") && !source.HasPriv("chanserv/access/list"))
    365 		{
    366 			source.Reply(ACCESS_DENIED);
    367 			return;
    368 		}
    369 
    370 		if (!ci->GetAccessCount())
    371 		{
    372 			source.Reply(_("%s %s list is empty."), ci->name.c_str(), source.command.c_str());
    373 			return;
    374 		}
    375 
    376 		ListFormatter list(source.GetAccount());
    377 		list.AddColumn(_("Number")).AddColumn(_("Mask"));
    378 
    379 		if (!nick.empty() && nick.find_first_not_of("1234567890,-") == Anope::string::npos)
    380 		{
    381 			class XOPListCallback : public NumberList
    382 			{
    383 				ListFormatter &list;
    384 				ChannelInfo *ci;
    385 				CommandSource &source;
    386 			 public:
    387 				XOPListCallback(ListFormatter &_list, ChannelInfo *_ci, const Anope::string &numlist, CommandSource &src) : NumberList(numlist, false), list(_list), ci(_ci), source(src)
    388 				{
    389 				}
    390 
    391 				void HandleNumber(unsigned Number) anope_override
    392 				{
    393 					if (!Number || Number > ci->GetAccessCount())
    394 						return;
    395 
    396 					const ChanAccess *a = ci->GetAccess(Number - 1);
    397 
    398 					if (a->provider->name != "access/xop" || this->source.command.upper() != a->AccessSerialize())
    399 						return;
    400 
    401 					ListFormatter::ListEntry entry;
    402 					entry["Number"] = stringify(Number);
    403 					entry["Mask"] = a->Mask();
    404 					this->list.AddEntry(entry);
    405 				}
    406 			} nl_list(list, ci, nick, source);
    407 			nl_list.Process();
    408 		}
    409 		else
    410 		{
    411 			for (unsigned i = 0, end = ci->GetAccessCount(); i < end; ++i)
    412 			{
    413 				const ChanAccess *a = ci->GetAccess(i);
    414 
    415 				if (a->provider->name != "access/xop" || source.command.upper() != a->AccessSerialize())
    416 					continue;
    417 				else if (!nick.empty() && !Anope::Match(a->Mask(), nick))
    418 					continue;
    419 
    420 				ListFormatter::ListEntry entry;
    421 				entry["Number"] = stringify(i + 1);
    422 				entry["Mask"] = a->Mask();
    423 				list.AddEntry(entry);
    424 			}
    425 		}
    426 
    427 		if (list.IsEmpty())
    428 			source.Reply(_("No matching entries on %s access list."), ci->name.c_str());
    429 		else
    430 		{
    431 			std::vector<Anope::string> replies;
    432 			list.Process(replies);
    433 
    434 			source.Reply(_("%s list for %s"), source.command.c_str(), ci->name.c_str());
    435 			for (unsigned i = 0; i < replies.size(); ++i)
    436 				source.Reply(replies[i]);
    437 		}
    438 	}
    439 
    440 	void DoClear(CommandSource &source, ChannelInfo *ci)
    441 	{
    442 		if (Anope::ReadOnly)
    443 		{
    444 			source.Reply(_("Sorry, channel %s list modification is temporarily disabled."), source.command.c_str());
    445 			return;
    446 		}
    447 
    448 		if (!ci->GetAccessCount())
    449 		{
    450 			source.Reply(_("%s %s list is empty."), ci->name.c_str(), source.command.c_str());
    451 			return;
    452 		}
    453 
    454 		if (!source.AccessFor(ci).HasPriv("FOUNDER") && !source.HasPriv("chanserv/access/modify"))
    455 		{
    456 			source.Reply(ACCESS_DENIED);
    457 			return;
    458 		}
    459 
    460 		bool override = !source.AccessFor(ci).HasPriv("FOUNDER");
    461 		Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to clear the access list";
    462 
    463 		for (unsigned i = ci->GetAccessCount(); i > 0; --i)
    464 		{
    465 			const ChanAccess *access = ci->GetAccess(i - 1);
    466 
    467 			if (access->provider->name != "access/xop" || source.command.upper() != access->AccessSerialize())
    468 				continue;
    469 
    470 			delete ci->EraseAccess(i - 1);
    471 		}
    472 
    473 		FOREACH_MOD(OnAccessClear, (ci, source));
    474 
    475 		source.Reply(_("Channel %s %s list has been cleared."), ci->name.c_str(), source.command.c_str());
    476 	}
    477 
    478  public:
    479 	CommandCSXOP(Module *modname) : Command(modname, "chanserv/xop", 2, 4)
    480 	{
    481 		this->SetSyntax(_("\037channel\037 ADD \037mask\037"));
    482 		this->SetSyntax(_("\037channel\037 DEL {\037mask\037 | \037entry-num\037 | \037list\037}"));
    483 		this->SetSyntax(_("\037channel\037 LIST [\037mask\037 | \037list\037]"));
    484 		this->SetSyntax(_("\037channel\037 CLEAR"));
    485 	}
    486 
    487 	const Anope::string GetDesc(CommandSource &source) const anope_override
    488 	{
    489 		return Anope::printf(Language::Translate(source.GetAccount(), _("Modify the list of %s users")), source.command.upper().c_str());
    490 	}
    491 
    492 	void Execute(CommandSource &source, const std::vector<Anope::string> &params) anope_override
    493 	{
    494 		ChannelInfo *ci = ChannelInfo::Find(params[0]);
    495 		if (ci == NULL)
    496 		{
    497 			source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str());
    498 			return;
    499 		}
    500 
    501 		const Anope::string &cmd = params[1];
    502 
    503 		if (cmd.equals_ci("ADD"))
    504 			return this->DoAdd(source, ci, params);
    505 		else if (cmd.equals_ci("DEL"))
    506 			return this->DoDel(source, ci, params);
    507 		else if (cmd.equals_ci("LIST"))
    508 			return this->DoList(source, ci, params);
    509 		else if (cmd.equals_ci("CLEAR"))
    510 			return this->DoClear(source, ci);
    511 		else
    512 			this->OnSyntaxError(source, "");
    513 	}
    514 
    515 
    516 	bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override
    517 	{
    518 		const Anope::string &cmd = source.command.upper();
    519 
    520 		this->SendSyntax(source);
    521 		source.Reply(" ");
    522 		source.Reply(_("Maintains the \002%s list\002 for a channel. Users who match an access entry\n"
    523 				"on the %s list receive the following privileges:\n"
    524 				" "), cmd.c_str(), cmd.c_str());
    525 
    526 		Anope::string buf;
    527 		for (unsigned i = 0; i < permissions[cmd].size(); ++i)
    528 		{
    529 			buf += ", " + permissions[cmd][i];
    530 			if (buf.length() > 75)
    531 			{
    532 				source.Reply("  %s\n", buf.substr(2).c_str());
    533 				buf.clear();
    534 			}
    535 		}
    536 		if (!buf.empty())
    537 		{
    538 			source.Reply("  %s\n", buf.substr(2).c_str());
    539 			buf.clear();
    540 		}
    541 
    542 		source.Reply(_(" \n"
    543 				"The \002%s ADD\002 command adds the given nickname to the\n"
    544 				"%s list.\n"
    545 				" \n"
    546 				"The \002%s DEL\002 command removes the given nick from the\n"
    547 				"%s list. If a list of entry numbers is given, those\n"
    548 				"entries are deleted. (See the example for LIST below.)\n"
    549 				" \n"
    550 				"The \002%s LIST\002 command displays the %s list. If\n"
    551 				"a wildcard mask is given, only those entries matching the\n"
    552 				"mask are displayed. If a list of entry numbers is given,\n"
    553 				"only those entries are shown; for example:\n"
    554 				"   \002%s #channel LIST 2-5,7-9\002\n"
    555 				"      Lists %s entries numbered 2 through 5 and\n"
    556 				"      7 through 9.\n"
    557 				"      \n"
    558 				"The \002%s CLEAR\002 command clears all entries of the\n"
    559 				"%s list."), cmd.c_str(), cmd.c_str(), cmd.c_str(), cmd.c_str(),
    560 				cmd.c_str(), cmd.c_str(), cmd.c_str(), cmd.c_str(), cmd.c_str(), cmd.c_str());
    561 		BotInfo *access_bi, *flags_bi;
    562 		Anope::string access_cmd, flags_cmd;
    563 		Command::FindCommandFromService("chanserv/access", access_bi, access_cmd);
    564 		Command::FindCommandFromService("chanserv/flags", flags_bi, flags_cmd);
    565 		if (!access_cmd.empty() || !flags_cmd.empty())
    566 		{
    567 			source.Reply(_("Alternative methods of modifying channel access lists are\n"
    568 					"available. "));
    569 			if (!access_cmd.empty())
    570 				source.Reply(_("See \002%s%s HELP %s\002 for more information\n"
    571 						"about the access list."), Config->StrictPrivmsg.c_str(), access_bi->nick.c_str(), access_cmd.c_str());
    572 			if (!flags_cmd.empty())
    573 				source.Reply(_("See \002%s%s HELP %s\002 for more information\n"
    574 						"about the flags system."), Config->StrictPrivmsg.c_str(), flags_bi->nick.c_str(), flags_cmd.c_str());
    575 		}
    576 		return true;
    577 	}
    578 };
    579 
    580 class CSXOP : public Module
    581 {
    582 	XOPAccessProvider accessprovider;
    583 	CommandCSXOP commandcsxop;
    584 
    585  public:
    586 	CSXOP(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR),
    587 		accessprovider(this), commandcsxop(this)
    588 	{
    589 		this->SetPermanent(true);
    590 
    591 	}
    592 
    593 	void OnReload(Configuration::Conf *conf) anope_override
    594 	{
    595 		order.clear();
    596 		permissions.clear();
    597 
    598 		for (int i = 0; i < conf->CountBlock("privilege"); ++i)
    599 		{
    600 			Configuration::Block *block = conf->GetBlock("privilege", i);
    601 			const Anope::string &pname = block->Get<const Anope::string>("name");
    602 
    603 			Privilege *p = PrivilegeManager::FindPrivilege(pname);
    604 			if (p == NULL)
    605 				continue;
    606 
    607 			const Anope::string &xop = block->Get<const Anope::string>("xop");
    608 			if (pname.empty() || xop.empty())
    609 				continue;
    610 
    611 			permissions[xop].push_back(pname);
    612 		}
    613 
    614 		for (int i = 0; i < conf->CountBlock("command"); ++i)
    615 		{
    616 			Configuration::Block *block = conf->GetBlock("command", i);
    617 			const Anope::string &cname = block->Get<const Anope::string>("name"),
    618 				&cserv = block->Get<const Anope::string>("command");
    619 			if (cname.empty() || cserv != "chanserv/xop")
    620 				continue;
    621 
    622 			order.push_back(cname);
    623 		}
    624 	}
    625 };
    626 
    627 MODULE_INIT(CSXOP)