1
0
mirror of https://github.com/anope/anope.git synced 2026-06-12 18:54:47 +02:00
Files
anope/modules/chanserv/cs_set.cpp
T
2026-03-26 14:35:31 +00:00

1338 lines
39 KiB
C++

// Anope IRC Services <https://www.anope.org/>
//
// Copyright (C) 2003-2026 Anope Contributors
//
// Anope is free software. You can use, modify, and/or distribute it under the
// terms of version 2 of the GNU General Public License. See docs/LICENSE.txt
// for the complete terms of this license and docs/AUTHORS.txt for a list of
// contributors.
//
// Based on the original code of Epona by Lara
// Based on the original code of Services by Andy Church
//
// SPDX-License-Identifier: GPL-2.0-only
#include "module.h"
#include "modules/chanserv/mode.h"
class CommandCSSet final
: public Command
{
public:
CommandCSSet(Module *creator) : Command(creator, "chanserv/set", 2, 3)
{
this->SetDesc(_("Set channel options and information"));
this->SetSyntax(_("\037option\037 \037channel\037 \037parameters\037"));
}
void Execute(CommandSource &source, const std::vector<Anope::string> &params) override
{
this->OnSyntaxError(source, "");
}
bool OnHelp(CommandSource &source, const Anope::string &subcommand) override
{
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_(
"Allows the channel founder to set various channel options "
"and other information."
"\n\n"
"Available options:"));
Anope::string this_name = source.command;
bool hide_privileged_commands = Config->GetBlock("options").Get<bool>("hideprivilegedcommands"),
hide_registered_commands = Config->GetBlock("options").Get<bool>("hideregisteredcommands");
HelpWrapper help;
for (const auto &[c_name, info] : source.service->commands)
{
if (c_name.find_ci(this_name + " ") == 0)
{
if (info.hide)
continue;
ServiceReference<Command> c("Command", info.name);
// XXX dup
if (!c)
continue;
else if (hide_registered_commands && !c->AllowUnregistered() && !source.GetAccount())
continue;
else if (hide_privileged_commands && !info.permission.empty() && !source.HasCommand(info.permission))
continue;
source.command = c_name;
c->OnServHelp(source, help);
}
}
help.SendTo(source);
source.Reply(_("Type \002%s\033\037option\037\002 for more information on a particular option."),
source.service->GetQueryCommand("generic/help", this_name).c_str());
return true;
}
};
class CommandCSSetAutoOp final
: public Command
{
public:
CommandCSSetAutoOp(Module *creator, const Anope::string &cname = "chanserv/set/autoop") : Command(creator, cname, 2, 2)
{
this->SetDesc(_("Should services automatically give status to users"));
this->SetSyntax(_("\037channel\037 {ON | OFF}"));
}
void Execute(CommandSource &source, const std::vector<Anope::string> &params) override
{
if (Anope::ReadOnly)
{
source.Reply(READ_ONLY_MODE);
return;
}
ChannelInfo *ci = ChannelInfo::Find(params[0]);
if (ci == NULL)
{
source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str());
return;
}
EventReturn MOD_RESULT;
FOREACH_RESULT(OnSetChannelOption, MOD_RESULT, (source, this, ci, params[1]));
if (MOD_RESULT == EVENT_STOP)
return;
if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration"))
{
source.Reply(ACCESS_DENIED);
return;
}
if (params[1].equals_ci("ON"))
{
Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to enable autoop";
ci->Shrink<bool>("NOAUTOOP");
source.Reply(_("Services will now automatically give modes to users in \002%s\002."), ci->name.c_str());
}
else if (params[1].equals_ci("OFF"))
{
Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to disable autoop";
ci->Extend<bool>("NOAUTOOP");
source.Reply(_("Services will no longer automatically give modes to users in \002%s\002."), ci->name.c_str());
}
else
this->OnSyntaxError(source, "AUTOOP");
}
bool OnHelp(CommandSource &source, const Anope::string &) override
{
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_(
"Enables or disables %s's autoop feature for a "
"channel. When disabled, users who join the channel will "
"not automatically gain any status from %s."
),
source.service->nick.c_str(),
source.service->nick.c_str());
return true;
}
};
class CommandCSSetBanType final
: public Command
{
public:
CommandCSSetBanType(Module *creator, const Anope::string &cname = "chanserv/set/bantype") : Command(creator, cname, 2, 2)
{
this->SetDesc(_("Set how services make bans on the channel"));
this->SetSyntax(_("\037channel\037 \037bantype\037"));
}
void Execute(CommandSource &source, const std::vector<Anope::string> &params) override
{
if (Anope::ReadOnly)
{
source.Reply(READ_ONLY_MODE);
return;
}
ChannelInfo *ci = ChannelInfo::Find(params[0]);
if (ci == NULL)
{
source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str());
return;
}
EventReturn MOD_RESULT;
FOREACH_RESULT(OnSetChannelOption, MOD_RESULT, (source, this, ci, params[1]));
if (MOD_RESULT == EVENT_STOP)
return;
if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration"))
{
source.Reply(ACCESS_DENIED);
return;
}
auto new_type = Anope::Convert<int16_t>(params[1], -1);
if (new_type < 0 || new_type > 3)
{
source.Reply(_("\002%s\002 is not a valid ban type."), params[1].c_str());
return;
}
Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to change the ban type to " << new_type;
ci->bantype = new_type;
source.Reply(_("Ban type for channel %s is now #%d."), ci->name.c_str(), ci->bantype);
}
bool OnHelp(CommandSource &source, const Anope::string &) override
{
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_(
"Sets the ban type that will be used by services whenever "
"they need to ban someone from your channel."
"\n\n"
"Bantype is a number between 0 and 3 that means:"
"\n\n"
"0: ban in the form *!user@host\n"
"1: ban in the form *!*user@host\n"
"2: ban in the form *!*@host\n"
"3: ban in the form *!*user@*.domain"
));
return true;
}
};
class CommandCSSetDescription final
: public Command
{
public:
CommandCSSetDescription(Module *creator, const Anope::string &cname = "chanserv/set/description") : Command(creator, cname, 1, 2)
{
this->SetDesc(_("Set the channel description"));
this->SetSyntax(_("\037channel\037 [\037description\037]"));
}
void Execute(CommandSource &source, const std::vector<Anope::string> &params) override
{
if (Anope::ReadOnly)
{
source.Reply(READ_ONLY_MODE);
return;
}
ChannelInfo *ci = ChannelInfo::Find(params[0]);
const Anope::string &param = params.size() > 1 ? params[1] : "";
if (ci == NULL)
{
source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str());
return;
}
EventReturn MOD_RESULT;
FOREACH_RESULT(OnSetChannelOption, MOD_RESULT, (source, this, ci, param));
if (MOD_RESULT == EVENT_STOP)
return;
if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration"))
{
source.Reply(ACCESS_DENIED);
return;
}
if (!param.empty())
{
ci->desc = param;
Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to change the description to " << ci->desc;
source.Reply(_("Description of %s changed to \002%s\002."), ci->name.c_str(), ci->desc.c_str());
}
else
{
ci->desc.clear();
Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to unset the description";
source.Reply(_("Description of %s unset."), ci->name.c_str());
}
}
bool OnHelp(CommandSource &source, const Anope::string &) override
{
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_(
"Sets the description for the channel, which shows up with "
"the \002LIST\002 and \002INFO\002 commands."
));
return true;
}
};
class CommandCSSetFounder final
: public Command
{
public:
CommandCSSetFounder(Module *creator, const Anope::string &cname = "chanserv/set/founder") : Command(creator, cname, 2, 2)
{
this->SetDesc(_("Set the founder of a channel"));
this->SetSyntax(_("\037channel\037 \037nick\037"));
}
void Execute(CommandSource &source, const std::vector<Anope::string> &params) override
{
if (Anope::ReadOnly)
{
source.Reply(READ_ONLY_MODE);
return;
}
ChannelInfo *ci = ChannelInfo::Find(params[0]);
if (ci == NULL)
{
source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str());
return;
}
EventReturn MOD_RESULT;
FOREACH_RESULT(OnSetChannelOption, MOD_RESULT, (source, this, ci, params[1]));
if (MOD_RESULT == EVENT_STOP)
return;
if (MOD_RESULT != EVENT_ALLOW && (ci->HasExt("SECUREFOUNDER") ? !source.IsFounder(ci) : !source.AccessFor(ci).HasPriv("FOUNDER")) && source.permission.empty() && !source.HasPriv("chanserv/administration"))
{
source.Reply(ACCESS_DENIED);
return;
}
const NickAlias *na = NickAlias::Find(params[1]);
if (!na)
{
source.Reply(NICK_X_NOT_REGISTERED, params[1].c_str());
return;
}
else if (na->nc->HasExt("NEVEROP"))
{
source.Reply(_("\002%s\002 does not wish to be added to channel access lists."),
na->nc->display.c_str());
return;
}
NickCore *nc = na->nc;
auto max_reg = Config->GetModule("chanserv").Get<unsigned>("maxregistered");
if (max_reg && nc->channelcount >= max_reg && !source.HasPriv("chanserv/no-register-limit"))
{
source.Reply(_("\002%s\002 has too many channels registered."), na->nick.c_str());
return;
}
Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to change the founder from " << (ci->GetFounder() ? ci->GetFounder()->display : "(none)") << " to " << nc->display;
ci->SetFounder(nc);
source.Reply(_("Founder of \002%s\002 changed to \002%s\002."), ci->name.c_str(), na->nick.c_str());
}
bool OnHelp(CommandSource &source, const Anope::string &) override
{
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_(
"Changes the founder of a channel. The new nickname must "
"be a registered one."
));
return true;
}
};
class CommandCSSetKeepModes final
: public Command
{
public:
CommandCSSetKeepModes(Module *creator, const Anope::string &cname = "chanserv/set/keepmodes") : Command(creator, cname, 2, 2)
{
this->SetDesc(_("Retain modes when channel is not in use"));
this->SetSyntax(_("\037channel\037 {ON | OFF}"));
}
void Execute(CommandSource &source, const std::vector<Anope::string> &params) override
{
if (Anope::ReadOnly)
{
source.Reply(READ_ONLY_MODE);
return;
}
ChannelInfo *ci = ChannelInfo::Find(params[0]);
if (ci == NULL)
{
source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str());
return;
}
EventReturn MOD_RESULT;
FOREACH_RESULT(OnSetChannelOption, MOD_RESULT, (source, this, ci, params[1]));
if (MOD_RESULT == EVENT_STOP)
return;
if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration"))
{
source.Reply(ACCESS_DENIED);
return;
}
if (params[1].equals_ci("ON"))
{
Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to enable keep modes";
ci->Extend<bool>("CS_KEEP_MODES");
source.Reply(_("Keep modes for %s is now \002on\002."), ci->name.c_str());
if (ci->c)
ci->last_modes = ci->c->GetModes();
}
else if (params[1].equals_ci("OFF"))
{
Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to disable keep modes";
ci->Shrink<bool>("CS_KEEP_MODES");
source.Reply(_("Keep modes for %s is now \002off\002."), ci->name.c_str());
ci->last_modes.clear();
}
else
this->OnSyntaxError(source, "KEEPMODES");
}
bool OnHelp(CommandSource &source, const Anope::string &) override
{
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_(
"Enables or disables keepmodes for the given channel. If keep "
"modes is enabled, services will remember modes set on the channel "
"and attempt to re-set them the next time the channel is created."
));
return true;
}
};
class CommandCSSetPeace final
: public Command
{
public:
CommandCSSetPeace(Module *creator, const Anope::string &cname = "chanserv/set/peace") : Command(creator, cname, 2, 2)
{
this->SetDesc(_("Regulate the use of critical commands"));
this->SetSyntax(_("\037channel\037 {ON | OFF}"));
}
void Execute(CommandSource &source, const std::vector<Anope::string> &params) override
{
if (Anope::ReadOnly)
{
source.Reply(READ_ONLY_MODE);
return;
}
ChannelInfo *ci = ChannelInfo::Find(params[0]);
if (ci == NULL)
{
source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str());
return;
}
EventReturn MOD_RESULT;
FOREACH_RESULT(OnSetChannelOption, MOD_RESULT, (source, this, ci, params[1]));
if (MOD_RESULT == EVENT_STOP)
return;
if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration"))
{
source.Reply(ACCESS_DENIED);
return;
}
if (params[1].equals_ci("ON"))
{
Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to enable peace";
ci->Extend<bool>("PEACE");
source.Reply(_("Peace option for %s is now \002on\002."), ci->name.c_str());
}
else if (params[1].equals_ci("OFF"))
{
Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to disable peace";
ci->Shrink<bool>("PEACE");
source.Reply(_("Peace option for %s is now \002off\002."), ci->name.c_str());
}
else
this->OnSyntaxError(source, "PEACE");
}
bool OnHelp(CommandSource &source, const Anope::string &) override
{
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_(
"Enables or disables the \002peace\002 option for a channel. "
"When \002peace\002 is set, a user won't be able to kick, "
"ban or remove a channel status of a user that has "
"a level superior or equal to theirs via %s commands."
),
source.service->nick.c_str());
return true;
}
};
inline static Anope::string BotModes()
{
return Config->GetModule("botserv").Get<Anope::string>("botmodes",
Config->GetModule("chanserv").Get<Anope::string>("botmodes", "o")
);
}
class CommandCSSetPersist final
: public Command
{
public:
CommandCSSetPersist(Module *creator, const Anope::string &cname = "chanserv/set/persist") : Command(creator, cname, 2, 2)
{
this->SetDesc(_("Set the channel as permanent"));
this->SetSyntax(_("\037channel\037 {ON | OFF}"));
}
void Execute(CommandSource &source, const std::vector<Anope::string> &params) override
{
if (Anope::ReadOnly)
{
source.Reply(READ_ONLY_MODE);
return;
}
ChannelInfo *ci = ChannelInfo::Find(params[0]);
if (ci == NULL)
{
source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str());
return;
}
EventReturn MOD_RESULT;
FOREACH_RESULT(OnSetChannelOption, MOD_RESULT, (source, this, ci, params[1]));
if (MOD_RESULT == EVENT_STOP)
return;
if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration"))
{
source.Reply(ACCESS_DENIED);
return;
}
ChannelMode *cm = ModeManager::FindChannelModeByName("PERM");
if (params[1].equals_ci("ON"))
{
if (!ci->HasExt("PERSIST"))
{
ci->Extend<bool>("PERSIST");
/* Set the perm mode */
if (cm)
{
/* Add it to the channels mlock */
auto *ml = ci->Require<ChanServ::ModeLocks>(CHANSERV_MODE_LOCK_EXT);
if (ml)
ml->SetMLock(cm, true, "", source.GetNick());
if (ci->c && !ci->c->HasMode("PERM"))
ci->c->SetMode(NULL, cm);
}
/* No botserv bot, no channel mode, give them ChanServ.
* Yes, this works fine with no BotServ.
*/
else if (!ci->bi)
{
BotInfo *ChanServ = Config->GetClient("ChanServ");
if (!ChanServ)
{
source.Reply(_("ChanServ is required to enable persist on this network."));
return;
}
ChanServ->Assign(NULL, ci);
if (ci->c && !ci->c->FindUser(ChanServ))
{
ChannelStatus status(BotModes());
ChanServ->Join(ci->c, &status);
}
}
}
Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to enable persist";
source.Reply(_("Channel \002%s\002 is now persistent."), ci->name.c_str());
}
else if (params[1].equals_ci("OFF"))
{
if (ci->HasExt("PERSIST"))
{
ci->Shrink<bool>("PERSIST");
BotInfo *ChanServ = Config->GetClient("ChanServ"),
*BotServ = Config->GetClient("BotServ");
/* Unset perm mode */
if (cm)
{
/* Remove from mlock */
auto *ml = ci->GetExt<ChanServ::ModeLocks>(CHANSERV_MODE_LOCK_EXT);
if (ml)
ml->RemoveMLock(cm, true);
if (ci->c && ci->c->HasMode("PERM"))
ci->c->RemoveMode(NULL, cm);
}
/* No channel mode, no BotServ, but using ChanServ as the botserv bot
* which was assigned when persist was set on
*/
else if (!cm && !BotServ && ci->bi)
{
if (!ChanServ)
{
source.Reply(_("ChanServ is required to enable persist on this network."));
return;
}
/* Unassign bot */
ChanServ->UnAssign(NULL, ci);
}
}
Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to disable persist";
source.Reply(_("Channel \002%s\002 is no longer persistent."), ci->name.c_str());
}
else
this->OnSyntaxError(source, "PERSIST");
}
bool OnHelp(CommandSource &source, const Anope::string &) override
{
BotInfo *BotServ = Config->GetClient("BotServ");
BotInfo *ChanServ = Config->GetClient("ChanServ");
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_(
"Enables or disables the persistent channel setting. "
"When persistent is set, the service bot will remain "
"in the channel when it has emptied of users. "
"\n\n"
"If your IRCd does not have a permanent (persistent) channel "
"mode you must have a service bot in your channel to "
"set persist on, and it can not be unassigned while persist "
"is on."
"\n\n"
"If this network does not have %s enabled and does "
"not have a permanent channel mode, %s will "
"join your channel when you set persist on (and leave when "
"it has been set off)."
"\n\n"
"If your IRCd has a permanent (persistent) channel mode "
"and it is set or unset (for any reason, including MODE LOCK), "
"persist is automatically set and unset for the channel as well. "
"Additionally, services will set or unset this mode when you "
"set persist on or off."
),
BotServ ? BotServ->nick.c_str() : "BotServ",
ChanServ ? ChanServ->nick.c_str() : "ChanServ"
);
return true;
}
};
class CommandCSSetRestricted final
: public Command
{
public:
CommandCSSetRestricted(Module *creator, const Anope::string &cname = "chanserv/set/restricted") : Command(creator, cname, 2, 2)
{
this->SetDesc(_("Restrict access to the channel"));
this->SetSyntax(_("\037channel\037 {ON | OFF}"));
}
void Execute(CommandSource &source, const std::vector<Anope::string> &params) override
{
if (Anope::ReadOnly)
{
source.Reply(READ_ONLY_MODE);
return;
}
ChannelInfo *ci = ChannelInfo::Find(params[0]);
if (ci == NULL)
{
source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str());
return;
}
EventReturn MOD_RESULT;
FOREACH_RESULT(OnSetChannelOption, MOD_RESULT, (source, this, ci, params[1]));
if (MOD_RESULT == EVENT_STOP)
return;
if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration"))
{
source.Reply(ACCESS_DENIED);
return;
}
if (params[1].equals_ci("ON"))
{
Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to enable restricted";
ci->Extend<bool>("RESTRICTED");
source.Reply(_("Restricted access option for %s is now \002on\002."), ci->name.c_str());
}
else if (params[1].equals_ci("OFF"))
{
Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to disable restricted";
ci->Shrink<bool>("RESTRICTED");
source.Reply(_("Restricted access option for %s is now \002off\002."), ci->name.c_str());
}
else
this->OnSyntaxError(source, "RESTRICTED");
}
bool OnHelp(CommandSource &source, const Anope::string &) override
{
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_(
"Enables or disables the \002restricted access\002 option for a "
"channel. When \002restricted access\002 is set, users not on the access list will "
"instead be kicked and banned from the channel."
));
return true;
}
};
class CommandCSSetSecureFounder final
: public Command
{
public:
CommandCSSetSecureFounder(Module *creator, const Anope::string &cname = "chanserv/set/securefounder") : Command(creator, cname, 2, 2)
{
this->SetDesc(_("Stricter control of channel founder status"));
this->SetSyntax(_("\037channel\037 {ON | OFF}"));
}
void Execute(CommandSource &source, const std::vector<Anope::string> &params) override
{
if (Anope::ReadOnly)
{
source.Reply(READ_ONLY_MODE);
return;
}
ChannelInfo *ci = ChannelInfo::Find(params[0]);
if (ci == NULL)
{
source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str());
return;
}
EventReturn MOD_RESULT;
FOREACH_RESULT(OnSetChannelOption, MOD_RESULT, (source, this, ci, params[1]));
if (MOD_RESULT == EVENT_STOP)
return;
if (MOD_RESULT != EVENT_ALLOW && (ci->HasExt("SECUREFOUNDER") ? !source.IsFounder(ci) : !source.AccessFor(ci).HasPriv("FOUNDER")) && source.permission.empty() && !source.HasPriv("chanserv/administration"))
{
source.Reply(ACCESS_DENIED);
return;
}
if (params[1].equals_ci("ON"))
{
Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to enable secure founder";
ci->Extend<bool>("SECUREFOUNDER");
source.Reply(_("Secure founder option for %s is now \002on\002."), ci->name.c_str());
}
else if (params[1].equals_ci("OFF"))
{
Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to disable secure founder";
ci->Shrink<bool>("SECUREFOUNDER");
source.Reply(_("Secure founder option for %s is now \002off\002."), ci->name.c_str());
}
else
this->OnSyntaxError(source, "SECUREFOUNDER");
}
bool OnHelp(CommandSource &source, const Anope::string &) override
{
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_(
"Enables or disables the \002secure founder\002 option for a channel. "
"When \002secure founder\002 is set, only the real founder will be "
"able to drop the channel, change its founder and its successor, "
"and not those who have founder level access through "
"the access/qop command."
));
return true;
}
};
class CommandCSSetSecureOps final
: public Command
{
public:
CommandCSSetSecureOps(Module *creator, const Anope::string &cname = "chanserv/set/secureops") : Command(creator, cname, 2, 2)
{
this->SetDesc(_("Stricter control of chanop status"));
this->SetSyntax(_("\037channel\037 {ON | OFF}"));
}
void Execute(CommandSource &source, const std::vector<Anope::string> &params) override
{
if (Anope::ReadOnly)
{
source.Reply(READ_ONLY_MODE);
return;
}
ChannelInfo *ci = ChannelInfo::Find(params[0]);
if (ci == NULL)
{
source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str());
return;
}
EventReturn MOD_RESULT;
FOREACH_RESULT(OnSetChannelOption, MOD_RESULT, (source, this, ci, params[1]));
if (MOD_RESULT == EVENT_STOP)
return;
if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration"))
{
source.Reply(ACCESS_DENIED);
return;
}
if (params[1].equals_ci("ON"))
{
Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to enable secure ops";
ci->Extend<bool>("SECUREOPS");
source.Reply(_("Secure ops option for %s is now \002on\002."), ci->name.c_str());
}
else if (params[1].equals_ci("OFF"))
{
Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to disable secure ops";
ci->Shrink<bool>("SECUREOPS");
source.Reply(_("Secure ops option for %s is now \002off\002."), ci->name.c_str());
}
else
this->OnSyntaxError(source, "SECUREOPS");
}
bool OnHelp(CommandSource &source, const Anope::string &) override
{
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_(
"Enables or disables the \002secure ops\002 option for a channel. "
"When \002secure ops\002 is set, users who are not on the access list "
"will not be allowed channel operator status."
));
return true;
}
};
class CommandCSSetSignKick final
: public Command
{
public:
CommandCSSetSignKick(Module *creator, const Anope::string &cname = "chanserv/set/signkick") : Command(creator, cname, 2, 2)
{
this->SetDesc(_("Sign kicks that are done with the KICK command"));
this->SetSyntax(_("\037channel\037 {ON | LEVEL | OFF}"));
}
void Execute(CommandSource &source, const std::vector<Anope::string> &params) override
{
if (Anope::ReadOnly)
{
source.Reply(READ_ONLY_MODE);
return;
}
ChannelInfo *ci = ChannelInfo::Find(params[0]);
if (ci == NULL)
{
source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str());
return;
}
EventReturn MOD_RESULT;
FOREACH_RESULT(OnSetChannelOption, MOD_RESULT, (source, this, ci, params[1]));
if (MOD_RESULT == EVENT_STOP)
return;
if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration"))
{
source.Reply(ACCESS_DENIED);
return;
}
if (params[1].equals_ci("ON"))
{
ci->Extend<bool>("SIGNKICK");
ci->Shrink<bool>("SIGNKICK_LEVEL");
source.Reply(_("Signed kick option for %s is now \002on\002."), ci->name.c_str());
Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to enable sign kick";
}
else if (params[1].equals_ci("LEVEL"))
{
ci->Extend<bool>("SIGNKICK_LEVEL");
ci->Shrink<bool>("SIGNKICK");
source.Reply(_("Signed kick option for %s is now \002on\002, but depends of the level of the user that is using the command."),
ci->name.c_str());
Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to enable sign kick level";
}
else if (params[1].equals_ci("OFF"))
{
ci->Shrink<bool>("SIGNKICK");
ci->Shrink<bool>("SIGNKICK_LEVEL");
source.Reply(_("Signed kick option for %s is now \002off\002."), ci->name.c_str());
Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to disable sign kick";
}
else
this->OnSyntaxError(source, "SIGNKICK");
}
bool OnHelp(CommandSource &source, const Anope::string &) override
{
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_(
"Enables or disables signed kicks for a "
"channel. When \002SIGNKICK\002 is set, kicks issued with "
"the \002KICK\002 command will have the nick that used the "
"command in their reason."
"\n\n"
"If you use \002LEVEL\002, those who have a level that is superior "
"or equal to the SIGNKICK level on the channel won't have their "
"kicks signed."
));
return true;
}
};
class CommandCSSetSuccessor final
: public Command
{
public:
CommandCSSetSuccessor(Module *creator, const Anope::string &cname = "chanserv/set/successor") : Command(creator, cname, 1, 2)
{
this->SetDesc(_("Set the successor for a channel"));
this->SetSyntax(_("\037channel\037 [\037nick\037]"));
}
void Execute(CommandSource &source, const std::vector<Anope::string> &params) override
{
if (Anope::ReadOnly)
{
source.Reply(READ_ONLY_MODE);
return;
}
ChannelInfo *ci = ChannelInfo::Find(params[0]);
const Anope::string &param = params.size() > 1 ? params[1] : "";
if (ci == NULL)
{
source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str());
return;
}
EventReturn MOD_RESULT;
FOREACH_RESULT(OnSetChannelOption, MOD_RESULT, (source, this, ci, param));
if (MOD_RESULT == EVENT_STOP)
return;
auto can_set_successor = ci->HasExt("SECUREFOUNDER")
? source.IsFounder(ci)
: source.AccessFor(ci).HasPriv("FOUNDER");
// Special case: users can remove themselves as successor with no other privs.
if (param.empty() && source.GetAccount() && source.GetAccount() == ci->GetSuccessor())
can_set_successor = true;
if (MOD_RESULT != EVENT_ALLOW && !can_set_successor && source.permission.empty() && !source.HasPriv("chanserv/administration"))
{
source.Reply(ACCESS_DENIED);
return;
}
NickCore *nc;
if (!param.empty())
{
const NickAlias *na = NickAlias::Find(param);
if (!na)
{
source.Reply(NICK_X_NOT_REGISTERED, param.c_str());
return;
}
else if (na->nc->HasExt("NEVEROP"))
{
source.Reply(_("\002%s\002 does not wish to be added to channel access lists."),
na->nc->display.c_str());
return;
}
if (na->nc == ci->GetFounder())
{
source.Reply(_("%s cannot be the successor on channel %s as they are the founder."), na->nick.c_str(), ci->name.c_str());
return;
}
nc = na->nc;
}
else
nc = NULL;
Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to change the successor from " << (ci->GetSuccessor() ? ci->GetSuccessor()->display : "(none)") << " to " << (nc ? nc->display : "(none)");
ci->SetSuccessor(nc);
if (nc)
source.Reply(_("Successor for \002%s\002 changed to \002%s\002."), ci->name.c_str(), nc->display.c_str());
else
source.Reply(_("Successor for \002%s\002 unset."), ci->name.c_str());
}
bool OnHelp(CommandSource &source, const Anope::string &) override
{
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_(
"Changes the successor of a channel. If the founder's "
"nickname expires or is dropped while the channel is still "
"registered, the successor will become the new founder of the "
"channel. The successor's nickname must be a registered one. "
"If there's no successor set, then the first nickname on the "
"access list (with the highest access, if applicable) will "
"become the new founder, but if the access list is empty, the "
"channel will be dropped."
));
auto max_reg = Config->GetModule("chanserv").Get<unsigned>("maxregistered");
if (max_reg)
{
source.Reply(" ");
source.Reply(_(
"Note, however, if the successor already has too many "
"channels registered (%u), they will not be able to "
"become the new founder and it will be as if the "
"channel had no successor set."
),
max_reg);
}
return true;
}
};
class CommandCSSetNoexpire final
: public Command
{
public:
CommandCSSetNoexpire(Module *creator) : Command(creator, "chanserv/saset/noexpire", 2, 2)
{
this->SetDesc(_("Prevent the channel from expiring"));
this->SetSyntax(_("\037channel\037 {ON | OFF}"));
}
void Execute(CommandSource &source, const std::vector<Anope::string> &params) override
{
if (Anope::ReadOnly)
{
source.Reply(READ_ONLY_MODE);
return;
}
ChannelInfo *ci = ChannelInfo::Find(params[0]);
if (ci == NULL)
{
source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str());
return;
}
if (source.permission.empty() && !source.AccessFor(ci).HasPriv("SET"))
{
source.Reply(ACCESS_DENIED);
return;
}
if (params[1].equals_ci("ON"))
{
Log(LOG_ADMIN, source, this, ci) << "to enable noexpire";
ci->Extend<bool>("CS_NO_EXPIRE");
source.Reply(_("Channel %s \002will not\002 expire."), ci->name.c_str());
}
else if (params[1].equals_ci("OFF"))
{
Log(LOG_ADMIN, source, this, ci) << "to disable noexpire";
ci->Shrink<bool>("CS_NO_EXPIRE");
source.Reply(_("Channel %s \002will\002 expire."), ci->name.c_str());
}
else
this->OnSyntaxError(source, "NOEXPIRE");
}
bool OnHelp(CommandSource &source, const Anope::string &) override
{
this->SendSyntax(source);
source.Reply(" ");
source.Reply(_(
"Sets whether the given channel will expire. Setting this "
"to ON prevents the channel from expiring."
));
return true;
}
};
class CSSet final
: public Module
{
SerializableExtensibleItem<bool> noautoop, peace, securefounder,
restricted, secureops, signkick, signkick_level, noexpire,
persist;
struct KeepModes final
: SerializableExtensibleItem<bool>
{
KeepModes(Module *m, const Anope::string &n) : SerializableExtensibleItem<bool>(m, n) { }
void ExtensibleSerialize(const Extensible *e, const Serializable *s, Serialize::Data &data) const override
{
SerializableExtensibleItem<bool>::ExtensibleSerialize(e, s, data);
if (s->GetSerializableType()->GetName() != CHANNELINFO_TYPE)
return;
const auto *ci = anope_dynamic_static_cast<const ChannelInfo *>(s);
Anope::string modes;
for (const auto &[last_mode, last_data] : ci->last_modes)
{
if (!modes.empty())
modes += " ";
modes += '+';
modes += last_mode;
if (!last_data.value.empty())
{
modes += "," + Anope::ToString(last_data.set_at);
modes += "," + last_data.set_by;
modes += "," + last_data.value;
}
}
data.Store("last_modes", modes);
}
void ExtensibleUnserialize(Extensible *e, Serializable *s, Serialize::Data &data) override
{
SerializableExtensibleItem<bool>::ExtensibleUnserialize(e, s, data);
if (s->GetSerializableType()->GetName() != CHANNELINFO_TYPE)
return;
auto *ci = anope_dynamic_static_cast<ChannelInfo *>(s);
ci->last_modes.clear();
spacesepstream sep(data.Load("last_modes"));
for (Anope::string modes; sep.GetToken(modes);)
{
if (modes[0] == '+')
{
commasepstream mode(modes, true);
mode.GetToken(modes);
modes.erase(0, 1);
ModeData info;
Anope::string set_at;
mode.GetToken(set_at);
info.set_at = Anope::Convert(set_at, 0);
mode.GetToken(info.set_by);
info.value = mode.GetRemaining();
ci->last_modes.emplace(modes, info);
continue;
}
else
{
// Begin 2.0 compatibility.
size_t c = modes.find(',');
if (c == Anope::string::npos)
ci->last_modes.emplace(modes, ModeData());
else
ci->last_modes.emplace(modes.substr(0, c), ModeData(modes.substr(c + 1)));
// End 2.0 compatibility.
}
}
}
} keep_modes;
CommandCSSet commandcsset;
CommandCSSetAutoOp commandcssetautoop;
CommandCSSetBanType commandcssetbantype;
CommandCSSetDescription commandcssetdescription;
CommandCSSetFounder commandcssetfounder;
CommandCSSetKeepModes commandcssetkeepmodes;
CommandCSSetPeace commandcssetpeace;
CommandCSSetPersist commandcssetpersist;
CommandCSSetRestricted commandcssetrestricted;
CommandCSSetSecureFounder commandcssetsecurefounder;
CommandCSSetSecureOps commandcssetsecureops;
CommandCSSetSignKick commandcssetsignkick;
CommandCSSetSuccessor commandcssetsuccessor;
CommandCSSetNoexpire commandcssetnoexpire;
ExtensibleRef<bool> inhabit;
bool persist_lower_ts;
public:
CSSet(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR),
noautoop(this, "NOAUTOOP"), peace(this, "PEACE"),
securefounder(this, "SECUREFOUNDER"), restricted(this, "RESTRICTED"),
secureops(this, "SECUREOPS"), signkick(this, "SIGNKICK"),
signkick_level(this, "SIGNKICK_LEVEL"), noexpire(this, "CS_NO_EXPIRE"),
persist(this, "PERSIST"),
keep_modes(this, "CS_KEEP_MODES"),
commandcsset(this), commandcssetautoop(this), commandcssetbantype(this),
commandcssetdescription(this), commandcssetfounder(this), commandcssetkeepmodes(this),
commandcssetpeace(this), commandcssetpersist(this), commandcssetrestricted(this),
commandcssetsecurefounder(this), commandcssetsecureops(this), commandcssetsignkick(this),
commandcssetsuccessor(this), commandcssetnoexpire(this),
inhabit("inhabit")
{
}
void OnReload(Configuration::Conf &conf) override
{
persist_lower_ts = conf.GetModule(this).Get<bool>("persist_lower_ts");
}
void OnCreateChan(ChannelInfo *ci) override
{
ci->bantype = Config->GetModule(this).Get<int>("defbantype", "2");
}
void OnChannelSync(Channel *c) override
{
if (c->ci && keep_modes.HasExt(c->ci))
{
Channel::ModeList ml = c->ci->last_modes;
for (const auto &[last_mode, last_data] : ml)
c->SetMode(c->ci->WhoSends(), last_mode, last_data);
}
}
EventReturn OnCheckKick(User *u, Channel *c, Anope::string &mask, Anope::string &reason) override
{
if (!c->ci || !restricted.HasExt(c->ci) || c->MatchesList(u, "EXCEPT"))
return EVENT_CONTINUE;
if (c->ci->AccessFor(u).empty() && (!c->ci->GetFounder() || u->Account() != c->ci->GetFounder()))
return EVENT_STOP;
return EVENT_CONTINUE;
}
void OnDelChan(ChannelInfo *ci) override
{
if (ci->c && persist.HasExt(ci))
ci->c->RemoveMode(ci->WhoSends(), "PERM", "", false);
persist.Unset(ci);
}
EventReturn OnChannelModeSet(Channel *c, MessageSource &setter, ChannelMode *mode, const ModeData &data) override
{
if (c->ci)
{
/* Channel mode +P or so was set, mark this channel as persistent */
if (mode->name == "PERM")
persist.Set(c->ci, true);
if (mode->type != MODE_STATUS && !c->syncing && Me->IsSynced() && (!inhabit || !inhabit->HasExt(c)))
c->ci->last_modes = c->GetModes();
}
return EVENT_CONTINUE;
}
EventReturn OnChannelModeUnset(Channel *c, MessageSource &setter, ChannelMode *mode, const Anope::string &param) override
{
if (mode->name == "PERM")
{
if (c->ci)
persist.Unset(c->ci);
}
if (c->ci && mode->type != MODE_STATUS && !c->syncing && Me->IsSynced() && (!inhabit || !inhabit->HasExt(c)))
c->ci->last_modes = c->GetModes();
return EVENT_CONTINUE;
}
void OnJoinChannel(User *u, Channel *c) override
{
if (u->server != Me && persist_lower_ts && c->ci && persist.HasExt(c->ci) && c->created > c->ci->registered)
{
Log(LOG_DEBUG) << "Changing TS of " << c->name << " from " << c->created << " to " << c->ci->registered;
c->created = c->ci->registered;
IRCD->SendChannel(c);
c->Reset();
}
}
void OnSetCorrectModes(User *user, Channel *chan, AccessGroup &access, bool &give_modes, bool &take_modes) override
{
if (chan->ci)
{
if (noautoop.HasExt(chan->ci))
give_modes = false;
if (secureops.HasExt(chan->ci) && !user->HasPriv("chanserv/administration"))
// This overrides what chanserv does because it is loaded after chanserv
take_modes = true;
}
}
void OnPreChanExpire(ChannelInfo *ci, bool &expire) override
{
if (noexpire.HasExt(ci))
expire = false;
}
void OnChanInfo(CommandSource &source, ChannelInfo *ci, InfoFormatter &info, bool show_all) override
{
if (!show_all)
return;
if (peace.HasExt(ci))
info.AddOption(_("Peace"));
if (restricted.HasExt(ci))
info.AddOption(_("Restricted access"));
if (securefounder.HasExt(ci))
info.AddOption(_("Secure founder"));
if (secureops.HasExt(ci))
info.AddOption(_("Secure ops"));
if (signkick.HasExt(ci) || signkick_level.HasExt(ci))
info.AddOption(_("Signed kicks"));
if (persist.HasExt(ci))
info.AddOption(_("Persistent"));
if (noexpire.HasExt(ci))
info.AddOption(_("No expiry"));
if (keep_modes.HasExt(ci))
info.AddOption(_("Keep modes"));
if (noautoop.HasExt(ci))
info.AddOption(_("No auto-op"));
}
};
MODULE_INIT(CSSet)