3

I want to transfer an environment variable over SSH.

The "correct" way is using SendEnv/~/.ssh/environment, but that requires the server to support AcceptEnv or PermitUserEnvironment, which it does not in my case.

So instead I am thinking to set the variable on the remote site like this:

FOO=val
export FOO
ssh server export FOO=$FOO'; do_stuff_which_uses_FOO'

That part is easy. I want a generic solution, so no matter the content of $FOO it will work. E.g.

FOO="  '\""
export FOO
QFOO=`quote "$FOO"` # quote will return "\ \ \'\\\""
export QFOO
ssh server export FOO=$QFOO'; do_stuff_which_uses_FOO'

This works no matter if the sending or receiving shell is sh or bash.

However, I also need it to work for csh/tcsh. And I will not know in advance which shell the receiving end is running. That means I have to code something that will work both in /bin/sh and /bin/csh.

So far I have managed to get it working for sh/bash:

ssh server // followed by the below quoted
eval `echo $SHELL | grep -E "/(t)?csh" > /dev/null && echo setenv FOO \\\ \\\ \\\\\'\\\\\" || echo export FOO=\\\ \\\ \\\\\'\\\\\";` ; echo "$FOO"

I can also get it to work for csh/tcsh (the user csh has csh as login shell):

ssh csh@server // followed by the below quoted
eval `echo $SHELL | grep -E "/(t)?csh" > /dev/null && echo setenv FOO \\\ \\\ \\\\\'\\\\\" || echo export FOO=\\\ \\\ \\\\\'\\\\\";` ; echo "$FOO"

If $FOO is * or ? it works fine with BASH:

ssh server eval\ \`echo\ \$SHELL\ \|\ grep\ -E\ \"/\(t\)\?csh\"\ \>\ /dev/null\ \&\&\ echo\ setenv\ FOO\ \\\\\\\\\\\*\\\;\ \|\|\ echo\ export\ FOO=\\\\\\\\\\\*\\\;\`\;echo\ \"\$FOO\"\ a;

But it fails with csh:

ssh csh@server eval\ \`echo\ \$SHELL\ \|\ grep\ -E\ \"/\(t\)\?csh\"\ \>\ /dev/null\ \&\&\ echo\ setenv\ FOO\ \\\\\\\\\\\*\\\;\ \|\|\ echo\ export\ FOO=\\\\\\\\\\\*\\\;\`\;echo\ \"\$FOO\"\ a;
No match.
FOO: Undefined variable.

It seems * and ? refuse to be quoted with .

To answer this question your solution should:

  • be able to transfer an environment variable to the remote server
  • not use SendEnv/AcceptEnv/PermitUserEnvironment
  • work no matter if the source shell is sh/bash/csh/tcsh
  • work no matter if the destination shell is sh/bash/csh/tcsh
  • work no matter the content of the environment variable. Specifically it should at least work for: \n * space ' " ? < > ! $ \ and any combination of those.

If you can find a better way to transfer the variable than quoting it, then that is fine, too.

Ole Tange
  • 5,099

2 Answers2

1

I now have a working model for all characters except \n:

sub shell_quote_scalar {
    # Quote the string so shell will not expand any special chars
    # Returns:
    #   string quoted with \ as needed by the shell
    my $a = shift;
    $a =~ s/([\002-\011\013-\032\\\#\?\`\(\)\{\}\[\]\*\>\<\~\|\; \"\!\$\&\'\202-\377])/\\$1/g;
    $a =~ s/[\n]/'\n'/g; # filenames with '\n' is quoted using \'
    return $a;
}

sub env_quote {
    my $v = shift;
    $v =~ s/([ \n\&\<\>\(\)\;\'\{\}\t\"\$\`\*\174\!\?\~])/\\$1/g;
    return $v;
}

my @qcsh = map { my $a=$_; "setenv $a " . env_quote($ENV{$a})  } @vars;
my @qbash = map { my $a=$_; "export $a=" . env_quote($ENV{$a}) } @vars;

$Global::envvar =
    join"",
    (q{echo $SHELL | grep -E "/t?csh" > /dev/null && }
     . join(" && ", @qcsh)
     . q{ || }
     . join(" && ", @qbash)
     .q{;});

print shell_quote_scalar($Global::envvar);
Ole Tange
  • 5,099
0

Can you just base64-encode the variable? The easiest way to deal with this is probably to simply treat the data as binary.

export QFOO=`echo $FOO | base64 --wrap=0`
ssh server "export FOO=`echo \"$QFOO\" | base64 --decode --wrap=0`; <command>"

You might have to fiddle with the quoting on the ssh line, but that's the gist of it. This should remove shell specifics with regards to quoting, but might introduce some with sub-commands (I'm not much familiar with anything outside of bash)

Darth Android
  • 38,658