Как выполнить произвольную простую команду по ssh, не зная логин shell удаленного пользователя?

Я отредактировал/etc/rc.conf и добавил mysql_args строку ниже:

mysql_enable="YES"
mysql_args="--character-set-server=utf8"

Это работает, и я вижу, что набор символов сервера обновляется.

26
15.12.2017, 19:45
2 ответа

Я не думаю, что какая-либо реализация ssh имеет родной способ передачи команды от клиента к серверу без участия оболочки.

Теперь всё может стать проще, если вы скажете удалённой оболочке запустить только определённый интерпретатор (например, sh, для которого мы знаем ожидаемый синтаксис) и дать код для выполнения другим способом.

Это другое средство может быть, например, стандартным входом или переменной окружения -.

Когда ни то, ни другое не может быть использовано, я предлагаю третье решение, приведенное ниже.

Использование stdin

Если вам не нужно подавать данные на удаленную команду, это самое простое решение.

Если вы знаете, что на удалённом хосте есть команда xargs, которая поддерживает опцию -0 и команда не слишком большая, вы можете сделать:

printf '%s\0' "${cmd[@]}" | ssh user@host 'xargs -0 env --'

Что командная строка xargs -0 env -- интерпретируется одинаково со всеми этими семействами оболочек. xargs считывает нулевой разделенный список аргументов по stdin и передает их в качестве аргументов в env. Это предполагает, что первый аргумент (имя команды) не содержит символов =.

Или вы можете использовать sh на удаленном хосте после того, как процитировали каждый элемент, используя sh цитирующий синтаксис.

shquote() {
  LC_ALL=C awk -v q=\' '
    BEGIN{
      for (i=1; i<ARGC; i++) {
        gsub(q, q "\\" q q, ARGV[i])
        printf "%s ", q ARGV[i] q
      }
      print ""
    }' "$@"
}
shquote "${cmd[@]}" | ssh user@host sh

Используя переменные окружения

Теперь, если вам нужно передать некоторые данные от клиента на stdin удаленной команды, вышеописанное решение не сработает.

Некоторые установки сервера ssh, однако, позволяют передавать произвольные переменные окружения от клиента к серверу. Например, многие установки openssh на системах на базе Debian позволяют передавать переменные, имя которых начинается с LC_.

В этих случаях, например, вы можете получить переменную LC_CODE, содержащую код shquoted sh, как описано выше, и запустить sh -c 'eval "$LC_CODE" на удалённом хосте после того, как вы скажете клиенту передать эту переменную (опять же, это командная строка, которая интерпретируется одинаково в каждой оболочке командной строки):

LC_CODE=$(shquote "${cmd[@]}") ssh -o SendEnv=LC_CODE user@host '
  sh -c '\''eval "$LC_CODE"'\'

Сборка командной строки, совместимой со всеми семействами оболочек

Если ни одна из вышеперечисленных опций не приемлема (потому что вам нужно, чтобы stdin и sshd не принимали никаких переменных, или потому что вам нужно общее решение), то вам нужно подготовить командную строку для удаленного хоста, совместимую со всеми поддерживаемыми оболочками командной строки.

Это особенно сложно, потому что все эти оболочки (Bourne, csh, rc, es, fish) имеют свой собственный разный синтаксис, и, в частности, разные механизмы цитирования, а некоторые из них имеют ограничения, с которыми тяжело работать.

Вот решение, которое я придумал, я описываю его далее:

