0

I encountered a strange problem when using ssh to execute commands on a remote host. Assuming hostname returns hostA on hostA, hostname returns hostB on hostB, and so on. Guess what the following command will return?

ssh -A -t userA@hostA ssh -A -tt userB@hostB "hostname;hostname;hostname"

I originally thought it would be:

hostB
hostB
hostB

But the actual result is:

hostB
Connection to hostB closed.
hostA
hostA

An even more complex example is:

ssh -A -t userA@hostA ssh -A -t userB@hostB ssh -A -tt userC@hostC \
    "hostname \''&&'\' hostname '&&' hostname '&&' hostname && hostname && hostname"

And it actually returns:

hostC
hostC
Connection to hostC closed.
hostB
hostB
Connection to hostB closed.
hostA
hostA
Connection to hostA closed.

I want to know how the above commands are parsed or interpreted by the shell or SSH. Can someone explain step by step how the commands are executed?

gleport
  • 65
  • 4
  • Everything you pass to ssh as a positional argument gets concatenated together into a single string, and that single string is passed to a shell. A space that's contained _within_ an argument and a space created by the act of joining arguments have exactly the same priority; it has all the same bugs as a script containing only `eval "$*"` or `sh -c "$*"`. – Charles Duffy Jul 11 '23 at 16:26
  • This all becomes very obvious when you look at the wire protocol spec, which only passes a single string over the wire as the command to run, instead of passing an argument vector. Because there's no argument vector, the protocol _can't_ represent an arbitrary argv unless it's escaped in a manner appropriate to the remote shell. – Charles Duffy Jul 11 '23 at 16:29
  • ...this is all to say, chepner's answer is completely correct and on-point, including in particular the advice to only ever pass `ssh` a single positional argument comprising a single command already correctly escaped for the remote shell to parse. – Charles Duffy Jul 11 '23 at 16:29
  • That said, think about `cmd=( ssh -A -tt userB@hostB "hostname;hostname;hostname" ); ssh -A -t userA@hostA "${cmd[*]@Q}"` if you only need support for bash 5.0+ – Charles Duffy Jul 11 '23 at 16:33

1 Answers1

2

The shell on host A executes the following command:

 ssh -A -tt userB@hostB hostname;hostname;hostname

not

 ssh -A -tt userB@hostB "hostname;hostname;hostname"

As such, only the first hostname is an argument to ssh. The semicolon terminates that command so that the following hostname commands are "peers" of ssh, not arguments of ssh.

The process of joining multiple command arguments into a single command is not straightforward, and I generally recommend passing a single string argument yourself.

ssh -A -t userA@hostA 'ssh -A -tt userB@hostB "hostname;hostname;hostname"'
chepner
  • 497,756
  • 71
  • 530
  • 681