/* ChanServ core functions * * (C) 2003-2013 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" #include "modules/cs_mode.h" struct ModeLockImpl : ModeLock, Serializable { ModeLockImpl() : Serializable("ModeLock") { } ~ModeLockImpl() { ChannelInfo *chan = ChannelInfo::Find(ci); if (chan) { ModeLocks *ml = chan->GetExt("modelocks"); if (ml) ml->RemoveMLock(this); } } void Serialize(Serialize::Data &data) const anope_override; static Serializable* Unserialize(Serializable *obj, Serialize::Data &data); }; struct ModeLocksImpl : ModeLocks { Serialize::Reference ci; Serialize::Checker mlocks; ModeLocksImpl(Extensible *obj) : ci(anope_dynamic_static_cast(obj)), mlocks("ModeLock") { } bool HasMLock(ChannelMode *mode, const Anope::string ¶m, bool status) const anope_override { if (!mode) return false; for (ModeList::const_iterator it = this->mlocks->begin(); it != this->mlocks->end(); ++it) { const ModeLock *ml = *it; if (ml->name == mode->name && ml->set == status && ml->param == param) return true; } return false; } bool SetMLock(ChannelMode *mode, bool status, const Anope::string ¶m, Anope::string setter, time_t created = Anope::CurTime) anope_override { if (!mode) return false; RemoveMLock(mode, status, param); if (setter.empty()) setter = ci->GetFounder() ? ci->GetFounder()->display : "Unknown"; ModeLock *ml = new ModeLockImpl(); ml->ci = ci->name; ml->set = status; ml->name = mode->name; ml->param = param; ml->setter = setter; ml->created = created; EventReturn MOD_RESULT; FOREACH_RESULT(OnMLock, MOD_RESULT, (this->ci, ml)); if (MOD_RESULT == EVENT_STOP) { delete ml; return false; } this->mlocks->push_back(ml); return true; } bool RemoveMLock(ChannelMode *mode, bool status, const Anope::string ¶m = "") anope_override { if (!mode) return false; for (ModeList::iterator it = this->mlocks->begin(); it != this->mlocks->end(); ++it) { ModeLock *m = *it; if (m->name == mode->name) { // For list or status modes, we must check the parameter if (mode->type == MODE_LIST || mode->type == MODE_STATUS) if (m->param != param) continue; EventReturn MOD_RESULT; FOREACH_RESULT(OnUnMLock, MOD_RESULT, (this->ci, m)); if (MOD_RESULT == EVENT_STOP) break; delete m; return true; } } return false; } void RemoveMLock(ModeLock *mlock) anope_override { ModeList::iterator it = std::find(this->mlocks->begin(), this->mlocks->end(), mlock); if (it != this->mlocks->end()) this->mlocks->erase(it); } void ClearMLock() anope_override { ModeList ml; this->mlocks->swap(ml); for (unsigned i = 0; i < ml.size(); ++i) delete ml[i]; } const ModeList &GetMLock() const anope_override { return this->mlocks; } std::list GetModeLockList(const Anope::string &name) anope_override { std::list mlist; for (ModeList::const_iterator it = this->mlocks->begin(); it != this->mlocks->end(); ++it) { ModeLock *m = *it; if (m->name == name) mlist.push_back(m); } return mlist; } const ModeLock *GetMLock(const Anope::string &mname, const Anope::string ¶m = "") anope_override { for (ModeList::const_iterator it = this->mlocks->begin(); it != this->mlocks->end(); ++it) { ModeLock *m = *it; if (m->name == mname && m->param == param) return m; } return NULL; } Anope::string GetMLockAsString(bool complete) const anope_override { Anope::string pos = "+", neg = "-", params; for (ModeList::const_iterator it = this->mlocks->begin(); it != this->mlocks->end(); ++it) { const ModeLock *ml = *it; ChannelMode *cm = ModeManager::FindChannelModeByName(ml->name); if (!cm || cm->type == MODE_LIST || cm->type == MODE_STATUS) continue; if (ml->set) pos += cm->mchar; else neg += cm->mchar; if (complete && ml->set && !ml->param.empty() && cm->type == MODE_PARAM) params += " " + ml->param; } if (pos.length() == 1) pos.clear(); if (neg.length() == 1) neg.clear(); return pos + neg + params; } void Check() anope_override { if (this->mlocks->empty()) ci->Shrink("modelocks"); } }; void ModeLockImpl::Serialize(Serialize::Data &data) const { data["ci"] << this->ci; data["set"] << this->set; data["name"] << this->name; data["param"] << this->param; data["setter"] << this->setter; data.SetType("created", Serialize::Data::DT_INT); data["created"] << this->created; } Serializable* ModeLockImpl::Unserialize(Serializable *obj, Serialize::Data &data) { Anope::string sci; data["ci"] >> sci; ChannelInfo *ci = ChannelInfo::Find(sci); if (!ci) return NULL; ModeLockImpl *ml; if (obj) ml = anope_dynamic_static_cast(obj); else { ml = new ModeLockImpl(); ml->ci = ci->name; } data["set"] >> ml->set; data["created"] >> ml->created; data["setter"] >> ml->setter; data["name"] >> ml->name; data["param"] >> ml->param; if (!obj) ci->Require("modelocks")->mlocks->push_back(ml); return ml; } class CommandCSMode : public Command { bool CanSet(CommandSource &source, ChannelInfo *ci, ChannelMode *cm, bool self) { if (!ci || !cm || cm->type != MODE_STATUS) return false; return source.AccessFor(ci).HasPriv(cm->name + (self ? "ME" : "")); } void DoLock(CommandSource &source, ChannelInfo *ci, const std::vector ¶ms) { User *u = source.GetUser(); const Anope::string &subcommand = params[2]; const Anope::string ¶m = params.size() > 3 ? params[3] : ""; bool override = !source.AccessFor(ci).HasPriv("MODE"); ModeLocks *modelocks = ci->Require("modelocks"); if ((subcommand.equals_ci("ADD") || subcommand.equals_ci("SET")) && !param.empty()) { /* If setting, remove the existing locks */ if (subcommand.equals_ci("SET")) { const ModeLocks::ModeList mlocks = modelocks->GetMLock(); for (ModeLocks::ModeList::const_iterator it = mlocks.begin(); it != mlocks.end(); ++it) { const ModeLock *ml = *it; ChannelMode *cm = ModeManager::FindChannelModeByName(ml->name); if (cm && cm->CanSet(source.GetUser())) modelocks->RemoveMLock(cm, ml->set, ml->param); } } spacesepstream sep(param); Anope::string modes; sep.GetToken(modes); Anope::string pos = "+", neg = "-", pos_params, neg_params; int adding = -1; for (size_t i = 0; i < modes.length(); ++i) { switch (modes[i]) { case '+': adding = 1; break; case '-': adding = 0; break; default: if (adding == -1) break; ChannelMode *cm = ModeManager::FindChannelModeByChar(modes[i]); if (!cm) { source.Reply(_("Unknown mode character %c ignored."), modes[i]); break; } else if (u && !cm->CanSet(u)) { source.Reply(_("You may not (un)lock mode %c."), modes[i]); break; } Anope::string mode_param; if (((cm->type == MODE_STATUS || cm->type == MODE_LIST) && !sep.GetToken(mode_param)) || (cm->type == MODE_PARAM && adding && !sep.GetToken(mode_param))) source.Reply(_("Missing parameter for mode %c."), cm->mchar); else if (cm->type == MODE_LIST && ci->c && IRCD->GetMaxListFor(ci->c) && ci->c->HasMode(cm->name) >= IRCD->GetMaxListFor(ci->c)) source.Reply(_("List for mode %c is full."), cm->mchar); else { modelocks->SetMLock(cm, adding, mode_param, source.GetNick()); if (adding) { pos += cm->mchar; if (!mode_param.empty()) pos_params += " " + mode_param; } else { neg += cm->mchar; if (!mode_param.empty()) neg_params += " " + mode_param; } } } } if (pos == "+") pos.clear(); if (neg == "-") neg.clear(); Anope::string reply = pos + neg + pos_params + neg_params; source.Reply(_("%s locked on %s."), modelocks->GetMLockAsString(true).c_str(), ci->name.c_str()); Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to lock " << modelocks->GetMLockAsString(true); if (ci->c) ci->c->CheckModes(); } else if (subcommand.equals_ci("DEL") && !param.empty()) { spacesepstream sep(param); Anope::string modes; sep.GetToken(modes); int adding = -1; for (size_t i = 0; i < modes.length(); ++i) { switch (modes[i]) { case '+': adding = 1; break; case '-': adding = 0; break; default: if (adding == -1) break; ChannelMode *cm = ModeManager::FindChannelModeByChar(modes[i]); if (!cm) { source.Reply(_("Unknown mode character %c ignored."), modes[i]); break; } else if (u && !cm->CanSet(u)) { source.Reply(_("You may not (un)lock mode %c."), modes[i]); break; } Anope::string mode_param; if (cm->type != MODE_REGULAR && !sep.GetToken(mode_param)) source.Reply(_("Missing parameter for mode %c."), cm->mchar); else { if (modelocks->RemoveMLock(cm, adding, mode_param)) { if (!mode_param.empty()) mode_param = " " + mode_param; source.Reply(_("%c%c%s has been unlocked from %s."), adding == 1 ? '+' : '-', cm->mchar, mode_param.c_str(), ci->name.c_str()); Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to unlock " << (adding ? '+' : '-') << cm->mchar << mode_param; } else source.Reply(_("%c%c is not locked on %s."), adding == 1 ? '+' : '-', cm->mchar, ci->name.c_str()); } } } } else if (subcommand.equals_ci("LIST")) { const ModeLocks::ModeList mlocks = modelocks->GetMLock(); if (mlocks.empty()) { source.Reply(_("Channel %s has no mode locks."), ci->name.c_str()); } else { ListFormatter list; list.AddColumn("Mode").AddColumn("Param").AddColumn("Creator").AddColumn("Created"); for (ModeLocks::ModeList::const_iterator it = mlocks.begin(), it_end = mlocks.end(); it != it_end; ++it) { const ModeLock *ml = *it; ChannelMode *cm = ModeManager::FindChannelModeByName(ml->name); if (!cm) continue; ListFormatter::ListEntry entry; entry["Mode"] = Anope::printf("%c%c", ml->set ? '+' : '-', cm->mchar); entry["Param"] = ml->param; entry["Creator"] = ml->setter; entry["Created"] = Anope::strftime(ml->created, source.nc, false); list.AddEntry(entry); } source.Reply(_("Mode locks for %s:"), ci->name.c_str()); std::vector replies; list.Process(replies); for (unsigned i = 0; i < replies.size(); ++i) source.Reply(replies[i]); } } else this->OnSyntaxError(source, subcommand); } void DoSet(CommandSource &source, ChannelInfo *ci, const std::vector ¶ms) { User *u = source.GetUser(); bool has_access = source.AccessFor(ci).HasPriv("MODE") || source.HasPriv("chanserv/administration"); spacesepstream sep(params.size() > 3 ? params[3] : ""); Anope::string modes = params[2], param; bool override = !source.AccessFor(ci).HasPriv("MODE") && source.HasPriv("chanserv/administration"); Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to set " << params[2]; int adding = -1; for (size_t i = 0; i < modes.length(); ++i) { switch (modes[i]) { case '+': adding = 1; break; case '-': adding = 0; break; case '*': if (adding == -1 || !has_access) break; for (unsigned j = 0; j < ModeManager::GetChannelModes().size(); ++j) { ChannelMode *cm = ModeManager::GetChannelModes()[j]; if (!cm) continue; if (!u || cm->CanSet(u)) { if (cm->type == MODE_REGULAR || (!adding && cm->type == MODE_PARAM)) { if (adding) ci->c->SetMode(NULL, cm); else ci->c->RemoveMode(NULL, cm); } } } break; default: if (adding == -1) break; ChannelMode *cm = ModeManager::FindChannelModeByChar(modes[i]); if (!cm || (u && !cm->CanSet(u))) continue; switch (cm->type) { case MODE_REGULAR: if (!has_access) break; if (adding) ci->c->SetMode(NULL, cm); else ci->c->RemoveMode(NULL, cm); break; case MODE_PARAM: if (!has_access) break; if (adding && !sep.GetToken(param)) break; if (adding) ci->c->SetMode(NULL, cm, param); else ci->c->RemoveMode(NULL, cm); break; case MODE_STATUS: { if (!sep.GetToken(param)) param = source.GetNick(); AccessGroup u_access = source.AccessFor(ci); if (param.find_first_of("*?") != Anope::string::npos) { if (!this->CanSet(source, ci, cm, false)) { source.Reply(_("You do not have access to set mode %c."), cm->mchar); break; } for (Channel::ChanUserList::const_iterator it = ci->c->users.begin(), it_end = ci->c->users.end(); it != it_end;) { ChanUserContainer *uc = it->second; ++it; AccessGroup targ_access = ci->AccessFor(uc->user); if (uc->user->IsProtected() || (ci->HasExt("PEACE") && targ_access >= u_access)) { source.Reply(_("You do not have the access to change %s's modes."), uc->user->nick.c_str()); continue; } if (Anope::Match(uc->user->GetMask(), param)) { if (adding) ci->c->SetMode(NULL, cm, uc->user->GetUID()); else ci->c->RemoveMode(NULL, cm, uc->user->GetUID()); } } } else { User *target = User::Find(param, true); if (target == NULL) { source.Reply(NICK_X_NOT_IN_USE, param.c_str()); break; } if (!this->CanSet(source, ci, cm, source.GetUser() == target)) { source.Reply(_("You do not have access to set mode %c."), cm->mchar); break; } if (source.GetUser() != target) { AccessGroup targ_access = ci->AccessFor(target); if (ci->HasExt("PEACE") && targ_access >= u_access) { source.Reply(_("You do not have the access to change %s's modes."), target->nick.c_str()); break; } else if (target->IsProtected()) { source.Reply(ACCESS_DENIED); break; } } if (adding) ci->c->SetMode(NULL, cm, target->GetUID()); else ci->c->RemoveMode(NULL, cm, target->GetUID()); } break; } case MODE_LIST: if (!has_access) break; if (!sep.GetToken(param)) break; if (adding) { if (IRCD->GetMaxListFor(ci->c) && ci->c->HasMode(cm->name) < IRCD->GetMaxListFor(ci->c)) ci->c->SetMode(NULL, cm, param); } else { std::pair its = ci->c->GetModeList(cm->name); for (; its.first != its.second;) { const Anope::string &mask = its.first->second; ++its.first; if (Anope::Match(mask, param)) ci->c->RemoveMode(NULL, cm, mask); } } } } } } void DoClear(CommandSource &source, ChannelInfo *ci, const std::vector ¶ms) { const Anope::string ¶m = params.size() > 2 ? params[2] : ""; if (param.empty()) { std::vector new_params; new_params.push_back(params[0]); new_params.push_back("SET"); new_params.push_back("-*"); this->DoSet(source, ci, new_params); } else if (param.equals_ci("BANS") || param.equals_ci("EXEMPTS") || param.equals_ci("INVITEOVERRIDES") || param.equals_ci("VOICES") || param.equals_ci("HALFOPS") || param.equals_ci("OPS")) { const Anope::string &mname = param.upper().substr(0, param.length() - 1); ChannelMode *cm = ModeManager::FindChannelModeByName(mname); if (cm == NULL) { source.Reply(_("Your IRCD does not support %s."), mname.upper().c_str()); return; } std::vector new_params; new_params.push_back(params[0]); new_params.push_back("SET"); new_params.push_back("-" + stringify(cm->mchar)); new_params.push_back("*"); this->DoSet(source, ci, new_params); } else this->SendSyntax(source); } public: CommandCSMode(Module *creator) : Command(creator, "chanserv/mode", 2, 4) { this->SetDesc(_("Control modes and mode locks on a channel")); this->SetSyntax(_("\037channel\037 LOCK {ADD|DEL|SET|LIST} [\037what\037]")); this->SetSyntax(_("\037channel\037 SET \037modes\037")); this->SetSyntax(_("\037channel\037 CLEAR [\037what\037]")); } void Execute(CommandSource &source, const std::vector ¶ms) anope_override { const Anope::string &subcommand = params[1]; ChannelInfo *ci = ChannelInfo::Find(params[0]); if (!ci || !ci->c) source.Reply(CHAN_X_NOT_IN_USE, params[0].c_str()); else if (subcommand.equals_ci("LOCK") && params.size() > 2) { if (!source.AccessFor(ci).HasPriv("MODE") && !source.HasPriv("chanserv/administration")) source.Reply(ACCESS_DENIED); else this->DoLock(source, ci, params); } else if (subcommand.equals_ci("SET") && params.size() > 2) this->DoSet(source, ci, params); else if (subcommand.equals_ci("CLEAR")) { if (!source.AccessFor(ci).HasPriv("MODE") && !source.HasPriv("chanserv/administration")) source.Reply(ACCESS_DENIED); else this->DoClear(source, ci, params); } else this->OnSyntaxError(source, ""); } bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override { this->SendSyntax(source); source.Reply(" "); source.Reply(_("Mainly controls mode locks and mode access (which is different from channel access)\n" "on a channel.\n" " \n" "The \002%s LOCK\002 command allows you to add, delete, and view mode locks on a channel.\n" "If a mode is locked on or off, services will not allow that mode to be changed. The \2SET\2\n" "command will clear all existing mode locks and set the new one given, while \2ADD\2 and \2DEL\2\n" "modify the existing mode lock.\n" "Example:\n" " \002MODE #channel LOCK ADD +bmnt *!*@*aol*\002\n" " \n" "The \002%s SET\002 command allows you to set modes through services. Wildcards * and ? may\n" "be given as parameters for list and status modes.\n" "Example:\n" " \002MODE #channel SET +v *\002\n" " Sets voice status to all users in the channel.\n" " \n" " \002MODE #channel SET -b ~c:*\n" " Clears all extended bans that start with ~c:\n" " \n" "The \002%s CLEAR\002 command is an easy way to clear modes on a channel. \037what\037 may be\n" "one of bans, exempts, inviteoverrides, ops, halfops, or voices. If \037what\037 is not given then all\n" "basic modes are removed."), source.command.upper().c_str(), source.command.upper().c_str(), source.command.upper().c_str()); return true; } }; class CSMode : public Module { CommandCSMode commandcsmode; ExtensibleItem modelocks; Serialize::Type modelocks_type; public: CSMode(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR), commandcsmode(this), modelocks(this, "modelocks"), modelocks_type("ModeLock", ModeLockImpl::Unserialize) { } EventReturn OnCheckModes(Channel *c) anope_override { if (!c->ci) return EVENT_CONTINUE; ModeLocks *ml = modelocks.Get(c->ci); if (ml) for (ModeLocks::ModeList::const_iterator it = ml->GetMLock().begin(), it_end = ml->GetMLock().end(); it != it_end; ++it) { const ModeLock *ml = *it; ChannelMode *cm = ModeManager::FindChannelModeByName(ml->name); if (!cm) continue; if (cm->type == MODE_REGULAR) { if (!c->HasMode(cm->name) && ml->set) c->SetMode(NULL, cm); else if (c->HasMode(cm->name) && !ml->set) c->RemoveMode(NULL, cm); } else if (cm->type == MODE_PARAM) { /* If the channel doesnt have the mode, or it does and it isn't set correctly */ if (ml->set) { Anope::string param; c->GetParam(cm->name, param); if (!c->HasMode(cm->name) || (!param.empty() && !ml->param.empty() && !param.equals_cs(ml->param))) c->SetMode(NULL, cm, ml->param); } else { if (c->HasMode(cm->name)) c->RemoveMode(NULL, cm); } } else if (cm->type == MODE_LIST) { if (ml->set) c->SetMode(NULL, cm, ml->param); else c->RemoveMode(NULL, cm, ml->param); } } } EventReturn OnChannelModeSet(Channel *c, MessageSource &setter, ChannelMode *mode, const Anope::string ¶m) anope_override { if (!c->ci) return EVENT_CONTINUE; ModeLocks *ml = modelocks.Get(c->ci); if (!ml) return EVENT_CONTINUE; if (ml->HasMLock(mode, param, false)) c->RemoveMode(c->ci->WhoSends(), mode, param); } EventReturn OnChannelModeUnset(Channel *c, MessageSource &setter, ChannelMode *mode, const Anope::string ¶m) anope_override { if (!c->ci) return EVENT_CONTINUE; ModeLocks *ml = modelocks.Get(c->ci); if (!ml) return EVENT_CONTINUE; if (ml->HasMLock(mode, param, true)) c->SetMode(c->ci->WhoSends(), mode, param); } void OnCreateChan(ChannelInfo *ci) anope_override { ModeLocks *ml = modelocks.Require(ci); Anope::string modes; spacesepstream sep(Config->GetModule(this)->Get("mlock", "+nrt")); if (sep.GetToken(modes)) { bool add = true; for (unsigned i = 0; i < modes.length(); ++i) { if (modes[i] == '+') add = true; else if (modes[i] == '-') add = false; else { ChannelMode *cm = ModeManager::FindChannelModeByChar(modes[i]); Anope::string param; if (cm && (cm->type == MODE_REGULAR || sep.GetToken(param))) ml->SetMLock(cm, add, param); } } } ml->Check(); } void OnChanInfo(CommandSource &source, ChannelInfo *ci, InfoFormatter &info, bool show_hidden) anope_override { if (!show_hidden) return; ModeLocks *ml = modelocks.Get(ci); if (ml) info[_("Mode lock")] = ml->GetMLockAsString(true); } }; MODULE_INIT(CSMode)