/* Mode support * * (C) 2008-2011 Adam * (C) 2008-2022 Anope Team * * Please read COPYING and README for further details. */ #include "services.h" #include "modules.h" #include "config.h" #include "sockets.h" #include "protocol.h" #include "channels.h" #include "uplink.h" struct StackerInfo; /* List of pairs of user/channels and their stacker info */ static std::map UserStackerObjects; static std::map ChannelStackerObjects; /* Array of all modes Anope knows about.*/ static std::vector ChannelModes; static std::vector UserModes; /* Modes are in this array are at position * modechar. Additionally, status modes are in this array (again) at statuschar. */ static std::vector ChannelModesIdx; static std::vector UserModesIdx; static std::map ChannelModesByName; static std::map UserModesByName; /* Sorted by status */ static std::vector ChannelModesByStatus; /* Number of generic modes we support */ unsigned ModeManager::GenericChannelModes = 0, ModeManager::GenericUserModes = 0; struct StackerInfo { /* Modes to be added */ std::list > AddModes; /* Modes to be deleted */ std::list > DelModes; /* Bot this is sent from */ BotInfo *bi; StackerInfo() : bi(NULL) { } /** Add a mode to this object * @param mode The mode * @param set true if setting, false if unsetting * @param param The param for the mode */ void AddMode(Mode *mode, bool set, const Anope::string ¶m); }; ChannelStatus::ChannelStatus() { } ChannelStatus::ChannelStatus(const Anope::string &m) : modes(m) { } void ChannelStatus::AddMode(char c) { if (modes.find(c) == Anope::string::npos) modes.append(c); } void ChannelStatus::DelMode(char c) { modes = modes.replace_all_cs(c, ""); } bool ChannelStatus::HasMode(char c) const { return modes.find(c) != Anope::string::npos; } bool ChannelStatus::Empty() const { return modes.empty(); } void ChannelStatus::Clear() { modes.clear(); } const Anope::string &ChannelStatus::Modes() const { return modes; } Anope::string ChannelStatus::BuildModePrefixList() const { Anope::string ret; for (size_t i = 0; i < modes.length(); ++i) { ChannelMode *cm = ModeManager::FindChannelModeByChar(modes[i]); if (cm != NULL && cm->type == MODE_STATUS) { ChannelModeStatus *cms = anope_dynamic_static_cast(cm); ret += cms->symbol; } } return ret; } Mode::Mode(const Anope::string &mname, ModeClass mcl, char mch, ModeType mt) : name(mname), mclass(mcl), mchar(mch), type(mt) { } Mode::~Mode() { } bool Mode::CanSet(User *u) const { return true; } UserMode::UserMode(const Anope::string &un, char mch) : Mode(un, MC_USER, mch, MODE_REGULAR) { } UserModeParam::UserModeParam(const Anope::string &un, char mch) : UserMode(un, mch) { this->type = MODE_PARAM; } ChannelMode::ChannelMode(const Anope::string &cm, char mch) : Mode(cm, MC_CHANNEL, mch, MODE_REGULAR) { } bool ChannelMode::CanSet(User *u) const { EventReturn MOD_RESULT; FOREACH_RESULT(OnCanSet, MOD_RESULT, (u, this)); return MOD_RESULT != EVENT_STOP; } ChannelMode *ChannelMode::Wrap(Anope::string ¶m) { return this; } ChannelMode *ChannelMode::Unwrap(Anope::string ¶m) { for (unsigned i = 0; i < listeners.size(); ++i) { ChannelMode *cm = listeners[i]->Unwrap(this, param); if (cm != this) return cm; } return this; } ChannelMode *ChannelMode::Unwrap(ChannelMode *, Anope::string ¶m) { throw CoreException("Unwrap in channel mode"); } ChannelModeList::ChannelModeList(const Anope::string &cm, char mch) : ChannelMode(cm, mch) { this->type = MODE_LIST; } bool ChannelModeList::IsValid(Anope::string &mask) const { if (name == "BAN" || name == "EXCEPT" || name == "INVITEOVERRIDE") mask = IRCD->NormalizeMask(mask); return true; } ChannelModeParam::ChannelModeParam(const Anope::string &cm, char mch, bool ma) : ChannelMode(cm, mch), minus_no_arg(ma) { this->type = MODE_PARAM; } ChannelModeStatus::ChannelModeStatus(const Anope::string &mname, char modeChar, char msymbol, unsigned mlevel) : ChannelMode(mname, modeChar), symbol(msymbol), level(mlevel) { this->type = MODE_STATUS; } template ChannelModeVirtual::ChannelModeVirtual(const Anope::string &mname, const Anope::string &basename) : T(mname, 0) , base(basename) { basech = ModeManager::FindChannelModeByName(base); if (basech) basech->listeners.push_back(this); } template ChannelModeVirtual::~ChannelModeVirtual() { if (basech) { std::vector::iterator it = std::find(basech->listeners.begin(), basech->listeners.end(), this); if (it != basech->listeners.end()) basech->listeners.erase(it); } } template void ChannelModeVirtual::Check() { if (basech == NULL) { basech = ModeManager::FindChannelModeByName(base); if (basech) basech->listeners.push_back(this); } } template ChannelMode *ChannelModeVirtual::Wrap(Anope::string ¶m) { return basech; } template class ChannelModeVirtual; template class ChannelModeVirtual; bool UserModeOperOnly::CanSet(User *u) const { return u && u->HasMode("OPER"); } bool UserModeNoone::CanSet(User *u) const { return false; } bool ChannelModeKey::IsValid(Anope::string &value) const { if (!value.empty() && value.find(':') == Anope::string::npos && value.find(',') == Anope::string::npos) return true; return false; } bool ChannelModeOperOnly::CanSet(User *u) const { return u && u->HasMode("OPER"); } bool ChannelModeNoone::CanSet(User *u) const { return false; } void StackerInfo::AddMode(Mode *mode, bool set, const Anope::string ¶m) { bool is_param = mode->type == MODE_PARAM; std::list > *list, *otherlist; if (set) { list = &AddModes; otherlist = &DelModes; } else { list = &DelModes; otherlist = &AddModes; } /* Loop through the list and find if this mode is already on here */ std::list >::iterator it, it_end; for (it = list->begin(), it_end = list->end(); it != it_end; ++it) { /* The param must match too (can have multiple status or list modes), but * if it is a param mode it can match no matter what the param is */ if (it->first == mode && (is_param || param.equals_cs(it->second))) { list->erase(it); /* It can only be on this list once */ break; } } /* If the mode is on the other list, remove it from there (eg, we don't want +o-o Adam Adam) */ for (it = otherlist->begin(), it_end = otherlist->end(); it != it_end; ++it) { /* The param must match too (can have multiple status or list modes), but * if it is a param mode it can match no matter what the param is */ if (it->first == mode && (is_param || param.equals_cs(it->second))) { otherlist->erase(it); return; /* Note that we return here because this is like setting + and - on the same mode within the same * cycle, no change is made. This causes no problems with something like - + and -, because after the * second mode change the list is empty, and the third mode change starts fresh. */ } } /* Add this mode and its param to our list */ list->push_back(std::make_pair(mode, param)); } static class ModePipe : public Pipe { public: void OnNotify() { ModeManager::ProcessModes(); } } *modePipe; /** Get the stacker info for an item, if one doesn't exist it is created * @param Item The user/channel etc * @return The stacker info */ template static StackerInfo *GetInfo(List &l, Object *o) { typename List::const_iterator it = l.find(o); if (it != l.end()) return it->second; StackerInfo *s = new StackerInfo(); l[o] = s; return s; } /** Build a list of mode strings to send to the IRCd from the mode stacker * @param info The stacker info for a channel or user * @return a list of strings */ static std::list BuildModeStrings(StackerInfo *info) { std::list ret; std::list >::iterator it, it_end; Anope::string buf = "+", parambuf; unsigned NModes = 0; for (it = info->AddModes.begin(), it_end = info->AddModes.end(); it != it_end; ++it) { if (++NModes > IRCD->MaxModes || (buf.length() + parambuf.length() > IRCD->MaxLine - 100)) // Leave room for command, channel, etc { ret.push_back(buf + parambuf); buf = "+"; parambuf.clear(); NModes = 1; } buf += it->first->mchar; if (!it->second.empty()) parambuf += " " + it->second; } if (buf[buf.length() - 1] == '+') buf.erase(buf.length() - 1); buf += "-"; for (it = info->DelModes.begin(), it_end = info->DelModes.end(); it != it_end; ++it) { if (++NModes > IRCD->MaxModes || (buf.length() + parambuf.length() > IRCD->MaxLine - 100)) // Leave room for command, channel, etc { ret.push_back(buf + parambuf); buf = "-"; parambuf.clear(); NModes = 1; } buf += it->first->mchar; if (!it->second.empty()) parambuf += " " + it->second; } if (buf[buf.length() - 1] == '-') buf.erase(buf.length() - 1); if (!buf.empty()) ret.push_back(buf + parambuf); return ret; } bool ModeManager::AddUserMode(UserMode *um) { if (ModeManager::FindUserModeByChar(um->mchar) != NULL) return false; if (ModeManager::FindUserModeByName(um->name) != NULL) return false; if (um->name.empty()) { um->name = stringify(++GenericUserModes); Log() << "ModeManager: Added generic support for user mode " << um->mchar; } unsigned want = um->mchar; if (want >= UserModesIdx.size()) UserModesIdx.resize(want + 1); UserModesIdx[want] = um; UserModesByName[um->name] = um; UserModes.push_back(um); FOREACH_MOD(OnUserModeAdd, (um)); return true; } bool ModeManager::AddChannelMode(ChannelMode *cm) { if (cm->mchar && ModeManager::FindChannelModeByChar(cm->mchar) != NULL) return false; if (ModeManager::FindChannelModeByName(cm->name) != NULL) return false; if (cm->name.empty()) { cm->name = stringify(++GenericChannelModes); Log() << "ModeManager: Added generic support for channel mode " << cm->mchar; } if (cm->mchar) { unsigned want = cm->mchar; if (want >= ChannelModesIdx.size()) ChannelModesIdx.resize(want + 1); ChannelModesIdx[want] = cm; } if (cm->type == MODE_STATUS) { ChannelModeStatus *cms = anope_dynamic_static_cast(cm); unsigned want = cms->symbol; if (want >= ChannelModesIdx.size()) ChannelModesIdx.resize(want + 1); ChannelModesIdx[want] = cms; RebuildStatusModes(); } ChannelModesByName[cm->name] = cm; ChannelModes.push_back(cm); FOREACH_MOD(OnChannelModeAdd, (cm)); for (unsigned int i = 0; i < ChannelModes.size(); ++i) ChannelModes[i]->Check(); return true; } void ModeManager::RemoveUserMode(UserMode *um) { if (!um) return; unsigned want = um->mchar; if (want >= UserModesIdx.size()) return; if (UserModesIdx[want] != um) return; UserModesIdx[want] = NULL; UserModesByName.erase(um->name); std::vector::iterator it = std::find(UserModes.begin(), UserModes.end(), um); if (it != UserModes.end()) UserModes.erase(it); StackerDel(um); } void ModeManager::RemoveChannelMode(ChannelMode *cm) { if (!cm) return; if (cm->mchar) { unsigned want = cm->mchar; if (want >= ChannelModesIdx.size()) return; if (ChannelModesIdx[want] != cm) return; ChannelModesIdx[want] = NULL; } if (cm->type == MODE_STATUS) { ChannelModeStatus *cms = anope_dynamic_static_cast(cm); unsigned want = cms->symbol; if (want >= ChannelModesIdx.size()) return; if (ChannelModesIdx[want] != cm) return; ChannelModesIdx[want] = NULL; RebuildStatusModes(); } ChannelModesByName.erase(cm->name); std::vector::iterator it = std::find(ChannelModes.begin(), ChannelModes.end(), cm); if (it != ChannelModes.end()) ChannelModes.erase(it); StackerDel(cm); } ChannelMode *ModeManager::FindChannelModeByChar(char mode) { unsigned want = mode; if (want >= ChannelModesIdx.size()) return NULL; return ChannelModesIdx[want]; } UserMode *ModeManager::FindUserModeByChar(char mode) { unsigned want = mode; if (want >= UserModesIdx.size()) return NULL; return UserModesIdx[want]; } ChannelMode *ModeManager::FindChannelModeByName(const Anope::string &name) { std::map::iterator it = ChannelModesByName.find(name); if (it != ChannelModesByName.end()) return it->second; return NULL; } UserMode *ModeManager::FindUserModeByName(const Anope::string &name) { std::map::iterator it = UserModesByName.find(name); if (it != UserModesByName.end()) return it->second; return NULL; } char ModeManager::GetStatusChar(char value) { unsigned want = value; if (want >= ChannelModesIdx.size()) return 0; ChannelMode *cm = ChannelModesIdx[want]; if (cm == NULL || cm->type != MODE_STATUS || cm->mchar == value) return 0; return cm->mchar; } const std::vector &ModeManager::GetChannelModes() { return ChannelModes; } const std::vector &ModeManager::GetUserModes() { return UserModes; } const std::vector &ModeManager::GetStatusChannelModesByRank() { return ChannelModesByStatus; } static struct StatusSort { bool operator()(ChannelModeStatus *cm1, ChannelModeStatus *cm2) const { return cm1->level > cm2->level; } } statuscmp; void ModeManager::RebuildStatusModes() { ChannelModesByStatus.clear(); for (unsigned j = 0; j < ChannelModesIdx.size(); ++j) { ChannelMode *cm = ChannelModesIdx[j]; if (cm && cm->type == MODE_STATUS && std::find(ChannelModesByStatus.begin(), ChannelModesByStatus.end(), cm) == ChannelModesByStatus.end()) ChannelModesByStatus.push_back(anope_dynamic_static_cast(cm)); } std::sort(ChannelModesByStatus.begin(), ChannelModesByStatus.end(), statuscmp); } void ModeManager::StackerAdd(BotInfo *bi, Channel *c, ChannelMode *cm, bool Set, const Anope::string &Param) { StackerInfo *s = GetInfo(ChannelStackerObjects, c); s->AddMode(cm, Set, Param); if (bi) s->bi = bi; else s->bi = c->ci->WhoSends(); if (!modePipe) modePipe = new ModePipe(); modePipe->Notify(); } void ModeManager::StackerAdd(BotInfo *bi, User *u, UserMode *um, bool Set, const Anope::string &Param) { StackerInfo *s = GetInfo(UserStackerObjects, u); s->AddMode(um, Set, Param); if (bi) s->bi = bi; if (!modePipe) modePipe = new ModePipe(); modePipe->Notify(); } void ModeManager::ProcessModes() { if (!UserStackerObjects.empty()) { for (std::map::const_iterator it = UserStackerObjects.begin(), it_end = UserStackerObjects.end(); it != it_end; ++it) { User *u = it->first; StackerInfo *s = it->second; std::list ModeStrings = BuildModeStrings(s); for (std::list::iterator lit = ModeStrings.begin(), lit_end = ModeStrings.end(); lit != lit_end; ++lit) IRCD->SendMode(s->bi, u, "%s", lit->c_str()); delete it->second; } UserStackerObjects.clear(); } if (!ChannelStackerObjects.empty()) { for (std::map::const_iterator it = ChannelStackerObjects.begin(), it_end = ChannelStackerObjects.end(); it != it_end; ++it) { Channel *c = it->first; StackerInfo *s = it->second; std::list ModeStrings = BuildModeStrings(s); for (std::list::iterator lit = ModeStrings.begin(), lit_end = ModeStrings.end(); lit != lit_end; ++lit) IRCD->SendMode(s->bi, c, "%s", lit->c_str()); delete it->second; } ChannelStackerObjects.clear(); } } template static void StackerDel(std::map &map, T *obj) { typename std::map::iterator it = map.find(obj); if (it != map.end()) { StackerInfo *si = it->second; std::list ModeStrings = BuildModeStrings(si); for (std::list::iterator lit = ModeStrings.begin(), lit_end = ModeStrings.end(); lit != lit_end; ++lit) IRCD->SendMode(si->bi, obj, "%s", lit->c_str()); delete si; map.erase(it); } } void ModeManager::StackerDel(User *u) { ::StackerDel(UserStackerObjects, u); } void ModeManager::StackerDel(Channel *c) { ::StackerDel(ChannelStackerObjects, c); } void ModeManager::StackerDel(Mode *m) { for (std::map::const_iterator it = UserStackerObjects.begin(), it_end = UserStackerObjects.end(); it != it_end;) { StackerInfo *si = it->second; ++it; for (std::list >::iterator it2 = si->AddModes.begin(), it2_end = si->AddModes.end(); it2 != it2_end;) { if (it2->first == m) it2 = si->AddModes.erase(it2); else ++it2; } for (std::list >::iterator it2 = si->DelModes.begin(), it2_end = si->DelModes.end(); it2 != it2_end;) { if (it2->first == m) it2 = si->DelModes.erase(it2); else ++it2; } } for (std::map::const_iterator it = ChannelStackerObjects.begin(), it_end = ChannelStackerObjects.end(); it != it_end;) { StackerInfo *si = it->second; ++it; for (std::list >::iterator it2 = si->AddModes.begin(), it2_end = si->AddModes.end(); it2 != it2_end;) { if (it2->first == m) it2 = si->AddModes.erase(it2); else ++it2; } for (std::list >::iterator it2 = si->DelModes.begin(), it2_end = si->DelModes.end(); it2 != it2_end;) { if (it2->first == m) it2 = si->DelModes.erase(it2); else ++it2; } } } Entry::Entry(const Anope::string &m, const Anope::string &fh) : name(m), mask(fh), cidr_len(0), family(0) { Anope::string n, u, h; size_t at = fh.find('@'); if (at != Anope::string::npos) { this->host = fh.substr(at + 1); const Anope::string &nu = fh.substr(0, at); size_t ex = nu.find('!'); if (ex != Anope::string::npos) { this->user = nu.substr(ex + 1); this->nick = nu.substr(0, ex); } else this->user = nu; } else { if (fh.find('.') != Anope::string::npos || fh.find(':') != Anope::string::npos) this->host = fh; else this->nick = fh; } at = this->host.find('#'); if (at != Anope::string::npos) { this->real = this->host.substr(at + 1); this->host = this->host.substr(0, at); } /* If the mask is all *'s it will match anything, so just clear it */ if (this->nick.find_first_not_of("*") == Anope::string::npos) this->nick.clear(); if (this->user.find_first_not_of("*") == Anope::string::npos) this->user.clear(); if (this->host.find_first_not_of("*") == Anope::string::npos) this->host.clear(); else { /* Host might be a CIDR range */ size_t sl = this->host.find_last_of('/'); if (sl != Anope::string::npos) { const Anope::string &cidr_ip = this->host.substr(0, sl), &cidr_range = this->host.substr(sl + 1); sockaddrs addr(cidr_ip); try { if (addr.valid() && cidr_range.is_pos_number_only()) { this->cidr_len = convertTo(cidr_range); /* If we got here, cidr_len is a valid number. */ this->host = cidr_ip; this->family = addr.family(); Log(LOG_DEBUG) << "Ban " << mask << " has cidr " << this->cidr_len; } } catch (const ConvertException &) { } } } if (this->real.find_first_not_of("*") == Anope::string::npos) this->real.clear(); } const Anope::string Entry::GetMask() const { return this->mask; } const Anope::string Entry::GetNUHMask() const { Anope::string n = nick.empty() ? "*" : nick, u = user.empty() ? "*" : user, h = host.empty() ? "*" : host, r = real.empty() ? "" : "#" + real, c; switch (family) { case AF_INET: if (cidr_len <= 32) c = "/" + stringify(cidr_len); break; case AF_INET6: if (cidr_len <= 128) c = "/" + stringify(cidr_len); break; } return n + "!" + u + "@" + h + c + r; } bool Entry::Matches(User *u, bool full) const { /* First check if this mode has defined any matches (usually for extbans). */ if (IRCD->IsExtbanValid(this->mask)) { ChannelMode *cm = ModeManager::FindChannelModeByName(this->name); if (cm != NULL && cm->type == MODE_LIST) { ChannelModeList *cml = anope_dynamic_static_cast(cm); if (cml->Matches(u, this)) return true; } } /* If the user's displayed host is their real host, then we can do a full match without * having to worry about exposing a user's IP */ full |= u->GetDisplayedHost() == u->host; bool ret = true; if (!this->nick.empty() && !Anope::Match(u->nick, this->nick)) ret = false; if (!this->user.empty() && !Anope::Match(u->GetVIdent(), this->user) && (!full || !Anope::Match(u->GetIdent(), this->user))) ret = false; if (this->cidr_len && full) { try { if (!cidr(this->host, this->cidr_len).match(u->ip)) ret = false; } catch (const SocketException &) { ret = false; } } else if (!this->host.empty() && !Anope::Match(u->GetDisplayedHost(), this->host) && !Anope::Match(u->GetCloakedHost(), this->host) && (!full || (!Anope::Match(u->host, this->host) && !Anope::Match(u->ip.addr(), this->host)))) ret = false; if (!this->real.empty() && !Anope::Match(u->realname, this->real)) ret = false; return ret; }