diff options
| author | Sadie Powell <sadie@witchery.services> | 2024-01-24 12:34:03 +0000 |
|---|---|---|
| committer | Sadie Powell <sadie@witchery.services> | 2024-01-24 12:34:34 +0000 |
| commit | 484160eb4ed560eeda97c94a42dd8e31431ab251 (patch) | |
| tree | 03600af16b55fa2f268904d7a42a8b87f86c09f4 /modules/hostserv | |
| parent | 7ac1fe58478d58e2480b6919c4abf3a82929169c (diff) | |
Shuffle modules around a bit.
Diffstat (limited to 'modules/hostserv')
| -rw-r--r-- | modules/hostserv/hostserv.cpp | 131 | ||||
| -rw-r--r-- | modules/hostserv/hs_del.cpp | 116 | ||||
| -rw-r--r-- | modules/hostserv/hs_group.cpp | 118 | ||||
| -rw-r--r-- | modules/hostserv/hs_list.cpp | 160 | ||||
| -rw-r--r-- | modules/hostserv/hs_off.cpp | 71 | ||||
| -rw-r--r-- | modules/hostserv/hs_on.cpp | 77 | ||||
| -rw-r--r-- | modules/hostserv/hs_request.cpp | 399 | ||||
| -rw-r--r-- | modules/hostserv/hs_set.cpp | 231 |
8 files changed, 1303 insertions, 0 deletions
diff --git a/modules/hostserv/hostserv.cpp b/modules/hostserv/hostserv.cpp new file mode 100644 index 000000000..17c820377 --- /dev/null +++ b/modules/hostserv/hostserv.cpp @@ -0,0 +1,131 @@ +/* HostServ core functions + * + * (C) 2003-2024 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" + +class HostServCore final + : public Module +{ + Reference<BotInfo> HostServ; +public: + HostServCore(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, PSEUDOCLIENT | VENDOR) + { + if (!IRCD || !IRCD->CanSetVHost) + throw ModuleException("Your IRCd does not support vhosts"); + } + + void OnReload(Configuration::Conf *conf) override + { + const Anope::string &hsnick = conf->GetModule(this)->Get<const Anope::string>("client"); + + if (hsnick.empty()) + throw ConfigException(Module::name + ": <client> must be defined"); + + BotInfo *bi = BotInfo::Find(hsnick, true); + if (!bi) + throw ConfigException(Module::name + ": no bot named " + hsnick); + + HostServ = bi; + } + + void OnUserLogin(User *u) override + { + if (!IRCD->CanSetVHost) + return; + + const NickAlias *na = NickAlias::Find(u->nick); + if (!na || na->nc != u->Account() || !na->HasVhost()) + na = NickAlias::Find(u->Account()->display); + if (!na || !na->HasVhost()) + return; + + if (u->vhost.empty() || !u->vhost.equals_cs(na->GetVhostHost()) || (!na->GetVhostIdent().empty() && !u->GetVIdent().equals_cs(na->GetVhostIdent()))) + { + IRCD->SendVhost(u, na->GetVhostIdent(), na->GetVhostHost()); + + u->vhost = na->GetVhostHost(); + u->UpdateHost(); + + if (IRCD->CanSetVIdent && !na->GetVhostIdent().empty()) + u->SetVIdent(na->GetVhostIdent()); + + if (HostServ) + { + if (!na->GetVhostIdent().empty()) + u->SendMessage(HostServ, _("Your vhost of \002%s\002@\002%s\002 is now activated."), na->GetVhostIdent().c_str(), na->GetVhostHost().c_str()); + else + u->SendMessage(HostServ, _("Your vhost of \002%s\002 is now activated."), na->GetVhostHost().c_str()); + } + } + } + + void OnNickDrop(CommandSource &source, NickAlias *na) override + { + if (na->HasVhost()) + { + FOREACH_MOD(OnDeleteVhost, (na)); + na->RemoveVhost(); + } + } + + void OnNickUpdate(User *u) override + { + this->OnUserLogin(u); + } + + EventReturn OnPreHelp(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + if (!params.empty() || source.c || source.service != *HostServ) + return EVENT_CONTINUE; + source.Reply(_("%s commands:"), HostServ->nick.c_str()); + return EVENT_CONTINUE; + } + + void OnSetVhost(NickAlias *na) override + { + if (Config->GetModule(this)->Get<bool>("activate_on_set")) + { + User *u = User::Find(na->nick); + + if (u && u->Account() == na->nc) + { + IRCD->SendVhost(u, na->GetVhostIdent(), na->GetVhostHost()); + + u->vhost = na->GetVhostHost(); + u->UpdateHost(); + + if (IRCD->CanSetVIdent && !na->GetVhostIdent().empty()) + u->SetVIdent(na->GetVhostIdent()); + + if (HostServ) + { + if (!na->GetVhostIdent().empty()) + u->SendMessage(HostServ, _("Your vhost of \002%s\002@\002%s\002 is now activated."), na->GetVhostIdent().c_str(), na->GetVhostHost().c_str()); + else + u->SendMessage(HostServ, _("Your vhost of \002%s\002 is now activated."), na->GetVhostHost().c_str()); + } + } + } + } + + void OnDeleteVhost(NickAlias *na) override + { + if (Config->GetModule(this)->Get<bool>("activate_on_set")) + { + User *u = User::Find(na->nick); + + if (u && u->Account() == na->nc) + IRCD->SendVhostDel(u); + } + } +}; + +MODULE_INIT(HostServCore) diff --git a/modules/hostserv/hs_del.cpp b/modules/hostserv/hs_del.cpp new file mode 100644 index 000000000..eb60afc00 --- /dev/null +++ b/modules/hostserv/hs_del.cpp @@ -0,0 +1,116 @@ +/* HostServ core functions + * + * (C) 2003-2024 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" + +class CommandHSDel final + : public Command +{ +public: + CommandHSDel(Module *creator) : Command(creator, "hostserv/del", 1, 1) + { + this->SetDesc(_("Delete the vhost of another user")); + this->SetSyntax(_("\037nick\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + if (Anope::ReadOnly) + { + source.Reply(READ_ONLY_MODE); + return; + } + + const Anope::string &nick = params[0]; + NickAlias *na = NickAlias::Find(nick); + if (na) + { + Log(LOG_ADMIN, source, this) << "for user " << na->nick; + FOREACH_MOD(OnDeleteVhost, (na)); + na->RemoveVhost(); + source.Reply(_("Vhost for \002%s\002 removed."), nick.c_str()); + } + else + source.Reply(NICK_X_NOT_REGISTERED, nick.c_str()); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Deletes the vhost assigned to the given nick from the\n" + "database.")); + return true; + } +}; + +class CommandHSDelAll final + : public Command +{ +public: + CommandHSDelAll(Module *creator) : Command(creator, "hostserv/delall", 1, 1) + { + this->SetDesc(_("Deletes the vhost for all nicks in a group")); + this->SetSyntax(_("\037nick\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + if (Anope::ReadOnly) + { + source.Reply(READ_ONLY_MODE); + return; + } + + const Anope::string &nick = params[0]; + NickAlias *na = NickAlias::Find(nick); + if (na) + { + FOREACH_MOD(OnDeleteVhost, (na)); + const NickCore *nc = na->nc; + for (auto *alias : *nc->aliases) + { + na = alias; + na->RemoveVhost(); + } + Log(LOG_ADMIN, source, this) << "for all nicks in group " << nc->display; + source.Reply(_("vhosts for group \002%s\002 have been removed."), nc->display.c_str()); + } + else + source.Reply(NICK_X_NOT_REGISTERED, nick.c_str()); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Deletes the vhost for all nicks in the same group as\n" + "that of the given nick.")); + return true; + } +}; + +class HSDel final + : public Module +{ + CommandHSDel commandhsdel; + CommandHSDelAll commandhsdelall; + +public: + HSDel(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR), + commandhsdel(this), commandhsdelall(this) + { + if (!IRCD || !IRCD->CanSetVHost) + throw ModuleException("Your IRCd does not support vhosts"); + } +}; + +MODULE_INIT(HSDel) diff --git a/modules/hostserv/hs_group.cpp b/modules/hostserv/hs_group.cpp new file mode 100644 index 000000000..9db0bbb00 --- /dev/null +++ b/modules/hostserv/hs_group.cpp @@ -0,0 +1,118 @@ +/* HostServ core functions + * + * (C) 2003-2024 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" + +class CommandHSGroup final + : public Command +{ + bool setting = false; + +public: + void Sync(const NickAlias *na) + { + if (setting) + return; + + if (!na || !na->HasVhost()) + return; + + setting = true; + for (auto *nick : *na->nc->aliases) + { + if (nick) + { + nick->SetVhost(na->GetVhostIdent(), na->GetVhostHost(), na->GetVhostCreator()); + FOREACH_MOD(OnSetVhost, (nick)); + } + } + setting = false; + } + + CommandHSGroup(Module *creator) : Command(creator, "hostserv/group", 0, 0) + { + this->SetDesc(_("Syncs the vhost for all nicks in a group")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + if (Anope::ReadOnly) + { + source.Reply(READ_ONLY_MODE); + return; + } + + NickAlias *na = NickAlias::Find(source.GetNick()); + if (na && source.GetAccount() == na->nc && na->HasVhost()) + { + this->Sync(na); + if (!na->GetVhostIdent().empty()) + source.Reply(_("All vhosts in the group \002%s\002 have been set to \002%s\002@\002%s\002."), source.nc->display.c_str(), na->GetVhostIdent().c_str(), na->GetVhostHost().c_str()); + else + source.Reply(_("All vhosts in the group \002%s\002 have been set to \002%s\002."), source.nc->display.c_str(), na->GetVhostHost().c_str()); + } + else + source.Reply(HOST_NOT_ASSIGNED); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("This command allows users to set the vhost of their\n" + "CURRENT nick to be the vhost for all nicks in the same\n" + "group.")); + return true; + } +}; + +class HSGroup final + : public Module +{ + CommandHSGroup commandhsgroup; + bool syncongroup; + bool synconset; + +public: + HSGroup(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR), + commandhsgroup(this) + { + if (!IRCD || !IRCD->CanSetVHost) + throw ModuleException("Your IRCd does not support vhosts"); + } + + void OnSetVhost(NickAlias *na) override + { + if (!synconset) + return; + + commandhsgroup.Sync(na); + } + + void OnNickGroup(User *u, NickAlias *na) override + { + if (!syncongroup) + return; + + commandhsgroup.Sync(na); + } + + void OnReload(Configuration::Conf *conf) override + { + Configuration::Block *block = conf->GetModule(this); + syncongroup = block->Get<bool>("syncongroup"); + synconset = block->Get<bool>("synconset"); + } +}; + +MODULE_INIT(HSGroup) diff --git a/modules/hostserv/hs_list.cpp b/modules/hostserv/hs_list.cpp new file mode 100644 index 000000000..8cda26c38 --- /dev/null +++ b/modules/hostserv/hs_list.cpp @@ -0,0 +1,160 @@ +/* HostServ core functions + * + * (C) 2003-2024 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" + +class CommandHSList final + : public Command +{ +public: + CommandHSList(Module *creator) : Command(creator, "hostserv/list", 0, 1) + { + this->SetDesc(_("Displays one or more vhost entries")); + this->SetSyntax(_("[\037key\037|\037#X-Y\037]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + const Anope::string &key = !params.empty() ? params[0] : ""; + int from = 0, to = 0, counter = 1; + + /** + * Do a check for a range here, then in the next loop + * we'll only display what has been requested.. + **/ + if (!key.empty() && key[0] == '#') + { + size_t tmp = key.find('-'); + if (tmp == Anope::string::npos || tmp == key.length() || tmp == 1) + { + source.Reply(LIST_INCORRECT_RANGE); + return; + } + for (unsigned i = 1, end = key.length(); i < end; ++i) + { + if (!isdigit(key[i]) && i != tmp) + { + source.Reply(LIST_INCORRECT_RANGE); + return; + } + try + { + from = convertTo<int>(key.substr(1, tmp - 1)); + to = convertTo<int>(key.substr(tmp + 1)); + } + catch (const ConvertException &) { } + } + } + + unsigned display_counter = 0, listmax = Config->GetModule(this->owner)->Get<unsigned>("listmax", "50"); + ListFormatter list(source.GetAccount()); + list.AddColumn(_("Number")).AddColumn(_("Nick")).AddColumn(_("Vhost")).AddColumn(_("Creator")).AddColumn(_("Created")); + + for (const auto &[_, na] : *NickAliasList) + { + if (!na->HasVhost()) + continue; + + if (!key.empty() && key[0] != '#') + { + if ((Anope::Match(na->nick, key) || Anope::Match(na->GetVhostHost(), key)) && display_counter < listmax) + { + ++display_counter; + + ListFormatter::ListEntry entry; + entry["Number"] = stringify(display_counter); + entry["Nick"] = na->nick; + if (!na->GetVhostIdent().empty()) + entry["Vhost"] = na->GetVhostIdent() + "@" + na->GetVhostHost(); + else + entry["Vhost"] = na->GetVhostHost(); + entry["Creator"] = na->GetVhostCreator(); + entry["Created"] = Anope::strftime(na->GetVhostCreated(), NULL, true); + list.AddEntry(entry); + } + } + else + { + /** + * List the host if its in the display range, and not more + * than NSListMax records have been displayed... + **/ + if (((counter >= from && counter <= to) || (!from && !to)) && display_counter < listmax) + { + ++display_counter; + ListFormatter::ListEntry entry; + entry["Number"] = stringify(display_counter); + entry["Nick"] = na->nick; + if (!na->GetVhostIdent().empty()) + entry["Vhost"] = na->GetVhostIdent() + "@" + na->GetVhostHost(); + else + entry["Vhost"] = na->GetVhostHost(); + entry["Creator"] = na->GetVhostCreator(); + entry["Created"] = Anope::strftime(na->GetVhostCreated(), NULL, true); + list.AddEntry(entry); + } + } + ++counter; + } + + if (!display_counter) + { + source.Reply(_("No records to display.")); + return; + } + + if (!key.empty()) + source.Reply(_("Displayed records matching key \002%s\002 (count: \002%d\002)."), key.c_str(), display_counter); + else + { + if (from) + source.Reply(_("Displayed records from \002%d\002 to \002%d\002."), from, to); + else + source.Reply(_("Displayed all records (count: \002%d\002)."), display_counter); + } + + std::vector<Anope::string> replies; + list.Process(replies); + + for (const auto &reply : replies) + source.Reply(reply); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("This command lists registered vhosts to the operator.\n" + "If a \037key\037 is specified, only entries whose nick or vhost match\n" + "the pattern given in \037key\037 are displayed e.g. Rob* for all\n" + "entries beginning with \"Rob\"\n" + "If a \037#X-Y\037 style is used, only entries between the range of \002X\002\n" + "and \002Y\002 will be displayed, e.g. \002#1-3\002 will display the first 3\n" + "nick/vhost entries.")); + return true; + } +}; + +class HSList final + : public Module +{ + CommandHSList commandhslist; + +public: + HSList(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR), + commandhslist(this) + { + if (!IRCD || !IRCD->CanSetVHost) + throw ModuleException("Your IRCd does not support vhosts"); + } +}; + +MODULE_INIT(HSList) diff --git a/modules/hostserv/hs_off.cpp b/modules/hostserv/hs_off.cpp new file mode 100644 index 000000000..8e4036726 --- /dev/null +++ b/modules/hostserv/hs_off.cpp @@ -0,0 +1,71 @@ +/* HostServ core functions + * + * (C) 2003-2024 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" + +class CommandHSOff final + : public Command +{ +public: + CommandHSOff(Module *creator) : Command(creator, "hostserv/off", 0, 0) + { + this->SetDesc(_("Deactivates your assigned vhost")); + this->RequireUser(true); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + User *u = source.GetUser(); + + const NickAlias *na = NickAlias::Find(u->nick); + if (!na || na->nc != u->Account() || !na->HasVhost()) + na = NickAlias::Find(u->Account()->display); + + if (!na || !na->HasVhost()) + source.Reply(HOST_NOT_ASSIGNED); + else + { + u->vhost.clear(); + IRCD->SendVhostDel(u); + u->UpdateHost(); + Log(LOG_COMMAND, source, this) << "to disable their vhost"; + source.Reply(_("Your vhost was removed and the normal cloaking restored.")); + } + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Deactivates the vhost currently assigned to the nick in use.\n" + "When you use this command any user who performs a /whois\n" + "on you will see your real host/IP address.")); + return true; + } +}; + +class HSOff final + : public Module +{ + CommandHSOff commandhsoff; + +public: + HSOff(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR), + commandhsoff(this) + { + if (!IRCD || !IRCD->CanSetVHost) + throw ModuleException("Your IRCd does not support vhosts"); + } +}; + +MODULE_INIT(HSOff) diff --git a/modules/hostserv/hs_on.cpp b/modules/hostserv/hs_on.cpp new file mode 100644 index 000000000..c8c01b7b2 --- /dev/null +++ b/modules/hostserv/hs_on.cpp @@ -0,0 +1,77 @@ +/* HostServ core functions + * + * (C) 2003-2024 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" + +class CommandHSOn final + : public Command +{ +public: + CommandHSOn(Module *creator) : Command(creator, "hostserv/on", 0, 0) + { + this->SetDesc(_("Activates your assigned vhost")); + this->RequireUser(true); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + if (!IRCD->CanSetVHost) + return; // HostServ wouldn't even be loaded at this point + + User *u = source.GetUser(); + const NickAlias *na = NickAlias::Find(u->nick); + if (!na || na->nc != u->Account() || !na->HasVhost()) + na = NickAlias::Find(u->Account()->display); + if (na && u->Account() == na->nc && na->HasVhost()) + { + if (!na->GetVhostIdent().empty()) + source.Reply(_("Your vhost of \002%s\002@\002%s\002 is now activated."), na->GetVhostIdent().c_str(), na->GetVhostHost().c_str()); + else + source.Reply(_("Your vhost of \002%s\002 is now activated."), na->GetVhostHost().c_str()); + Log(LOG_COMMAND, source, this) << "to enable their vhost of " << (!na->GetVhostIdent().empty() ? na->GetVhostIdent() + "@" : "") << na->GetVhostHost(); + IRCD->SendVhost(u, na->GetVhostIdent(), na->GetVhostHost()); + u->vhost = na->GetVhostHost(); + if (IRCD->CanSetVIdent && !na->GetVhostIdent().empty()) + u->SetVIdent(na->GetVhostIdent()); + u->UpdateHost(); + } + else + source.Reply(HOST_NOT_ASSIGNED); + + return; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Activates the vhost currently assigned to the nick in use.\n" + "When you use this command any user who performs a /whois\n" + "on you will see the vhost instead of your real host/IP address.")); + return true; + } +}; + +class HSOn final + : public Module +{ + CommandHSOn commandhson; + +public: + HSOn(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR), + commandhson(this) + { + if (!IRCD || !IRCD->CanSetVHost) + throw ModuleException("Your IRCd does not support vhosts"); + } +}; + +MODULE_INIT(HSOn) diff --git a/modules/hostserv/hs_request.cpp b/modules/hostserv/hs_request.cpp new file mode 100644 index 000000000..25f089647 --- /dev/null +++ b/modules/hostserv/hs_request.cpp @@ -0,0 +1,399 @@ +/* hs_request.c - Add request and activate functionality to HostServ + * + * (C) 2003-2024 Anope Team + * Contact us at team@anope.org + * + * Based on the original module by Rob <rob@anope.org> + * Included in the Anope module pack since Anope 1.7.11 + * Anope Coder: GeniusDex <geniusdex@anope.org> + * + * Please read COPYING and README for further details. + * + * Send bug reports to the Anope Coder instead of the module + * author, because any changes since the inclusion into anope + * are not supported by the original author. + */ + +#include "module.h" + +static ServiceReference<MemoServService> memoserv("MemoServService", "MemoServ"); + +static void req_send_memos(Module *me, CommandSource &source, const Anope::string &vIdent, const Anope::string &vHost); + +struct HostRequest final + : Serializable +{ + Anope::string nick; + Anope::string ident; + Anope::string host; + time_t time; + + HostRequest(Extensible *) : Serializable("HostRequest") { } + + void Serialize(Serialize::Data &data) const override + { + data["nick"] << this->nick; + data["ident"] << this->ident; + data["host"] << this->host; + data.SetType("time", Serialize::Data::DT_INT); data["time"] << this->time; + } + + static Serializable* Unserialize(Serializable *obj, Serialize::Data &data) + { + Anope::string snick; + data["nick"] >> snick; + + NickAlias *na = NickAlias::Find(snick); + if (na == NULL) + return NULL; + + HostRequest *req; + if (obj) + req = anope_dynamic_static_cast<HostRequest *>(obj); + else + req = na->Extend<HostRequest>("hostrequest"); + if (req) + { + req->nick = na->nick; + data["ident"] >> req->ident; + data["host"] >> req->host; + data["time"] >> req->time; + } + + return req; + } +}; + +class CommandHSRequest final + : public Command +{ + bool isvalidchar(char c) + { + if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '.' || c == '-') + return true; + return false; + } + +public: + CommandHSRequest(Module *creator) : Command(creator, "hostserv/request", 1, 1) + { + this->SetDesc(_("Request a vHost for your nick")); + this->SetSyntax(_("vhost")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + if (Anope::ReadOnly) + { + source.Reply(READ_ONLY_MODE); + return; + } + + User *u = source.GetUser(); + NickAlias *na = NickAlias::Find(source.GetNick()); + if (!na || na->nc != source.GetAccount()) + { + source.Reply(ACCESS_DENIED); + return; + } + + if (source.GetAccount()->HasExt("UNCONFIRMED")) + { + source.Reply(_("You must confirm your account before you may request a vhost.")); + return; + } + + Anope::string rawhostmask = params[0]; + + Anope::string user, host; + size_t a = rawhostmask.find('@'); + + if (a == Anope::string::npos) + host = rawhostmask; + else + { + user = rawhostmask.substr(0, a); + host = rawhostmask.substr(a + 1); + } + + if (host.empty()) + { + this->OnSyntaxError(source, ""); + return; + } + + if (!user.empty()) + { + if (user.length() > Config->GetBlock("networkinfo")->Get<unsigned>("userlen")) + { + source.Reply(HOST_SET_IDENTTOOLONG, Config->GetBlock("networkinfo")->Get<unsigned>("userlen")); + return; + } + else if (!IRCD->CanSetVIdent) + { + source.Reply(HOST_NO_VIDENT); + return; + } + for (const auto &chr : user) + { + if (!isvalidchar(chr)) + { + source.Reply(HOST_SET_IDENT_ERROR); + return; + } + } + } + + if (host.length() > Config->GetBlock("networkinfo")->Get<unsigned>("hostlen")) + { + source.Reply(HOST_SET_TOOLONG, Config->GetBlock("networkinfo")->Get<unsigned>("hostlen")); + return; + } + + if (!IRCD->IsHostValid(host)) + { + source.Reply(HOST_SET_ERROR); + return; + } + + time_t send_delay = Config->GetModule("memoserv")->Get<time_t>("senddelay"); + if (Config->GetModule(this->owner)->Get<bool>("memooper") && send_delay > 0 && u && u->lastmemosend + send_delay > Anope::CurTime) + { + source.Reply(_("Please wait %lu seconds before requesting a new vHost."), (unsigned long)send_delay); + u->lastmemosend = Anope::CurTime; + return; + } + + HostRequest req(na); + req.nick = source.GetNick(); + req.ident = user; + req.host = host; + req.time = Anope::CurTime; + na->Extend<HostRequest>("hostrequest", req); + + source.Reply(_("Your vHost has been requested.")); + req_send_memos(owner, source, user, host); + Log(LOG_COMMAND, source, this) << "to request new vhost " << (!user.empty() ? user + "@" : "") << host; + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Request the given vHost to be activated for your nick by the\n" + "network administrators. Please be patient while your request\n" + "is being considered.")); + return true; + } +}; + +class CommandHSActivate final + : public Command +{ +public: + CommandHSActivate(Module *creator) : Command(creator, "hostserv/activate", 1, 1) + { + this->SetDesc(_("Approve the requested vHost of a user")); + this->SetSyntax(_("\037nick\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + if (Anope::ReadOnly) + { + source.Reply(READ_ONLY_MODE); + return; + } + + const Anope::string &nick = params[0]; + + NickAlias *na = NickAlias::Find(nick); + HostRequest *req = na ? na->GetExt<HostRequest>("hostrequest") : NULL; + if (req) + { + na->SetVhost(req->ident, req->host, source.GetNick(), req->time); + FOREACH_MOD(OnSetVhost, (na)); + + if (Config->GetModule(this->owner)->Get<bool>("memouser") && memoserv) + memoserv->Send(source.service->nick, na->nick, _("[auto memo] Your requested vHost has been approved."), true); + + source.Reply(_("vHost for %s has been activated."), na->nick.c_str()); + Log(LOG_COMMAND, source, this) << "for " << na->nick << " for vhost " << (!req->ident.empty() ? req->ident + "@" : "") << req->host; + na->Shrink<HostRequest>("hostrequest"); + } + else + source.Reply(_("No request for nick %s found."), nick.c_str()); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Activate the requested vHost for the given nick.")); + if (Config->GetModule(this->owner)->Get<bool>("memouser")) + source.Reply(_("A memo informing the user will also be sent.")); + + return true; + } +}; + +class CommandHSReject final + : public Command +{ +public: + CommandHSReject(Module *creator) : Command(creator, "hostserv/reject", 1, 2) + { + this->SetDesc(_("Reject the requested vHost of a user")); + this->SetSyntax(_("\037nick\037 [\037reason\037]")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + if (Anope::ReadOnly) + { + source.Reply(READ_ONLY_MODE); + return; + } + + const Anope::string &nick = params[0]; + const Anope::string &reason = params.size() > 1 ? params[1] : ""; + + NickAlias *na = NickAlias::Find(nick); + HostRequest *req = na ? na->GetExt<HostRequest>("hostrequest") : NULL; + if (req) + { + na->Shrink<HostRequest>("hostrequest"); + + if (Config->GetModule(this->owner)->Get<bool>("memouser") && memoserv) + { + Anope::string message; + if (!reason.empty()) + message = Anope::printf(_("[auto memo] Your requested vHost has been rejected. Reason: %s"), reason.c_str()); + else + message = _("[auto memo] Your requested vHost has been rejected."); + + memoserv->Send(source.service->nick, nick, Language::Translate(source.GetAccount(), message.c_str()), true); + } + + source.Reply(_("vHost for %s has been rejected."), nick.c_str()); + Log(LOG_COMMAND, source, this) << "to reject vhost for " << nick << " (" << (!reason.empty() ? reason : "no reason") << ")"; + } + else + source.Reply(_("No request for nick %s found."), nick.c_str()); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Reject the requested vHost for the given nick.")); + if (Config->GetModule(this->owner)->Get<bool>("memouser")) + source.Reply(_("A memo informing the user will also be sent, which includes the reason for the rejection if supplied.")); + + return true; + } +}; + +class CommandHSWaiting final + : public Command +{ +public: + CommandHSWaiting(Module *creator) : Command(creator, "hostserv/waiting", 0, 0) + { + this->SetDesc(_("Retrieves the vhost requests")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + unsigned counter = 0; + unsigned display_counter = 0, listmax = Config->GetModule(this->owner)->Get<unsigned>("listmax"); + ListFormatter list(source.GetAccount()); + + list.AddColumn(_("Number")).AddColumn(_("Nick")).AddColumn(_("Vhost")).AddColumn(_("Created")); + + for (const auto &[nick, na] : *NickAliasList) + { + HostRequest *hr = na->GetExt<HostRequest>("hostrequest"); + if (!hr) + continue; + + if (!listmax || display_counter < listmax) + { + ++display_counter; + + ListFormatter::ListEntry entry; + entry["Number"] = stringify(display_counter); + entry["Nick"] = nick; + if (!hr->ident.empty()) + entry["Vhost"] = hr->ident + "@" + hr->host; + else + entry["Vhost"] = hr->host; + entry["Created"] = Anope::strftime(hr->time, NULL, true); + list.AddEntry(entry); + } + ++counter; + } + + std::vector<Anope::string> replies; + list.Process(replies); + + for (const auto &reply : replies) + source.Reply(reply); + + source.Reply(_("Displayed \002%d\002 records (\002%d\002 total)."), display_counter, counter); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("This command retrieves the vhost requests.")); + + return true; + } +}; + +class HSRequest final + : public Module +{ + CommandHSRequest commandhsrequest; + CommandHSActivate commandhsactive; + CommandHSReject commandhsreject; + CommandHSWaiting commandhswaiting; + ExtensibleItem<HostRequest> hostrequest; + Serialize::Type request_type; + +public: + HSRequest(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR), + commandhsrequest(this), commandhsactive(this), + commandhsreject(this), commandhswaiting(this), hostrequest(this, "hostrequest"), request_type("HostRequest", HostRequest::Unserialize) + { + if (!IRCD || !IRCD->CanSetVHost) + throw ModuleException("Your IRCd does not support vhosts"); + } +}; + +static void req_send_memos(Module *me, CommandSource &source, const Anope::string &vIdent, const Anope::string &vHost) +{ + Anope::string host; + if (!vIdent.empty()) + host = vIdent + "@" + vHost; + else + host = vHost; + + if (Config->GetModule(me)->Get<bool>("memooper") && memoserv) + { + for (auto *o : Oper::opers) + { + const NickAlias *na = NickAlias::Find(o->name); + if (!na) + continue; + + Anope::string message = Anope::printf(_("[auto memo] vHost \002%s\002 has been requested by %s."), host.c_str(), source.GetNick().c_str()); + + memoserv->Send(source.service->nick, na->nick, message, true); + } + } +} + +MODULE_INIT(HSRequest) diff --git a/modules/hostserv/hs_set.cpp b/modules/hostserv/hs_set.cpp new file mode 100644 index 000000000..e35f4d3b9 --- /dev/null +++ b/modules/hostserv/hs_set.cpp @@ -0,0 +1,231 @@ +/* HostServ core functions + * + * (C) 2003-2024 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" + +class CommandHSSet final + : public Command +{ +public: + CommandHSSet(Module *creator) : Command(creator, "hostserv/set", 2, 2) + { + this->SetDesc(_("Set the vhost of another user")); + this->SetSyntax(_("\037nick\037 \037hostmask\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + if (Anope::ReadOnly) + { + source.Reply(READ_ONLY_MODE); + return; + } + + const Anope::string &nick = params[0]; + + NickAlias *na = NickAlias::Find(nick); + if (na == NULL) + { + source.Reply(NICK_X_NOT_REGISTERED, nick.c_str()); + return; + } + + Anope::string rawhostmask = params[1]; + + Anope::string user, host; + size_t a = rawhostmask.find('@'); + + if (a == Anope::string::npos) + host = rawhostmask; + else + { + user = rawhostmask.substr(0, a); + host = rawhostmask.substr(a + 1); + } + + if (host.empty()) + { + this->OnSyntaxError(source, ""); + return; + } + + if (!user.empty()) + { + if (!IRCD->CanSetVIdent) + { + source.Reply(HOST_NO_VIDENT); + return; + } + else if (!IRCD->IsIdentValid(user)) + { + source.Reply(HOST_SET_IDENT_ERROR); + return; + } + } + + if (host.length() > Config->GetBlock("networkinfo")->Get<unsigned>("hostlen")) + { + source.Reply(HOST_SET_TOOLONG, Config->GetBlock("networkinfo")->Get<unsigned>("hostlen")); + return; + } + + if (!IRCD->IsHostValid(host)) + { + source.Reply(HOST_SET_ERROR); + return; + } + + Log(LOG_ADMIN, source, this) << "to set the vhost of " << na->nick << " to " << (!user.empty() ? user + "@" : "") << host; + + na->SetVhost(user, host, source.GetNick()); + FOREACH_MOD(OnSetVhost, (na)); + if (!user.empty()) + source.Reply(_("VHost for \002%s\002 set to \002%s\002@\002%s\002."), nick.c_str(), user.c_str(), host.c_str()); + else + source.Reply(_("VHost for \002%s\002 set to \002%s\002."), nick.c_str(), host.c_str()); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Sets the vhost for the given nick to that of the given\n" + "hostmask. If your IRCD supports vIdents, then using\n" + "SET <nick> <ident>@<hostmask> set idents for users as\n" + "well as vhosts.")); + return true; + } +}; + +class CommandHSSetAll final + : public Command +{ + void Sync(const NickAlias *na) + { + if (!na || !na->HasVhost()) + return; + + for (auto *nick : *na->nc->aliases) + { + if (nick) + nick->SetVhost(na->GetVhostIdent(), na->GetVhostHost(), na->GetVhostCreator()); + } + } + +public: + CommandHSSetAll(Module *creator) : Command(creator, "hostserv/setall", 2, 2) + { + this->SetDesc(_("Set the vhost for all nicks in a group")); + this->SetSyntax(_("\037nick\037 \037hostmask\037")); + } + + void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) override + { + if (Anope::ReadOnly) + { + source.Reply(READ_ONLY_MODE); + return; + } + + Anope::string nick = params[0]; + + NickAlias *na = NickAlias::Find(nick); + if (na == NULL) + { + source.Reply(NICK_X_NOT_REGISTERED, nick.c_str()); + return; + } + + Anope::string rawhostmask = params[1]; + + Anope::string user, host; + size_t a = rawhostmask.find('@'); + + if (a == Anope::string::npos) + host = rawhostmask; + else + { + user = rawhostmask.substr(0, a); + host = rawhostmask.substr(a + 1); + } + + if (host.empty()) + { + this->OnSyntaxError(source, ""); + return; + } + + if (!user.empty()) + { + if (!IRCD->CanSetVIdent) + { + source.Reply(HOST_NO_VIDENT); + return; + } + else if (!IRCD->IsIdentValid(user)) + { + source.Reply(HOST_SET_IDENT_ERROR); + return; + } + } + + if (host.length() > Config->GetBlock("networkinfo")->Get<unsigned>("hostlen")) + { + source.Reply(HOST_SET_TOOLONG, Config->GetBlock("networkinfo")->Get<unsigned>("hostlen")); + return; + } + + if (!IRCD->IsHostValid(host)) + { + source.Reply(HOST_SET_ERROR); + return; + } + + Log(LOG_ADMIN, source, this) << "to set the vhost of " << na->nick << " to " << (!user.empty() ? user + "@" : "") << host; + + na->SetVhost(user, host, source.GetNick()); + this->Sync(na); + FOREACH_MOD(OnSetVhost, (na)); + if (!user.empty()) + source.Reply(_("VHost for group \002%s\002 set to \002%s\002@\002%s\002."), nick.c_str(), user.c_str(), host.c_str()); + else + source.Reply(_("VHost for group \002%s\002 set to \002%s\002."), nick.c_str(), host.c_str()); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + this->SendSyntax(source); + source.Reply(" "); + source.Reply(_("Sets the vhost for all nicks in the same group as that\n" + "of the given nick. If your IRCD supports vIdents, then\n" + "using SETALL <nick> <ident>@<hostmask> will set idents\n" + "for users as well as vhosts.\n" + "* NOTE, this will not update the vhost for any nicks\n" + "added to the group after this command was used.")); + return true; + } +}; + +class HSSet final + : public Module +{ + CommandHSSet commandhsset; + CommandHSSetAll commandhssetall; + +public: + HSSet(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR), commandhsset(this), commandhssetall(this) + { + if (!IRCD || !IRCD->CanSetVHost) + throw ModuleException("Your IRCd does not support vhosts"); + } +}; + +MODULE_INIT(HSSet) |
