mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-10 01:58:19 +00:00
Add possibility to ban players by name
This uses the Unicode confusable data together with judging how close two strings are by using the Levenshtein distance. Adds the commands `name_ban`, `name_unban` and `name_bans`. Kicks players who join using a banned name and doesn't allow ingame players to change their names to the banned ones.
This commit is contained in:
parent
710d0610d9
commit
31a3e8d4c0
|
@ -1101,6 +1101,7 @@ if(GTEST_FOUND OR DOWNLOAD_GTEST)
|
|||
fs.cpp
|
||||
git_revision.cpp
|
||||
jobs.cpp
|
||||
str.cpp
|
||||
strip_path_and_extension.cpp
|
||||
teehistorian.cpp
|
||||
test.cpp
|
||||
|
|
|
@ -63,6 +63,23 @@ int str_utf8_skeleton_next(struct SKELETON *skel)
|
|||
return ch;
|
||||
}
|
||||
|
||||
int str_utf8_to_skeleton(const char *str, int *buf, int buf_len)
|
||||
{
|
||||
int i;
|
||||
struct SKELETON skel;
|
||||
str_utf8_skeleton_begin(&skel, str);
|
||||
for(i = 0; i < buf_len; i++)
|
||||
{
|
||||
int ch = str_utf8_skeleton_next(&skel);
|
||||
if(ch == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
buf[i] = ch;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
int str_utf8_comp_confusable(const char *str1, const char *str2)
|
||||
{
|
||||
struct SKELETON skel1;
|
||||
|
|
|
@ -2408,6 +2408,98 @@ int str_comp_filenames(const char *a, const char *b)
|
|||
return *a - *b;
|
||||
}
|
||||
|
||||
static int min3(int a, int b, int c)
|
||||
{
|
||||
int min = a;
|
||||
if(b < min)
|
||||
min = b;
|
||||
if(c < min)
|
||||
min = c;
|
||||
return min;
|
||||
}
|
||||
|
||||
int str_utf8_dist(const char *a, const char *b)
|
||||
{
|
||||
int buf_len = 2 * (str_length(a) + 1 + str_length(b) + 1);
|
||||
int *buf = (int *)mem_alloc(buf_len * sizeof(*buf), 1);
|
||||
int result = str_utf8_dist_buffer(a, b, buf, buf_len);
|
||||
mem_free(buf);
|
||||
return result;
|
||||
}
|
||||
|
||||
static int str_to_utf32_unchecked(const char *str, int **out)
|
||||
{
|
||||
int out_len = 0;
|
||||
while((**out = str_utf8_decode(&str)))
|
||||
{
|
||||
(*out)++;
|
||||
out_len++;
|
||||
}
|
||||
return out_len;
|
||||
}
|
||||
|
||||
int str_utf32_dist_buffer(const int *a, int a_len, const int *b, int b_len, int *buf, int buf_len)
|
||||
{
|
||||
int i, j;
|
||||
dbg_assert(buf_len >= (a_len + 1) + (b_len + 1), "buffer too small");
|
||||
if(a_len > b_len)
|
||||
{
|
||||
int tmp1 = a_len;
|
||||
const int *tmp2 = a;
|
||||
|
||||
a_len = b_len;
|
||||
a = b;
|
||||
|
||||
b_len = tmp1;
|
||||
b = tmp2;
|
||||
}
|
||||
#define B(i, j) buf[((j)&1) * (a_len + 1) + (i)]
|
||||
for(i = 0; i <= a_len; i++)
|
||||
{
|
||||
B(i, 0) = i;
|
||||
}
|
||||
for(j = 1; j <= b_len; j++)
|
||||
{
|
||||
B(0, j) = j;
|
||||
for(i = 1; i <= a_len; i++)
|
||||
{
|
||||
int subst = (a[i - 1] != b[j - 1]);
|
||||
B(i, j) = min3(
|
||||
B(i - 1, j) + 1,
|
||||
B(i, j - 1) + 1,
|
||||
B(i - 1, j - 1) + subst
|
||||
);
|
||||
}
|
||||
}
|
||||
return B(a_len, b_len);
|
||||
#undef B
|
||||
}
|
||||
|
||||
int str_utf8_dist_buffer(const char *a_utf8, const char *b_utf8, int *buf, int buf_len)
|
||||
{
|
||||
int a_utf8_len = str_length(a_utf8);
|
||||
int b_utf8_len = str_length(b_utf8);
|
||||
int *a, *b; // UTF-32
|
||||
int a_len, b_len; // UTF-32 length
|
||||
dbg_assert(buf_len >= 2 * (a_utf8_len + 1 + b_utf8_len + 1), "buffer too small");
|
||||
if(a_utf8_len > b_utf8_len)
|
||||
{
|
||||
int tmp1 = a_utf8_len;
|
||||
const char *tmp2 = a_utf8;
|
||||
|
||||
a_utf8_len = b_utf8_len;
|
||||
a_utf8 = b_utf8;
|
||||
|
||||
b_utf8_len = tmp1;
|
||||
b_utf8 = tmp2;
|
||||
}
|
||||
a = buf;
|
||||
a_len = str_to_utf32_unchecked(a_utf8, &buf);
|
||||
b = buf;
|
||||
b_len = str_to_utf32_unchecked(b_utf8, &buf);
|
||||
return str_utf32_dist_buffer(a, a_len, b, b_len, buf, buf_len - b_len - a_len);
|
||||
}
|
||||
|
||||
const char *str_find_nocase(const char *haystack, const char *needle)
|
||||
{
|
||||
while(*haystack) /* native implementation */
|
||||
|
|
|
@ -1181,6 +1181,64 @@ int str_comp_num(const char *a, const char *b, const int num);
|
|||
*/
|
||||
int str_comp_filenames(const char *a, const char *b);
|
||||
|
||||
/*
|
||||
Function: str_utf8_dist
|
||||
Computes the edit distance between two strings.
|
||||
|
||||
Parameters:
|
||||
a - First string for the edit distance.
|
||||
b - Second string for the edit distance.
|
||||
|
||||
Returns:
|
||||
The edit distance between the both strings.
|
||||
|
||||
Remarks:
|
||||
- The strings are treated as zero-terminated strings.
|
||||
*/
|
||||
int str_utf8_dist(const char *a, const char *b);
|
||||
|
||||
/*
|
||||
Function: str_utf8_dist_buffer
|
||||
Computes the edit distance between two strings, allows buffers
|
||||
to be passed in.
|
||||
|
||||
Parameters:
|
||||
a - First string for the edit distance.
|
||||
b - Second string for the edit distance.
|
||||
buf - Buffer for the function.
|
||||
buf_len - Length of the buffer, must be at least as long as
|
||||
twice the length of both strings combined plus two.
|
||||
|
||||
Returns:
|
||||
The edit distance between the both strings.
|
||||
|
||||
Remarks:
|
||||
- The strings are treated as zero-terminated strings.
|
||||
*/
|
||||
int str_utf8_dist_buffer(const char *a, const char *b, int *buf, int buf_len);
|
||||
|
||||
/*
|
||||
Function: str_utf32_dist_buffer
|
||||
Computes the edit distance between two strings, allows buffers
|
||||
to be passed in.
|
||||
|
||||
Parameters:
|
||||
a - First string for the edit distance.
|
||||
a_len - Length of the first string.
|
||||
b - Second string for the edit distance.
|
||||
b_len - Length of the second string.
|
||||
buf - Buffer for the function.
|
||||
buf_len - Length of the buffer, must be at least as long as
|
||||
the length of both strings combined plus two.
|
||||
|
||||
Returns:
|
||||
The edit distance between the both strings.
|
||||
|
||||
Remarks:
|
||||
- The strings are treated as zero-terminated strings.
|
||||
*/
|
||||
int str_utf32_dist_buffer(const int *a, int a_len, const int *b, int b_len, int *buf, int buf_len);
|
||||
|
||||
/*
|
||||
Function: str_find_nocase
|
||||
Finds a string inside another string case insensitive.
|
||||
|
@ -1235,18 +1293,18 @@ void str_hex(char *dst, int dst_size, const void *data, int data_size);
|
|||
Function: str_hex_decode
|
||||
Takes a hex string and returns a byte array.
|
||||
|
||||
Parameters:
|
||||
dst - Buffer for the byte array
|
||||
dst_size - size of the buffer
|
||||
data - String to decode
|
||||
Parameters:
|
||||
dst - Buffer for the byte array
|
||||
dst_size - size of the buffer
|
||||
data - String to decode
|
||||
|
||||
Returns:
|
||||
2 - String doesn't exactly fit the buffer
|
||||
1 - Invalid character in string
|
||||
0 - Success
|
||||
Returns:
|
||||
2 - String doesn't exactly fit the buffer
|
||||
1 - Invalid character in string
|
||||
0 - Success
|
||||
|
||||
Remarks:
|
||||
- The contents of the buffer is only valid on success
|
||||
Remarks:
|
||||
- The contents of the buffer is only valid on success
|
||||
*/
|
||||
int str_hex_decode(unsigned char *dst, int dst_size, const char *src);
|
||||
/*
|
||||
|
@ -1504,6 +1562,11 @@ int str_isspace(char c);
|
|||
char str_uppercase(char c);
|
||||
unsigned str_quickhash(const char *str);
|
||||
|
||||
struct SKELETON;
|
||||
void str_utf8_skeleton_begin(struct SKELETON *skel, const char *str);
|
||||
int str_utf8_skeleton_next(struct SKELETON *skel);
|
||||
int str_utf8_to_skeleton(const char *str, int *buf, int buf_len);
|
||||
|
||||
/*
|
||||
Function: str_utf8_comp_confusable
|
||||
Compares two strings for visual appearance.
|
||||
|
|
|
@ -391,6 +391,29 @@ void CServer::SetClientName(int ClientID, const char *pName)
|
|||
if(!pName)
|
||||
return;
|
||||
|
||||
int Skeleton[MAX_NAME_SKELETON_LENGTH];
|
||||
int SkeletonLength = str_utf8_to_skeleton(pName, Skeleton, sizeof(Skeleton) / sizeof(Skeleton[0]));
|
||||
int Buffer[MAX_NAME_SKELETON_LENGTH * 2 + 2];
|
||||
bool Banned = false;
|
||||
for(int i = 0; i < m_aNameBans.size(); i++)
|
||||
{
|
||||
CNameBan *pBan = &m_aNameBans[i];
|
||||
int Distance = str_utf32_dist_buffer(Skeleton, SkeletonLength, pBan->m_aSkeleton, pBan->m_SkeletonLength, Buffer, sizeof(Buffer) / sizeof(Buffer[0]));
|
||||
if(Distance <= pBan->m_Distance)
|
||||
{
|
||||
Banned = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(Banned)
|
||||
{
|
||||
if(m_aClients[ClientID].m_State == CClient::STATE_READY)
|
||||
{
|
||||
Kick(ClientID, "Kicked (your name is banned)");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
char aNameTry[MAX_NAME_LENGTH];
|
||||
str_copy(aNameTry, pName, sizeof(aNameTry));
|
||||
if(TrySetClientName(ClientID, aNameTry))
|
||||
|
@ -2328,6 +2351,67 @@ void CServer::ConAuthList(IConsole::IResult *pResult, void *pUser)
|
|||
pManager->ListKeys(ListKeysCallback, pThis);
|
||||
}
|
||||
|
||||
void CServer::ConNameBan(IConsole::IResult *pResult, void *pUser)
|
||||
{
|
||||
CServer *pThis = (CServer *)pUser;
|
||||
char aBuf[64];
|
||||
const char *pName = pResult->GetString(0);
|
||||
int Distance;
|
||||
if(pResult->NumArguments() > 1)
|
||||
{
|
||||
Distance = pResult->GetInteger(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
Distance = str_length(pName) / 3;
|
||||
}
|
||||
for(int i = 0; i < pThis->m_aNameBans.size(); i++)
|
||||
{
|
||||
CNameBan *pBan = &pThis->m_aNameBans[i];
|
||||
if(str_comp(pBan->m_aName, pName) == 0)
|
||||
{
|
||||
str_format(aBuf, sizeof(aBuf), "changed name='%s' distance=%d old_distance=%d", pName, Distance, pBan->m_Distance);
|
||||
pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "name_ban", aBuf);
|
||||
pBan->m_Distance = Distance;
|
||||
return;
|
||||
}
|
||||
}
|
||||
pThis->m_aNameBans.add(CNameBan(pName, Distance));
|
||||
str_format(aBuf, sizeof(aBuf), "added name='%s' distance=%d", pName, Distance);
|
||||
pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "name_ban", aBuf);
|
||||
}
|
||||
|
||||
void CServer::ConNameUnban(IConsole::IResult *pResult, void *pUser)
|
||||
{
|
||||
CServer *pThis = (CServer *)pUser;
|
||||
const char *pName = pResult->GetString(0);
|
||||
|
||||
for(int i = 0; i < pThis->m_aNameBans.size(); i++)
|
||||
{
|
||||
CNameBan *pBan = &pThis->m_aNameBans[i];
|
||||
if(str_comp(pBan->m_aName, pName) == 0)
|
||||
{
|
||||
char aBuf[64];
|
||||
str_format(aBuf, sizeof(aBuf), "removed name='%s' distance=%d", pBan->m_aName, pBan->m_Distance);
|
||||
pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "name_ban", aBuf);
|
||||
pThis->m_aNameBans.remove_index(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CServer::ConNameBans(IConsole::IResult *pResult, void *pUser)
|
||||
{
|
||||
CServer *pThis = (CServer *)pUser;
|
||||
|
||||
for(int i = 0; i < pThis->m_aNameBans.size(); i++)
|
||||
{
|
||||
CNameBan *pBan = &pThis->m_aNameBans[i];
|
||||
char aBuf[64];
|
||||
str_format(aBuf, sizeof(aBuf), "name='%s' distance=%d", pBan->m_aName, pBan->m_Distance);
|
||||
pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "name_ban", aBuf);
|
||||
}
|
||||
}
|
||||
|
||||
void CServer::ConShutdown(IConsole::IResult *pResult, void *pUser)
|
||||
{
|
||||
((CServer *)pUser)->m_RunServer = 0;
|
||||
|
@ -2718,6 +2802,10 @@ void CServer::RegisterCommands()
|
|||
Console()->Register("auth_remove", "s[ident]", CFGFLAG_SERVER|CFGFLAG_NONTEEHISTORIC, ConAuthRemove, this, "Remove a rcon key");
|
||||
Console()->Register("auth_list", "", CFGFLAG_SERVER, ConAuthList, this, "List all rcon keys");
|
||||
|
||||
Console()->Register("name_ban", "s[name] ?i[distance]", CFGFLAG_SERVER, ConNameBan, this, "Ban a certain nick name");
|
||||
Console()->Register("name_unban", "s[name]", CFGFLAG_SERVER, ConNameUnban, this, "Unban a certain nick name");
|
||||
Console()->Register("name_bans", "", CFGFLAG_SERVER, ConNameBans, this, "List all name bans");
|
||||
|
||||
Console()->Chain("sv_name", ConchainSpecialInfoupdate, this);
|
||||
Console()->Chain("password", ConchainSpecialInfoupdate, this);
|
||||
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
#include <engine/shared/netban.h>
|
||||
#include <engine/shared/uuid_manager.h>
|
||||
|
||||
#include <base/tl/array.h>
|
||||
|
||||
#include "authmanager.h"
|
||||
|
||||
#if defined (CONF_SQL)
|
||||
|
@ -96,6 +98,27 @@ class CServer : public IServer
|
|||
UNIXSOCKET m_ConnLoggingSocket;
|
||||
#endif
|
||||
|
||||
enum
|
||||
{
|
||||
MAX_NAME_SKELETON_LENGTH=MAX_NAME_LENGTH*4,
|
||||
};
|
||||
|
||||
class CNameBan
|
||||
{
|
||||
public:
|
||||
CNameBan() {}
|
||||
CNameBan(const char *pName, int Distance) :
|
||||
m_Distance(Distance)
|
||||
{
|
||||
str_copy(m_aName, pName, sizeof(m_aName));
|
||||
m_SkeletonLength = str_utf8_to_skeleton(m_aName, m_aSkeleton, sizeof(m_aSkeleton) / sizeof(m_aSkeleton[0]));
|
||||
}
|
||||
char m_aName[MAX_NAME_LENGTH];
|
||||
int m_aSkeleton[MAX_NAME_SKELETON_LENGTH];
|
||||
int m_SkeletonLength;
|
||||
int m_Distance;
|
||||
};
|
||||
|
||||
public:
|
||||
class IGameServer *GameServer() { return m_pGameServer; }
|
||||
class IConsole *Console() { return m_pConsole; }
|
||||
|
@ -217,6 +240,8 @@ public:
|
|||
|
||||
char m_aErrorShutdownReason[128];
|
||||
|
||||
array<CNameBan> m_aNameBans;
|
||||
|
||||
CServer();
|
||||
|
||||
int TrySetClientName(int ClientID, const char *pName);
|
||||
|
@ -308,6 +333,10 @@ public:
|
|||
static void ConAuthRemove(IConsole::IResult *pResult, void *pUser);
|
||||
static void ConAuthList(IConsole::IResult *pResult, void *pUser);
|
||||
|
||||
static void ConNameBan(IConsole::IResult *pResult, void *pUser);
|
||||
static void ConNameUnban(IConsole::IResult *pResult, void *pUser);
|
||||
static void ConNameBans(IConsole::IResult *pResult, void *pUser);
|
||||
|
||||
static void StatusImpl(IConsole::IResult *pResult, void *pUser, bool DnsblBlacklistedOnly);
|
||||
|
||||
#if defined (CONF_SQL)
|
||||
|
|
19
src/test/str.cpp
Normal file
19
src/test/str.cpp
Normal file
|
@ -0,0 +1,19 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <base/system.h>
|
||||
|
||||
TEST(Str, Dist)
|
||||
{
|
||||
EXPECT_EQ(str_utf8_dist("aaa", "aaa"), 0);
|
||||
EXPECT_EQ(str_utf8_dist("123", "123"), 0);
|
||||
EXPECT_EQ(str_utf8_dist("", ""), 0);
|
||||
EXPECT_EQ(str_utf8_dist("a", "b"), 1);
|
||||
EXPECT_EQ(str_utf8_dist("", "aaa"), 3);
|
||||
EXPECT_EQ(str_utf8_dist("123", ""), 3);
|
||||
EXPECT_EQ(str_utf8_dist("ä", ""), 1);
|
||||
EXPECT_EQ(str_utf8_dist("Hëllö", "Hello"), 2);
|
||||
// https://en.wikipedia.org/w/index.php?title=Levenshtein_distance&oldid=828480025#Example
|
||||
EXPECT_EQ(str_utf8_dist("kitten", "sitting"), 3);
|
||||
EXPECT_EQ(str_utf8_dist("flaw", "lawn"), 2);
|
||||
EXPECT_EQ(str_utf8_dist("saturday", "sunday"), 3);
|
||||
}
|
Loading…
Reference in a new issue