mirror of
https://github.com/livingcomputermuseum/IFS.git
synced 2026-03-27 10:21:14 +00:00
Initial implementation of console interface for maintenance/config
This commit is contained in:
@@ -38,6 +38,18 @@ namespace IFS
|
||||
return _accounts.Values.ToList<UserToken>();
|
||||
}
|
||||
|
||||
public static UserToken GetUser(string userName)
|
||||
{
|
||||
if (_accounts.ContainsKey(userName))
|
||||
{
|
||||
return _accounts[userName];
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static UserToken Authenticate(string userName, string password)
|
||||
{
|
||||
//
|
||||
|
||||
@@ -152,7 +152,7 @@ namespace IFS.BSP
|
||||
case PupType.Abort:
|
||||
{
|
||||
string abortMessage = Helpers.ArrayToString(p.Contents);
|
||||
Log.Write(LogType.Warning, LogComponent.RTP, String.Format("BSP aborted, message: '{0}'", abortMessage));
|
||||
Log.Write(LogType.Warning, LogComponent.RTP, String.Format("BSP aborted, message from client: '{0}'", abortMessage));
|
||||
|
||||
DestroyChannel(channel);
|
||||
}
|
||||
@@ -257,7 +257,8 @@ namespace IFS.BSP
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: send back "server full" repsonse of some sort.
|
||||
// Send an Abort with an informative message.
|
||||
channel.SendAbort("IFS Server full, try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Debug settings
|
||||
|
||||
LogTypes = All
|
||||
LogTypes = Error
|
||||
LogComponents = All
|
||||
|
||||
# Normal configuration
|
||||
@@ -15,3 +15,5 @@ CopyDiskRoot = c:\ifs\copydisk
|
||||
BootRoot = c:\ifs\boot
|
||||
InterfaceType = RAW
|
||||
InterfaceName = Ethernet 2
|
||||
ServerNetwork = 1
|
||||
ServerHost = 1
|
||||
|
||||
@@ -63,6 +63,16 @@ namespace IFS
|
||||
/// </summary>
|
||||
public static readonly string InterfaceName;
|
||||
|
||||
/// <summary>
|
||||
/// The network that this server lives on
|
||||
/// </summary>
|
||||
public static readonly int ServerNetwork;
|
||||
|
||||
/// <summary>
|
||||
/// The host number for this server.
|
||||
/// </summary>
|
||||
public static readonly int ServerHost;
|
||||
|
||||
/// <summary>
|
||||
/// The root directory for the FTP file store.
|
||||
/// </summary>
|
||||
|
||||
581
PUP/Console/Console.cs
Normal file
581
PUP/Console/Console.cs
Normal file
@@ -0,0 +1,581 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
|
||||
namespace IFS.IfsConsole
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a node in the debug command tree.
|
||||
/// </summary>
|
||||
public class ConsoleCommand
|
||||
{
|
||||
public ConsoleCommand(string name, String description, String usage, MethodInfo method)
|
||||
{
|
||||
Name = name.Trim().ToLower();
|
||||
Description = description;
|
||||
Usage = usage;
|
||||
Methods = new List<MethodInfo>(4);
|
||||
|
||||
if (method != null)
|
||||
{
|
||||
Methods.Add(method);
|
||||
}
|
||||
|
||||
SubCommands = new List<ConsoleCommand>();
|
||||
}
|
||||
|
||||
public string Name;
|
||||
public string Description;
|
||||
public string Usage;
|
||||
public List<MethodInfo> Methods;
|
||||
public List<ConsoleCommand> SubCommands;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (this.Methods.Count == 0)
|
||||
{
|
||||
return String.Format("{0}... ({1})", this.Name, this.SubCommands.Count);
|
||||
}
|
||||
else
|
||||
{
|
||||
return this.Name;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddSubNode(List<string> words, MethodInfo method)
|
||||
{
|
||||
// We should never hit this case.
|
||||
if (words.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("Out of words building command node.");
|
||||
}
|
||||
|
||||
// Check the root to see if a node for the first incoming word has already been added
|
||||
ConsoleCommand subNode = FindSubNodeByName(words[0]);
|
||||
|
||||
if (subNode == null)
|
||||
{
|
||||
// No, it has not -- create one and add it now.
|
||||
subNode = new ConsoleCommand(words[0], null, null, null);
|
||||
this.SubCommands.Add(subNode);
|
||||
|
||||
if (words.Count == 1)
|
||||
{
|
||||
// This is the last stop -- set the method and be done with it now.
|
||||
subNode.Methods.Add(method);
|
||||
|
||||
// early return.
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// The node already exists, we will be adding a subnode, hopefully.
|
||||
if (words.Count == 1)
|
||||
{
|
||||
//
|
||||
// If we're on the last word at this point then this is an overloaded command.
|
||||
// Check that we don't have any other commands with this number of arguments.
|
||||
//
|
||||
int argCount = method.GetParameters().Length;
|
||||
foreach (MethodInfo info in subNode.Methods)
|
||||
{
|
||||
if (info.GetParameters().Length == argCount)
|
||||
{
|
||||
throw new InvalidOperationException("Duplicate overload for console command");
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// We're ok. Add it to the method list.
|
||||
//
|
||||
subNode.Methods.Add(method);
|
||||
|
||||
// and return early.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// We have more words to go.
|
||||
words.RemoveAt(0);
|
||||
subNode.AddSubNode(words, method);
|
||||
|
||||
}
|
||||
|
||||
public ConsoleCommand FindSubNodeByName(string name)
|
||||
{
|
||||
ConsoleCommand found = null;
|
||||
|
||||
foreach (ConsoleCommand sub in SubCommands)
|
||||
{
|
||||
if (sub.Name == name)
|
||||
{
|
||||
found = sub;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
}
|
||||
|
||||
public class ConsoleExecutor
|
||||
{
|
||||
private ConsoleExecutor()
|
||||
{
|
||||
BuildCommandTree();
|
||||
|
||||
_consolePrompt = new ConsolePrompt(_commandRoot);
|
||||
}
|
||||
|
||||
public static ConsoleExecutor Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = new ConsoleExecutor();
|
||||
}
|
||||
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
public void Run()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Get the command string from the prompt.
|
||||
string command = _consolePrompt.Prompt().Trim();
|
||||
|
||||
if (command != String.Empty)
|
||||
{
|
||||
ExecuteLine(command);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ExecuteLine(string line)
|
||||
{
|
||||
// Comments start with "#"
|
||||
if (line.StartsWith("#"))
|
||||
{
|
||||
// Do nothing, ignore.
|
||||
}
|
||||
else
|
||||
{
|
||||
string[] args = null;
|
||||
List<MethodInfo> methods = GetMethodsFromCommandString(line, out args);
|
||||
|
||||
if (methods == null)
|
||||
{
|
||||
// Not a command.
|
||||
Console.WriteLine("Invalid command.");
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
InvokeConsoleMethod(methods, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InvokeConsoleMethod(List<MethodInfo> methods, string[] args)
|
||||
{
|
||||
MethodInfo method = null;
|
||||
|
||||
//
|
||||
// Find the method that matches the arg count we were passed
|
||||
// (i.e. handle overloaded commands)
|
||||
//
|
||||
foreach (MethodInfo m in methods)
|
||||
{
|
||||
ParameterInfo[] paramInfo = m.GetParameters();
|
||||
|
||||
if (args == null && paramInfo.Length == 0 ||
|
||||
paramInfo.Length == args.Length)
|
||||
{
|
||||
// found a match
|
||||
method = m;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (method == null)
|
||||
{
|
||||
// invalid argument count.
|
||||
// todo: display usage?
|
||||
throw new ArgumentException(String.Format("Invalid argument count to command."));
|
||||
}
|
||||
|
||||
ParameterInfo[] parameterInfo = method.GetParameters();
|
||||
object[] invokeParams;
|
||||
|
||||
if (args == null)
|
||||
{
|
||||
invokeParams = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
invokeParams = new object[parameterInfo.Length];
|
||||
}
|
||||
|
||||
for (int i = 0; i < parameterInfo.Length; i++)
|
||||
{
|
||||
ParameterInfo p = parameterInfo[i];
|
||||
|
||||
if (p.ParameterType.IsEnum)
|
||||
{
|
||||
//
|
||||
// This is an enumeration type.
|
||||
// See if we can find an enumerant that matches the argument.
|
||||
//
|
||||
FieldInfo[] fields = p.ParameterType.GetFields();
|
||||
|
||||
foreach (FieldInfo f in fields)
|
||||
{
|
||||
if (!f.IsSpecialName && args[i].ToLower() == f.Name.ToLower())
|
||||
{
|
||||
invokeParams[i] = f.GetRawConstantValue();
|
||||
}
|
||||
}
|
||||
|
||||
if (invokeParams[i] == null)
|
||||
{
|
||||
// no match, provide possible values
|
||||
StringBuilder sb = new StringBuilder(String.Format("Invalid value for parameter {0}. Possible values are:", i));
|
||||
|
||||
foreach (FieldInfo f in fields)
|
||||
{
|
||||
if (!f.IsSpecialName)
|
||||
{
|
||||
sb.AppendFormat("{0} ", f.Name);
|
||||
}
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
|
||||
throw new ArgumentException(sb.ToString());
|
||||
}
|
||||
|
||||
}
|
||||
else if (p.ParameterType.IsArray)
|
||||
{
|
||||
//
|
||||
// If a function takes an array type, i should do something here, yeah.
|
||||
//
|
||||
}
|
||||
else
|
||||
{
|
||||
// must be something more normal...
|
||||
if (p.ParameterType == typeof(uint))
|
||||
{
|
||||
invokeParams[i] = TryParseUint(args[i]);
|
||||
}
|
||||
else if (p.ParameterType == typeof(ushort))
|
||||
{
|
||||
invokeParams[i] = TryParseUshort(args[i]);
|
||||
}
|
||||
else if (p.ParameterType == typeof(string))
|
||||
{
|
||||
invokeParams[i] = args[i];
|
||||
}
|
||||
else if (p.ParameterType == typeof(char))
|
||||
{
|
||||
invokeParams[i] = (char)args[i][0];
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException(String.Format("Unhandled type for parameter {0}, type {1}", i, p.ParameterType));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// If we've made it THIS far, then we were able to parse all the commands into what they should be.
|
||||
// Invoke the method on the static instance exposed by the class.
|
||||
//
|
||||
object instance = GetInstanceFromMethod(method);
|
||||
|
||||
method.Invoke(instance, invokeParams);
|
||||
|
||||
}
|
||||
|
||||
private object GetInstanceFromMethod(MethodInfo method)
|
||||
{
|
||||
Type instanceType = method.DeclaringType;
|
||||
PropertyInfo property = instanceType.GetProperty("Instance");
|
||||
return property.GetValue(null, null);
|
||||
}
|
||||
|
||||
enum ParseState
|
||||
{
|
||||
NonWhiteSpace = 0,
|
||||
WhiteSpace = 1,
|
||||
QuotedString = 2,
|
||||
}
|
||||
|
||||
private List<string> SplitArgs(string commandString)
|
||||
{
|
||||
// We split on whitespace and specially handle quoted strings (quoted strings count as a single arg)
|
||||
//
|
||||
List<string> args = new List<string>();
|
||||
|
||||
commandString = commandString.Trim();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
ParseState state = ParseState.NonWhiteSpace;
|
||||
|
||||
foreach(char c in commandString)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case ParseState.NonWhiteSpace:
|
||||
if (char.IsWhiteSpace(c))
|
||||
{
|
||||
// End of token
|
||||
args.Add(sb.ToString());
|
||||
sb.Clear();
|
||||
state = ParseState.WhiteSpace;
|
||||
}
|
||||
else if (c == '\"')
|
||||
{
|
||||
// Start of quoted string
|
||||
state = ParseState.QuotedString;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Character in token
|
||||
sb.Append(c);
|
||||
}
|
||||
break;
|
||||
|
||||
case ParseState.WhiteSpace:
|
||||
if (!char.IsWhiteSpace(c))
|
||||
{
|
||||
// Start of new token
|
||||
if (c != '\"')
|
||||
{
|
||||
sb.Append(c);
|
||||
state = ParseState.NonWhiteSpace;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Start of quoted string
|
||||
state = ParseState.QuotedString;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ParseState.QuotedString:
|
||||
if (c == '\"')
|
||||
{
|
||||
// End of quoted string.
|
||||
args.Add(sb.ToString());
|
||||
sb.Clear();
|
||||
state = ParseState.WhiteSpace;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Character in quoted string
|
||||
sb.Append(c);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (sb.Length > 0)
|
||||
{
|
||||
// Add the last token to the args list
|
||||
args.Add(sb.ToString());
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
private List<MethodInfo> GetMethodsFromCommandString(string command, out string[] args)
|
||||
{
|
||||
args = null;
|
||||
|
||||
List<string> cmdArgs = SplitArgs(command);
|
||||
|
||||
ConsoleCommand current = _commandRoot;
|
||||
int commandIndex = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
// If this node has an executor, then we're done
|
||||
// (We assume that the tree is correctly built and that only
|
||||
// leaves have executors)
|
||||
if (current.Methods.Count > 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (commandIndex > cmdArgs.Count - 1)
|
||||
{
|
||||
// Out of args!
|
||||
return null;
|
||||
}
|
||||
|
||||
// Otherwise we continue down the tree.
|
||||
current = current.FindSubNodeByName(cmdArgs[commandIndex]);
|
||||
commandIndex++;
|
||||
|
||||
if (current == null)
|
||||
{
|
||||
// If the node was not found, then the command is invalid.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
//Now current should point to the command with the executor
|
||||
//and commandIndex should point to the first argument to the command.
|
||||
|
||||
cmdArgs.RemoveRange(0, commandIndex);
|
||||
|
||||
args = cmdArgs.ToArray();
|
||||
return current.Methods;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static uint TryParseUint(string arg)
|
||||
{
|
||||
uint result = 0;
|
||||
bool hexadecimal = false;
|
||||
|
||||
//if args starts with a "0x" it's hex
|
||||
//otherwise assume decimal
|
||||
if (arg.StartsWith("0x"))
|
||||
{
|
||||
hexadecimal = true;
|
||||
|
||||
//strip the "0x"
|
||||
arg = arg.Remove(0, 2);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
result = uint.Parse(arg, hexadecimal ? System.Globalization.NumberStyles.HexNumber : System.Globalization.NumberStyles.Integer);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine("{0} was not a valid 32-bit decimal or hexadecimal constant.", arg);
|
||||
throw;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static ushort TryParseUshort(string arg)
|
||||
{
|
||||
ushort result = 0;
|
||||
bool hexadecimal = false;
|
||||
|
||||
//if args starts with a "0x" it's hex
|
||||
//otherwise assume decimal
|
||||
if (arg.StartsWith("0x"))
|
||||
{
|
||||
hexadecimal = true;
|
||||
|
||||
//strip the "0x"
|
||||
arg = arg.Remove(0, 2);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
result = ushort.Parse(arg, hexadecimal ? System.Globalization.NumberStyles.HexNumber : System.Globalization.NumberStyles.Integer);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine("{0} was not a valid 16-bit decimal or hexadecimal constant.", arg);
|
||||
throw;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the debugger command tree.
|
||||
/// </summary>
|
||||
private void BuildCommandTree()
|
||||
{
|
||||
// Build the flat list which will be built into the tree, by walking
|
||||
// the classes that provide the methods
|
||||
_commandList = new List<ConsoleCommand>();
|
||||
|
||||
Type[] commandTypes = {
|
||||
typeof(ConsoleCommands),
|
||||
typeof(ConsoleExecutor),
|
||||
};
|
||||
|
||||
foreach (Type type in commandTypes)
|
||||
{
|
||||
foreach (MethodInfo info in type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
|
||||
{
|
||||
object[] attribs = info.GetCustomAttributes(typeof(ConsoleFunction), true);
|
||||
|
||||
if (attribs.Length > 1)
|
||||
{
|
||||
throw new InvalidOperationException(String.Format("More than one ConsoleFunction attribute set on {0}", info.Name));
|
||||
}
|
||||
else if (attribs.Length == 1)
|
||||
{
|
||||
// we have a debugger attribute set on this method
|
||||
// this cast should always succeed given that we're filtering for this type above.
|
||||
ConsoleFunction function = (ConsoleFunction)attribs[0];
|
||||
|
||||
ConsoleCommand newCommand = new ConsoleCommand(function.CommandName, function.Description, function.Usage, info);
|
||||
|
||||
_commandList.Add(newCommand);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now actually build the command tree from the above list!
|
||||
_commandRoot = new ConsoleCommand("Root", null, null, null);
|
||||
|
||||
foreach (ConsoleCommand c in _commandList)
|
||||
{
|
||||
string[] commandWords = c.Name.Split(' ');
|
||||
|
||||
// This is kind of ugly, we know that at this point every command built above have only
|
||||
// one method. When building the tree, overloaded commands may end up with more than one.
|
||||
_commandRoot.AddSubNode(new List<string>(commandWords), c.Methods[0]);
|
||||
}
|
||||
}
|
||||
|
||||
[ConsoleFunction("show commands", "Shows debugger commands and their descriptions.")]
|
||||
private void ShowCommands()
|
||||
{
|
||||
foreach (ConsoleCommand cmd in _commandList)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(cmd.Usage))
|
||||
{
|
||||
Console.WriteLine("{0} - {1}\n {2}\n", cmd.Name, cmd.Description, cmd.Usage);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("{0} - {1}", cmd.Name, cmd.Description);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ConsolePrompt _consolePrompt;
|
||||
private ConsoleCommand _commandRoot;
|
||||
private List<ConsoleCommand> _commandList;
|
||||
|
||||
private static ConsoleExecutor _instance;
|
||||
}
|
||||
}
|
||||
49
PUP/Console/ConsoleAttributes.cs
Normal file
49
PUP/Console/ConsoleAttributes.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IFS.IfsConsole
|
||||
{
|
||||
public class ConsoleFunction : Attribute
|
||||
{
|
||||
public ConsoleFunction(string commandName)
|
||||
{
|
||||
_commandName = commandName;
|
||||
_usage = "<No help available>";
|
||||
}
|
||||
|
||||
public ConsoleFunction(string commandName, string description)
|
||||
{
|
||||
_commandName = commandName;
|
||||
_description = description;
|
||||
}
|
||||
|
||||
public ConsoleFunction(string commandName, string description, string usage)
|
||||
{
|
||||
_commandName = commandName;
|
||||
_description = description;
|
||||
_usage = usage;
|
||||
}
|
||||
|
||||
public string CommandName
|
||||
{
|
||||
get { return _commandName; }
|
||||
}
|
||||
|
||||
public string Usage
|
||||
{
|
||||
get { return _usage; }
|
||||
}
|
||||
|
||||
public string Description
|
||||
{
|
||||
get { return _description; }
|
||||
}
|
||||
|
||||
private string _commandName;
|
||||
private string _description;
|
||||
private string _usage;
|
||||
}
|
||||
}
|
||||
96
PUP/Console/ConsoleCommands.cs
Normal file
96
PUP/Console/ConsoleCommands.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IFS.IfsConsole
|
||||
{
|
||||
public class ConsoleCommands
|
||||
{
|
||||
private ConsoleCommands()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public static ConsoleCommands Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = new ConsoleCommands();
|
||||
}
|
||||
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
[ConsoleFunction("show users", "Displays the current user database")]
|
||||
private void ShowUsers()
|
||||
{
|
||||
foreach(UserToken user in Authentication.EnumerateUsers())
|
||||
{
|
||||
Console.WriteLine("{0}: ({1}) - {2},{3}", user.UserName, user.Privileges, user.FullName, user.HomeDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
[ConsoleFunction("show user", "Displays information for the specified user")]
|
||||
private void ShowUser(string username)
|
||||
{
|
||||
UserToken user = Authentication.GetUser(username);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
Console.WriteLine("User '{0}' does not exist.", username);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("{0}: ({1}) - {2},{3}", user.UserName, user.Privileges, user.FullName, user.HomeDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
[ConsoleFunction("set password", "Sets the password for the specified user")]
|
||||
private void SetPassword(string username, string newPassword)
|
||||
{
|
||||
UserToken user = Authentication.GetUser(username);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
Console.WriteLine("User '{0}' does not exist.", username);
|
||||
}
|
||||
else
|
||||
{
|
||||
Authentication.SetPassword(username, newPassword);
|
||||
}
|
||||
}
|
||||
|
||||
[ConsoleFunction("add user", "Adds a new user", "<username> <password> [User|Admin] <full name> <home directory>")]
|
||||
private void AddUser(string username, string newPassword, string privileges, string fullName, string homeDir)
|
||||
{
|
||||
IFSPrivileges privs = IFSPrivileges.ReadOnly;
|
||||
|
||||
switch(privileges.ToLowerInvariant())
|
||||
{
|
||||
case "user":
|
||||
privs = IFSPrivileges.ReadOnly;
|
||||
break;
|
||||
|
||||
case "admin":
|
||||
privs = IFSPrivileges.ReadWrite;
|
||||
break;
|
||||
}
|
||||
|
||||
if (Authentication.AddUser(username, newPassword, fullName, homeDir, privs))
|
||||
{
|
||||
Console.WriteLine("User added.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("User already exists.");
|
||||
}
|
||||
}
|
||||
|
||||
private static ConsoleCommands _instance;
|
||||
}
|
||||
}
|
||||
456
PUP/Console/ConsolePrompt.cs
Normal file
456
PUP/Console/ConsolePrompt.cs
Normal file
@@ -0,0 +1,456 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace IFS.IfsConsole
|
||||
{
|
||||
public class ConsolePrompt
|
||||
{
|
||||
|
||||
public ConsolePrompt(ConsoleCommand root)
|
||||
{
|
||||
_commandTree = root;
|
||||
_commandHistory = new List<string>(64);
|
||||
_historyIndex = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs a nifty interactive debugger prompt.
|
||||
/// </summary>
|
||||
public string Prompt()
|
||||
{
|
||||
DisplayPrompt();
|
||||
ClearInput();
|
||||
UpdateOrigin();
|
||||
|
||||
bool entryDone = false;
|
||||
|
||||
while (!entryDone)
|
||||
{
|
||||
UpdateDisplay();
|
||||
|
||||
// Read one keystroke from the console...
|
||||
ConsoleKeyInfo key = Console.ReadKey(true);
|
||||
|
||||
//Parse special chars...
|
||||
switch (key.Key)
|
||||
{
|
||||
case ConsoleKey.Escape: //Clear input
|
||||
ClearInput();
|
||||
break;
|
||||
|
||||
case ConsoleKey.Backspace: // Delete last char
|
||||
DeleteCharAtCursor(true /* backspace */);
|
||||
break;
|
||||
|
||||
case ConsoleKey.Delete: //Delete character at cursor
|
||||
DeleteCharAtCursor(false /* delete */);
|
||||
break;
|
||||
|
||||
case ConsoleKey.LeftArrow:
|
||||
MoveLeft();
|
||||
break;
|
||||
|
||||
case ConsoleKey.RightArrow:
|
||||
MoveRight();
|
||||
break;
|
||||
|
||||
case ConsoleKey.UpArrow:
|
||||
HistoryPrev();
|
||||
break;
|
||||
|
||||
case ConsoleKey.DownArrow:
|
||||
HistoryNext();
|
||||
break;
|
||||
|
||||
case ConsoleKey.Home:
|
||||
MoveToBeginning();
|
||||
break;
|
||||
|
||||
case ConsoleKey.End:
|
||||
MoveToEnd();
|
||||
break;
|
||||
|
||||
case ConsoleKey.Tab:
|
||||
DoCompletion(false /* silent */);
|
||||
break;
|
||||
|
||||
case ConsoleKey.Enter:
|
||||
DoCompletion(true /* silent */);
|
||||
UpdateDisplay();
|
||||
CRLF();
|
||||
entryDone = true;
|
||||
break;
|
||||
|
||||
case ConsoleKey.Spacebar:
|
||||
if (!_input.EndsWith(" ") &&
|
||||
DoCompletion(true /* silent */))
|
||||
{
|
||||
UpdateDisplay();
|
||||
}
|
||||
else
|
||||
{
|
||||
InsertChar(key.KeyChar);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// Not a special key, just insert it if it's deemed printable.
|
||||
if (char.IsLetterOrDigit(key.KeyChar) ||
|
||||
char.IsPunctuation(key.KeyChar) ||
|
||||
char.IsSymbol(key.KeyChar) ||
|
||||
char.IsWhiteSpace(key.KeyChar))
|
||||
{
|
||||
InsertChar(key.KeyChar);
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Done. Add to history if input is non-empty
|
||||
if (_input != string.Empty)
|
||||
{
|
||||
_commandHistory.Add(_input);
|
||||
HistoryIndex = _commandHistory.Count - 1;
|
||||
}
|
||||
|
||||
return _input;
|
||||
}
|
||||
|
||||
private void DeleteCharAtCursor(bool backspace)
|
||||
{
|
||||
if (_input.Length == 0)
|
||||
{
|
||||
//nothing to delete, bail.
|
||||
return;
|
||||
}
|
||||
|
||||
if (backspace)
|
||||
{
|
||||
if (TextPosition == 0)
|
||||
{
|
||||
// We's at the beginning of the input,
|
||||
// can't backspace from here.
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// remove 1 char at the position before the cursor.
|
||||
// and move the cursor back one char
|
||||
_input = _input.Remove(TextPosition - 1, 1);
|
||||
|
||||
TextPosition--;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (TextPosition == _input.Length)
|
||||
{
|
||||
// At the end of input, can't delete a char from here
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// remove one char at the current cursor pos.
|
||||
// do not move the cursor
|
||||
_input = _input.Remove(TextPosition, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DisplayPrompt()
|
||||
{
|
||||
Console.Write(">");
|
||||
}
|
||||
|
||||
private void UpdateDisplay()
|
||||
{
|
||||
//if the current input string is shorter than the last, then we need to erase a few chars at the end.
|
||||
string clear = String.Empty;
|
||||
|
||||
if (_input.Length < _lastInputLength)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder(_lastInputLength - _input.Length);
|
||||
for (int i = 0; i < _lastInputLength - _input.Length; i++)
|
||||
{
|
||||
sb.Append(' ');
|
||||
}
|
||||
|
||||
clear = sb.ToString();
|
||||
}
|
||||
|
||||
int column = ((_textPosition + _originColumn) % Console.BufferWidth);
|
||||
int row = ((_textPosition + _originColumn) / Console.BufferWidth) + _originRow;
|
||||
|
||||
// Move cursor to origin to draw string
|
||||
Console.CursorLeft = _originColumn;
|
||||
Console.CursorTop = _originRow;
|
||||
Console.Write(_input + clear);
|
||||
|
||||
// Move cursor to text position to draw cursor
|
||||
Console.CursorLeft = column;
|
||||
Console.CursorTop = row;
|
||||
Console.CursorVisible = true;
|
||||
|
||||
_lastInputLength = _input.Length;
|
||||
}
|
||||
|
||||
private void MoveLeft()
|
||||
{
|
||||
TextPosition--;
|
||||
}
|
||||
|
||||
private void MoveRight()
|
||||
{
|
||||
TextPosition++;
|
||||
}
|
||||
|
||||
private void HistoryPrev()
|
||||
{
|
||||
|
||||
HistoryIndex--;
|
||||
if (HistoryIndex < _commandHistory.Count)
|
||||
{
|
||||
_input = _commandHistory[HistoryIndex];
|
||||
TextPosition = _input.Length;
|
||||
}
|
||||
}
|
||||
|
||||
private void HistoryNext()
|
||||
{
|
||||
HistoryIndex++;
|
||||
if (HistoryIndex < _commandHistory.Count)
|
||||
{
|
||||
_input = _commandHistory[HistoryIndex];
|
||||
TextPosition = _input.Length;
|
||||
}
|
||||
}
|
||||
|
||||
private void MoveToBeginning()
|
||||
{
|
||||
TextPosition = 0;
|
||||
}
|
||||
|
||||
private void MoveToEnd()
|
||||
{
|
||||
TextPosition = _input.Length;
|
||||
}
|
||||
|
||||
private void ClearInput()
|
||||
{
|
||||
_input = String.Empty;
|
||||
TextPosition = 0;
|
||||
}
|
||||
|
||||
private void InsertChar(char c)
|
||||
{
|
||||
_input = _input.Insert(TextPosition, c.ToString());
|
||||
TextPosition++;
|
||||
}
|
||||
|
||||
private void CRLF()
|
||||
{
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
private bool DoCompletion(bool silent)
|
||||
{
|
||||
// This code should probably move to another class, but hey, I'm lazy.
|
||||
|
||||
// Take the current input and see if it matches anything in the command tree
|
||||
string[] tokens = _input.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
// Save off the current cursor row; this is an ugly-ish hack to detect whether the match process
|
||||
// output anything. If the cursor row changes then we'll need to move the prompt
|
||||
int oldRow = Console.CursorTop;
|
||||
|
||||
string matchString = FuzzyMatch(_commandTree, new List<string>(tokens), silent);
|
||||
|
||||
bool changed = false;
|
||||
|
||||
if (matchString != String.Empty)
|
||||
{
|
||||
changed = _input.Trim().ToLower() != matchString.Trim().ToLower();
|
||||
|
||||
_input = matchString;
|
||||
TextPosition = _input.Length;
|
||||
}
|
||||
|
||||
if (!silent && oldRow != Console.CursorTop)
|
||||
{
|
||||
DisplayPrompt();
|
||||
UpdateOrigin();
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
private string FuzzyMatch(ConsoleCommand root, List<string> tokens, bool silent)
|
||||
{
|
||||
if (tokens.Count == 0)
|
||||
{
|
||||
if (!silent)
|
||||
{
|
||||
// If there are no tokens, just show the completion for the root.
|
||||
PrintCompletions(root.SubCommands);
|
||||
}
|
||||
return String.Empty;
|
||||
}
|
||||
|
||||
ConsoleCommand match = null;
|
||||
bool exactMatch = false;
|
||||
|
||||
// Search for exact matches. If we find one it's guaranteed to be unique
|
||||
// so we can follow that node.
|
||||
foreach (ConsoleCommand c in root.SubCommands)
|
||||
{
|
||||
if (c.Name.ToLower() == tokens[0].ToLower())
|
||||
{
|
||||
match = c;
|
||||
exactMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match == null)
|
||||
{
|
||||
// No exact match. Try a substring match.
|
||||
// If we have an unambiguous match then we can complete it automatically.
|
||||
// If the match is ambiguous, display possible completions and return String.Empty.
|
||||
List<ConsoleCommand> completions = new List<ConsoleCommand>();
|
||||
|
||||
foreach (ConsoleCommand c in root.SubCommands)
|
||||
{
|
||||
if (c.Name.StartsWith(tokens[0], StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
completions.Add(c);
|
||||
}
|
||||
}
|
||||
|
||||
if (completions.Count == 1)
|
||||
{
|
||||
// unambiguous match. use it.
|
||||
match = completions[0];
|
||||
}
|
||||
else if (completions.Count > 1)
|
||||
{
|
||||
// ambiguous match. display possible completions.
|
||||
if (!silent)
|
||||
{
|
||||
PrintCompletions(completions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (match == null)
|
||||
{
|
||||
// If we reach this point then no matches are available. return the tokens we have...
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < tokens.Count; i++)
|
||||
{
|
||||
if (i < tokens.Count - 1)
|
||||
{
|
||||
sb.AppendFormat("{0} ", tokens[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendFormat("{0}", tokens[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
// A match was found
|
||||
tokens.RemoveAt(0);
|
||||
|
||||
string subMatch = String.Empty;
|
||||
|
||||
if (tokens.Count > 0)
|
||||
{
|
||||
subMatch = FuzzyMatch(match, tokens, silent);
|
||||
}
|
||||
else if (exactMatch)
|
||||
{
|
||||
if (!silent && match.SubCommands.Count > 0)
|
||||
{
|
||||
// Just show the completions for this node.
|
||||
PrintCompletions(match.SubCommands);
|
||||
}
|
||||
}
|
||||
|
||||
if (subMatch == String.Empty)
|
||||
{
|
||||
return String.Format("{0} ", match.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
return String.Format("{0} {1}", match.Name, subMatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PrintCompletions(List<ConsoleCommand> completions)
|
||||
{
|
||||
// Just print all available completions at this node
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Possible completions are:");
|
||||
foreach (ConsoleCommand c in completions)
|
||||
{
|
||||
Console.Write("{0}\t", c);
|
||||
}
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
private void UpdateOrigin()
|
||||
{
|
||||
_originRow = Console.CursorTop;
|
||||
_originColumn = Console.CursorLeft;
|
||||
}
|
||||
|
||||
private int TextPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
return _textPosition;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
// Clip input between 0 and the length of input (+1, to allow adding text at end)
|
||||
_textPosition = Math.Max(0, value);
|
||||
_textPosition = Math.Min(_textPosition, _input.Length);
|
||||
}
|
||||
}
|
||||
|
||||
private int HistoryIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
return _historyIndex;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_historyIndex = Math.Min(_commandHistory.Count - 1, value);
|
||||
_historyIndex = Math.Max(0, _historyIndex);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private ConsoleCommand _commandTree;
|
||||
|
||||
|
||||
private int _originRow;
|
||||
private int _originColumn;
|
||||
|
||||
private string _input;
|
||||
private int _textPosition;
|
||||
private int _lastInputLength;
|
||||
|
||||
private List<string> _commandHistory;
|
||||
private int _historyIndex;
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ namespace IFS
|
||||
// Get our host address; for now just hardcode it.
|
||||
// TODO: need to define config files, etc.
|
||||
|
||||
_localHost = new HostAddress(1, 1);
|
||||
_localHost = new HostAddress((byte)Configuration.ServerNetwork, (byte)Configuration.ServerHost);
|
||||
|
||||
// Load in hosts table from hosts file.
|
||||
LoadHostTable();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using IFS.Boot;
|
||||
using IFS.CopyDisk;
|
||||
using IFS.FTP;
|
||||
using IFS.IfsConsole;
|
||||
using IFS.Transport;
|
||||
using PcapDotNet.Core;
|
||||
using PcapDotNet.Core.Extensions;
|
||||
@@ -96,20 +97,10 @@ namespace IFS
|
||||
}
|
||||
|
||||
private static void RunCommandPrompt()
|
||||
{
|
||||
List<UserToken> users = Authentication.EnumerateUsers();
|
||||
|
||||
Authentication.SetPassword(users[0].UserName, "hamdinger");
|
||||
|
||||
UserToken user = Authentication.Authenticate(users[0].UserName, "hamdinger");
|
||||
|
||||
while (true)
|
||||
{
|
||||
Console.Write(">>>");
|
||||
string command = Console.ReadLine();
|
||||
}
|
||||
{
|
||||
ConsoleExecutor.Instance.Run();
|
||||
}
|
||||
|
||||
private static BreathOfLife _breathOfLifeServer;
|
||||
private static BreathOfLife _breathOfLifeServer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +79,10 @@
|
||||
<Compile Include="BSP\BSPChannel.cs" />
|
||||
<Compile Include="BSP\BSPManager.cs" />
|
||||
<Compile Include="Configuration.cs" />
|
||||
<Compile Include="Console\Console.cs" />
|
||||
<Compile Include="Console\ConsoleAttributes.cs" />
|
||||
<Compile Include="Console\ConsoleCommands.cs" />
|
||||
<Compile Include="Console\ConsolePrompt.cs" />
|
||||
<Compile Include="CopyDisk\CopyDiskServer.cs" />
|
||||
<Compile Include="CopyDisk\DiabloPack.cs" />
|
||||
<Compile Include="DirectoryServices.cs" />
|
||||
|
||||
Reference in New Issue
Block a user