Remove /modhelp (fixes #1401)

This commit is contained in:
Dennis Felsing 2018-12-12 09:59:42 +01:00
parent b224a4dafe
commit 5c18d6c481
21 changed files with 97 additions and 241 deletions

View file

@ -361,8 +361,8 @@ if(WEBSOCKETS)
show_dependency_status("Websockets" WEBSOCKETS)
endif()
if(NOT(CURL_FOUND))
message(SEND_ERROR "You must install Curl to compile the DDNet")
if(CLIENT AND NOT(CURL_FOUND))
message(SEND_ERROR "You must install Curl to compile DDNet")
endif()
if(NOT(PYTHONINTERP_FOUND))
message(SEND_ERROR "You must install Python to compile DDNet")
@ -632,12 +632,12 @@ set_glob(ENGINE_SHARED GLOB src/engine/shared
ghost.cpp
ghost.h
global_uuid_manager.cpp
http.cpp
http.h
huffman.cpp
huffman.h
jobs.cpp
jobs.h
json.cpp
json.h
kernel.cpp
linereader.cpp
linereader.h
@ -725,7 +725,7 @@ set(GAME_GENERATED_SHARED
set(DEPS ${DEP_JSON} ${DEP_MD5} ${ZLIB_DEP})
# Libraries
set(LIBS ${CURL_LIBRARIES} ${CRYPTO_LIBRARIES} ${WEBSOCKETS_LIBRARIES} ${ZLIB_LIBRARIES} ${PLATFORM_LIBS})
set(LIBS ${CRYPTO_LIBRARIES} ${WEBSOCKETS_LIBRARIES} ${ZLIB_LIBRARIES} ${PLATFORM_LIBS})
# Add pthreads (on non-Windows) at the end, so that other libraries can depend
# on it.
list(APPEND LIBS ${CMAKE_THREAD_LIBS_INIT})
@ -751,6 +751,8 @@ if(CLIENT)
friends.h
graphics_threaded.cpp
graphics_threaded.h
http.cpp
http.h
input.cpp
input.h
keynames.h
@ -873,6 +875,7 @@ if(CLIENT)
# Libraries
set(LIBS_CLIENT
${LIBS}
${CURL_LIBRARIES}
${FREETYPE_LIBRARIES}
${GLEW_LIBRARIES}
${PNGLITE_LIBRARIES}
@ -912,6 +915,7 @@ if(CLIENT)
target_link_libraries(${TARGET_CLIENT} ${LIBS_CLIENT})
target_include_directories(${TARGET_CLIENT} PRIVATE
${CURL_INCLUDE_DIRS}
${FREETYPE_INCLUDE_DIRS}
${GLEW_INCLUDE_DIRS}
${OGG_INCLUDE_DIRS}
@ -1554,7 +1558,6 @@ foreach(target ${TARGETS_OWN})
target_include_directories(${target} PRIVATE ${PROJECT_BINARY_DIR}/src)
target_include_directories(${target} PRIVATE src)
target_compile_definitions(${target} PRIVATE $<$<CONFIG:Debug>:CONF_DEBUG>)
target_include_directories(${target} PRIVATE ${CURL_INCLUDE_DIRS})
target_include_directories(${target} PRIVATE ${ZLIB_INCLUDE_DIRS})
target_compile_definitions(${target} PRIVATE GLEW_STATIC)
if(CRYPTO_FOUND)

View file

@ -36,13 +36,13 @@
#include <engine/external/md5/md5.h>
#include <engine/client/http.h>
#include <engine/shared/config.h>
#include <engine/shared/compression.h>
#include <engine/shared/datafile.h>
#include <engine/shared/demo.h>
#include <engine/shared/filecollection.h>
#include <engine/shared/ghost.h>
#include <engine/shared/http.h>
#include <engine/shared/network.h>
#include <engine/shared/packer.h>
#include <engine/shared/protocol.h>

View file

@ -6,7 +6,7 @@
#include <memory>
#include <base/hash.h>
#include <engine/shared/http.h>
#include <engine/client/http.h>
class CGraph
{

View file

@ -312,74 +312,3 @@ int CPostJson::AfterInit(void *pCurl)
return 0;
}
static char EscapeJsonChar(char c)
{
switch(c)
{
case '\"': return '\"';
case '\\': return '\\';
case '\b': return 'b';
case '\n': return 'n';
case '\r': return 'r';
case '\t': return 't';
// Don't escape '\f', who uses that. :)
default: return 0;
}
}
char *EscapeJson(char *pBuffer, int BufferSize, const char *pString)
{
dbg_assert(BufferSize > 0, "can't null-terminate the string");
// Subtract the space for null termination early.
BufferSize--;
char *pResult = pBuffer;
while(BufferSize && *pString)
{
char c = *pString;
pString++;
char Escaped = EscapeJsonChar(c);
if(Escaped)
{
if(BufferSize < 2)
{
break;
}
*pBuffer++ = '\\';
*pBuffer++ = Escaped;
BufferSize -= 2;
}
// Assuming ASCII/UTF-8, "if control character".
else if((unsigned char)c < 0x20)
{
// \uXXXX
if(BufferSize < 6)
{
break;
}
str_format(pBuffer, BufferSize, "\\u%04x", c);
pBuffer += 6;
BufferSize -= 6;
}
else
{
*pBuffer++ = c;
BufferSize--;
}
}
*pBuffer = 0;
return pResult;
}
const char *JsonBool(bool Bool)
{
if(Bool)
{
return "true";
}
else
{
return "false";
}
}

View file

@ -1,5 +1,5 @@
#ifndef ENGINE_SHARED_HTTP_H
#define ENGINE_SHARED_HTTP_H
#ifndef ENGINE_CLIENT_HTTP_H
#define ENGINE_CLIENT_HTTP_H
#include <engine/shared/jobs.h>
#include <engine/storage.h>
@ -104,6 +104,4 @@ public:
bool HttpInit(IStorage *pStorage);
void EscapeUrl(char *pBuf, int Size, const char *pStr);
char *EscapeJson(char *pBuffer, int BufferSize, const char *pString);
const char *JsonBool(bool Bool);
#endif // ENGINE_SHARED_HTTP_H
#endif // ENGINE_CLIENT_HTTP_H

View file

@ -2,7 +2,7 @@
#define ENGINE_CLIENT_UPDATER_H
#include <engine/updater.h>
#include <engine/shared/http.h>
#include <engine/client/http.h>
#include <map>
#include <string>

View file

@ -20,7 +20,6 @@
#include <engine/shared/demo.h>
#include <engine/shared/econ.h>
#include <engine/shared/filecollection.h>
#include <engine/shared/http.h>
#include <engine/shared/netban.h>
#include <engine/shared/network.h>
#include <engine/shared/packer.h>
@ -2940,8 +2939,6 @@ int main(int argc, const char **argv) // ignore_convention
pEngineMasterServer->Init();
pEngineMasterServer->Load();
HttpInit(pStorage);
// register all console commands
pServer->RegisterCommands();

View file

@ -159,7 +159,6 @@ MACRO_CONFIG_INT(SvPlayerDemoRecord, sv_player_demo_record, 0, 0, 1, CFGFLAG_SER
MACRO_CONFIG_INT(SvDemoChat, sv_demo_chat, 0, 0, 1, CFGFLAG_SERVER, "Record chat for demos")
MACRO_CONFIG_INT(SvServerInfoPerSecond, sv_server_info_per_second, 50, 1, 1000, CFGFLAG_SERVER, "Maximum number of complete server info responses that are sent out per second")
MACRO_CONFIG_INT(SvVanConnPerSecond, sv_van_conn_per_second, 10, 1, 1000, CFGFLAG_SERVER, "Antispoof specific ratelimit")
MACRO_CONFIG_STR(SvModhelpUrl, sv_modhelp_url, 128, "", CFGFLAG_SERVER|CFGFLAG_NONTEEHISTORIC, "HTTP URL to POST moderator help requests to")
MACRO_CONFIG_STR(EcBindaddr, ec_bindaddr, 128, "localhost", CFGFLAG_ECON, "Address to bind the external console to. Anything but 'localhost' is dangerous")
MACRO_CONFIG_INT(EcPort, ec_port, 0, 0, 0, CFGFLAG_ECON, "Port to use for the external console")

View file

@ -0,0 +1,73 @@
#include <base/system.h>
#include <engine/shared/json.h>
static char EscapeJsonChar(char c)
{
switch(c)
{
case '\"': return '\"';
case '\\': return '\\';
case '\b': return 'b';
case '\n': return 'n';
case '\r': return 'r';
case '\t': return 't';
// Don't escape '\f', who uses that. :)
default: return 0;
}
}
char *EscapeJson(char *pBuffer, int BufferSize, const char *pString)
{
dbg_assert(BufferSize > 0, "can't null-terminate the string");
// Subtract the space for null termination early.
BufferSize--;
char *pResult = pBuffer;
while(BufferSize && *pString)
{
char c = *pString;
pString++;
char Escaped = EscapeJsonChar(c);
if(Escaped)
{
if(BufferSize < 2)
{
break;
}
*pBuffer++ = '\\';
*pBuffer++ = Escaped;
BufferSize -= 2;
}
// Assuming ASCII/UTF-8, "if control character".
else if((unsigned char)c < 0x20)
{
// \uXXXX
if(BufferSize < 6)
{
break;
}
str_format(pBuffer, BufferSize, "\\u%04x", c);
pBuffer += 6;
BufferSize -= 6;
}
else
{
*pBuffer++ = c;
BufferSize--;
}
}
*pBuffer = 0;
return pResult;
}
const char *JsonBool(bool Bool)
{
if(Bool)
{
return "true";
}
else
{
return "false";
}
}

7
src/engine/shared/json.h Normal file
View file

@ -0,0 +1,7 @@
#ifndef ENGINE_SHARED_JSON_H
#define ENGINE_SHARED_JSON_H
char *EscapeJson(char *pBuffer, int BufferSize, const char *pString);
const char *JsonBool(bool Bool);
#endif // ENGINE_SHARED_JSON_H

View file

@ -1399,80 +1399,6 @@ void CGameContext::ConProtectedKill(IConsole::IResult *pResult, void *pUserData)
}
}
void CGameContext::ConModhelp(IConsole::IResult *pResult, void *pUserData)
{
CGameContext *pSelf = (CGameContext *) pUserData;
if(!CheckClientID(pResult->m_ClientID))
return;
CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientID];
if(!pPlayer)
return;
if(pPlayer->m_pPostJson)
{
pSelf->SendChatTarget(pResult->m_ClientID, "Your last request hasn't finished processing yet, please slow down.");
return;
}
int CurTick = pSelf->Server()->Tick();
if(pPlayer->m_ModhelpTick != -1)
{
int TickSpeed = pSelf->Server()->TickSpeed();
int NextModhelpTick = pPlayer->m_ModhelpTick + g_Config.m_SvModhelpDelay * TickSpeed;
if(NextModhelpTick > CurTick)
{
char aBuf[128];
str_format(aBuf, sizeof(aBuf), "You must wait %d seconds before you can execute this command again.",
(NextModhelpTick - CurTick) / TickSpeed);
pSelf->SendChatTarget(pResult->m_ClientID, aBuf);
return;
}
}
pPlayer->m_ModhelpTick = CurTick;
char aBuf[512];
str_format(aBuf, sizeof(aBuf), "Moderator help is requested by '%s' (ID: %d):",
pSelf->Server()->ClientName(pResult->m_ClientID),
pResult->m_ClientID);
// Send the request to all authed clients.
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(pSelf->m_apPlayers[i] && pSelf->Server()->ClientAuthed(i))
{
pSelf->SendChatTarget(i, aBuf);
pSelf->SendChatTarget(i, pResult->GetString(0));
}
}
if(g_Config.m_SvModhelpUrl[0])
{
bool ModeratorPresent = false;
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(pSelf->m_apPlayers[i] && pSelf->Server()->ClientAuthed(i))
{
ModeratorPresent = true;
break;
}
}
char aJson[512];
char aPlayerName[64];
char aMessage[128];
str_format(aJson, sizeof(aJson), "{\"port\":%d,\"moderator_present\":%s,\"player_id\":%d,\"blacklisted\":%s,\"player_name\":\"%s\",\"message\":\"%s\"}",
g_Config.m_SvPort,
JsonBool(ModeratorPresent),
pResult->m_ClientID,
!pSelf->Server()->DnsblWhite(pResult->m_ClientID) ? "true" : "false",
EscapeJson(aPlayerName, sizeof(aPlayerName), pSelf->Server()->ClientName(pResult->m_ClientID)),
EscapeJson(aMessage, sizeof(aMessage), pResult->GetString(0)));
pSelf->Engine()->AddJob(pPlayer->m_pPostJson = std::make_shared<CPostJson>(g_Config.m_SvModhelpUrl, false, aJson));
}
}
#if defined(CONF_SQL)
void CGameContext::ConPoints(IConsole::IResult *pResult, void *pUserData)
{

View file

@ -50,8 +50,6 @@ CHAT_COMMAND("rescue", "", CFGFLAG_CHAT|CFGFLAG_SERVER, ConRescue, this, "Telepo
CHAT_COMMAND("kill", "", CFGFLAG_CHAT|CFGFLAG_SERVER, ConProtectedKill, this, "Kill yourself")
CHAT_COMMAND("modhelp", "r[message]", CFGFLAG_CHAT|CFGFLAG_SERVER, ConModhelp, this, "Request the help of a moderator with a description of the problem")
#if defined(CONF_SQL)
CHAT_COMMAND("times", "?s[player name] ?i[number of times to skip]", CFGFLAG_CHAT|CFGFLAG_SERVER, ConTimes, this, "/times ?s?i shows last 5 times of the server or of a player beginning with name s starting with time i (i = 1 by default)")
CHAT_COMMAND("points", "?r[player name]", CFGFLAG_CHAT|CFGFLAG_SERVER, ConPoints, this, "Shows the global points of a player beginning with name r (your rank by default)")

View file

@ -902,22 +902,6 @@ void CGameContext::OnTick()
m_aMutes[i] = m_aMutes[m_NumMutes];
}
}
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(m_apPlayers[i] && m_apPlayers[i]->m_pPostJson)
{
switch(m_apPlayers[i]->m_pPostJson->State())
{
case HTTP_DONE:
m_apPlayers[i]->m_pPostJson = NULL;
break;
case HTTP_ERROR:
dbg_msg("modhelp", "http request failed for cid=%d", i);
m_apPlayers[i]->m_pPostJson = NULL;
break;
}
}
}
if(Server()->Tick() % (g_Config.m_SvAnnouncementInterval * Server()->TickSpeed() * 60) == 0)
{

View file

@ -357,7 +357,6 @@ private:
static void ConUnmute(IConsole::IResult *pResult, void *pUserData);
static void ConMutes(IConsole::IResult *pResult, void *pUserData);
static void ConModerate(IConsole::IResult *pResult, void *pUserData);
static void ConModhelp(IConsole::IResult *pResult, void *pUserData);
static void ConList(IConsole::IResult *pResult, void *pUserData);
static void ConSetDDRTeam(IConsole::IResult *pResult, void *pUserData);

View file

@ -69,7 +69,6 @@ void CPlayer::Reset()
m_LastWhisperTo = -1;
m_LastSetSpectatorMode = 0;
m_TimeoutCode[0] = '\0';
m_ModhelpTick = -1;
m_TuneZone = 0;
m_TuneZoneOld = m_TuneZone;

View file

@ -6,7 +6,6 @@
// this include should perhaps be removed
#include "entities/character.h"
#include "gamecontext.h"
#include <engine/shared/http.h>
// player object
class CPlayer
@ -175,7 +174,6 @@ public:
int m_ChatScore;
bool m_Moderating;
int m_ModhelpTick;
bool AfkTimer(int new_target_x, int new_target_y); //returns true if kicked
void AfkVoteTimer(CNetObj_PlayerInput *NewTarget);
@ -198,7 +196,6 @@ public:
#if defined(CONF_SQL)
int64 m_LastSQLQuery;
#endif
std::shared_ptr<CPostJson> m_pPostJson;
};
#endif

