/* OperServ core functions * * (C) 2003-2012 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 "global.h" #include "os_session.h" enum DefconLevel { DEFCON_NO_NEW_CHANNELS, DEFCON_NO_NEW_NICKS, DEFCON_NO_MLOCK_CHANGE, DEFCON_FORCE_CHAN_MODES, DEFCON_REDUCE_SESSION, DEFCON_NO_NEW_CLIENTS, DEFCON_OPER_ONLY, DEFCON_SILENT_OPER_ONLY, DEFCON_AKILL_NEW_CLIENTS, DEFCON_NO_NEW_MEMOS }; bool DefConModesSet = false; struct DefconConfig { std::vector > DefCon; Flags DefConModesOn; Flags DefConModesOff; std::map DefConModesOnParams; int defaultlevel, sessionlimit; Anope::string chanmodes, message, offmessage, akillreason; std::vector defcons; time_t akillexpire, timeout; bool globalondefcon; DefconConfig() { this->DefCon.resize(6); this->defcons.resize(5); } bool Check(DefconLevel level) { return this->Check(this->defaultlevel, level); } bool Check(int dlevel, DefconLevel level) { return this->DefCon[dlevel].test(level); } void Add(int dlevel, DefconLevel level) { this->DefCon[dlevel][level] = true; } void Del(int dlevel, DefconLevel level) { this->DefCon[dlevel][level] = false; } bool SetDefConParam(ChannelModeName Name, const Anope::string &buf) { return DefConModesOnParams.insert(std::make_pair(Name, buf)).second; } void UnsetDefConParam(ChannelModeName Name) { DefConModesOnParams.erase(Name); } bool GetDefConParam(ChannelModeName Name, Anope::string &buf) { std::map::iterator it = DefConModesOnParams.find(Name); buf.clear(); if (it != DefConModesOnParams.end()) { buf = it->second; return true; } return false; } }; static DefconConfig DConfig; /**************************************************************************/ void defcon_sendlvls(CommandSource &source); void runDefCon(); static Anope::string defconReverseModes(const Anope::string &modes); class DefConTimeout : public CallBack { int level; public: DefConTimeout(Module *mod, int newlevel) : CallBack(mod, DConfig.timeout), level(newlevel) { } void Tick(time_t) { if (DConfig.defaultlevel != level) { DConfig.defaultlevel = level; FOREACH_MOD(I_OnDefconLevel, OnDefconLevel(level)); Log(findbot(Config->OperServ), "operserv/defcon") << "Defcon level timeout, returning to level " << level; if (DConfig.globalondefcon) { if (!DConfig.offmessage.empty()) global->SendGlobal(findbot(Config->Global), "", DConfig.offmessage); else global->SendGlobal(findbot(Config->Global), "", Anope::printf(translate(_("The Defcon Level is now at Level: \002%d\002")), DConfig.defaultlevel)); if (!DConfig.message.empty()) global->SendGlobal(findbot(Config->Global), "", DConfig.message); } runDefCon(); } } }; static DefConTimeout *timeout; class CommandOSDefcon : public Command { void SendLevels(CommandSource &source) { if (DConfig.Check(DEFCON_NO_NEW_CHANNELS)) source.Reply(_("* No new channel registrations")); if (DConfig.Check(DEFCON_NO_NEW_NICKS)) source.Reply(_("* No new nick registrations")); if (DConfig.Check(DEFCON_NO_MLOCK_CHANGE)) source.Reply(_("* No mode lock changes")); if (DConfig.Check(DEFCON_FORCE_CHAN_MODES) && !DConfig.chanmodes.empty()) source.Reply(_("* Force Chan Modes (%s) to be set on all channels"), DConfig.chanmodes.c_str()); if (DConfig.Check(DEFCON_REDUCE_SESSION)) source.Reply(_("* Use the reduced session limit of %d"), DConfig.sessionlimit); if (DConfig.Check(DEFCON_NO_NEW_CLIENTS)) source.Reply(_("* Kill any NEW clients connecting")); if (DConfig.Check(DEFCON_OPER_ONLY)) source.Reply(_("* Ignore any non-opers with message")); if (DConfig.Check(DEFCON_SILENT_OPER_ONLY)) source.Reply(_("* Silently ignore non-opers")); if (DConfig.Check(DEFCON_AKILL_NEW_CLIENTS)) source.Reply(_("* AKILL any new clients connecting")); if (DConfig.Check(DEFCON_NO_NEW_MEMOS)) source.Reply(_("* No new memos sent")); } public: CommandOSDefcon(Module *creator) : Command(creator, "operserv/defcon", 1, 1) { this->SetDesc(_("Manipulate the DefCon system")); this->SetSyntax(_("[\0021\002|\0022\002|\0023\002|\0024\002|\0025\002]")); } void Execute(CommandSource &source, const std::vector ¶ms) { User *u = source.u; const Anope::string &lvl = params[0]; if (lvl.empty()) { source.Reply(_("Services are now at DEFCON \002%d\002"), DConfig.defaultlevel); this->SendLevels(source); return; } int newLevel = 0; try { newLevel = convertTo(lvl); } catch (const ConvertException &) { } if (newLevel < 1 || newLevel > 5) { this->OnSyntaxError(source, ""); return; } DConfig.defaultlevel = newLevel; FOREACH_MOD(I_OnDefconLevel, OnDefconLevel(newLevel)); if (timeout) { delete timeout; timeout = NULL; } if (DConfig.timeout) timeout = new DefConTimeout(this->module, 5); source.Reply(_("Services are now at DEFCON \002%d\002"), DConfig.defaultlevel); this->SendLevels(source); Log(LOG_ADMIN, u, this) << "to change defcon level to " << newLevel; /* Global notice the user what is happening. Also any Message that the Admin would like to add. Set in config file. */ if (DConfig.globalondefcon) { if (DConfig.defaultlevel == 5 && !DConfig.offmessage.empty()) global->SendGlobal(findbot(Config->Global), "", DConfig.offmessage); else if (DConfig.defaultlevel != 5) { global->SendGlobal(findbot(Config->Global), "", Anope::printf(_("The Defcon level is now at: \002%d\002"), DConfig.defaultlevel)); if (!DConfig.message.empty()) global->SendGlobal(findbot(Config->Global), "", DConfig.message); } } /* Run any defcon functions, e.g. FORCE CHAN MODE */ runDefCon(); return; } bool OnHelp(CommandSource &source, const Anope::string &subcommand) { this->SendSyntax(source); source.Reply(" "); source.Reply(_("The defcon system can be used to implement a pre-defined\n" "set of restrictions to services useful during an attempted\n" "attack on the network.")); return true; } }; class OSDefcon : public Module { service_reference session_service; service_reference akills; CommandOSDefcon commandosdefcon; void ParseModeString() { int add = -1; /* 1 if adding, 0 if deleting, -1 if neither */ unsigned char mode; ChannelMode *cm; ChannelModeParam *cmp; Anope::string modes, param; spacesepstream ss(DConfig.chanmodes); DConfig.DefConModesOn.ClearFlags(); DConfig.DefConModesOff.ClearFlags(); ss.GetToken(modes); /* Loop while there are modes to set */ for (unsigned i = 0, end = modes.length(); i < end; ++i) { mode = modes[i]; switch (mode) { case '+': add = 1; continue; case '-': add = 0; continue; default: if (add < 0) continue; } if ((cm = ModeManager::FindChannelModeByChar(mode))) { if (cm->Type == MODE_STATUS || cm->Type == MODE_LIST || !cm->CanSet(NULL)) { Log() << "DefConChanModes mode character '" << mode << "' cannot be locked"; continue; } else if (add) { DConfig.DefConModesOn.SetFlag(cm->Name); DConfig.DefConModesOff.UnsetFlag(cm->Name); if (cm->Type == MODE_PARAM) { cmp = debug_cast(cm); if (!ss.GetToken(param)) { Log() << "DefConChanModes mode character '" << mode << "' has no parameter while one is expected"; continue; } if (!cmp->IsValid(param)) continue; DConfig.SetDefConParam(cmp->Name, param); } } else if (DConfig.DefConModesOn.HasFlag(cm->Name)) { DConfig.DefConModesOn.UnsetFlag(cm->Name); if (cm->Type == MODE_PARAM) DConfig.UnsetDefConParam(cm->Name); } } } /* We can't mlock +L if +l is not mlocked as well. */ if ((cm = ModeManager::FindChannelModeByName(CMODE_REDIRECT)) && DConfig.DefConModesOn.HasFlag(cm->Name) && !DConfig.DefConModesOn.HasFlag(CMODE_LIMIT)) { DConfig.DefConModesOn.UnsetFlag(CMODE_REDIRECT); Log() << "DefConChanModes must lock mode +l as well to lock mode +L"; } /* Some ircd we can't set NOKNOCK without INVITE */ /* So check if we need there is a NOKNOCK MODE and that we need INVITEONLY */ if (ircd->knock_needs_i && (cm = ModeManager::FindChannelModeByName(CMODE_NOKNOCK)) && DConfig.DefConModesOn.HasFlag(cm->Name) && !DConfig.DefConModesOn.HasFlag(CMODE_INVITE)) { DConfig.DefConModesOn.UnsetFlag(CMODE_NOKNOCK); Log() << "DefConChanModes must lock mode +i as well to lock mode +K"; } } public: OSDefcon(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), session_service("session"), akills("xlinemanager/sgline"), commandosdefcon(this) { this->SetAuthor("Anope"); Implementation i[] = { I_OnReload, I_OnChannelModeSet, I_OnChannelModeUnset, I_OnPreCommand, I_OnUserConnect, I_OnChannelModeAdd, I_OnChannelCreate }; ModuleManager::Attach(i, this, sizeof(i) / sizeof(Implementation)); try { this->OnReload(); } catch (const ConfigException &ex) { throw ModuleException(ex.GetReason()); } } void OnReload() { ConfigReader config; DefconConfig dconfig; dconfig.defaultlevel = config.ReadInteger("defcon", "defaultlevel", 0, 0); dconfig.defcons[4] = config.ReadValue("defcon", "level4", 0); dconfig.defcons[3] = config.ReadValue("defcon", "level3", 0); dconfig.defcons[2] = config.ReadValue("defcon", "level2", 0); dconfig.defcons[1] = config.ReadValue("defcon", "level1", 0); dconfig.sessionlimit = config.ReadInteger("defcon", "sessionlimit", 0, 0); dconfig.akillreason = config.ReadValue("defcon", "akillreason", 0); dconfig.akillexpire = dotime(config.ReadValue("defcon", "akillexpire", 0)); dconfig.chanmodes = config.ReadValue("defcon", "chanmodes", 0); dconfig.timeout = dotime(config.ReadValue("defcon", "timeout", 0)); dconfig.globalondefcon = config.ReadFlag("defcon", "globalondefcon", 0); dconfig.message = config.ReadValue("defcon", "message", 0); dconfig.offmessage = config.ReadValue("defcon", "offmessage", 0); if (dconfig.defaultlevel < 1 || dconfig.defaultlevel > 5) throw ConfigException("The value for must be between 1 and 5"); else if (dconfig.akillexpire <= 0) throw ConfigException("The value for must be greater than zero!"); for (unsigned level = 1; level < 5; ++level) { spacesepstream operations(dconfig.defcons[level]); Anope::string operation; while (operations.GetToken(operation)) { if (operation.equals_ci("nonewchannels")) dconfig.Add(level, DEFCON_NO_NEW_CHANNELS); else if (operation.equals_ci("nonewnicks")) dconfig.Add(level, DEFCON_NO_NEW_NICKS); else if (operation.equals_ci("nomlockchanges")) dconfig.Add(level, DEFCON_NO_MLOCK_CHANGE); else if (operation.equals_ci("forcechanmodes")) dconfig.Add(level, DEFCON_FORCE_CHAN_MODES); else if (operation.equals_ci("reducedsessions")) dconfig.Add(level, DEFCON_REDUCE_SESSION); else if (operation.equals_ci("nonewclients")) dconfig.Add(level, DEFCON_NO_NEW_CLIENTS); else if (operation.equals_ci("operonly")) dconfig.Add(level, DEFCON_OPER_ONLY); else if (operation.equals_ci("silentoperonly")) dconfig.Add(level, DEFCON_SILENT_OPER_ONLY); else if (operation.equals_ci("akillnewclients")) dconfig.Add(level, DEFCON_AKILL_NEW_CLIENTS); else if (operation.equals_ci("nonewmemos")) dconfig.Add(level, DEFCON_NO_NEW_MEMOS); } if (dconfig.Check(level, DEFCON_REDUCE_SESSION) && dconfig.sessionlimit <= 0) throw ConfigException("The value for must be greater than zero!"); else if (dconfig.Check(level, DEFCON_AKILL_NEW_CLIENTS) && dconfig.akillreason.empty()) throw ConfigException("The value for must not be empty!"); else if (dconfig.Check(level, DEFCON_FORCE_CHAN_MODES) && dconfig.chanmodes.empty()) throw ConfigException("The value for must not be empty!"); } DConfig = dconfig; this->ParseModeString(); } EventReturn OnUserConnect(User *u, bool &exempt) { if (!exempt && u->server->IsSynced() && DConfig.Check(DEFCON_AKILL_NEW_CLIENTS) && !u->server->IsULined()) { if (DConfig.Check(DEFCON_AKILL_NEW_CLIENTS) && akills) { Log(findbot(Config->OperServ), "operserv/defcon") << "DEFCON: adding akill for *@" << u->host; XLine *x = new XLine("*@" + u->host, Config->OperServ, Anope::CurTime + DConfig.akillexpire, DConfig.akillreason, XLineManager::GenerateUID()); x->By = Config->OperServ; akills->AddXLine(x); } if (DConfig.Check(DEFCON_NO_NEW_CLIENTS) || DConfig.Check(DEFCON_AKILL_NEW_CLIENTS)) u->Kill(Config->OperServ, DConfig.akillreason); return EVENT_STOP; } return EVENT_CONTINUE; } EventReturn OnChannelModeSet(Channel *c, ChannelModeName Name, const Anope::string ¶m) { ChannelMode *cm = ModeManager::FindChannelModeByName(Name); if (DConfig.Check(DEFCON_FORCE_CHAN_MODES) && cm && DConfig.DefConModesOff.HasFlag(Name)) { c->RemoveMode(findbot(Config->OperServ), Name, param); return EVENT_STOP; } return EVENT_CONTINUE; } EventReturn OnChannelModeUnset(Channel *c, ChannelModeName Name, const Anope::string &) { ChannelMode *cm = ModeManager::FindChannelModeByName(Name); if (DConfig.Check(DEFCON_FORCE_CHAN_MODES) && cm && DConfig.DefConModesOn.HasFlag(Name)) { Anope::string param; if (DConfig.GetDefConParam(Name, param)) c->SetMode(findbot(Config->OperServ), Name, param); else c->SetMode(findbot(Config->OperServ), Name); return EVENT_STOP; } return EVENT_CONTINUE; } EventReturn OnPreCommand(CommandSource &source, Command *command, std::vector ¶ms) { if (command->name == "nickserv/register" || command->name == "nickserv/group") { if (DConfig.Check(DEFCON_NO_NEW_NICKS)) { source.Reply(_("Services are in Defcon mode, Please try again later.")); return EVENT_STOP; } } else if (command->name == "chanserv/mode" && params.size() > 1 && params[1].equals_ci("LOCK")) { if (DConfig.Check(DEFCON_NO_MLOCK_CHANGE)) { source.Reply(_("Services are in Defcon mode, Please try again later.")); return EVENT_STOP; } } else if (command->name == "chanserv/register") { if (DConfig.Check(DEFCON_NO_NEW_CHANNELS)) { source.Reply(_("Services are in Defcon mode, Please try again later.")); return EVENT_STOP; } } else if (command->name == "memoserv/send") { if (DConfig.Check(DEFCON_NO_NEW_MEMOS)) { source.Reply(_("Services are in Defcon mode, Please try again later.")); return EVENT_STOP; } } return EVENT_CONTINUE; } void OnUserConnect(dynamic_reference &u, bool &exempt) { if (exempt || !u || !u->server->IsSynced() || u->server->IsULined()) return; if (DConfig.Check(DEFCON_AKILL_NEW_CLIENTS) && akills) { Log(findbot(Config->OperServ), "operserv/defcon") << "DEFCON: adding akill for *@" << u->host; XLine x("*@" + u->host, Config->OperServ, Anope::CurTime + DConfig.akillexpire, DConfig.akillreason, XLineManager::GenerateUID()); x.By = Config->OperServ; akills->Send(NULL, &x); } if (DConfig.Check(DEFCON_NO_NEW_CLIENTS) || DConfig.Check(DEFCON_AKILL_NEW_CLIENTS)) { u->Kill(Config->OperServ, DConfig.akillreason); return; } if (DConfig.Check(DEFCON_NO_NEW_CLIENTS) || DConfig.Check(DEFCON_AKILL_NEW_CLIENTS)) { u->Kill(Config->OperServ, DConfig.akillreason); return; } if (!DConfig.sessionlimit) return; Session *session = session_service->FindSession(u->host); Exception *exception = session_service->FindException(u); if (DConfig.Check(DEFCON_REDUCE_SESSION) && !exception) { if (session && session->count > DConfig.sessionlimit) { if (!Config->SessionLimitExceeded.empty()) ircdproto->SendMessage(findbot(Config->OperServ), u->nick, Config->SessionLimitExceeded.c_str(), u->host.c_str()); if (!Config->SessionLimitDetailsLoc.empty()) ircdproto->SendMessage(findbot(Config->OperServ), u->nick, "%s", Config->SessionLimitDetailsLoc.c_str()); u->Kill(Config->OperServ, "Defcon session limit exceeded"); ++session->hits; if (akills && Config->MaxSessionKill && session->hits >= Config->MaxSessionKill) { XLine x("*@" + u->host, Config->OperServ, Anope::CurTime + Config->SessionAutoKillExpiry, "Defcon session limit exceeded", XLineManager::GenerateUID()); akills->Send(NULL, &x); ircdproto->SendGlobops(findbot(Config->OperServ), "[DEFCON] Added a temporary AKILL for \2*@%s\2 due to excessive connections", u->host.c_str()); } } } } void OnChannelModeAdd(ChannelMode *cm) { if (DConfig.chanmodes.find(cm->ModeChar) != Anope::string::npos) this->ParseModeString(); } void OnChannelCreate(Channel *c) { if (DConfig.Check(DEFCON_FORCE_CHAN_MODES)) c->SetModes(findbot(Config->OperServ), false, "%s", DConfig.chanmodes.c_str()); } }; /** * Send a message to the oper about which precautions are "active" for this level **/ void defcon_sendlvls(CommandSource &source) { if (DConfig.Check(DEFCON_NO_NEW_CHANNELS)) source.Reply(_("* No new channel registrations")); if (DConfig.Check(DEFCON_NO_NEW_NICKS)) source.Reply(_("* No new nick registrations")); if (DConfig.Check(DEFCON_NO_MLOCK_CHANGE)) source.Reply(_("* No MLOCK changes")); if (DConfig.Check(DEFCON_FORCE_CHAN_MODES) && !DConfig.chanmodes.empty()) source.Reply(_("* Force Chan Modes (%s) to be set on all channels"), DConfig.chanmodes.c_str()); if (DConfig.Check(DEFCON_REDUCE_SESSION)) source.Reply(_("* Use the reduced session limit of %d"), DConfig.sessionlimit); if (DConfig.Check(DEFCON_NO_NEW_CLIENTS)) source.Reply(_("* Kill any NEW clients connecting")); if (DConfig.Check(DEFCON_OPER_ONLY)) source.Reply(_("* Ignore any non-opers with message")); if (DConfig.Check(DEFCON_SILENT_OPER_ONLY)) source.Reply(_("* Silently ignore non-opers")); if (DConfig.Check(DEFCON_AKILL_NEW_CLIENTS)) source.Reply(_("* AKILL any new clients connecting")); if (DConfig.Check(DEFCON_NO_NEW_MEMOS)) source.Reply(_("* No new memos sent")); } void runDefCon() { if (DConfig.Check(DEFCON_FORCE_CHAN_MODES)) { if (!DConfig.chanmodes.empty() && !DefConModesSet) { if (DConfig.chanmodes[0] == '+' || DConfig.chanmodes[0] == '-') { Log(findbot(Config->OperServ), "operserv/defcon") << "DEFCON: setting " << DConfig.chanmodes << " on all channels"; DefConModesSet = true; for (channel_map::const_iterator it = ChannelList.begin(), it_end = ChannelList.end(); it != it_end; ++it) it->second->SetModes(findbot(Config->OperServ), false, "%s", DConfig.chanmodes.c_str()); } } } else { if (!DConfig.chanmodes.empty() && DefConModesSet) { if (DConfig.chanmodes[0] == '+' || DConfig.chanmodes[0] == '-') { DefConModesSet = false; Anope::string newmodes = defconReverseModes(DConfig.chanmodes); if (!newmodes.empty()) { Log(findbot(Config->OperServ), "operserv/defcon") << "DEFCON: setting " << newmodes << " on all channels"; for (channel_map::const_iterator it = ChannelList.begin(), it_end = ChannelList.end(); it != it_end; ++it) it->second->SetModes(findbot(Config->OperServ), false, "%s", newmodes.c_str()); } } } } } static Anope::string defconReverseModes(const Anope::string &modes) { if (modes.empty()) return ""; Anope::string newmodes; for (unsigned i = 0, end = modes.length(); i < end; ++i) { if (modes[i] == '+') newmodes += '-'; else if (modes[i] == '-') newmodes += '+'; else newmodes += modes[i]; } return newmodes; } MODULE_INIT(OSDefcon)