anope

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

cs_access.cpp (28674B)

      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 static std::map<Anope::string, int16_t, ci::less> defaultLevels;
     15 
     16 static inline void reset_levels(ChannelInfo *ci)
     17 {
     18 	ci->ClearLevels();
     19 	for (std::map<Anope::string, int16_t, ci::less>::iterator it = defaultLevels.begin(), it_end = defaultLevels.end(); it != it_end; ++it)
     20 		ci->SetLevel(it->first, it->second);
     21 }
     22 
     23 class AccessChanAccess : public ChanAccess
     24 {
     25  public:
     26 	int level;
     27 
     28 	AccessChanAccess(AccessProvider *p) : ChanAccess(p), level(0)
     29 	{
     30 	}
     31 
     32 	bool HasPriv(const Anope::string &name) const anope_override
     33 	{
     34 		return this->ci->GetLevel(name) != ACCESS_INVALID && this->level >= this->ci->GetLevel(name);
     35 	}
     36 
     37 	Anope::string AccessSerialize() const anope_override
     38 	{
     39 		return stringify(this->level);
     40 	}
     41 
     42 	void AccessUnserialize(const Anope::string &data) anope_override
     43 	{
     44 		try
     45 		{
     46 			this->level = convertTo<int>(data);
     47 		}
     48 		catch (const ConvertException &)
     49 		{
     50 		}
     51 	}
     52 
     53 	bool operator>(const ChanAccess &other) const anope_override
     54 	{
     55 		if (this->provider != other.provider)
     56 			return ChanAccess::operator>(other);
     57 		else
     58 			return this->level > anope_dynamic_static_cast<const AccessChanAccess *>(&other)->level;
     59 	}
     60 
     61 	bool operator<(const ChanAccess &other) const anope_override
     62 	{
     63 		if (this->provider != other.provider)
     64 			return ChanAccess::operator<(other);
     65 		else
     66 			return this->level < anope_dynamic_static_cast<const AccessChanAccess *>(&other)->level;
     67 	}
     68 };
     69 
     70 class AccessAccessProvider : public AccessProvider
     71 {
     72  public:
     73 	static AccessAccessProvider *me;
     74 
     75 	AccessAccessProvider(Module *o) : AccessProvider(o, "access/access")
     76 	{
     77 		me = this;
     78 	}
     79 
     80 	ChanAccess *Create() anope_override
     81 	{
     82 		return new AccessChanAccess(this);
     83 	}
     84 };
     85 AccessAccessProvider* AccessAccessProvider::me;
     86 
     87 class CommandCSAccess : public Command
     88 {
     89 	void DoAdd(CommandSource &source, ChannelInfo *ci, const std::vector<Anope::string> &params)
     90 	{
     91 		Anope::string mask = params[2];
     92 		Privilege *p = NULL;
     93 		int level = ACCESS_INVALID;
     94 
     95 		try
     96 		{
     97 			level = convertTo<int>(params[3]);
     98 		}
     99 		catch (const ConvertException &)
    100 		{
    101 			p = PrivilegeManager::FindPrivilege(params[3]);
    102 			if (p != NULL && defaultLevels[p->name])
    103 				level = defaultLevels[p->name];
    104 		}
    105 
    106 		if (!level)
    107 		{
    108 			source.Reply(_("Access level must be non-zero."));
    109 			return;
    110 		}
    111 		else if (level <= ACCESS_INVALID || level >= ACCESS_FOUNDER)
    112 		{
    113 			source.Reply(CHAN_ACCESS_LEVEL_RANGE, ACCESS_INVALID + 1, ACCESS_FOUNDER - 1);
    114 			return;
    115 		}
    116 
    117 		AccessGroup u_access = source.AccessFor(ci);
    118 		const ChanAccess *highest = u_access.Highest();
    119 
    120 		AccessChanAccess tmp_access(AccessAccessProvider::me);
    121 		tmp_access.ci = ci;
    122 		tmp_access.level = level;
    123 
    124 		bool override = false;
    125 		const NickAlias *na = NULL;
    126 
    127 		if ((!highest || *highest <= tmp_access) && !u_access.founder)
    128 		{
    129 			if (source.HasPriv("chanserv/access/modify"))
    130 				override = true;
    131 			else
    132 			{
    133 				source.Reply(ACCESS_DENIED);
    134 				return;
    135 			}
    136 		}
    137 
    138 		if (IRCD->IsChannelValid(mask))
    139 		{
    140 			if (Config->GetModule("chanserv")->Get<bool>("disallow_channel_access"))
    141 			{
    142 				source.Reply(_("Channels may not be on access lists."));
    143 				return;
    144 			}
    145 
    146 			ChannelInfo *targ_ci = ChannelInfo::Find(mask);
    147 			if (targ_ci == NULL)
    148 			{
    149 				source.Reply(CHAN_X_NOT_REGISTERED, mask.c_str());
    150 				return;
    151 			}
    152 			else if (ci == targ_ci)
    153 			{
    154 				source.Reply(_("You can't add a channel to its own access list."));
    155 				return;
    156 			}
    157 
    158 			mask = targ_ci->name;
    159 		}
    160 		else
    161 		{
    162 			na = NickAlias::Find(mask);
    163 
    164 			if (!na && Config->GetModule("chanserv")->Get<bool>("disallow_hostmask_access"))
    165 			{
    166 				source.Reply(_("Masks and unregistered users may not be on access lists."));
    167 				return;
    168 			}
    169 			else if (mask.find_first_of("!*@") == Anope::string::npos && !na)
    170 			{
    171 				User *targ = User::Find(mask, true);
    172 				if (targ != NULL)
    173 					mask = "*!*@" + targ->GetDisplayedHost();
    174 				else
    175 				{
    176 					source.Reply(NICK_X_NOT_REGISTERED, mask.c_str());
    177 					return;
    178 				}
    179 			}
    180 
    181 			if (na)
    182 				mask = na->nick;
    183 		}
    184 
    185 		for (unsigned i = ci->GetAccessCount(); i > 0; --i)
    186 		{
    187 			const ChanAccess *access = ci->GetAccess(i - 1);
    188 			if ((na && na->nc == access->GetAccount()) || mask.equals_ci(access->Mask()))
    189 			{
    190 				/* Don't allow lowering from a level >= u_level */
    191 				if ((!highest || *access >= *highest) && !u_access.founder && !source.HasPriv("chanserv/access/modify"))
    192 				{
    193 					source.Reply(ACCESS_DENIED);
    194 					return;
    195 				}
    196 				delete ci->EraseAccess(i - 1);
    197 				break;
    198 			}
    199 		}
    200 
    201 		unsigned access_max = Config->GetModule("chanserv")->Get<unsigned>("accessmax", "1024");
    202 		if (access_max && ci->GetDeepAccessCount() >= access_max)
    203 		{
    204 			source.Reply(_("Sorry, you can only have %d access entries on a channel, including access entries from other channels."), access_max);
    205 			return;
    206 		}
    207 
    208 		ServiceReference<AccessProvider> provider("AccessProvider", "access/access");
    209 		if (!provider)
    210 			return;
    211 		AccessChanAccess *access = anope_dynamic_static_cast<AccessChanAccess *>(provider->Create());
    212 		access->SetMask(mask, ci);
    213 		access->creator = source.GetNick();
    214 		access->level = level;
    215 		access->last_seen = 0;
    216 		access->created = Anope::CurTime;
    217 		ci->AddAccess(access);
    218 
    219 		FOREACH_MOD(OnAccessAdd, (ci, source, access));
    220 
    221 		Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to add " << mask << " with level " << level;
    222 		if (p != NULL)
    223 			source.Reply(_("\002%s\002 added to %s access list at privilege %s (level %d)"), access->Mask().c_str(), ci->name.c_str(), p->name.c_str(), level);
    224 		else
    225 			source.Reply(_("\002%s\002 added to %s access list at level \002%d\002."), access->Mask().c_str(), ci->name.c_str(), level);
    226 	}
    227 
    228 	void DoDel(CommandSource &source, ChannelInfo *ci, const std::vector<Anope::string> &params)
    229 	{
    230 		Anope::string mask = params[2];
    231 
    232 		if (!isdigit(mask[0]) && mask.find_first_of("#!*@") == Anope::string::npos && !NickAlias::Find(mask))
    233 		{
    234 			User *targ = User::Find(mask, true);
    235 			if (targ != NULL)
    236 				mask = "*!*@" + targ->GetDisplayedHost();
    237 			else
    238 			{
    239 				source.Reply(NICK_X_NOT_REGISTERED, mask.c_str());
    240 				return;
    241 			}
    242 		}
    243 
    244 		if (!ci->GetAccessCount())
    245 			source.Reply(_("%s access list is empty."), ci->name.c_str());
    246 		else if (isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos)
    247 		{
    248 			class AccessDelCallback : public NumberList
    249 			{
    250 				CommandSource &source;
    251 				ChannelInfo *ci;
    252 				Command *c;
    253 				unsigned deleted;
    254 				Anope::string Nicks;
    255 				bool denied;
    256 				bool override;
    257 			 public:
    258 				AccessDelCallback(CommandSource &_source, ChannelInfo *_ci, Command *_c, const Anope::string &numlist) : NumberList(numlist, true), source(_source), ci(_ci), c(_c), deleted(0), denied(false), override(false)
    259 				{
    260 					if (!source.AccessFor(ci).HasPriv("ACCESS_CHANGE") && source.HasPriv("chanserv/access/modify"))
    261 						this->override = true;
    262 				}
    263 
    264 				~AccessDelCallback()
    265 				{
    266 					if (denied && !deleted)
    267 						source.Reply(ACCESS_DENIED);
    268 					else if (!deleted)
    269 						source.Reply(_("No matching entries on %s access list."), ci->name.c_str());
    270 					else
    271 					{
    272 						Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, c, ci) << "to delete " << Nicks;
    273 
    274 						if (deleted == 1)
    275 							source.Reply(_("Deleted 1 entry from %s access list."), ci->name.c_str());
    276 						else
    277 							source.Reply(_("Deleted %d entries from %s access list."), deleted, ci->name.c_str());
    278 					}
    279 				}
    280 
    281 				void HandleNumber(unsigned Number) anope_override
    282 				{
    283 					if (!Number || Number > ci->GetAccessCount())
    284 						return;
    285 
    286 					ChanAccess *access = ci->GetAccess(Number - 1);
    287 
    288 					AccessGroup ag = source.AccessFor(ci);
    289 					const ChanAccess *u_highest = ag.Highest();
    290 
    291 					if ((!u_highest || *u_highest <= *access) && !ag.founder && !this->override && access->GetAccount() != source.nc)
    292 					{
    293 						denied = true;
    294 						return;
    295 					}
    296 
    297 					++deleted;
    298 					if (!Nicks.empty())
    299 						Nicks += ", " + access->Mask();
    300 					else
    301 						Nicks = access->Mask();
    302 
    303 					ci->EraseAccess(Number - 1);
    304 
    305 					FOREACH_MOD(OnAccessDel, (ci, source, access));
    306 					delete access;
    307 				}
    308 			}
    309 			delcallback(source, ci, this, mask);
    310 			delcallback.Process();
    311 		}
    312 		else
    313 		{
    314 			AccessGroup u_access = source.AccessFor(ci);
    315 			const ChanAccess *highest = u_access.Highest();
    316 
    317 			for (unsigned i = ci->GetAccessCount(); i > 0; --i)
    318 			{
    319 				ChanAccess *access = ci->GetAccess(i - 1);
    320 				if (mask.equals_ci(access->Mask()))
    321 				{
    322 					if (access->GetAccount() != source.nc && !u_access.founder && (!highest || *highest <= *access) && !source.HasPriv("chanserv/access/modify"))
    323 						source.Reply(ACCESS_DENIED);
    324 					else
    325 					{
    326 						source.Reply(_("\002%s\002 deleted from %s access list."), access->Mask().c_str(), ci->name.c_str());
    327 						bool override = !u_access.founder && !u_access.HasPriv("ACCESS_CHANGE") && access->GetAccount() != source.nc;
    328 						Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to delete " << access->Mask();
    329 
    330 						ci->EraseAccess(i - 1);
    331 						FOREACH_MOD(OnAccessDel, (ci, source, access));
    332 						delete access;
    333 					}
    334 					return;
    335 				}
    336 			}
    337 
    338 			source.Reply(_("\002%s\002 not found on %s access list."), mask.c_str(), ci->name.c_str());
    339 		}
    340 
    341 		return;
    342 	}
    343 
    344 	void ProcessList(CommandSource &source, ChannelInfo *ci, const std::vector<Anope::string> &params, ListFormatter &list)
    345 	{
    346 		const Anope::string &nick = params.size() > 2 ? params[2] : "";
    347 
    348 		if (!ci->GetAccessCount())
    349 			source.Reply(_("%s access list is empty."), ci->name.c_str());
    350 		else if (!nick.empty() && nick.find_first_not_of("1234567890,-") == Anope::string::npos)
    351 		{
    352 			class AccessListCallback : public NumberList
    353 			{
    354 				ListFormatter &list;
    355 				ChannelInfo *ci;
    356 
    357 			 public:
    358 				AccessListCallback(ListFormatter &_list, ChannelInfo *_ci, const Anope::string &numlist) : NumberList(numlist, false), list(_list), ci(_ci)
    359 				{
    360 				}
    361 
    362 				void HandleNumber(unsigned number) anope_override
    363 				{
    364 					if (!number || number > ci->GetAccessCount())
    365 						return;
    366 
    367 					const ChanAccess *access = ci->GetAccess(number - 1);
    368 
    369 					Anope::string timebuf;
    370 					if (ci->c)
    371 						for (Channel::ChanUserList::const_iterator cit = ci->c->users.begin(), cit_end = ci->c->users.end(); cit != cit_end; ++cit)
    372 						{
    373 							ChannelInfo *p;
    374 							if (access->Matches(cit->second->user, cit->second->user->Account(), p))
    375 								timebuf = "Now";
    376 						}
    377 					if (timebuf.empty())
    378 					{
    379 						if (access->last_seen == 0)
    380 							timebuf = "Never";
    381 						else
    382 							timebuf = Anope::strftime(access->last_seen, NULL, true);
    383 					}
    384 
    385 					ListFormatter::ListEntry entry;
    386 					entry["Number"] = stringify(number);
    387 					entry["Level"] = access->AccessSerialize();
    388 					entry["Mask"] = access->Mask();
    389 					entry["By"] = access->creator;
    390 					entry["Last seen"] = timebuf;
    391 					this->list.AddEntry(entry);
    392 				}
    393 			}
    394 			nl_list(list, ci, nick);
    395 			nl_list.Process();
    396 		}
    397 		else
    398 		{
    399 			for (unsigned i = 0, end = ci->GetAccessCount(); i < end; ++i)
    400 			{
    401 				const ChanAccess *access = ci->GetAccess(i);
    402 
    403 				if (!nick.empty() && !Anope::Match(access->Mask(), nick))
    404 					continue;
    405 
    406 				Anope::string timebuf;
    407 				if (ci->c)
    408 					for (Channel::ChanUserList::const_iterator cit = ci->c->users.begin(), cit_end = ci->c->users.end(); cit != cit_end; ++cit)
    409 					{
    410 						ChannelInfo *p;
    411 						if (access->Matches(cit->second->user, cit->second->user->Account(), p))
    412 							timebuf = "Now";
    413 					}
    414 				if (timebuf.empty())
    415 				{
    416 					if (access->last_seen == 0)
    417 						timebuf = "Never";
    418 					else
    419 						timebuf = Anope::strftime(access->last_seen, NULL, true);
    420 				}
    421 
    422 				ListFormatter::ListEntry entry;
    423 				entry["Number"] = stringify(i + 1);
    424 				entry["Level"] = access->AccessSerialize();
    425 				entry["Mask"] = access->Mask();
    426 				entry["By"] = access->creator;
    427 				entry["Last seen"] = timebuf;
    428 				list.AddEntry(entry);
    429 			}
    430 		}
    431 
    432 		if (list.IsEmpty())
    433 			source.Reply(_("No matching entries on %s access list."), ci->name.c_str());
    434 		else
    435 		{
    436 			std::vector<Anope::string> replies;
    437 			list.Process(replies);
    438 
    439 			source.Reply(_("Access list for %s:"), ci->name.c_str());
    440 
    441 			for (unsigned i = 0; i < replies.size(); ++i)
    442 				source.Reply(replies[i]);
    443 
    444 			source.Reply(_("End of access list"));
    445 		}
    446 
    447 		return;
    448 	}
    449 
    450 	void DoList(CommandSource &source, ChannelInfo *ci, const std::vector<Anope::string> &params)
    451 	{
    452 		if (!ci->GetAccessCount())
    453 		{
    454 			source.Reply(_("%s access list is empty."), ci->name.c_str());
    455 			return;
    456 		}
    457 
    458 		ListFormatter list(source.GetAccount());
    459 		list.AddColumn(_("Number")).AddColumn(_("Level")).AddColumn(_("Mask"));
    460 		this->ProcessList(source, ci, params, list);
    461 	}
    462 
    463 	void DoView(CommandSource &source, ChannelInfo *ci, const std::vector<Anope::string> &params)
    464 	{
    465 		if (!ci->GetAccessCount())
    466 		{
    467 			source.Reply(_("%s access list is empty."), ci->name.c_str());
    468 			return;
    469 		}
    470 
    471 		ListFormatter list(source.GetAccount());
    472 		list.AddColumn(_("Number")).AddColumn(_("Level")).AddColumn(_("Mask")).AddColumn(_("By")).AddColumn(_("Last seen"));
    473 		this->ProcessList(source, ci, params, list);
    474 	}
    475 
    476 	void DoClear(CommandSource &source, ChannelInfo *ci)
    477 	{
    478 		if (!source.IsFounder(ci) && !source.HasPriv("chanserv/access/modify"))
    479 			source.Reply(ACCESS_DENIED);
    480 		else
    481 		{
    482 			FOREACH_MOD(OnAccessClear, (ci, source));
    483 
    484 			ci->ClearAccess();
    485 
    486 			source.Reply(_("Channel %s access list has been cleared."), ci->name.c_str());
    487 
    488 			bool override = !source.IsFounder(ci);
    489 			Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to clear the access list";
    490 		}
    491 
    492 		return;
    493 	}
    494 
    495  public:
    496 	CommandCSAccess(Module *creator) : Command(creator, "chanserv/access", 2, 4)
    497 	{
    498 		this->SetDesc(_("Modify the list of privileged users"));
    499 		this->SetSyntax(_("\037channel\037 ADD \037mask\037 \037level\037"));
    500 		this->SetSyntax(_("\037channel\037 DEL {\037mask\037 | \037entry-num\037 | \037list\037}"));
    501 		this->SetSyntax(_("\037channel\037 LIST [\037mask\037 | \037list\037]"));
    502 		this->SetSyntax(_("\037channel\037 VIEW [\037mask\037 | \037list\037]"));
    503 		this->SetSyntax(_("\037channel\037 CLEAR"));
    504 	}
    505 
    506 	void Execute(CommandSource &source, const std::vector<Anope::string> &params) anope_override
    507 	{
    508 		const Anope::string &cmd = params[1];
    509 		const Anope::string &nick = params.size() > 2 ? params[2] : "";
    510 		const Anope::string &s = params.size() > 3 ? params[3] : "";
    511 
    512 		ChannelInfo *ci = ChannelInfo::Find(params[0]);
    513 		if (ci == NULL)
    514 		{
    515 			source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str());
    516 			return;
    517 		}
    518 
    519 		bool is_list = cmd.equals_ci("LIST") || cmd.equals_ci("VIEW");
    520 		bool is_clear = cmd.equals_ci("CLEAR");
    521 		bool is_del = cmd.equals_ci("DEL");
    522 
    523 		bool has_access = false;
    524 		if (source.HasPriv("chanserv/access/modify"))
    525 			has_access = true;
    526 		else if (is_list && source.HasPriv("chanserv/access/list"))
    527 			has_access = true;
    528 		else if (is_list && source.AccessFor(ci).HasPriv("ACCESS_LIST"))
    529 			has_access = true;
    530 		else if (source.AccessFor(ci).HasPriv("ACCESS_CHANGE"))
    531 			has_access = true;
    532 		else if (is_del)
    533 		{
    534 			const NickAlias *na = NickAlias::Find(nick);
    535 			if (na && na->nc == source.GetAccount())
    536 				has_access = true;
    537 		}
    538 
    539 		/* If LIST, we don't *require* any parameters, but we can take any.
    540 		 * If DEL, we require a nick and no level.
    541 		 * Else (ADD), we require a level (which implies a nick). */
    542 		if (is_list || is_clear ? 0 : (cmd.equals_ci("DEL") ? (nick.empty() || !s.empty()) : s.empty()))
    543 			this->OnSyntaxError(source, cmd);
    544 		else if (!has_access)
    545 			source.Reply(ACCESS_DENIED);
    546 		else if (Anope::ReadOnly && !is_list)
    547 			source.Reply(_("Sorry, channel access list modification is temporarily disabled."));
    548 		else if (cmd.equals_ci("ADD"))
    549 			this->DoAdd(source, ci, params);
    550 		else if (cmd.equals_ci("DEL"))
    551 			this->DoDel(source, ci, params);
    552 		else if (cmd.equals_ci("LIST"))
    553 			this->DoList(source, ci, params);
    554 		else if (cmd.equals_ci("VIEW"))
    555 			this->DoView(source, ci, params);
    556 		else if (cmd.equals_ci("CLEAR"))
    557 			this->DoClear(source, ci);
    558 		else
    559 			this->OnSyntaxError(source, "");
    560 
    561 		return;
    562 	}
    563 
    564 	bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override
    565 	{
    566 		this->SendSyntax(source);
    567 		source.Reply(" ");
    568 		source.Reply(_("Maintains the \002access list\002 for a channel.  The access\n"
    569 				"list specifies which users are allowed chanop status or\n"
    570 				"access to %s commands on the channel.  Different\n"
    571 				"user levels allow for access to different subsets of\n"
    572 				"privileges. Any registered user not on the access list has\n"
    573 				"a user level of 0, and any unregistered user has a user level\n"
    574 				"of -1."), source.service->nick.c_str());
    575 		source.Reply(" ");
    576 		source.Reply(_("The \002ACCESS ADD\002 command adds the given mask to the\n"
    577 				"access list with the given user level; if the mask is\n"
    578 				"already present on the list, its access level is changed to\n"
    579 				"the level specified in the command.  The \037level\037 specified\n"
    580 				"may be a numerical level or the name of a privilege (eg AUTOOP).\n"
    581 				"When a user joins the channel the access they receive is from the\n"
    582 				"highest level entry in the access list."));
    583 		if (!Config->GetModule("chanserv")->Get<bool>("disallow_channel_access"))
    584 			source.Reply(_("The given mask may also be a channel, which will use the\n"
    585 					"access list from the other channel up to the given \037level\037."));
    586 		source.Reply(" ");
    587 		source.Reply(_("The \002ACCESS DEL\002 command removes the given nick from the\n"
    588 				"access list.  If a list of entry numbers is given, those\n"
    589 				"entries are deleted.  (See the example for LIST below.)\n"
    590 				"You may remove yourself from an access list, even if you\n"
    591 				"do not have access to modify that list otherwise."));
    592 		source.Reply(" ");
    593 		source.Reply(_("The \002ACCESS LIST\002 command displays the access list.  If\n"
    594 				"a wildcard mask is given, only those entries matching the\n"
    595 				"mask are displayed.  If a list of entry numbers is given,\n"
    596 				"only those entries are shown; for example:\n"
    597 				"   \002ACCESS #channel LIST 2-5,7-9\002\n"
    598 				"      Lists access entries numbered 2 through 5 and\n"
    599 				"      7 through 9.\n"
    600 				" \n"
    601 				"The \002ACCESS VIEW\002 command displays the access list similar\n"
    602 				"to \002ACCESS LIST\002 but shows the creator and last used time.\n"
    603 				" \n"
    604 				"The \002ACCESS CLEAR\002 command clears all entries of the\n"
    605 				"access list."));
    606 		source.Reply(" ");
    607 
    608 		BotInfo *bi;
    609 		Anope::string cmd;
    610 		if (Command::FindCommandFromService("chanserv/levels", bi, cmd))
    611 			source.Reply(_("\002User access levels\002 can be seen by using the\n"
    612 					"\002%s\002 command; type \002%s%s HELP LEVELS\002 for\n"
    613 					"information."), cmd.c_str(), Config->StrictPrivmsg.c_str(), bi->nick.c_str());
    614 		return true;
    615 	}
    616 };
    617 
    618 class CommandCSLevels : public Command
    619 {
    620 	void DoSet(CommandSource &source, ChannelInfo *ci, const std::vector<Anope::string> &params)
    621 	{
    622 		const Anope::string &what = params[2];
    623 		const Anope::string &lev = params[3];
    624 
    625 		int level;
    626 
    627 		if (lev.equals_ci("FOUNDER"))
    628 			level = ACCESS_FOUNDER;
    629 		else
    630 		{
    631 			try
    632 			{
    633 				level = convertTo<int>(lev);
    634 			}
    635 			catch (const ConvertException &)
    636 			{
    637 				this->OnSyntaxError(source, "SET");
    638 				return;
    639 			}
    640 		}
    641 
    642 		if (level <= ACCESS_INVALID || level > ACCESS_FOUNDER)
    643 			source.Reply(_("Level must be between %d and %d inclusive."), ACCESS_INVALID + 1, ACCESS_FOUNDER - 1);
    644 		else
    645 		{
    646 			Privilege *p = PrivilegeManager::FindPrivilege(what);
    647 			if (p == NULL)
    648 				source.Reply(_("Setting \002%s\002 not known.  Type \002%s%s HELP LEVELS\002 for a list of valid settings."), what.c_str(), Config->StrictPrivmsg.c_str(), source.service->nick.c_str());
    649 			else
    650 			{
    651 				bool override = !source.AccessFor(ci).HasPriv("FOUNDER");
    652 				Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to set " << p->name << " to level " << level;
    653 
    654 				ci->SetLevel(p->name, level);
    655 				FOREACH_MOD(OnLevelChange, (source, ci, p->name, level));
    656 
    657 				if (level == ACCESS_FOUNDER)
    658 					source.Reply(_("Level for %s on channel %s changed to founder only."), p->name.c_str(), ci->name.c_str());
    659 				else
    660 					source.Reply(_("Level for \002%s\002 on channel %s changed to \002%d\002."), p->name.c_str(), ci->name.c_str(), level);
    661 			}
    662 		}
    663 	}
    664 
    665 	void DoDisable(CommandSource &source, ChannelInfo *ci, const std::vector<Anope::string> &params)
    666 	{
    667 		const Anope::string &what = params[2];
    668 
    669 		/* Don't allow disabling of the founder level. It would be hard to change it back if you don't have access to use this command */
    670 		if (what.equals_ci("FOUNDER"))
    671 		{
    672 			source.Reply(_("You can not disable the founder privilege because it would be impossible to reenable it at a later time."));
    673 			return;
    674 		}
    675 
    676 		Privilege *p = PrivilegeManager::FindPrivilege(what);
    677 		if (p != NULL)
    678 		{
    679 			bool override = !source.AccessFor(ci).HasPriv("FOUNDER");
    680 			Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to disable " << p->name;
    681 
    682 			ci->SetLevel(p->name, ACCESS_INVALID);
    683 			FOREACH_MOD(OnLevelChange, (source, ci, p->name, ACCESS_INVALID));
    684 
    685 			source.Reply(_("\002%s\002 disabled on channel %s."), p->name.c_str(), ci->name.c_str());
    686 			return;
    687 		}
    688 
    689 		source.Reply(_("Setting \002%s\002 not known.  Type \002%s%s HELP LEVELS\002 for a list of valid settings."), what.c_str(), Config->StrictPrivmsg.c_str(), source.service->nick.c_str());
    690 	}
    691 
    692 	void DoList(CommandSource &source, ChannelInfo *ci)
    693 	{
    694 		source.Reply(_("Access level settings for channel %s:"), ci->name.c_str());
    695 
    696 		ListFormatter list(source.GetAccount());
    697 		list.AddColumn(_("Name")).AddColumn(_("Level"));
    698 
    699 		const std::vector<Privilege> &privs = PrivilegeManager::GetPrivileges();
    700 
    701 		for (unsigned i = 0; i < privs.size(); ++i)
    702 		{
    703 			const Privilege &p = privs[i];
    704 			int16_t j = ci->GetLevel(p.name);
    705 
    706 			ListFormatter::ListEntry entry;
    707 			entry["Name"] = p.name;
    708 
    709 			if (j == ACCESS_INVALID)
    710 				entry["Level"] = Language::Translate(source.GetAccount(), _("(disabled)"));
    711 			else if (j == ACCESS_FOUNDER)
    712 				entry["Level"] = Language::Translate(source.GetAccount(), _("(founder only)"));
    713 			else
    714 				entry["Level"] = stringify(j);
    715 
    716 			list.AddEntry(entry);
    717 		}
    718 
    719 		std::vector<Anope::string> replies;
    720 		list.Process(replies);
    721 
    722 		for (unsigned i = 0; i < replies.size(); ++i)
    723 			source.Reply(replies[i]);
    724 	}
    725 
    726 	void DoReset(CommandSource &source, ChannelInfo *ci)
    727 	{
    728 		bool override = !source.AccessFor(ci).HasPriv("FOUNDER");
    729 		Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to reset all levels";
    730 
    731 		reset_levels(ci);
    732 		FOREACH_MOD(OnLevelChange, (source, ci, "ALL", 0));
    733 
    734 		source.Reply(_("Access levels for \002%s\002 reset to defaults."), ci->name.c_str());
    735 		return;
    736 	}
    737 
    738  public:
    739 	CommandCSLevels(Module *creator) : Command(creator, "chanserv/levels", 2, 4)
    740 	{
    741 		this->SetDesc(_("Redefine the meanings of access levels"));
    742 		this->SetSyntax(_("\037channel\037 SET \037type\037 \037level\037"));
    743 		this->SetSyntax(_("\037channel\037 {DIS | DISABLE} \037type\037"));
    744 		this->SetSyntax(_("\037channel\037 LIST"));
    745 		this->SetSyntax(_("\037channel\037 RESET"));
    746 	}
    747 
    748 	void Execute(CommandSource &source, const std::vector<Anope::string> &params) anope_override
    749 	{
    750 		const Anope::string &cmd = params[1];
    751 		const Anope::string &what = params.size() > 2 ? params[2] : "";
    752 		const Anope::string &s = params.size() > 3 ? params[3] : "";
    753 
    754 		ChannelInfo *ci = ChannelInfo::Find(params[0]);
    755 		if (ci == NULL)
    756 		{
    757 			source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str());
    758 			return;
    759 		}
    760 
    761 		bool has_access = false;
    762 		if (source.HasPriv("chanserv/access/modify"))
    763 			has_access = true;
    764 		else if (cmd.equals_ci("LIST") && source.HasPriv("chanserv/access/list"))
    765 			has_access = true;
    766 		else if (source.AccessFor(ci).HasPriv("FOUNDER"))
    767 			has_access = true;
    768 
    769 		/* If SET, we want two extra parameters; if DIS[ABLE] or FOUNDER, we want only
    770 		 * one; else, we want none.
    771 		 */
    772 		if (cmd.equals_ci("SET") ? s.empty() : (cmd.substr(0, 3).equals_ci("DIS") ? (what.empty() || !s.empty()) : !what.empty()))
    773 			this->OnSyntaxError(source, cmd);
    774 		else if (!has_access)
    775 			source.Reply(ACCESS_DENIED);
    776 		else if (Anope::ReadOnly && !cmd.equals_ci("LIST"))
    777 			source.Reply(READ_ONLY_MODE);
    778 		else if (cmd.equals_ci("SET"))
    779 			this->DoSet(source, ci, params);
    780 		else if (cmd.equals_ci("DIS") || cmd.equals_ci("DISABLE"))
    781 			this->DoDisable(source, ci, params);
    782 		else if (cmd.equals_ci("LIST"))
    783 			this->DoList(source, ci);
    784 		else if (cmd.equals_ci("RESET"))
    785 			this->DoReset(source, ci);
    786 		else
    787 			this->OnSyntaxError(source, "");
    788 
    789 		return;
    790 	}
    791 
    792 	bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override
    793 	{
    794 		if (subcommand.equals_ci("DESC"))
    795 		{
    796 			source.Reply(_("The following feature/function names are available:"));
    797 
    798 			ListFormatter list(source.GetAccount());
    799 			list.AddColumn(_("Name")).AddColumn(_("Description"));
    800 
    801 			const std::vector<Privilege> &privs = PrivilegeManager::GetPrivileges();
    802 			for (unsigned i = 0; i < privs.size(); ++i)
    803 			{
    804 				const Privilege &p = privs[i];
    805 				ListFormatter::ListEntry entry;
    806 				entry["Name"] = p.name;
    807 				entry["Description"] = Language::Translate(source.nc, p.desc.c_str());
    808 				list.AddEntry(entry);
    809 			}
    810 
    811 			std::vector<Anope::string> replies;
    812 			list.Process(replies);
    813 
    814 			for (unsigned i = 0; i < replies.size(); ++i)
    815 				source.Reply(replies[i]);
    816 		}
    817 		else
    818 		{
    819 			this->SendSyntax(source);
    820 			source.Reply(" ");
    821 			source.Reply(_("The \002LEVELS\002 command allows fine control over the meaning of\n"
    822 					"the numeric access levels used for channels.  With this\n"
    823 					"command, you can define the access level required for most\n"
    824 					"of %s's functions. (The \002SET FOUNDER\002 and this command\n"
    825 					"are always restricted to the channel founder.)\n"
    826 					" \n"
    827 					"\002LEVELS SET\002 allows the access level for a function or group of\n"
    828 					"functions to be changed. \002LEVELS DISABLE\002 (or \002DIS\002 for short)\n"
    829 					"disables an automatic feature or disallows access to a\n"
    830 					"function by anyone, INCLUDING the founder (although, the founder\n"
    831 					"can always reenable it). Use \002LEVELS SET founder\002 to make a level\n"
    832 					"founder only.\n"
    833 					" \n"
    834 					"\002LEVELS LIST\002 shows the current levels for each function or\n"
    835 					"group of functions. \002LEVELS RESET\002 resets the levels to the\n"
    836 					"default levels of a newly-created channel.\n"
    837 					" \n"
    838 					"For a list of the features and functions whose levels can be\n"
    839 					"set, see \002HELP LEVELS DESC\002."), source.service->nick.c_str());
    840 		}
    841 		return true;
    842 	}
    843 };
    844 
    845 class CSAccess : public Module
    846 {
    847 	AccessAccessProvider accessprovider;
    848 	CommandCSAccess commandcsaccess;
    849 	CommandCSLevels commandcslevels;
    850 
    851  public:
    852 	CSAccess(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR),
    853 		accessprovider(this), commandcsaccess(this), commandcslevels(this)
    854 	{
    855 		this->SetPermanent(true);
    856 
    857 	}
    858 
    859 	void OnReload(Configuration::Conf *conf) anope_override
    860 	{
    861 		defaultLevels.clear();
    862 
    863 		for (int i = 0; i < conf->CountBlock("privilege"); ++i)
    864 		{
    865 			Configuration::Block *priv = conf->GetBlock("privilege", i);
    866 
    867 			const Anope::string &pname = priv->Get<const Anope::string>("name");
    868 
    869 			Privilege *p = PrivilegeManager::FindPrivilege(pname);
    870 			if (p == NULL)
    871 				continue;
    872 
    873 			const Anope::string &value = priv->Get<const Anope::string>("level");
    874 			if (value.empty())
    875 				continue;
    876 			else if (value.equals_ci("founder"))
    877 				defaultLevels[p->name] = ACCESS_FOUNDER;
    878 			else if (value.equals_ci("disabled"))
    879 				defaultLevels[p->name] = ACCESS_INVALID;
    880 			else
    881 				defaultLevels[p->name] = priv->Get<int16_t>("level");
    882 		}
    883 	}
    884 
    885 	void OnCreateChan(ChannelInfo *ci) anope_override
    886 	{
    887 		reset_levels(ci);
    888 	}
    889 
    890 	EventReturn OnGroupCheckPriv(const AccessGroup *group, const Anope::string &priv) anope_override
    891 	{
    892 		if (group->ci == NULL)
    893 			return EVENT_CONTINUE;
    894 
    895 		const ChanAccess *highest = group->Highest();
    896 		if (highest && highest->provider == &accessprovider)
    897 		{
    898 			/* Access accessprovider is the only accessprovider with the concept of negative access,
    899 			 * so check they don't have negative access
    900 			 */
    901 			const AccessChanAccess *aca = anope_dynamic_static_cast<const AccessChanAccess *>(highest);
    902 
    903 			if (aca->level < 0)
    904 				return EVENT_CONTINUE;
    905 		}
    906 
    907 		/* Special case. Allows a level of -1 to match anyone, and a level of 0 to match anyone identified. */
    908 		int16_t level = group->ci->GetLevel(priv);
    909 		if (level == -1)
    910 			return EVENT_ALLOW;
    911 		else if (level == 0 && group->nc && !group->nc->HasExt("UNCONFIRMED"))
    912 			return EVENT_ALLOW;
    913 		return EVENT_CONTINUE;
    914 	}
    915 };
    916 
    917 MODULE_INIT(CSAccess)