2

I need help in determining why I get

syntax error near unexpected token `then'

from the following command:

for vm in {20..30} ; do ssh -q -o "StrictHostKeyChecking no" 192.168.210.${vm} "hostname; echo $password | su - root -c \"\ if $(whoami | grep -q root);then echo Good; else echo Bad;fi \"" ; done

vm201scf001
-bash: -c: line 0: syntax error near unexpected token `then'
-bash: -c: line 0: `\ if ;then echo Good; else echo Bad;fi '

This simple command does work though:

 for vm in {20..30} ; do ssh -q -o "StrictHostKeyChecking no" 192.168.210.${vm} "hostname; echo $password | su - root -c \"\uptime \"" ; done
vm201scf001
 22:11:41 up 422 days,  3:32,  0 users,  load average: 0.28, 0.44, 0.52

Does the if, then, fi statement not work in this case? Please advise.

3
  • 3
    This question is similar to: How to execute an arbitrary simple command over ssh without knowing the login shell of the remote user?. If you believe it’s different, please edit the question, make it clear how it’s different and/or how the answers on that question are not helpful for your problem. Commented May 9 at 23:09
  • 1
    Also: unix.stackexchange.com/q/450020/70524. Search term of relevance: "quoting hell" Commented May 9 at 23:10
  • 1) Don't disable StrictHostKeyChecking; implement proper public key distribution instead. You are disabling an essential security feature of SSH. 2) For any nontrivial command, prefer to run a (shell, or maybe python!) script on the server. Passing complicated commands like that directly over SSH can work but is somewhat brittle for various reasons. Commented May 10 at 17:38

2 Answers 2

9

The problem is the missing command list after if:

if $(whoami | grep -q root);then

This would work under normal circumstances but is highly confusing and not useful. The command substitution ($(...)) does return an exit code, so technically it could be used.

But in this case the command substitution is run on the SSH client (due to the double quotes), not on the SSH server. grep -q never creates any output, the command substiturion is not quoted so it "expands" to this:

if; then

And that does not work. It could be prevented by

ssh "if \$(whoami | grep -q root);then ..."

(the backslash preventing the execution on the client side.

But (as Stéphane Chazelas pointed out in the comments) here the code is not to be run by the SSH login shell but by the su shell called from there. So another level of escaping is required - if it is done this way:

ssh "su - root -c \"if \\\$(whoami | grep -q root);then ...\""

or

ssh su - root -c "if \\\$(whoami | grep -q root);then ..."

What you really want is this, though:

if whoami | grep -q root; then

Unrelated security problem

ssh "echo $password | su - root -c ..."

is a really bad idea as every user on the system can read the root password. That should be

echo "$password" | ssh "su - root -c ..."

instead (as echo is a shell builtin and thus does not appear as a separate process with arguments in the system).

7
  • 1
    Namely, that's because the command substitution is in a double-quoted string, so it expands in the local shell, before ssh is launched, so the remote shell doesn't see a token there. If it was single-quoted so that the remote shell saw if $(...); then, it wouldn't be an error, but the exit status of the command inside the command substitution would be considered if it didn't output anything. Commented May 10 at 12:13
  • @ilkkachu Wow, OK, but why would anyone do it this way... Commented May 10 at 15:25
  • $( ... ) gets you the string value of the output not the exit status. if you want exit status do ( ) without the $ (but ( ) not needed between if and ;then ) Commented May 10 at 21:29
  • 1
    @Jasen, it expands to the output, yes, and usually the exit status of a command substitution isn't visible, e.g. in something like echo $(false), it's the exit status of echo that remains. But if there is no "main" command, then the exit status of the (rightmost) command substitution is used. Usually that would be an assignment, like if a=$(whatever); then do something with $a; fi, but it works without the assigment, try e.g. if $(false); then echo no; elif $(true); then echo yes; fi and variants. :) Commented May 11 at 15:52
  • 1
    if \$(whoami | grep -q root);then would not be enough to prevent it as there are 3 layers of shell interpretation here, the local shell, the shell started by sshd and the shell that su starts. As you want that code to be run by the latter, and double quotes are used everywhere, you'd need to escape that $ for both the local and remote shell. Commented May 11 at 17:51
1

Let's rewrite your code on several lines to make it more legible:

for vm in {20..30}; do
  ssh -q -o "StrictHostKeyChecking no" 192.168.210.${vm} "
    hostname
    echo $password |
      su - root -c \"
        \ if $(whoami | grep -q root);then
          echo Good
        else
          echo Bad
        fi \"
    "
done

We see several problems:

  • ${vm} unquoted which is asking the shell to split+glob it which makes no sense. However, in practice here that will only become a problem in contexts where $IFS contains digits, so we can probably ignore it.
  • Then we have the the expansion of $password which is presumably sensitive data meant to be secret (especially if that's meant to be the password for a root account) passed inside an argument to an executed command which means it will be visible by anyone on both the local and remote systems (and possible also stored in some audit logs). Best would be to ssh as root directly using key authentication which would avoid leaking any secret anywhere.
  • that $password is expanded inside the code passed to the remote shell, so if it contain any character special to the shell (as is common for passwords), that will cause havoc.
  • echo can't be used to output arbitrary data anyway.
  • Then you have that \ used to quote a space character before if, which means the shell started by su would try to run a command called " if".
  • Because as a result, there was no if keyword, the then is unexpected which is the error you're getting.
  • That $(whoami | grep -q root) will be expanded by the local shell, not the remote shell starting by sshd, not the shell started by su.
  • In any case, using command substitution makes no sense here. Command substitution if for when you want to use the output of a command. grep with -q produces no output.
  • Also grep -q root returns true if the input contains at least one line that contains root. Here you want to check that the whole output of whoami is one line that is exactly root.

Assuming sshd on the remote machines have AcceptEnv LC_* in its configuration (as is often the case to allow localisation preferences to be passed across from client to server), you can do something like:

for ip in 192.168.210.{20..30}; do
  LC_PASS=$password ssh -o SendEnv=LC_PASS \
                        -o StrictHostKeyChecking=no -q "$ip" '
    uname -n # standard equivalent of hostname
    printf "%s\n" "$LC_PASS" |
      su - root -c '\''
        if [ "$(id -u)" -eq 0 ]; then
          echo Good
        else
          echo Bad
        fi'\''
  '
done

Where:

  • the password is passed via environment variables rather than command arguments which is a lot safer as the environment is meant to remain private (at last not visible to other users other than root), but of course, still a lot less good than using keypair authentication.
  • We use single quotes around the code passed to the remote shell and the shell started by su, which means it's fixed code. The data (password) is passed via another mean (environment). $(id -u) (standard and returns the uid which is what matters here if you want to test that you're superuser) is expanded by the correct shell (the one started by sh)
  • printf instead of echo to be able to cope with arbitrary password (here assuming the remote shell has it builtin (as most do); if not, that would mean leaking it as well).

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.