github.com/iDigitalFlame/xmt@v0.5.4/com/pipe/pipe_windows.go (about)

     1  //go:build windows
     2  // +build windows
     3  
     4  // Copyright (C) 2020 - 2023 iDigitalFlame
     5  //
     6  // This program is free software: you can redistribute it and/or modify
     7  // it under the terms of the GNU General Public License as published by
     8  // the Free Software Foundation, either version 3 of the License, or
     9  // any later version.
    10  //
    11  // This program is distributed in the hope that it will be useful,
    12  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  // GNU General Public License for more details.
    15  //
    16  // You should have received a copy of the GNU General Public License
    17  // along with this program.  If not, see <https://www.gnu.org/licenses/>.
    18  //
    19  
    20  package pipe
    21  
    22  import (
    23  	"context"
    24  	"io"
    25  	"net"
    26  	"sync/atomic"
    27  	"time"
    28  	"unsafe"
    29  
    30  	"github.com/iDigitalFlame/xmt/com"
    31  	"github.com/iDigitalFlame/xmt/device/winapi"
    32  	"github.com/iDigitalFlame/xmt/util/bugtrack"
    33  )
    34  
    35  const retry = 100 * time.Millisecond
    36  
    37  // ErrClosed is an error returned by the 'Accept' function when the
    38  // underlying Pipe was closed.
    39  var ErrClosed = &errno{m: io.ErrClosedPipe.Error()}
    40  
    41  type addr string
    42  
    43  // Conn is a struct that implements a Windows Pipe connection. This is similar
    44  // to the 'net.Conn' interface except it adds the 'Impersonate' function, which
    45  // is only from the 'AcceptPipe' function.
    46  type Conn struct {
    47  	_           [0]func()
    48  	read, write time.Time
    49  	addr        addr
    50  	handle      uintptr
    51  }
    52  type wait struct {
    53  	_   [0]func()
    54  	err error
    55  	n   uint32
    56  }
    57  type errno struct {
    58  	e error
    59  	m string
    60  	t bool
    61  }
    62  
    63  // Listener is a struct that fulfils the 'net.Listener' interface, but used for
    64  // Windows named pipes.
    65  type Listener struct {
    66  	_              [0]func()
    67  	overlap        *winapi.Overlapped
    68  	perms          *winapi.SecurityAttributes
    69  	addr           addr
    70  	active, handle uintptr
    71  	done           uint32
    72  }
    73  
    74  // Fd returns the file descriptor handle for this PipeCon.
    75  func (c *Conn) Fd() uintptr {
    76  	return c.handle
    77  }
    78  func (addr) Network() string {
    79  	return com.NamePipe
    80  }
    81  
    82  // Close releases the associated Pipe's resources. The connection is no longer
    83  // considered valid after a call to this function.
    84  func (c *Conn) Close() error {
    85  	winapi.CancelIoEx(c.handle, nil)
    86  	winapi.DisconnectNamedPipe(c.handle)
    87  	err := winapi.CloseHandle(c.handle)
    88  	c.handle = 0
    89  	return err
    90  }
    91  func (a addr) String() string {
    92  	return string(a)
    93  }
    94  func (e errno) Timeout() bool {
    95  	return e.t
    96  }
    97  func (e errno) Error() string {
    98  	if len(e.m) == 0 && e.e != nil {
    99  		return e.e.Error()
   100  	}
   101  	return e.m
   102  }
   103  func (e errno) Unwrap() error {
   104  	return e.e
   105  }
   106  func (e errno) Temporary() bool {
   107  	return e.t
   108  }
   109  
   110  // Close closes the listener. Any blocked Accept operations will be unblocked
   111  // and return errors.
   112  func (l *Listener) Close() error {
   113  	if atomic.LoadUint32(&l.done) == 1 {
   114  		return nil
   115  	}
   116  	var err error // We shouldn't let errors stop us from cleaning up!
   117  	if atomic.StoreUint32(&l.done, 1); l.handle > 0 {
   118  		if err = winapi.DisconnectNamedPipe(l.handle); err != nil {
   119  			if bugtrack.Enabled {
   120  				bugtrack.Track("com.(*Listener).Close(): DisconnectNamedPipe error: %s!", err.Error())
   121  			}
   122  		}
   123  		if err = winapi.CloseHandle(l.handle); err != nil {
   124  			if bugtrack.Enabled {
   125  				bugtrack.Track("com.(*Listener).Close(): CloseHandle(handle) error: %s!", err.Error())
   126  			}
   127  		}
   128  		l.handle = 0
   129  	}
   130  	if l.overlap != nil && l.active > 0 {
   131  		if err = winapi.CancelIoEx(l.active, l.overlap); err != nil {
   132  			if bugtrack.Enabled {
   133  				bugtrack.Track("com.(*Listener).Close(): CancelIoEx error: %s!", err.Error())
   134  			}
   135  		}
   136  		if err = winapi.CloseHandle(l.overlap.Event); err != nil {
   137  			if bugtrack.Enabled {
   138  				bugtrack.Track("com.(*Listener).Close(): CloseHandle(event) error: %s!", err.Error())
   139  			}
   140  		}
   141  		if l.active > 0 { // Extra check as it can be reset here sometimes.
   142  			if err = winapi.CloseHandle(l.active); err != nil {
   143  				if bugtrack.Enabled {
   144  					bugtrack.Track("com.(*Listener).Close(): CloseHandle(active) error: %s!", err.Error())
   145  				}
   146  			}
   147  		}
   148  		l.active = 0
   149  	}
   150  	return err
   151  }
   152  
   153  // Addr returns the listener's network address.
   154  func (l *Listener) Addr() net.Addr {
   155  	return l.addr
   156  }
   157  
   158  // Impersonate will attempt to call 'ImpersonatePipeToken' which, if successful,
   159  // will set the token of this Thread to the Pipe's connected client token.
   160  //
   161  // A call to 'device.RevertToSelf()' will reset the token.
   162  func (c *Conn) Impersonate() error {
   163  	if c.handle == 0 {
   164  		return nil
   165  	}
   166  	return winapi.ImpersonatePipeToken(c.handle)
   167  }
   168  
   169  // LocalAddr returns the Pipe's local endpoint address.
   170  func (c *Conn) LocalAddr() net.Addr {
   171  	return c.addr
   172  }
   173  
   174  // RemoteAddr returns the Pipe's remote endpoint address.
   175  func (c *Conn) RemoteAddr() net.Addr {
   176  	return c.addr
   177  }
   178  
   179  // Dial connects to the specified Pipe path. This function will return a 'net.Conn'
   180  // instance or any errors that may occur during the connection attempt.
   181  //
   182  // Pipe names are in the form of "\\<computer>\pipe\<path>".
   183  //
   184  // This function blocks indefinitely. Use the DialTimeout or DialContext to specify
   185  // a control method.
   186  func Dial(path string) (net.Conn, error) {
   187  	return DialContext(context.Background(), path)
   188  }
   189  
   190  // Read implements the 'net.Conn' interface.
   191  func (c *Conn) Read(b []byte) (int, error) {
   192  	if c.handle == 0 {
   193  		return 0, ErrClosed
   194  	}
   195  	var (
   196  		a   uint32
   197  		o   = new(winapi.Overlapped)
   198  		err error
   199  	)
   200  	if o.Event, err = winapi.CreateEvent(nil, true, true, ""); err != nil {
   201  		return 0, &errno{m: "could not create event", e: err}
   202  	}
   203  	return c.finish(winapi.ReadFile(c.handle, b, &a, o), int(a), c.read, o)
   204  }
   205  func (l *Listener) wait(x context.Context) {
   206  	<-x.Done()
   207  	l.Close()
   208  }
   209  
   210  // Write implements the 'net.Conn' interface.
   211  func (c *Conn) Write(b []byte) (int, error) {
   212  	var (
   213  		a   uint32
   214  		o   = new(winapi.Overlapped)
   215  		err error
   216  	)
   217  	if o.Event, err = winapi.CreateEvent(nil, true, true, ""); err != nil {
   218  		return 0, &errno{m: "could not create event", e: err}
   219  	}
   220  	return c.finish(winapi.WriteFile(c.handle, b, &a, o), int(a), c.write, o)
   221  }
   222  
   223  // Listen returns a 'net.Listener' that will listen for new connections on the
   224  // Named Pipe path specified or any errors that may occur during listener
   225  // creation.
   226  //
   227  // Pipe names are in the form of "\\<computer>\pipe\<path>".
   228  func Listen(path string) (*Listener, error) {
   229  	return ListenSecurityContext(context.Background(), path, nil)
   230  }
   231  
   232  // Accept waits for and returns the next connection to the listener.
   233  func (l *Listener) Accept() (net.Conn, error) {
   234  	return l.AcceptPipe()
   235  }
   236  
   237  // SetDeadline implements the 'net.Conn' interface.
   238  func (c *Conn) SetDeadline(t time.Time) error {
   239  	c.read, c.write = t, t
   240  	return nil
   241  }
   242  
   243  // AcceptPipe waits for and returns the next connection to the listener.
   244  //
   245  // This function returns the real type of 'Conn' that can be used with the
   246  // 'Impersonate' function.
   247  func (l *Listener) AcceptPipe() (*Conn, error) {
   248  	if atomic.LoadUint32(&l.done) == 1 {
   249  		return nil, ErrClosed
   250  	}
   251  	var (
   252  		h   uintptr
   253  		err error
   254  	)
   255  	if l.handle == 0 {
   256  		if h, err = create(l.addr, l.perms, 50, 512, false); err != nil {
   257  			return nil, &errno{e: err}
   258  		}
   259  	} else {
   260  		h, l.handle = l.handle, 0
   261  	}
   262  	o := new(winapi.Overlapped)
   263  	if o.Event, err = winapi.CreateEvent(nil, true, true, ""); err != nil {
   264  		winapi.CloseHandle(h)
   265  		return nil, &errno{m: "could not create event", e: err}
   266  	}
   267  	if err = winapi.ConnectNamedPipe(h, o); err == winapi.ErrIoPending || err == winapi.ErrIoIncomplete {
   268  		l.overlap, l.active = o, h
   269  		_, err = complete(h, o)
   270  	}
   271  	if l.active = 0; atomic.LoadUint32(&l.done) == 1 {
   272  		winapi.CloseHandle(o.Event)
   273  		return nil, ErrClosed
   274  	}
   275  	winapi.CancelIoEx(l.active, l.overlap)
   276  	if winapi.CloseHandle(o.Event); err == winapi.ErrOperationAborted {
   277  		winapi.CloseHandle(h)
   278  		return nil, ErrClosed
   279  	}
   280  	if err == winapi.ErrNoData {
   281  		winapi.CloseHandle(h)
   282  		return nil, ErrEmptyConn
   283  	}
   284  	if err != nil && err != winapi.ErrPipeConnected {
   285  		winapi.CloseHandle(h)
   286  		return nil, &errno{m: "could not connect", e: err}
   287  	}
   288  	return &Conn{addr: l.addr, handle: h}, nil
   289  }
   290  
   291  // SetReadDeadline implements the 'net.Conn' interface.
   292  func (c *Conn) SetReadDeadline(t time.Time) error {
   293  	c.read = t
   294  	return nil
   295  }
   296  
   297  // SetWriteDeadline implements the 'net.Conn' interface.
   298  func (c *Conn) SetWriteDeadline(t time.Time) error {
   299  	c.write = t
   300  	return nil
   301  }
   302  func connect(path string, t uint32) (*Conn, error) {
   303  	if len(path) == 0 || len(path) > 255 {
   304  		return nil, &errno{m: "invalid path length"}
   305  	}
   306  	if err := winapi.WaitNamedPipe(path, t); err != nil {
   307  		return nil, err
   308  	}
   309  	// 0xC0000000 - FILE_FLAG_OVERLAPPED | FILE_FLAG_WRITE_THROUGH
   310  	// 0x3        - FILE_SHARE_READ | FILE_SHARE_WRITE
   311  	// 0x3        - OPEN_EXISTING
   312  	// 0x40000000 - FILE_FLAG_OVERLAPPED
   313  	h, err := winapi.CreateFile(path, 0xC0000000, 0x3, nil, 0x3, 0x40000000, 0)
   314  	if err != nil {
   315  		return nil, err
   316  	}
   317  	return &Conn{addr: addr(path), handle: h}, nil
   318  }
   319  
   320  // ListenPerms returns a Listener that will listen for new connections on the
   321  // Named Pipe path specified or any errors that may occur during listener
   322  // creation.
   323  //
   324  // Pipe names are in the form of "\\<computer>\pipe\<path>".
   325  //
   326  // This function allows for specifying a SDDL string used to set the permissions
   327  // of the listening Pipe.
   328  func ListenPerms(path, perms string) (*Listener, error) {
   329  	return ListenPermsContext(context.Background(), path, perms)
   330  }
   331  func complete(h uintptr, o *winapi.Overlapped) (uint32, error) {
   332  	if _, err := winapi.WaitForSingleObject(o.Event, -1); err != nil {
   333  		return 0, err
   334  	}
   335  	var (
   336  		n   uint32
   337  		err = winapi.GetOverlappedResult(h, o, &n, true)
   338  	)
   339  	return n, err
   340  }
   341  
   342  // DialTimeout connects to the specified Pipe path. This function will return a
   343  // net.Conn instance or any errors that may occur during the connection attempt.
   344  //
   345  // Pipe names are in the form of "\\<computer>\pipe\<path>".
   346  //
   347  // This function blocks for the specified amount of time and will return 'ErrTimeout'
   348  // if the timeout is reached.
   349  func DialTimeout(path string, t time.Duration) (net.Conn, error) {
   350  	for n, d := time.Now(), time.Now().Add(t); n.Before(d); n = time.Now() {
   351  		c, err := connect(path, uint32(d.Sub(n)/time.Millisecond))
   352  		if err == nil {
   353  			return c, nil
   354  		}
   355  		if err == winapi.ErrSemTimeout {
   356  			return nil, ErrTimeout
   357  		}
   358  		if err == winapi.ErrBadPathname {
   359  			return nil, &errno{m: `invalid path "` + path + `"`, e: err}
   360  		}
   361  		if err == winapi.ErrFileNotFound || err == winapi.ErrPipeBusy {
   362  			if l := time.Until(d); l < retry {
   363  				time.Sleep(l - time.Millisecond)
   364  			} else {
   365  				time.Sleep(retry)
   366  			}
   367  			continue
   368  		}
   369  		return nil, &errno{m: err.Error(), e: err}
   370  	}
   371  	return nil, ErrTimeout
   372  }
   373  
   374  // DialContext connects to the specified Pipe path. This function will return a
   375  // net.Conn instance or any errors that may occur during the connection attempt.
   376  //
   377  // Pipe names are in the form of "\\<computer>\pipe\<path>".
   378  //
   379  // This function blocks until the supplied context is canceled and will return the
   380  // context's Err() if the cancel occurs before the connection.
   381  func DialContext(x context.Context, path string) (net.Conn, error) {
   382  	for {
   383  		if x != nil {
   384  			select {
   385  			case <-x.Done():
   386  				return nil, x.Err()
   387  			default:
   388  			}
   389  		}
   390  		c, err := connect(path, 250)
   391  		if err == nil {
   392  			return c, nil
   393  		}
   394  		if err == winapi.ErrSemTimeout {
   395  			return nil, ErrTimeout
   396  		}
   397  		if err == winapi.ErrBadPathname {
   398  			return nil, &errno{m: `invalid path "` + path + `"`, e: err}
   399  		}
   400  		if err == winapi.ErrFileNotFound || err == winapi.ErrPipeBusy {
   401  			time.Sleep(retry)
   402  			continue
   403  		}
   404  		return nil, &errno{m: err.Error(), e: err}
   405  	}
   406  }
   407  
   408  // ListenContext returns a 'net.Listener' that will listen for new connections
   409  // on the Named Pipe path specified or any errors that may occur during listener
   410  // creation.
   411  //
   412  // Pipe names are in the form of "\\<computer>\pipe\<path>".
   413  //
   414  // The provided Context can be used to cancel the Listener.
   415  func ListenContext(x context.Context, path string) (*Listener, error) {
   416  	return ListenSecurityContext(x, path, nil)
   417  }
   418  func waitComplete(w chan<- wait, h uintptr, o *winapi.Overlapped, s *uint32) {
   419  	if n, err := complete(h, o); atomic.LoadUint32(s) == 0 {
   420  		w <- wait{n: n, err: err}
   421  	}
   422  }
   423  
   424  // ListenSecurity returns a net.Listener that will listen for new connections on
   425  // the Named Pipe path specified or any errors that may occur during listener
   426  // creation.
   427  //
   428  // Pipe names are in the form of "\\<computer>\pipe\<path>".
   429  //
   430  // This function allows for specifying a SecurityAttributes object used to set
   431  // the permissions of the listening Pipe.
   432  func ListenSecurity(path string, p *winapi.SecurityAttributes) (*Listener, error) {
   433  	return ListenSecurityContext(context.Background(), path, p)
   434  }
   435  
   436  // ListenPermsContext returns a Listener that will listen for new connections on
   437  // the Named Pipe path specified or any errors that may occur during listener
   438  // creation.
   439  //
   440  // Pipe names are in the form of "\\<computer>\pipe\<path>".
   441  //
   442  // This function allows for specifying a SDDL string used to set the permissions
   443  // of the listening Pipe.
   444  //
   445  // The provided Context can be used to cancel the Listener.
   446  func ListenPermsContext(x context.Context, path, perms string) (*Listener, error) {
   447  	var (
   448  		s   = winapi.SecurityAttributes{InheritHandle: 0}
   449  		err error
   450  	)
   451  	if len(perms) > 0 {
   452  		if s.SecurityDescriptor, err = winapi.SecurityDescriptorFromString(perms); err != nil {
   453  			return nil, err
   454  		}
   455  		s.Length = uint32(unsafe.Sizeof(s))
   456  	}
   457  	return ListenSecurityContext(x, path, &s)
   458  }
   459  func (c *Conn) finish(e error, a int, t time.Time, o *winapi.Overlapped) (int, error) {
   460  	if e == winapi.ErrBrokenPipe {
   461  		winapi.CloseHandle(o.Event)
   462  		return a, io.EOF
   463  	}
   464  	if e != winapi.ErrIoIncomplete && e != winapi.ErrIoPending {
   465  		winapi.CloseHandle(o.Event)
   466  		return a, e
   467  	}
   468  	var (
   469  		z *time.Timer
   470  		s uint32
   471  		f <-chan time.Time
   472  		w = make(chan wait, 1)
   473  	)
   474  	if !t.IsZero() && time.Now().Before(t) {
   475  		z = time.NewTimer(time.Until(t))
   476  		f = z.C
   477  	}
   478  	go waitComplete(w, c.handle, o, &s)
   479  	select {
   480  	case <-f:
   481  		winapi.CancelIoEx(c.handle, o)
   482  		a, e = 0, ErrTimeout
   483  	case d := <-w:
   484  		a, e = int(d.n), d.err
   485  	}
   486  	atomic.StoreUint32(&s, 1)
   487  	if close(w); e == winapi.ErrBrokenPipe {
   488  		e = io.EOF
   489  	}
   490  	if winapi.CloseHandle(o.Event); z != nil {
   491  		z.Stop()
   492  	}
   493  	return a, e
   494  }
   495  func create(path addr, p *winapi.SecurityAttributes, t, l uint32, f bool) (uintptr, error) {
   496  	// 0x40040003 - PIPE_ACCESS_DUPLEX | WRITE_DAC | FILE_FLAG_OVERLAPPED
   497  	m := uint32(0x40040003)
   498  	if f {
   499  		// 0x80000 - FILE_FLAG_FIRST_PIPE_INSTANCE
   500  		m |= 0x80000
   501  	}
   502  	h, err := winapi.CreateNamedPipe(string(path), m, 0, 0xFF, l, l, t, p)
   503  	if err != nil {
   504  		return 0, err
   505  	}
   506  	return h, nil
   507  }
   508  
   509  // ListenSecurityContext returns a net.Listener that will listen for new connections
   510  // on the Named Pipe path specified or any errors that may occur during listener
   511  // creation.
   512  //
   513  // Pipe names are in the form of "\\<computer>\pipe\<path>".
   514  //
   515  // This function allows for specifying a SecurityAttributes object used to set
   516  // the permissions of the listening Pipe.
   517  //
   518  // The provided Context can be used to cancel the Listener.
   519  func ListenSecurityContext(x context.Context, path string, p *winapi.SecurityAttributes) (*Listener, error) {
   520  	var (
   521  		a      = addr(path)
   522  		l, err = create(a, p, 50, 512, true)
   523  	)
   524  	if err != nil {
   525  		if err == winapi.ErrInvalidName {
   526  			return nil, &errno{m: `invalid path "` + path + `"`, e: err}
   527  		}
   528  		return nil, &errno{m: err.Error(), e: err}
   529  	}
   530  	n := &Listener{addr: a, handle: l, perms: p}
   531  	if x != context.Background() {
   532  		go n.wait(x)
   533  	}
   534  	return n, nil
   535  }