diff --git a/npiperelay.go b/npiperelay.go index 08cbaab..3ceefa7 100644 --- a/npiperelay.go +++ b/npiperelay.go @@ -1,10 +1,13 @@ package main import ( + "bufio" + "errors" "flag" "io" "log" "os" + "strconv" "sync" "syscall" "time" @@ -14,12 +17,18 @@ import ( const cERROR_PIPE_NOT_CONNECTED syscall.Errno = 233 +const WSAECONNREFUSED syscall.Errno = 10061 +const WSAENETUNREACH syscall.Errno = 10051 +const WSAETIMEDOUT syscall.Errno = 10060 +const ERROR_CONNECTION_REFUSED syscall.Errno = 1225 + var ( poll = flag.Bool("p", false, "poll until the the named pipe exists") closeWrite = flag.Bool("s", false, "send a 0-byte message to the pipe after EOF on stdin") 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) { @@ -27,17 +36,46 @@ func dialPipe(p string, poll bool) (*overlappedFile, error) { if err != nil { return nil, err } - for { - h, err := windows.CreateFile(&p16[0], windows.GENERIC_READ|windows.GENERIC_WRITE, 0, nil, windows.OPEN_EXISTING, windows.FILE_FLAG_OVERLAPPED, 0) - if err == nil { - return newOverlappedFile(h), nil - } - if poll && os.IsNotExist(err) { - time.Sleep(200 * time.Millisecond) - continue - } - return nil, &os.PathError{Path: p, Op: "open", Err: err} + + h, err := windows.CreateFile(&p16[0], windows.GENERIC_READ|windows.GENERIC_WRITE, 0, nil, windows.OPEN_EXISTING, windows.FILE_FLAG_OVERLAPPED, 0) + if err == nil { + return newOverlappedFile(h), nil } + + return nil, 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 conn, nil + } + + return nil, err } func underlyingError(err error) error { @@ -59,9 +97,73 @@ func main() { log.Println("connecting to", args[0]) } - conn, err := dialPipe(args[0], *poll) - if err != nil { - log.Fatalln(err) + var conn *overlappedFile + var err error + + // Loop only if we're polling the named pipe or socket + for { + conn, err = dialPipe(args[0], *poll) + + if *poll && os.IsNotExist(err) { + time.Sleep(200 * time.Millisecond) + continue + } + + if err != nil { + err = &os.PathError{Path: args[0], Op: "open", Err: err} + log.Fatalln(err) + } + + // LibAssaaun file socket: Attempt to read contents of the target file and connect to a TCP port + if *assuan { + var port int + var nonce [16]byte + + reader := bufio.NewReader(conn) + + // Read the target port number from the first line + tmp, _, err := reader.ReadLine() + port, err = strconv.Atoi(string(tmp)) + if err != nil { + log.Fatalln(err) + } + + // Read the rest of the nonce from the file + n, err := reader.Read(nonce[:]) + if err != nil { + log.Fatalln(err) + } + + if n != 16 { + log.Fatalf("Read incorrect number of bytes for nonce. Expected 16, got %d (0x%X)", n, nonce) + } + + if *verbose { + log.Printf("Port: %d, Nonce: %X", port, nonce) + } + + _ = conn.Close() + + // Try to connect to the libassaun TCP socket hosted on localhost + conn, err = dialPort(port, *poll) + + if *poll && (err == WSAETIMEDOUT || err == WSAECONNREFUSED || err == WSAENETUNREACH || err == ERROR_CONNECTION_REFUSED) { + time.Sleep(200 * time.Millisecond) + continue + } + + err = os.NewSyscallError("ConnectEx", err) + + if err != nil { + log.Fatal(err) + } + + _, err = conn.Write(nonce[:]) + if err != nil { + log.Fatal(err) + } + } + break } if *verbose { 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