Skip to content

Add SecurityAttribute support #16

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

Open
wants to merge 1 commit into
base: v2
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
127 changes: 51 additions & 76 deletions npipe_windows.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,37 @@
// Copyright 2013 Nate Finch. All rights reserved.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

// Package npipe provides a pure Go wrapper around Windows named pipes.
//
// See http://msdn.microsoft.com/en-us/library/windows/desktop/aa365780
//
// npipe provides an interface based on stdlib's net package, with Dial, Listen, and Accept
// functions, as well as associated implementations of net.Conn and net.Listener
//
// The Dial function connects a client to a named pipe:
// conn, err := npipe.Dial(`\\.\pipe\mypipename`)
// if err != nil {
// <handle error>
// }
// fmt.Fprintf(conn, "Hi server!\n")
// msg, err := bufio.NewReader(conn).ReadString('\n')
// ...
//
// The Listen function creates servers:
//
// ln, err := npipe.Listen(`\\.\pipe\mypipename`)
// if err != nil {
// // handle error
// }
// for {
// conn, err := ln.Accept()
// if err != nil {
// // handle error
// continue
// }
// go handleConnection(conn)
// }
package npipe

//sys createNamedPipe(name *uint16, openMode uint32, pipeMode uint32, maxInstances uint32, outBufSize uint32, inBufSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateNamedPipeW
Expand All @@ -6,13 +40,12 @@ package npipe
//sys waitNamedPipe(name *uint16, timeout uint32) (err error) = WaitNamedPipeW
//sys createEvent(sa *syscall.SecurityAttributes, manualReset bool, initialState bool, name *uint16) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateEventW
//sys getOverlappedResult(handle syscall.Handle, overlapped *syscall.Overlapped, transferred *uint32, wait bool) (err error) = GetOverlappedResult
//sys cancelIoEx(handle syscall.Handle, overlapped *syscall.Overlapped) (err error) = CancelIoEx

