【问题标题】:ssh port forwarding ("ssh -fNL") doesn't work via expect spawn to automatically provide passwordssh 端口转发(“ssh -fNL”)无法通过期望 spawn 自动提供密码
【发布时间】:2021-01-29 08:38:11
【问题描述】:

我知道做端口转发,命令是ssh -L。我还使用其他选项来装饰它。例如,最终的完整命令可能如下所示ssh -fCNL *:10000:127.0.0.1:10001 127.0.0.1。输入密码后一切正常。

然后,因为需要转发的端口不止一个,我决定把工作交给shell脚本,用expect(tcl)提供密码(都一样)。

虽然对expect没有深入了解,但还是借助互联网成功编写了代码。该脚本成功生成 ssh 并提供正确的密码。但是当我尝试使用ps -ef | grep ssh 和netstat -anp | grep 10000 进行检查时,我最终发现没有这样的过程。

我给 ssh 提供了-v 选项,输出似乎没问题。

那么问题出在哪里?我已经通过 Internet 进行了搜索,但大多数问题都与端口转发无关。我只是想让脚本自动提供密码,我不确定使用期望是否合适。

这里是脚本。

#!/bin/sh

# Port Forwarding

# set -x

## function definition
connection ()
{
    ps -ef | grep -v grep | grep ssh | grep $1 | grep $2 > /dev/null
    if [ $? -eq 0 ] ; then
        echo "forward $1 -> $2 done"
        exit 0
    fi

    # ssh-keygen -f "$HOME/.ssh/known_hosts" -R "127.0.0.1"

    /usr/bin/expect << EOF
set timeout 30
spawn /usr/bin/ssh -v -fCNL *:$1:127.0.0.1:$2 127.0.0.1
expect {
"yes/no" {send "yes\r" ; exp_continue}
"password:" {send "1234567\r" ; exp_continue}
eof
}
catch wait result
exit [lindex \$result 3]
EOF
    echo "expect ssh return $?"
    echo "forward $1 -> $2 done"
}

## check expect available
which expect > /dev/null
if [ $? -ne 0 ] ; then
    echo "command expect not available"
    exit 1
fi

login_port="10000"
forward_port="10001"

## check whether the number of elements is equal
login_port_num=$(echo ${login_port} | wc -w)
forward_port_num=$(echo ${forward_port} | wc -w)
if [ ${login_port_num} -ne ${forward_port_num} ] ; then
    echo "The numbers of login ports and forward ports are not equal"
    exit 1
fi
port_num=${login_port_num}

## provide pair of arguments to ssh main function
index=1
while [ ${index} -le ${port_num} ] ; do
    login_p=$(echo ${login_port} | awk '{print $'$index'}')
    forward_p=$(echo ${forward_port} | awk '{print $'$index'}')
    connection ${login_p} ${forward_p}
    index=$((index + 1))
done

这里是脚本的输出

spawn /usr/bin/ssh -v -fCNL *:10000:127.0.0.1:10001 127.0.0.1
OpenSSH_7.2p2 Ubuntu-4ubuntu2.10, OpenSSL 1.0.2g  1 Mar 2016
...
debug1: Next authentication method: password
wang@127.0.0.1's password: 
debug1: Enabling compression at level 6.
debug1: Authentication succeeded (password).
Authenticated to 127.0.0.1 ([127.0.0.1]:22).
debug1: Local connections to *:10000 forwarded to remote address 127.0.0.1:10001
debug1: Local forwarding listening on 0.0.0.0 port 10000.
debug1: channel 0: new [port listener]
debug1: Local forwarding listening on :: port 10000.
debug1: channel 1: new [port listener]
debug1: Requesting no-more-sessions@openssh.com
debug1: forking to background
expect ssh return 0
forward 10000 -> 10001 done

【问题讨论】:

  • 不要使用密码。使用键。

标签: linux ssh sh expect


【解决方案1】:

这应该适合你:

spawn -ignore SIGHUP ssh -f ...

更新:

另一种解决方法是:

spawn bash -c "ssh -f ...; sleep 1"

UPDATE 2(稍微解释一下):

ssh -f 调用daemon() 使自己成为一个守护进程。参见源代码中的ssh.c:

