0 votes
ago by (240 points)

I have created an SSH client in WinForms (C#) using Rebex.TerminalEmulation.TerminalControl. I am able to successfully connect, execute commands from the terminal and intercept SSH shell commands when the user presses the ENTER key on the control. This works correctly for single-line commands. However, I need a way to capture the complete command when it spans multiple lines and the user presses ENTER.

enter image description here

For example, in the first case (Green), the command “curl -v telnet://google.com:443” is a single-line command. It can be successfully captured as the last line on the TerminalControl using the Screen.GetRegionText() function.

However, in the second case (Red), the command spans two lines. When intercepted using Screen.GetRegionText(), it only captures “:443” instead of the complete command curl -v “telnet://abc.xyz.google.com:443”.

In many scenarios, user may enter commands that span 3, 4, 5, or even more lines in the terminal. Is there a way to properly capture or intercept the full multi-line command instead of just the last line?

1 Answer

0 votes
ago by (76.5k points)

I am not sure what exactly you are trying to achieve or what you are actually doing. Also what you know about the input - for example, do you know what the prompt is or whether each command starts with curl, or something else?

If I had to implement a method to capture a command to be executed, I would do it like this:

private Regex Prompt = new Regex("^.*@.*:.*[$#>] ?");

private void terminal_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
{
    if (e.KeyCode == Keys.Enter)
    {
        var s = terminal.Screen;
        var top = s.CursorTop;
        var lines = new StringBuilder();
        for (int i = top; i >= 0; i--)
        {
            var line = s.GetRegionText(0, i, s.Columns, 1)[0];
            var m = Prompt.Match(line);
            if (m.Success)
            {
                lines.Insert(0, line.Substring(m.Length));
                break;
            }
            else
            {
                lines.Insert(0, line);
            }
        }
        MessageBox.Show(lines.ToString().Trim(), "Captured command");
    }
}

The code above expects the prompt to be in the format user@machine:path$
It searches for the line containing the prompt.
It starts at the current cursor line (Screen.CursorTop) and goes one line up if the prompt was not detected.

This code was successfully tested with command echo Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris eget urna ut nibh laoreet ultrices. which spanned across three lines.

Please also note that if the user types very quickly, the last one or two characters of the command may not yet be displayed on the terminal screen when the ENTER is pressed. To ensure all sent/received data have been processed by the terminal before calling the GetRegionText() method, you should add terminal.Scripting.Process() method call at the beginning of the detection logic (just after the if (e.KeyCode == Keys.Enter) check). It can look like this:

terminal.UserInputEnabled = false;
terminal.SetDataProcessingMode(DataProcessingMode.None);
try
{
    while (terminal.Scripting.Process(500) == TerminalState.DataReceived) ;
}
finally
{
    terminal.UserInputEnabled = true;
    terminal.SetDataProcessingMode(DataProcessingMode.Automatic);
}

If you want to achieve something else, or if you have a different scenario, please describe it in more detail. Also providing some of your existing code would be appreciated.

ago by (240 points)
Thank you for quick response, the code given by you work perfectly as long as prompt format is same.

I have created custom SSH terminal (like PuTTY). The purpose of capturing commands is to log every command (whether single-line or multi-line) that a user enters or executes on the target server using custom SSH terminal.

NOTE:
• There is no fixed shell prompt. Each target server or user account may have a different prompt and the prompt text or format may change dynamically if the user switches shells during the SSH session.
• There is no predefined command pattern. Users can enter or execute any arbitrary command, including complex multi-line commands.
• Target server can be any flavour of UNIX/Linux running any version of SSH server.

Currently, I am using the Terminal.Screen.GetRegionText() function to capture the command entered by the user, assuming that the command is a single line.

Sample Code:
String EnteredCommand = "";
string[] lines = terminal.Screen.GetRegionText(0, terminal.Screen.CursorTop, terminal.Screen.Columns, 1);
if (lines.Length == 1)
{
    EnteredCommand = lines[0].TrimEnd(new char[] { ' ' });
}

Since, I unable to identify from which line number user started entering command, I am only capturing last line from terminal.

So, how can I capture multi-line command entered by user without knowing prompt format or without knowing from where user started entering command?
ago by (76.5k points)
Please note that terminals are designed for human interaction, not machine processing. What you're trying to achieve would require a human-like text interpreter.

What sounds like a reasonable solution to me would be to use the PreviewKeyDown event as shown above to capture all user keystrokes. Using this approach, you will capture all user input. It won't be exactly commands, but simply everything the user types, which can be used to identify issued commands if needed.

Moreover, you can use your current solution when ENTER is pressed to capture the current line (or two/three lines) and emit this information to your log with a message like "Current screen content:". This will be useful in cases when the user uses UP/DOWN arrows to select a command from history.

Does this fulfill your needs?
ago by (240 points)
The expectation was to capture exact command (single or multi-line) irrespective of typed or historical using UP/DOWN arrows.

I will try and work on mixed approach like combining keystrokes and GetRegionText() to capture near exact single or multi-line command.

Thank you for responses and suggestions. I will connect again in case I need more help around this :)
...