google / goexpect

Expect for Go
BSD 3-Clause "New" or "Revised" License
759 stars 134 forks source link

`Expect` wouldn't work when GenericSpawn a `ssh user@address` command #62

Closed fangxlmr closed 3 years ago

fangxlmr commented 3 years ago

I use exec.Command("ssh", "user@addr") to create a system process, and use ExpectBatcher to send password expecting login automatically. However, this wouldn't work since the password promot comming neither from stdout nor stderr.

As said, OpenSSH client directly talks to /dev/tty which makes goexpect unable to receive any data from stdout/stderr, resulting in Expect not working.

This is not covered by current version, and it's pretty interesting to talk about.

Example code

package main

import (
    "fmt"
    "io"
    "os"
    "os/exec"
    "syscall"

    expect "github.com/google/goexpect"
)

func main() {
    cmd := exec.Command("ssh", "localhost")
    bs := []expect.Batcher{
        &expect.BExpT{R: "password: ", T: 2},
        &expect.BSnd{S: "some_password\n"},
    }

    pr1, pw1 := io.Pipe()
    pr2, pw2 := io.Pipe()
    cmd.Stdin, cmd.Stdout = pr1, pw2
    err := cmd.Start()
    if err != nil {
        closeDescriptors(pr1, pw1, pr2, pw2)
        panic(fmt.Sprintf("1: %s", err))
    }
    opt := &expect.GenOptions{
        In:    pw1,
        Out:   pr2,
        Wait:  func() error { return nil },
        Close: func() error { return cmd.Process.Kill() },
        Check: func() bool {
            if cmd.Process == nil {
                return false
            }
            // Sending Signal 0 to a process returns nil if
            // process can take a signal , something else if not.
            return cmd.Process.Signal(syscall.Signal(0)) == nil
        },
    }

    exp, _, err := expect.SpawnGeneric(opt, -1, expect.Verbose(true), expect.VerboseWriter(os.Stdout))
    if err != nil {
        closeDescriptors(pr1, pw1, pr2, pw2)
        panic(fmt.Sprintf("2: %s", err))
    }

    // not working since no data coming out from stdout/stderr
    res, err := exp.ExpectBatch(bs, -1)
    if err != nil {
        panic(fmt.Sprintf("3: %s", err))
    }
    for _, item := range res {
        fmt.Printf(">>> find res: %s\n", item.Output)
    }
    fmt.Println("done")
}

func closeDescriptors(closers ...io.Closer) {
    for _, closer := range closers {
        _ = closer.Close()
    }
}
fangxlmr commented 3 years ago

My bad. :( I should use SpawnWithArgs which employs a PTY already.