Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Add support for libassuan file sockets (WinGnuPG support) #6

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
116 changes: 111 additions & 5 deletions npiperelay.go
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
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")
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) {
Expand All @@ -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
Expand All @@ -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)
}
Expand Down Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions scripts/gpg-relay
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/sh

exec socat UNIX-LISTEN:/home/<your-username>/.gnupg/S.gpg-agent,fork, EXEC:'npiperelay.exe -ei -ep -s -a "C:/Users/<your-user-folder>/AppData/Roaming/gnupg/S.gpg-agent"',nofork