#! /usr/bin/perl
my $arg, @ssh, $preamble =
q{printf '%.0s' "'\";set x=\! b=\\\\;setenv n "\
";set q=\';printf %.0s "\""'"';q='''';n=``()echo;x=!;b='\'
printf '%.0s' '\'';set b \\\\;set x !;set -x n \n;set q \'
printf '%.0s' '\'' #'"\"'";export n;x=!;b=\\\\;IFS=.;set `echo;echo \.`;n=$1 IFS= q=\'
};

@ssh = ('ssh');
while ($arg = shift @ARGV and $arg ne '--') {
  push @ssh, $arg;
}

if (@ARGV) {
  for (@ARGV) {
    s/'/'\$q\$b\$q\$q'/g;
    s/\n/'\$q'\$n'\$q'/g;
    s/!/'\$x'/g;
    s/\\/'\$b'/g;
    $_ = "\$q'$_'\$q";
  }
  push @ssh, "${preamble}exec sh -c 'IFS=;exec '" . join "' '", @ARGV;
}

exec @ssh;

Это perl оберточный скрипт вокруг ssh. Я называю его sexec. Вы называете его как:

sexec [ssh-options] user@host -- cmd and its args

так что в вашем примере:

sexec user@host -- "${cmd[@]}"

А обертка превращает cmd и его аргументы в командную строку, которую все оболочки интерпретируют как вызов cmd со своими аргументами (не считая их содержимого).

Ограничения:

  • Преамбула и способ кавычки команды означают, что удалённая командная строка заканчивается значительно большим размером, что означает, что ограничение на максимальный размер командной строки будет достигнуто раньше.
  • Я только проверил: Bourne shell (из heirloom toolchest), dash, bash, zsh, mksh, lksh, yash, ksh93, rc, es, akanga, csh, tcsh, fish, найденный на недавней системе Debian и /bin/sh, /usr/bin/ksh, /bin/csh и /usr/xpg4/bin/sh на Solaris 10.
  • Если yash является оболочкой удалённого входа, вы не можете передать команду, аргументы которой содержат недопустимые символы, но это ограничение в yash, с которым вы всё равно не можете работать.
  • Некоторые оболочки, такие как csh или bash, читают некоторые стартовые файлы при вызове по ssh. Мы предполагаем, что они не сильно меняют поведение, так что преамбула все равно работает.
  • кроме sh, также предполагается, что удалённая система имеет команду printf.

Чтобы понять, как это работает, необходимо знать, как работает цитирование в различных оболочках:

  • Bourne: '...' - сильные кавычки с нет специального символа. "..." - это слабые кавычки, в которых " можно избежать обратного слеша.
  • csh. Так же, как и Борн, за исключением того, что " нельзя избежать внутри "...". Также должен быть введен символ новой строки с префиксом обратной косой чертой. И ! вызывает проблемы даже внутри одиночных кавычек.
  • rc. Единственными кавычками являются '...' (сильная). Одиночная котировка внутри одиночных котировок вводится как '' (как '...'...'). Двойные кавычки или обратные слеши не являются специальными.
  • es. То же самое, что и rc, за исключением того, что вне кавычек обратная косая черта может экранироваться от одиночной кавычки.
  • fish: то же самое, что и Борн, за исключением того, что обратный слеш ускользает ' внутри '...'.

При всех этих противоречиях легко заметить, что нельзя надежно процитировать аргументы командной строки, чтобы они работали со всеми оболочками.

Использование одинарных кавычек, как в:

'foo' 'bar'

работает во всех, кроме:

'echo' 'It'\''s'

не сработает в rc.

'echo' 'foo
bar'

не будет работать в csh.

'echo' 'foo\'

не сработает в rc.

Однако мы должны быть в состоянии работать над большинством из этих проблем, если нам удастся сохранить эти проблемные символы в переменных, таких как обратный слеш в $b, одиночная кавычка в $q, newline в $n! в $x для расширения истории csh) в независимом от оболочки способе.

'echo' 'It'$q's'
'echo' 'foo'$b

будет работать во всех оболочках. Но для csh это все равно не сработает. Если $n содержит newline, то в csh его нужно записать как $n:q, чтобы он расширился до новой строки, и это не будет работать для других оболочек. Итак, вместо этого мы вызываем sh и заставляем sh развернуть эти $n. Это также означает, что необходимо сделать два уровня кавычки, один для оболочки удаленного входа, а другой для sh.

Преамбула $ в этом коде - самая хитрая часть. Она использует различные правила цитирования во всех оболочках, чтобы некоторые участки кода интерпретировались только одной из оболочек (в то время как для других они закомментированы), каждая из которых просто определяет эти $b, $q, $n, $x переменные для их соответствующей оболочки.

Вот код оболочки, который будет интерпретироваться оболочкой входа удаленного пользователя на хосте для вашего примера:

printf '%.0s' "'\";set x=\! b=\\;setenv n "\
";set q=\';printf %.0s "\""'"';q='''';n=``()echo;x=!;b='\'
printf '%.0s' '\'';set b \\;set x !;set -x n \n;set q \'
printf '%.0s' '\'' #'"\"'";export n;x=!;b=\\;IFS=.;set `echo;echo \.`;n=$1 IFS= q=\'
exec sh -c 'IFS=;exec '$q'printf'$q' '$q'<%s>'$b'n'$q' '$q'arg with $and spaces'$q' '$q''$q' '$q'even'$q'$n'$q'* * *'$q'$n'$q'newlines'$q' '$q'and '$q$b$q$q'single quotes'$q$b$q$q''$q' '$q''$x''$x''$q

Этот код заканчивается выполнением одной и той же команды при интерпретации любой из поддерживаемых оболочек.

19
27.01.2020, 19:40

tl; dr

ssh USER@HOST -p PORT $(printf "%q" "cmd") $(printf "%q" "arg1") \
    $(printf "%q" "arg2")

Для более детального решения прочтите комментарии и просмотрите другой ответ .

описание

Что ж, мое решение не будет работать с оболочками, отличными от bash . Но если предположить, что с другой стороны это bash , все станет проще. Моя идея состоит в том, чтобы повторно использовать printf "% q" для экранирования. Также, как правило, удобнее иметь на другом конце сценарий, принимающий аргументы. Но если команда короткая, возможно, ее можно встроить. Вот несколько примеров функций для использования в сценариях:

local.sh :

#!/usr/bin/env bash
set -eu

ssh_run() {
    local user_host_port=($(echo "$1" | tr '@:' ' '))
    local user=${user_host_port[0]}
    local host=${user_host_port[1]}
    local port=${user_host_port[2]-22}
    shift 1
    local cmd=("$@")
    local a qcmd=()
    for a in ${cmd[@]+"${cmd[@]}"}; do
        qcmd+=("$(printf "%q" "$a")")
    done
    ssh "$user"@"$host" -p "$port" ${qcmd[@]+"${qcmd[@]}"}
}

ssh_cmd() {
    local user_host_port=$1
    local cmd=$2
    shift 2
    local args=("$@")
    ssh_run "$user_host_port" bash -lc "$cmd" - ${args[@]+"${args[@]}"}
}

ssh_run USER@HOST ./remote.sh "1  '  \"  2" '3  '\''  "  4'
ssh_cmd USER@HOST:22 "for a; do echo \"'\$a'\"; done" "1  '  \"  2" '3  '\''  "  4'
ssh_cmd USER@HOST:22 'for a; do echo "$a"; done' '1  "2' "3'  4"

remote.sh :

#!/usr/bin/env bash
set -eu
for a; do
    echo "'$a'"
done

Вывод:

'1  '  "  2'
'3  '  "  4'
'1  '  "  2'
'3  '  "  4'
1  "2
3'  4

В качестве альтернативы вы можете выполнить printf сами, если знаете, что делаете:

ssh USER@HOST ./1.sh '"1  '\''  \"  2"' '"3  '\''  \"  4"'
0
27.01.2020, 19:40

Теги

Похожие вопросы