312 lines
10 KiB
C++
312 lines
10 KiB
C++
/* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
|
* Version 2, December 2004
|
|
*
|
|
* Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
|
*
|
|
* Everyone is permitted to copy and distribute verbatim or modified
|
|
* copies of this license document, and changing it is allowed as long
|
|
* as the name is changed.
|
|
*
|
|
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
|
* TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
|
*
|
|
* 0. You just DO WHAT THE FUCK YOU WANT TO.
|
|
*/
|
|
|
|
#include "llviewerprecompiledheaders.h"
|
|
|
|
#include "NACLantispam.h"
|
|
#include "llagent.h"
|
|
#include "llavataractions.h"
|
|
#include "llcallbacklist.h" // For idle cleaning
|
|
#include "llnotificationsutil.h"
|
|
#include "lltrans.h"
|
|
#include "llviewerobjectlist.h"
|
|
|
|
#include <time.h>
|
|
|
|
class NACLAntiSpamQueue
|
|
{
|
|
friend class NACLAntiSpamRegistry;
|
|
public:
|
|
const U32& getAmount() const { return mAmount; }
|
|
const U32& getTime() const { return mTime; }
|
|
protected:
|
|
NACLAntiSpamQueue(const U32& time, const U32& amount) : mTime(time), mAmount(amount) {}
|
|
void setAmount(const U32& amount) { mAmount = amount; }
|
|
void setTime(const U32& time) { mTime = time; }
|
|
void block(const LLUUID& source) { mEntries[source.asString()].block(); }
|
|
void reset() { mEntries.clear(); }
|
|
// Returns 0 if unblocked/disabled, 1 if check results in a new block, 2 if by an existing block
|
|
U8 check(const LLUUID& source, const U32& multiplier)
|
|
{
|
|
const auto key = source.asString();
|
|
auto it = mEntries.find(key);
|
|
if (it != mEntries.end())
|
|
return it->second.blockIfNeeded(mAmount * multiplier, mTime);
|
|
mEntries[key]; // Default construct an Entry
|
|
return 0U;
|
|
}
|
|
void idle()
|
|
{
|
|
// Clean out old unblocked entries
|
|
const auto time_limit = mTime + 1; // One second after time has gone up, the next offense would reset anyway
|
|
for (auto it = mEntries.begin(); it != mEntries.end();)
|
|
{
|
|
const auto& entry = it->second;
|
|
if (entry.getBlocked() || entry.withinBlockTime(time_limit))
|
|
++it;
|
|
else it = mEntries.erase(it);
|
|
}
|
|
}
|
|
|
|
private:
|
|
class Entry
|
|
{
|
|
friend class NACLAntiSpamQueue;
|
|
protected:
|
|
void reset() { updateTime(); mAmount = 1; mBlocked = false; }
|
|
const U32& getAmount() const { return mAmount; }
|
|
const std::time_t& getTime() const { return mTime; }
|
|
void updateTime() { mTime = time(nullptr); }
|
|
void block() { mBlocked = true; }
|
|
bool withinBlockTime(const U32& time_limit) const { return (time(nullptr) - mTime) <= time_limit; }
|
|
U8 blockIfNeeded(const U32& amount, const U32& time_limit)
|
|
{
|
|
if (mBlocked) return 2U; // Already blocked
|
|
if (withinBlockTime(time_limit))
|
|
{
|
|
if (++mAmount > amount)
|
|
{
|
|
block();
|
|
return 1U;
|
|
}
|
|
}
|
|
else reset(); // Enough time has passed to forgive or not already in list
|
|
return 0U;
|
|
}
|
|
bool getBlocked() const { return mBlocked; }
|
|
private:
|
|
U32 mAmount = 1;
|
|
std::time_t mTime = time(nullptr);
|
|
bool mBlocked = false;
|
|
};
|
|
boost::unordered_map<std::string, Entry> mEntries;
|
|
U32 mAmount, mTime;
|
|
};
|
|
|
|
bool can_block(const LLUUID& id)
|
|
{
|
|
if (id.isNull() || gAgentID == id) return false; // Can't block system or self.
|
|
if (const LLViewerObject* obj = gObjectList.findObject(id)) // From an object,
|
|
return !obj->permYouOwner(); // not own object.
|
|
return true;
|
|
}
|
|
|
|
bool is_collision_sound(const std::string& sound)
|
|
{
|
|
// The following sounds will be ignored for purposes of spam protection. They have been gathered from wiki documentation of frequent official sounds.
|
|
const std::array<const std::string, 29> COLLISION_SOUNDS = {
|
|
"dce5fdd4-afe4-4ea1-822f-dd52cac46b08",
|
|
"51011582-fbca-4580-ae9e-1a5593f094ec",
|
|
"68d62208-e257-4d0c-bbe2-20c9ea9760bb",
|
|
"75872e8c-bc39-451b-9b0b-042d7ba36cba",
|
|
"6a45ba0b-5775-4ea8-8513-26008a17f873",
|
|
"992a6d1b-8c77-40e0-9495-4098ce539694",
|
|
"2de4da5a-faf8-46be-bac6-c4d74f1e5767",
|
|
"6e3fb0f7-6d9c-42ca-b86b-1122ff562d7d",
|
|
"14209133-4961-4acc-9649-53fc38ee1667",
|
|
"bc4a4348-cfcc-4e5e-908e-8a52a8915fe6",
|
|
"9e5c1297-6eed-40c0-825a-d9bcd86e3193",
|
|
"e534761c-1894-4b61-b20c-658a6fb68157",
|
|
"8761f73f-6cf9-4186-8aaa-0948ed002db1",
|
|
"874a26fd-142f-4173-8c5b-890cd846c74d",
|
|
"0e24a717-b97e-4b77-9c94-b59a5a88b2da",
|
|
"75cf3ade-9a5b-4c4d-bb35-f9799bda7fb2",
|
|
"153c8bf7-fb89-4d89-b263-47e58b1b4774",
|
|
"55c3e0ce-275a-46fa-82ff-e0465f5e8703",
|
|
"24babf58-7156-4841-9a3f-761bdbb8e237",
|
|
"aca261d8-e145-4610-9e20-9eff990f2c12",
|
|
"0642fba6-5dcf-4d62-8e7b-94dbb529d117",
|
|
"25a863e8-dc42-4e8a-a357-e76422ace9b5",
|
|
"9538f37c-456e-4047-81be-6435045608d4",
|
|
"8c0f84c3-9afd-4396-b5f5-9bca2c911c20",
|
|
"be582e5d-b123-41a2-a150-454c39e961c8",
|
|
"c70141d4-ba06-41ea-bcbc-35ea81cb8335",
|
|
"7d1826f4-24c4-4aac-8c2e-eff45df37783",
|
|
"063c97d3-033a-4e9b-98d8-05c8074922cb",
|
|
"00000000-0000-0000-0000-000000000120"
|
|
};
|
|
|
|
for (const auto& collision : COLLISION_SOUNDS)
|
|
if (collision == sound)
|
|
return true;
|
|
return false;
|
|
}
|
|
// NaClAntiSpamRegistry
|
|
|
|
constexpr std::array<const char*, NACLAntiSpamRegistry::QUEUE_MAX> QUEUE_NAME = {
|
|
"Chat",
|
|
"Inventory",
|
|
"Instant Message",
|
|
"calling card",
|
|
"sound",
|
|
"Sound Preload",
|
|
"Script Dialog",
|
|
"Teleport"
|
|
};
|
|
|
|
NACLAntiSpamRegistry::NACLAntiSpamRegistry()
|
|
{
|
|
auto control = gSavedSettings.getControl("_NACL_AntiSpamTime");
|
|
const U32 time = control->get().asInteger();
|
|
mConnections[0] = control->getSignal()->connect(boost::bind(&NACLAntiSpamRegistry::handleNaclAntiSpamTimeChanged, _2));
|
|
|
|
control = gSavedSettings.getControl("_NACL_AntiSpamAmount");
|
|
const U32 amount = control->get().asInteger();
|
|
mConnections[1] = control->getSignal()->connect(boost::bind(&NACLAntiSpamRegistry::handleNaclAntiSpamAmountChanged, _2));
|
|
|
|
control = gSavedSettings.getControl("_NACL_AntiSpamGlobalQueue");
|
|
mConnections[2] = control->getSignal()->connect(boost::bind(&NACLAntiSpamRegistry::handleNaclAntiSpamGlobalQueueChanged, _2));
|
|
initializeQueues(control->get(), time, amount);
|
|
}
|
|
|
|
void NACLAntiSpamRegistry::initializeQueues(bool global, const U32& time, const U32& amount)
|
|
{
|
|
if (global) // If Global, initialize global queue
|
|
mGlobalQueue.reset(new NACLAntiSpamQueue(time, amount));
|
|
else
|
|
{
|
|
mQueues.reset(new std::array<NACLAntiSpamQueue, QUEUE_MAX>{{
|
|
NACLAntiSpamQueue(time, amount), // QUEUE_CHAT
|
|
NACLAntiSpamQueue(time, amount), // QUEUE_INVENTORY
|
|
NACLAntiSpamQueue(time, amount), // QUEUE_IM
|
|
NACLAntiSpamQueue(time, amount), // QUEUE_CALLING_CARD
|
|
NACLAntiSpamQueue(time, amount), // QUEUE_SOUND
|
|
NACLAntiSpamQueue(time, amount), // QUEUE_SOUND_PRELOAD
|
|
NACLAntiSpamQueue(time, amount), // QUEUE_SCRIPT_DIALOG
|
|
NACLAntiSpamQueue(time, amount) // QUEUE_TELEPORT
|
|
}});
|
|
}
|
|
}
|
|
|
|
constexpr const char* getQueueName(const NACLAntiSpamRegistry::Type& name)
|
|
{
|
|
return name >= QUEUE_NAME.size() ? "Unknown" : QUEUE_NAME[name];
|
|
}
|
|
|
|
void NACLAntiSpamRegistry::setAllQueueTimes(U32 time)
|
|
{
|
|
if (mGlobalQueue) mGlobalQueue->setTime(time);
|
|
else for(auto& queue : *mQueues) queue.setTime(time);
|
|
}
|
|
|
|
void NACLAntiSpamRegistry::setAllQueueAmounts(U32 amount)
|
|
{
|
|
if (mGlobalQueue) mGlobalQueue->setAmount(amount);
|
|
else for (U8 queue = 0U; queue < QUEUE_MAX; ++queue)
|
|
{
|
|
auto& q = (*mQueues)[queue];
|
|
if (queue == QUEUE_SOUND || queue == QUEUE_SOUND_PRELOAD)
|
|
q.setAmount(amount*5);
|
|
else
|
|
q.setAmount(amount);
|
|
}
|
|
}
|
|
|
|
void NACLAntiSpamRegistry::blockOnQueue(const Type& name, const LLUUID& source)
|
|
{
|
|
if (name >= QUEUE_MAX)
|
|
LL_ERRS("AntiSpam") << "CODE BUG: Attempting to use a antispam queue that was outside of the reasonable range of queues. Queue: " << getQueueName(name) << LL_ENDL;
|
|
else (mGlobalQueue ? *mGlobalQueue : (*mQueues)[name]).block(source);
|
|
}
|
|
|
|
bool NACLAntiSpamRegistry::checkQueue(const Type& name, const LLUUID& source, const LFIDBearer::Type& type, const U32& multiplier)
|
|
//returns true if blocked
|
|
{
|
|
if (name >= QUEUE_MAX)
|
|
{
|
|
LL_ERRS("AntiSpam") << "CODE BUG: Attempting to check antispam queue that was outside of the reasonable range of queues. Queue: " << getQueueName(name) << LL_ENDL;
|
|
return false;
|
|
}
|
|
|
|
if (!can_block(source)) return false;
|
|
|
|
auto& queue = mGlobalQueue ? *mGlobalQueue : (*mQueues)[name];
|
|
const auto result = queue.check(source, multiplier);
|
|
if (!result) return false; // Safe
|
|
|
|
if (result != 2 // Not previously blocked
|
|
&& gSavedSettings.getBOOL("AntiSpamNotify")) // and Just blocked!
|
|
{
|
|
const std::string get_slurl_for(const LLUUID& id, const LFIDBearer::Type& type);
|
|
const auto slurl = get_slurl_for(source, type);
|
|
LLSD args;
|
|
args["SOURCE"] = slurl.empty() ? source.asString() : slurl;
|
|
args["TYPE"] = LLTrans::getString(getQueueName(name));
|
|
args["AMOUNT"] = (LLSD::Integer)(multiplier * queue.getAmount());
|
|
args["TIME"] = (LLSD::Integer)queue.getTime();
|
|
LLNotificationsUtil::add("AntiSpamBlock", args);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void NACLAntiSpamRegistry::idle()
|
|
{
|
|
if (mGlobalQueue) mGlobalQueue->idle();
|
|
else for (auto& queue : *mQueues) queue.idle();
|
|
}
|
|
|
|
void NACLAntiSpamRegistry::resetQueues()
|
|
{
|
|
if (mGlobalQueue) mGlobalQueue->reset();
|
|
else for (auto& queue : *mQueues) queue.reset();
|
|
|
|
LL_INFOS() << "AntiSpam Queues Reset" << LL_ENDL;
|
|
}
|
|
|
|
void NACLAntiSpamRegistry::purgeAllQueues()
|
|
{
|
|
// Note: These resets are upon the unique_ptr, not the Queue itself!
|
|
if (mGlobalQueue)
|
|
mGlobalQueue.reset();
|
|
else
|
|
mQueues.reset();
|
|
}
|
|
|
|
// Handlers
|
|
// static
|
|
void NACLAntiSpamRegistry::startup()
|
|
{
|
|
auto onAntiSpamToggle = [](const LLControlVariable*, const LLSD& value) {
|
|
if (value.asBoolean()) instance();
|
|
else deleteSingleton();
|
|
};
|
|
auto control = gSavedSettings.getControl("AntiSpamEnabled");
|
|
control->getSignal()->connect(onAntiSpamToggle);
|
|
onAntiSpamToggle(control, control->get());
|
|
}
|
|
|
|
// static
|
|
bool NACLAntiSpamRegistry::handleNaclAntiSpamGlobalQueueChanged(const LLSD& newvalue)
|
|
{
|
|
if (instanceExists())
|
|
{
|
|
auto& inst = instance();
|
|
inst.purgeAllQueues();
|
|
inst.initializeQueues(newvalue.asBoolean(), gSavedSettings.getU32("_NACL_AntiSpamTime"), gSavedSettings.getU32("_NACL_AntiSpamAmount"));
|
|
}
|
|
return true;
|
|
}
|
|
//static
|
|
bool NACLAntiSpamRegistry::handleNaclAntiSpamTimeChanged(const LLSD& newvalue)
|
|
{
|
|
if (auto inst = getIfExists()) inst->setAllQueueTimes(newvalue.asInteger());
|
|
return true;
|
|
}
|
|
//static
|
|
bool NACLAntiSpamRegistry::handleNaclAntiSpamAmountChanged(const LLSD& newvalue)
|
|
{
|
|
if (auto inst = getIfExists()) inst->setAllQueueAmounts(newvalue.asInteger());
|
|
return true;
|
|
} |