![SCP 的工作原理](https://linux22.com/image/1484149/SCP%20%E7%9A%84%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86.png)
WinSCP 工具能够sudo
在远程计算机上以scp
sudo 用户身份执行。这让我想到,命令行版本scp
也应该可以做到这一点。
那么,WinSCP 是如何执行这一操作的呢?
我不想通过具有用户暂存目录的解决方案。文件可能太大,我需要直接使用用户发布sudo
。结果文件被复制过来,但复制到我的本地目录,而不是 sudo 目录。
一个解决方案可能是通过 ssh 手动进行管道传输:
tar zcvf - MyBackups | ssh user@server "cat > /path/to/backup/foo.tgz"
我猜想可以自定义执行 sudo 如下:
cat local.txt | ssh user@server "sudo -u sudouser cat > ~/remote_file"
唯一的问题是,通过这种方式,我需要解决很多已经使用基础实现的负担scp
。出于这个和其他原因,我也不想使用这个解决方案。
我怎样才能让它scp
直接复制到远程,使用sudo
更改用户来获取其权限。
答案1
Scp 的工作原理是与远程服务器建立 ssh 连接,然后使用该连接scp
在远程系统上执行另一个命令。本地 scp 实例和远程实例通过 ssh 链接进行通信以发送或接收文件。
OpenSSH scp 特别构建了一个要在远程系统上运行的 scp 命令字符串。然后它启动 ssh 来运行该命令。最终调用的 ssh 命令相当于以下内容:
/path/to/ssh [ssh options...] "scp [scp options...]"
- /path/to/ssh 通常是 ssh 程序的内置路径。
- [ssh options...] 是 ssh 程序的一个或多个选项。
- “scp [scp options...]” 是一个包含 scp 命令及其在远程系统上运行的参数的单个参数。
OpenSSH scp 没有提供太多选项来改变远程 scp 命令的形式。没有办法让它调用除“scp”之外的其他命令,或者在“scp”部分前面插入“sudo”之类的命令。
正如您所注意到的,scp 确实有一个选项可以调用其他程序而不是 ssh。知道如何编写 ssh 包装程序并让 scp 调用包装程序而不是直接调用 ssh。包装程序必须检查其命令行参数以找到包含 scp 命令的参数,根据需要更改命令,然后使用更改后的命令行参数调用 ssh。
答案2
SCP 的工作原理
的内部工作scp
依赖于 的两种未记录形式scp
,即scp -f
和scp -t
。它们基本上是监听 stdin 并通过 stdout 生成数据的远程服务器。这些进程使用与 sftp 类似的协议。更多信息可以在Oracle 博客。
启动时scp
,它将首先启动 ssh 会话以启动scp -t
或scp -f
。然后,您的本地scp
进程将通过管道传输到 ssh 的 stdout 和 stdin 与远程scp
进程通信(完全加密),并将作为远程scp
会话的 stdin/stdout。
如何定制
该scp
命令提供了一个选项 -s
,允许您自定义用于设置远程scp
会话的程序。它应该处理 的所有典型选项ssh
。为了帮助调试它的外观,我们设置了一个仅转储参数的虚拟程序:
/tmp/scp-dump.sh:
echo $0 $*
我们将按如下方式执行:
scp -S /tmp/scp-dump.sh /tmp/dump.txt [email protected]:/tmp
我们得到了以下输出:
/home/me/dump.sh \
-x -oForwardAgent=no \
-oPermitLocalCommand=no \
-oClearAllForwardings=yes \
-l me -- server.com scp -t /tmp
为了便于阅读,添加了带有反斜杠的换行符。
这使我们有机会在将其发送给命令之前修改参数和命令ssh
。
设置 sudo
现在我们可以编写一个程序来根据需要修改参数,启动 ssh,并使用修改后的形式启动scp -t
。我们基本上会捕获参数,捕获我们想要在内部使用的参数,只添加额外的参数,然后进行更改。
/tmp/scp-sudo.sh
:
add_ssh_option() {
local option
if [ $# = 2 ]; then
option="$1 \"$2\""
else
option="$1"
fi
if [ -z "${ssh_options}" ]; then
ssh_options=${option}
else
ssh_options="${ssh_options} ${option}"
fi
}
parse_options() {
ssh_options=
local option
while [ $# > 0 ] ; do
case $1 in
-oSudoUser=* )
SSH_SUDO_USER=${option##*=}
shift
;;
-l ) ssh_user=$2 ; shift 2 ;;
-i ) add_ssh_option "$1" "$2" ; shift 2 ;;
-- ) shift ; break ;;
-* ) add_ssh_option "${1}" ; shift ;;
* ) break ;;
esac
done
ssh_host=$1
shift
ssh_command="$*"
}
parse_options "$@"
# To avoid intererence with Standard-In, we change this to
# Strict:
add_ssh_option "-oStrictHostKeyChecking=yes"
if [ -z "${SSH_SUDO_USER}" ]; then
# As before without sudo
cat | ssh $ssh_options -l $ssh_user $ssh_host $ssh_command
exit $?
else
# Send standard-in to the customized "scp -t" sink.
cat | ssh $ssh_options -l $ssh_user $ssh_host sudo -i -u ${SSH_SUDO_USER} $ssh_command
exit $?
fi
现在我们可以使用修改后的 scp 形式:
scp -oSudoUser=other \
-i /tmp/me-id_rsa \
/tmp/scp-sudo.sh \
/tmp/dump.txt \
[email protected]:/tmp
这对于将文件发送到远程来说很有效。但是当我检索远程文件时,它不知何故挂起了。当我中断会话(control-c)时,我确实看到传输成功了,但我不知何故有一个名为“0”的额外文件。不知道有没有人能帮我解决这个问题?
更好的选择:sftp
然而 sftp 要好得多。
- 它有一个
-S
选项 - 允许设置整个 ssh 命令行。我很难弄清楚并让它工作。我怀疑我不了解如何在 shell-ssh 包装器中正确绑定 stdin/stdout。 - 它有一个
-s
(小写)选项 - 允许仅设置负责设置远程服务器的子命令。我发现这更容易让它工作。 - 您可以根据 sftp 的手册页设置远程路径、远程文件权限、执行 put/get 等。
在这种情况下我们只需
sftp -s "sudo -u sudo-user -- /usr/libexec/openssh/sftp-server" [email protected]
这给了我快乐的“ sftp>
”提示
请注意,子程序有时位于不同的路径中,例如/usr/libexec/openssh/sftp-server
。我建议从文件Subsystem sftp
内部的“ ”字符串进行解析/etc/ssh/sshd_conf
?