I want to connect with an SSH server which, upon connection, runs a custom command which expects XML as input (UTF-8 encoded, multiple lines, with a special line at the end of each XML document marking its end) and outputs XML (UTF-8 encoded, multiple lines, with same special line at the end to signify end-of-document).
There currently is a mechanism using plink.exe (of PuTTy fame) that works (meaning: the server-side does work as specified), but I am in the process of eliminating the need for an external process.
I'm giving a shot to the Renci.SshNet library, but I find the documentation inadequate, and can't find examples for what I need.
My attempt so far:
using System;
using Renci.SshNet;
namespace ServerConnect
{
public class ServerConnection
{
private const string EOC= "*-* COMMAND DELIMITER *-*";
private SshClient scon;
private System.IO.Stream stdin, stdout, stderr;
private System.IO.StreamWriter stdin_writer;
private System.IO.StreamReader stdout_reader;
private Shell shell;
public ServerConnection (string keyFileName, string keyFilePassphrase)
{
ConnectionInfo ci= new ConnectionInfo("my.server.local", 22, "datamgr",
new PrivateKeyAuthenticationMethod("datamgr", new PrivateKeyFile(keyFileName, keyFilePassphrase)));
this.scon= new SshClient(ci);
}
public ServerConnection (string keyFilePassphrase): this("client_id_rsa", keyFilePassphrase) {}
public void Connect() {
this.scon.Connect();
this.stdin= new System.IO.MemoryStream();
this.stdin_writer= new System.IO.StreamWriter(stdin);
this.stdout= new System.IO.MemoryStream();
this.stdout_reader= new System.IO.StreamReader(stdout);
this.stderr= new System.IO.MemoryStream();
this.shell= this.scon.CreateShell(this.stdin, this.stdout, this.stderr);
this.shell.Start();
}
public void Disconnect() {
this.scon.Disconnect();
}
public string Command(string text) {
Console.WriteLine("sending command");
this.stdin_writer.WriteLine(text);
Console.WriteLine("sending EOC");
this.stdin_writer.WriteLine(EOC);
this.stdin_writer.Flush();
for (;;) {
string line;
line= this.stdout_reader.ReadLine();
if (line == null) {
Console.WriteLine("got null");
break;
}
Console.WriteLine("READ");
Console.WriteLine(line);
if (line.StartsWith(EOC)) break;
}
return ""; // yes, this should return the built output
}
}
}
Given this as an assembly, I'm at the stage that I try to connect to the server (tests run through booish
for ease):
>>> import ServerConnect
>>> a= ServerConnection("passphrase")
ServerConnect.ServerConnection
>>> a.Connect()
>>> a.Command("<i command=\"ping\"/>")
sending command
sending EOC
got null
''
I can see (in the server's auth.log) that the connection is established after the a.Connect()
call. However, it seems that whatever I write to stdin_writer
does not get sent to the server.
How can I accomplish this mechanism? Send data to the server, then send the delimiter, and subsequently read data until I read the delimiter? The cycle will be repeated many times during a connection.
Please note that I am not an experienced C# programmer, so it's possible that I have a lack of basic understanding of the C# stream model.
PS I have seen this answer, but it seems that the CreateShellStream
call blocks, probably in a PseudoTty
allocation (the process has no need for pseudo-ttys, just input/output).
PS2 I downloaded and compiled the most current version of Renci.SshNet from codeplex.com, and now I am at this point (excerpt from the Connect method):
this.scon.Connect();
this.stream= this.scon.CreateShellStream("dumb", 80, 24, 800, 600, 1024);
this.stdin_writer= new System.IO.StreamWriter(this.stream);
this.stdout_reader= new System.IO.StreamReader(this.stream);
this.shell= this.scon.CreateShell(this.stream, this.stream, this.stream);
this.shell.Start();
However, any writes to the stdin_writer (with subsequent flushes) do not seem to go through.