I would like to create a server process with Go which can receive or send files via scp, like:
scp -P 2200 -i ssh/tester_rsa foo@localhost:/test output.txt
or
scp -P 2200 -i ssh/tester_rsa input.txt foo@localhost:/test
With help of this gist I was already able to create an interactive ssh server which is able to accpet connections/commands via
ssh -i ssh/tester_rsa foo@localhost -p 2200 'somecommand'
and works nicely.
I understood that the payload contains "scp -f /test" for the first shown scp request. I am stuck in how to return actual content from the server, I tried in replying with a string (as with a normal ssh reply) but I get no file back.
Here my output of scp -vv
...
debug2: callback done
debug2: channel 0: open confirm rwindow 2097152 rmax 32768
debug2: channel_input_status_confirm: type 99 id 0
debug2: exec request accepted on channel 0
debug1: client_input_channel_req: channel 0 rtype exit-status reply 0
debug2: channel 0: rcvd close
debug2: channel 0: output open -> drain
debug2: channel 0: close_read
...
When I am trying with a propper server I get following lines
...
debug2: exec request accepted on channel 0
debug2: channel 0: rcvd ext data 43
Sending file modes: C0644 98 output.txt
debug2: channel 0: written 43 to efd 7
Sink: C0644 98 output.txt
output.txt
debug1: client_input_channel_req: channel 0 rtype exit-status reply 0
debug1: client_input_channel_req: channel 0 rtype eow@openssh.com reply 0
debug2: channel 0: rcvd eow
debug2: channel 0: close_read
...
So I guess I am missing some protocol / flow parameters or commands in Go.
Only the snippet of Go Code, as project is huge and is not easy to copy and paste here:
func (a *Shell) handleExec(connection ssh.Channel, payload []byte) {
...
for _, c := range a.ExecCmds {
if matchCmds(str, c.Cmd) {
connection.Write([]byte("Here comes the file content")
}
}
...
I know that the code is not right for scp, but it works fine for normal ssh. I don't know how to handle the connection, and what exactly to return so that a correct file transfer is done.
Googling for a while led me to this question on stack overflow which is nearly the same but got not completely answered, as I am exactly looking for the part
runYourCommand(msg.Command, ch)
which I guess contains the logic :)
Update: I am looking not for external ressources any more, I got some more input from this java snippet
It looks like they are emulating the scp protocol and I would perfectly be fine to have someone helping me to translate it to Go:
StringBuilder params = new StringBuilder("C0").append(perms);
params.append(" ").append(data.length).append(" ");
params.append(remoteFileName).append("\n");
out.write(params.toString().getBytes());
out.flush();
if (!waitForInputStream(logger, in)) {
cleanup(logger, out, channel);
logger.log(Level.SEVERE, "Error before writing SCP bytes");
return UNKNOWN_ERROR; /* TODO: Improve */
}
out.write(data);
out.write(new byte[]{0}, 0, 1);
out.flush();
I tried this so far, but didn't help
o:="Some content of a faked file"
connection.Write([]byte("C0644 "+string(len(o))+"test.txt\n"))
connection.Write([]byte(o))
connection.Write([]byte{0,0,1})
To clarify: I would not like to serve a real file, rather then Go simulating a file delivery over scp (using a string as the content of the file).
Thanks in advance!
Ok, found myself the solution, small but that made the code work:
prep := "C0644 " + strconv.Itoa(len(o)) + " test.txt\n"
connection.Write([]byte(prep))
connection.Write([]byte(o))
connection.Write([]byte("\x00"))
I was missing a space in prep variable after length, the length was not converted correctly and the closing "\x00" is working better this way. Now I am able to receive a fake file with content :)