Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

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

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 115 additions & 13 deletions npiperelay.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package main

import (
"bufio"
"errors"
"flag"
"io"
"log"
"os"
"strconv"
"sync"
"syscall"
"time"
Expand All @@ -14,30 +17,65 @@ 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) {
p16, err := windows.UTF16FromString(p)
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 {
Expand All @@ -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 {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer that the existing pipe path stay the same as it was (don't change dialPipe) and that this path be a separate branch, maybe a separate function dialAssuan.

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()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
_ = conn.Close()
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)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like this should be inside the if err != nil block below.


if err != nil {
log.Fatal(err)
}

_, err = conn.Write(nonce[:])
if err != nil {
log.Fatal(err)
}
}
break
}

if *verbose {
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