无法在 Go 中将 stdin 转发到 sudo 子进程

Cannot forward stdin to sudo subprocess in Go

提问人:nagylzs 提问时间:10/16/2023 更新时间:10/16/2023 访问量:32

问:

下面是一个最小的工作示例,演示了这个问题。

package main

import (
    "bufio"
    "fmt"
    "io"
    "log"
    "os"
    "os/exec"
)

func main() {
    //cmd := exec.Command("sudo", "tail", "-10", "/var/log/syslog")
    cmd := exec.Command("cat")
    stdin, err := cmd.StdinPipe()
    if err != nil {
        log.Fatal(err)
    }
    stdout, err := cmd.StdoutPipe()
    if err != nil {
        log.Fatal(err)
    }
    stderr, err := cmd.StderrPipe()
    if err != nil {
        log.Fatal(err)
    }
    if err := cmd.Start(); err != nil {
        log.Fatal(err)
    }
    ForwardStdIn(stdin)
    go ForwardStdOut(stdout)
    go ForwardStdErr(stderr)
    err = cmd.Wait()
    if err == nil {
        os.Exit(0)
    }
    if ee, ok := err.(*exec.ExitError); ok {
        fmt.Println(ee.Error())
        os.Exit(ee.ExitCode())
    } else {
        log.Fatal(err)
    }

}

func ForwardStdOut(stdout io.ReadCloser) {
    scanner := bufio.NewScanner(stdout)
    for scanner.Scan() {
        line := scanner.Text()
        _, err := fmt.Fprint(os.Stdout, line+"\n")
        if err != nil {
            log.Fatal(err)
        }
    }
}

func ForwardStdErr(stderr io.ReadCloser) {
    scanner := bufio.NewScanner(stderr)
    for scanner.Scan() {
        line := scanner.Text()
        _, err := fmt.Fprint(os.Stderr, line+"\n")
        if err != nil {
            log.Fatal(err)
        }
    }
}

func readStdin(ch chan []byte) {
    for {
        buf := make([]byte, 4096)
        n, err := os.Stdin.Read(buf)
        if n == 0 && err == io.EOF {
            close(ch)
            //fmt.Println("stdin closed #1")
            break
        }
        //fmt.Printf("read %v bytes\n", n)
        if n > 0 {
            ch <- buf[:n]
        }
    }
}

func writeStdin(stdin io.WriteCloser, ch chan []byte) {
    for {
        buf, ok := <-ch
        if !ok {
            err := stdin.Close()
            //fmt.Println("stdin closed #2")
            if err != nil {
                log.Fatal(err)
            }
            break
        }
        pos := 0
        for pos < len(buf) {
            n, err := stdin.Write(buf[pos:])
            if err != nil {
                log.Fatal(err)
            }
            //fmt.Printf("wrote %v bytes\n", n)
            pos += n
        }
    }
}

func ForwardStdIn(stdin io.WriteCloser) {
    ch := make(chan []byte, 2)
    go readStdin(ch)
    go writeStdin(stdin, ch)
}

如果我运行这个程序,那么我写到 stdin 中的任何内容都会出现在 stdout 上。

但是如果我替换,那么它就不起作用。首先,它要求我输入密码。我输入密码,按 .调试器显示密码已发送到 的 stdin,但不会发生其他任何事情。没有错误消息,并且不会生成进一步的输出。catsudo tail -10 /var/log/syslogENTERsudo

我最初认为可能需要一个功能齐全的 pty(终端)才能工作,但事实并非如此。我可以这样在没有 tty 的情况下运行:sudosudo

sudo tail -10 /var/log/syslog </dev/null |& cat

而且效果很好。

问题是这样的:为什么上面的代码对 ?如何更改此代码以使其与(或使用 askpass 的任何其他程序)一起使用。catsudosudo

Linux Go 进程 IO

评论

0赞 nagylzs 10/16/2023
如果我在密码后按两次,那么 sudo 会写“对不起,再试一次。它看起来直到第二个“\n”才接收到数据,然后它认为第一个\n是密码的一部分。这是怎么回事?ENTER
0赞 nagylzs 10/16/2023
sudo -S tail -10 /var/log/syslog也有效,似乎默认的密码读取方法需要终端;但由于某种原因,sudo 无法检测到它没有连接到终端 github.com/sudo-project/sudo/blob/main/src/tgetpass.c#L141???

答: 暂无答案