From 5e637af89f65d7ec937ab3babdf76125505fef24 Mon Sep 17 00:00:00 2001 From: AntINFINAit <43472352+AntINFINAit@users.noreply.github.com> Date: Thu, 26 Nov 2020 13:28:43 +0200 Subject: [PATCH] A lot of changes for the 0.6 release. --- .gitignore | 2 + ImpostorHQ.Command/Impostor.Command.sln | 47 ++++- .../AnnouncementServer.cs | 1 + .../Impostor.Commands.Core/Class.cs | 23 ++- .../DashBoard/HTTPServer.cs | 97 +++++++--- .../DashBoard/PerformanceMonitors.cs | 2 + .../DashBoard/QuiteEffectiveDetector.cs | 172 ++++++++++++++++++ .../DashBoard/WebAPIServer.cs | 29 ++- .../GameChatCommandInterface.cs | 17 +- .../PluginFileSystem.cs | 13 +- .../QuantumExtensionDirector/PluginLoader.cs | 13 +- .../QuiteExtendableDirectInterface.cs | 18 +- .../Impostor.Commands.Core/Structures.cs | 36 ++++ .../ImpostorHQ.Plugin.DDoSInfo.csproj | 11 ++ .../ImpostorHQ.Plugin.DDoSInfo/MainClass.cs | 127 +++++++++++++ .../ImpostorHQ.Plugin.DiskStatus.csproj | 11 ++ .../ImpostorHQ.Plugin.DiskStatus/MainClass.cs | 110 +++++++++++ .../MainClass.cs | 2 +- .../MainClass.cs | 2 +- ImpostorHQ.Command/Plugin.Test/MainClass.cs | 41 ++++- 20 files changed, 720 insertions(+), 54 deletions(-) create mode 100644 ImpostorHQ.Command/Impostor.Commands.Core/DashBoard/QuiteEffectiveDetector.cs create mode 100644 ImpostorHQ.Command/ImpostorHQ.Plugin.DDoSInfo/ImpostorHQ.Plugin.DDoSInfo.csproj create mode 100644 ImpostorHQ.Command/ImpostorHQ.Plugin.DDoSInfo/MainClass.cs create mode 100644 ImpostorHQ.Command/ImpostorHQ.Plugin.DiskStatus/ImpostorHQ.Plugin.DiskStatus.csproj create mode 100644 ImpostorHQ.Command/ImpostorHQ.Plugin.DiskStatus/MainClass.cs diff --git a/.gitignore b/.gitignore index cb71238..f79efb9 100644 --- a/.gitignore +++ b/.gitignore @@ -345,3 +345,5 @@ Server.Test Server.Test/ /ImpostorHQ.Command/ImpostorHQ.TestPlugins.antiSync /ImpostorHQ.Command/SupremeSyncServer +/ImpostorHQ.Command/ImpostorHQ.GameMode.Timeframe +/ImpostorHQ.Command/Optimization diff --git a/ImpostorHQ.Command/Impostor.Command.sln b/ImpostorHQ.Command/Impostor.Command.sln index 49f298b..3da3c16 100644 --- a/ImpostorHQ.Command/Impostor.Command.sln +++ b/ImpostorHQ.Command/Impostor.Command.sln @@ -17,7 +17,17 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImpostorHQ.Plugin.Fashionab EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImpostorHQ.Plugin.Fashionable.Designer", "ImpostorHQ.Plugin.Fashionable.Designer\ImpostorHQ.Plugin.Fashionable.Designer.csproj", "{76E59656-C45F-4F28-A184-9CB15EE5AF73}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImpostorHQ.Plugin.HallOfShame", "ImpostorHQ.Plugin.HallOfShame\ImpostorHQ.Plugin.HallOfShame.csproj", "{6E531144-02F5-46A3-9758-D660A09EE7C0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImpostorHQ.Plugin.HallOfShame", "ImpostorHQ.Plugin.HallOfShame\ImpostorHQ.Plugin.HallOfShame.csproj", "{6E531144-02F5-46A3-9758-D660A09EE7C0}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Game Modes", "Game Modes", "{74E1796B-1ECB-415E-906A-BBF1C7FFB31F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImpostorHQ.GameMode.Timeframe", "ImpostorHQ.GameMode.Timeframe\ImpostorHQ.GameMode.Timeframe.csproj", "{45F86E59-A175-48AD-BB42-D8D961139094}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Optimization", "Optimization\Optimization.csproj", "{66D3D3C3-EE1C-4486-BB42-F76150E5213F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImpostorHQ.Plugin.DDoSInfo", "ImpostorHQ.Plugin.DDoSInfo\ImpostorHQ.Plugin.DDoSInfo.csproj", "{5C3E0DD4-DA0C-454C-8200-B82A344D2D56}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImpostorHQ.Plugin.DiskStatus", "ImpostorHQ.Plugin.DiskStatus\ImpostorHQ.Plugin.DiskStatus.csproj", "{92B1CBAF-43CC-4FEA-BC66-F89C72EBD84A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -83,6 +93,38 @@ Global {6E531144-02F5-46A3-9758-D660A09EE7C0}.Release_Ubuntu|Any CPU.Build.0 = Release|Any CPU {6E531144-02F5-46A3-9758-D660A09EE7C0}.Release|Any CPU.ActiveCfg = Release|Any CPU {6E531144-02F5-46A3-9758-D660A09EE7C0}.Release|Any CPU.Build.0 = Release|Any CPU + {45F86E59-A175-48AD-BB42-D8D961139094}.Debug_Ubuntu|Any CPU.ActiveCfg = Debug|Any CPU + {45F86E59-A175-48AD-BB42-D8D961139094}.Debug_Ubuntu|Any CPU.Build.0 = Debug|Any CPU + {45F86E59-A175-48AD-BB42-D8D961139094}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {45F86E59-A175-48AD-BB42-D8D961139094}.Debug|Any CPU.Build.0 = Debug|Any CPU + {45F86E59-A175-48AD-BB42-D8D961139094}.Release_Ubuntu|Any CPU.ActiveCfg = Release|Any CPU + {45F86E59-A175-48AD-BB42-D8D961139094}.Release_Ubuntu|Any CPU.Build.0 = Release|Any CPU + {45F86E59-A175-48AD-BB42-D8D961139094}.Release|Any CPU.ActiveCfg = Release|Any CPU + {45F86E59-A175-48AD-BB42-D8D961139094}.Release|Any CPU.Build.0 = Release|Any CPU + {66D3D3C3-EE1C-4486-BB42-F76150E5213F}.Debug_Ubuntu|Any CPU.ActiveCfg = Debug|Any CPU + {66D3D3C3-EE1C-4486-BB42-F76150E5213F}.Debug_Ubuntu|Any CPU.Build.0 = Debug|Any CPU + {66D3D3C3-EE1C-4486-BB42-F76150E5213F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {66D3D3C3-EE1C-4486-BB42-F76150E5213F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {66D3D3C3-EE1C-4486-BB42-F76150E5213F}.Release_Ubuntu|Any CPU.ActiveCfg = Release|Any CPU + {66D3D3C3-EE1C-4486-BB42-F76150E5213F}.Release_Ubuntu|Any CPU.Build.0 = Release|Any CPU + {66D3D3C3-EE1C-4486-BB42-F76150E5213F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {66D3D3C3-EE1C-4486-BB42-F76150E5213F}.Release|Any CPU.Build.0 = Release|Any CPU + {5C3E0DD4-DA0C-454C-8200-B82A344D2D56}.Debug_Ubuntu|Any CPU.ActiveCfg = Debug|Any CPU + {5C3E0DD4-DA0C-454C-8200-B82A344D2D56}.Debug_Ubuntu|Any CPU.Build.0 = Debug|Any CPU + {5C3E0DD4-DA0C-454C-8200-B82A344D2D56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5C3E0DD4-DA0C-454C-8200-B82A344D2D56}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5C3E0DD4-DA0C-454C-8200-B82A344D2D56}.Release_Ubuntu|Any CPU.ActiveCfg = Release|Any CPU + {5C3E0DD4-DA0C-454C-8200-B82A344D2D56}.Release_Ubuntu|Any CPU.Build.0 = Release|Any CPU + {5C3E0DD4-DA0C-454C-8200-B82A344D2D56}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5C3E0DD4-DA0C-454C-8200-B82A344D2D56}.Release|Any CPU.Build.0 = Release|Any CPU + {92B1CBAF-43CC-4FEA-BC66-F89C72EBD84A}.Debug_Ubuntu|Any CPU.ActiveCfg = Debug|Any CPU + {92B1CBAF-43CC-4FEA-BC66-F89C72EBD84A}.Debug_Ubuntu|Any CPU.Build.0 = Debug|Any CPU + {92B1CBAF-43CC-4FEA-BC66-F89C72EBD84A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {92B1CBAF-43CC-4FEA-BC66-F89C72EBD84A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {92B1CBAF-43CC-4FEA-BC66-F89C72EBD84A}.Release_Ubuntu|Any CPU.ActiveCfg = Release|Any CPU + {92B1CBAF-43CC-4FEA-BC66-F89C72EBD84A}.Release_Ubuntu|Any CPU.Build.0 = Release|Any CPU + {92B1CBAF-43CC-4FEA-BC66-F89C72EBD84A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {92B1CBAF-43CC-4FEA-BC66-F89C72EBD84A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -91,6 +133,9 @@ Global {F4D0DB97-AEE8-49B8-A568-0D5B2F714AA0} = {591D8590-F104-4D3A-AD0B-B8C825AA66B7} {76E59656-C45F-4F28-A184-9CB15EE5AF73} = {591D8590-F104-4D3A-AD0B-B8C825AA66B7} {6E531144-02F5-46A3-9758-D660A09EE7C0} = {591D8590-F104-4D3A-AD0B-B8C825AA66B7} + {45F86E59-A175-48AD-BB42-D8D961139094} = {74E1796B-1ECB-415E-906A-BBF1C7FFB31F} + {5C3E0DD4-DA0C-454C-8200-B82A344D2D56} = {591D8590-F104-4D3A-AD0B-B8C825AA66B7} + {92B1CBAF-43CC-4FEA-BC66-F89C72EBD84A} = {591D8590-F104-4D3A-AD0B-B8C825AA66B7} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {362CA084-8ED3-49DF-91CA-BF06EE4A969C} diff --git a/ImpostorHQ.Command/Impostor.Commands.Core/AnnouncementServer.cs b/ImpostorHQ.Command/Impostor.Commands.Core/AnnouncementServer.cs index f79cacf..eddf651 100644 --- a/ImpostorHQ.Command/Impostor.Commands.Core/AnnouncementServer.cs +++ b/ImpostorHQ.Command/Impostor.Commands.Core/AnnouncementServer.cs @@ -3,6 +3,7 @@ using Hazel.Udp; using System.IO; using System.Net; +using System.Runtime.CompilerServices; using Impostor.Commands.Core.SELF; namespace Impostor.Commands.Core diff --git a/ImpostorHQ.Command/Impostor.Commands.Core/Class.cs b/ImpostorHQ.Command/Impostor.Commands.Core/Class.cs index 8168c90..4f5b8a4 100644 --- a/ImpostorHQ.Command/Impostor.Commands.Core/Class.cs +++ b/ImpostorHQ.Command/Impostor.Commands.Core/Class.cs @@ -30,7 +30,7 @@ namespace Impostor.Commands.Core [ImpostorPlugin("ImpostorHQ","Impostor HeadQuarters API","anti, Dimaguy","0.0.4 beta")] public class Class : PluginBase { - public const int PluginApiVersion = 1; + public const int PluginApiVersion = 3; #region Members //indicates that the plugin is active. @@ -74,6 +74,7 @@ public class Class : PluginBase public QuodEratDemonstrandum.QuiteElegantDirectory QED{ get; private set; } public PluginLoader PluginLoader { get; private set; } public QuiteExtendableDirectInterface QEDi { get; set; } + public QuiteEffectiveDetector QEDetector { get; private set; } #endregion /// /// This constructor will be 'injected' with the required references by the plugin API. @@ -124,6 +125,7 @@ public override ValueTask DisableAsync() AnnouncementManager.Shutdown(); QED.Shutdown(); PluginLoader.Shutdown(); + QEDetector.Shutdown(); return default; } #endregion @@ -166,12 +168,13 @@ public void Main() } this.LogManager = new SpatiallyEfficientLogFileManager("hqlogs"); + QEDetector = new QuiteEffectiveDetector(250); InitializeInterfaces(); InitializeServers(); HighCourt = new JusticeSystem(BanFolder,ChatCommandCfg.ReportsRequiredForBan,Logger,ChatInterface,this); HighCourt.OnPlayerBanned += PlayerBanned; //after we initialize everything, we can load the plugins. - QEDi = new QuiteExtendableDirectInterface() + QEDi = new QuiteExtendableDirectInterface(this) { Logger = Logger, MainThread = MainThread, @@ -189,11 +192,11 @@ public void Main() LogManager = LogManager, AnnouncementServer = AnnouncementManager, QED = QED, + QEDetector = QEDetector, UnsafeDirectReference = this }; this.PluginLoader = new PluginLoader(PluginFolderPath, QEDi, PluginApiVersion); PluginLoader.LoadPlugins(); - } private void InitializeInterfaces() @@ -219,8 +222,8 @@ private void InitializeServers() var error404Html = File.ReadAllText(Path.Combine("dashboard", "404.html")); var errorMimeHtml = File.ReadAllText(Path.Combine("dashboard", "mime.html")); //we initialize our servers and set up the events. - this.ApiServer = new WebApiServer(Configuration.APIPort, Configuration.ListenInterface, Configuration.APIKeys.ToArray(), Logger,GameManager,this); - this.DashboardServer = new HttpServer(Configuration.ListenInterface, Configuration.WebsitePort, ClientHTML, error404Html, errorMimeHtml,this,ApiServer); + this.ApiServer = new WebApiServer(Configuration.APIPort, Configuration.ListenInterface, Configuration.APIKeys.ToArray(), Logger,GameManager,this,QEDetector); + this.DashboardServer = new HttpServer(Configuration.ListenInterface, Configuration.WebsitePort, ClientHTML, error404Html, errorMimeHtml,this,ApiServer,QEDetector); Logger.LogInformation($"ImpostorHQ : API Server listening on: {Configuration.ListenInterface}:{Configuration.APIPort}. Dashboard listening on: {Configuration.ListenInterface}:{Configuration.WebsitePort}/client.html"); this.AnnouncementManager = new AnnouncementServer(this,"configs"); ApiServer.OnMessageReceived += DashboardCommandReceived; @@ -887,6 +890,11 @@ private void DashboardCommandReceived(Structures.BaseMessage message,IWebSocketC break; } + default: + OnExternalCommandInvoked?.Invoke(cmd,message.Text,isSingle,client); + isSingle = true; //inhibit 'Command executed successfully'. + break; + } if (commandHandled) @@ -1048,5 +1056,10 @@ public void ConsolePluginStatus(string message) { Logger.LogInformation("ImpostorHQ Plugin System : " + message); } + + public delegate void DelDashboardCommandInvoked(string command, string data, bool single, + IWebSocketConnection source); + + public event DelDashboardCommandInvoked OnExternalCommandInvoked; } } \ No newline at end of file diff --git a/ImpostorHQ.Command/Impostor.Commands.Core/DashBoard/HTTPServer.cs b/ImpostorHQ.Command/Impostor.Commands.Core/DashBoard/HTTPServer.cs index c4a651a..db71e35 100644 --- a/ImpostorHQ.Command/Impostor.Commands.Core/DashBoard/HTTPServer.cs +++ b/ImpostorHQ.Command/Impostor.Commands.Core/DashBoard/HTTPServer.cs @@ -1,10 +1,13 @@ using System; +using System.Buffers; +using System.Collections.Concurrent; using System.IO; using System.Linq; using System.Net; using System.Text; using System.Net.Sockets; using System.Collections.Generic; +using System.Runtime.CompilerServices; using Impostor.Commands.Core.SELF; namespace Impostor.Commands.Core.DashBoard @@ -13,6 +16,7 @@ public class HttpServer { // our main listener. private TcpListener Listener { get; set; } + private ArrayPool ReceiveBufferPool { get; set; } private Impostor.Commands.Core.Class Interface { get; set; } /// /// The constant webpages. They are read from the disk and always remain constant. @@ -23,6 +27,8 @@ public class HttpServer private WebApiServer ApiServer { get; set; } private CsvComposer LogComposer { get; set; } public List InvalidPageHandlers { get; private set; } + public QuiteEffectiveDetector QEDetector { get; private set; } + public ConcurrentBag AttackerAddresses { get; private set; } /// /// Creates a new instance of our HTTP server. This is used to inject the HTML client into browsers. /// @@ -31,9 +37,10 @@ public class HttpServer /// The webpage. /// An HTML document used to indicate 404 errors. /// An HTML document used to indicate that a type of data is unsupported. This should never happen under normal circumstances, as the dashboard only uses supported file types. - public HttpServer(string listenInterface, ushort port,string document,string document404Error,string documentMimeError, Impostor.Commands.Core.Class Interface,WebApiServer apiServer) + public HttpServer(string listenInterface, ushort port,string document,string document404Error,string documentMimeError, Impostor.Commands.Core.Class Interface,WebApiServer apiServer, QuiteEffectiveDetector dosDetector) { //utf8 is standard. + this.ReceiveBufferPool = ArrayPool.Shared; this.Document = Encoding.UTF8.GetBytes(document); this.Document404Error = Encoding.UTF8.GetBytes(document404Error); this.DocumentTypeNotSupported = Encoding.UTF8.GetBytes(documentMimeError); @@ -43,11 +50,14 @@ public HttpServer(string listenInterface, ushort port,string document,string doc this.ApiServer = apiServer; this.LogComposer = new CsvComposer(); this.InvalidPageHandlers = new List(); + this.QEDetector = dosDetector; + this.AttackerAddresses = new ConcurrentBag(); Listener.Start(); //we begin listening and accepting clients. Listener.BeginAcceptTcpClient(EndAccept, Listener); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void AddInvalidPageHandler(string handler) { lock (InvalidPageHandlers) @@ -65,14 +75,27 @@ private void EndAccept(IAsyncResult ar) var listener = (TcpListener) ar.AsyncState; if (listener == null) return; string address = "[BEFORE ACCEPT]"; - if(Running) listener.BeginAcceptTcpClient(EndAccept, listener); + byte[] receiveBuffer = null; + if (Running) listener.BeginAcceptTcpClient(EndAccept, listener); try { var client = listener.EndAcceptTcpClient(ar); - var ns = client.GetStream(); address = (((IPEndPoint) client.Client.RemoteEndPoint).Address).ToString(); + + if(QEDetector.IsAttacking(((IPEndPoint)(client.Client.RemoteEndPoint)).Address)) + { + if (!AttackerAddresses.Contains(address)) + { + ApiServer.Push( + $"Under denial of service attack from: {address}! The address has been booted from accessing the HTTP server, for 5 minutes. Please take action!", + "-HIGHEST PRIORITY/CRITICAL-", Structures.MessageFlag.ConsoleLogMessage); + AttackerAddresses.Add(address); + } + return; + } + receiveBuffer = ReceiveBufferPool.Rent(1024); //should not give us trouble. + var ns = client.GetStream(); var startPos = 0; - var receiveBuffer = new byte[1024]; //should not give us trouble. //maybe i will use 'read' at some point... var read = ns.Read(receiveBuffer, 0, receiveBuffer.Length); @@ -80,33 +103,40 @@ private void EndAccept(IAsyncResult ar) if (strData.Substring(0, 3) != "GET") { //only GET is allowed. - WriteHeader("HTTP/1.1", "text/html", DocumentTypeNotSupported.Length, " 501 Not Implemented", ref ns); + WriteHeader("HTTP/1.1", "text/html", DocumentTypeNotSupported.Length, " 501 Not Implemented", + ref ns); ns.Write(DocumentTypeNotSupported, 0, DocumentTypeNotSupported.Length); ns.Dispose(); client.Dispose(); return; } + // Extract the request. - startPos = strData.IndexOf("HTTP", 1,StringComparison.InvariantCultureIgnoreCase); + startPos = strData.IndexOf("HTTP", 1, StringComparison.InvariantCultureIgnoreCase); var version = strData.Substring(startPos, 8); var request = strData.Substring(0, startPos - 1); request = request.Replace("\\", "/"); - if ((request.IndexOf(".",StringComparison.InvariantCultureIgnoreCase) < 1) && (!request.EndsWith("/"))) + if ((request.IndexOf(".", StringComparison.InvariantCultureIgnoreCase) < 1) && (!request.EndsWith("/"))) { request += "/"; } - startPos = request.LastIndexOf("/",StringComparison.CurrentCultureIgnoreCase) + 1; + + startPos = request.LastIndexOf("/", StringComparison.CurrentCultureIgnoreCase) + 1; var file = request.Substring(startPos); - var directory = request.Substring(request.IndexOf("/",StringComparison.InvariantCultureIgnoreCase), request.LastIndexOf("/",StringComparison.InvariantCultureIgnoreCase) - 3); + var directory = request.Substring(request.IndexOf("/", StringComparison.InvariantCultureIgnoreCase), + request.LastIndexOf("/", StringComparison.InvariantCultureIgnoreCase) - 3); //COMPARISION : If the path is clean, move on to the next comparision. If not, send the error. if (!directory.Contains("..")) // Only root ('/') is allowed. { var cleanedPath = GetLocalPath(file, directory); - if (!(cleanedPath.Contains("players.csv")||cleanedPath.Contains("logs")) && cleanedPath.Contains("?")) //you are angering me dima + if (!(cleanedPath.Contains("players.csv") || cleanedPath.Contains("logs")) && + cleanedPath.Contains("?")) //you are angering me dima { - cleanedPath = cleanedPath.Remove(cleanedPath.IndexOf("?",StringComparison.InvariantCultureIgnoreCase), - cleanedPath.Length - cleanedPath.IndexOf("?",StringComparison.InvariantCultureIgnoreCase)); + cleanedPath = cleanedPath.Remove( + cleanedPath.IndexOf("?", StringComparison.InvariantCultureIgnoreCase), + cleanedPath.Length - cleanedPath.IndexOf("?", StringComparison.InvariantCultureIgnoreCase)); } + //COMPARISION : If file exists, move on to the next comparision. If not, send the error. if (File.Exists(cleanedPath)) { @@ -117,33 +147,34 @@ private void EndAccept(IAsyncResult ar) //COMPARISION : Send client from memory or an unloaded file from the disk. if (cleanedPath.Contains("client.html")) { - WriteHeader(version,"text/html",Document.Length," 200 OK",ref ns); - ns.Write(Document,0,Document.Length); + WriteHeader(version, "text/html", Document.Length, " 200 OK", ref ns); + ns.Write(Document, 0, Document.Length); } else { var document = File.ReadAllBytes(cleanedPath); - WriteHeader(version,mimeType,document.Length," 200 OK",ref ns); - ns.Write(document,0,document.Length); + WriteHeader(version, mimeType, document.Length, " 200 OK", ref ns); + ns.Write(document, 0, document.Length); } } else { - WriteHeader(version, "text/html", DocumentTypeNotSupported.Length, " 501 Not Implemented", ref ns); + WriteHeader(version, "text/html", DocumentTypeNotSupported.Length, " 501 Not Implemented", + ref ns); ns.Write(DocumentTypeNotSupported, 0, DocumentTypeNotSupported.Length); } } else { List handlers; - lock (InvalidPageHandlers) handlers = InvalidPageHandlers.ToList(); - if (cleanedPath.Contains("players.csv")&&cleanedPath.Contains('?')) + lock (InvalidPageHandlers) handlers = InvalidPageHandlers.ToList(); + if (cleanedPath.Contains("players.csv") && cleanedPath.Contains('?')) { var key = cleanedPath.Substring(cleanedPath.IndexOf('?') + 1); - if(ApiServer.CheckKey(key)) + if (ApiServer.CheckKey(key)) { var response = Encoding.UTF8.GetBytes(Interface.CompilePlayers()); - WriteHeader(version, "text/csv", (int)response.Length, " 200 OK", ref ns); + WriteHeader(version, "text/csv", (int) response.Length, " 200 OK", ref ns); ns.Write(response, 0, response.Length); ns.Flush(); //ApiServer.Push($"Players listed by: {address}, with key : {key}.",Structures.ServerSources.CommandSystem,Structures.MessageFlag.ConsoleLogMessage); @@ -153,14 +184,15 @@ private void EndAccept(IAsyncResult ar) var data = Encoding.UTF8.GetBytes( "

You have entered an invalid key. Please stop trying to break into our system!

"); WriteHeader(version, "text/html", data.Length, " 200 OK", ref ns); - ns.Write(data,0,data.Length); + ns.Write(data, 0, data.Length); } } else if (cleanedPath.Contains("logs.csv") && cleanedPath.Contains('?')) { var requestData = cleanedPath.Substring(cleanedPath.IndexOf('?') + 1).Split('&'); - if (requestData.Length == 2 && !string.IsNullOrEmpty(requestData[0])&& !string.IsNullOrEmpty(requestData[1])) + if (requestData.Length == 2 && !string.IsNullOrEmpty(requestData[0]) && + !string.IsNullOrEmpty(requestData[1])) { var key = requestData[0]; if (!ApiServer.CheckKey(key)) @@ -172,7 +204,8 @@ private void EndAccept(IAsyncResult ar) } else { - var requestedLog = Path.Combine("hqlogs", Path.GetFileNameWithoutExtension(requestData[1]) + ".self"); + var requestedLog = Path.Combine("hqlogs", + Path.GetFileNameWithoutExtension(requestData[1]) + ".self"); var logs = Interface.LogManager.GetLogNames(); if (!logs.Contains(requestedLog)) { @@ -191,9 +224,10 @@ private void EndAccept(IAsyncResult ar) lock (Interface.LogManager.FileLock) { data = new byte[fs.Length]; - fs.Read(data, 0, (int)fs.Length); + fs.Read(data, 0, (int) fs.Length); } } + //i am not doing it directly off the stream so i don't lock the manager for too long... //should not be a problem, logs will be quite small. var logData = GetCsv(data); @@ -206,7 +240,8 @@ private void EndAccept(IAsyncResult ar) } else if (handlers.Contains(cleanedPath)) { - OnSpecialHandlerInvoked?.Invoke(cleanedPath.Replace("dashboard\\","").Replace("dashboard/",""),ns,version,address); + OnSpecialHandlerInvoked?.Invoke( + cleanedPath.Replace("dashboard\\", "").Replace("dashboard/", ""), ns, version, address); } else { @@ -220,13 +255,19 @@ private void EndAccept(IAsyncResult ar) WriteHeader(version, "text/html", Document404Error.Length, " 404 Not Found", ref ns); ns.Write(Document404Error, 0, Document404Error.Length); } + ns.Dispose(); client.Dispose(); } - catch(Exception ex) + catch (Exception ex) { // *shutting down - if(!address.Equals("[BEFORE ACCEPT]")) Interface.LogManager.LogError($"SRC: {address}: {ex.ToString()}", Shared.ErrorLocation.HttpServer); + if (!address.Equals("[BEFORE ACCEPT]")) + Interface.LogManager.LogError($"SRC: {address}: {ex.ToString()}", Shared.ErrorLocation.HttpServer); + } + finally + { + if(receiveBuffer!=null)ReceiveBufferPool.Return(receiveBuffer); } } diff --git a/ImpostorHQ.Command/Impostor.Commands.Core/DashBoard/PerformanceMonitors.cs b/ImpostorHQ.Command/Impostor.Commands.Core/DashBoard/PerformanceMonitors.cs index dcddea3..5be54d2 100644 --- a/ImpostorHQ.Command/Impostor.Commands.Core/DashBoard/PerformanceMonitors.cs +++ b/ImpostorHQ.Command/Impostor.Commands.Core/DashBoard/PerformanceMonitors.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using System.Diagnostics; +using System.Runtime.CompilerServices; namespace Impostor.Commands.Core.DashBoard { @@ -23,6 +24,7 @@ public PerformanceMonitors() private DateTime _startTime, _endTime; private TimeSpan _startUsage, _endUsage; + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DoCount() { while (Running) diff --git a/ImpostorHQ.Command/Impostor.Commands.Core/DashBoard/QuiteEffectiveDetector.cs b/ImpostorHQ.Command/Impostor.Commands.Core/DashBoard/QuiteEffectiveDetector.cs new file mode 100644 index 0000000..c38fcda --- /dev/null +++ b/ImpostorHQ.Command/Impostor.Commands.Core/DashBoard/QuiteEffectiveDetector.cs @@ -0,0 +1,172 @@ +using System; +using System.Net; +using System.Threading; +using System.Collections.Generic; + +namespace Impostor.Commands.Core.DashBoard +{ + public class QuiteEffectiveDetector + { + // this super simple system will offer denial of service detection. + // using this, we can effectively boot skids off. + + /// + /// Will get or set the maximum packet rate allowed. + /// + public ushort RateThreshold { get; set; } + + private readonly List ClearerQueue = new List(); + private readonly List UnBlockerQueue = new List(); + private readonly List Blocked = new List(); + private readonly Dictionary History = new Dictionary(); + public bool Running { get; private set; } + public QuiteEffectiveDetector(ushort maxRequestsPerMinute) + { + this.Running = true; + this.RateThreshold = (ushort)(maxRequestsPerMinute/6); + var t = new Thread(UpdaterCallback); + t.Start(); + } + + /// + /// Use this whenever you receive a new connection. This will indicate whether or not the client is attacking you / the HTTP server / the API server. + /// + /// The address to check. + /// True if the address is attacking. + public bool IsAttacking(IPAddress address) + { + lock (Blocked) + { + if (Blocked.Contains(address)) return true; + } + + lock (History) + { + if (History.ContainsKey(address)) + { + if (History[address] > RateThreshold) + { + lock(Blocked) Blocked.Add(address); + lock(UnBlockerQueue) UnBlockerQueue.Add(new Removable(DateTime.Now, address)); + History.Remove(address); + OnBlockedOnce?.Invoke(address); + } + else + { + History[address]++; + } + } + else + { + History.Add(address,1); + lock(ClearerQueue) ClearerQueue.Add(new Removable(DateTime.Now, address)); + } + } + return false; + } + + private void UpdaterCallback() + { + while (Running) + { + Thread.Sleep(1000); + lock (ClearerQueue) + { + for (int i = 0; i < ClearerQueue.Count; i++) + { + var item = ClearerQueue[i]; + if ((DateTime.Now - item.StartTime).TotalSeconds < 10) continue; + lock (History) + { + if (History.ContainsKey(item.Address)) History.Remove(item.Address); + } + + ClearerQueue.Remove(item); + } + } + + lock (UnBlockerQueue) + { + for (int i = 0; i < UnBlockerQueue.Count; i++) + { + var item = UnBlockerQueue[i]; + if((DateTime.Now - item.StartTime).TotalSeconds < 300) continue; + UnBlockerQueue.Remove(item); + lock (Blocked) + { + if (Blocked.Contains(item.Address)) Blocked.Remove(item.Address); + } + } + } + } + } + + /// + /// Will return the current attackers. + /// + /// + public IEnumerable GetBlocked() + { + lock (UnBlockerQueue) + { + if(UnBlockerQueue.Count==0) yield break; + foreach (var removable in UnBlockerQueue) + { + yield return new BlockedAddressInfo(removable.Address,removable.StartTime,DateTime.Now,(uint)(DateTime.Now - removable.StartTime).TotalSeconds); + } + } + } + + public void Shutdown() + { + Running = false; + } + + internal class Removable + { + public Removable(DateTime start, IPAddress address) + { + this.Address = address; + this.StartTime = start; + } + public DateTime StartTime { get; set; } + public IPAddress Address { get; set; } + } + + public class BlockedAddressInfo + { + public BlockedAddressInfo(IPAddress addr, DateTime start, DateTime current, uint ago) + { + this.Address = addr; + this.AttackStart = start; + this.RecordDate = current; + this.SecondsAgo = ago; + this.SecondsLeft = 300 - ago; + } + /// + /// The attacker's IPv4 Address. + /// + public IPAddress Address { get; private set; } + /// + /// The exact date when the address was blocked. + /// + public DateTime AttackStart { get; private set; } + /// + /// The time when this record was created. + /// + public DateTime RecordDate { get; private set; } + /// + /// The time since the address was blocked. + /// + public uint SecondsAgo { get; private set; } + /// + /// The time left until the address is unblocked. + /// + public uint SecondsLeft { get; private set; } + } + + public delegate void DelBlockedFirst(IPAddress address); + + public event DelBlockedFirst OnBlockedOnce; + } +} diff --git a/ImpostorHQ.Command/Impostor.Commands.Core/DashBoard/WebAPIServer.cs b/ImpostorHQ.Command/Impostor.Commands.Core/DashBoard/WebAPIServer.cs index de2a1e6..d09b805 100644 --- a/ImpostorHQ.Command/Impostor.Commands.Core/DashBoard/WebAPIServer.cs +++ b/ImpostorHQ.Command/Impostor.Commands.Core/DashBoard/WebAPIServer.cs @@ -5,6 +5,7 @@ using System.Threading; using System.Threading.Tasks; using System.Collections.Generic; +using System.Runtime.CompilerServices; using Impostor.Api.Games.Managers; using Impostor.Commands.Core.SELF; using Microsoft.Extensions.Logging; @@ -38,6 +39,8 @@ public class WebApiServer public DateTime StartTime { get; private set; } public PerformanceMonitors Counters { get; private set; } private Class Master { get; set; } + private QuiteEffectiveDetector QEDector { get; set; } + private readonly List AttackerAddresses = new List(); /// /// This will host an API server, that can be accessed with the given API keys. /// @@ -45,7 +48,7 @@ public class WebApiServer /// The interface to bind the socket to. /// The accepted API keys. /// The global logger. - public WebApiServer(ushort port, string listenInterface,string[] keys,ILogger logger, IGameManager manager,Class masterClass) + public WebApiServer(ushort port, string listenInterface,string[] keys,ILogger logger, IGameManager manager,Class masterClass,QuiteEffectiveDetector detector) { this.StartTime = DateTime.UtcNow; this.Counters = new PerformanceMonitors(); @@ -57,6 +60,7 @@ public WebApiServer(ushort port, string listenInterface,string[] keys,ILogger(); @@ -109,6 +113,21 @@ public void Shutdown() /// The client to process. private void OnOpen(IWebSocketConnection conn) { + if (QEDector.IsAttacking(IPAddress.Parse((ReadOnlySpan) conn.ConnectionInfo.ClientIpAddress))) + { + lock (AttackerAddresses) + { + if (!AttackerAddresses.Contains(conn.ConnectionInfo.ClientIpAddress)) + { + Push( + $"Under denial of service attack from: {conn.ConnectionInfo.ClientIpAddress}! The address has been booted from accessing the Web API server, for 5 minutes. Please take action!", + "-HIGHEST PRIORITY/CRITICAL-", Structures.MessageFlag.ConsoleLogMessage); + AttackerAddresses.Add(conn.ConnectionInfo.ClientIpAddress); + } + } + + return; + } conn.OnMessage = message => { //we will handle AUTH and commands here. @@ -289,6 +308,13 @@ private async Task AsyncSend(IWebSocketConnection conn, string data) if (Clients.Contains(conn)) /*why shouldn't it...*/ Clients.Remove(conn); } } + catch (ObjectDisposedException) + { + lock (Clients) + { + if (Clients.Contains(conn)) /*why shouldn't it...*/ Clients.Remove(conn); + } + } catch (Exception ex) { Master.LogManager.LogError(ex.ToString(),Shared.ErrorLocation.AsyncSend); @@ -306,6 +332,7 @@ public static ulong GetTime() return (ulong)t.TotalMilliseconds; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void DoHeartbeat() { while (Running) diff --git a/ImpostorHQ.Command/Impostor.Commands.Core/GameChatCommandInterface.cs b/ImpostorHQ.Command/Impostor.Commands.Core/GameChatCommandInterface.cs index 56f4c3b..ab2c6f2 100644 --- a/ImpostorHQ.Command/Impostor.Commands.Core/GameChatCommandInterface.cs +++ b/ImpostorHQ.Command/Impostor.Commands.Core/GameChatCommandInterface.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Impostor.Api.Net.Messages; using System.Collections.Generic; +using System.Runtime.CompilerServices; using Impostor.Api.Events.Player; using Microsoft.Extensions.Logging; @@ -49,6 +50,7 @@ public void RegisterCommand(string command) } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public string GenerateDocs() { var str = "ImpostorHQ Commands: "; @@ -59,7 +61,7 @@ public string GenerateDocs() return str; } - #pragma warning disable +#pragma warning disable /// /// This function is used to broadcast a chat message to a specific lobby. /// @@ -67,6 +69,7 @@ public string GenerateDocs() /// The message to broadcast. /// The type of broadcast message. This affects the color of the in-game source. /// The source of the message, that will appear in-game. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public async void Broadcast(IGame game, string message, Structures.BroadcastType messageType, string src) { var colorName = string.Empty; @@ -113,7 +116,7 @@ public async void Broadcast(IGame game, string message, Structures.BroadcastType } } - #pragma warning disable CS1998 +#pragma warning disable CS1998 /// /// This will be used to send a message to a specific player. /// @@ -121,6 +124,7 @@ public async void Broadcast(IGame game, string message, Structures.BroadcastType /// The message to send. /// The source of the message. /// The type of the message. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public async void PrivateMessage(IClientPlayer player,string message,string source, Structures.BroadcastType type) { var coloredName = string.Empty; @@ -171,6 +175,7 @@ public async void PrivateMessage(IClientPlayer player,string message,string sour /// The message to broadcast. /// The type of broadcast. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public async Task SafeAsyncBroadcast(IGame game, string message, Structures.BroadcastType type) #pragma warning restore CS1998 { @@ -195,6 +200,7 @@ public async Task SafeAsyncBroadcast(IGame game, string message, Structures.Broa /// The type of message. /// The source player name (can be anything) of the message. /// (Only assign for directional messages) the recipient of the message. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SafeMultiMessage(IGame game, string message, Structures.BroadcastType broadcastType, string source = "(server)", IClientPlayer destination = null) { try @@ -216,12 +222,7 @@ public void SafeMultiMessage(IGame game, string message, Structures.BroadcastTyp } } - public void SendMessage() - { - - } - - #pragma warning restore +#pragma warning restore /// /// Use this on any chat event. If a command is registered, the OnCommandInvoked event will be fired. diff --git a/ImpostorHQ.Command/Impostor.Commands.Core/QuantumExtensionDirector/PluginFileSystem.cs b/ImpostorHQ.Command/Impostor.Commands.Core/QuantumExtensionDirector/PluginFileSystem.cs index 0096318..9883cb8 100644 --- a/ImpostorHQ.Command/Impostor.Commands.Core/QuantumExtensionDirector/PluginFileSystem.cs +++ b/ImpostorHQ.Command/Impostor.Commands.Core/QuantumExtensionDirector/PluginFileSystem.cs @@ -6,8 +6,10 @@ namespace Impostor.Commands.Core.QuantumExtensionDirector public class PluginFileSystem { public readonly string Store,ConfigPath; - public PluginFileSystem(string baseDirectory, string pluginName) + private readonly PluginLoader loader; + public PluginFileSystem(string baseDirectory, string pluginName,PluginLoader loader) { + this.loader = loader; Store = Path.Combine(baseDirectory, pluginName); ConfigPath = Path.Combine(Store, $"{pluginName}.cfg"); if (!Directory.Exists(Store)) Directory.CreateDirectory(Store); @@ -28,5 +30,14 @@ public void Save(T config) if(File.Exists(ConfigPath))File.Delete(ConfigPath); File.WriteAllText(ConfigPath,JsonSerializer.Serialize(config)); } + + /// + /// This is used to get all active plugin folders. + /// + /// + public string[] GetAllStores() + { + return loader.GetStores(); + } } } diff --git a/ImpostorHQ.Command/Impostor.Commands.Core/QuantumExtensionDirector/PluginLoader.cs b/ImpostorHQ.Command/Impostor.Commands.Core/QuantumExtensionDirector/PluginLoader.cs index f4ff3c9..0df8163 100644 --- a/ImpostorHQ.Command/Impostor.Commands.Core/QuantumExtensionDirector/PluginLoader.cs +++ b/ImpostorHQ.Command/Impostor.Commands.Core/QuantumExtensionDirector/PluginLoader.cs @@ -12,6 +12,7 @@ public class PluginLoader public Type TargetType { get; private set; } public uint ApiVersion { get; private set; } public string FolderPath { get; private set; } + private string[] Stores { get; set; } public readonly QuiteExtendableDirectInterface Master; public PluginLoader(string folderPath, QuiteExtendableDirectInterface master, uint version) { @@ -52,9 +53,13 @@ public void LoadPlugins() Master.UnsafeDirectReference.ConsolePluginStatus($"Loaded \"{x.Name}\" by \"{x.Author}\""); } } + Stores = new string[Plugins.Count]; + int index = 0; foreach (var plugin in Plugins) { - plugin.Load(Master, new PluginFileSystem(Path.Combine("hqplugins", "data"), plugin.Name)); + var pfs = new PluginFileSystem(Path.Combine("hqplugins", "data"),plugin.Name, this); + plugin.Load(Master, pfs); + Stores[index] = pfs.Store; } Master.UnsafeDirectReference.ConsolePluginStatus($"Loaded {Plugins.Count} plugins."); } @@ -66,5 +71,11 @@ public void Shutdown() plugin.Destroy(); } } + + /// + /// Gets all active stores. It is equivalent to the function found in the PluginFileSystem class. + /// + /// + public string[] GetStores() => Stores; } } diff --git a/ImpostorHQ.Command/Impostor.Commands.Core/QuantumExtensionDirector/QuiteExtendableDirectInterface.cs b/ImpostorHQ.Command/Impostor.Commands.Core/QuantumExtensionDirector/QuiteExtendableDirectInterface.cs index b2e9290..72da45b 100644 --- a/ImpostorHQ.Command/Impostor.Commands.Core/QuantumExtensionDirector/QuiteExtendableDirectInterface.cs +++ b/ImpostorHQ.Command/Impostor.Commands.Core/QuantumExtensionDirector/QuiteExtendableDirectInterface.cs @@ -1,4 +1,6 @@ -using System.Threading; +using System; +using System.Threading; +using Fleck; using Impostor.Api.Net.Manager; using Impostor.Api.Net.Messages; using Impostor.Api.Games.Managers; @@ -10,6 +12,11 @@ namespace Impostor.Commands.Core.QuantumExtensionDirector { public class QuiteExtendableDirectInterface { + public QuiteExtendableDirectInterface(Class source) + { + source.OnExternalCommandInvoked += (cmd, data, single, connection) => + OnDashboardCommandReceived?.Invoke(cmd, data, single, connection); + } /// /// A logger, to directly output to the console. /// @@ -79,9 +86,16 @@ public class QuiteExtendableDirectInterface /// public PluginFileSystem StorageProvider { get; set; } /// + /// Use this for any server services that you have. + /// + public QuiteEffectiveDetector QEDetector { get; set; } + /// + /// This is triggered when the main command handler processed an unknown command. It means that the command was hooked externally, from a plugin. + /// + public event Class.DelDashboardCommandInvoked OnDashboardCommandReceived; + /// /// This is a direct reference to the plugin main. Please use this if the referenced object do not meet your requirements. Warning: this offers total access over ImpostorHQ. Messing with the wrong values or function could lead to unwanted errors. Please refer to the source code before accessing this. /// public Class UnsafeDirectReference { get; set; } - } } diff --git a/ImpostorHQ.Command/Impostor.Commands.Core/Structures.cs b/ImpostorHQ.Command/Impostor.Commands.Core/Structures.cs index 59a233f..a028a90 100644 --- a/ImpostorHQ.Command/Impostor.Commands.Core/Structures.cs +++ b/ImpostorHQ.Command/Impostor.Commands.Core/Structures.cs @@ -1,8 +1,10 @@ using System; using System.IO; using System.Text.Json; +using Impostor.Api.Innersloth; using Impostor.Api.Net.Messages; using System.Collections.Generic; +using Hazel; namespace Impostor.Commands.Core { @@ -384,6 +386,39 @@ public IMessageWriter WriteChat(int game, uint netId, string original, string[] return messageWriter; } + public IMessageWriter GenerateDataPacket(GameOptionsData data, int game, uint netId) + { + var writer = Provider.Get(MessageType.Reliable); + writer.StartMessage(Api.Net.Messages.MessageFlags.GameData); + writer.Write(game); + writer.StartMessage((byte)GameDataType.RpcFlag); + writer.WritePacked(netId); + writer.Write((byte)Structures.RpcCalls.SyncSettings); + using(var stream = new MemoryStream()) + using (BinaryWriter bWriter = new BinaryWriter(stream)) + { + data.Serialize(bWriter,4); + writer.WriteBytesAndSize(stream.ToArray()); + } + writer.EndMessage(); + writer.EndMessage(); + return writer; + } + + public IMessageWriter GenerateNameChangePacket(string name, uint netId, int gameCode) + { + var messageWriter = Provider.Get(MessageType.Reliable); + messageWriter.StartMessage(Api.Net.Messages.MessageFlags.GameData); + messageWriter.Write(gameCode); + messageWriter.StartMessage((byte)GameDataType.RpcFlag); + messageWriter.WritePacked(netId); + messageWriter.Write((byte)Structures.RpcCalls.SetName); + messageWriter.Write(name); + messageWriter.EndMessage(); + messageWriter.EndMessage(); + return messageWriter; + } + internal static class MessageFlags { public const byte HostGame = 0; @@ -403,6 +438,7 @@ internal static class MessageFlags public const byte GetGameList = 9; public const byte GetGameListV2 = 16; } + public enum GameDataType : byte { RpcFlag = 2, diff --git a/ImpostorHQ.Command/ImpostorHQ.Plugin.DDoSInfo/ImpostorHQ.Plugin.DDoSInfo.csproj b/ImpostorHQ.Command/ImpostorHQ.Plugin.DDoSInfo/ImpostorHQ.Plugin.DDoSInfo.csproj new file mode 100644 index 0000000..3a74d8c --- /dev/null +++ b/ImpostorHQ.Command/ImpostorHQ.Plugin.DDoSInfo/ImpostorHQ.Plugin.DDoSInfo.csproj @@ -0,0 +1,11 @@ + + + + netcoreapp3.1 + + + + + + + diff --git a/ImpostorHQ.Command/ImpostorHQ.Plugin.DDoSInfo/MainClass.cs b/ImpostorHQ.Command/ImpostorHQ.Plugin.DDoSInfo/MainClass.cs new file mode 100644 index 0000000..155b35c --- /dev/null +++ b/ImpostorHQ.Command/ImpostorHQ.Plugin.DDoSInfo/MainClass.cs @@ -0,0 +1,127 @@ +using System; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text.Json; +using Impostor.Commands.Core; +using Impostor.Commands.Core.QuantumExtensionDirector; + +namespace ImpostorHQ.Plugin.DDoSInfo +{ + public class MainClass : IPlugin + { + public string Name => "DDoS Info"; + + public string Author => "anti"; + + public uint HqVersion => 3; + + public QuiteExtendableDirectInterface PluginBase { get; private set; } + public PluginFileSystem FileSys { get; private set; } + public string HistoryFilePath { get; private set; } + public const string MainCommand = "/attackinfo"; + public void Destroy() + { + } + + public void Load(QuiteExtendableDirectInterface reference, PluginFileSystem system) + { + this.PluginBase = reference; + this.FileSys = system; + this.HistoryFilePath = Path.Combine(system.Store, "history.json"); + if(!File.Exists(HistoryFilePath)) File.Create(HistoryFilePath).Close(); + Main(); + } + + private void Main() + { + PluginBase.QEDetector.OnBlockedOnce += (address) => + { + File.AppendAllLines(HistoryFilePath,new string[] + { + new HistoricRecord(){Address = address.ToString(),AttackTime = DateTime.Now}.Serialize() + }); + }; + PluginBase.ApiServer.RegisterCommand(MainCommand," => will display information about the ongoing DoS/DDoS attack(if applicable) and all the past attacks, respectively."); + PluginBase.OnDashboardCommandReceived += (command, data, single, source) => + { + if (command.Equals(MainCommand)) + { + if (string.IsNullOrEmpty(data) || single) + { + PluginBase.ApiServer.PushTo("Invalid syntax. Please use /help first.","DDoSInfo",Structures.MessageFlag.ConsoleLogMessage,source); + } + else + { + if (data.Equals("now")) + { + PluginBase.ApiServer.PushTo(CompileBlocks(), "DDoSInfo", Structures.MessageFlag.ConsoleLogMessage, source); + } + else if (data.Equals("history")) + { + var lines = File.ReadAllLines(HistoryFilePath); + if (lines.Length == 0) + { + PluginBase.ApiServer.PushTo("No attack history.", "DDoSInfo", Structures.MessageFlag.ConsoleLogMessage, source); + return; + } + + string result = $"All recorded attacks [{lines.Length}]:\n"; + foreach (var line in lines) + { + var record = HistoricRecord.Deserialize(line); + result += + $" IPA: {record.Address}, on: {record.AttackTime.ToString("G")}."; + } + PluginBase.ApiServer.PushTo(result, "DDoSInfo", Structures.MessageFlag.ConsoleLogMessage, source); + } + else + { + PluginBase.ApiServer.PushTo("Invalid command. Available: now, history.", "DDoSInfo", Structures.MessageFlag.ConsoleLogMessage, source); + } + } + } + }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private string CompileBlocks() + { + var blocks = PluginBase.QEDetector.GetBlocked().ToArray(); + if (blocks.Length == 0) return "Attackers: none."; + + string result = $"Attackers [{blocks.Length}]:\n"; + foreach (var blockedAddressInfo in blocks) + { + result += + $" {blockedAddressInfo.Address}, blocked at {blockedAddressInfo.AttackStart.ToString("t")} ({blockedAddressInfo.SecondsAgo} rels ago)\n"; + } + + return result; + } + } + + [Serializable] + class HistoricRecord + { + /// + /// The attacker's IP address. + /// + public string Address { get; set; } + /// + /// The time of the block. + /// + public DateTime AttackTime { get; set; } + + public string Serialize() + { + return JsonSerializer.Serialize(this); + } + + public static HistoricRecord Deserialize(string str) + { + return JsonSerializer.Deserialize(str); + } + } +} diff --git a/ImpostorHQ.Command/ImpostorHQ.Plugin.DiskStatus/ImpostorHQ.Plugin.DiskStatus.csproj b/ImpostorHQ.Command/ImpostorHQ.Plugin.DiskStatus/ImpostorHQ.Plugin.DiskStatus.csproj new file mode 100644 index 0000000..b7c7134 --- /dev/null +++ b/ImpostorHQ.Command/ImpostorHQ.Plugin.DiskStatus/ImpostorHQ.Plugin.DiskStatus.csproj @@ -0,0 +1,11 @@ + + + + netcoreapp3.1 + + + + + + + diff --git a/ImpostorHQ.Command/ImpostorHQ.Plugin.DiskStatus/MainClass.cs b/ImpostorHQ.Command/ImpostorHQ.Plugin.DiskStatus/MainClass.cs new file mode 100644 index 0000000..77f1b33 --- /dev/null +++ b/ImpostorHQ.Command/ImpostorHQ.Plugin.DiskStatus/MainClass.cs @@ -0,0 +1,110 @@ +using System; +using System.IO; +using Impostor.Commands.Core; +using Impostor.Commands.Core.QuantumExtensionDirector; + +namespace ImpostorHQ.Plugin.DiskStatus +{ + public class MainClass : IPlugin + { + public string Name => "Disk status"; + + public string Author => "anti"; + + public uint HqVersion => 3; + + public QuiteExtendableDirectInterface PluginBase { get; private set; } + public PluginFileSystem FileSys { get; private set; } + public const string MainCommand = "/diskstatus"; + public void Destroy() + { + } + + public void Load(QuiteExtendableDirectInterface reference, PluginFileSystem system) + { + this.PluginBase = reference; + this.FileSys = system; + Main(); + } + + public void Main() + { + PluginBase.ApiServer.RegisterCommand(MainCommand,"=> displays storage information."); + PluginBase.OnDashboardCommandReceived += (command, data, single,source) => + { + if (command.Equals(MainCommand)) + { + long size = 0; + long current = 0; + string result = "Sizes:\n"; + + current = (long)SizeOf("hqplugins"); + size += current; + result += $" Plugins total: {Suffix.Convert(current,2)}\n"; + + current = (long)SizeOf("hqlogs"); + size += current; + result += $" Logs total: {Suffix.Convert(current, 2)}\n"; + + current = (long)SizeOf("configs"); + size += current; + result += $" Configs total: {Suffix.Convert(current, 2)}\n"; + + current = (long)SizeOf("dashboard"); + size += current; + result += $" Website total: {Suffix.Convert(current, 2)}\n"; + + current = (long)SizeOf("libraries"); + size += current; + result += $" Libs total: {Suffix.Convert(current, 2)}\n"; + + result += $" Listed total: {Suffix.Convert(size, 2)}"; + PluginBase.ApiServer.PushTo(result,"(diskinfo)",Structures.MessageFlag.ConsoleLogMessage,source); + } + }; + } + + private ulong SizeOf(string directory) + { + var dirInfo = new DirectoryInfo(directory); + ulong size = 0; + foreach(var file in dirInfo.EnumerateFiles("*", SearchOption.AllDirectories)) + { + if (file.Length <= 0) continue; + size += (ulong)file.Length; + } + + return size; + } + } + class Suffix + { + private static readonly string[] SizeSuffixes = + { "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" }; + public static string Convert(Int64 value, int decimalPlaces = 1) + { + if (decimalPlaces < 0) { throw new ArgumentOutOfRangeException("decimalPlaces"); } + if (value < 0) { return "-" + Convert(-value); } + if (value == 0) { return string.Format("{0:n" + decimalPlaces + "} bytes", 0); } + + // mag is 0 for bytes, 1 for KB, 2, for MB, etc. + int mag = (int)Math.Log(value, 1024); + + // 1L << (mag * 10) == 2 ^ (10 * mag) + // [i.e. the number of bytes in the unit corresponding to mag] + decimal adjustedSize = (decimal)value / (1L << (mag * 10)); + + // make adjustment when the value is large enough that + // it would round up to 1000 or more + if (Math.Round(adjustedSize, decimalPlaces) >= 1000) + { + mag += 1; + adjustedSize /= 1024; + } + + return string.Format("{0:n" + decimalPlaces + "} {1}", + adjustedSize, + SizeSuffixes[mag]); + } + } +} diff --git a/ImpostorHQ.Command/ImpostorHQ.Plugin.Fashionable/MainClass.cs b/ImpostorHQ.Command/ImpostorHQ.Plugin.Fashionable/MainClass.cs index 33d5384..94ef267 100644 --- a/ImpostorHQ.Command/ImpostorHQ.Plugin.Fashionable/MainClass.cs +++ b/ImpostorHQ.Command/ImpostorHQ.Plugin.Fashionable/MainClass.cs @@ -15,7 +15,7 @@ public class MainClass : IPlugin public string Author => "anti"; - public uint HqVersion => 1; + public uint HqVersion => 3; public string SkinDir { get; private set; } diff --git a/ImpostorHQ.Command/ImpostorHQ.Plugin.HallOfShame/MainClass.cs b/ImpostorHQ.Command/ImpostorHQ.Plugin.HallOfShame/MainClass.cs index 7c1df5d..5d16f2d 100644 --- a/ImpostorHQ.Command/ImpostorHQ.Plugin.HallOfShame/MainClass.cs +++ b/ImpostorHQ.Command/ImpostorHQ.Plugin.HallOfShame/MainClass.cs @@ -14,7 +14,7 @@ public class MainClass : IPlugin public string Author => "anti"; - public uint HqVersion => 1; + public uint HqVersion => 3; public QuiteExtendableDirectInterface Interface { get; private set; } public string WebpageDir { get; private set; } diff --git a/ImpostorHQ.Command/Plugin.Test/MainClass.cs b/ImpostorHQ.Command/Plugin.Test/MainClass.cs index 0421a2d..291c11b 100644 --- a/ImpostorHQ.Command/Plugin.Test/MainClass.cs +++ b/ImpostorHQ.Command/Plugin.Test/MainClass.cs @@ -1,13 +1,18 @@ -using Impostor.Api.Net; +using System; +using Impostor.Api.Net; using Impostor.Commands.Core; using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reflection.Metadata.Ecma335; using Impostor.Commands.Core.QuantumExtensionDirector; +using Microsoft.Extensions.Logging; namespace Plugin.Test { public class MainClass : IPlugin { - public uint HqVersion => 1; + public uint HqVersion => 3; public string Name => "Test / Example plugin."; public string Author => "anti"; public QuiteExtendableDirectInterface PluginBase { get; private set; } @@ -21,18 +26,21 @@ private void MyFunction() { PluginBase.UnsafeDirectReference.ConsolePluginStatus("Greetings from the test plugin!"); PluginBase.ApiServer.RegisterCommand("/greet", "=> A test command, added by the test plugin."); + //this is a 'low level' hook, showing how to make your own handler. + //Please use the default OnDashboardCommandReceived event if your structure corresponds to the default one. PluginBase.ApiServer.OnMessageReceived += (message, source) => { if (message.Text.Equals("/greet")) { - PluginBase.ApiServer.PushTo("Hi there! This is the test plugin.", "TestPlugin", Structures.MessageFlag.ConsoleLogMessage, source); + PluginBase.ApiServer.PushTo("Hi there! This is the test plugin.", + "TestPlugin", Structures.MessageFlag.ConsoleLogMessage, source); } }; - PluginBase.ChatInterface.RegisterCommand("/test"); PluginBase.ChatInterface.RegisterCommand("/slap"); PluginBase.ChatInterface.RegisterCommand("/kill"); - + PluginBase.ChatInterface.RegisterCommand("/speed"); + PluginBase.ChatInterface.RegisterCommand("/freeze"); PluginBase.ChatInterface.OnCommandInvoked += async (command, data, player) => { if (command.Equals("/test")) @@ -69,6 +77,29 @@ private void MyFunction() PluginBase.ChatInterface.SafeMultiMessage(player.Game, "Player not found.", Structures.BroadcastType.Information, destination: player.ClientPlayer); } + else if (command.Equals("/speed")) + { + if (player == null || player.ClientPlayer.Character == null|| player.ClientPlayer.Client.Connection==null) return; + var options = player.Game.Options; + var before = player.Game.Options.PlayerSpeedMod; + options.PlayerSpeedMod = 0.000001f; + var packet = PluginBase.ChatInterface.Generator.GenerateDataPacket(options, player.Game.Code.Value, + player.ClientPlayer.Character.NetId); + options.PlayerSpeedMod = before; + await player.ClientPlayer.Client.Connection.SendAsync(packet).ConfigureAwait(false); + + } + else if (command.Equals("/freeze")) + { + if (player == null || player.ClientPlayer.Character == null || player.ClientPlayer.Client.Connection == null) return; + var options = player.Game.Options; + var before = player.Game.Options.PlayerSpeedMod; + options.PlayerSpeedMod = 0f; + var packet = PluginBase.ChatInterface.Generator.GenerateDataPacket(options, player.Game.Code.Value, + player.ClientPlayer.Character.NetId); + options.PlayerSpeedMod = before; + await player.ClientPlayer.Client.Connection.SendAsync(packet).ConfigureAwait(false); + } }; } public void Destroy()