using System; using System.Collections.Generic; using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; using Robust.Shared.Configuration; using Robust.Shared.Console; using Robust.Shared.Interfaces.Configuration; using Robust.Shared.Interfaces.Log; using Robust.Shared.Interfaces.Network; using Robust.Shared.Interfaces.Reflection; using Robust.Shared.IoC; using Robust.Shared.IoC.Exceptions; using Robust.Shared.Log; using Robust.Shared.Network.Messages; using Robust.Shared.Utility; namespace Robust.Server.Console { /// internal class ConsoleShell : IConsoleShell { private const string SawmillName = "con"; #pragma warning disable 649 [Dependency] private readonly IReflectionManager _reflectionManager; [Dependency] private readonly IPlayerManager _players; [Dependency] private readonly IServerNetManager _net; [Dependency] private readonly ISystemConsoleManager _systemConsole; [Dependency] private readonly ILogManager _logMan; [Dependency] private readonly IConfigurationManager _configMan; [Dependency] private readonly IConGroupController _groupController; #pragma warning restore 649 private readonly Dictionary _availableCommands = new Dictionary(); /// public IReadOnlyDictionary AvailableCommands => _availableCommands; private void HandleRegistrationRequest(INetChannel senderConnection) { var netMgr = IoCManager.Resolve(); var message = netMgr.CreateNetMessage(); var counter = 0; message.Commands = new MsgConCmdReg.Command[AvailableCommands.Count]; foreach (var command in AvailableCommands.Values) { message.Commands[counter++] = new MsgConCmdReg.Command { Name = command.Command, Description = command.Description, Help = command.Help }; } netMgr.ServerSendMessage(message, senderConnection); } /// public void Initialize() { // register console admin global password. DO NOT ADD THE REPLICATED FLAG if (!_configMan.IsCVarRegistered("console.password")) _configMan.RegisterCVar("console.password", string.Empty, CVar.ARCHIVE | CVar.SERVER | CVar.NOT_CONNECTED); if (!_configMan.IsCVarRegistered("console.adminGroup")) _configMan.RegisterCVar("console.adminGroup", 100, CVar.ARCHIVE | CVar.SERVER); ReloadCommands(); // setup networking with clients _net.RegisterNetMessage(MsgConCmd.NAME, ProcessCommand); _net.RegisterNetMessage(MsgConCmdAck.NAME); _net.RegisterNetMessage(MsgConCmdReg.NAME, message => HandleRegistrationRequest(message.MsgChannel)); } /// public void ReloadCommands() { // search for all client commands in all assemblies, and register them _availableCommands.Clear(); foreach (var type in _reflectionManager.GetAllChildren()) { var instance = (IClientCommand) Activator.CreateInstance(type, null); if (AvailableCommands.TryGetValue(instance.Command, out var duplicate)) throw new InvalidImplementationException(instance.GetType(), typeof(IClientCommand), $"Command name already registered: {instance.Command}, previous: {duplicate.GetType()}"); _availableCommands[instance.Command] = instance; } } private void ProcessCommand(MsgConCmd message) { var text = message.Text; var sender = message.MsgChannel; var session = _players.GetSessionByChannel(sender); _logMan.GetSawmill(SawmillName).Info($"{FormatPlayerString(session)}:{text}"); ExecuteCommand(session, text); } /// public void ExecuteCommand(string command) { ExecuteCommand(null, command); } /// public void ExecuteCommand(IPlayerSession session, string command) { try { var args = new List(); CommandParsing.ParseArguments(command, args); // missing cmdName if (args.Count == 0) return; var cmdName = args[0]; if (_availableCommands.TryGetValue(cmdName, out var conCmd)) // command registered { if (session != null) // remote client { if (_groupController.CanCommand(session, cmdName)) // client has permission { args.RemoveAt(0); conCmd.Execute(this, session, args.ToArray()); } else SendText(session, $"Unknown command: '{cmdName}'"); } else // system console { args.RemoveAt(0); conCmd.Execute(this, null, args.ToArray()); } } else SendText(session, $"Unknown command: '{cmdName}'"); } catch (Exception e) { _logMan.GetSawmill(SawmillName).Warning($"{FormatPlayerString(session)}: ExecuteError - {command}"); SendText(session, $"There was an error while executing the command: {e}"); } } /// public void SendText(IPlayerSession session, string text) { if (session != null) SendText(session.ConnectedClient, text); else _systemConsole.Print(text + "\n"); } /// public void SendText(INetChannel target, string text) { var replyMsg = _net.CreateNetMessage(); replyMsg.Text = text; _net.ServerSendMessage(replyMsg, target); } private static string FormatPlayerString(IPlayerSession session) { return session != null ? $"{session.Name}" : "[HOST]"; } public bool ElevateShell(IPlayerSession session, string password) { if (session == null) throw new ArgumentNullException(nameof(session)); var realPass = _configMan.GetCVar("console.password"); // password disabled if (string.IsNullOrWhiteSpace(realPass)) return false; // wrong password if (password != realPass) return false; // success! _groupController.SetGroup(session, new ConGroupIndex(_configMan.GetCVar("console.adminGroup"))); return true; } private class LoginCommand : IClientCommand { public string Command => "login"; public string Description => "Elevates client to admin permission group."; public string Help => "login"; public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) { // system console can't log in to itself, and is pointless anyways if (player == null) return; // If the password is null/empty/whitespace in the config, this effectively disables the command if (args.Length < 1 || string.IsNullOrWhiteSpace(args[0])) return; // WE ARE AT THE BRIDGE OF DEATH if (shell.ElevateShell(player, args[0])) return; // CAST INTO THE GORGE OF ETERNAL PERIL Logger.WarningS( "con.auth", $"Failed console login authentication.\n NAME:{player}\n IP: {player.ConnectedClient.RemoteEndPoint}"); var net = IoCManager.Resolve(); net.DisconnectChannel(player.ConnectedClient, "Failed login authentication."); } } private class GroupCommand : IClientCommand { public string Command => "group"; public string Description => "Prints your current permission group."; public string Help => "group"; public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) { // only the local server console bypasses permissions if (player == null) shell.SendText(player, "LOCAL_CONSOLE"); //TODO: Turn console commands into delegates so that this can actually work. } } } }