diff --git a/TShockAPI/ConfigFile.cs b/TShockAPI/ConfigFile.cs index acf5a1a2..83de138f 100644 --- a/TShockAPI/ConfigFile.cs +++ b/TShockAPI/ConfigFile.cs @@ -67,6 +67,9 @@ namespace TShockAPI public int MaximumLoginAttempts = 3; + public string RconPassword = ""; + public int RconPort = 7778; + public static ConfigFile Read(string path) { if (!File.Exists(path)) diff --git a/TShockAPI/RconHandler.cs b/TShockAPI/RconHandler.cs new file mode 100644 index 00000000..037a54a2 --- /dev/null +++ b/TShockAPI/RconHandler.cs @@ -0,0 +1,253 @@ +/* +TShock, a server mod for Terraria +Copyright (C) 2011 The TShock Team + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.IO; +using System.Net; +using System.Net.Sockets; +using Terraria; + +namespace TShockAPI +{ + class RconHandler + { + public static string Password = ""; + private static DateTime lastRequest; + public static int ListenPort; + public static bool ContinueServer = true; + public static string Response = ""; + + public static void StartThread() + { + (new Thread(Start)).Start(); + } + + public static void Start() + { + Log.Info("Starting RconHandler."); + try + { + Console.WriteLine(string.Format("RconHandler is running at UDP port {0} and password is {1}", + ListenPort.ToString(), + Password)); + Thread listen = new Thread(new ThreadStart(Listener)); + listen.Start(); + while (true) + { + if (listen.ThreadState != ThreadState.Running) + { + listen.Abort(); + while (listen.ThreadState != ThreadState.Stopped) + continue; + listen.Start(); + } + Thread.Sleep(3000); + } + } + catch (Exception e) + { + Log.Error(e.ToString()); + } + } + + private static void Listener() + { + while (ContinueServer) + { + UdpClient listener = new UdpClient(ListenPort); + try + { + var listenEP = new IPEndPoint(IPAddress.Any, ListenPort); + lastRequest = DateTime.Now; + byte[] bytes = listener.Receive(ref listenEP); + Log.Info(string.Format("Recieved packet from {0}:{1}", listenEP.Address.ToString(), listenEP.Port.ToString())); + var packet = parsePacket(bytes, listenEP); + listener.Send(packet, packet.Length, listenEP); + listener.Close(); + } + catch (Exception e) + { + Log.Error(e.ToString()); + listener.Close(); + } + } + } + + private static string sendPacket(byte[] bytes, string hostname, int port) + { + var response = Encoding.UTF8.GetString(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF }) + "disconnect"; + try + { + var EP = new IPEndPoint(IPAddress.Any, port); + UdpClient client = new UdpClient(); + client.Connect(hostname, port); + client.Client.ReceiveTimeout = 500; + client.Send(bytes, bytes.Length); + response = Encoding.UTF8.GetString(client.Receive(ref EP)); + } + catch (Exception e) + { + Log.Error(e.ToString()); + } + return response; + } + + private static byte[] parsePacket(byte[] bytes, IPEndPoint EP) + { + string response = ""; + var packetstring = Encoding.UTF8.GetString(padPacket(bytes)); + var redirect = false; + var print = true; + if ((DateTime.Now - lastRequest).Milliseconds >= 100) + { + if (packetstring.StartsWith("rcon") || packetstring.Substring(4).StartsWith("rcon") || packetstring.Substring(5).StartsWith("rcon")) + { + if (!string.IsNullOrEmpty(Password)) + { + var args = packetstring.Split(' '); + if (args.Length >= 3) + { + if (args[1] == Password) + { + string command = ""; + for (int i = 2; i < args.Length; i++) + command += args[i] + " "; + command = command.TrimEnd(' ').TrimEnd('\0'); + response = executeCommand(command); + if (response == "" && Response != "") + { + response = Response; + Response = ""; + } + } + else + { + response = "Invalid password."; + Log.ConsoleInfo("Bad rcon password from " + EP.ToString()); + } + } + else + response = ""; + } + else + { + response = "The server must set a password for clients to use rcon."; + Log.Info("No password for rcon set"); + } + } + else + redirect = true; + } + if (!redirect) + return (constructPacket(response, print)); + else + return (constructPacket("", false)); + } + + private static string executeCommand(string text) + { + if (Main.rand == null) + Main.rand = new Random(); + if (WorldGen.genRand == null) + WorldGen.genRand = new Random(); + if (text.StartsWith("exit")) + { + Tools.ForceKickAll("Server shutting down!"); + return "Server shutting down."; + } + else if (text.StartsWith("playing") || text.StartsWith("/playing")) + { + int count = 0; + foreach (TSPlayer player in TShock.Players) + { + if (player != null && player.Active) + { + count++; + TSPlayer.Server.SendMessage(string.Format("{0} ({1}) [{2}]", player.Name, player.IP, player.Group.Name)); + } + } + TSPlayer.Server.SendMessage(string.Format("{0} players connected.", count)); + } + else if (text.StartsWith("status")) + { + Response += "map: " + Main.worldName + "\n"; + Response += "num score ping name\tlastmsg address\tqport rate\n"; + int count = 0; + foreach (TSPlayer player in TShock.Players) + { + if (player != null && player.Active) + { + count++; + Response += (string.Format("{0} 0 0 {1}({2})\t{3} {4}\t0 0", count, player.Name, player.Group.Name, Netplay.serverSock[player.Index].tcpClient.Client.RemoteEndPoint.ToString())) + "\n"; + } + } + } + else if (text.StartsWith("say ")) + { + Log.Info(string.Format("Server said: {0}", text.Remove(0, 4))); + return string.Format("Server said: {0}", text.Remove(0, 4)); + } + else if (text == "autosave") + { + Main.autoSave = TShock.Config.AutoSave = !TShock.Config.AutoSave; + Log.ConsoleInfo("AutoSave " + (TShock.Config.AutoSave ? "Enabled" : "Disabled")); + return "AutoSave " + (TShock.Config.AutoSave ? "Enabled" : "Disabled"); + } + else + if (!Commands.HandleCommand(TSPlayer.Server, text)) + return "Invalid command."; + return ""; + } + + private static byte[] constructPacket(string response, bool print) + { + var oob = new byte[] { 0xFF, 0xFF, 0xFF, 0xFF }; + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + writer.Write(oob); + if (print) + writer.Write(Encoding.UTF8.GetBytes(string.Format("print\n{0}", response))); + else + writer.Write(Encoding.UTF8.GetBytes("disconnect\n")); + var packet = Encoding.UTF8.GetBytes( + (Encoding.UTF8.GetString(stream.GetBuffer()) + .Substring(0, (int)stream.Length))); + return packet; + } + + private static byte[] padPacket(byte[] packet) + { + var returnpacket = new byte[(4 + packet.Length)]; + int h = 0; + if (packet[0] != 0xFF) + { + for (int i = 0; i < 4; i++) + returnpacket[i] = 0xFF; + for (int i = 4; i < returnpacket.Length; i++) + returnpacket[i] = packet[h++]; + } + else + returnpacket = packet; + return returnpacket; + } + } +} diff --git a/TShockAPI/TSPlayer.cs b/TShockAPI/TSPlayer.cs index 4d6503a9..9737ac03 100644 --- a/TShockAPI/TSPlayer.cs +++ b/TShockAPI/TSPlayer.cs @@ -370,6 +370,7 @@ namespace TShockAPI public override void SendMessage(string msg, byte red, byte green, byte blue) { Console.WriteLine(msg); + RconHandler.Response += msg + "\n"; } public void SetBloodMoon(bool bloodMoon) diff --git a/TShockAPI/TShock.cs b/TShockAPI/TShock.cs index ba7d5e91..e267e469 100755 --- a/TShockAPI/TShock.cs +++ b/TShockAPI/TShock.cs @@ -113,6 +113,7 @@ namespace TShockAPI RegionManager.ReadAllSettings(); WarpsManager.ReadAllSettings(); ItemManager.LoadBans(); + RconHandler.StartThread(); Log.ConsoleInfo("AutoSave " + (TShock.Config.AutoSave ? "Enabled" : "Disabled")); Log.ConsoleInfo("Backups " + (Backups.Interval > 0 ? "Enabled" : "Disabled")); @@ -373,6 +374,8 @@ namespace TShockAPI if (text.StartsWith("exit")) { Tools.ForceKickAll("Server shutting down!"); + WorldGen.saveWorld(false); + Netplay.disconnect = true; } else if (text.StartsWith("playing") || text.StartsWith("/playing")) { @@ -595,6 +598,9 @@ namespace TShockAPI Netplay.serverPort = file.ServerPort; Netplay.spamCheck = file.SpamChecks; + + RconHandler.Password = file.RconPassword; + RconHandler.ListenPort = file.RconPort; } diff --git a/TShockAPI/TShockAPI.csproj b/TShockAPI/TShockAPI.csproj index b16f3565..5ecf8d47 100644 --- a/TShockAPI/TShockAPI.csproj +++ b/TShockAPI/TShockAPI.csproj @@ -84,6 +84,7 @@ + @@ -149,11 +150,11 @@ - \ No newline at end of file