【问题标题】:Entering ssh prompt data输入 ssh 提示数据
【发布时间】:2023-03-18 20:05:02
【问题描述】:

所以我可以通过 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"))

    // }
...

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

我还附上了提示的屏幕截图,以防上面的描述在试图描述会话类型时造成混淆。

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

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()...有点像使用通道和等待组时所做的那样......在 for 循环中我使用stdin.Write([]byte("10000000\n")) 发送数据,其中需要注意的重要一点是添加@987654329 @delimiter 模拟在键盘上敲击回车..

如果有任何更好的方法来实现我想要实现的目标,请随意。接下来的步骤是解析标准输出以获取响应并做出相应的回复。

【问题讨论】:

  • 您不能只使用fmt.Scanln(...) 接收用户输入然后从程序中执行您需要的任何命令吗?
  • @acidic 这是一个 ssh 会话。 ssh 会话内部是一个正在运行的程序。这不是我的程序在运行。我不是在等待用户输入。我是用户,连接到远程终端。目标是解析标准输出,确定我的算法应该如何响应,然后提交答案以在 30 秒内赢得 10 场比赛。这意味着我不能只是远程进入并自己在键盘上输入。我的程序通过 ssh 登录到远程机器,必须解析来自标准输出的响应并为我输入数字。所以没有 fmt 扫描。
  • @acidic 我还看到你可能从哪里得到了使用扫描仪的想法,stackoverflow.com/q/23019890/4639336,但是在这种情况下,我没有输入 shell 命令。我只是通过 ssh 与游戏互动。所以终端命令对它不起作用。我需要一种将按键数据发送到游戏外壳的方法。换句话说,当我运行我的代码时,我可以在我的 shell 上与游戏进行交互,就像我手动 ssh 进入它一样。与其让我与我的终端和键盘进行交互,不如 go 与它进行交互。你知道,像 selenium 一样自动化。
  • 哦,是的,我明白了。我正在做一个项目,过去几天我也一直在使用 SSH 包,我想我明白你的问题是什么。我有同样的问题,我想。我的 go 程序运行一个脚本,但我想用sudo ... 运行它,唯一的问题是,在以 sudo 执行我的命令的中途运行sudo ... 之后,它会要求用户输入密码但没有完成执行。我不确定在发送命令后如何输入密码。这与您遇到的问题相同吗?
  • @acidic 不是 100%,但是我上面发布的链接显示了您如何输入密码,或者我帖子中的代码显示了您如何提供密码,至少也许您可以使用那些为您自己的问题找到解决方案的人。我的更像......我很好。我收到来自服务器的提示。服务器要求我输入一个数字......这就是它下地狱的地方。我还没弄清楚如何用 go 输入数字。

标签: go ssh


【解决方案1】:

空的 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() 可能更容易。在大多数情况下,您可以顺序发送多个命令(或数字)而无需阅读提示,但有些事情(如密码提示)会清除输入缓冲区。如果您遇到这种情况,那么您需要阅读标准输出以找到您的提示或利用诸如 goexpect (https://github.com/google/goexpect) 之类的期望工具。 Go 有几个类似于 expect 的包,但这个来自 Google,并且(截至本文发布)最近仍在维护。

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

写入 StdinPipe() 的 for 循环应该允许您输入多个命令(或者在您的情况下,输入一组数字)......例如,我有这个从 os.read 读取命令(数字等)。 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() 并通过通道读取响应可以与 time.After() 一起使用(如果有帮助,我可以提供一个示例)。

【讨论】:

    猜你喜欢
    • 2018-02-14
    • 2016-04-08
    • 2022-01-06
    • 2014-02-28
    • 2013-04-03
    • 2012-08-11
    • 1970-01-01
    • 1970-01-01
    • 2023-03-28
    相关资源
    最近更新 更多