diff --git a/npiperelay.go b/npiperelay.go index 08cbaab..15f72ee 100644 --- a/npiperelay.go +++ b/npiperelay.go @@ -1,18 +1,22 @@ package main import ( + "bufio" + "errors" "flag" + "fmt" "io" "log" "os" + "strconv" "sync" - "syscall" "time" "golang.org/x/sys/windows" ) -const cERROR_PIPE_NOT_CONNECTED syscall.Errno = 233 +// How long to sleep between failures while polling +const pollTimeout = 200 * time.Millisecond var ( poll = flag.Bool("p", false, "poll until the the named pipe exists") @@ -20,6 +24,7 @@ var ( closeOnEOF = flag.Bool("ep", false, "terminate on EOF reading from the pipe, even if there is more data to write") closeOnStdinEOF = flag.Bool("ei", false, "terminate on EOF reading from stdin, even if there is more data to write") verbose = flag.Bool("v", false, "verbose output on stderr") + assuan = flag.Bool("a", false, "treat the target as a libassuan file socket (Used by GnuPG)") ) func dialPipe(p string, poll bool) (*overlappedFile, error) { @@ -33,13 +38,107 @@ func dialPipe(p string, poll bool) (*overlappedFile, error) { return newOverlappedFile(h), nil } if poll && os.IsNotExist(err) { - time.Sleep(200 * time.Millisecond) + time.Sleep(pollTimeout) continue } return nil, &os.PathError{Path: p, Op: "open", Err: err} } } +func dialPort(p int, poll bool) (*overlappedFile, error) { + if p < 0 || p > 65535 { + return nil, errors.New("Invalid port value") + } + + h, err := windows.Socket(windows.AF_INET, windows.SOCK_STREAM, 0) + if err != nil { + return nil, err + } + + // Create a SockaddrInet4 for connecting to + sa := &windows.SockaddrInet4{Addr: [4]byte{0x7F, 0x00, 0x00, 0x01}, Port: p} + + // Bind to a randomly assigned local port + err = windows.Bind(h, &windows.SockaddrInet4{}) + if err != nil { + return nil, err + } + + // Wrap our socket up to be properly handled + conn := newOverlappedFile(h) + + // Connect to the LibAssuan socket using overlapped ConnectEx operation + _, err = conn.asyncIo(func(h windows.Handle, n *uint32, o *windows.Overlapped) error { + return windows.ConnectEx(h, sa, nil, 0, nil, o) + }) + if err != nil { + return nil, err + } + + return conn, nil +} + +// LibAssaun file socket: Attempt to read contents of the target file and connect to a TCP port +func dialAssuan(p string, poll bool) (*overlappedFile, error) { + pipeConn, err := dialPipe(p, poll) + if err != nil { + return nil, err + } + + var port int + var nonce [16]byte + + reader := bufio.NewReader(pipeConn) + + // Read the target port number from the first line + tmp, _, err := reader.ReadLine() + if err == nil { + port, err = strconv.Atoi(string(tmp)) + } + if err != nil { + return nil, err + } + + // Read the rest of the nonce from the file + n, err := reader.Read(nonce[:]) + if err != nil { + return nil, err + } + + if n != 16 { + err = fmt.Errorf("Read incorrect number of bytes for nonce. Expected 16, got %d (0x%X)", n, nonce) + return nil, err + } + + if *verbose { + log.Printf("Port: %d, Nonce: %X", port, nonce) + } + + pipeConn.Close() + + for { + // Try to connect to the libassaun TCP socket hosted on localhost + conn, err := dialPort(port, poll) + + if poll && (err == windows.WSAETIMEDOUT || err == windows.WSAECONNREFUSED || err == windows.WSAENETUNREACH || err == windows.ERROR_CONNECTION_REFUSED) { + time.Sleep(pollTimeout) + continue + } + + if err != nil { + err = os.NewSyscallError("ConnectEx", err) + return nil, err + } + + _, err = conn.Write(nonce[:]) + if err != nil { + return nil, err + } + + return conn, nil + } +} + func underlyingError(err error) error { if serr, ok := err.(*os.SyscallError); ok { return serr.Err @@ -59,7 +158,14 @@ func main() { log.Println("connecting to", args[0]) } - conn, err := dialPipe(args[0], *poll) + var conn *overlappedFile + var err error + + if !*assuan { + conn, err = dialPipe(args[0], *poll) + } else { + conn, err = dialAssuan(args[0], *poll) + } if err != nil { log.Fatalln(err) } @@ -94,7 +200,7 @@ func main() { }() _, err = io.Copy(os.Stdout, conn) - if underlyingError(err) == windows.ERROR_BROKEN_PIPE || underlyingError(err) == cERROR_PIPE_NOT_CONNECTED { + if underlyingError(err) == windows.ERROR_BROKEN_PIPE || underlyingError(err) == windows.ERROR_PIPE_NOT_CONNECTED { // The named pipe is closed and there is no more data to read. Since // named pipes are not bidirectional, there is no way for the other side // of the pipe to get more data, so do not wait for the stdin copy to diff --git a/scripts/gpg-relay b/scripts/gpg-relay new file mode 100644 index 0000000..70603af --- /dev/null +++ b/scripts/gpg-relay @@ -0,0 +1,3 @@ +#!/bin/sh + +exec socat UNIX-LISTEN:/home//.gnupg/S.gpg-agent,fork, EXEC:'npiperelay.exe -ei -ep -s -a "C:/Users//AppData/Roaming/gnupg/S.gpg-agent"',nofork