mirror of
https://github.com/anope/anope.git
synced 2026-06-12 17:04:47 +02:00
339 lines
7.7 KiB
C++
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)
|