1
0
mirror of https://github.com/anope/anope.git synced 2026-06-12 17:04:47 +02:00
Files
2026-01-01 18:07:12 +00:00

339 lines
7.7 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/rpc.h"
enum
{
// Used by anope.checkCredentials, anope.identify, and anope.command.
ERR_INVALID_ACCOUNT = RPC::ERR_CUSTOM_START,
// Used by anope.checkCredentials
ERR_INVALID_PASSWORD = RPC::ERR_CUSTOM_START + 1,
ERR_ACCOUNT_SUSPENDED = RPC::ERR_CUSTOM_START + 2,
// Used by anope.identify
ERR_INVALID_USER = RPC::ERR_CUSTOM_START + 1,
// Used by anope.listCommands, and anope.command
ERR_INVALID_SERVICE = RPC::ERR_CUSTOM_START + 1,
// Used by anope.command
ERR_INVALID_COMMAND = RPC::ERR_CUSTOM_START + 2,
};
class AnopeCheckCredentialsRPCEvent final
: public RPC::Event
{
private:
class RPCIdentifyRequest final
: public IdentifyRequest
{
private:
RPC::Request request;
Reference<HTTP::Client> client;
Reference<RPC::ServiceInterface> rpcinterface;
public:
RPCIdentifyRequest(Module *m, RPC::Request &r, HTTP::Client *c, RPC::ServiceInterface *i, const Anope::string &a, const Anope::string &p)
: IdentifyRequest(m, a, p, c->GetIP())
, request(r)
, client(c)
, rpcinterface(i)
{
}
void OnSuccess(NickAlias *na) override
{
if (!rpcinterface || !client)
return;
NickCore *nc = na->nc;
if (nc->HasExt("NS_SUSPENDED"))
{
request.Error(ERR_ACCOUNT_SUSPENDED, "Account suspended");
rpcinterface->Reply(request);
client->SendReply(&request.reply);
return;
}
auto &root = request.Root();
root.Reply("account",nc->display)
.Reply("confirmed", !nc->HasExt("UNCONFIRMED"))
.Reply("uniqueid", nc->GetId());
rpcinterface->Reply(request);
client->SendReply(&request.reply);
}
void OnFail() override
{
if (!rpcinterface || !client)
return;
if (NickAlias::Find(GetAccount()))
request.Error(ERR_INVALID_PASSWORD, "Invalid password");
else
request.Error(ERR_INVALID_ACCOUNT, "Invalid account");
rpcinterface->Reply(request);
client->SendReply(&request.reply);
}
};
public:
AnopeCheckCredentialsRPCEvent(Module *o)
: RPC::Event(o, "anope.checkCredentials", 2)
{
}
bool Run(RPC::ServiceInterface *iface, HTTP::Client *client, RPC::Request &request) override
{
const auto &username = request.data[0];
const auto &password = request.data[1];
if (username.empty() || password.empty())
{
request.Error(RPC::ERR_INVALID_PARAMS, "Not enough parameters");
return true;
}
auto *req = new RPCIdentifyRequest(this->owner, request, client, iface, username, password);
FOREACH_MOD(OnCheckAuthentication, (nullptr, req));
req->Dispatch();
return false;
}
};
class AnopeIdentifyRPCEvent final
: public RPC::Event
{
public:
AnopeIdentifyRPCEvent(Module *o)
: RPC::Event(o, "anope.identify", 2)
{
}
bool Run(RPC::ServiceInterface *iface, HTTP::Client *client, RPC::Request &request) override
{
auto *na = request.data[0].is_pos_number_only()
? NickAlias::FindId(Anope::Convert(request.data[0], 0))
: NickAlias::Find(request.data[0]);
if (!na)
{
request.Error(ERR_INVALID_ACCOUNT, "No such account");
return true;
}
auto *u = User::Find(request.data[1]);
if (!u)
{
request.Error(ERR_INVALID_USER, "No such user");
return true;
}
u->Identify(na);
return true;
}
};
class AnopeListCommandsRPCEvent final
: public RPC::Event
{
public:
AnopeListCommandsRPCEvent(Module *o)
: RPC::Event(o, "anope.listCommands")
{
}
bool Run(RPC::ServiceInterface *iface, HTTP::Client *client, RPC::Request &request) override
{
std::vector<BotInfo *> bots;
if (request.data.empty())
{
for (const auto &[_, bi] : *BotListByNick)
bots.push_back(bi);
}
else
{
for (const auto &bot : request.data)
{
auto *bi = BotInfo::Find(bot);
if (!bi)
{
request.Error(ERR_INVALID_SERVICE, "No such service");
return true;
}
bots.push_back(bi);
}
}
auto &root = request.Root();
for (const auto *bi : bots)
{
if (bi->commands.empty())
continue;
auto &commands = root.ReplyMap(bi->nick);
for (const auto &[command, info] : bi->commands)
{
ServiceReference<Command> cmdref("Command", info.name);
if (!cmdref)
continue;
auto &cmdinfo = commands.ReplyMap(command);
cmdinfo.Reply("hidden", info.hide)
.Reply("minparams", cmdref->min_params)
.Reply("requiresaccount", !cmdref->AllowUnregistered())
.Reply("requiresuser", cmdref->RequireUser());
if (info.group.empty())
cmdinfo.Reply("group", nullptr);
else
cmdinfo.Reply("group", info.group);
if (cmdref->max_params)
cmdinfo.Reply("maxparams", cmdref->max_params);
else
cmdinfo.Reply("maxparams", nullptr);
if (info.permission.empty())
cmdinfo.Reply("permission", nullptr);
else
cmdinfo.Reply("permission", info.permission);
}
}
return true;
}
};
class AnopeCommandRPCEvent final
: public RPC::Event
{
private:
class RPCCommandReply final
: public CommandReply
{
private:
RPC::Array &root;
public:
RPCCommandReply(RPC::Array &r)
: root(r)
{
}
void SendMessage(BotInfo *source, const Anope::string &msg) override
{
root.Reply(Anope::RemoveFormatting(msg.replace_all_cs("\x1A", "\x20")));
};
};
public:
static bool pretenduser;
AnopeCommandRPCEvent(Module *o)
: RPC::Event(o, "anope.command", 3)
{
}
bool Run(RPC::ServiceInterface *iface, HTTP::Client *client, RPC::Request &request) override
{
NickAlias *na = nullptr;
if (!request.data[0].empty())
{
na = request.data[0].is_pos_number_only()
? NickAlias::FindId(Anope::Convert(request.data[0], 0))
: NickAlias::Find(request.data[0]);
if (!na)
{
request.Error(ERR_INVALID_ACCOUNT, "No such account");
return true;
}
}
auto *bi = BotInfo::Find(request.data[1], true);
if (!bi)
{
request.Error(ERR_INVALID_SERVICE, "No such service");
return true;
}
Anope::string command;
for (size_t i = 2; i < request.data.size(); ++i)
{
if (!command.empty())
command.push_back(' ');
command.append(request.data[i]);
}
User *u = nullptr;
if (pretenduser && na && !na->nc->users.empty())
{
// Try and find the nick user first.
for (auto *user : na->nc->users)
{
if (user->nick.equals_ci(na->nick))
{
u = user;
break;
}
}
// No nick user, fallback to the first.
if (!u)
u = na->nc->users.front();
}
RPCCommandReply reply(request.Root<RPC::Array>());
CommandSource source(na ? na->nick : "RPC", u, na ? *na->nc : nullptr, &reply, bi, request.id);
if (!Command::Run(source, command))
request.Error(ERR_INVALID_COMMAND, "No such command");
return true;
}
};
bool AnopeCommandRPCEvent::pretenduser = false;
class ModuleRPCAccount final
: public Module
{
private:
AnopeCheckCredentialsRPCEvent anopecheckcredentialsrpcevent;
AnopeIdentifyRPCEvent anopeidentifyrpcevent;
AnopeListCommandsRPCEvent anopelistcommandsrpcevent;
AnopeCommandRPCEvent anopecommandrpcevent;
public:
ModuleRPCAccount(const Anope::string &modname, const Anope::string &creator)
: Module(modname, creator, EXTRA | VENDOR)
, anopecheckcredentialsrpcevent(this)
, anopeidentifyrpcevent(this)
, anopelistcommandsrpcevent(this)
, anopecommandrpcevent(this)
{
}
void OnReload(Configuration::Conf &conf) override
{
AnopeCommandRPCEvent::pretenduser = conf.GetModule(this).Get<bool>("pretenduser");
}
};
MODULE_INIT(ModuleRPCAccount)