View file

@ -2,6 +2,7 @@
#include <engine/shared/config.h>
#include <engine/shared/snapshot.h>
#include <engine/shared/json.h>
#include <game/gamecore.h>
static const char TEEHISTORIAN_NAME[] = "teehistorian@ddnet.tw";

View file

@ -3,7 +3,6 @@
#include <base/hash.h>
#include <engine/console.h>
#include <engine/shared/http.h>
#include <engine/shared/packer.h>
#include <engine/shared/protocol.h>
#include <game/generated/protocol.h>

View file

@ -158,8 +158,6 @@ MACRO_CONFIG_INT(SvSendVotesPerTick, sv_send_votes_per_tick, 5, 1, 15, CFGFLAG_S
MACRO_CONFIG_INT(SvRescue, sv_rescue, 0, 0, 1, CFGFLAG_SERVER, "Allow /rescue command so players can teleport themselves out of freeze")
MACRO_CONFIG_INT(SvRescueDelay, sv_rescue_delay, 5, 0, 1000, CFGFLAG_SERVER, "Number of seconds between two rescues")
MACRO_CONFIG_INT(SvModhelpDelay, sv_modhelp_delay, 60, 0, 0, CFGFLAG_SERVER, "Number of seconds to wait before executing /modhelp again")
// debug
#ifdef CONF_DEBUG // this one can crash the server if not used correctly
MACRO_CONFIG_INT(DbgDummies, dbg_dummies, 0, 0, 15, CFGFLAG_SERVER, "")

View file

@ -1,51 +0,0 @@
#!/usr/bin/env python3
from flask import Flask, make_response, request
import http
import os
import re
import requests
SERVER=os.getenv('MODHELP_SERVER')
# Generate one by right-clicking on the server icon in the sidebar, clicking on
# "Server Settings" → "Webhooks" → "Create Webhook". You can then select the
# channel in which the messages should appear. Copy the "Webhook URL" to the
# following config variable:
# DISCORD_WEBHOOK="https://discordapp.com/api/webhooks/.../..."
DISCORD_WEBHOOK=os.getenv('MODHELP_DISCORD_WEBHOOK')
DISCORD_MESSAGE=os.getenv('MODHELP_DISCORD_FORMAT', "<{player_id}:**{player_name}**> {message}")
DISCORD_MESSAGE_PREFIX=os.getenv('MODHELP_DISCORD_PREFIX')
app = Flask(__name__)
def sanitize(s):
return re.sub(r"([^\0- 0-9A-Za-z])", r"\\\1", s)
def no_content():
return make_response("", http.HTTPStatus.NO_CONTENT)
def sanitize_string_values(dictionary):
return {k: v if not isinstance(v, str) else sanitize(v) for k, v in dictionary.items()}
@app.route("/modhelp", methods=['POST'])
def modhelp():
json = request.get_json()
if "server" not in json:
if SERVER:
json["server"] = SERVER
if "server" not in json:
user = "{port}".format(**json)
else:
user = "{server}:{port}".format(**json)
discord_prefix = DISCORD_MESSAGE_PREFIX + " " if DISCORD_MESSAGE_PREFIX is not None else ""
discord_message = discord_prefix + DISCORD_MESSAGE.format(**sanitize_string_values(json))
if DISCORD_WEBHOOK:
try:
requests.post(DISCORD_WEBHOOK, data={"username": user, "content": discord_message}).raise_for_status()
except requests.HTTPError as e:
print(repr(e))
raise
return no_content()

View file

@ -1,6 +1,6 @@
#include <gtest/gtest.h>
#include <engine/shared/http.h>
#include <engine/shared/json.h>
TEST(Json, Escape)
{