From f7adc0b35b50f06706872a161f1c7476e6e6981e Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 1 Aug 2011 22:37:27 -0400 Subject: Rewrote the access systems and added a flags access system --- modules/core/bs_assign.cpp | 8 +- modules/core/bs_badwords.cpp | 12 +- modules/core/bs_control.cpp | 4 +- modules/core/bs_info.cpp | 2 +- modules/core/bs_kick.cpp | 8 +- modules/core/bs_main.cpp | 4 +- modules/core/bs_set.cpp | 4 +- modules/core/cs_access.cpp | 408 ++++++++++++++++++++---------- modules/core/cs_akick.cpp | 31 ++- modules/core/cs_ban.cpp | 9 +- modules/core/cs_clearusers.cpp | 5 +- modules/core/cs_clone.cpp | 6 +- modules/core/cs_drop.cpp | 4 +- modules/core/cs_flags.cpp | 455 ++++++++++++++++++++++++++++++++++ modules/core/cs_getkey.cpp | 4 +- modules/core/cs_info.cpp | 3 +- modules/core/cs_invite.cpp | 2 +- modules/core/cs_kick.cpp | 9 +- modules/core/cs_main.cpp | 23 +- modules/core/cs_mode.cpp | 25 +- modules/core/cs_modes.cpp | 58 +++-- modules/core/cs_saset_noexpire.cpp | 2 +- modules/core/cs_set_bantype.cpp | 2 +- modules/core/cs_set_description.cpp | 2 +- modules/core/cs_set_founder.cpp | 4 +- modules/core/cs_set_keeptopic.cpp | 2 +- modules/core/cs_set_opnotice.cpp | 2 +- modules/core/cs_set_peace.cpp | 2 +- modules/core/cs_set_persist.cpp | 2 +- modules/core/cs_set_private.cpp | 2 +- modules/core/cs_set_restricted.cpp | 6 +- modules/core/cs_set_secure.cpp | 2 +- modules/core/cs_set_securefounder.cpp | 2 +- modules/core/cs_set_secureops.cpp | 2 +- modules/core/cs_set_signkick.cpp | 2 +- modules/core/cs_set_successor.cpp | 4 +- modules/core/cs_set_topiclock.cpp | 2 +- modules/core/cs_set_xop.cpp | 146 ----------- modules/core/cs_status.cpp | 75 ------ modules/core/cs_topic.cpp | 4 +- modules/core/cs_unban.cpp | 2 +- modules/core/cs_xop.cpp | 417 ++++++++++++++++++++++--------- modules/core/db_mysql.cpp | 51 ++-- modules/core/db_plain.cpp | 102 ++++---- modules/core/ms_del.cpp | 2 +- modules/core/ms_ignore.cpp | 2 +- modules/core/ms_info.cpp | 2 +- modules/core/ms_list.cpp | 2 +- modules/core/ms_main.cpp | 4 +- modules/core/ms_read.cpp | 2 +- modules/core/ms_set.cpp | 2 +- modules/core/ns_ajoin.cpp | 4 +- modules/core/ns_alist.cpp | 123 ++------- modules/extra/cs_appendtopic.cpp | 4 +- modules/extra/cs_enforce.cpp | 7 +- modules/extra/cs_tban.cpp | 2 +- modules/extra/m_helpchan.cpp | 2 +- modules/extra/m_statusupdate.cpp | 52 ++-- 58 files changed, 1331 insertions(+), 800 deletions(-) create mode 100644 modules/core/cs_flags.cpp delete mode 100644 modules/core/cs_set_xop.cpp delete mode 100644 modules/core/cs_status.cpp (limited to 'modules') diff --git a/modules/core/bs_assign.cpp b/modules/core/bs_assign.cpp index 0a08eec3b..803368994 100644 --- a/modules/core/bs_assign.cpp +++ b/modules/core/bs_assign.cpp @@ -49,7 +49,7 @@ class CommandBSAssign : public Command return; } - if (ci->botflags.HasFlag(BS_NOBOT) || (!check_access(u, ci, CA_ASSIGN) && !u->HasPriv("botserv/administration"))) + if (ci->botflags.HasFlag(BS_NOBOT) || (!ci->HasPriv(u, CA_ASSIGN) && !u->HasPriv("botserv/administration"))) { source.Reply(ACCESS_DENIED); return; @@ -67,7 +67,7 @@ class CommandBSAssign : public Command return; } - bool override = !check_access(u, ci, CA_ASSIGN); + bool override = !ci->HasPriv(u, CA_ASSIGN); Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "for " << bi->nick; bi->Assign(u, ci); @@ -111,7 +111,7 @@ class CommandBSUnassign : public Command return; } - if (!u->HasPriv("botserv/administration") && !check_access(u, ci, CA_ASSIGN)) + if (!u->HasPriv("botserv/administration") && !ci->HasPriv(u, CA_ASSIGN)) { source.Reply(ACCESS_DENIED); return; @@ -129,7 +129,7 @@ class CommandBSUnassign : public Command return; } - bool override = !check_access(u, ci, CA_ASSIGN); + bool override = !ci->HasPriv(u, CA_ASSIGN); Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "for " << ci->bi->nick; ci->bi->UnAssign(u, ci); diff --git a/modules/core/bs_badwords.cpp b/modules/core/bs_badwords.cpp index a272bdff9..a27dca2d4 100644 --- a/modules/core/bs_badwords.cpp +++ b/modules/core/bs_badwords.cpp @@ -60,7 +60,7 @@ class BadwordsDelCallback : public NumberList public: BadwordsDelCallback(CommandSource &_source, ChannelInfo *_ci, Command *_c, const Anope::string &list) : NumberList(list, true), source(_source), ci(_ci), c(_c), Deleted(0), override(false) { - if (!check_access(source.u, ci, CA_BADWORDS) && source.u->HasPriv("botserv/administration")) + if (!ci->HasPriv(source.u, CA_BADWORDS) && source.u->HasPriv("botserv/administration")) this->override = true; } @@ -90,7 +90,7 @@ class CommandBSBadwords : public Command private: void DoList(CommandSource &source, ChannelInfo *ci, const Anope::string &word) { - bool override = !check_access(source.u, ci, CA_BADWORDS); + bool override = !ci->HasPriv(source.u, CA_BADWORDS); Log(override ? LOG_OVERRIDE : LOG_COMMAND, source.u, this, ci) << "LIST"; if (!ci->GetBadWordCount()) @@ -167,7 +167,7 @@ class CommandBSBadwords : public Command } } - bool override = !check_access(source.u, ci, CA_BADWORDS); + bool override = !ci->HasPriv(source.u, CA_BADWORDS); Log(override ? LOG_OVERRIDE : LOG_COMMAND, source.u, this, ci) << "ADD " << realword; ci->AddBadWord(realword, type); @@ -203,7 +203,7 @@ class CommandBSBadwords : public Command return; } - bool override = !check_access(source.u, ci, CA_BADWORDS); + bool override = !ci->HasPriv(source.u, CA_BADWORDS); Log(override ? LOG_OVERRIDE : LOG_COMMAND, source.u, this, ci) << "DEL " << badword->word; source.Reply(_("\002%s\002 deleted from %s bad words list."), badword->word.c_str(), ci->name.c_str()); @@ -216,7 +216,7 @@ class CommandBSBadwords : public Command void DoClear(CommandSource &source, ChannelInfo *ci) { - bool override = !check_access(source.u, ci, CA_BADWORDS); + bool override = !ci->HasPriv(source.u, CA_BADWORDS); Log(override ? LOG_OVERRIDE : LOG_COMMAND, source.u, this, ci) << "CLEAR"; ci->ClearBadWords(); @@ -254,7 +254,7 @@ class CommandBSBadwords : public Command } - if (!check_access(u, ci, CA_BADWORDS) && (!need_args || !u->HasPriv("botserv/administration"))) + if (!ci->HasPriv(u, CA_BADWORDS) && (!need_args || !u->HasPriv("botserv/administration"))) { source.Reply(ACCESS_DENIED); return; diff --git a/modules/core/bs_control.cpp b/modules/core/bs_control.cpp index 431739693..16a5b372a 100644 --- a/modules/core/bs_control.cpp +++ b/modules/core/bs_control.cpp @@ -35,7 +35,7 @@ class CommandBSSay : public Command return; } - if (!check_access(u, ci, CA_SAY)) + if (!ci->HasPriv(u, CA_SAY)) { source.Reply(ACCESS_DENIED); return; @@ -98,7 +98,7 @@ class CommandBSAct : public Command return; } - if (!check_access(u, ci, CA_SAY)) + if (!ci->HasPriv(u, CA_SAY)) { source.Reply(ACCESS_DENIED); return; diff --git a/modules/core/bs_info.cpp b/modules/core/bs_info.cpp index 38178a07b..b48a0b296 100644 --- a/modules/core/bs_info.cpp +++ b/modules/core/bs_info.cpp @@ -70,7 +70,7 @@ class CommandBSInfo : public Command } else if ((ci = cs_findchan(query))) { - if (!check_access(u, ci, CA_FOUNDER) && !u->HasPriv("botserv/administration")) + if (!ci->HasPriv(u, CA_FOUNDER) && !u->HasPriv("botserv/administration")) { source.Reply(ACCESS_DENIED); return; diff --git a/modules/core/bs_kick.cpp b/modules/core/bs_kick.cpp index a8c34e5cd..3f433f773 100644 --- a/modules/core/bs_kick.cpp +++ b/modules/core/bs_kick.cpp @@ -41,13 +41,13 @@ class CommandBSKick : public Command this->OnSyntaxError(source, ""); else if (!value.equals_ci("ON") && !value.equals_ci("OFF")) this->OnSyntaxError(source, ""); - else if (!check_access(u, ci, CA_SET) && !u->HasPriv("botserv/administration")) + else if (!ci->HasPriv(u, CA_SET) && !u->HasPriv("botserv/administration")) source.Reply(ACCESS_DENIED); else if (!ci->bi) source.Reply(BOT_NOT_ASSIGNED); else { - bool override = !check_access(u, ci, CA_SET); + bool override = !ci->HasPriv(u, CA_SET); Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << option << " " << value; if (option.equals_ci("BADWORDS")) @@ -768,7 +768,7 @@ class BSKick : public Module return; bool Allow = true; - if (check_access(u, ci, CA_NOKICK)) + if (ci->HasPriv(u, CA_NOKICK)) Allow = false; else if (ci->botflags.HasFlag(BS_DONTKICKOPS) && (c->HasUserStatus(u, CMODE_HALFOP) || c->HasUserStatus(u, CMODE_OP) || c->HasUserStatus(u, CMODE_PROTECT) || c->HasUserStatus(u, CMODE_OWNER))) Allow = false; @@ -993,7 +993,7 @@ class BSKick : public Module Channel *chan = (*it)->chan; ++it; - if (chan->ci != NULL && chan->ci->botflags.HasFlag(BS_KICK_AMSGS) && !check_access(u, chan->ci, CA_NOKICK)) + if (chan->ci != NULL && chan->ci->botflags.HasFlag(BS_KICK_AMSGS) && !chan->ci->HasPriv(u, CA_NOKICK)) { check_ban(chan->ci, u, TTB_AMSGS); bot_kick(chan->ci, u, _("Don't use AMSGs!")); diff --git a/modules/core/bs_main.cpp b/modules/core/bs_main.cpp index 515b99ae0..69eab6040 100644 --- a/modules/core/bs_main.cpp +++ b/modules/core/bs_main.cpp @@ -81,7 +81,7 @@ class BotServCore : public Module if (bi == NULL || !bi->commands.count(command)) return; - if (check_access(u, c->ci, CA_FANTASIA)) + if (c->ci->HasPriv(u, CA_FANTASIA)) { this->fantasy_channel = c; @@ -125,7 +125,7 @@ class BotServCore : public Module * to has synced, or we'll get greet-floods when the net * recovers from a netsplit. -GD */ - if (c->FindUser(c->ci->bi) && c->ci->botflags.HasFlag(BS_GREET) && user->Account() && !user->Account()->greet.empty() && check_access(user, c->ci, CA_GREET) && user->server->IsSynced()) + if (c->FindUser(c->ci->bi) && c->ci->botflags.HasFlag(BS_GREET) && user->Account() && !user->Account()->greet.empty() && c->ci->HasPriv(user, CA_GREET) && user->server->IsSynced()) { ircdproto->SendPrivmsg(c->ci->bi, c->name, "[%s] %s", user->Account()->display.c_str(), user->Account()->greet.c_str()); c->ci->bi->lastmsg = Anope::CurTime; diff --git a/modules/core/bs_set.cpp b/modules/core/bs_set.cpp index 0fbba874e..ccd1bb615 100644 --- a/modules/core/bs_set.cpp +++ b/modules/core/bs_set.cpp @@ -59,11 +59,11 @@ class CommandBSSet : public Command } else if (!(ci = cs_findchan(chan))) source.Reply(CHAN_X_NOT_REGISTERED, chan.c_str()); - else if (!u->HasPriv("botserv/administration") && !check_access(u, ci, CA_SET)) + else if (!u->HasPriv("botserv/administration") && !ci->HasPriv(u, CA_SET)) source.Reply(ACCESS_DENIED); else { - bool override = !check_access(u, ci, CA_SET); + bool override = !ci->HasPriv(u, CA_SET); Log(override ? LOG_ADMIN : LOG_COMMAND, u, this, ci) << option << " " << value; if (option.equals_ci("DONTKICKOPS")) diff --git a/modules/core/cs_access.cpp b/modules/core/cs_access.cpp index 643292c38..777b0c176 100644 --- a/modules/core/cs_access.cpp +++ b/modules/core/cs_access.cpp @@ -13,6 +13,132 @@ #include "module.h" +enum +{ + ACCESS_INVALID = -10000, + ACCESS_FOUNDER = 10001 +}; + +static struct AccessLevels +{ + ChannelAccess priv; + int default_level; + Anope::string config_name; + Anope::string name; + Anope::string desc; +} defaultLevels[] = { + { CA_ACCESS_CHANGE, 10, "level_change", "ACC-CHANGE", _("Allowed to modify the access list") }, + { CA_ACCESS_LIST, 1, "level_list", "ACC-LIST", _("Allowed to view the access list") }, + { CA_AKICK, 10, "level_akick", "AKICK", _("Allowed to use AKICK command") }, + { CA_ASSIGN, ACCESS_FOUNDER, "level_assign", "ASSIGN", _("Allowed to assign/unassign a bot") }, + { CA_AUTOHALFOP, 4, "level_autohalfop", "AUTOHALFOP", _("Automatic mode +h") }, + { CA_AUTOOP, 5, "level_autoop", "AUTOOP", _("Automatic channel operator status") }, + { CA_AUTOOWNER, 10000, "level_autoowner", "AUTOOWNER", _("Automatic mode +q") }, + { CA_AUTOPROTECT, 10, "level_autoprotect", "AUTOPROTECT", _("Automatic mode +a") }, + { CA_AUTOVOICE, 3, "level_autovoice", "AUTOVOICE", _("Automatic mode +v") }, + { CA_BADWORDS, 10, "level_badwords", "BADWORDS", _("Allowed to modify channel badwords list") }, + { CA_BAN, 4, "level_ban", "BAN", _("Allowed to use ban users") }, + { CA_FANTASIA, 3, "level_fantasia", "FANTASIA", _("Allowed to use fantaisist commands") }, + { CA_FOUNDER, ACCESS_FOUNDER, "level_founder", "FOUNDER", _("Allowed to issue commands restricted to channel founders") }, + { CA_GETKEY, 5, "level_getkey", "GETKEY", _("Allowed to use GETKEY command") }, + { CA_GREET, 5, "level_greet", "GREET", _("Greet message displayed") }, + { CA_HALFOP, 5, "level_halfop", "HALFOP", _("Allowed to (de)halfop users") }, + { CA_HALFOPME, 4, "level_halfopme", "HALFOPME", _("Allowed to (de)halfop him/herself") }, + { CA_INFO, 10000, "level_info", "INFO", _("Allowed to use INFO command with ALL option") }, + { CA_INVITE, 5, "level_invite", "INVITE", _("Allowed to use the INVITE command") }, + { CA_KICK, 4, "level_kick", "KICK", _("Allowed to use the KICK command") }, + { CA_MEMO, 10, "level_memo", "MEMO", _("Allowed to read channel memos") }, + { CA_MODE, 5, "level_mode", "MODE", _("Allowed to change channel modes") }, + { CA_NOKICK, 1, "level_nokick", "NOKICK", _("Never kicked by the bot's kickers") }, + { CA_OPDEOP, 5, "level_opdeop", "OPDEOP", _("Allowed to (de)op users") }, + { CA_OPDEOPME, 5, "level_opdeopme", "OPDEOPME", _("Allowed to (de)op him/herself") }, + { CA_OWNER, ACCESS_FOUNDER, "level_owner", "OWNER", _("Allowed to use (de)owner users") }, + { CA_OWNERME, 10000, "level_ownerme", "OWNERME", _("Allowed to (de)owner him/herself") }, + { CA_PROTECT, 10000, "level_protect", "PROTECT", _("Allowed to (de)protect users") }, + { CA_PROTECTME, 10, "level_protectme", "PROTECTME", _("Allowed to (de)protect him/herself"), }, + { CA_SAY, 5, "level_say", "SAY", _("Allowed to use SAY and ACT commands") }, + { CA_SIGNKICK, ACCESS_FOUNDER, "level_signkick", "SIGNKICK", _("No signed kick when SIGNKICK LEVEL is used") }, + { CA_SET, 10000, "level_set", "SET", _("Allowed to set channel settings") }, + { CA_TOPIC, 5, "level_topic", "TOPIC", _("Allowed to change channel topics") }, + { CA_UNBAN, 4, "level_unban", "UNBAN", _("Allowed to unban users") }, + { CA_VOICE, 4, "level_voice", "VOICE", _("Allowed to (de)voice users") }, + { CA_VOICEME, 3, "level_voiceme", "VOICEME", _("Allowed to (de)voice him/herself") }, + { CA_SIZE, -1, "", "", "" } +}; + +static void reset_levels(ChannelInfo *ci) +{ + for (int i = 0; defaultLevels[i].priv != CA_SIZE; ++i) + ci->levels[defaultLevels[i].priv] = defaultLevels[i].default_level; +} + +class AccessChanAccess : public ChanAccess +{ + public: + int level; + + AccessChanAccess(AccessProvider *p) : ChanAccess(p) + { + } + + bool Matches(User *u, NickCore *nc) + { + if (u && (Anope::Match(u->nick, this->mask) || Anope::Match(u->GetMask(), this->mask))) + return true; + else if (nc && Anope::Match(nc->display, this->mask)) + return true; + return false; + } + + bool HasPriv(ChannelAccess priv) + { + for (int i = 0; defaultLevels[i].priv != CA_SIZE; ++i) + if (defaultLevels[i].priv == priv) + return DetermineLevel(this) >= this->ci->levels[priv]; + return false; + } + + Anope::string Serialize() + { + return stringify(this->level); + } + + void Unserialize(const Anope::string &data) + { + this->level = convertTo(data); + } + + static int DetermineLevel(ChanAccess *access) + { + if (access->provider->name == "access/access") + { + AccessChanAccess *aaccess = debug_cast(access); + return aaccess->level; + } + else + { + int highest = 1; + for (int i = 0; defaultLevels[i].priv != CA_SIZE; ++i) + if (access->ci->levels[defaultLevels[i].priv] > highest && access->HasPriv(defaultLevels[i].priv)) + highest = access->ci->levels[defaultLevels[i].priv]; + return highest; + } + } +}; + +class AccessAccessProvider : public AccessProvider +{ + public: + AccessAccessProvider(Module *o) : AccessProvider(o, "access/access") + { + } + + ChanAccess *Create() + { + return new AccessChanAccess(this); + } +}; + class AccessListCallback : public NumberList { protected: @@ -27,7 +153,7 @@ class AccessListCallback : public NumberList ~AccessListCallback() { if (SentHeader) - source.Reply(_("End of access list."), ci->name.c_str()); + source.Reply(_("End of access list.")); else source.Reply(_("No matching entries on %s access list."), ci->name.c_str()); } @@ -48,13 +174,7 @@ class AccessListCallback : public NumberList static void DoList(CommandSource &source, ChannelInfo *ci, unsigned Number, ChanAccess *access) { - if (ci->HasFlag(CI_XOP)) - { - Anope::string xop = get_xop_level(access->level); - source.Reply(_(" %3d %s %s"), Number + 1, xop.c_str(), access->GetMask().c_str()); - } - else - source.Reply(_(" %3d %4d %s"), Number + 1, access->level, access->GetMask().c_str()); + source.Reply(_(" %3d %4d %s"), Number + 1, AccessChanAccess::DetermineLevel(access), access->mask.c_str()); } }; @@ -82,20 +202,19 @@ class AccessViewCallback : public AccessListCallback static void DoList(CommandSource &source, ChannelInfo *ci, unsigned Number, ChanAccess *access) { Anope::string timebuf; - if (ci->c && nc_on_chan(ci->c, access->nc)) - timebuf = "Now"; - else if (access->last_seen == 0) - timebuf = "Never"; - else - timebuf = do_strftime(access->last_seen); - - if (ci->HasFlag(CI_XOP)) + if (ci->c) + for (CUserList::const_iterator cit = ci->c->users.begin(), cit_end = ci->c->users.end(); cit != cit_end; ++cit) + if (access->Matches((*cit)->user, (*cit)->user->Account())) + timebuf = "Now"; + if (timebuf.empty()) { - Anope::string xop = get_xop_level(access->level); - source.Reply(CHAN_ACCESS_VIEW_XOP_FORMAT, Number + 1, xop.c_str(), access->GetMask().c_str(), access->creator.c_str(), timebuf.c_str()); + if (access->last_seen == 0) + timebuf = "Never"; + else + timebuf = do_strftime(access->last_seen); } - else - source.Reply(CHAN_ACCESS_VIEW_AXS_FORMAT, Number + 1, access->level, access->GetMask().c_str(), access->creator.c_str(), timebuf.c_str()); + + source.Reply(CHAN_ACCESS_VIEW_AXS_FORMAT, Number + 1, AccessChanAccess::DetermineLevel(access), access->mask.c_str(), access->creator.c_str(), timebuf.c_str()); } }; @@ -111,7 +230,7 @@ class AccessDelCallback : public NumberList public: AccessDelCallback(CommandSource &_source, ChannelInfo *_ci, Command *_c, const Anope::string &numlist) : NumberList(numlist, true), source(_source), ci(_ci), c(_c), Deleted(0), Denied(false) { - if (!check_access(source.u, ci, CA_ACCESS_CHANGE) && source.u->HasPriv("chanserv/access/modify")) + if (!ci->HasPriv(source.u, CA_ACCESS_CHANGE) && source.u->HasPriv("chanserv/access/modify")) this->override = true; } @@ -141,9 +260,10 @@ class AccessDelCallback : public NumberList ChanAccess *access = ci->GetAccess(Number - 1); - ChanAccess *u_access = ci->GetAccess(u); - int16 u_level = u_access ? u_access->level : 0; - if (u_level <= access->level && !u->HasPriv("chanserv/access/modify")) + AccessGroup u_access = ci->AccessFor(u); + ChanAccess *u_highest = u_access.Highest(); + + if (u_highest ? AccessChanAccess::DetermineLevel(u_highest) : 0 <= AccessChanAccess::DetermineLevel(access) && !u->HasPriv("chanserv/access/modify")) { Denied = true; return; @@ -151,9 +271,9 @@ class AccessDelCallback : public NumberList ++Deleted; if (!Nicks.empty()) - Nicks += ", " + access->GetMask(); + Nicks += ", " + access->mask; else - Nicks = access->GetMask(); + Nicks = access->mask; FOREACH_MOD(I_OnAccessDel, OnAccessDel(ci, u, access)); @@ -176,17 +296,18 @@ class CommandCSAccess : public Command } catch (const ConvertException &) { } - ChanAccess *u_access = ci->GetAccess(u); - int16 u_level = u_access ? u_access->level : 0; - if (level >= u_level && !u->HasPriv("chanserv/access/modify")) + if (!level) { - source.Reply(ACCESS_DENIED); + source.Reply(_("Access level must be non-zero.")); return; } - if (!level) + AccessGroup u_access = ci->AccessFor(u); + ChanAccess *highest = u_access.Highest(); + int u_level = (highest ? AccessChanAccess::DetermineLevel(highest) : 0); + if (!ci->HasPriv(u, CA_FOUNDER) && level >= u_level && !u->HasPriv("chanserv/access/modify")) { - source.Reply(_("Access level must be non-zero.")); + source.Reply(ACCESS_DENIED); return; } else if (level <= ACCESS_INVALID || level >= ACCESS_FOUNDER) @@ -195,29 +316,22 @@ class CommandCSAccess : public Command return; } - bool override = !check_access(u, ci, CA_ACCESS_CHANGE) || level >= u_level; + bool override = !ci->HasPriv(u, CA_ACCESS_CHANGE) || level >= u_level; - ChanAccess *access = ci->GetAccess(mask, 0, false); - if (access) + for (unsigned i = ci->GetAccessCount(); i > 0; --i) { - /* Don't allow lowering from a level >= u_level */ - if (access->level >= u_level && !u->HasPriv("chanserv/access/modify")) - { - source.Reply(ACCESS_DENIED); - return; - } - if (access->level == level) + ChanAccess *access = ci->GetAccess(i - 1); + if (mask.equals_ci(access->mask)) { - source.Reply(_("Access level for \002%s\002 on %s unchanged from \002%d\002."), access->GetMask().c_str(), ci->name.c_str(), level); - return; + /* Don't allow lowering from a level >= u_level */ + if (AccessChanAccess::DetermineLevel(access) >= u_level && !u->HasPriv("chanserv/access/modify")) + { + source.Reply(ACCESS_DENIED); + return; + } + ci->EraseAccess(i - 1); + break; } - access->level = level; - - FOREACH_MOD(I_OnAccessChange, OnAccessChange(ci, u, access)); - - Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "ADD " << mask << " (level: " << level << ") as level " << u_level; - source.Reply(_("Access level for \002%s\002 on %s changed to \002%d\002."), access->GetMask().c_str(), ci->name.c_str(), level); - return; } if (ci->GetAccessCount() >= Config->CSAccessMax) @@ -226,12 +340,25 @@ class CommandCSAccess : public Command return; } - access = ci->AddAccess(mask, level, u->nick); + if (mask.find_first_of("!*@") == Anope::string::npos && findnick(mask) == NULL) + mask += "!*@*"; + + service_reference provider("access/access"); + if (!provider) + return; + AccessChanAccess *access = debug_cast(provider->Create()); + access->ci = ci; + access->mask = mask; + access->creator = u->nick; + access->level = level; + access->last_seen = 0; + access->created = Anope::CurTime; + ci->AddAccess(access); FOREACH_MOD(I_OnAccessAdd, OnAccessAdd(ci, u, access)); Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "ADD " << mask << " (level: " << level << ") as level " << u_level; - source.Reply(_("\002%s\002 added to %s access list at level \002%d\002."), access->GetMask().c_str(), ci->name.c_str(), level); + source.Reply(_("\002%s\002 added to %s access list at level \002%d\002."), access->mask.c_str(), ci->name.c_str(), level); return; } @@ -251,23 +378,32 @@ class CommandCSAccess : public Command } else { - ChanAccess *access = ci->GetAccess(mask, 0, false); - ChanAccess *u_access = ci->GetAccess(u); - int16 u_level = u_access ? u_access->level : 0; - if (!access) - source.Reply(_("\002%s\002 not found on %s access list."), mask.c_str(), ci->name.c_str()); - else if (access->nc != u->Account() && check_access(u, ci, CA_NOJOIN) && u_level <= access->level && !u->HasPriv("chanserv/access/modify")) - source.Reply(ACCESS_DENIED); - else - { - source.Reply(_("\002%s\002 deleted from %s access list."), access->GetMask().c_str(), ci->name.c_str()); - bool override = !check_access(u, ci, CA_ACCESS_CHANGE) && access->nc != u->Account(); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "DEL " << access->GetMask() << " from level " << access->level; - - FOREACH_MOD(I_OnAccessDel, OnAccessDel(ci, u, access)); + AccessGroup u_access = ci->AccessFor(u); + ChanAccess *highest = u_access.Highest(); + int u_level = (highest ? AccessChanAccess::DetermineLevel(highest) : 0); - ci->EraseAccess(access); + for (unsigned i = ci->GetAccessCount(); i > 0; --i) + { + ChanAccess *access = ci->GetAccess(i - 1); + if (mask.equals_ci(access->mask)) + { + int access_level = AccessChanAccess::DetermineLevel(access); + if (!access->mask.equals_ci(u->Account()->display) && u_level <= access_level && !u->HasPriv("chanserv/access/modify")) + source.Reply(ACCESS_DENIED); + else + { + source.Reply(_("\002%s\002 deleted from %s access list."), access->mask.c_str(), ci->name.c_str()); + bool override = !ci->HasPriv(u, CA_ACCESS_CHANGE) && !access->mask.equals_ci(u->Account()->display); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "DEL " << access->mask; + + FOREACH_MOD(I_OnAccessDel, OnAccessDel(ci, u, access)); + ci->EraseAccess(access); + } + return; + } } + + source.Reply(_("\002%s\002 not found on %s access list."), mask.c_str(), ci->name.c_str()); } return; @@ -292,7 +428,7 @@ class CommandCSAccess : public Command { ChanAccess *access = ci->GetAccess(i); - if (!nick.empty() && !Anope::Match(access->GetMask(), nick)) + if (!nick.empty() && !Anope::Match(access->mask, nick)) continue; if (!SentHeader) @@ -305,7 +441,7 @@ class CommandCSAccess : public Command } if (SentHeader) - source.Reply(_("End of access list."), ci->name.c_str()); + source.Reply(_("End of access list.")); else source.Reply(_("No matching entries on %s access list."), ci->name.c_str()); } @@ -332,7 +468,7 @@ class CommandCSAccess : public Command { ChanAccess *access = ci->GetAccess(i); - if (!nick.empty() && !Anope::Match(access->GetMask(), nick)) + if (!nick.empty() && !Anope::Match(access->mask, nick)) continue; if (!SentHeader) @@ -345,7 +481,7 @@ class CommandCSAccess : public Command } if (SentHeader) - source.Reply(_("End of access list."), ci->name.c_str()); + source.Reply(_("End of access list.")); else source.Reply(_("No matching entries on %s access list."), ci->name.c_str()); } @@ -406,9 +542,9 @@ class CommandCSAccess : public Command bool has_access = false; if (u->HasPriv("chanserv/access/modify")) has_access = true; - else if (is_list && check_access(u, ci, CA_ACCESS_LIST)) + else if (is_list && ci->HasPriv(u, CA_ACCESS_LIST)) has_access = true; - else if (check_access(u, ci, CA_ACCESS_CHANGE)) + else if (ci->HasPriv(u, CA_ACCESS_CHANGE)) has_access = true; else if (is_del) { @@ -424,18 +560,6 @@ class CommandCSAccess : public Command this->OnSyntaxError(source, cmd); else if (!has_access) source.Reply(ACCESS_DENIED); - /* We still allow LIST and CLEAR in xOP mode, but not others */ - else if (ci->HasFlag(CI_XOP) && !is_list && !is_clear) - { - if (ModeManager::FindChannelModeByName(CMODE_HALFOP)) - source.Reply(_("You can't use this command. \n" - "Use the AOP, SOP, HOP and VOP commands instead.\n" - "Type \002%s%s HELP \037command\037\002 for more information."), Config->UseStrictPrivMsgString.c_str(), source.owner->nick.c_str()); - else - source.Reply(_("You can't use this command. \n" - "Use the AOP, SOP and VOP commands instead.\n" - "Type \002%s%s HELP \037command\037\002 for more information."), Config->UseStrictPrivMsgString.c_str(), source.owner->nick.c_str()); - } else if (readonly && !is_list) source.Reply(_("Sorry, channel access list modification is temporarily disabled.")); else if (cmd.equals_ci("ADD")) @@ -520,6 +644,8 @@ class CommandCSAccess : public Command class CommandCSLevels : public Command { + int levelinfo_maxwidth; + void DoSet(CommandSource &source, ChannelInfo *ci, const std::vector ¶ms) { User *u = source.u; @@ -533,7 +659,6 @@ class CommandCSLevels : public Command level = ACCESS_FOUNDER; else { - level = 1; try { level = convertTo(lev); @@ -549,20 +674,22 @@ class CommandCSLevels : public Command source.Reply(_("Level must be between %d and %d inclusive."), ACCESS_INVALID + 1, ACCESS_FOUNDER - 1); else { - for (int i = 0; levelinfo[i].what >= 0; ++i) + for (int i = 0; defaultLevels[i].priv != CA_SIZE; ++i) { - if (what.equals_ci(levelinfo[i].name)) + AccessLevels &l = defaultLevels[i]; + + if (what.equals_ci(l.name)) { - ci->levels[levelinfo[i].what] = level; + ci->levels[l.priv] = level; FOREACH_MOD(I_OnLevelChange, OnLevelChange(u, ci, i, level)); - bool override = !check_access(u, ci, CA_FOUNDER); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "SET " << levelinfo[i].name << " to " << level; + bool override = !ci->HasPriv(u, CA_FOUNDER); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "SET " << l.name << " to " << level; if (level == ACCESS_FOUNDER) - source.Reply(_("Level for %s on channel %s changed to founder only."), levelinfo[i].name.c_str(), ci->name.c_str()); + source.Reply(_("Level for %s on channel %s changed to founder only."), l.name.c_str(), ci->name.c_str()); else - source.Reply(_("Level for \002%s\002 on channel %s changed to \002%d\002."), levelinfo[i].name.c_str(), ci->name.c_str(), level); + source.Reply(_("Level for \002%s\002 on channel %s changed to \002%d\002."), l.name.c_str(), ci->name.c_str(), level); return; } } @@ -581,17 +708,19 @@ class CommandCSLevels : public Command /* Don't allow disabling of the founder level. It would be hard to change it back if you dont have access to use this command */ if (!what.equals_ci("FOUNDER")) - for (int i = 0; levelinfo[i].what >= 0; ++i) + for (int i = 0; defaultLevels[i].priv != CA_SIZE; ++i) { - if (what.equals_ci(levelinfo[i].name)) + AccessLevels &l = defaultLevels[i]; + + if (what.equals_ci(l.name)) { - ci->levels[levelinfo[i].what] = ACCESS_INVALID; - FOREACH_MOD(I_OnLevelChange, OnLevelChange(u, ci, i, levelinfo[i].what)); + ci->levels[l.priv] = ACCESS_INVALID; + FOREACH_MOD(I_OnLevelChange, OnLevelChange(u, ci, i, l.priv)); - bool override = !check_access(u, ci, CA_FOUNDER); - Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "DISABLE " << levelinfo[i].name; + bool override = !ci->HasPriv(u, CA_FOUNDER); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "DISABLE " << l.name; - source.Reply(_("\002%s\002 disabled on channel %s."), levelinfo[i].name.c_str(), ci->name.c_str()); + source.Reply(_("\002%s\002 disabled on channel %s."), l.name.c_str(), ci->name.c_str()); return; } } @@ -606,27 +735,26 @@ class CommandCSLevels : public Command source.Reply(_("Access level settings for channel %s:"), ci->name.c_str()); if (!levelinfo_maxwidth) - for (int i = 0; levelinfo[i].what >= 0; ++i) + for (int i = 0; defaultLevels[i].priv != CA_SIZE; ++i) { - int len = levelinfo[i].name.length(); + AccessLevels &l = defaultLevels[i]; + + int len = l.name.length(); if (len > levelinfo_maxwidth) levelinfo_maxwidth = len; } - for (int i = 0; levelinfo[i].what >= 0; ++i) + for (int i = 0; defaultLevels[i].priv != CA_SIZE; ++i) { - int j = ci->levels[levelinfo[i].what]; + AccessLevels &l = defaultLevels[i]; + int j = ci->levels[l.priv]; if (j == ACCESS_INVALID) - { - j = levelinfo[i].what; - - source.Reply(_(" %-*s (disabled)"), levelinfo_maxwidth, levelinfo[i].name.c_str()); - } + source.Reply(_(" %-*s (disabled)"), levelinfo_maxwidth, l.name.c_str()); else if (j == ACCESS_FOUNDER) - source.Reply(_(" %-*s (founder only)"), levelinfo_maxwidth, levelinfo[i].name.c_str()); + source.Reply(_(" %-*s (founder only)"), levelinfo_maxwidth, l.name.c_str()); else - source.Reply(_(" %-*s %d"), levelinfo_maxwidth, levelinfo[i].name.c_str(), j); + source.Reply(_(" %-*s %d"), levelinfo_maxwidth, l.name.c_str(), j); } return; @@ -639,7 +767,7 @@ class CommandCSLevels : public Command reset_levels(ci); FOREACH_MOD(I_OnLevelChange, OnLevelChange(u, ci, -1, 0)); - bool override = !check_access(u, ci, CA_FOUNDER); + bool override = !ci->HasPriv(u, CA_FOUNDER); Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "RESET"; source.Reply(_("Access levels for \002%s\002 reset to defaults."), ci->name.c_str()); @@ -647,7 +775,7 @@ class CommandCSLevels : public Command } public: - CommandCSLevels(Module *creator) : Command(creator, "chanserv/levels", 2, 4) + CommandCSLevels(Module *creator) : Command(creator, "chanserv/levels", 2, 4), levelinfo_maxwidth(0) { this->SetDesc(_("Redefine the meanings of access levels")); this->SetSyntax(_("\037channel\037 SET \037type\037 \037level\037")); @@ -676,9 +804,7 @@ class CommandCSLevels : public Command */ if (cmd.equals_ci("SET") ? s.empty() : (cmd.substr(0, 3).equals_ci("DIS") ? (what.empty() || !s.empty()) : !what.empty())) this->OnSyntaxError(source, cmd); - else if (ci->HasFlag(CI_XOP)) - source.Reply(_("Levels are not available as xOP is enabled on this channel.")); - else if (!check_access(u, ci, CA_FOUNDER) && !u->HasPriv("chanserv/access/modify")) + else if (!ci->HasPriv(u, CA_FOUNDER) && !u->HasPriv("chanserv/access/modify")) source.Reply(ACCESS_DENIED); else if (cmd.equals_ci("SET")) this->DoSet(source, ci, params); @@ -698,19 +824,21 @@ class CommandCSLevels : public Command { if (subcommand.equals_ci("DESC")) { - int i; - source.Reply(_("The following feature/function names are understood. Note\n" - "that the leves for NOJOIN is the maximum level,\n" - "while all others are minimum levels.")); + source.Reply(_("The following feature/function names are understood.")); if (!levelinfo_maxwidth) - for (i = 0; levelinfo[i].what >= 0; ++i) + for (int i = 0; defaultLevels[i].priv != CA_SIZE; ++i) { - int len = levelinfo[i].name.length(); + AccessLevels &l = defaultLevels[i]; + + int len = l.name.length(); if (len > levelinfo_maxwidth) levelinfo_maxwidth = len; } - for (i = 0; levelinfo[i].what >= 0; ++i) - source.Reply(_(" %-*s %s"), levelinfo_maxwidth, levelinfo[i].name.c_str(), translate(source.u, levelinfo[i].desc)); + for (int i = 0; defaultLevels[i].priv != CA_SIZE; ++i) + { + AccessLevels &l = defaultLevels[i]; + source.Reply(_(" %-*s %s"), levelinfo_maxwidth, l.name.c_str(), translate(source.u, l.desc.c_str())); + } } else { @@ -742,17 +870,45 @@ class CommandCSLevels : public Command class CSAccess : public Module { + AccessAccessProvider accessprovider; CommandCSAccess commandcsaccess; CommandCSLevels commandcslevels; public: CSAccess(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), - commandcsaccess(this), commandcslevels(this) + accessprovider(this), commandcsaccess(this), commandcslevels(this) { this->SetAuthor("Anope"); + ModuleManager::RegisterService(&accessprovider); ModuleManager::RegisterService(&commandcsaccess); ModuleManager::RegisterService(&commandcslevels); + + Implementation i[] = { I_OnReload, I_OnChanRegistered }; + ModuleManager::Attach(i, this, 2); + + this->OnReload(); + } + + void OnReload() + { + ConfigReader config; + + for (int i = 0; defaultLevels[i].priv != CA_SIZE; ++i) + { + AccessLevels &l = defaultLevels[i]; + + const Anope::string &value = config.ReadValue("chanserv", l.config_name, "", 0); + if (value.equals_ci("founder")) + l.default_level = ACCESS_FOUNDER; + else + l.default_level = config.ReadInteger("chanserv", l.config_name, 0, false); + } + } + + void OnChanRegistered(ChannelInfo *ci) + { + reset_levels(ci); } }; diff --git a/modules/core/cs_akick.cpp b/modules/core/cs_akick.cpp index 2a2ca8fa2..437bf5572 100644 --- a/modules/core/cs_akick.cpp +++ b/modules/core/cs_akick.cpp @@ -134,7 +134,7 @@ class AkickDelCallback : public NumberList ~AkickDelCallback() { User *u = source.u; - bool override = !check_access(u, ci, CA_AKICK); + bool override = !ci->HasPriv(u, CA_AKICK); Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, c, ci) << "DEL on " << Deleted << " users"; if (!Deleted) @@ -195,9 +195,8 @@ class CommandCSAKick : public Command * or whether the mask matches a user with higher/equal access - Viper */ if (ci->HasFlag(CI_PEACE) && nc) { - ChanAccess *nc_access = ci->GetAccess(nc), *u_access = ci->GetAccess(u); - int16 nc_level = nc_access ? nc_access->level : 0, u_level = u_access ? u_access->level : 0; - if (nc == ci->GetFounder() || nc_level >= u_level) + AccessGroup nc_access = ci->AccessFor(nc), u_access = ci->AccessFor(u); + if (nc == ci->GetFounder() || nc_access >= u_access) { source.Reply(ACCESS_DENIED); return; @@ -211,11 +210,10 @@ class CommandCSAKick : public Command { User *u2 = it->second; - ChanAccess *u2_access = ci->GetAccess(nc), *u_access = ci->GetAccess(u); - int16 u2_level = u2_access ? u2_access->level : 0, u_level = u_access ? u_access->level : 0; + AccessGroup nc_access = ci->AccessFor(nc), u_access = ci->AccessFor(u); Entry entry_mask(CMODE_BEGIN, mask); - if ((check_access(u2, ci, CA_FOUNDER) || u2_level >= u_level) && entry_mask.Matches(u2)) + if ((ci->HasPriv(u2, CA_FOUNDER) || nc_access >= u_access) && entry_mask.Matches(u2)) { source.Reply(ACCESS_DENIED); return; @@ -228,9 +226,8 @@ class CommandCSAKick : public Command { na = it->second; - ChanAccess *na_access = ci->GetAccess(na->nc), *u_access = ci->GetAccess(u); - int16 na_level = na_access ? na_access->level : 0, u_level = u_access ? u_access->level : 0; - if (na->nc && (na->nc == ci->GetFounder() || na_level >= u_level)) + AccessGroup nc_access = ci->AccessFor(na->nc), u_access = ci->AccessFor(u); + if (na->nc && (na->nc == ci->GetFounder() || nc_access >= u_access)) { Anope::string buf = na->nick + "!" + na->last_usermask; if (Anope::Match(buf, mask)) @@ -263,7 +260,7 @@ class CommandCSAKick : public Command else akick = ci->AddAkick(u->nick, mask, reason); - bool override = !check_access(u, ci, CA_AKICK); + bool override = !ci->HasPriv(u, CA_AKICK); Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "ADD " << mask << ": " << reason; FOREACH_MOD(I_OnAkickAdd, OnAkickAdd(u, ci, akick)); @@ -312,7 +309,7 @@ class CommandCSAKick : public Command return; } - bool override = !check_access(u, ci, CA_AKICK); + bool override = !ci->HasPriv(u, CA_AKICK); Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "DEL " << mask; ci->EraseAkick(i); @@ -327,7 +324,7 @@ class CommandCSAKick : public Command const Anope::string &mask = params.size() > 2 ? params[2] : ""; - bool override = !check_access(u, ci, CA_AKICK); + bool override = !ci->HasPriv(u, CA_AKICK); Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "LIST"; if (!ci->GetAkickCount()) @@ -377,7 +374,7 @@ class CommandCSAKick : public Command const Anope::string &mask = params.size() > 2 ? params[2] : ""; - bool override = !check_access(u, ci, CA_AKICK); + bool override = !ci->HasPriv(u, CA_AKICK); Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "VIEW"; if (!ci->GetAkickCount()) @@ -441,7 +438,7 @@ class CommandCSAKick : public Command ++count; } - bool override = !check_access(u, ci, CA_AKICK); + bool override = !ci->HasPriv(u, CA_AKICK); Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "ENFORCE, affects " << count << " users"; source.Reply(_("AKICK ENFORCE for \002%s\002 complete; \002%d\002 users were affected."), ci->name.c_str(), count); @@ -450,7 +447,7 @@ class CommandCSAKick : public Command void DoClear(CommandSource &source, ChannelInfo *ci) { User *u = source.u; - bool override = !check_access(u, ci, CA_AKICK); + bool override = !ci->HasPriv(u, CA_AKICK); Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "CLEAR"; ci->ClearAkick(); @@ -486,7 +483,7 @@ class CommandCSAKick : public Command if (mask.empty() && (cmd.equals_ci("ADD") || cmd.equals_ci("DEL"))) this->OnSyntaxError(source, cmd); - else if (!check_access(u, ci, CA_AKICK) && !u->HasPriv("chanserv/access/modify")) + else if (!ci->HasPriv(u, CA_AKICK) && !u->HasPriv("chanserv/access/modify")) source.Reply(ACCESS_DENIED); else if (!cmd.equals_ci("LIST") && !cmd.equals_ci("VIEW") && !cmd.equals_ci("ENFORCE") && readonly) source.Reply(_("Sorry, channel autokick list modification is temporarily disabled.")); diff --git a/modules/core/cs_ban.cpp b/modules/core/cs_ban.cpp index 599bab969..759649662 100644 --- a/modules/core/cs_ban.cpp +++ b/modules/core/cs_ban.cpp @@ -40,16 +40,15 @@ class CommandCSBan : public Command bool is_same = target.equals_ci(u->nick); User *u2 = is_same ? u : finduser(target); - ChanAccess *u_access = ci->GetAccess(u), *u2_access = ci->GetAccess(u2); - uint16 u_level = u_access ? u_access->level : 0, u2_level = u2_access ? u2_access->level : 0; + AccessGroup u_access = ci->AccessFor(u), u2_access = ci->AccessFor(u2); if (!c) source.Reply(CHAN_X_NOT_IN_USE, chan.c_str()); else if (!u2) source.Reply(NICK_X_NOT_IN_USE, target.c_str()); - else if (!is_same ? !check_access(u, ci, CA_BAN) : !check_access(u, ci, CA_BANME)) + else if (!ci->HasPriv(u, CA_BAN)) source.Reply(ACCESS_DENIED); - else if (!is_same && ci->HasFlag(CI_PEACE) && u2_level >= u_level) + else if (!is_same && ci->HasFlag(CI_PEACE) && u2_access >= u_access) source.Reply(ACCESS_DENIED); /* * Dont ban/kick the user on channels where he is excepted @@ -73,7 +72,7 @@ class CommandCSBan : public Command if (!c->FindUser(u2)) return; - if (ci->HasFlag(CI_SIGNKICK) || (ci->HasFlag(CI_SIGNKICK_LEVEL) && !check_access(u, ci, CA_SIGNKICK))) + if (ci->HasFlag(CI_SIGNKICK) || (ci->HasFlag(CI_SIGNKICK_LEVEL) && !ci->HasPriv(u, CA_SIGNKICK))) c->Kick(ci->WhoSends(), u2, "%s (%s)", reason.c_str(), u->nick.c_str()); else c->Kick(ci->WhoSends(), u2, "%s", reason.c_str()); diff --git a/modules/core/cs_clearusers.cpp b/modules/core/cs_clearusers.cpp index 52fb3b990..18dc75479 100644 --- a/modules/core/cs_clearusers.cpp +++ b/modules/core/cs_clearusers.cpp @@ -34,8 +34,11 @@ class CommandCSClearUsers : public Command source.Reply(CHAN_X_NOT_IN_USE, chan.c_str()); else if (!c->ci) source.Reply(CHAN_X_NOT_REGISTERED, c->name.c_str()); - else if (!check_access(u, c->ci, CA_FOUNDER)) + else if (!c->ci->HasPriv(u, CA_FOUNDER)) + { source.Reply(ACCESS_DENIED); + return; + } Anope::string buf = "CLEARUSERS command from " + u->nick + " (" + u->Account()->display + ")"; diff --git a/modules/core/cs_clone.cpp b/modules/core/cs_clone.cpp index 9ce877825..b7f8763f7 100644 --- a/modules/core/cs_clone.cpp +++ b/modules/core/cs_clone.cpp @@ -36,7 +36,7 @@ public: return; } - if (!check_access(u, ci, CA_SET)) + if (!ci->HasPriv(u, CA_SET)) { source.Reply(ACCESS_DENIED); return; @@ -111,7 +111,7 @@ public: } else if (what.equals_ci("ACCESS")) { - target_ci->ClearAccess(); + /*target_ci->ClearAccess(); for (unsigned i = 0; i < ci->GetAccessCount(); ++i) { ChanAccess *access = ci->GetAccess(i); @@ -119,6 +119,8 @@ public: } source.Reply(_("All access entries from \002%s\002 have been transferred to \002%s\002"), channel.c_str(), target.c_str()); + XXX + */ } else if (what.equals_ci("AKICK")) { diff --git a/modules/core/cs_drop.cpp b/modules/core/cs_drop.cpp index d206e35d2..eb01875db 100644 --- a/modules/core/cs_drop.cpp +++ b/modules/core/cs_drop.cpp @@ -49,7 +49,7 @@ class CommandCSDrop : public Command return; } - if ((ci->HasFlag(CI_SECUREFOUNDER) ? !IsFounder(u, ci) : !check_access(u, ci, CA_FOUNDER)) && !u->HasCommand("chanserv/chanserv/drop")) + if ((ci->HasFlag(CI_SECUREFOUNDER) ? !IsFounder(u, ci) : !ci->HasPriv(u, CA_FOUNDER)) && !u->HasCommand("chanserv/chanserv/drop")) { source.Reply(ACCESS_DENIED); return; @@ -58,7 +58,7 @@ class CommandCSDrop : public Command if (ci->c && ModeManager::FindChannelModeByName(CMODE_REGISTERED)) ci->c->RemoveMode(NULL, CMODE_REGISTERED, "", false); - bool override = (ci->HasFlag(CI_SECUREFOUNDER) ? !IsFounder(u, ci) : !check_access(u, ci, CA_FOUNDER)); + bool override = (ci->HasFlag(CI_SECUREFOUNDER) ? !IsFounder(u, ci) : !ci->HasPriv(u, CA_FOUNDER)); Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "founder: " << (ci->GetFounder() ? ci->GetFounder()->display : "none"); delete ci; diff --git a/modules/core/cs_flags.cpp b/modules/core/cs_flags.cpp new file mode 100644 index 000000000..48f1a22f1 --- /dev/null +++ b/modules/core/cs_flags.cpp @@ -0,0 +1,455 @@ +/* ChanServ core functions + * + * (C) 2003-2011 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + */ + +/*************************************************************************/ + +#include "module.h" + +static struct FlagLevels +{ + ChannelAccess priv; + char default_char; + Anope::string config_name; + Anope::string desc; +} flagLevels[] = { + { CA_ACCESS_CHANGE, 'f', "flag_change", _("Allowed to modify the access list") }, + { CA_ACCESS_LIST, 'l', "flag_list", _("Allowed to view the access list") }, + { CA_AKICK, 'K', "flag_akick", _("Allowed to use AKICK command") }, + { CA_ASSIGN, 's', "flag_assign", _("Allowed to assign/unassign a bot") }, + { CA_AUTOHALFOP, 'H', "flag_autohalfop", _("Automatic mode +h") }, + { CA_AUTOOP, 'O', "flag_autoop", _("Automatic channel operator status") }, + { CA_AUTOOWNER, 'Q', "flag_autoowner", _("Automatic mode +q") }, + { CA_AUTOPROTECT, 'A', "flag_autoprotect", _("Automatic mode +a") }, + { CA_AUTOVOICE, 'V', "flag_autovoice", _("Automatic mode +v") }, + { CA_BADWORDS, 'k', "flag_badwords", _("Allowed to modify channel badwords list") }, + { CA_BAN, 'b', "flag_ban", _("Allowed to use ban users") }, + { CA_FANTASIA, 'c', "flag_fantasia", _("Allowed to use fantasy commands") }, + { CA_FOUNDER, 'F', "flag_founder", _("Allowed to issue commands restricted to channel founders") }, + { CA_GETKEY, 'G', "flag_getkey", _("Allowed to use GETKEY command") }, + { CA_GREET, 'g', "flag_greet", _("Greet message displayed") }, + { CA_HALFOP, 'h', "flag_halfop", _("Allowed to (de)halfop users") }, + { CA_HALFOPME, 'h', "flag_halfopme", _("Allowed to (de)halfop him/herself") }, + { CA_INFO, 'I', "flag_info", _("Allowed to use INFO command with ALL option") }, + { CA_INVITE, 'i', "flag_invite", _("Allowed to use the INVITE command") }, + { CA_KICK, 'k', "flag_kick", _("Allowed to use the KICK command") }, + { CA_MEMO, 'm', "flag_memo", _("Allowed to read channel memos") }, + { CA_MODE, 's', "flag_mode", _("Allowed to change channel modes") }, + { CA_NOKICK, 'N', "flag_nokick", _("Prevents users being kicked by Services") }, + { CA_OPDEOP, 'o', "flag_opdeop", _("Allowed to (de)op users") }, + { CA_OPDEOPME, 'o', "flag_opdeopme", _("Allowed to (de)op him/herself") }, + { CA_OWNER, 'q', "flag_owner", _("Allowed to use (de)owner users") }, + { CA_OWNERME, 'q', "flag_ownerme", _("Allowed to (de)owner him/herself") }, + { CA_PROTECT, 'a', "flag_protect", _("Allowed to (de)protect users") }, + { CA_PROTECTME, 'a', "flag_protectme", _("Allowed to (de)protect him/herself"), }, + { CA_SAY, 'B', "flag_say", _("Allowed to use SAY and ACT commands") }, + { CA_SET, 's', "flag_set", _("Allowed to set channel settings") }, + { CA_SIGNKICK, 'K', "flag_signkick", _("Prevents kicks from being signed") }, + { CA_TOPIC, 't', "flag_topic", _("Allowed to change channel topics") }, + { CA_UNBAN, 'u', "flag_unban", _("Allowed to unban users") }, + { CA_VOICE, 'v', "flag_voice", _("Allowed to (de)voice users") }, + { CA_VOICEME, 'v', "flag_voiceme", _("Allowed to (de)voice him/herself") }, + { CA_SIZE, -1, "", "" } +}; + +class FlagsChanAccess : public ChanAccess +{ + public: + std::set flags; + + FlagsChanAccess(AccessProvider *p) : ChanAccess(p) + { + } + + bool Matches(User *u, NickCore *nc) + { + if (u && (Anope::Match(u->nick, this->mask) || Anope::Match(u->GetMask(), this->mask))) + return true; + else if (nc && Anope::Match(nc->display, this->mask)) + return true; + return false; + } + + bool HasPriv(ChannelAccess priv) + { + for (int i = 0; flagLevels[i].priv != CA_SIZE; ++i) + if (flagLevels[i].priv == priv) + return this->flags.count(flagLevels[i].default_char); + + return false; + } + + Anope::string Serialize() + { + return Anope::string(this->flags.begin(), this->flags.end()); + } + + void Unserialize(const Anope::string &data) + { + for (unsigned i = data.length(); i > 0; --i) + this->flags.insert(data[i - 1]); + } + + static Anope::string DetermineFlags(ChanAccess *access) + { + if (access->provider->name == "access/flags") + return access->Serialize(); + + std::set buffer; + + for (int i = 0; flagLevels[i].priv != CA_SIZE; ++i) + { + FlagLevels &l = flagLevels[i]; + + if (access->HasPriv(l.priv)) + buffer.insert(l.default_char); + } + + return Anope::string(buffer.begin(), buffer.end()); + } +}; + +class FlagsAccessProvider : public AccessProvider +{ + public: + FlagsAccessProvider(Module *o) : AccessProvider(o, "access/flags") + { + } + + ChanAccess *Create() + { + return new FlagsChanAccess(this); + } +}; + +class CommandCSFlags : public Command +{ + void DoModify(CommandSource &source, ChannelInfo *ci, const std::vector ¶ms) + { + User *u = source.u; + + Anope::string mask = params.size() > 2 ? params[2] : ""; + Anope::string flags = params.size() > 3 ? params[3] : ""; + + if (flags.empty()) + { + this->OnSyntaxError(source, ""); + return; + } + + AccessGroup u_access = ci->AccessFor(u); + + if (mask.find_first_of("!*@") == Anope::string::npos && findnick(mask) == NULL) + mask += "!*@*"; + + ChanAccess *current = NULL; + std::set current_flags; + for (unsigned i = ci->GetAccessCount(); i > 0; --i) + { + ChanAccess *access = ci->GetAccess(i - 1); + if (mask.equals_ci(access->mask)) + { + current = access; + Anope::string cur_flags = FlagsChanAccess::DetermineFlags(access); + for (unsigned j = cur_flags.length(); j > 0; --j) + current_flags.insert(cur_flags[j - 1]); + break; + } + } + + if (ci->GetAccessCount() >= Config->CSAccessMax) + { + source.Reply(_("Sorry, you can only have %d access entries on a channel."), Config->CSAccessMax); + return; + } + + bool override = false; + int add = -1; + for (size_t i = 0; i < flags.length(); ++i) + { + char f = flags[i]; + switch (f) + { + case '+': + add = 1; + break; + case '-': + add = 0; + break; + case '*': + if (add == -1) + break; + for (int j = 0; flagLevels[j].priv != CA_SIZE; ++j) + { + FlagLevels &l = flagLevels[j]; + if (!u_access.HasPriv(l.priv)) + { + if (u->HasPriv("chanserv/access/modify")) + override = true; + else + continue; + } + if (add == 1) + current_flags.insert(f); + else if (add == 0) + current_flags.erase(f); + } + break; + default: + if (add == -1) + break; + for (int j = 0; flagLevels[j].priv != CA_SIZE; ++j) + { + FlagLevels &l = flagLevels[j]; + if (f != l.default_char) + continue; + else if (!u_access.HasPriv(l.priv)) + { + if (u->HasPriv("chanserv/access/modify")) + override = true; + else + { + source.Reply(_("You can not set the \002%c\002 flag."), f); + continue; + } + } + if (add == 1) + current_flags.insert(f); + else if (add == 0) + current_flags.erase(f); + break; + } + } + } + if (current_flags.empty()) + { + if (current != NULL) + { + FOREACH_MOD(I_OnAccessDel, OnAccessDel(ci, u, current)); + ci->EraseAccess(current); + source.Reply(_("\002%s\002 removed from the %s access list."), mask.c_str(), ci->name.c_str()); + } + else + { + source.Reply(_("Insufficient flags given")); + } + return; + } + + service_reference provider("access/flags"); + if (!provider) + return; + FlagsChanAccess *access = debug_cast(provider->Create()); + access->ci = ci; + access->mask = mask; + access->creator = u->nick; + access->last_seen = current ? current->last_seen : 0; + access->created = Anope::CurTime; + access->flags = current_flags; + + if (current != NULL) + ci->EraseAccess(current); + + ci->AddAccess(access); + + FOREACH_MOD(I_OnAccessAdd, OnAccessAdd(ci, u, access)); + + Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "MODIFY " << mask << " with flags " << access->Serialize(); + source.Reply(_("Access for \002%s\002 on %s set to +\002%s\002"), access->mask.c_str(), ci->name.c_str(), access->Serialize().c_str()); + + return; + } + + void DoList(CommandSource &source, ChannelInfo *ci, const std::vector ¶ms) + { + const Anope::string &arg = params.size() > 2 ? params[2] : ""; + + if (!ci->GetAccessCount()) + source.Reply(_("%s access list is empty."), ci->name.c_str()); + else + { + unsigned total = 0; + + for (unsigned i = 0, end = ci->GetAccessCount(); i < end; ++i) + { + ChanAccess *access = ci->GetAccess(i); + const Anope::string &flags = FlagsChanAccess::DetermineFlags(access); + + if (!arg.empty()) + { + if (arg[0] == '+') + { + bool pass = true; + for (size_t j = 1; j < arg.length(); ++j) + if (flags.find(arg[j]) == Anope::string::npos) + pass = false; + if (pass == false) + continue; + } + else if (!Anope::Match(access->mask, arg)) + continue; + } + + if (++total == 1) + { + source.Reply(_("Flags list for %s"), ci->name.c_str()); + } + + source.Reply(_(" %3d %-10s +%-10s [last modified on %s by %s]"), i + 1, access->mask.c_str(), FlagsChanAccess::DetermineFlags(access).c_str(), do_strftime(access->created, source.u->Account(), true).c_str(), access->creator.c_str()); + } + + if (total == 0) + source.Reply(_("No matching entries on %s access list."), ci->name.c_str()); + else if (total == ci->GetAccessCount()) + source.Reply(_("End of access list.")); + else + source.Reply(_("End of access list - %d/%d entries shown."), total, ci->GetAccessCount()); + } + } + + void DoClear(CommandSource &source, ChannelInfo *ci) + { + User *u = source.u; + + if (!IsFounder(u, ci) && !u->HasPriv("chanserv/access/modify")) + source.Reply(ACCESS_DENIED); + else + { + ci->ClearAccess(); + + FOREACH_MOD(I_OnAccessClear, OnAccessClear(ci, u)); + + source.Reply(_("Channel %s access list has been cleared."), ci->name.c_str()); + + bool override = !IsFounder(u, ci); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci) << "CLEAR"; + } + + return; + } + + public: + CommandCSFlags(Module *creator) : Command(creator, "chanserv/flags", 2, 4) + { + this->SetDesc(_("Modify the list of privileged users")); + this->SetSyntax(_("\037channel\037 MODIFY \037mask\037 \037changes\037")); + this->SetSyntax(_("\037channel\037 LIST [\037mask\037 | +\037flags\037]")); + this->SetSyntax(_("\037channel\037 CLEAR\002")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) + { + const Anope::string &chan = params[0]; + const Anope::string &cmd = params[1]; + + User *u = source.u; + ChannelInfo *ci = cs_findchan(chan); + if (ci == NULL) + { + source.Reply(CHAN_X_NOT_REGISTERED, chan.c_str()); + return; + } + + bool is_list = cmd.equals_ci("LIST"); + bool has_access = false; + if (u->HasPriv("chanserv/access/modify")) + has_access = true; + else if (is_list && ci->HasPriv(u, CA_ACCESS_LIST)) + has_access = true; + else if (ci->HasPriv(u, CA_ACCESS_CHANGE)) + has_access = true; + + if (!has_access) + source.Reply(ACCESS_DENIED); + else if (readonly && !is_list) + source.Reply(_("Sorry, channel access list modification is temporarily disabled.")); + else if (cmd.equals_ci("MODIFY")) + this->DoModify(source, ci, params); + else if (cmd.equals_ci("LIST")) + this->DoList(source, ci, params); + else if (cmd.equals_ci("CLEAR")) + this->DoClear(source, ci); + else + this->OnSyntaxError(source, cmd); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("%s is another way to modify the channel access list, similar to\n" + "the XOP and ACCESS methods."), source.command.c_str()); + source.Reply(" "); + source.Reply(_("The MODIFY command allows you to modify the access list. If mask is\n" + "not already on the access list is it added, then the changes are applied.\n" + "If the mask has no more flags, then the mask is removed from the access list.\n" + "Additionally, you may use +* or -* to add or remove all flags, respectively. You are\n" + "only able to modify the access list if you have the proper permission on the channel,\n" + "and even then you can only give other people access to up what you already have.")); + source.Reply(" "); + source.Reply(_("The LIST command allows you to list existing entries on the channel access list.\n" + "If a mask is given, the mask is wildcard matched against all existing entries on the\n" + "access list, and only those entries are returned. If a set of flags is given, only those\n" + "on the access list with the specified flags are returned.")); + source.Reply(" "); + source.Reply(_("The CLEAR command clears the channel access list, which requires channel founder.")); + source.Reply(" "); + source.Reply(_("The available flags are:")); + + std::multimap > levels; + for (int i = 0; flagLevels[i].priv != CA_SIZE; ++i) + levels.insert(std::make_pair(flagLevels[i].default_char, &flagLevels[i])); + for (std::multimap >::iterator it = levels.begin(), it_end = levels.end(); it != it_end; ++it) + { + FlagLevels *l = it->second; + source.Reply(" %c - %s", l->default_char, translate(source.u->Account(), l->desc.c_str())); + } + + return true; + } +}; + +class CSFlags : public Module +{ + FlagsAccessProvider accessprovider; + CommandCSFlags commandcsflags; + + public: + CSFlags(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), + accessprovider(this), commandcsflags(this) + { + this->SetAuthor("Anope"); + + ModuleManager::RegisterService(&accessprovider); + ModuleManager::RegisterService(&commandcsflags); + + Implementation i[] = { I_OnReload }; + ModuleManager::Attach(i, this, 1); + + this->OnReload(); + } + + void OnReload() + { + ConfigReader config; + + for (int i = 0; flagLevels[i].priv != CA_SIZE; ++i) + { + FlagLevels &l = flagLevels[i]; + + const Anope::string &value = config.ReadValue("chanserv", l.config_name, "", 0); + if (value.empty()) + continue; + l.default_char = value[0]; + } + } +}; + +MODULE_INIT(CSFlags) diff --git a/modules/core/cs_getkey.cpp b/modules/core/cs_getkey.cpp index 289110f14..4a6d62442 100644 --- a/modules/core/cs_getkey.cpp +++ b/modules/core/cs_getkey.cpp @@ -35,7 +35,7 @@ class CommandCSGetKey : public Command } - if (!check_access(u, ci, CA_GETKEY) && !u->HasCommand("chanserv/chanserv/getkey")) + if (!ci->HasPriv(u, CA_GETKEY) && !u->HasCommand("chanserv/chanserv/getkey")) { source.Reply(ACCESS_DENIED); return; @@ -48,7 +48,7 @@ class CommandCSGetKey : public Command return; } - bool override = !check_access(u, ci, CA_GETKEY); + bool override = !ci->HasPriv(u, CA_GETKEY); Log(override ? LOG_OVERRIDE : LOG_COMMAND, u, this, ci); source.Reply(_("Key for channel \002%s\002 is \002%s\002."), chan.c_str(), key.c_str()); diff --git a/modules/core/cs_info.cpp b/modules/core/cs_info.cpp index 23303224f..ef026563e 100644 --- a/modules/core/cs_info.cpp +++ b/modules/core/cs_info.cpp @@ -50,7 +50,7 @@ class CommandCSInfo : public Command bool show_all = false; /* Should we show all fields? Only for sadmins and identified users */ - if (has_auspex || check_access(u, ci, CA_INFO)) + if (has_auspex || ci->HasPriv(u, CA_INFO)) show_all = true; source.Reply(CHAN_INFO_HEADER, chan.c_str()); @@ -90,7 +90,6 @@ class CommandCSInfo : public Command else CheckOptStr(optbuf, CI_SIGNKICK_LEVEL, _("Signed kicks"), ci, u->Account()); CheckOptStr(optbuf, CI_TOPICLOCK, _("Topic Lock"), ci, u->Account()); - CheckOptStr(optbuf, CI_XOP, _("xOP lists system"), ci, u->Account()); CheckOptStr(optbuf, CI_PERSIST, _("Persistant"), ci, u->Account()); CheckOptStr(optbuf, CI_NO_EXPIRE, _("No expire"), ci, u->Account()); diff --git a/modules/core/cs_invite.cpp b/modules/core/cs_invite.cpp index f3305a01d..27b4b03a3 100644 --- a/modules/core/cs_invite.cpp +++ b/modules/core/cs_invite.cpp @@ -42,7 +42,7 @@ class CommandCSInvite : public Command return; } - if (!check_access(u, ci, CA_INVITE)) + if (!ci->HasPriv(u, CA_INVITE)) { source.Reply(ACCESS_DENIED); return; diff --git a/modules/core/cs_kick.cpp b/modules/core/cs_kick.cpp index bead90487..b98d0953d 100644 --- a/modules/core/cs_kick.cpp +++ b/modules/core/cs_kick.cpp @@ -34,8 +34,7 @@ class CommandCSKick : public Command bool is_same = target.equals_ci(u->nick); User *u2 = is_same ? u : finduser(target); - ChanAccess *u_access = ci->GetAccess(u), *u2_access = ci->GetAccess(u2); - uint16 u_level = u_access ? u_access->level : 0, u2_level = u2_access ? u2_access->level : 0; + AccessGroup u_access = ci->AccessFor(u), u2_access = ci->AccessFor(u2); if (!c) source.Reply(CHAN_X_NOT_IN_USE, chan.c_str()); @@ -43,9 +42,9 @@ class CommandCSKick : public Command source.Reply(CHAN_X_NOT_REGISTERED, chan.c_str()); else if (!u2) source.Reply(NICK_X_NOT_IN_USE, target.c_str()); - else if (!is_same ? !check_access(u, ci, CA_KICK) : !check_access(u, ci, CA_KICKME)) + else if (!ci->HasPriv(u, CA_KICK)) source.Reply(ACCESS_DENIED); - else if (!is_same && (ci->HasFlag(CI_PEACE)) && u2_level >= u_level) + else if (!is_same && (ci->HasFlag(CI_PEACE)) && u2_access >= u_access) source.Reply(ACCESS_DENIED); else if (u2->IsProtected()) source.Reply(ACCESS_DENIED); @@ -56,7 +55,7 @@ class CommandCSKick : public Command // XXX Log(LOG_COMMAND, u, this, ci) << "for " << u2->nick; - if (ci->HasFlag(CI_SIGNKICK) || (ci->HasFlag(CI_SIGNKICK_LEVEL) && !check_access(u, ci, CA_SIGNKICK))) + if (ci->HasFlag(CI_SIGNKICK) || (ci->HasFlag(CI_SIGNKICK_LEVEL) && !ci->HasPriv(u, CA_SIGNKICK))) ci->c->Kick(ci->WhoSends(), u2, "%s (%s)", reason.c_str(), u->nick.c_str()); else ci->c->Kick(ci->WhoSends(), u2, "%s", reason.c_str()); diff --git a/modules/core/cs_main.cpp b/modules/core/cs_main.cpp index 7093ab3ca..5c490e4ff 100644 --- a/modules/core/cs_main.cpp +++ b/modules/core/cs_main.cpp @@ -58,14 +58,15 @@ class ChanServCore : public Module for (unsigned j = 0; j < ci->GetAccessCount(); ++j) { ChanAccess *ca = ci->GetAccess(j); - - if (!ca->nc || (!ca->nc->IsServicesOper() && Config->CSMaxReg && ca->nc->channelcount >= Config->CSMaxReg) || (ca->nc == nc)) + NickCore *anc = findcore(ca->mask); + + if (!anc || (!anc->IsServicesOper() && Config->CSMaxReg && anc->channelcount >= Config->CSMaxReg) || (anc == nc)) continue; - if (!highest || ca->level > highest->level) + if (!highest || *ca > *highest) highest = ca; }