mirror of
https://github.com/sbrl/Nibriboard.git
synced 2018-01-10 21:33:49 +00:00
374 lines
13 KiB
C#
374 lines
13 KiB
C#
using System;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Net.Sockets;
|
|
using System.Threading.Tasks;
|
|
using Nibriboard.Client;
|
|
using Nibriboard.RippleSpace;
|
|
using Nibriboard.Userspace;
|
|
using Nibriboard.Utilities;
|
|
|
|
namespace Nibriboard
|
|
{
|
|
public class CommandConsole
|
|
{
|
|
private NibriboardServer server;
|
|
private TcpListener commandServer;
|
|
|
|
private int commandPort;
|
|
|
|
public CommandConsole(NibriboardServer inServer, int inCommandPort)
|
|
{
|
|
server = inServer;
|
|
commandPort = inCommandPort;
|
|
}
|
|
|
|
public async Task Start()
|
|
{
|
|
commandServer = new TcpListener(IPAddress.IPv6Loopback, server.CommandPort);
|
|
commandServer.Start();
|
|
Log.WriteLine("[CommandConsole] Listening on {0}.", new IPEndPoint(IPAddress.IPv6Loopback, server.CommandPort));
|
|
while(true)
|
|
{
|
|
TcpClient nextClient = await commandServer.AcceptTcpClientAsync();
|
|
|
|
StreamReader source = new StreamReader(nextClient.GetStream());
|
|
StreamWriter destination = new StreamWriter(nextClient.GetStream()) { AutoFlush = true };
|
|
|
|
string rawCommand = await source.ReadLineAsync();
|
|
string[] commandParts = rawCommand.Split(" \t".ToCharArray());
|
|
Log.WriteLine("[CommandConsole] Client executing {0}", rawCommand);
|
|
|
|
try
|
|
{
|
|
await executeCommand(destination, commandParts);
|
|
}
|
|
catch(Exception error)
|
|
{
|
|
try
|
|
{
|
|
await destination.WriteLineAsync(error.ToString());
|
|
}
|
|
catch { nextClient.Close(); } // Make absolutely sure that the command server won't die
|
|
}
|
|
nextClient.Close();
|
|
}
|
|
}
|
|
|
|
private async Task executeCommand(StreamWriter dest, string[] commandParts)
|
|
{
|
|
string commandName = commandParts[0].Trim();
|
|
switch(commandName)
|
|
{
|
|
case "help":
|
|
await dest.WriteLineAsync("Nibriboard Server Command Console");
|
|
await dest.WriteLineAsync("=================================");
|
|
await dest.WriteLineAsync("Available commands:");
|
|
await dest.WriteLineAsync(" help Show this message");
|
|
await dest.WriteLineAsync(" version Show the version of nibriboard that is currently running");
|
|
await dest.WriteLineAsync(" save Save the ripplespace to disk");
|
|
await dest.WriteLineAsync(" plane {subcommand} Interact with planes");
|
|
await dest.WriteLineAsync(" users Interact with user accounts");
|
|
await dest.WriteLineAsync(" clients List the currently connected clients");
|
|
break;
|
|
case "version":
|
|
await dest.WriteLineAsync($"Nibriboard Server {NibriboardServer.Version}, built on {NibriboardServer.BuildDate.ToString("R")}");
|
|
await dest.WriteLineAsync("By Starbeamrainbowlabs, licensed under MPL-2.0");
|
|
break;
|
|
case "save":
|
|
await dest.WriteAsync("Saving ripple space - ");
|
|
Stopwatch timer = Stopwatch.StartNew();
|
|
long bytesWritten = await server.PlaneManager.Save();
|
|
long msTaken = timer.ElapsedMilliseconds;
|
|
await dest.WriteLineAsync("done.");
|
|
await dest.WriteLineAsync($"{Formatters.HumanSize(bytesWritten)} written in {msTaken}ms.");
|
|
await dest.WriteLineAsync($"Save is now {Formatters.HumanSize(server.PlaneManager.LastSaveSize)} in size.");
|
|
break;
|
|
case "plane":
|
|
await handlePlaneCommand(commandParts, dest);
|
|
break;
|
|
|
|
case "clients":
|
|
|
|
foreach(NibriClient client in server.AppServer.NibriClients) {
|
|
await dest.WriteLineAsync($"{client.Id}: {client.Name} from {client.RemoteEndpoint}, on {client.CurrentPlane.Name} looking at {client.CurrentViewPort}");
|
|
}
|
|
await dest.WriteLineAsync();
|
|
await dest.WriteLineAsync($"Total {server.AppServer.ClientCount} clients");
|
|
break;
|
|
|
|
case "users":
|
|
await handleUsersCommand(commandParts, dest);
|
|
break;
|
|
|
|
/*case "chunk":
|
|
if(commandParts.Length < 2) {
|
|
await destination.WriteLineAsync("Error: No sub-action specified.");
|
|
break;
|
|
}
|
|
|
|
string chunkSubAction = commandParts[1].Trim();
|
|
switch(chunkSubAction)
|
|
{
|
|
case "list":
|
|
if(commandParts.Length < 3) {
|
|
await destination.WriteLineAsync("Error: No plane specified to list the chunks of!");
|
|
return;
|
|
}
|
|
|
|
Plane plane = server.PlaneManager.GetByName(commandParts[2].Trim());
|
|
|
|
foreach(Chunk chunk in plane.
|
|
break;
|
|
|
|
default:
|
|
await destination.WriteLineAsync($"Error: Unknown sub-action {chunkSubAction}.");
|
|
break;
|
|
}
|
|
|
|
break;*/
|
|
|
|
default:
|
|
await dest.WriteLineAsync($"Error: Unrecognised command {commandName}");
|
|
break;
|
|
}
|
|
}
|
|
|
|
private async Task handlePlaneCommand(string[] commandParts, StreamWriter dest)
|
|
{
|
|
if (commandParts.Length < 2)
|
|
{
|
|
await dest.WriteLineAsync("Nibriboard Server Command Console: plane");
|
|
await dest.WriteLineAsync("----------------------------------------");
|
|
await dest.WriteLineAsync("Interact with planes.");
|
|
await dest.WriteLineAsync("Usage:");
|
|
await dest.WriteLineAsync(" plane {subcommand}");
|
|
await dest.WriteLineAsync();
|
|
await dest.WriteLineAsync("Subcommands:");
|
|
await dest.WriteLineAsync(" list");
|
|
await dest.WriteLineAsync(" List all the currently loaded planes");
|
|
await dest.WriteLineAsync(" create {new-plane-name} [{chunkSize}]");
|
|
await dest.WriteLineAsync(" Create a new named plane, optionally with the specified chunk size");
|
|
await dest.WriteLineAsync(" status {plane-name}");
|
|
await dest.WriteLineAsync(" Show the statistics of the specified plane");
|
|
return;
|
|
}
|
|
string subAction = commandParts[1].Trim();
|
|
switch (subAction)
|
|
{
|
|
case "list":
|
|
await dest.WriteLineAsync("Planes:");
|
|
foreach (Plane plane in server.PlaneManager.Planes)
|
|
await dest.WriteLineAsync($" {plane.Name} @ {plane.ChunkSize} ({plane.LoadedChunks} / ~{plane.SoftLoadedChunkLimit} chunks loaded, {plane.UnloadableChunks} inactive, {plane.TotalChunks} total at last save)");
|
|
await dest.WriteLineAsync();
|
|
await dest.WriteLineAsync($"Total {server.PlaneManager.Planes.Count}");
|
|
break;
|
|
case "create":
|
|
if (commandParts.Length < 3)
|
|
{
|
|
await dest.WriteLineAsync("Error: No name specified for the new plane!");
|
|
return;
|
|
}
|
|
string newPlaneName = commandParts[2].Trim();
|
|
int chunkSize = server.PlaneManager.DefaultChunkSize;
|
|
if (commandParts.Length >= 4)
|
|
chunkSize = int.Parse(commandParts[3]);
|
|
|
|
server.PlaneManager.CreatePlane(new PlaneInfo(
|
|
newPlaneName,
|
|
chunkSize
|
|
));
|
|
|
|
await dest.WriteLineAsync($"Created plane with name {newPlaneName} and chunk size {chunkSize}.");
|
|
|
|
break;
|
|
case "status":
|
|
if (commandParts.Length < 3)
|
|
{
|
|
await dest.WriteLineAsync("Error: No plane name specified!");
|
|
return;
|
|
}
|
|
|
|
string targetPlaneName = commandParts[2].Trim();
|
|
Plane targetPlane = server.PlaneManager.GetByName(targetPlaneName);
|
|
if (targetPlane == null)
|
|
{
|
|
await dest.WriteLineAsync($"Error: A plane with the name {targetPlaneName} doesn't exist.");
|
|
return;
|
|
}
|
|
|
|
await dest.WriteLineAsync($"Name: {targetPlane.Name}");
|
|
await dest.WriteLineAsync($"Chunk size: {targetPlane.ChunkSize}");
|
|
await dest.WriteLineAsync($"Loaded chunks: {targetPlane.LoadedChunks}");
|
|
await dest.WriteLineAsync($"Unloaded chunks: {targetPlane.TotalChunks - targetPlane.LoadedChunks}");
|
|
await dest.WriteLineAsync($"Total chunks: {targetPlane.TotalChunks}");
|
|
await dest.WriteLineAsync($"Primary chunk area size: {targetPlane.PrimaryChunkAreaSize}");
|
|
await dest.WriteLineAsync($"Min unloadeable chunks: {targetPlane.MinUnloadeableChunks}");
|
|
await dest.WriteLineAsync($"Soft loaded chunk limit: {targetPlane.SoftLoadedChunkLimit}");
|
|
|
|
break;
|
|
default:
|
|
await dest.WriteLineAsync($"Error: Unknown sub-action {subAction}.");
|
|
break;
|
|
}
|
|
}
|
|
|
|
private async Task handleUsersCommand(string[] commandParts, StreamWriter dest)
|
|
{
|
|
if (commandParts.Length < 2)
|
|
{
|
|
await dest.WriteLineAsync("Nibriboard Server Command Console: users");
|
|
await dest.WriteLineAsync("----------------------------------------");
|
|
await dest.WriteLineAsync("Interact with user accounts.");
|
|
await dest.WriteLineAsync("Usage:");
|
|
await dest.WriteLineAsync(" users {subcommand}");
|
|
await dest.WriteLineAsync();
|
|
await dest.WriteLineAsync("Subcommands:");
|
|
await dest.WriteLineAsync(" list");
|
|
await dest.WriteLineAsync(" Lists all users.");
|
|
await dest.WriteLineAsync(" add {username} {password}");
|
|
await dest.WriteLineAsync(" Adds a new user");
|
|
await dest.WriteLineAsync(" roles list");
|
|
await dest.WriteLineAsync(" Lists all roles");
|
|
await dest.WriteLineAsync(" roles grant {role-name} {username}");
|
|
await dest.WriteLineAsync(" Adds a role to a user");
|
|
await dest.WriteLineAsync(" roles revoke {role-name} {username}");
|
|
await dest.WriteLineAsync(" Removes a role from a user");
|
|
return;
|
|
}
|
|
|
|
string subAction = commandParts[1].Trim();
|
|
switch (subAction)
|
|
{
|
|
case "list":
|
|
await dest.WriteLineAsync(
|
|
string.Join("\n", server.AccountManager.Users.Select(
|
|
(User user) => $"{user.CreationTime}\t{user.Username}\t{string.Join(", ", user.Roles.Select((RbacRole role) => role.Name))}"
|
|
))
|
|
);
|
|
break;
|
|
case "add":
|
|
string newUsername = (commandParts[2] ?? "").Trim();
|
|
string password = (commandParts[3] ?? "").Trim();
|
|
|
|
if (newUsername.Length == 0) {
|
|
await dest.WriteLineAsync("Error: No username specified!");
|
|
break;
|
|
}
|
|
if (password.Length == 0) {
|
|
await dest.WriteLineAsync("Error: No password specified!");
|
|
break;
|
|
}
|
|
|
|
server.AccountManager.AddUser(newUsername, password);
|
|
await server.SaveUserData();
|
|
await dest.WriteLineAsync($"Added user with name {newUsername} successfully.");
|
|
break;
|
|
case "roles":
|
|
await handleRoleCommand(commandParts, dest);
|
|
break;
|
|
default:
|
|
await dest.WriteLineAsync($"Unrecognised sub-command {subAction}.");
|
|
break;
|
|
}
|
|
}
|
|
|
|
private async Task handleRoleCommand(string[] commandParts, StreamWriter dest)
|
|
{
|
|
string subAction = (commandParts[2] ?? "").Trim();
|
|
if (subAction.Length == 0)
|
|
{
|
|
await dest.WriteLineAsync($"Error: No sub-action specified.");
|
|
return;
|
|
}
|
|
|
|
switch (subAction)
|
|
{
|
|
case "list":
|
|
await dest.WriteLineAsync(string.Join("\n", server.AccountManager.Roles.Select(
|
|
(RbacRole role) => role.ToString()
|
|
)));
|
|
break;
|
|
case "grant":
|
|
await handleRoleGrantCommand(commandParts, dest);
|
|
break;
|
|
|
|
case "revoke":
|
|
await handleRoleRevokeCommand(commandParts, dest);
|
|
break;
|
|
}
|
|
}
|
|
|
|
private async Task handleRoleGrantCommand(string[] commandParts, StreamWriter dest)
|
|
{
|
|
|
|
string roleName = (commandParts[3] ?? "").Trim();
|
|
string recievingUsername = (commandParts[4] ?? "").Trim();
|
|
if (roleName.Length == 0) {
|
|
await dest.WriteLineAsync("Error: No role name specified!");
|
|
return;
|
|
}
|
|
if (recievingUsername.Length == 0) {
|
|
await dest.WriteLineAsync("Error: No username specified!");
|
|
return;
|
|
}
|
|
|
|
User user = server.AccountManager.GetByName(recievingUsername);
|
|
RbacRole roleToGrant = server.AccountManager.ResolveRole(roleName);
|
|
if (user == null) {
|
|
await dest.WriteLineAsync($"Error: No user with the the name {recievingUsername} could be found.");
|
|
return;
|
|
}
|
|
if (roleToGrant == null) {
|
|
await dest.WriteLineAsync($"Error: No role with the the name {roleName} could be found.");
|
|
return;
|
|
}
|
|
if (user.HasRole(roleToGrant)) {
|
|
await dest.WriteLineAsync($"Error: {recievingUsername} already has the role {roleToGrant.Name}.");
|
|
return;
|
|
}
|
|
|
|
user.Roles.Add(roleToGrant);
|
|
await server.SaveUserData();
|
|
|
|
await dest.WriteLineAsync($"Role {roleToGrant.Name} added to {user.Username} successfully.");
|
|
}
|
|
private async Task handleRoleRevokeCommand(string[] commandParts, StreamWriter dest)
|
|
{
|
|
|
|
string roleName = (commandParts[3] ?? "").Trim();
|
|
string recievingUsername = (commandParts[4] ?? "").Trim();
|
|
if (roleName.Length == 0) {
|
|
await dest.WriteLineAsync("Error: No role name specified!");
|
|
return;
|
|
}
|
|
if (recievingUsername.Length == 0) {
|
|
await dest.WriteLineAsync("Error: No username specified!");
|
|
return;
|
|
}
|
|
|
|
User user = server.AccountManager.GetByName(recievingUsername);
|
|
RbacRole roleToGrant = server.AccountManager.ResolveRole(roleName);
|
|
if (user == null) {
|
|
await dest.WriteLineAsync($"Error: No user with the the name {recievingUsername} could be found.");
|
|
return;
|
|
}
|
|
if (roleToGrant == null) {
|
|
await dest.WriteLineAsync($"Error: No role with the the name {roleName} could be found.");
|
|
return;
|
|
}
|
|
if (!user.HasRole(roleToGrant)) {
|
|
await dest.WriteLineAsync($"Error: {recievingUsername} doesn't have the role {roleToGrant.Name}.");
|
|
return;
|
|
}
|
|
|
|
user.Roles.Remove(roleToGrant);
|
|
await server.SaveUserData();
|
|
|
|
await dest.WriteLineAsync($"Role {roleToGrant.Name} removed from {user.Username} successfully.");
|
|
}
|
|
}
|
|
}
|