/* +------------------------------------+ * | Inspire Internet Relay Chat Daemon | * +------------------------------------+ * * InspIRCd: (C) 2002-2011 InspIRCd Development Team * See: http://wiki.inspircd.org/Credits * * This program is free but copyrighted software; see * the file COPYING for details. * * --------------------------------------------------- */ #include "inspircd.h" #include "builtin-modes.h" ModeHandler::ModeHandler(Module* Creator, const std::string& Name, char modeletter, ParamSpec Params, ModeType type) : ServiceProvider(Creator, Name, SERVICE_MODE), m_paramtype(TR_TEXT), parameters_taken(Params), m_type(type), levelrequired(HALFOP_VALUE), mode(modeletter), prefix(0), oper(false), fixed_letter(true), list(false), disabled(false) { } CullResult ModeHandler::cull() { if (ServerInstance->Modes) ServerInstance->Modes->DelMode(this); return classbase::cull(); } ModeHandler::~ModeHandler() { if (ServerInstance && ServerInstance->Modes && ServerInstance->Modes->FindMode(mode, m_type) == this) ServerInstance->Logs->Log("MODE", DEFAULT, "ERROR: Destructor for mode %c called while still registered", mode); } bool ModeHandler::IsListMode() { return list; } unsigned int ModeHandler::GetPrefixRank() { return 0; } void ModeHandler::AdjustModeChar(char proposed_letter) { if (fixed_letter) return; mode = proposed_letter; } int ModeHandler::GetNumParams(bool adding) { switch (parameters_taken) { case PARAM_ALWAYS: return 1; case PARAM_SETONLY: return adding ? 1 : 0; case PARAM_NONE: break; } return 0; } std::string ModeHandler::GetUserParameter(User* user) { return ""; } void ModeHandler::AccessCheck(ModePermissionData&) { } void ModeHandler::DisplayList(User*, Channel*) { } void ModeHandler::DisplayEmptyList(User*, Channel*) { } void ModeHandler::OnParameterMissing(User* user, User* dest, Channel* channel, std::string& param) { } bool ModeHandler::ParamOrder(const std::string& theirs, const std::string& ours) { return (theirs < ours); } ModeAction SimpleUserModeHandler::OnModeChange(User* source, User* dest, Channel* channel, std::string ¶meter, bool adding) { if (adding) { if (!dest->IsModeSet(this->GetModeChar())) { dest->SetMode(this->GetModeChar(),true); return MODEACTION_ALLOW; } } else { if (dest->IsModeSet(this->GetModeChar())) { dest->SetMode(this->GetModeChar(),false); return MODEACTION_ALLOW; } } return MODEACTION_DENY; } ModeAction SimpleChannelModeHandler::OnModeChange(User* source, User* dest, Channel* channel, std::string ¶meter, bool adding) { if (adding) { if (!channel->IsModeSet(this)) { channel->SetMode(this,true); return MODEACTION_ALLOW; } } else { if (channel->IsModeSet(this)) { channel->SetMode(this,false); return MODEACTION_ALLOW; } } return MODEACTION_DENY; } ModeAction ParamChannelModeHandler::OnModeChange(User* source, User* dest, Channel* channel, std::string ¶meter, bool adding) { if (adding) { std::string now = channel->GetModeParameter(this); if (parameter == now) return MODEACTION_DENY; if (!ParamValidate(parameter)) return MODEACTION_DENY; channel->SetModeParam(this, parameter); } else channel->SetModeParam(this, ""); return MODEACTION_ALLOW; } bool ParamChannelModeHandler::ParamValidate(std::string& parameter) { return true; } ModeWatcher::ModeWatcher(Module* Creator, char modeletter, ModeType type) : mode(modeletter), m_type(type), creator(Creator) { } ModeWatcher::~ModeWatcher() { } char ModeWatcher::GetModeChar() { return mode; } ModeType ModeWatcher::GetModeType() { return m_type; } bool ModeWatcher::BeforeMode(User*, User*, Channel*, std::string&, bool, ModeType) { return true; } void ModeWatcher::AfterMode(User*, User*, Channel*, const std::string&, bool, ModeType) { } irc::modechange::modechange(const std::string& name, const std::string& param, bool add) : adding(add), value(param) { ModeHandler* mh = ServerInstance->Modes->FindMode(name); if (mh) mode = mh->id; } irc::modechange::modechange(char modechar, ModeType type, const std::string& param, bool add) : adding(add), value(param) { ModeHandler* mh = ServerInstance->Modes->FindMode(modechar, type); if (mh) mode = mh->id; } std::string irc::modestacker::popModeLine(SerializeFormat format, int maxlen, int maxmodes) { char pm_now = '\0'; std::string modeline; std::stringstream params; int nmodes = 0, linelen = 0; if (maxmodes == 0) maxmodes = ServerInstance->Config->Limits.MaxModes; std::vector::iterator iter = sequence.begin(); for(; iter != sequence.end() && nmodes < maxmodes; ++iter) { char pm = iter->adding ? '+' : '-'; ModeHandler* mh = ServerInstance->Modes->FindMode(iter->mode); if (!mh) continue; char modechar = mh->GetModeChar(); std::string value = iter->value; if (mh->GetTranslateType() == TR_NICK) { User* u = ServerInstance->FindNick(value); if (u && format == FORMAT_USER) value = u->nick; else if (u && (format == FORMAT_NETWORK || format == FORMAT_INTERNAL)) value = u->uuid; else value.clear(); } else if (mh->GetTranslateType() == TR_CUSTOM) { mh->TranslateMode(value, iter->adding, format); } if (mh->GetNumParams(iter->adding) && value.empty()) { // value is empty when we want a param - failed conversion? continue; } if (format == FORMAT_PERSIST || !modechar) { // this will only happen if we already allowed NameOnlyModes, no need to check modechar = 'Z'; if (mh->GetNumParams(iter->adding)) value = mh->name + "=" + value; else value = mh->name; } else if (!mh->GetNumParams(iter->adding)) value.clear(); int mylen = (pm == pm_now) ? 1 : 2; if (!value.empty()) mylen += 1 + value.length(); if (mylen + linelen > maxlen) break; linelen += mylen; if (pm != pm_now) { modeline.push_back(pm); pm_now = pm; } modeline.push_back(modechar); if (!value.empty()) params << " " << value; } sequence.erase(sequence.begin(), iter); if (format == FORMAT_PERSIST) { std::string rv = params.str(); if (rv.empty()) return rv; // drop the initial space return rv.substr(1); } if (modeline.empty()) return "+"; return modeline + params.str(); } void ModeParser::DisplayCurrentModes(User *user, User* targetuser, Channel* targetchannel, const char* text) { if (targetchannel) { irc::modestacker ms; targetchannel->ChanModes(ms, targetchannel->HasUser(user) ? MODELIST_SHORT : MODELIST_PUBLIC); /* Display channel's current mode string */ user->WriteNumeric(RPL_CHANNELMODEIS, "%s %s %s", user->nick.c_str(), targetchannel->name.c_str(), ms.popModeLine(FORMAT_USER, 400, INT_MAX).c_str()); user->WriteNumeric(RPL_CHANNELCREATED, "%s %s %lu", user->nick.c_str(), targetchannel->name.c_str(), (unsigned long)targetchannel->age); return; } else { if (targetuser == user || user->HasPrivPermission("users/auspex")) { /* Display user's current mode string */ user->WriteNumeric(RPL_UMODEIS, "%s :+%s",targetuser->nick.c_str(),targetuser->FormatModes()); if (IS_OPER(targetuser)) user->WriteNumeric(RPL_SNOMASKIS, "%s +%s :Server notice mask", targetuser->nick.c_str(), targetuser->FormatNoticeMasks()); return; } else { user->WriteNumeric(ERR_USERSDONTMATCH, "%s :Can't view modes for other users", user->nick.c_str()); return; } } } ModeAction ModeParser::TryMode(User* user, User* targetuser, Channel* chan, irc::modechange& mc, bool SkipACL) { bool adding = mc.adding; ModeID modeid = mc.mode; ModeType type = chan ? MODETYPE_CHANNEL : MODETYPE_USER; ModeHandler *mh = FindMode(modeid); int pcnt = mh->GetNumParams(adding); // crop mode parameter size to 250 characters if (mc.value.length() > 250) mc.value = mc.value.substr(0, 250); ModResult MOD_RESULT; FIRST_MOD_RESULT(OnRawMode, MOD_RESULT, (user, chan, mc)); if (IS_LOCAL(user) && (MOD_RESULT == MOD_RES_DENY)) return MODEACTION_DENY; if (chan && !SkipACL && (MOD_RESULT != MOD_RES_ALLOW)) { User* targ = (mh->GetTranslateType() == TR_NICK) ? ServerInstance->FindNick(mc.value) : NULL; ModePermissionData perm(user, "mode/" + mh->name, chan, targ, mc); mh->AccessCheck(perm); FOR_EACH_MOD(OnPermissionCheck, (perm)); perm.DoRankCheck(); if (perm.result == MOD_RES_DENY) { if (!perm.reason.empty()) user->SendText(perm.reason); return MODEACTION_DENY; } } std::pair watchers = modewatchers.equal_range(modeid); for (ModeWatcherMap::iterator watcher = watchers.first; watcher != watchers.second; watcher++) { if (watcher->second->BeforeMode(user, targetuser, chan, mc.value, adding, type) == false) return MODEACTION_DENY; /* A module whacked the parameter completely, and there was one. abort. */ if (pcnt && mc.value.empty()) return MODEACTION_DENY; } if (!SkipACL && (mh->disabled || (adding && mh->NeedsOper())) && !user->HasModePermission(mh->id)) { /* It's an oper only mode, and they don't have access to it. */ if (IS_OPER(user)) { user->WriteNumeric(ERR_NOPRIVILEGES, "%s :Permission Denied - Oper type %s does not have access to set the %s mode", user->nick.c_str(), user->oper->NameStr(), mh->name.c_str()); } else if (mh->disabled) { user->WriteNumeric(ERR_NOPRIVILEGES, "%s :Permission Denied - mode %s has been locked by the administrator", user->nick.c_str(), mh->name.c_str()); } else { user->WriteNumeric(ERR_NOPRIVILEGES, "%s :Permission Denied - Only operators may set the %s mode", user->nick.c_str(), mh->name.c_str()); } return MODEACTION_DENY; } if (mh->GetTranslateType() == TR_NICK) { User* targ = ServerInstance->FindNick(mc.value); if(!targ) { user->WriteNumeric(ERR_NOSUCHNICK, "%s %s :No such nick/channel", user->nick.c_str(), mc.value.c_str()); return MODEACTION_DENY; } else mc.value = targ->uuid; } if (mh->GetPrefixRank() && chan) { User* user_to_prefix = ServerInstance->FindNick(mc.value); if (!user_to_prefix) return MODEACTION_DENY; if (!chan->SetPrefix(user_to_prefix, mh->GetModeChar(), adding)) return MODEACTION_DENY; } /* Call the handler for the mode */ ModeAction ma = mh->OnModeChange(user, targetuser, chan, mc.value, adding); if (pcnt && mc.value.empty()) return MODEACTION_DENY; if (ma != MODEACTION_ALLOW) return ma; for (ModeWatcherMap::iterator watcher = watchers.first; watcher != watchers.second; watcher++) watcher->second->AfterMode(user, targetuser, chan, mc.value, adding, type); return MODEACTION_ALLOW; } void ModeParser::Parse(const std::vector& parameters, User *user, Extensible*& target, irc::modestacker& modes) { std::string targetstr = parameters[0]; Channel* targetchannel = ServerInstance->FindChan(targetstr); User* targetuser = ServerInstance->FindNick(targetstr); target = targetuser ? (Extensible*)targetuser : targetchannel; ModeType type = targetchannel ? MODETYPE_CHANNEL : MODETYPE_USER; if (!targetchannel && !targetuser) { user->WriteNumeric(ERR_NOSUCHNICK, "%s %s :No such nick/channel",user->nick.c_str(),targetstr.c_str()); return; } if (parameters.size() == 1) { this->DisplayCurrentModes(user, targetuser, targetchannel, targetstr.c_str()); return; } if (targetchannel && parameters.size() == 2) { /* Special case for displaying the list for listmodes, * e.g. MODE #chan b, or MODE #chan +b without a parameter */ this->DisplayListModes(user, targetchannel, parameters[1]); } unsigned int param_at = 2; bool adding = true; bool listednamedmodes = false; for (std::string::const_iterator letter = parameters[1].begin(); letter != parameters[1].end(); letter++) { unsigned char modechar = *letter; // skip s2s mode-merge indicator if (modechar == '=') continue; if (modechar == '+' || modechar == '-') { adding = (modechar == '+'); continue; } if (modechar == 'Z' && ServerInstance->Config->NameOnlyModes) { // special case: Z is the named mode change interface std::string name, value; if (param_at == parameters.size()) { /* Ensure the user doesnt request the same mode twice, * so they cant flood themselves off out of idiocy. */ if (listednamedmodes) continue; listednamedmodes = true; for(ModeIDIter id; id; id++) { ModeHandler* mh = ServerInstance->Modes->FindMode(id); if (!mh || mh->IsListMode() || mh->GetModeType() != type) continue; if (targetchannel && !targetchannel->IsModeSet(mh)) continue; if (targetuser && !targetuser->IsModeSet(mh->GetModeChar())) continue; if (mh->GetNumParams(true)) user->WriteNumeric(963, "%s %s %s=%s", user->nick.c_str(), targetchannel->name.c_str(), mh->name.c_str(), targetchannel->GetModeParameter(mh).c_str()); else { if(targetchannel) user->WriteNumeric(963, "%s %s %s", user->nick.c_str(), targetchannel->name.c_str(), mh->name.c_str()); else user->WriteNumeric(963, "%s %s %s", user->nick.c_str(), targetuser->nick.c_str(), mh->name.c_str()); } } if(targetchannel) user->WriteNumeric(962, "%s :End of Mode List", targetchannel->name.c_str()); else user->WriteNumeric(962, "%s :End of Mode List", targetuser->nick.c_str()); continue; } name = parameters[param_at++]; std::string::size_type eq = name.find('='); if (eq != std::string::npos) { value = name.substr(eq + 1); name = name.substr(0, eq); } ModeHandler *mh = FindMode(name); if (mh && mh->GetModeType() == type) { if (mh->GetNumParams(adding) && value.empty()) { if (mh->IsListMode() && targetchannel && adding) { /* Special case for displaying the list for listmodes, * e.g. MODE #chan +Z ban, or MODE #chan +Z ban= */ mh->DisplayList(user, targetchannel); continue; } /* No parameter, continue to the next mode unless OnParameterMissing fills one in */ mh->OnParameterMissing(user, targetuser, targetchannel, value); if (value.empty()) continue; } modes.push(irc::modechange(mh->id, value, adding)); } else { user->WriteServ("%d %s %s :is unknown mode string to me", type == MODETYPE_CHANNEL ? 472 : 501, user->nick.c_str(), name.c_str()); } continue; } ModeHandler *mh = this->FindMode(modechar, type); if (!mh) { /* No mode handler? Unknown mode character then. */ user->WriteServ("%d %s %c :is unknown mode char to me", type == MODETYPE_CHANNEL ? 472 : 501, user->nick.c_str(), modechar); continue; } std::string parameter = ""; int pcnt = mh->GetNumParams(adding); if (pcnt && param_at == parameters.size()) { /* No parameter, continue to the next mode unless OnParameterMissing fills one in */ mh->OnParameterMissing(user, targetuser, targetchannel, parameter); if (parameter.empty()) continue; } else if (pcnt) { parameter = parameters[param_at++]; } modes.push(irc::modechange(mh->id, parameter, adding)); } } void ModeParser::Send(User *src, Extensible* target, irc::modestacker modes) { Channel* targetchannel = IS_CHANNEL(target); User* targetuser = IS_USER(target); if (targetchannel) { while (!modes.empty()) targetchannel->WriteChannel(src, "MODE %s %s", targetchannel->name.c_str(), modes.popModeLine(FORMAT_USER).c_str()); } else if (targetuser) { while (!modes.empty()) targetuser->WriteFrom(src, "MODE %s %s", targetuser->nick.c_str(), modes.popModeLine(FORMAT_USER).c_str()); } } void ModeParser::Process(User *src, Extensible* target, irc::modestacker& modes, bool merge, bool SkipAccessChecks) { Channel* targetchannel = IS_CHANNEL(target); User* targetuser = IS_USER(target); if (!targetuser && !targetchannel) return; ModResult MOD_RESULT; FIRST_MOD_RESULT(OnPreMode, MOD_RESULT, (src, target, modes)); if (!IS_LOCAL(src) || MOD_RESULT == MOD_RES_ALLOW) SkipAccessChecks = true; else if (MOD_RESULT == MOD_RES_DENY) return; if (targetuser && src != targetuser && !SkipAccessChecks) { src->WriteNumeric(ERR_USERSDONTMATCH, "%s :Can't change mode for other users", src->nick.c_str()); return; } std::vector::iterator mc = modes.sequence.begin(); while (mc != modes.sequence.end()) { ModeHandler* mh = FindMode(mc->mode); if (!mh) throw CoreException("Invalid mode sequence passed to ModeParser::Process!"); if (mc->value.c_str()[0] == ':' || mc->value.find(' ') != std::string::npos) { // you can't do that in a mode value, sorry mc = modes.sequence.erase(mc); continue; } if (merge && targetchannel && targetchannel->IsModeSet(mh) && mc->adding && !mh->IsListMode()) { std::string ours = targetchannel->GetModeParameter(mh); if (mc->value == ours || !mh->ParamOrder(mc->value, ours)) { /* local side won the mode merge, don't apply this mode */ mc = modes.sequence.erase(mc); continue; } } ModeAction ma = TryMode(src, targetuser, targetchannel, *mc, SkipAccessChecks); if (ma == MODEACTION_ALLOW) { mc++; } else { /* mode change was denied */ mc = modes.sequence.erase(mc); continue; } } FOREACH_MOD(I_OnMode, OnMode(src, target, modes)); } void ModeParser::DisplayListModes(User* user, Channel* chan, const std::string &mode_sequence) { std::bitset sent; for (std::string::const_iterator letter = mode_sequence.begin(); letter != mode_sequence.end(); letter++) { unsigned char mletter = *letter; if (mletter == '+') continue; ModeHandler *mh = this->FindMode(mletter, MODETYPE_CHANNEL); if (!mh || !mh->IsListMode()) return; /* Ensure the user doesnt request the same mode twice, * so they cant flood themselves off out of idiocy. */ if (sent[mh->id.GetID()]) continue; sent[mh->id.GetID()] = true; irc::modechange mc(mh->id); ModResult MOD_RESULT; FIRST_MOD_RESULT(OnRawMode, MOD_RESULT, (user, chan, mc)); if (MOD_RESULT == MOD_RES_DENY) continue; bool display = true; if (!user->HasPrivPermission("channels/auspex") && ServerInstance->Config->HideModeLists[mletter] && (chan->GetAccessRank(user) < HALFOP_VALUE)) { user->WriteNumeric(ERR_CHANOPRIVSNEEDED, "%s %s :You do not have access to view the +%c list", user->nick.c_str(), chan->name.c_str(), mletter); display = false; } std::pair watchers = modewatchers.equal_range(mh->id); for(ModeWatcherMap::iterator watcher = watchers.first; watcher != watchers.second; watcher++) { std::string dummyparam; if (!watcher->second->BeforeMode(user, NULL, chan, dummyparam, true, MODETYPE_CHANNEL)) display = false; } if (display) mh->DisplayList(user, chan); else mh->DisplayEmptyList(user, chan); } } void ModeParser::CleanMask(std::string &mask) { std::string::size_type pos_of_pling = mask.find_first_of('!'); std::string::size_type pos_of_at = mask.find_first_of('@'); std::string::size_type pos_of_dot = mask.find_first_of('.'); std::string::size_type pos_of_colons = mask.find("::"); /* Because ipv6 addresses are colon delimited -- double so it treats extban as nick */ if (mask.length() >= 2 && mask[1] == ':') return; // if it's an extban, don't even try guess how it needs to be formed. if ((pos_of_pling == std::string::npos) && (pos_of_at == std::string::npos)) { /* Just a nick, or just a host - or clearly ipv6 (starting with :) */ if ((pos_of_dot == std::string::npos) && (pos_of_colons == std::string::npos) && mask[0] != ':') { /* It has no '.' in it, it must be a nick. */ mask.append("!*@*"); } else { /* Got a dot in it? Has to be a host */ mask = "*!*@" + mask; } } else if ((pos_of_pling == std::string::npos) && (pos_of_at != std::string::npos)) { /* Has an @ but no !, its a user@host */ mask = "*!" + mask; } else if ((pos_of_pling != std::string::npos) && (pos_of_at == std::string::npos)) { /* Has a ! but no @, it must be a nick!ident */ mask.append("@*"); } } void ModeParser::AddMode(ModeHandler* mh) { /* A mode prefix of ',' is not acceptable, it would fuck up server to server. * A mode prefix of ':' will fuck up both server to server, and client to server. * A mode prefix of '#' will mess up /whois and /privmsg */ if ((mh->GetPrefix() == ',') || (mh->GetPrefix() == ':') || (mh->GetPrefix() == '#')) throw ModuleException("Invalid prefix defined in mode"); std::string myletter = ServerInstance->Config->GetTag("modeletters")->getString(mh->name, std::string(1,mh->GetModeChar())); mh->AdjustModeChar(myletter.c_str()[0]); if (FindMode(mh->name)) throw ModuleException("Duplicate mode name found"); // prefixes can't be duplicated if (mh->GetPrefix() && FindPrefix(mh->GetPrefix())) throw ModuleException("Duplicate channel prefix found"); char mc = mh->GetModeChar(); // user modes have to be alpha (can't be null either) if (mh->GetModeType() == MODETYPE_USER && !isalpha(mc)) throw ModuleException("Invalid mode character"); // all modes have to be alphanumeric or null if (mc && !isalnum(mc)) throw ModuleException("Invalid mode character"); if (mh->GetPrefixRank() && !mc) throw ModuleException("Prefix modes must define a mode character"); if (mc && FindMode(mc, mh->GetModeType())) throw ModuleException("Duplicate mode character '" + std::string(1, mc) + "' found"); if (!mc && !ServerInstance->Config->NameOnlyModes) throw ModuleException(" must be enabled to clear a mode character"); // find a free ID, and add it for(int id = 1; id < MODE_ID_MAX; id++) { if (handlers[id]) continue; mh->id.SetID(id); handlers[id] = mh; return; } // whoops, you need to increase MODE_ID_MAX throw ModuleException("Out of Mode IDs!"); } bool ModeParser::DelMode(ModeHandler* mh) { if (handlers[mh->id.GetID()] != mh) return false; /* Note: We can't stack here, as we have modes potentially being removed across many different channels. * To stack here we have to make the algorithm slower. Discuss. */ switch (mh->GetModeType()) { case MODETYPE_USER: for (user_hash::iterator i = ServerInstance->Users->clientlist->begin(); i != ServerInstance->Users->clientlist->end();) { mh->RemoveMode(i++->second); } break; case MODETYPE_CHANNEL: for (chan_hash::iterator i = ServerInstance->chanlist->begin(); i != ServerInstance->chanlist->end();) { mh->RemoveMode(i++->second); } break; } handlers[mh->id.GetID()] = NULL; mh->id.SetID(0); return true; } ModeHandler* ModeParser::FindMode(const std::string& name) { for(int id = 1; id < MODE_ID_MAX; id++) { if (handlers[id] && handlers[id]->name == name) return handlers[id]; } return NULL; } ModeHandler* ModeParser::FindMode(unsigned const char modeletter, ModeType mt) { for(int id = 1; id < MODE_ID_MAX; id++) { if (handlers[id] && handlers[id]->GetModeChar() == modeletter && handlers[id]->GetModeType() == mt) return handlers[id]; } return NULL; } std::string ModeParser::UserModeList() { char modestr[256]; int pointer = 0; for(ModeIDIter id; id; id++) { ModeHandler* mh = FindMode(id); if(mh && mh->GetModeType() == MODETYPE_USER && mh->GetModeChar()) modestr[pointer++] = mh->GetModeChar(); } std::sort(modestr, modestr + pointer); modestr[pointer++] = 0; return modestr; } std::string ModeParser::ChannelModeList() { char modestr[256]; int pointer = 0; for(ModeIDIter id; id; id++) { ModeHandler* mh = FindMode(id); if (mh && mh->GetModeType() == MODETYPE_CHANNEL && mh->GetModeChar()) modestr[pointer++] = mh->GetModeChar(); } if (ServerInstance->Config->NameOnlyModes) modestr[pointer++] = 'Z'; modestr[pointer] = 0; std::sort(modestr, modestr + pointer); return modestr; } std::string ModeParser::ParaModeList() { char modestr[256]; int pointer = 0; for(ModeIDIter id; id; id++) { ModeHandler* mh = FindMode(id); if (mh && mh->GetModeType() == MODETYPE_CHANNEL && mh->GetNumParams(true) && mh->GetModeChar()) modestr[pointer++] = mh->GetModeChar(); } if (ServerInstance->Config->NameOnlyModes) modestr[pointer++] = 'Z'; modestr[pointer] = 0; std::sort(modestr, modestr + pointer); return modestr; } ModeHandler* ModeParser::FindPrefix(unsigned const char pfxletter) { for(ModeIDIter id; id; id++) { ModeHandler* mh = FindMode(id); if (mh && mh->GetPrefix() == pfxletter) return mh; } return NULL; } std::string ModeParser::GiveModeList(ModeType m) { std::string type1; /* Listmodes EXCEPT those with a prefix */ std::string type2; /* Modes that take a param when adding or removing */ std::string type3; /* Modes that only take a param when adding */ std::string type4; /* Modes that dont take a param */ if (m == MODETYPE_CHANNEL && ServerInstance->Config->NameOnlyModes) type1.push_back('Z'); for(ModeIDIter id; id; id++) { ModeHandler* mh = FindMode(id); if (mh && mh->GetModeType() == m && mh->GetModeChar()) { if (mh->GetNumParams(true)) { if ((mh->IsListMode()) && (!mh->GetPrefix())) { type1 += mh->GetModeChar(); } else { /* ... and one parameter when removing */ if (mh->GetNumParams(false)) { /* But not a list mode */ if (!mh->GetPrefix()) { type2 += mh->GetModeChar(); } } else { /* No parameters when removing */ type3 += mh->GetModeChar(); } } } else { type4 += mh->GetModeChar(); } } } std::sort(type1.begin(), type1.end()); std::sort(type2.begin(), type2.end()); std::sort(type3.begin(), type3.end()); std::sort(type4.begin(), type4.end()); return type1 + "," + type2 + "," + type3 + "," + type4; } std::string ModeParser::BuildPrefixes(bool lettersAndModes) { std::string mletters; std::string mprefixes; std::map > prefixes; for(ModeIDIter id; id; id++) { ModeHandler* mh = FindMode(id); if (mh && mh->GetPrefix()) { prefixes[mh->GetPrefixRank()] = std::make_pair(mh->GetPrefix(), mh->GetModeChar()); } } for(std::map >::reverse_iterator n = prefixes.rbegin(); n != prefixes.rend(); n++) { mletters = mletters + n->second.first; mprefixes = mprefixes + n->second.second; } return lettersAndModes ? "(" + mprefixes + ")" + mletters : mletters; } bool ModeParser::AddModeWatcher(ModeWatcher* mw) { if (!mw) return false; ModeHandler* mh = FindMode(mw->GetModeChar(), mw->GetModeType()); if (!mh) return false; modewatchers.insert(std::make_pair(mh->id,mw)); return true; } bool ModeParser::DelModeWatcher(ModeWatcher* mw) { if (!mw) return false; ModeHandler* mh = FindMode(mw->GetModeChar(), mw->GetModeType()); if (!mh) return false; std::pair watchers = modewatchers.equal_range(mh->id); while (watchers.first != watchers.second) { if (watchers.first->second == mw) { modewatchers.erase(watchers.first); return true; } watchers.first++; } return false; } /** This default implementation can remove simple user modes */ void ModeHandler::RemoveMode(User* user, irc::modestacker* stack) { if (user->IsModeSet(this->GetModeChar())) { irc::modechange mc(id, "", false); if (stack) { stack->push(mc); } else { irc::modestacker tmp; tmp.push(mc); ServerInstance->SendMode(ServerInstance->FakeClient, user, tmp, false); } } } /** This default implementation can remove non-list modes */ void ModeHandler::RemoveMode(Channel* channel, irc::modestacker* stack) { if (channel->IsModeSet(this)) { irc::modechange mc(id, channel->GetModeParameter(this), false); if (stack) { stack->push(mc); } else { irc::modestacker ms; ms.push(mc); ServerInstance->SendMode(ServerInstance->FakeClient, channel, ms, false); } } } void PrefixModeHandler::RemoveMode(Channel* channel, irc::modestacker* stack) { irc::modestacker loc_ms; if (!stack) stack = &loc_ms; for (UserMembIter i = channel->userlist.begin(); i != channel->userlist.end(); i++) { Membership* memb = i->second; if (memb->hasMode(mode)) stack->push(irc::modechange(id, i->first->nick, false)); } if (stack == &loc_ms) ServerInstance->SendMode(ServerInstance->FakeClient, channel, loc_ms, false); } void PrefixModeHandler::RemoveMode(User*, irc::modestacker*) { } void ModeHandler::PopulateChanModes(Channel* channel, irc::modestacker& stack) { irc::modechange mc(id, channel->GetModeParameter(this), true); if (channel->IsModeSet(this)) stack.push(mc); } void ListExtItem::free(void* item) { modelist* ml = static_cast(item); delete ml; } void ListModeBase::DisplayList(User* user, Channel* channel) { modelist* el = extItem.get(channel); if (el) { for (modelist::reverse_iterator it = el->rbegin(); it != el->rend(); ++it) { user->WriteNumeric(listnumeric, "%s %s %s %s %ld", user->nick.c_str(), channel->name.c_str(), it->mask.c_str(), it->setter.c_str(), (long)it->time); } } user->WriteNumeric(endoflistnumeric, "%s %s :%s", user->nick.c_str(), channel->name.c_str(), endofliststring.c_str()); } const modelist* ListModeBase::GetList(Channel* channel) { return extItem.get(channel); } void ListModeBase::PopulateChanModes(Channel* channel, irc::modestacker& stack) { modelist* ml = extItem.get(channel); if (!ml) return; for(modelist::const_iterator i = ml->begin(); i != ml->end(); i++) { stack.push(irc::modechange(id, i->mask, true)); } } void ListModeBase::RemoveMode(Channel* channel, irc::modestacker* stack) { modelist* el = extItem.get(channel); if (el) { irc::modestacker modestack; for (modelist::iterator it = el->begin(); it != el->end(); it++) { if (stack) stack->push(irc::modechange(id, it->mask, false)); else modestack.push(irc::modechange(id, it->mask, false)); } if (stack) return; ServerInstance->SendMode(ServerInstance->FakeClient, channel, modestack, false); } } void ListModeBase::RemoveMode(User*, irc::modestacker* stack) { // listmodes don't get set on users } void ListModeBase::DoRehash() { ConfigTagList tags = ServerInstance->Config->GetTags(configtag); chanlimits.clear(); for (ConfigIter i = tags.first; i != tags.second; i++) { // For each tag ConfigTag* c = i->second; ListLimit limit; limit.mask = c->getString("chan"); limit.limit = c->getInt("limit"); if (limit.mask.size() && limit.limit > 0) chanlimits.push_back(limit); } if (chanlimits.size() == 0) { ListLimit limit; limit.mask = "*"; limit.limit = 64; chanlimits.push_back(limit); } } ModeAction ListModeBase::OnModeChange(User* source, User*, Channel* channel, std::string ¶meter, bool adding) { // Try and grab the list modelist* el = extItem.get(channel); if (adding) { // If there was no list, make one if (!el) el = extItem.make(channel); // Clean the mask up if (this->tidy) ModeParser::CleanMask(parameter); // Check if the item already exists in the list for (modelist::iterator it = el->begin(); it != el->end(); it++) { if (parameter == it->mask) { /* Give a subclass a chance to error about this */ TellAlreadyOnList(source, channel, parameter); // it does, deny the change return MODEACTION_DENY; } } unsigned int maxsize = 0; for (limitlist::iterator it = chanlimits.begin(); it != chanlimits.end(); it++) { if (InspIRCd::Match(channel->name, it->mask)) { // We have a pattern matching the channel... maxsize = el->size(); if (!IS_LOCAL(source) || (maxsize < it->limit)) { /* Ok, it *could* be allowed, now give someone subclassing us * a chance to validate the parameter. * The param is passed by reference, so they can both modify it * and tell us if we allow it or not. * * eg, the subclass could: * 1) allow * 2) 'fix' parameter and then allow * 3) deny */ if (ValidateParam(source, channel, parameter)) { // And now add the mask onto the list... BanItem e; e.mask = parameter; e.setter = source->nick; e.time = ServerInstance->Time(); el->push_back(e); return MODEACTION_ALLOW; } else { /* If they deny it they have the job of giving an error message */ return MODEACTION_DENY; } } } } /* List is full, give subclass a chance to send a custom message */ if (!TellListTooLong(source, channel, parameter)) { source->WriteNumeric(478, "%s %s %s :Channel ban/ignore list is full", source->nick.c_str(), channel->name.c_str(), parameter.c_str()); } parameter = ""; return MODEACTION_DENY; } else { // We're taking the mode off if (el) { for (modelist::iterator it = el->begin(); it != el->end(); it++) { if (parameter == it->mask) { el->erase(it); if (el->size() == 0) { extItem.unset(channel); } return MODEACTION_ALLOW; } } /* Tried to remove something that wasn't set */ TellNotSet(source, channel, parameter); parameter = ""; return MODEACTION_DENY; } else { /* Hmm, taking an exception off a non-existant list, DIE */ TellNotSet(source, channel, parameter); parameter = ""; return MODEACTION_DENY; } } return MODEACTION_DENY; } struct builtin_modes { ModeChannelSecret s; ModeChannelPrivate p; ModeChannelModerated m; ModeChannelTopicOps t; ModeChannelNoExternal n; ModeChannelInviteOnly i; ModeChannelKey k; ModeChannelLimit l; ModeChannelBan b; ModeChannelOp o; ModeChannelVoice v; ModeUserWallops uw; ModeUserInvisible ui; ModeUserOperator uo; ModeUserServerNoticeMask us; void init(ModeParser* modes) { b.init(); modes->AddMode(&s); modes->AddMode(&p); modes->AddMode(&m); modes->AddMode(&t); modes->AddMode(&n); modes->AddMode(&i); modes->AddMode(&k); modes->AddMode(&l); modes->AddMode(&b); modes->AddMode(&o); modes->AddMode(&v); modes->AddMode(&uw); modes->AddMode(&ui); modes->AddMode(&uo); modes->AddMode(&us); } }; static builtin_modes static_modes; ModeParser::ModeParser() { /* Clear mode handler list */ memset(handlers, 0, sizeof(handlers)); static_modes.init(this); } ModeParser::~ModeParser() { }