ModResult ProcessMessages(User* user,Channel* dest, const std::string &text) { if ((!IS_LOCAL(user)) || !dest->IsModeSet(mf)) return MOD_RES_PASSTHRU; if (ServerInstance->OnCheckExemption(user,dest,"flood") == MOD_RES_ALLOW) return MOD_RES_PASSTHRU; floodsettings *f = mf.ext.get(dest); if (f) { if (f->addmessage(user)) { /* Youre outttta here! */ f->clear(user); if (f->ban) { std::vector<std::string> parameters; parameters.push_back(dest->name); parameters.push_back("+b"); parameters.push_back("*!*@" + user->dhost); ServerInstance->Modes->Process(parameters, ServerInstance->FakeClient); } const std::string kickMessage = "Channel flood triggered (limit is " + ConvToStr(f->lines) + " in " + ConvToStr(f->secs) + " secs)"; dest->KickUser(ServerInstance->FakeClient, user, kickMessage); return MOD_RES_DENY; } } return MOD_RES_PASSTHRU; }
void SendHeaders(unsigned long size, int response, HTTPHeaders &rheaders) { WriteData(http_version + " "+ConvToStr(response)+" "+Response(response)+"\r\n"); time_t local = ServerInstance->Time(); struct tm *timeinfo = gmtime(&local); char *date = asctime(timeinfo); date[strlen(date) - 1] = '\0'; rheaders.CreateHeader("Date", date); rheaders.CreateHeader("Server", BRANCH); rheaders.SetHeader("Content-Length", ConvToStr(size)); if (size) rheaders.CreateHeader("Content-Type", "text/html"); else rheaders.RemoveHeader("Content-Type"); /* Supporting Connection: keep-alive causes a whole world of hurt syncronizing timeouts, * so remove it, its not essential for what we need. */ rheaders.SetHeader("Connection", "Close"); WriteData(rheaders.GetFormattedHeaders()); WriteData("\r\n"); }
ModResult OnStats(Stats::Context& stats) override { if (stats.GetSymbol() != 'G') return MOD_RES_PASSTHRU; unsigned int unknown = 0; std::map<std::string, unsigned int> results; const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers(); for (UserManager::LocalList::const_iterator i = list.begin(); i != list.end(); ++i) { std::string* cc = ext.get(*i); if (cc) results[*cc]++; else unknown++; } for (std::map<std::string, unsigned int>::const_iterator i = results.begin(); i != results.end(); ++i) { stats.AddRow(801, "GeoIPSTATS " + i->first + " " + ConvToStr(i->second)); } if (unknown) stats.AddRow(801, "GeoIPSTATS Unknown " + ConvToStr(unknown)); return MOD_RES_DENY; }
virtual ModResult OnSetAway(User *user, const std::string &awaymsg) { std::string numeric; int inum; if (awaymsg.empty()) { numeric = user->nick + " " + user->ident + " " + user->dhost + " " + ConvToStr(ServerInstance->Time()) + " :is no longer away"; inum = 599; } else { numeric = user->nick + " " + user->ident + " " + user->dhost + " " + ConvToStr(ServerInstance->Time()) + " :" + awaymsg; inum = 598; } watchentries::iterator x = whos_watching_me->find(user->nick.c_str()); if (x != whos_watching_me->end()) { for (std::deque<User*>::iterator n = x->second.begin(); n != x->second.end(); n++) { (*n)->WriteNumeric(inum, numeric); } } return MOD_RES_PASSTHRU; }
void ModuleSpanningTree::OnUserJoin(userrec* user, chanrec* channel, bool &silent) { // Only do this for local users if (IS_LOCAL(user)) { if (channel->GetUserCounter() == 1) { std::deque<std::string> params; // set up their permissions and the channel TS with FJOIN. // All users are FJOINed now, because a module may specify // new joining permissions for the user. params.push_back(channel->name); params.push_back(ConvToStr(channel->age)); params.push_back(std::string(channel->GetAllPrefixChars(user))+","+std::string(user->nick)); Utils->DoOneToMany(ServerInstance->Config->ServerName,"FJOIN",params); /* First user in, sync the modes for the channel */ params.pop_back(); params.push_back(channel->ChanModes(true)); Utils->DoOneToMany(ServerInstance->Config->ServerName,"FMODE",params); } else { std::deque<std::string> params; params.push_back(channel->name); params.push_back(ConvToStr(channel->age)); Utils->DoOneToMany(user->nick,"JOIN",params); } } }
CmdResult Handle (const std::vector<std::string> ¶meters, User *src) { std::map<std::string,int> closed; for (LocalUserList::const_iterator u = ServerInstance->Users->local_users.begin(); u != ServerInstance->Users->local_users.end(); ++u) { LocalUser* user = *u; if (user->registered != REG_ALL) { ServerInstance->Users->QuitUser(user, "Closing all unknown connections per request"); std::string key = ConvToStr(user->GetIPString())+"."+ConvToStr(user->GetServerPort()); closed[key]++; } } int total = 0; for (std::map<std::string,int>::iterator ci = closed.begin(); ci != closed.end(); ci++) { src->WriteNotice("*** Closed " + ConvToStr(ci->second) + " unknown " + (ci->second == 1 ? "connection" : "connections") + " from [" + ci->first + "]"); total += ci->second; } if (total) src->WriteNotice("*** " + ConvToStr(total) + " unknown " + (total == 1 ? "connection" : "connections") + " closed"); else src->WriteNotice("*** No unknown connections found"); return CMD_SUCCESS; }
ModResult OnStats(char symbol, User* user, string_list &out) { if (symbol != 'G') return MOD_RES_PASSTHRU; unsigned int unknown = 0; std::map<std::string, unsigned int> results; for (LocalUserList::const_iterator i = ServerInstance->Users->local_users.begin(); i != ServerInstance->Users->local_users.end(); ++i) { std::string* cc = ext.get(*i); if (cc) results[*cc]++; else unknown++; } std::string p = ServerInstance->Config->ServerName + " 801 " + user->nick + " :GeoIPSTATS "; for (std::map<std::string, unsigned int>::const_iterator i = results.begin(); i != results.end(); ++i) { out.push_back(p + i->first + " " + ConvToStr(i->second)); } if (unknown) out.push_back(p + "Unknown " + ConvToStr(unknown)); return MOD_RES_DENY; }
void ModuleSpanningTree::OnUserJoin(Membership* memb, bool sync, bool created_by_local, CUList& excepts) { // Only do this for local users if (!IS_LOCAL(memb->user)) return; if (created_by_local) { CmdBuilder params("FJOIN"); params.push_back(memb->chan->name); params.push_back(ConvToStr(memb->chan->age)); params.push_raw(" +").push_raw(memb->chan->ChanModes(true)); params.push(memb->modes).push_raw(',').push_raw(memb->user->uuid); params.Broadcast(); } else { CmdBuilder params(memb->user, "IJOIN"); params.push_back(memb->chan->name); if (!memb->modes.empty()) { params.push_back(ConvToStr(memb->chan->age)); params.push_back(memb->modes); } params.Broadcast(); } }
void XLineManager::InvokeStats(const std::string &type, int numeric, User* user, string_list &results) { ContainerIter n = lookup_lines.find(type); time_t current = ServerInstance->Time(); LookupIter safei; if (n != lookup_lines.end()) { XLineLookup& list = n->second; for (LookupIter i = list.begin(); i != list.end(); ) { safei = i; safei++; if (i->second->duration && current > i->second->expiry) { ExpireLine(n, i); } else results.push_back(ServerInstance->Config->ServerName+" "+ConvToStr(numeric)+" "+user->nick+" :"+i->second->Displayable()+" "+ ConvToStr(i->second->set_time)+" "+ConvToStr(i->second->duration)+" "+i->second->source+" :"+i->second->reason); i = safei; } } }
ModePair ModeSet(userrec* source, userrec* dest, chanrec* channel, const std::string ¶meter) { floodsettings* x; if (channel->GetExt("flood",x)) return std::make_pair(true, (x->ban ? "*" : "")+ConvToStr(x->lines)+":"+ConvToStr(x->secs)); else return std::make_pair(false, parameter); }
std::string CommandWhowas::GetStats() { int whowas_size = 0; int whowas_bytes = 0; for (whowas_users::iterator i = whowas.begin(); i != whowas.end(); ++i) { whowas_set* n = i->second; whowas_size += n->size(); whowas_bytes += (sizeof(whowas_set) + ( sizeof(WhoWasGroup) * n->size() ) ); } return "Whowas entries: " +ConvToStr(whowas_size)+" ("+ConvToStr(whowas_bytes)+" bytes)"; }
void SpanningTreeProtocolInterface::SendTopic(Channel* channel, std::string &topic) { parameterlist params; params.push_back(channel->name); params.push_back(ConvToStr(channel->age)); params.push_back(ConvToStr(ServerInstance->Time())); params.push_back(ServerInstance->Config->ServerName); params.push_back(":" + topic); Utils->DoOneToMany(ServerInstance->Config->GetSID(),"FTOPIC", params); }
std::string ModuleSpanningTree::TimeToStr(time_t secs) { time_t mins_up = secs / 60; time_t hours_up = mins_up / 60; time_t days_up = hours_up / 24; secs = secs % 60; mins_up = mins_up % 60; hours_up = hours_up % 24; return ((days_up ? (ConvToStr(days_up) + "d") : "") + (hours_up ? (ConvToStr(hours_up) + "h") : "") + (mins_up ? (ConvToStr(mins_up) + "m") : "") + ConvToStr(secs) + "s"); }
void DoCommand(std::string newline, userrec* user, chanrec *c,const std::string &original_line) { for (int v = 1; v < 10; v++) { std::string var = "$"; var.append(ConvToStr(v)); var.append("-"); std::string::size_type x = newline.find(var); while (x != std::string::npos) { newline.erase(x, var.length()); newline.insert(x, GetVar(var, original_line)); x = newline.find(var); } var = "$"; var.append(ConvToStr(v)); x = newline.find(var); while (x != std::string::npos) { newline.erase(x, var.length()); newline.insert(x, GetVar(var, original_line)); x = newline.find(var); } } /* Special variables */ SearchAndReplace(newline, "$nick", user->nick); SearchAndReplace(newline, "$ident", user->ident); SearchAndReplace(newline, "$host", user->host); SearchAndReplace(newline, "$vhost", user->dhost); SearchAndReplace(newline, "$chan", c->name); /* Unescape any variable names in the user text before sending */ SearchAndReplace(newline, "\r", "$"); irc::tokenstream ss(newline); const char* parv[127]; int x = 0; while (ss.GetToken(pars[x])) { parv[x] = pars[x].c_str(); x++; } ServerInstance->Parser->CallHandler(parv[0], &parv[1], x-1, user); }
void ModuleSpanningTree::OnUserJoin(Membership* memb, bool sync, bool created_by_local, CUList& excepts) { // Only do this for local users if (!IS_LOCAL(memb->user)) return; // Assign the current membership id to the new Membership and increase it memb->id = currmembid++; if (created_by_local) { CommandFJoin::Builder params(memb->chan); params.add(memb); params.finalize(); params.Broadcast(); } else { CmdBuilder params(memb->user, "IJOIN"); params.push_back(memb->chan->name); params.push_int(memb->id); if (!memb->modes.empty()) { params.push_back(ConvToStr(memb->chan->age)); params.push_back(memb->modes); } params.Broadcast(); } }
CmdResult CommandIJoin::HandleRemote(RemoteUser* user, std::vector<std::string>& params) { Channel* chan = ServerInstance->FindChan(params[0]); if (!chan) { // Desync detected, recover // Ignore the join and send RESYNC, this will result in the remote server sending all channel data to us ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Received IJOIN for non-existant channel: " + params[0]); CmdBuilder("RESYNC").push(params[0]).Unicast(user); return CMD_FAILURE; } bool apply_modes; if (params.size() > 2) { time_t RemoteTS = ServerCommand::ExtractTS(params[2]); if (RemoteTS < chan->age) throw ProtocolException("Attempted to lower TS via IJOIN. LocalTS=" + ConvToStr(chan->age)); apply_modes = ((params.size() > 3) && (RemoteTS == chan->age)); } else apply_modes = false; // Join the user and set the membership id to what they sent Membership* memb = chan->ForceJoin(user, apply_modes ? ¶ms[3] : NULL); if (!memb) return CMD_FAILURE; memb->id = Membership::IdFromString(params[1]); return CMD_SUCCESS; }
std::string Sanitize(const std::string &str) { std::string ret; for (std::string::const_iterator x = str.begin(); x != str.end(); ++x) { switch (*x) { case '<': ret += "<"; break; case '>': ret += ">"; break; case '&': ret += "&"; break; case '"': ret += """; break; default: if (*x < 32 || *x > 126) { int n = *x; ret += ("&#" + ConvToStr(n) + ";"); } else ret += *x; break; } } return ret; }
CmdResult Handle (const std::vector<std::string> ¶meters, User *user) { std::string clonesstr = "304 " + std::string(user->nick) + " :CLONES"; unsigned long limit = atoi(parameters[0].c_str()); /* * Syntax of a /clones reply: * :server.name 304 target :CLONES START * :server.name 304 target :CLONES <count> <ip> * :server.name 304 target :CHECK END */ user->WriteServ(clonesstr + " START"); /* hostname or other */ // XXX I really don't like marking global_clones public for this. at all. -- w00t for (clonemap::iterator x = ServerInstance->Users->global_clones.begin(); x != ServerInstance->Users->global_clones.end(); x++) { if (x->second >= limit) user->WriteServ(clonesstr + " "+ ConvToStr(x->second) + " " + assign(x->first)); } user->WriteServ(clonesstr + " END"); return CMD_LOCALONLY; }
CmdResult Handle (const std::vector<std::string> ¶meters, User *user) { std::string clonesstr = "CLONES "; unsigned long limit = atoi(parameters[0].c_str()); /* * Syntax of a /clones reply: * :server.name 304 target :CLONES START * :server.name 304 target :CLONES <count> <ip> * :server.name 304 target :CLONES END */ user->WriteNumeric(304, clonesstr + "START"); /* hostname or other */ const UserManager::CloneMap& clonemap = ServerInstance->Users->GetCloneMap(); for (UserManager::CloneMap::const_iterator i = clonemap.begin(); i != clonemap.end(); ++i) { const UserManager::CloneCounts& counts = i->second; if (counts.global >= limit) user->WriteNumeric(304, clonesstr + ConvToStr(counts.global) + " " + i->first.str()); } user->WriteNumeric(304, clonesstr + "END"); return CMD_SUCCESS; }
void ModuleSpanningTree::OnMode(User* user, void* dest, int target_type, const std::deque<std::string> &text, const std::deque<TranslateType> &translate) { if ((IS_LOCAL(user)) && (user->registered == REG_ALL)) { std::deque<std::string> params; std::string command; std::string output_text; ServerInstance->Parser->TranslateUIDs(translate, text, output_text); if (target_type == TYPE_USER) { User* u = (User*)dest; params.push_back(u->uuid); params.push_back(output_text); command = "MODE"; } else { Channel* c = (Channel*)dest; params.push_back(c->name); params.push_back(ConvToStr(c->age)); params.push_back(output_text); command = "FMODE"; } Utils->DoOneToMany(user->uuid, command, params); } }
ModResult OnStats(char symbol, User* user, string_list &results) { if (symbol == 'z') results.push_back("249 "+user->nick+" :Whowas entries: "+ConvToStr(cmd.manager.GetStats().entrycount)); return MOD_RES_PASSTHRU; }
void Snomask::Flush() { if (Count > 1) { std::string desc = this->Description; std::string mesg = "(last message repeated "+ConvToStr(Count)+" times)"; char mysnomask = MySnomask; ServerInstance->Logs->Log("snomask", DEFAULT, "%s: %s", desc.c_str(), mesg.c_str()); FOREACH_MOD(I_OnSendSnotice, OnSendSnotice(mysnomask, desc, mesg)); if (!LastBlocked) { /* Only opers can receive snotices, so we iterate the oper list */ std::list<User*>::iterator i = ServerInstance->Users->all_opers.begin(); while (i != ServerInstance->Users->all_opers.end()) { User* a = *i; if (IS_LOCAL(a) && a->IsModeSet('s') && a->IsNoticeMaskSet(mysnomask) && !a->quitting) { a->WriteServ("NOTICE %s :*** %s: %s", a->nick.c_str(), desc.c_str(), mesg.c_str()); } i++; } } } LastMessage = ""; LastBlocked = false; Count = 0; }
/** This function is called when we want to send a netburst to a local * server. There is a set order we must do this, because for example * users require their servers to exist, and channels require their * users to exist. You get the idea. */ void TreeSocket::DoBurst(TreeServer* s) { ServerInstance->SNO->WriteToSnoMask('l',"Bursting to \2%s\2 (Authentication: %s%s).", s->GetName().c_str(), capab->auth_fingerprint ? "SSL Fingerprint and " : "", capab->auth_challenge ? "challenge-response" : "plaintext password"); this->CleanNegotiationInfo(); this->WriteLine(":" + ServerInstance->Config->GetSID() + " BURST " + ConvToStr(ServerInstance->Time())); /* send our version string */ this->WriteLine(":" + ServerInstance->Config->GetSID() + " VERSION :"+ServerInstance->GetVersionString()); /* Send server tree */ this->SendServers(Utils->TreeRoot, s); BurstState bs(this); /* Send users and their oper status */ this->SendUsers(bs); for (chan_hash::const_iterator i = ServerInstance->chanlist->begin(); i != ServerInstance->chanlist->end(); ++i) SyncChannel(i->second, bs); this->SendXLines(); FOREACH_MOD(OnSyncNetwork, (bs.server)); this->WriteLine(":" + ServerInstance->Config->GetSID() + " ENDBURST"); ServerInstance->SNO->WriteToSnoMask('l',"Finished bursting to \2"+ s->GetName()+"\2."); }
/* * OnPreCommand() * Intercept the LIST command. */ ModResult OnPreCommand(std::string& command, CommandBase::Params& parameters, LocalUser* user, bool validated) override { /* If the command doesnt appear to be valid, we dont want to mess with it. */ if (!validated) return MOD_RES_PASSTHRU; if ((command == "LIST") && (ServerInstance->Time() < (user->signon+WaitTime)) && (!user->IsOper())) { /* Normally wouldnt be allowed here, are they exempt? */ for (std::vector<std::string>::iterator x = allowlist.begin(); x != allowlist.end(); x++) if (InspIRCd::Match(user->MakeHost(), *x, ascii_case_insensitive_map)) return MOD_RES_PASSTHRU; const AccountExtItem* ext = GetAccountExtItem(); if (exemptregistered && ext && ext->get(user)) return MOD_RES_PASSTHRU; /* Not exempt, BOOK EM DANNO! */ user->WriteNotice("*** You cannot list within the first " + ConvToStr(WaitTime) + " seconds of connecting. Please try again later."); /* Some clients (e.g. mIRC, various java chat applets) muck up if they don't * receive these numerics whenever they send LIST, so give them an empty LIST to mull over. */ user->WriteNumeric(RPL_LISTSTART, "Channel", "Users Name"); user->WriteNumeric(RPL_LISTEND, "End of channel list."); return MOD_RES_DENY; } return MOD_RES_PASSTHRU; }
void ModuleSpanningTree::OnMode(userrec* user, void* dest, int target_type, const std::string &text) { if ((IS_LOCAL(user)) && (user->registered == REG_ALL)) { std::deque<std::string> params; std::string command; if (target_type == TYPE_USER) { userrec* u = (userrec*)dest; params.push_back(u->nick); params.push_back(text); command = "MODE"; } else { chanrec* c = (chanrec*)dest; params.push_back(c->name); params.push_back(ConvToStr(c->age)); params.push_back(text); command = "FMODE"; } Utils->DoOneToMany(user->nick, command, params); } }
ModeAction OnModeChange(userrec* source, userrec* dest, chanrec* channel, std::string ¶meter, bool adding) { if (!adding) { // Taking the mode off, we need to clean up. delaylist* dl; if (channel->GetExt("norejoinusers", dl)) { DELETE(dl); channel->Shrink("norejoinusers"); } } if ((!adding) || (atoi(parameter.c_str()) > 0)) { parameter = ConvToStr(atoi(parameter.c_str())); channel->SetModeParam('J', parameter.c_str(), adding); channel->SetMode('J', adding); return MODEACTION_ALLOW; } else { return MODEACTION_DENY; } }
int ModuleSpanningTree::OnStats(char statschar, userrec* user, string_list &results) { if ((statschar == 'c') || (statschar == 'n')) { for (unsigned int i = 0; i < Utils->LinkBlocks.size(); i++) { results.push_back(std::string(ServerInstance->Config->ServerName)+" 213 "+user->nick+" "+statschar+" *@"+(Utils->LinkBlocks[i].HiddenFromStats ? "<hidden>" : Utils->LinkBlocks[i].IPAddr)+" * "+Utils->LinkBlocks[i].Name.c_str()+" "+ConvToStr(Utils->LinkBlocks[i].Port)+" "+(Utils->LinkBlocks[i].Hook.empty() ? "plaintext" : Utils->LinkBlocks[i].Hook)+" "+(Utils->LinkBlocks[i].AutoConnect ? 'a' : '-')+'s'); if (statschar == 'c') results.push_back(std::string(ServerInstance->Config->ServerName)+" 244 "+user->nick+" H * * "+Utils->LinkBlocks[i].Name.c_str()); } results.push_back(std::string(ServerInstance->Config->ServerName)+" 219 "+user->nick+" "+statschar+" :End of /STATS report"); ServerInstance->SNO->WriteToSnoMask('t',"%s '%c' requested by %s (%s@%s)",(!strcmp(user->server,ServerInstance->Config->ServerName) ? "Stats" : "Remote stats"),statschar,user->nick,user->ident,user->host); return 1; } if (statschar == 'p') { /* show all server ports, after showing client ports. -- w00t */ for (unsigned int i = 0; i < Utils->Bindings.size(); i++) { std::string ip = Utils->Bindings[i]->IP; if (ip.empty()) ip = "*"; std::string transport("plaintext"); if (Utils->Bindings[i]->GetHook()) transport = InspSocketNameRequest(this, Utils->Bindings[i]->GetHook()).Send(); results.push_back(ConvToStr(ServerInstance->Config->ServerName) + " 249 "+user->nick+" :" + ip + ":" + ConvToStr(Utils->Bindings[i]->port)+ " (server, " + transport + ")"); } } return 0; }
std::string irc::sockets::cidr_mask::str() const { irc::sockets::sockaddrs sa; sa.sa.sa_family = type; unsigned char* base; size_t len; switch (type) { case AF_INET: base = (unsigned char*)&sa.in4.sin_addr; len = 4; break; case AF_INET6: base = (unsigned char*)&sa.in6.sin6_addr; len = 16; break; case AF_UNIX: return sa.un.sun_path; default: // If we have reached this point then we have encountered a bug. ServerInstance->Logs.Log("SOCKET", LOG_DEBUG, "BUG: irc::sockets::cidr_mask::str(): socket type %d is unknown!", type); return "<unknown>"; } memcpy(base, bits, len); return sa.addr() + "/" + ConvToStr((int)length); }
void CommandWhowas::GetStats(Extensible* ext) { int whowas_size = 0; int whowas_bytes = 0; whowas_users_fifo::iterator iter; for (iter = whowas_fifo.begin(); iter != whowas_fifo.end(); iter++) { whowas_set* n = (whowas_set*)whowas.find(iter->second)->second; if (n->size()) { whowas_size += n->size(); whowas_bytes += (sizeof(whowas_set) + ( sizeof(WhoWasGroup) * n->size() ) ); } } stats.assign("Whowas(MAPSETS) " +ConvToStr(whowas_size)+" ("+ConvToStr(whowas_bytes)+" bytes)"); ext->Extend("stats", stats.c_str()); }
ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string ¶meter, bool adding) { if (adding) { std::string::size_type colon = parameter.find(':'); if ((colon == std::string::npos) || (parameter.find('-') != std::string::npos)) { source->WriteNumeric(608, "%s %s :Invalid flood parameter",source->nick.c_str(),channel->name.c_str()); return MODEACTION_DENY; } /* Set up the flood parameters for this channel */ unsigned int njoins = ConvToInt(parameter.substr(0, colon)); unsigned int nsecs = ConvToInt(parameter.substr(colon+1)); if ((njoins<1) || (nsecs<1)) { source->WriteNumeric(608, "%s %s :Invalid flood parameter",source->nick.c_str(),channel->name.c_str()); return MODEACTION_DENY; } joinfloodsettings jfs(nsecs, njoins); joinfloodsettings* f = ext.get(channel); if ((f) && (*f == jfs)) // mode params match return MODEACTION_DENY; ext.set(channel, jfs); parameter = ConvToStr(njoins) + ":" + ConvToStr(nsecs); channel->SetModeParam(this, parameter); return MODEACTION_ALLOW; } else { if (!channel->IsModeSet(this)) return MODEACTION_DENY; joinfloodsettings* f = ext.get(channel); if (f) { ext.unset(channel); channel->SetModeParam(this, ""); return MODEACTION_ALLOW; } } return MODEACTION_DENY; }