输入ssh提示数据

时间:2017-06-09 04:53:23

标签: go ssh

所以我能够进入机器,但是我无法在提示中输入数据。

...
    sshConfig := &ssh.ClientConfig{
        User: user,
        Auth: []ssh.AuthMethod{
            ssh.Password(password),
        },
        HostKeyCallback: KeyPrint,
    }

    connection, err := ssh.Dial("tcp", connStr, sshConfig)
    if err != nil {

        log.Fatalln(err)
    }

    session, err := connection.NewSession()
    if err != nil {
        log.Fatalln(err)
    }

    modes := ssh.TerminalModes{
        ssh.ECHO:          0,     // disable echoing
        ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
        ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
    }

    if err := session.RequestPty("xterm", 80, 40, modes); err != nil {
        session.Close()
        log.Fatalf("request for pseudo terminal failed: %s", err)
    }

    stdin, err := session.StdinPipe()
    if err != nil {
        log.Fatalf("Unable to setup stdin for session: %v", err)
    }
    go io.Copy(stdin, os.Stdin)

    stdout, err := session.StdoutPipe()
    if err != nil {
        log.Fatalf("Unable to setup stdout for session: %v", err)
    }
    go io.Copy(os.Stdout, stdout)

    stderr, err := session.StderrPipe()
    if err != nil {
        log.Fatalf("Unable to setup stderr for session: %v", err)
    }
    go io.Copy(os.Stderr, stderr)

    // err = session.Run("1")

    session.Run("") // running it allows me to interact with the remote machines terminal in my own terminal.. session.Start("") exits and session.Wait() doesn't display the Welcome screen that normally greats users, and the prompt doesn't appear.

    stdin.Write([]byte("10000"))

    os.Stdin.WriteString("110000")

    // log.Fatalln(n, err)
    // os.Stdin.WriteString("1")
    // for {
    //  session.Run("1")
    //  go os.Stdin.WriteString("1")
    //  go stdin.Write([]byte("10000"))

    // }
...

上面的代码片段让我进入机器,机器的提示会显示在我的屏幕上,就像我手动输入一样。我可以输入shell ...但是我需要能够在shell中输入Go类型。我与之交互的提示是一个基于文本的游戏,所以我不能发出命令,例如没有(ls,echo,grep等等)我唯一允许传递的命令是数字。如何将输入发送到ssh会话?我尝试了很多方法,似乎没有任何输入。

我还附上了提示的屏幕截图,只是因为上面的描述在尝试描绘会话类型时是混乱的。

ssh session screen

更新 我想我已经找到了一种发送数据的方法,至少一次。

session, err := connection.NewSession()
    if err != nil {
        log.Fatalln(err)
    }

    // ---------------------------------

    modes := ssh.TerminalModes{
        ssh.ECHO:          0,     // disable echoing
        ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
        ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
    }

    if err := session.RequestPty("xterm", 80, 40, modes); err != nil {
        session.Close()
        log.Fatalf("request for pseudo terminal failed: %s", err)
    }

    stdin, err := session.StdinPipe()
    if err != nil {
        log.Fatalf("Unable to setup stdin for session: %v", err)
    }
    go io.Copy(stdin, os.Stdin)

    stdout, err := session.StdoutPipe()
    if err != nil {
        log.Fatalf("Unable to setup stdout for session: %v", err)
    }
    go io.Copy(os.Stdout, stdout)

    stderr, err := session.StderrPipe()
    if err != nil {
        log.Fatalf("Unable to setup stderr for session: %v", err)
    }
    go io.Copy(os.Stderr, stderr)

    go session.Start("")
    for {
        stdin.Write([]byte("10000000\n"))
        break
    }
    session.Wait()

我开始与go session.Start("")的会话时,请记住传递命令是没有意义的,因为我所做的就是输入数据以响应提示。 然后我在for循环结束时使用session.Wait() ...有点像使用channel和waitgroups时...在for循环中我用stdin.Write([]byte("10000000\n"))发送数据,其中重要的是要注意的是添加\n分隔符以模拟按键在键盘上输入..

如果有更好的方法来实现我想要实现的目标,请随意。接下来的步骤是解析stdout以获得响应并相应地进行回复。

1 个答案:

答案 0 :(得分:0)

空的Start将起作用,但是在ssh包中,Start,Run和Shell都是基本相同的调用。 Start(“cmd”)在shell中执行命令,Run(“cmd”)是对Start(“cmd”)的简单调用,然后为您调用Wait()(给出无并发执行的感觉)和Shell打开一个shell(如Start),但没有传递命令。它实际上只有六个,六个,但使用Shell()可能是最简洁的方法。

另外,请记住,Start()和Shell()都可以利用并发性,而无需显式调用“go”。它可以释放额外的毫秒左右来手动调用并发,但如果这对你没有意义,那么你应该能够放弃它。 Start和Shell的自动并发是Wait()和Run(“cmd”)方法的原因。

如果您不需要解释输出(stdout / err),那么您可以在没有pipe()调用或io.Copy()的情况下映射它们,这样更容易,更有效。我在下面的示例中这样做了,但请记住,如果您确实解释了输出,那么使用Pipe()可能更容易。在大多数情况下,您可以顺序发送多个命令(或数字),而无需读取提示,但有些事情(如密码提示)会清除输入缓冲区。如果您遇到这种情况,那么您需要阅读Stdout以查找提示或利用goexpect(https://github.com/google/goexpect)之类的期望工具。 Go有几个类似于期望的软件包,但是这个软件包来自谷歌,并且(截至本文)仍然是最近维护的。

StdinPipe()暴露了一个可以在没有io.Copy()的情况下使用的writeCloser,这应该更有效。

写入StdinPipe()的for循环应该允许你输入几个命令(或者在你的情况下,数组)...作为一个例子,我有来自os的读取命令(数字等)。 Args并迭代它们。

最后,您应该添加一个session.Close()以便健康完成(您已经调用了错误)。也就是说,这就是我的建议(基于你的上一个例子):

modes := ssh.TerminalModes{
    ssh.ECHO:          0,     // disable echoing
    ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
    ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
}

if err := session.RequestPty("xterm", 80, 40, modes); err != nil {
    session.Close()
    log.Fatalf("request for pseudo terminal failed: %s", err)
}
defer session.Close()

stdin, err := session.StdinPipe()
if err != nil {
    log.Fatalf("Unable to setup stdin for session: %v", err)
}

session.Stdout = os.Stdout
session.Stderr = os.Stderr

err = session.Shell()
if err != nil {
    log.Fatalf("Unable to setup stdin for session: %v", err)
}
for _, cmd := range os.Args {
    stdin.Write([]byte(cmd + "\n"))
}

session.Wait()

哦,还有一件需要注意的事项是,Wait()方法依赖于一个未经检查的通道,该通道从命令中检索exitStatus,这在极少数情况下会挂起(在连接到cisco gear时尤其有问题,但是和其他人一起发生)。如果您发现遇到这种情况(或者您只是要小心),您可能希望将Wait()包含在某种超时方法中,例如通过并发调用Wait()并通过通道读取响应这可以随着时间的推移而得到.After()(我可以提供一个例子,如果这会有所帮助)。