Quantcast
Channel: Rebex Q&A Forum - Recent questions and answers
Viewing all articles
Browse latest Browse all 3860

Answered: ssh exec program

$
0
0

You are right, Rebex File Server doesn't support executing external commands. Its simplified virtual shell is mostly supposed to complement SFTP and SCP protocols and all the built-in commands only work with the virtual file system.

However, what you are trying to do is a very interesting scenario and it actually turned out that adding support for it is quite simple - we just had to enhance the internals a bit and make the inner streams accessible through SshConsole, which is passed with ShellCommand event arguments.

You can give this a try as well - just sent you a link to the current beta build.

We have not sufficiently tested this yet, but I was able to clone a 300 MB repository without any issues by running hg clone ssh://demo@localhost:8022/repository_name against this proof-of-concept server app:

using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Rebex.Net;
using Rebex.Net.Servers;

namespace Rebex.Samples
{
    public class MercurialServer
    {
        // path to mercurial
        private const string MercurialPath = @"c:\Program Files\TortoiseHg\hg.exe";

        // path to a directory that contains the repositories
        private const string RepositoryRoot = @"c:\data\repository";

        // buffer size for data proxy
        private const int BufferSize = 32 * 1024;

        // log writer
        private static readonly ILogWriter LogWriter = new ConsoleLogWriter(LogLevel.Debug);

        // user name
        private const string UserName = "demo";

        // password
        private const string Password = "password";

        // server port
        private const int ServerPort = 8022;

        // server key
        private const string ServerKey = @"-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDIkIOdSx47C4slUBVeEeVlCySg2IyT3laqBIr0uLlKIwwkI61F
Qen57kmgFG3Cs2zmbrnnRZYKR38EbrVyLH68wC9a1l6T7evUfnOD/O1fsPM1/XB6
m2shgUZsX6kXt1AUaPv2G4uH8J6Mk+RiJcrtBFslK0Lout5rLVm9Wn1d6QIDAQAB
AoGAYoBLC2S5l6EPOQeQPu+GHG5xEkfYHsUrBfwOLKtOYOE+lL8q2WFKYrOLWEHA
OEe7m55U0gckba74bDpdBZJhuT4MqaQ29PJn8fpi6RsFvvEqsgm6f9AWiScQ/n+h
/qSUraC5V8s03qF8XWuo5/87NNUunZDaA2UvUg8Urb0p87MCQQDjww8bwYVQS0lG
cpmjtNKqeLyfXtWPzo73/sCjIGsq/0Umdlufqlhezx3jvr+H/zaopp3A2dN6AKO8
nwFRrQrHAkEA4W48KB1R/Pjo5OefdiVAa6PFkmphFK0N7qjxg0WMCnxaqgbhwZFz
qnVq8RP2Al84Wn4fwpJwpKaqk82ZF2IhzwJBAMw1r+4q7OS5G9HWHnrxPZEq/7PE
y6ZMhVNFTmL0RiIfDlkV9cCKcwFOonX4KLI+2TsNaJPoufvBZw1PY1df1zECQCNt
CmEXcnn5t8e5KpMLeZswymye8RCpvWXDAOkrNb20Gx9bI4Ei1XV1LFAkXeWzhwyZ
g241SyRk2KuPhL5q+nsCQC8oiNdcTepTCGss+gNHlTdw2U5bhgUAZacCp5WgcEQJ
YNAx781J3Bwqh7s9vOCT0ubDxbi9+8uOui0ruL8AWA8=
-----END RSA PRIVATE KEY-----
";

        public static void Main()
        {
            var server = new FileServer();

            // enable logging
            server.LogWriter = LogWriter;

            // bind SSH shell subsystem to the specified port
            server.Bind(ServerPort, FileServerProtocol.Shell);

            // use the embedded private key for the sake of simplicity
            server.Keys.Add(new SshPrivateKey(new MemoryStream(Encoding.ASCII.GetBytes(ServerKey)), "password"));

            // only one user is supported for now
            server.Users.Add(UserName, Password);

            // custom command handler
            server.ShellCommand += (sender, e) =>
            {
                switch (e.Command)
                {
                    case "hg":
                        // intercept "hg" command
                        e.Action = RunMercurial;
                        break;
                    default:
                        // reject all other commands (optional)
                        e.WriteLine("Command not supported.");
                        e.ExitCode = 127;
                        break;
                }
            };

            server.Start();

            Console.WriteLine("Server is running, press any key to stop it.");
            Console.ReadKey(true);
        }

        public static int RunMercurial(string[] args, SshConsole console)
        {
            // start Mercurial

            var info = new ProcessStartInfo(MercurialPath, PrepareArguments(args));
            info.WorkingDirectory = RepositoryRoot;
            info.CreateNoWindow = true;
            info.UseShellExecute = false;
            info.RedirectStandardInput = true;
            info.RedirectStandardOutput = true;
            info.RedirectStandardError = true;

            var process = Process.Start(info);

            // proxy stdin/stdout/stderr

            Stream consoleInput = console.GetInputStream();
            Stream consoleOutput = console.GetOutputStream();
            Stream consoleError = console.GetErrorStream() ?? Stream.Null; // error stream is only available in terminal-less mode

            Stream processInput = process.StandardInput.BaseStream;
            Stream processOutput = process.StandardOutput.BaseStream;
            Stream processError = process.StandardError.BaseStream;

            CopyAllAsync("stdin", consoleInput, processInput);
            CopyAllAsync("stderr", processError, consoleError);
            CopyAllAsync("stdout", processOutput, consoleOutput);

            // wait for the process to end
            process.WaitForExit();

            // return the exit code
            return process.ExitCode;
        }

        private static string PrepareArguments(string[] args)
        {
            // TODO: This method is supposed to convert the argument array into a string,
            // but it is not yet implemented properly - if any of the arguments contains whitespaces or quotes,
            // this produces wrong results.

            // TODO: But if the users are only supposed to have SSH access to Mercurial repositories,
            // we should only allow "-R REPO_NAME serve --stdio" or its variants anyway.

            return string.Join("", args);
        }

        private static Task CopyAllAsync(string name, Stream input, Stream output)
        {
            // TODO: Error handling is missing.

            return Task.Run(() =>
            {
                byte[] buffer = new byte[BufferSize];
                while (true)
                {
                    int n = input.Read(buffer, 0, buffer.Length);
                    //Console.WriteLine("{0}: {1} bytes", name, n);
                    if (n == 0)
                    {
                        output.Close();
                        break;
                    }
                    output.Write(buffer, 0, n);
                    output.Flush();
                }
            });
        }

    }
}

Viewing all articles
Browse latest Browse all 3860

Trending Articles