簡體   English   中英

ssh 端口轉發(“ssh -fNL”)無法通過 expect spawn 自動提供密碼來工作

[英]ssh port forwarding ("ssh -fNL") doesn't work via expect spawn to automatically provide password

我知道要進行端口轉發,命令是ssh -L 我還使用其他選項來裝飾它。 因此,例如,最終的完整命令可能如下所示ssh -fCNL *:10000:127.0.0.1:10001 127.0.0.1 輸入密碼后一切正常。

然后,因為不止一個端口需要轉發,我決定把工作留給shell腳本並使用expect(tcl)來提供密碼(都一樣)。

雖然對expect沒有深入的了解,但還是借助互聯網寫出了代碼。 該腳本成功生成 ssh 並提供正確的密碼。 但是當我嘗試使用ps -ef | grep ssh檢查時,我最終發現沒有這樣的過程ps -ef | grep ssh ps -ef | grep sshnetstat -anp | grep 10000 netstat -anp | grep 10000

我給 ssh 提供了-v選項,輸出似乎沒問題。

那么問題出在哪里呢? 我已經通過 Internet 進行了搜索,但大多數問題都與端口轉發無關。 我不確定使用 expect 是否合適,而我只想讓腳本自動提供密碼。

這里是腳本。

#!/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

這應該適合你:

spawn -ignore SIGHUP ssh -f ...

更新:

另一種解決方法是:

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

更新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));
}

守護進程()是等來實現這樣

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 -c 'sshh -f ...; sleep 1'bash將成為會話領導者,最后sleep 1可防止會話領導者過早退出。 所以在sleep 1 ,子ssh進程的setsid()已經完成並且子ssh已經在一個新的進程 session 中

更新 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));
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM