github.com/hashicorp/go-plugin@v1.6.0/server.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package plugin
     5  
     6  import (
     7  	"context"
     8  	"crypto/tls"
     9  	"crypto/x509"
    10  	"encoding/base64"
    11  	"errors"
    12  	"fmt"
    13  	"io"
    14  	"net"
    15  	"os"
    16  	"os/signal"
    17  	"os/user"
    18  	"runtime"
    19  	"sort"
    20  	"strconv"
    21  	"strings"
    22  
    23  	hclog "github.com/hashicorp/go-hclog"
    24  	"github.com/hashicorp/go-plugin/internal/grpcmux"
    25  	"google.golang.org/grpc"
    26  )
    27  
    28  // CoreProtocolVersion is the ProtocolVersion of the plugin system itself.
    29  // We will increment this whenever we change any protocol behavior. This
    30  // will invalidate any prior plugins but will at least allow us to iterate
    31  // on the core in a safe way. We will do our best to do this very
    32  // infrequently.
    33  const CoreProtocolVersion = 1
    34  
    35  // HandshakeConfig is the configuration used by client and servers to
    36  // handshake before starting a plugin connection. This is embedded by
    37  // both ServeConfig and ClientConfig.
    38  //
    39  // In practice, the plugin host creates a HandshakeConfig that is exported
    40  // and plugins then can easily consume it.
    41  type HandshakeConfig struct {
    42  	// ProtocolVersion is the version that clients must match on to
    43  	// agree they can communicate. This should match the ProtocolVersion
    44  	// set on ClientConfig when using a plugin.
    45  	// This field is not required if VersionedPlugins are being used in the
    46  	// Client or Server configurations.
    47  	ProtocolVersion uint
    48  
    49  	// MagicCookieKey and value are used as a very basic verification
    50  	// that a plugin is intended to be launched. This is not a security
    51  	// measure, just a UX feature. If the magic cookie doesn't match,
    52  	// we show human-friendly output.
    53  	MagicCookieKey   string
    54  	MagicCookieValue string
    55  }
    56  
    57  // PluginSet is a set of plugins provided to be registered in the plugin
    58  // server.
    59  type PluginSet map[string]Plugin
    60  
    61  // ServeConfig configures what sorts of plugins are served.
    62  type ServeConfig struct {
    63  	// HandshakeConfig is the configuration that must match clients.
    64  	HandshakeConfig
    65  
    66  	// TLSProvider is a function that returns a configured tls.Config.
    67  	TLSProvider func() (*tls.Config, error)
    68  
    69  	// Plugins are the plugins that are served.
    70  	// The implied version of this PluginSet is the Handshake.ProtocolVersion.
    71  	Plugins PluginSet
    72  
    73  	// VersionedPlugins is a map of PluginSets for specific protocol versions.
    74  	// These can be used to negotiate a compatible version between client and
    75  	// server. If this is set, Handshake.ProtocolVersion is not required.
    76  	VersionedPlugins map[int]PluginSet
    77  
    78  	// GRPCServer should be non-nil to enable serving the plugins over
    79  	// gRPC. This is a function to create the server when needed with the
    80  	// given server options. The server options populated by go-plugin will
    81  	// be for TLS if set. You may modify the input slice.
    82  	//
    83  	// Note that the grpc.Server will automatically be registered with
    84  	// the gRPC health checking service. This is not optional since go-plugin
    85  	// relies on this to implement Ping().
    86  	GRPCServer func([]grpc.ServerOption) *grpc.Server
    87  
    88  	// Logger is used to pass a logger into the server. If none is provided the
    89  	// server will create a default logger.
    90  	Logger hclog.Logger
    91  
    92  	// Test, if non-nil, will put plugin serving into "test mode". This is
    93  	// meant to be used as part of `go test` within a plugin's codebase to
    94  	// launch the plugin in-process and output a ReattachConfig.
    95  	//
    96  	// This changes the behavior of the server in a number of ways to
    97  	// accomodate the expectation of running in-process:
    98  	//
    99  	//   * The handshake cookie is not validated.
   100  	//   * Stdout/stderr will receive plugin reads and writes
   101  	//   * Connection information will not be sent to stdout
   102  	//
   103  	Test *ServeTestConfig
   104  }
   105  
   106  // ServeTestConfig configures plugin serving for test mode. See ServeConfig.Test.
   107  type ServeTestConfig struct {
   108  	// Context, if set, will force the plugin serving to end when cancelled.
   109  	// This is only a test configuration because the non-test configuration
   110  	// expects to take over the process and therefore end on an interrupt or
   111  	// kill signal. For tests, we need to kill the plugin serving routinely
   112  	// and this provides a way to do so.
   113  	//
   114  	// If you want to wait for the plugin process to close before moving on,
   115  	// you can wait on CloseCh.
   116  	Context context.Context
   117  
   118  	// If this channel is non-nil, we will send the ReattachConfig via
   119  	// this channel. This can be encoded (via JSON recommended) to the
   120  	// plugin client to attach to this plugin.
   121  	ReattachConfigCh chan<- *ReattachConfig
   122  
   123  	// CloseCh, if non-nil, will be closed when serving exits. This can be
   124  	// used along with Context to determine when the server is fully shut down.
   125  	// If this is not set, you can still use Context on its own, but note there
   126  	// may be a period of time between canceling the context and the plugin
   127  	// server being shut down.
   128  	CloseCh chan<- struct{}
   129  
   130  	// SyncStdio, if true, will enable the client side "SyncStdout/Stderr"
   131  	// functionality to work. This defaults to false because the implementation
   132  	// of making this work within test environments is particularly messy
   133  	// and SyncStdio functionality is fairly rare, so we default to the simple
   134  	// scenario.
   135  	SyncStdio bool
   136  }
   137  
   138  func unixSocketConfigFromEnv() UnixSocketConfig {
   139  	return UnixSocketConfig{
   140  		Group:     os.Getenv(EnvUnixSocketGroup),
   141  		socketDir: os.Getenv(EnvUnixSocketDir),
   142  	}
   143  }
   144  
   145  // protocolVersion determines the protocol version and plugin set to be used by
   146  // the server. In the event that there is no suitable version, the last version
   147  // in the config is returned leaving the client to report the incompatibility.
   148  func protocolVersion(opts *ServeConfig) (int, Protocol, PluginSet) {
   149  	protoVersion := int(opts.ProtocolVersion)
   150  	pluginSet := opts.Plugins
   151  	protoType := ProtocolNetRPC
   152  	// Check if the client sent a list of acceptable versions
   153  	var clientVersions []int
   154  	if vs := os.Getenv("PLUGIN_PROTOCOL_VERSIONS"); vs != "" {
   155  		for _, s := range strings.Split(vs, ",") {
   156  			v, err := strconv.Atoi(s)
   157  			if err != nil {
   158  				fmt.Fprintf(os.Stderr, "server sent invalid plugin version %q", s)
   159  				continue
   160  			}
   161  			clientVersions = append(clientVersions, v)
   162  		}
   163  	}
   164  
   165  	// We want to iterate in reverse order, to ensure we match the newest
   166  	// compatible plugin version.
   167  	sort.Sort(sort.Reverse(sort.IntSlice(clientVersions)))
   168  
   169  	// set the old un-versioned fields as if they were versioned plugins
   170  	if opts.VersionedPlugins == nil {
   171  		opts.VersionedPlugins = make(map[int]PluginSet)
   172  	}
   173  
   174  	if pluginSet != nil {
   175  		opts.VersionedPlugins[protoVersion] = pluginSet
   176  	}
   177  
   178  	// Sort the version to make sure we match the latest first
   179  	var versions []int
   180  	for v := range opts.VersionedPlugins {
   181  		versions = append(versions, v)
   182  	}
   183  
   184  	sort.Sort(sort.Reverse(sort.IntSlice(versions)))
   185  
   186  	// See if we have multiple versions of Plugins to choose from
   187  	for _, version := range versions {
   188  		// Record each version, since we guarantee that this returns valid
   189  		// values even if they are not a protocol match.
   190  		protoVersion = version
   191  		pluginSet = opts.VersionedPlugins[version]
   192  
   193  		// If we have a configured gRPC server we should select a protocol
   194  		if opts.GRPCServer != nil {
   195  			// All plugins in a set must use the same transport, so check the first
   196  			// for the protocol type
   197  			for _, p := range pluginSet {
   198  				switch p.(type) {
   199  				case GRPCPlugin:
   200  					protoType = ProtocolGRPC
   201  				default:
   202  					protoType = ProtocolNetRPC
   203  				}
   204  				break
   205  			}
   206  		}
   207  
   208  		for _, clientVersion := range clientVersions {
   209  			if clientVersion == protoVersion {
   210  				return protoVersion, protoType, pluginSet
   211  			}
   212  		}
   213  	}
   214  
   215  	// Return the lowest version as the fallback.
   216  	// Since we iterated over all the versions in reverse order above, these
   217  	// values are from the lowest version number plugins (which may be from
   218  	// a combination of the Handshake.ProtocolVersion and ServeConfig.Plugins
   219  	// fields). This allows serving the oldest version of our plugins to a
   220  	// legacy client that did not send a PLUGIN_PROTOCOL_VERSIONS list.
   221  	return protoVersion, protoType, pluginSet
   222  }
   223  
   224  // Serve serves the plugins given by ServeConfig.
   225  //
   226  // Serve doesn't return until the plugin is done being executed. Any
   227  // fixable errors will be output to os.Stderr and the process will
   228  // exit with a status code of 1. Serve will panic for unexpected
   229  // conditions where a user's fix is unknown.
   230  //
   231  // This is the method that plugins should call in their main() functions.
   232  func Serve(opts *ServeConfig) {
   233  	exitCode := -1
   234  	// We use this to trigger an `os.Exit` so that we can execute our other
   235  	// deferred functions. In test mode, we just output the err to stderr
   236  	// and return.
   237  	defer func() {
   238  		if opts.Test == nil && exitCode >= 0 {
   239  			os.Exit(exitCode)
   240  		}
   241  
   242  		if opts.Test != nil && opts.Test.CloseCh != nil {
   243  			close(opts.Test.CloseCh)
   244  		}
   245  	}()
   246  
   247  	if opts.Test == nil {
   248  		// Validate the handshake config
   249  		if opts.MagicCookieKey == "" || opts.MagicCookieValue == "" {
   250  			fmt.Fprintf(os.Stderr,
   251  				"Misconfigured ServeConfig given to serve this plugin: no magic cookie\n"+
   252  					"key or value was set. Please notify the plugin author and report\n"+
   253  					"this as a bug.\n")
   254  			exitCode = 1
   255  			return
   256  		}
   257  
   258  		// First check the cookie
   259  		if os.Getenv(opts.MagicCookieKey) != opts.MagicCookieValue {
   260  			fmt.Fprintf(os.Stderr,
   261  				"This binary is a plugin. These are not meant to be executed directly.\n"+
   262  					"Please execute the program that consumes these plugins, which will\n"+
   263  					"load any plugins automatically\n")
   264  			exitCode = 1
   265  			return
   266  		}
   267  	}
   268  
   269  	// negotiate the version and plugins
   270  	// start with default version in the handshake config
   271  	protoVersion, protoType, pluginSet := protocolVersion(opts)
   272  
   273  	logger := opts.Logger
   274  	if logger == nil {
   275  		// internal logger to os.Stderr
   276  		logger = hclog.New(&hclog.LoggerOptions{
   277  			Level:      hclog.Trace,
   278  			Output:     os.Stderr,
   279  			JSONFormat: true,
   280  		})
   281  	}
   282  
   283  	// Register a listener so we can accept a connection
   284  	listener, err := serverListener(unixSocketConfigFromEnv())
   285  	if err != nil {
   286  		logger.Error("plugin init error", "error", err)
   287  		return
   288  	}
   289  
   290  	// Close the listener on return. We wrap this in a func() on purpose
   291  	// because the "listener" reference may change to TLS.
   292  	defer func() {
   293  		listener.Close()
   294  	}()
   295  
   296  	var tlsConfig *tls.Config
   297  	if opts.TLSProvider != nil {
   298  		tlsConfig, err = opts.TLSProvider()
   299  		if err != nil {
   300  			logger.Error("plugin tls init", "error", err)
   301  			return
   302  		}
   303  	}
   304  
   305  	var serverCert string
   306  	clientCert := os.Getenv("PLUGIN_CLIENT_CERT")
   307  	// If the client is configured using AutoMTLS, the certificate will be here,
   308  	// and we need to generate our own in response.
   309  	if tlsConfig == nil && clientCert != "" {
   310  		logger.Info("configuring server automatic mTLS")
   311  		clientCertPool := x509.NewCertPool()
   312  		if !clientCertPool.AppendCertsFromPEM([]byte(clientCert)) {
   313  			logger.Error("client cert provided but failed to parse", "cert", clientCert)
   314  		}
   315  
   316  		certPEM, keyPEM, err := generateCert()
   317  		if err != nil {
   318  			logger.Error("failed to generate server certificate", "error", err)
   319  			panic(err)
   320  		}
   321  
   322  		cert, err := tls.X509KeyPair(certPEM, keyPEM)
   323  		if err != nil {
   324  			logger.Error("failed to parse server certificate", "error", err)
   325  			panic(err)
   326  		}
   327  
   328  		tlsConfig = &tls.Config{
   329  			Certificates: []tls.Certificate{cert},
   330  			ClientAuth:   tls.RequireAndVerifyClientCert,
   331  			ClientCAs:    clientCertPool,
   332  			MinVersion:   tls.VersionTLS12,
   333  			RootCAs:      clientCertPool,
   334  			ServerName:   "localhost",
   335  		}
   336  
   337  		// We send back the raw leaf cert data for the client rather than the
   338  		// PEM, since the protocol can't handle newlines.
   339  		serverCert = base64.RawStdEncoding.EncodeToString(cert.Certificate[0])
   340  	}
   341  
   342  	// Create the channel to tell us when we're done
   343  	doneCh := make(chan struct{})
   344  
   345  	// Create our new stdout, stderr files. These will override our built-in
   346  	// stdout/stderr so that it works across the stream boundary.
   347  	var stdout_r, stderr_r io.Reader
   348  	stdout_r, stdout_w, err := os.Pipe()
   349  	if err != nil {
   350  		fmt.Fprintf(os.Stderr, "Error preparing plugin: %s\n", err)
   351  		os.Exit(1)
   352  	}
   353  	stderr_r, stderr_w, err := os.Pipe()
   354  	if err != nil {
   355  		fmt.Fprintf(os.Stderr, "Error preparing plugin: %s\n", err)
   356  		os.Exit(1)
   357  	}
   358  
   359  	// If we're in test mode, we tee off the reader and write the data
   360  	// as-is to our normal Stdout and Stderr so that they continue working
   361  	// while stdio works. This is because in test mode, we assume we're running
   362  	// in `go test` or some equivalent and we want output to go to standard
   363  	// locations.
   364  	if opts.Test != nil {
   365  		// TODO(mitchellh): This isn't super ideal because a TeeReader
   366  		// only works if the reader side is actively read. If we never
   367  		// connect via a plugin client, the output still gets swallowed.
   368  		stdout_r = io.TeeReader(stdout_r, os.Stdout)
   369  		stderr_r = io.TeeReader(stderr_r, os.Stderr)
   370  	}
   371  
   372  	// Build the server type
   373  	var server ServerProtocol
   374  	switch protoType {
   375  	case ProtocolNetRPC:
   376  		// If we have a TLS configuration then we wrap the listener
   377  		// ourselves and do it at that level.
   378  		if tlsConfig != nil {
   379  			listener = tls.NewListener(listener, tlsConfig)
   380  		}
   381  
   382  		// Create the RPC server to dispense
   383  		server = &RPCServer{
   384  			Plugins: pluginSet,
   385  			Stdout:  stdout_r,
   386  			Stderr:  stderr_r,
   387  			DoneCh:  doneCh,
   388  		}
   389  
   390  	case ProtocolGRPC:
   391  		var muxer *grpcmux.GRPCServerMuxer
   392  		if multiplex, _ := strconv.ParseBool(os.Getenv(envMultiplexGRPC)); multiplex {
   393  			muxer = grpcmux.NewGRPCServerMuxer(logger, listener)
   394  			listener = muxer
   395  		}
   396  
   397  		// Create the gRPC server
   398  		server = &GRPCServer{
   399  			Plugins: pluginSet,
   400  			Server:  opts.GRPCServer,
   401  			TLS:     tlsConfig,
   402  			Stdout:  stdout_r,
   403  			Stderr:  stderr_r,
   404  			DoneCh:  doneCh,
   405  			logger:  logger,
   406  			muxer:   muxer,
   407  		}
   408  
   409  	default:
   410  		panic("unknown server protocol: " + protoType)
   411  	}
   412  
   413  	// Initialize the servers
   414  	if err := server.Init(); err != nil {
   415  		logger.Error("protocol init", "error", err)
   416  		return
   417  	}
   418  
   419  	logger.Debug("plugin address", "network", listener.Addr().Network(), "address", listener.Addr().String())
   420  
   421  	// Output the address and service name to stdout so that the client can
   422  	// bring it up. In test mode, we don't do this because clients will
   423  	// attach via a reattach config.
   424  	if opts.Test == nil {
   425  		const grpcBrokerMultiplexingSupported = true
   426  		protocolLine := fmt.Sprintf("%d|%d|%s|%s|%s|%s",
   427  			CoreProtocolVersion,
   428  			protoVersion,
   429  			listener.Addr().Network(),
   430  			listener.Addr().String(),
   431  			protoType,
   432  			serverCert)
   433  
   434  		// Old clients will error with new plugins if we blindly append the
   435  		// seventh segment for gRPC broker multiplexing support, because old
   436  		// client code uses strings.SplitN(line, "|", 6), which means a seventh
   437  		// segment will get appended to the sixth segment as "sixthpart|true".
   438  		//
   439  		// If the environment variable is set, we assume the client is new enough
   440  		// to handle a seventh segment, as it should now use
   441  		// strings.Split(line, "|") and always handle each segment individually.
   442  		if os.Getenv(envMultiplexGRPC) != "" {
   443  			protocolLine += fmt.Sprintf("|%v", grpcBrokerMultiplexingSupported)
   444  		}
   445  		fmt.Printf("%s\n", protocolLine)
   446  		os.Stdout.Sync()
   447  	} else if ch := opts.Test.ReattachConfigCh; ch != nil {
   448  		// Send back the reattach config that can be used. This isn't
   449  		// quite ready if they connect immediately but the client should
   450  		// retry a few times.
   451  		ch <- &ReattachConfig{
   452  			Protocol:        protoType,
   453  			ProtocolVersion: protoVersion,
   454  			Addr:            listener.Addr(),
   455  			Pid:             os.Getpid(),
   456  			Test:            true,
   457  		}
   458  	}
   459  
   460  	// Eat the interrupts. In test mode we disable this so that go test
   461  	// can be cancelled properly.
   462  	if opts.Test == nil {
   463  		ch := make(chan os.Signal, 1)
   464  		signal.Notify(ch, os.Interrupt)
   465  		go func() {
   466  			count := 0
   467  			for {
   468  				<-ch
   469  				count++
   470  				logger.Trace("plugin received interrupt signal, ignoring", "count", count)
   471  			}
   472  		}()
   473  	}
   474  
   475  	// Set our stdout, stderr to the stdio stream that clients can retrieve
   476  	// using ClientConfig.SyncStdout/err. We only do this for non-test mode
   477  	// or if the test mode explicitly requests it.
   478  	//
   479  	// In test mode, we use a multiwriter so that the data continues going
   480  	// to the normal stdout/stderr so output can show up in test logs. We
   481  	// also send to the stdio stream so that clients can continue working
   482  	// if they depend on that.
   483  	if opts.Test == nil || opts.Test.SyncStdio {
   484  		if opts.Test != nil {
   485  			// In test mode we need to maintain the original values so we can
   486  			// reset it.
   487  			defer func(out, err *os.File) {
   488  				os.Stdout = out
   489  				os.Stderr = err
   490  			}(os.Stdout, os.Stderr)
   491  		}
   492  		os.Stdout = stdout_w
   493  		os.Stderr = stderr_w
   494  	}
   495  
   496  	// Accept connections and wait for completion
   497  	go server.Serve(listener)
   498  
   499  	ctx := context.Background()
   500  	if opts.Test != nil && opts.Test.Context != nil {
   501  		ctx = opts.Test.Context
   502  	}
   503  	select {
   504  	case <-ctx.Done():
   505  		// Cancellation. We can stop the server by closing the listener.
   506  		// This isn't graceful at all but this is currently only used by
   507  		// tests and its our only way to stop.
   508  		listener.Close()
   509  
   510  		// If this is a grpc server, then we also ask the server itself to
   511  		// end which will kill all connections. There isn't an easy way to do
   512  		// this for net/rpc currently but net/rpc is more and more unused.
   513  		if s, ok := server.(*GRPCServer); ok {
   514  			s.Stop()
   515  		}
   516  
   517  		// Wait for the server itself to shut down
   518  		<-doneCh
   519  
   520  	case <-doneCh:
   521  		// Note that given the documentation of Serve we should probably be
   522  		// setting exitCode = 0 and using os.Exit here. That's how it used to
   523  		// work before extracting this library. However, for years we've done
   524  		// this so we'll keep this functionality.
   525  	}
   526  }
   527  
   528  func serverListener(unixSocketCfg UnixSocketConfig) (net.Listener, error) {
   529  	if runtime.GOOS == "windows" {
   530  		return serverListener_tcp()
   531  	}
   532  
   533  	return serverListener_unix(unixSocketCfg)
   534  }
   535  
   536  func serverListener_tcp() (net.Listener, error) {
   537  	envMinPort := os.Getenv("PLUGIN_MIN_PORT")
   538  	envMaxPort := os.Getenv("PLUGIN_MAX_PORT")
   539  
   540  	var minPort, maxPort int64
   541  	var err error
   542  
   543  	switch {
   544  	case len(envMinPort) == 0:
   545  		minPort = 0
   546  	default:
   547  		minPort, err = strconv.ParseInt(envMinPort, 10, 32)
   548  		if err != nil {
   549  			return nil, fmt.Errorf("Couldn't get value from PLUGIN_MIN_PORT: %v", err)
   550  		}
   551  	}
   552  
   553  	switch {
   554  	case len(envMaxPort) == 0:
   555  		maxPort = 0
   556  	default:
   557  		maxPort, err = strconv.ParseInt(envMaxPort, 10, 32)
   558  		if err != nil {
   559  			return nil, fmt.Errorf("Couldn't get value from PLUGIN_MAX_PORT: %v", err)
   560  		}
   561  	}
   562  
   563  	if minPort > maxPort {
   564  		return nil, fmt.Errorf("PLUGIN_MIN_PORT value of %d is greater than PLUGIN_MAX_PORT value of %d", minPort, maxPort)
   565  	}
   566  
   567  	for port := minPort; port <= maxPort; port++ {
   568  		address := fmt.Sprintf("127.0.0.1:%d", port)
   569  		listener, err := net.Listen("tcp", address)
   570  		if err == nil {
   571  			return listener, nil
   572  		}
   573  	}
   574  
   575  	return nil, errors.New("Couldn't bind plugin TCP listener")
   576  }
   577  
   578  func serverListener_unix(unixSocketCfg UnixSocketConfig) (net.Listener, error) {
   579  	tf, err := os.CreateTemp(unixSocketCfg.socketDir, "plugin")
   580  	if err != nil {
   581  		return nil, err
   582  	}
   583  	path := tf.Name()
   584  
   585  	// Close the file and remove it because it has to not exist for
   586  	// the domain socket.
   587  	if err := tf.Close(); err != nil {
   588  		return nil, err
   589  	}
   590  	if err := os.Remove(path); err != nil {
   591  		return nil, err
   592  	}
   593  
   594  	l, err := net.Listen("unix", path)
   595  	if err != nil {
   596  		return nil, err
   597  	}
   598  
   599  	// By default, unix sockets are only writable by the owner. Set up a custom
   600  	// group owner and group write permissions if configured.
   601  	if unixSocketCfg.Group != "" {
   602  		err = setGroupWritable(path, unixSocketCfg.Group, 0o660)
   603  		if err != nil {
   604  			return nil, err
   605  		}
   606  	}
   607  
   608  	// Wrap the listener in rmListener so that the Unix domain socket file
   609  	// is removed on close.
   610  	return newDeleteFileListener(l, path), nil
   611  }
   612  
   613  func setGroupWritable(path, groupString string, mode os.FileMode) error {
   614  	groupID, err := strconv.Atoi(groupString)
   615  	if err != nil {
   616  		group, err := user.LookupGroup(groupString)
   617  		if err != nil {
   618  			return fmt.Errorf("failed to find gid from %q: %w", groupString, err)
   619  		}
   620  		groupID, err = strconv.Atoi(group.Gid)
   621  		if err != nil {
   622  			return fmt.Errorf("failed to parse %q group's gid as an integer: %w", groupString, err)
   623  		}
   624  	}
   625  
   626  	err = os.Chown(path, -1, groupID)
   627  	if err != nil {
   628  		return err
   629  	}
   630  
   631  	err = os.Chmod(path, mode)
   632  	if err != nil {
   633  		return err
   634  	}
   635  
   636  	return nil
   637  }
   638  
   639  // rmListener is an implementation of net.Listener that forwards most
   640  // calls to the listener but also calls an additional close function. We
   641  // use this to cleanup the unix domain socket on close, as well as clean
   642  // up multiplexed listeners.
   643  type rmListener struct {
   644  	net.Listener
   645  	close func() error
   646  }
   647  
   648  func newDeleteFileListener(ln net.Listener, path string) *rmListener {
   649  	return &rmListener{
   650  		Listener: ln,
   651  		close: func() error {
   652  			return os.Remove(path)
   653  		},
   654  	}
   655  }
   656  
   657  func (l *rmListener) Close() error {
   658  	// Close the listener itself
   659  	if err := l.Listener.Close(); err != nil {
   660  		return err
   661  	}
   662  
   663  	// Remove the file
   664  	return l.close()
   665  }