import (
"fmt"
"io"
"net"
"sync"
"os"
"syscall"
"time"
)
Expand Down Expand Up @@ -53,9 +86,8 @@ const (

nmpwait_wait_forever = 0xFFFFFFFF

// the two not-an-errors below occur if a client connects to the pipe between
// this not-an-error that occurs if a client connects to the pipe between
// the server's CreateNamedPipe and ConnectNamedPipe calls.
error_no_data syscall.Errno = 0xE8
error_pipe_connected syscall.Errno = 0x217
error_pipe_busy syscall.Errno = 0xE7
error_sem_timeout syscall.Errno = 0x79
Expand All @@ -66,13 +98,6 @@ const (
error_io_incomplete syscall.Errno = 0x3e4
)

var _ net.Conn = (*PipeConn)(nil)
var _ net.Listener = (*PipeListener)(nil)

// ErrClosed is the error returned by PipeListener.Accept when Close is called
// on the PipeListener.
var ErrClosed = PipeError{"Pipe has been closed.", false}

// PipeError is an error related to a call to a pipe
type PipeError struct {
msg string
Expand Down Expand Up @@ -162,7 +187,10 @@ func isPipeNotReady(err error) bool {
// File Not Found means the server hasn't created the pipe yet.
// Neither is a fatal error.

return err == syscall.ERROR_FILE_NOT_FOUND || err == error_pipe_busy
if err, ok := err.(*os.PathError); ok {
return err.Err == error_pipe_busy
}
return err == syscall.ERROR_FILE_NOT_FOUND
}

// newOverlapped creates a structure used to track asynchronous
Expand Down Expand Up @@ -221,8 +249,8 @@ func dial(address string, timeout uint32) (*PipeConn, error) {
return &PipeConn{handle: handle, addr: PipeAddr(address)}, nil
}

// Listen returns a new PipeListener that will listen on a pipe with the given
// address. The address must be of the form \\.\pipe\<name>
// New returns a new PipeListener that will listen on a pipe with the given address.
// The address must be of the form \\.\pipe\<name>
//
// Listen will return a PipeError for an incorrectly formatted pipe name.
func Listen(address string) (*PipeListener, error) {
Expand All @@ -233,10 +261,7 @@ func Listen(address string) (*PipeListener, error) {
if err != nil {
return nil, err
}
return &PipeListener{
addr: PipeAddr(address),
handle: handle,
}, nil
return &PipeListener{PipeAddr(address), handle, false}, nil
}

// PipeListener is a named pipe listener. Clients should typically
Expand All @@ -245,34 +270,19 @@ type PipeListener struct {
addr PipeAddr
handle syscall.Handle
closed bool

// acceptHandle contains the current handle waiting for
// an incoming connection or nil.
acceptHandle syscall.Handle
// acceptOverlapped is set before waiting on a connection.
// If not waiting, it is nil.
acceptOverlapped *syscall.Overlapped
// acceptMutex protects the handle and overlapped structure.
acceptMutex sync.Mutex
}

// Accept implements the Accept method in the net.Listener interface; it
// waits for the next call and returns a generic net.Conn.
func (l *PipeListener) Accept() (net.Conn, error) {
c, err := l.AcceptPipe()
for err == error_no_data {
// Ignore clients that connect and immediately disconnect.
c, err = l.AcceptPipe()
}
if err != nil {
return nil, err
}
return c, nil
}

// AcceptPipe accepts the next incoming call and returns the new connection.
// It might return an error if a client connected and immediately cancelled
// the connection.
func (l *PipeListener) AcceptPipe() (*PipeConn, error) {
if l == nil || l.addr == "" || l.closed {
return nil, syscall.EINVAL
Expand Down Expand Up @@ -300,24 +310,8 @@ func (l *PipeListener) AcceptPipe() (*PipeConn, error) {
defer syscall.CloseHandle(overlapped.HEvent)
if err := connectNamedPipe(handle, overlapped); err != nil && err != error_pipe_connected {
if err == error_io_incomplete || err == syscall.ERROR_IO_PENDING {
l.acceptMutex.Lock()
l.acceptOverlapped = overlapped
l.acceptHandle = handle
l.acceptMutex.Unlock()
defer func() {
l.acceptMutex.Lock()
l.acceptOverlapped = nil
l.acceptHandle = 0
l.acceptMutex.Unlock()
}()

_, err = waitForCompletion(handle, overlapped)
}
if err == syscall.ERROR_OPERATION_ABORTED {
// Return error compatible to net.Listener.Accept() in case the
// listener was closed.
return nil, ErrClosed
}
if err != nil {
return nil, err
}
Expand All @@ -334,33 +328,8 @@ func (l *PipeListener) Close() error {
l.closed = true
if l.handle != 0 {
err := disconnectNamedPipe(l.handle)
if err != nil {
return err
}
err = syscall.CloseHandle(l.handle)
if err != nil {
return err
}
l.handle = 0
}
l.acceptMutex.Lock()
defer l.acceptMutex.Unlock()
if l.acceptOverlapped != nil && l.acceptHandle != 0 {
// Cancel the pending IO. This call does not block, so it is safe
// to hold onto the mutex above.
if err := cancelIoEx(l.acceptHandle, l.acceptOverlapped); err != nil {
return err
}
err := syscall.CloseHandle(l.acceptOverlapped.HEvent)
if err != nil {
return err
}
l.acceptOverlapped.HEvent = 0
err = syscall.CloseHandle(l.acceptHandle)
if err != nil {
return err
}
l.acceptHandle = 0
return err
}
return nil
}
Expand Down Expand Up @@ -503,11 +472,17 @@ func createPipe(address string, first bool) (syscall.Handle, error) {
if first {
mode |= file_flag_first_pipe_instance
}

sa, err := initSecurityAttributes()
if err != nil {
return 0, err
}

return createNamedPipe(n,
mode,
pipe_type_byte,
pipe_unlimited_instances,
512, 512, 0, nil)
512, 512, 0, sa)
}

func badAddr(addr string) PipeError {
Expand Down
46 changes: 46 additions & 0 deletions sec_attr.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package npipe

import (
"os"
"syscall"
"unsafe"
)

const SECURITY_DESCRIPTOR_REVISION = 1

var (
advapi32 = syscall.NewLazyDLL("advapi32.dll")
procInitializeSecurityDescriptor = advapi32.NewProc("InitializeSecurityDescriptor")
procSetSecurityDescriptorDacl = advapi32.NewProc("SetSecurityDescriptorDacl")
)

func initSecurityAttributes() (*syscall.SecurityAttributes, error) {

// create security descriptor
sd := make([]byte, 4096)
if res, _, err := procInitializeSecurityDescriptor.Call(
uintptr(unsafe.Pointer(&sd[0])),
SECURITY_DESCRIPTOR_REVISION); int(res) == 0 {

return nil, os.NewSyscallError("InitializeSecurityDescriptor", err)
}

// configure security descriptor
present := 1
defaulted := 0
if res, _, err := procSetSecurityDescriptorDacl.Call(
uintptr(unsafe.Pointer(&sd[0])),
uintptr(present),
uintptr(unsafe.Pointer(nil)), // acl
uintptr(defaulted)); int(res) == 0 {

return nil, os.NewSyscallError("SetSecurityDescriptorDacl", err)
}

var sa syscall.SecurityAttributes
sa.Length = uint32(unsafe.Sizeof(sa))
sa.SecurityDescriptor = uintptr(unsafe.Pointer(&sd[0]))

return &sa, nil

}