/* Do fork() after authentication. Used by "ssh -f" */
static void
fork_postauth(void)
{
        if (need_controlpersist_detach)
                control_persist_detach();
        debug("forking to background");
        fork_after_authentication_flag = 0;
        if (daemon(1, 1) == -1)
                fatal("daemon() failed: %.200s", strerror(errno));
}

daemon() 的实现类似于this:

int
daemon(int nochdir, int noclose)
{
        int fd;

        switch (fork()) {
        case -1:
                return (-1);
        case 0:
                break;
        default:
                _exit(0);
        }

        if (setsid() == -1)
                return (-1);

        if (!nochdir)
                (void)chdir("/");

        if (!noclose && (fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) {
                (void)dup2(fd, STDIN_FILENO);
                (void)dup2(fd, STDOUT_FILENO);
                (void)dup2(fd, STDERR_FILENO);
                if (fd > 2)
                        (void)close (fd);
        }
        return (0);
}

在父进程中的_exit() 和子进程中的setsid() 之间存在竞争条件(不确定它是否正确)。这里_exit() 总是首先完成,因为“函数_exit() 会立即 终止调用进程”并且setsid() 的权重更大。所以当父进程退出时,setsid() 还没有生效,子进程仍然和父进程在同一个会话中。根据apue book(我指的是2005年版,第10章:信号),SIGHUP“也会产生如果会话负责人终止。在这种情况下,发送信号到前台进程组中的每个进程。”

简而言之:

  • Expect 分配一个 pty 并在该 pty 上运行 ssh。在这里,ssh 将在一个新会话中运行并成为会话负责人。
  • ssh -f 致电 daemon()。父进程(会话负责人)调用_exit()。此时,子进程仍在会话中,所以它会得到SIGHUP,其默认行为是终止进程。

变通方法的工作原理:

  • nohup 方式 (spawn -ignore SIGHUP) 是明确要求进程忽略 SIGHUP,因此它不会被终止。
  • 对于bash -c 'sshh -f ...; sleep 1',bash 将成为会话负责人,sleep 1 最终防止会话负责人过早退出。所以在sleep 1 之后,子ssh 进程的setsid() 已经完成并且子ssh 已经在一个新的进程会话中。

更新 3:

您可以通过以下修改(在ssh.c)编译ssh并验证:

static int
my_daemon(int nochdir, int noclose)
{
    int fd;

    switch (fork()) {
    case -1:
        return (-1);
    case 0:
        break;
    default:
        // wait a while for child's setsid() to complete
        sleep(1);
//      ^^^^^^^^
        _exit(0);
    }

    if (setsid() == -1)
        return (-1);

    if (!nochdir)
        (void)chdir("/");

    if (!noclose && (fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) {
        (void)dup2(fd, STDIN_FILENO);
        (void)dup2(fd, STDOUT_FILENO);
        (void)dup2(fd, STDERR_FILENO);
        if (fd > 2)
            (void)close (fd);
    }
    return (0);
}

/* Do fork() after authentication. Used by "ssh -f" */
static void
fork_postauth(void)
{
    if (need_controlpersist_detach)
        control_persist_detach();
    debug("forking to background");
    fork_after_authentication_flag = 0;
    if (my_daemon(1, 1) == -1)
//      ^^^^^^^^^
        fatal("my_daemon() failed: %.200s", strerror(errno));
}

【讨论】:

  • 哇,工作就像一个魅力!让我猜猜。 ssh 进程由 expect 拥有。当 expect 退出时,SIGHUP 也被发送到 ssh 进程,因此它被关闭。如果这是正确的,你怎么知道?如果期望手册中提到了这一点,请原谅我,但我快速搜索并没有结果。
  • 非常感谢!显然我需要更多的学习才能完全理解这些东西哈哈。但我很好奇,一个人怎么能如此深入地探索源代码并理解其机制。无论如何,这是一个巨大的鼓励,促使我了解更多!
猜你喜欢
  • 1970-01-01
  • 2013-08-25
  • 2017-08-03
  • 2018-10-06
  • 2013-02-12
  • 1970-01-01
  • 2018-10-24
  • 1970-01-01
  • 2013-01-19
相关资源
最近更新 更多