pax_global_header00006660000000000000000000000064150276213610014515gustar00rootroot0000000000000052 comment=1ac23b2e75353f0714c14c1543f70901fa1948af go-listener-0.7.0/000077500000000000000000000000001502762136100137515ustar00rootroot00000000000000go-listener-0.7.0/LICENSE000066400000000000000000000023361502762136100147620ustar00rootroot00000000000000Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Except as contained in this notice, the name(s) of the above copyright holders shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization. go-listener-0.7.0/README.md000066400000000000000000000155341502762136100152400ustar00rootroot00000000000000# `go-listener` [![Documentation](https://pkg.go.dev/badge/src.agwa.name/go-listener)](https://pkg.go.dev/src.agwa.name/go-listener) `src.agwa.name/go-listener` is a Go library for creating `net.Listener`s. Typically, server software only supports listening on TCP ports. `go-listener` makes it easy to also listen on: * TCP ports * UNIX domain sockets * Pre-opened file descriptors Additionally, `go-listener` makes it easy to support: * The [PROXY protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) * TLS (with several options for certificate management, including ACME) Listeners are specified using a string syntax, which makes them easy to pass as command line arguments. ## How To Use ```go import "src.agwa.name/go-listener" netListener, err := listener.Open(listenerString) if err != nil { // Handle err } defer netListener.Close() ``` `listener.Open` takes a string which describes a listener per the syntax described below, and returns a `net.Listener`, which you can use by calling `Accept`, passing to `http.Serve`, etc. ## Listener Syntax ### TCP Listen on all interfaces: ``` tcp:PORT ``` Listen on a specific IPv4 interface: ``` tcp:IPV4ADDRESS:PORT ``` Listen on a specific IPv6 interface: ``` tcp:[IPV6ADDRESS]:PORT ``` Listen on all IPv4 interfaces: ``` tcp:0.0.0.0:PORT ``` Listen on all IPv6 interfaces: ``` tcp:[::]:PORT ``` ### UNIX Domain Socket ``` unix:PATH ``` ### File Descriptor Listen on a file descriptor that is already open, bound, and listening: ``` fd:NUMBER ``` ### Named File Descriptor (compatible with systemd socket activation) Listen on a named file descriptor that is already open, bound, and listening: ``` fdname:NAME ``` `NAME` must match the `FileDescriptorName` option in the systemd socket file. ### PROXY Protocol Wrap a listener with the [PROXY Protocol version 2](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt): ``` proxy:LISTENER ``` (where `LISTENER` is one of the syntaxes specified here) `go-listener` will transparently read the PROXY protocol header and make the true client IP address available via the `net.Conn`'s `LocalAddr` method. ### TLS Note: TLS support must be enabled by importing `src.agwa.name/go-listener/tls` like this: ```go import _ "src.agwa.name/go-listener/tls" ``` Wrap a listener with TLS, using the certificate/key in the given file (which must be absolute path): ``` tls:/PATH/TO/CERTIFICATE_FILE:LISTENER ``` Wrap a listener with TLS, using the certificate/key named `SERVER_NAME.pem` in the given directory (which must be an absolute path and end with a slash): ``` tls:/PATH/TO/CERTIFICATE_DIRECTORY/:LISTENER ``` Wrap a listener with TLS and automatically obtain certificates for each hostname using ACME (requires the hostname to be publicly-accessible on port 443): ``` tls:HOSTNAME,HOSTNAME,...:LISTENER ``` #### Certificate Files When you specify a certificate file or directory, certificates must be PEM-encoded and contain the following blocks: * Exactly one `PRIVATE KEY`, containing the private key in PKCS#8 format. * At least one `CERTIFICATE`, comprising the certificate chain, leaf certificate first and root certificate omitted. * Up to one `OCSP RESPONSE`, containing a stapled OCSP response. * Any number of `SIGNED CERTIFICATE TIMESTAMP`, containing stapled SCTs. Certificate files are automatically reloaded when they change. #### ACME Configuration When you obtain certificates automatically, the following environment variables can be used to configure the ACME client: | Environment Variable | Description | Default | | ---------------------- | ----------- | ------- | | `AUTOCERT_ACME_SERVER` | The directory URL of the certificate authority's ACME server | [`autocert.DefaultACMEDirectory`](https://pkg.go.dev/golang.org/x/crypto/acme/autocert#DefaultACMEDirectory) | | `AUTOCERT_EMAIL` | Contact email address for your ACME account, used by certificate authority to notify you of certificate problems (highly recommended) | (none) | | `AUTOCERT_EAB_KID` | Key ID of the External Account Binding to use with ACME | (none) | | `AUTOCERT_EAB_KEY` | base64url-encoded HMAC-SHA256 key of the External Account Binding to use with ACME | (none) | | `AUTOCERT_CACHE_DIR` | The directory where issued certificates are stored | When root, `/var/lib/autocert-cache`; otherwise, `autocert-cache` under [`$XDG_DATA_HOME`](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) | ## Example Here's how to use `go-listener` with `http.Server`: ```go package main import ( "flag" "log" "net/http" "src.agwa.name/go-listener" _ "src.agwa.name/go-listener/tls" ) func main() { var listenerString string flag.StringVar(&listenerString, "listen", "", "Socket to listen on") flag.Parse() netListener, err := listener.Open(listenerString) if err != nil { log.Fatal(err) } defer netListener.Close() log.Fatal(http.Serve(netListener, nil)) } ``` Listen on localhost, port 80: ``` httpd -listen tcp:127.0.0.1:80 ``` Listen on IPv6 localhost, port 80: ``` httpd -listen tcp:[::1]:80 ``` Listen on file descriptor 3: ``` httpd -listen fd:3 ``` Listen on port 443, all interfaces, with TLS, using certificates in `/var/certs`: ``` httpd -listen tls:/var/certs/:tcp:443 ``` Listen on port 443, all interfaces, with TLS, with automatic certificates for `www.example.com` and `example.com`: ``` httpd -listen tls:www.example.com,example.com:tcp:443 ``` Listen on UNIX domain docket `/run/example.sock` with the PROXY protocol: ``` httpd -listen proxy:unix:/run/example.sock ``` Listen on UNIX domain socket `/run/example.sock` with TLS and the PROXY protocol, with certificate in `/etc/ssl/example.com.pem`: ``` httpd -listen tls:/etc/ssl/example.com.pem:proxy:unix:/run/example.sock ``` (Details: `go-listener` will listen on `/run/example.sock`. When a connection is accepted, `go-listener` will first read the PROXY protocol header to get the true client IP address, which will be made available through the `net.Conn`'s `LocalAddr` method. It will then do a TLS handshake using the private key and certificate in `/etc/ssl/example.com.pem`.) ### Socket Activation Here's how to use systemd socket activation to run httpd as an unprivileged user listening on port 80 (which is a privileged port): In `/etc/systemd/system/httpd.socket` put: ``` [Socket] ListenStream=80 [Install] WantedBy=sockets.target ``` In `/etc/systemd/system/httpd.service` put: ``` [Service] ExecStart=/path/to/httpd -listen fd:3 DynamicUser=yes [Install] WantedBy=multi-user.target ``` You can also name the socket using the `FileDescriptorName` option in the `httpd.socket` file, and refer to it using the `fdname` listener type (instead of `fd:3`). You don't have to use systemd; the `fd` listener type can be used with any process supervisor which supports listening on a file descriptor, dropping privileges, and passing the listening file descriptor to the daemon. go-listener-0.7.0/builtin.go000066400000000000000000000126031502762136100157500ustar00rootroot00000000000000// Copyright (C) 2022 Andrew Ayer // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // // Except as contained in this notice, the name(s) of the above copyright // holders shall not be used in advertising or otherwise to promote the // sale, use or other dealings in this Software without prior written // authorization. package listener // import "src.agwa.name/go-listener" import ( "encoding/json" "errors" "fmt" "net" "os" "strconv" "strings" "src.agwa.name/go-listener/proxy" "src.agwa.name/go-listener/unix" ) func init() { RegisterListenerType("fd", openFDListener) RegisterListenerType("fdname", openFDNameListener) RegisterListenerType("tcp", openTCPListener) RegisterListenerType("unix", openUnixListener) RegisterListenerType("proxy", openProxyListener) } func openFDListener(params map[string]interface{}, arg string) (net.Listener, error) { var fdString string if arg != "" { fdString = arg } else if param, ok := params["fd"].(json.Number); ok { fdString = string(param) } else if param, ok := params["fd"].(string); ok { fdString = param } else { return nil, errors.New("file descriptor not specified for FD listener") } fd, err := strconv.ParseUint(fdString, 10, 64) if err != nil { return nil, fmt.Errorf("'%s' is a malformed file descriptor", fdString) } file := os.NewFile(uintptr(fd), fdString) defer file.Close() return net.FileListener(file) } func openFDNameListener(params map[string]interface{}, arg string) (net.Listener, error) { var name string if arg != "" { name = arg } else if param, ok := params["name"].(string); ok { name = param } else { return nil, errors.New("name not specified for fdname listener") } if listenPidStr := os.Getenv("LISTEN_PID"); listenPidStr == "" { return nil, errors.New("cannot create fdname listener because $LISTEN_PID is not set") } else if listenPid, err := strconv.Atoi(listenPidStr); err != nil { return nil, errors.New("cannot create fdname listener because $LISTEN_PID does not contain an integer") } else if ourPid := os.Getpid(); listenPid != ourPid { return nil, fmt.Errorf("cannot create fdname listener because $LISTEN_PID (%d) does not match our PID (%d)", listenPid, ourPid) } for i, ithname := range strings.Split(os.Getenv("LISTEN_FDNAMES"), ":") { if ithname == name { file := os.NewFile(uintptr(3+i), name) defer file.Close() return net.FileListener(file) } } return nil, fmt.Errorf("fdname: %q not found in $LISTEN_FDNAMES", name) } func openTCPListener(params map[string]interface{}, arg string) (net.Listener, error) { var ipString string var portString string var err error if arg != "" { if strings.Contains(arg, ":") { ipString, portString, err = net.SplitHostPort(arg) if err != nil { return nil, fmt.Errorf("TCP listener has invalid argument: %w", err) } } else { portString = arg } } else if param, ok := params["address"].(string); ok { ipString = param } else if param, ok := params["port"].(json.Number); ok { portString = string(param) } else if param, ok := params["port"].(string); ok { portString = param } network := "tcp" address := new(net.TCPAddr) if ipString != "" { address.IP = net.ParseIP(ipString) if address.IP == nil { return nil, errors.New("TCP listener has invalid IP address") } // Explicitly specify the IP protocol, to ensure that 0.0.0.0 // and :: work as expected (listen only on IPv4 or IPv6 interfaces) if address.IP.To4() == nil { network = "tcp6" } else { network = "tcp4" } } address.Port, err = strconv.Atoi(portString) if err != nil { return nil, fmt.Errorf("TCP listener has invalid port: %w", err) } return net.ListenTCP(network, address) } func openUnixListener(params map[string]interface{}, arg string) (net.Listener, error) { var path string if arg != "" { path = arg } else if value, ok := params["path"].(string); ok { path = value } else { return nil, errors.New("path not specified for UNIX listener") } return unix.Listen(path, 0666) } func openProxyListener(params map[string]interface{}, arg string) (net.Listener, error) { var inner net.Listener var err error if arg != "" { inner, err = Open(arg) } else if spec, ok := params["listener"].(map[string]interface{}); ok { inner, err = OpenJSON(spec) } else { return nil, errors.New("inner socket not specified for proxy listener") } if err != nil { return nil, err } return proxy.NewListener(inner), nil } go-listener-0.7.0/cert/000077500000000000000000000000001502762136100147065ustar00rootroot00000000000000go-listener-0.7.0/cert/auto.go000066400000000000000000000064331502762136100162130ustar00rootroot00000000000000// Copyright (C) 2022 Andrew Ayer // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // // Except as contained in this notice, the name(s) of the above copyright // holders shall not be used in advertising or otherwise to promote the // sale, use or other dealings in this Software without prior written // authorization. package cert import ( "encoding/base64" "golang.org/x/crypto/acme" "golang.org/x/crypto/acme/autocert" "os" "path/filepath" ) func getAutocertCache() autocert.Cache { if cacheDir := os.Getenv("AUTOCERT_CACHE_DIR"); cacheDir != "" { return autocert.DirCache(cacheDir) } else if os.Getuid() == 0 { return autocert.DirCache("/var/lib/autocert-cache") } else if dataDir := os.Getenv("XDG_DATA_HOME"); dataDir != "" { return autocert.DirCache(filepath.Join(dataDir, "autocert-cache")) } else if homeDir, err := os.UserHomeDir(); err == nil { return autocert.DirCache(filepath.Join(homeDir, ".local/share/autocert-cache")) } else { return nil } } func getAutocertEAB() *acme.ExternalAccountBinding { kidString := os.Getenv("AUTOCERT_EAB_KID") keyString := os.Getenv("AUTOCERT_EAB_KEY") if kidString == "" || keyString == "" { return nil } key, err := base64.RawURLEncoding.DecodeString(keyString) if err != nil { return nil } return &acme.ExternalAccountBinding{ KID: kidString, Key: key, } } func getCertificateAutomatically(hostPolicy autocert.HostPolicy) GetCertificateFunc { manager := &autocert.Manager{ Client: &acme.Client{ DirectoryURL: os.Getenv("AUTOCERT_ACME_SERVER"), }, Prompt: autocert.AcceptTOS, Cache: getAutocertCache(), HostPolicy: hostPolicy, Email: os.Getenv("AUTOCERT_EMAIL"), ExternalAccountBinding: getAutocertEAB(), } return manager.GetCertificate } // Returns a GetCertificateFunc that automatically obtains certificates using ACME // for the given hostnames. Various environment variables can be used to customize the ACME // client. See the [go-listener README] for details. // // [go-listener README]: https://pkg.go.dev/src.agwa.name/go-listener#readme-acme-configuration func GetCertificateAutomatically(hostnames []string) GetCertificateFunc { if hostnames == nil { return getCertificateAutomatically(nil) } else { return getCertificateAutomatically(autocert.HostWhitelist(hostnames...)) } } go-listener-0.7.0/cert/cache.go000066400000000000000000000051441502762136100163040ustar00rootroot00000000000000// Copyright (C) 2022 Andrew Ayer // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // // Except as contained in this notice, the name(s) of the above copyright // holders shall not be used in advertising or otherwise to promote the // sale, use or other dealings in this Software without prior written // authorization. package cert import ( "crypto/tls" "os" "sync" "time" ) type cachedFile struct { *tls.Certificate modTime time.Time } func (c *cachedFile) isFresh(latestModTime time.Time) bool { return c.Certificate != nil && c.modTime.Equal(latestModTime) } type fileCache struct { mu sync.RWMutex certs map[string]cachedFile } func newFileCache() *fileCache { return &fileCache{ certs: make(map[string]cachedFile), } } func (c *fileCache) get(filename string) cachedFile { c.mu.RLock() defer c.mu.RUnlock() return c.certs[filename] } func (c *fileCache) add(filename string, cert cachedFile) { c.mu.Lock() defer c.mu.Unlock() c.certs[filename] = cert } func (c *fileCache) Load(filename string) (*tls.Certificate, error) { fileinfo, err := os.Stat(filename) if err != nil { return nil, err } modTime := fileinfo.ModTime() if cachedFile := c.get(filename); cachedFile.isFresh(modTime) { return cachedFile.Certificate, nil } cert, err := LoadCertificate(filename) if err != nil { return nil, err } c.add(filename, cachedFile{Certificate: cert, modTime: modTime}) return cert, nil } func (c *fileCache) Clean() { c.mu.Lock() defer c.mu.Unlock() now := time.Now() for filename, cert := range c.certs { if now.After(cert.Leaf.NotAfter) { delete(c.certs, filename) } } } go-listener-0.7.0/cert/cert.go000066400000000000000000000111001502762136100161630ustar00rootroot00000000000000// Copyright (C) 2022 Andrew Ayer // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // // Except as contained in this notice, the name(s) of the above copyright // holders shall not be used in advertising or otherwise to promote the // sale, use or other dealings in this Software without prior written // authorization. // Package cert provides helper functions for working with TLS certificates. package cert // import "src.agwa.name/go-listener/cert" import ( "crypto" "crypto/tls" "crypto/x509" "encoding/pem" "errors" "fmt" "os" ) // A function that returns a [tls.Certificate] based on the given [tls.ClientHelloInfo] type GetCertificateFunc func(*tls.ClientHelloInfo) (*tls.Certificate, error) // Load a [tls.Certificate] from the given PEM-encoded file. The file must contain // the following blocks: // - Exactly one PRIVATE KEY, containing the private key in PKCS#8 format. // - At least one CERTIFICATE, comprising the certificate chain, leaf certificate first and root certificate omitted. // - Up to one OCSP RESPONSE, containing a stapled OCSP response. // - Any number of SIGNED CERTIFICATE TIMESTAMP, containing stapled SCTs. func LoadCertificate(filename string) (*tls.Certificate, error) { data, err := os.ReadFile(filename) if err != nil { return nil, err } cert := new(tls.Certificate) for { block, rest := pem.Decode(data) if block == nil { break } switch block.Type { case "PRIVATE KEY", "RSA PRIVATE KEY", "EC PRIVATE KEY": if cert.PrivateKey != nil { return nil, errors.New("contains more than one private key") } cert.PrivateKey, err = makePrivateKey(block) if err != nil { return nil, fmt.Errorf("contains invalid private key: %w", err) } case "CERTIFICATE": cert.Certificate = append(cert.Certificate, block.Bytes) case "OCSP RESPONSE": if cert.OCSPStaple != nil { return nil, errors.New("contains more than one OCSP response") } cert.OCSPStaple = block.Bytes case "SIGNED CERTIFICATE TIMESTAMP": cert.SignedCertificateTimestamps = append(cert.SignedCertificateTimestamps, block.Bytes) default: return nil, errors.New("contains unrecognized PEM block `" + block.Type + "'") } data = rest } if cert.PrivateKey == nil { return nil, errors.New("doesn't contain any private key") } if len(cert.Certificate) == 0 { return nil, errors.New("doesn't contain any certificates") } cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0]) if err != nil { return nil, fmt.Errorf("contains invalid leaf certificate: %w", err) } return cert, nil } func parsePrivateKey(block *pem.Block) (crypto.PrivateKey, error) { switch block.Type { case "PRIVATE KEY": return x509.ParsePKCS8PrivateKey(block.Bytes) case "RSA PRIVATE KEY": return x509.ParsePKCS1PrivateKey(block.Bytes) case "EC PRIVATE KEY": return x509.ParseECPrivateKey(block.Bytes) default: return nil, errors.New("unrecognized private key type `" + block.Type + "'") } } func makePrivateKey(block *pem.Block) (crypto.PrivateKey, error) { key, err := parsePrivateKey(block) if err != nil { return nil, err } usage := block.Headers["Usage"] switch usage { case "": return key, nil case "decrypt": decrypter, ok := key.(crypto.Decrypter) if !ok { return nil, fmt.Errorf("this key type does not support decryption") } return struct{ crypto.Decrypter }{decrypter}, nil case "sign": signer, ok := key.(crypto.Signer) if !ok { return nil, fmt.Errorf("this key type does not support signing") } return struct{ crypto.Signer }{signer}, nil default: return nil, errors.New("unrecognized usage `" + usage + "'") } } go-listener-0.7.0/cert/dir.go000066400000000000000000000113351502762136100160160ustar00rootroot00000000000000// Copyright (C) 2022 Andrew Ayer // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // // Except as contained in this notice, the name(s) of the above copyright // holders shall not be used in advertising or otherwise to promote the // sale, use or other dealings in this Software without prior written // authorization. package cert import ( "crypto/tls" "errors" "fmt" "os" "path/filepath" "strings" ) type directory struct { Path string Cache *fileCache } func (dir *directory) loadCertificate(filename string) (*tls.Certificate, error) { fullpath := filepath.Join(dir.Path, filename) if dir.Cache != nil { return dir.Cache.Load(fullpath) } else { return LoadCertificate(fullpath) } } func (dir *directory) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { serverName := hello.ServerName if serverName == "" { return nil, errors.New("go-listener: client does not support SNI") } if serverName[0] == '.' || strings.IndexByte(hello.ServerName, '/') != -1 { return nil, errors.New("go-listener: client sent an invalid server name") } var serverNameSuffix string if dot := strings.IndexByte(serverName, '.'); dot != -1 { serverNameSuffix = serverName[dot:] } keyTypes := getSupportedKeyTypes(hello) if keyTypes.ecdsa { if cert, err := dir.loadCertificate(serverName + ".pem.ecdsa"); err == nil { return cert, nil } else if !os.IsNotExist(err) { // TODO: log this } if cert, err := dir.loadCertificate("_" + serverNameSuffix + ".pem.ecdsa"); err == nil { return cert, nil } else if !os.IsNotExist(err) { // TODO: log this } } if keyTypes.rsa { if cert, err := dir.loadCertificate(serverName + ".pem.rsa"); err == nil { return cert, nil } else if !os.IsNotExist(err) { // TODO: log this } if cert, err := dir.loadCertificate("_" + serverNameSuffix + ".pem.rsa"); err == nil { return cert, nil } else if !os.IsNotExist(err) { // TODO: log this } } if cert, err := dir.loadCertificate(serverName + ".pem"); err == nil { return cert, nil } else if !os.IsNotExist(err) { // TODO: log this } if cert, err := dir.loadCertificate("_" + serverNameSuffix + ".pem"); err == nil { return cert, nil } else if !os.IsNotExist(err) { // TODO: log this } return nil, fmt.Errorf("go-listener: no certificate found for %q", serverName) } // Return a [GetCertificateFunc] that gets the certificate from a file // in the given directory. The function searches for files in the // following order: // // 1. SERVER_NAME.pem.ecdsa (only if client supports ECDSA certificates) // 2. WILDCARD_NAME.pem.ecdsa (only if client supports ECDSA certificates) // 3. SERVER_NAME.pem.rsa (only if client supports RSA certificates) // 4. WILDCARD_NAME.pem.rsa (only if client supports RSA certificates) // 5. SERVER_NAME.pem // 6. WILDCARD_NAME.pem // // SERVER_NAME is the SNI hostname provided by the client, and WILDCARD_NAME // is the SNI hostname with the first label replaced with an underscore // (e.g. the wildcard name for www.example.com is _.example.com) // // Certificate files are cached in memory, and reloaded automatically when they // change, allowing zero-downtime certificate rotation. See the documentation of // [LoadCertificate] for the required format of the files. // // If no certificate file is found, or if the client does not // provide an SNI hostname, then the GetCertificateFunc returns an error, // causing the TLS connection to be terminated. If you need to support clients // that don't provide SNI, wrap the GetCertificateFunc with // [GetCertificateDefaultServerName] to specify a default SNI hostname. func GetCertificateFromDirectory(path string) GetCertificateFunc { dir := &directory{ Path: path, Cache: globalFileCache(), } return dir.GetCertificate } go-listener-0.7.0/cert/file.go000066400000000000000000000034651502762136100161640ustar00rootroot00000000000000// Copyright (C) 2022 Andrew Ayer // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // // Except as contained in this notice, the name(s) of the above copyright // holders shall not be used in advertising or otherwise to promote the // sale, use or other dealings in this Software without prior written // authorization. package cert import ( "crypto/tls" ) // Return a [GetCertificateFunc] that gets the certificate from the given file. // The file is reloaded automatically when it changes, allowing zero-downtime // certificate rotation. See the documentation of [LoadCertificate] for the // required format of the file. func GetCertificateFromFile(path string) GetCertificateFunc { cache := globalFileCache() return func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { return cache.Load(path) } } go-listener-0.7.0/cert/globalcache.go000066400000000000000000000033051502762136100174620ustar00rootroot00000000000000// Copyright (C) 2022 Andrew Ayer // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // // Except as contained in this notice, the name(s) of the above copyright // holders shall not be used in advertising or otherwise to promote the // sale, use or other dealings in this Software without prior written // authorization. package cert import ( "sync" "time" ) var ( globalFileCachePtr *fileCache globalFileCacheInit sync.Once ) func cleanGlobalFileCache() { for range time.Tick(24 * time.Hour) { globalFileCachePtr.Clean() } } func globalFileCache() *fileCache { globalFileCacheInit.Do(func() { globalFileCachePtr = newFileCache() go cleanGlobalFileCache() }) return globalFileCachePtr } go-listener-0.7.0/cert/keytype.go000066400000000000000000000064141502762136100167340ustar00rootroot00000000000000// Copyright (C) 2023 Andrew Ayer // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // // Except as contained in this notice, the name(s) of the above copyright // holders shall not be used in advertising or otherwise to promote the // sale, use or other dealings in this Software without prior written // authorization. package cert import ( "crypto/tls" ) type keyTypes struct { rsa bool ecdsa bool } func getSupportedKeyTypes(clientHello *tls.ClientHelloInfo) keyTypes { if clientHello.SignatureSchemes != nil { return keyTypesForSignatureSchemes(clientHello.SignatureSchemes) } else { return keyTypesForCipherSuites(clientHello.CipherSuites) } } func keyTypesForSignatureSchemes(schemes []tls.SignatureScheme) keyTypes { var types keyTypes for _, scheme := range schemes { switch scheme { case tls.ECDSAWithSHA1, tls.ECDSAWithP256AndSHA256, tls.ECDSAWithP384AndSHA384, tls.ECDSAWithP521AndSHA512: types.ecdsa = true case tls.PKCS1WithSHA1, tls.PKCS1WithSHA256, tls.PKCS1WithSHA384, tls.PKCS1WithSHA512, tls.PSSWithSHA256, tls.PSSWithSHA384, tls.PSSWithSHA512: types.rsa = true } } return types } func keyTypesForCipherSuites(suites []uint16) keyTypes { var types keyTypes for _, suite := range suites { switch suite { case tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: types.ecdsa = true case tls.TLS_RSA_WITH_RC4_128_SHA, tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, tls.TLS_RSA_WITH_AES_128_CBC_SHA, tls.TLS_RSA_WITH_AES_256_CBC_SHA, tls.TLS_RSA_WITH_AES_128_CBC_SHA256, tls.TLS_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA, tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: types.rsa = true } } return types } go-listener-0.7.0/cert/sni.go000066400000000000000000000035771502762136100160420ustar00rootroot00000000000000// Copyright (C) 2022 Andrew Ayer // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // // Except as contained in this notice, the name(s) of the above copyright // holders shall not be used in advertising or otherwise to promote the // sale, use or other dealings in this Software without prior written // authorization. package cert import ( "crypto/tls" ) // Wraps a [GetCertificateFunc] with logic that sets [tls.ClientHelloInfo.ServerName] // to defaultServerName if it is empty (e.g. because the client does not support SNI). func GetCertificateDefaultServerName(defaultServerName string, getCertificate GetCertificateFunc) GetCertificateFunc { return func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { if hello.ServerName == "" { newHello := *hello newHello.ServerName = defaultServerName return getCertificate(&newHello) } return getCertificate(hello) } } go-listener-0.7.0/close.go000066400000000000000000000030671502762136100154130ustar00rootroot00000000000000// Copyright (C) 2022 Andrew Ayer // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // // Except as contained in this notice, the name(s) of the above copyright // holders shall not be used in advertising or otherwise to promote the // sale, use or other dealings in this Software without prior written // authorization. package listener // import "src.agwa.name/go-listener" import ( "net" ) // CloseAll invokes Close on every listener. Errors are ignored. func CloseAll(listeners []net.Listener) { for _, listener := range listeners { listener.Close() } } go-listener-0.7.0/go.mod000066400000000000000000000002531502762136100150570ustar00rootroot00000000000000module src.agwa.name/go-listener go 1.23.0 require golang.org/x/crypto v0.12.0 require ( golang.org/x/net v0.14.0 // indirect golang.org/x/text v0.12.0 // indirect ) go-listener-0.7.0/go.sum000066400000000000000000000057351502762136100151160ustar00rootroot00000000000000golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f h1:OeJjE6G4dgCY4PIXvIRQbE8+RX+uXZyGhUy/ksMGJoc= golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220708220712-1185a9018129 h1:vucSRfWwTsoXro7P+3Cjlr6flUMtzCwzlvkxEQtHHB0= golang.org/x/net v0.0.0-20220708220712-1185a9018129/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b h1:ZmngSVLe/wycRns9MKikG9OWIEjGcGAkacif7oYQaUY= golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= go-listener-0.7.0/listener.go000066400000000000000000000056371502762136100161400ustar00rootroot00000000000000// Copyright (C) 2022 Andrew Ayer // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // // Except as contained in this notice, the name(s) of the above copyright // holders shall not be used in advertising or otherwise to promote the // sale, use or other dealings in this Software without prior written // authorization. package listener // import "src.agwa.name/go-listener" import ( "net" "sync" ) // A function that is called by [Open] or [OpenJSON] to create a [net.Listener] of // a particular type. If called by Open, then the first argument is nil, // and the second argument is the string passed to Open, with the listener // type and colon character removed. If called by OpenJSON, the first argument // is the JSON object passed to OpenJSON, and the second argument is empty. // // You only need to care about this if you are extending go-listener with // your own custom listener types using [RegisterListenerType]. type OpenListenerFunc func(map[string]interface{}, string) (net.Listener, error) var ( listenerTypes = make(map[string]OpenListenerFunc) listenerTypesMu sync.RWMutex ) // RegisterListenerType makes a listener type available by the provided name. // Use this function to extend go-listener with your own custom listener types. // See the documentation for [OpenListenerFunc] for details. // // If RegisterListenerType is called twice with the same name or if // openListener is nil, it panics. func RegisterListenerType(name string, openListener OpenListenerFunc) { listenerTypesMu.Lock() defer listenerTypesMu.Unlock() if openListener == nil { panic("RegisterListenerType: openListener is nil") } if _, isDup := listenerTypes[name]; isDup { panic("RegisterListenerType: called twice for " + name) } listenerTypes[name] = openListener } func getOpenListenerFunc(listenerType string) OpenListenerFunc { listenerTypesMu.RLock() defer listenerTypesMu.RUnlock() return listenerTypes[listenerType] } go-listener-0.7.0/multi.go000066400000000000000000000065421502762136100154410ustar00rootroot00000000000000// Copyright (C) 2023 Andrew Ayer // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // // Except as contained in this notice, the name(s) of the above copyright // holders shall not be used in advertising or otherwise to promote the // sale, use or other dealings in this Software without prior written // authorization. package listener // import "src.agwa.name/go-listener" import ( "errors" "net" "sync" ) type multiAddr struct{} func (multiAddr) Network() string { return "multi" } func (multiAddr) String() string { return "multi" } type multiListener struct { listeners []net.Listener closed chan struct{} conns chan net.Conn errors chan error closeMu sync.Mutex } // Create a net.Listener that aggregates the provided listeners. Calling Accept() returns // the next available connection among all the listeners. Calling Close() closes each of // the listeners, and causes blocked Accept calls to return with net.ErrClosed. Addr() // returns a placeholder address that is probably not useful. func MultiListener(listeners ...net.Listener) net.Listener { ml := &multiListener{ listeners: listeners, closed: make(chan struct{}), conns: make(chan net.Conn), errors: make(chan error), } for _, l := range ml.listeners { l := l go ml.handleAccepts(l) } return ml } func (ml *multiListener) handleAccepts(l net.Listener) { for { conn, err := l.Accept() if errors.Is(err, net.ErrClosed) { break } else if err != nil { if !ml.sendError(err) { break } } else { if !ml.sendConn(conn) { conn.Close() break } } } } func (ml *multiListener) sendError(err error) bool { select { case <-ml.closed: return false case ml.errors <- err: return true } } func (ml *multiListener) sendConn(conn net.Conn) bool { select { case <-ml.closed: return false case ml.conns <- conn: return true } } func (ml *multiListener) Accept() (net.Conn, error) { select { case <-ml.closed: return nil, net.ErrClosed case conn := <-ml.conns: return conn, nil case err := <-ml.errors: return nil, err } } func (ml *multiListener) Close() error { ml.closeMu.Lock() defer ml.closeMu.Unlock() select { case <-ml.closed: return net.ErrClosed default: close(ml.closed) for _, l := range ml.listeners { l.Close() } return nil } } func (ml *multiListener) Addr() net.Addr { return multiAddr{} } go-listener-0.7.0/open.go000066400000000000000000000056511502762136100152500ustar00rootroot00000000000000// Copyright (C) 2022 Andrew Ayer // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // // Except as contained in this notice, the name(s) of the above copyright // holders shall not be used in advertising or otherwise to promote the // sale, use or other dealings in this Software without prior written // authorization. // Package listener provides functions for creating net.Listeners package listener // import "src.agwa.name/go-listener" import ( "errors" "fmt" "net" "strings" ) func openType(listenerType string, params map[string]interface{}, argument string) (net.Listener, error) { openListener := getOpenListenerFunc(listenerType) if openListener == nil { return nil, fmt.Errorf("Unknown listener type: " + listenerType) } return openListener(params, argument) } // Open a listener with the given string notation func Open(spec string) (net.Listener, error) { if strings.Contains(spec, ":") { fields := strings.SplitN(spec, ":", 2) listenerType, arg := fields[0], fields[1] return openType(listenerType, nil, arg) } else { return openTCPListener(nil, spec) } } // Open all of the listeners specified in specs (using string notation). // If any listener fails to open, an error is returned, and none of the // listeners are left open. func OpenAll(specs []string) ([]net.Listener, error) { listeners := []net.Listener{} for _, spec := range specs { listener, err := Open(spec) if err != nil { CloseAll(listeners) return nil, fmt.Errorf("%s: %w", spec, err) } listeners = append(listeners, listener) } return listeners, nil } // Experimental: Open a listener with the given JSON notation. Note that numbers in spec // must be represented using [json.Number]. func OpenJSON(spec map[string]interface{}) (net.Listener, error) { listenerType, ok := spec["type"].(string) if !ok { return nil, errors.New("listener object does not contain a string type field") } return openType(listenerType, spec, "") } go-listener-0.7.0/proxy/000077500000000000000000000000001502762136100151325ustar00rootroot00000000000000go-listener-0.7.0/proxy/conn.go000066400000000000000000000032061502762136100164170ustar00rootroot00000000000000// Copyright (C) 2022 Andrew Ayer // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // // Except as contained in this notice, the name(s) of the above copyright // holders shall not be used in advertising or otherwise to promote the // sale, use or other dealings in this Software without prior written // authorization. package proxy import ( "net" ) type proxyConn struct { net.Conn localAddr net.Addr remoteAddr net.Addr } func (conn *proxyConn) LocalAddr() net.Addr { return conn.localAddr } func (conn *proxyConn) RemoteAddr() net.Addr { return conn.remoteAddr } func (conn *proxyConn) NetConn() net.Conn { return conn.Conn } go-listener-0.7.0/proxy/error.go000066400000000000000000000032121502762136100166100ustar00rootroot00000000000000// Copyright (C) 2022 Andrew Ayer // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // // Except as contained in this notice, the name(s) of the above copyright // holders shall not be used in advertising or otherwise to promote the // sale, use or other dealings in this Software without prior written // authorization. package proxy import ( "net" ) type acceptError struct { error temporary bool } func (err *acceptError) Temporary() bool { return err.temporary } func (err *acceptError) Timeout() bool { return false } var _ net.Error = (*acceptError)(nil) // Cause compile error if acceptError does not implement net.Error interface go-listener-0.7.0/proxy/header.go000066400000000000000000000145571502762136100167250ustar00rootroot00000000000000// Copyright (C) 2022 Andrew Ayer // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // // Except as contained in this notice, the name(s) of the above copyright // holders shall not be used in advertising or otherwise to promote the // sale, use or other dealings in this Software without prior written // authorization. package proxy import ( "bytes" "encoding/binary" "errors" "fmt" "io" "net" ) var protocolSignature = [12]byte{0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A} const protocolVersion = 2 const ( commandLocal = 0x00 commandProxy = 0x01 ) const ( familyUnspecified = 0x00 familyTCP4 = 0x11 familyUDP4 = 0x12 familyTCP6 = 0x21 familyUDP6 = 0x22 ) // Header represents a PROXY protocol header type Header struct { RemoteAddr net.Addr LocalAddr net.Addr } // Read the PROXY protocol header from conn func ReadHeader(conn net.Conn) (*Header, error) { var preamble [16]byte if _, err := io.ReadFull(conn, preamble[:]); err != nil { return nil, err } var ( signature = preamble[0:12] version = preamble[12] >> 4 command = preamble[12] & 0xF family = preamble[13] length = binary.BigEndian.Uint16(preamble[14:16]) ) if !bytes.Equal(signature[:], protocolSignature[:]) { return nil, errors.New("not a proxied connection") } if version != protocolVersion { return nil, errors.New("unsupported proxy protocol version") } payload := make([]byte, length) if _, err := io.ReadFull(conn, payload); err != nil { return nil, err } switch command { case commandLocal: return &Header{LocalAddr: conn.LocalAddr(), RemoteAddr: conn.RemoteAddr()}, nil case commandProxy: return parseProxyHeader(family, payload) default: return nil, fmt.Errorf("unsupported proxy command %x", command) } } func parseProxyHeader(family uint8, payload []byte) (*Header, error) { switch family { case familyTCP4: if len(payload) < 12 { return nil, errors.New("header too short for TCP over IPv4") } return &Header{ RemoteAddr: &net.TCPAddr{ IP: payload[0:4], Port: int(binary.BigEndian.Uint16(payload[8:10])), }, LocalAddr: &net.TCPAddr{ IP: payload[4:8], Port: int(binary.BigEndian.Uint16(payload[10:12])), }, }, nil case familyUDP4: if len(payload) < 12 { return nil, errors.New("header too short for UDP over IPv4") } return &Header{ RemoteAddr: &net.UDPAddr{ IP: payload[0:4], Port: int(binary.BigEndian.Uint16(payload[8:10])), }, LocalAddr: &net.UDPAddr{ IP: payload[4:8], Port: int(binary.BigEndian.Uint16(payload[10:12])), }, }, nil case familyTCP6: if len(payload) < 36 { return nil, errors.New("header too short for TCP over IPv6") } return &Header{ RemoteAddr: &net.TCPAddr{ IP: payload[0:16], Port: int(binary.BigEndian.Uint16(payload[32:34])), }, LocalAddr: &net.TCPAddr{ IP: payload[16:32], Port: int(binary.BigEndian.Uint16(payload[34:36])), }, }, nil case familyUDP6: if len(payload) < 36 { return nil, errors.New("header too short for UDP over IPv6") } return &Header{ RemoteAddr: &net.UDPAddr{ IP: payload[0:16], Port: int(binary.BigEndian.Uint16(payload[32:34])), }, LocalAddr: &net.UDPAddr{ IP: payload[16:32], Port: int(binary.BigEndian.Uint16(payload[34:36])), }, }, nil default: return nil, fmt.Errorf("unsupported address family %x", family) } } // Return the wire representation of header func (header Header) Format() []byte { switch remoteAddr := header.RemoteAddr.(type) { case *net.TCPAddr: localAddr := header.LocalAddr.(*net.TCPAddr) if remoteAddr.IP.To4() != nil { return formatIPv4Header(familyTCP4, remoteAddr.IP, localAddr.IP, remoteAddr.Port, localAddr.Port) } else { return formatIPv6Header(familyTCP6, remoteAddr.IP, localAddr.IP, remoteAddr.Port, localAddr.Port) } case *net.UDPAddr: localAddr := header.LocalAddr.(*net.UDPAddr) if remoteAddr.IP.To4() != nil { return formatIPv4Header(familyUDP4, remoteAddr.IP, localAddr.IP, remoteAddr.Port, localAddr.Port) } else { return formatIPv6Header(familyUDP6, remoteAddr.IP, localAddr.IP, remoteAddr.Port, localAddr.Port) } default: return formatUnspecifiedHeader() } } func formatIPv4Header(family uint8, remoteIP, localIP net.IP, remotePort, localPort int) []byte { header := make([]byte, 28) copy(header[0:12], protocolSignature[:]) header[12] = (protocolVersion << 4) | commandProxy header[13] = family binary.BigEndian.PutUint16(header[14:16], 12) copy(header[16:20], remoteIP.To4()) copy(header[20:24], localIP.To4()) binary.BigEndian.PutUint16(header[24:26], uint16(remotePort)) binary.BigEndian.PutUint16(header[26:28], uint16(localPort)) return header[:] } func formatIPv6Header(family uint8, remoteIP, localIP net.IP, remotePort, localPort int) []byte { header := make([]byte, 52) copy(header[0:12], protocolSignature[:]) header[12] = (protocolVersion << 4) | commandProxy header[13] = family binary.BigEndian.PutUint16(header[14:16], 36) copy(header[16:32], remoteIP) copy(header[32:48], localIP) binary.BigEndian.PutUint16(header[48:50], uint16(remotePort)) binary.BigEndian.PutUint16(header[50:52], uint16(localPort)) return header[:] } func formatUnspecifiedHeader() []byte { var header [16]byte copy(header[0:12], protocolSignature[:]) header[12] = (protocolVersion << 4) | commandProxy header[13] = familyUnspecified return header[:] } go-listener-0.7.0/proxy/listener.go000066400000000000000000000100011502762136100172760ustar00rootroot00000000000000// Copyright (C) 2022 Andrew Ayer // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // // Except as contained in this notice, the name(s) of the above copyright // holders shall not be used in advertising or otherwise to promote the // sale, use or other dealings in this Software without prior written // authorization. // Package proxy implements version 2 of the PROXY protocol (https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) package proxy // import "src.agwa.name/go-listener/proxy" import ( "errors" "fmt" "net" "sync" "time" ) type proxyListener struct { inner net.Listener conns chan net.Conn errors chan error done chan struct{} closeMu sync.Mutex } // NewListener creates a [net.Listener] which accepts connections from an // inner net.Listener, reads the PROXY v2 header from the client, and // sets the local and remote addresses of the [net.Conn] to the values // specified in the PROXY header. func NewListener(inner net.Listener) net.Listener { listener := &proxyListener{ inner: inner, conns: make(chan net.Conn), errors: make(chan error), done: make(chan struct{}), } go listener.handleAccepts() return listener } func (listener *proxyListener) Accept() (net.Conn, error) { select { case conn := <-listener.conns: return conn, nil case err := <-listener.errors: return nil, err case <-listener.done: return nil, net.ErrClosed } } func (listener *proxyListener) Close() error { listener.closeMu.Lock() defer listener.closeMu.Unlock() select { case <-listener.done: return net.ErrClosed default: close(listener.done) return listener.inner.Close() } } func (listener *proxyListener) Addr() net.Addr { return listener.inner.Addr() } func (listener *proxyListener) handleAccepts() { for { conn, err := listener.inner.Accept() if errors.Is(err, net.ErrClosed) { break } else if err != nil { if !listener.sendError(err) { break } } else { go listener.handleConnection(conn) } } } func (listener *proxyListener) handleConnection(conn net.Conn) { if err := conn.SetReadDeadline(time.Now().Add(1 * time.Minute)); err != nil { conn.Close() listener.sendError(&acceptError{error: err, temporary: true}) return } header, err := ReadHeader(conn) if err != nil { conn.Close() err = fmt.Errorf("reading proxy header: %w", err) listener.sendError(&acceptError{error: err, temporary: true}) return } if err := conn.SetReadDeadline(time.Time{}); err != nil { conn.Close() listener.sendError(&acceptError{error: err, temporary: true}) return } proxyConn := &proxyConn{ Conn: conn, localAddr: header.LocalAddr, remoteAddr: header.RemoteAddr, } if !listener.sendConn(proxyConn) { proxyConn.Close() } } func (listener *proxyListener) sendError(err error) bool { select { case listener.errors <- err: return true case <-listener.done: return false } } func (listener *proxyListener) sendConn(conn net.Conn) bool { select { case listener.conns <- conn: return true case <-listener.done: return false } } go-listener-0.7.0/tls/000077500000000000000000000000001502762136100145535ustar00rootroot00000000000000go-listener-0.7.0/tls/listener.go000066400000000000000000000076601502762136100167400ustar00rootroot00000000000000// Copyright (C) 2022 Andrew Ayer // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // // Except as contained in this notice, the name(s) of the above copyright // holders shall not be used in advertising or otherwise to promote the // sale, use or other dealings in this Software without prior written // authorization. // Package tls adds support for TLS listeners to src.agwa.name/go-listener. // // This package contains no exported identifiers and is intended to be // imported from package main like this: // // import _ "src.agwa.name/go-listener/tls" // package tls // import "src.agwa.name/go-listener/tls" import ( "crypto/tls" "errors" "net" "strings" "golang.org/x/crypto/acme" "src.agwa.name/go-listener" "src.agwa.name/go-listener/cert" ) func init() { listener.RegisterListenerType("tls", openHTTPSListener) // TODO: either remove this listener type or replace it with a generic TLS non-HTTPS listener listener.RegisterListenerType("https", openHTTPSListener) } func openHTTPSListener(params map[string]interface{}, arg string) (net.Listener, error) { var getCertificate cert.GetCertificateFunc var nextProtos = []string{"h2", "http/1.1"} var inner net.Listener var err error if arg != "" { fields := strings.SplitN(arg, ":", 2) if len(fields) < 2 { return nil, errors.New("TLS listener spec invalid; must be CERT_SPEC:SOCKET_SPEC") } certSpec, innerSpec := fields[0], fields[1] if strings.HasPrefix(certSpec, "/") && strings.HasSuffix(certSpec, "/") { getCertificate = cert.GetCertificateFromDirectory(certSpec) } else if strings.HasPrefix(certSpec, "/") { getCertificate = cert.GetCertificateFromFile(certSpec) } else { getCertificate = cert.GetCertificateAutomatically(strings.Split(certSpec, ",")) nextProtos = append(nextProtos, acme.ALPNProto) } inner, err = listener.Open(innerSpec) if err != nil { return nil, err } } else { if path, ok := params["cert"].(string); ok { getCertificate = cert.GetCertificateFromFile(path) } else if path, ok := params["cert_directory"].(string); ok { getCertificate = cert.GetCertificateFromDirectory(path) } else if hostnames, ok := params["autocert_hostnames"].([]string); ok { getCertificate = cert.GetCertificateAutomatically(hostnames) nextProtos = append(nextProtos, acme.ALPNProto) } else { return nil, errors.New("certificate not specified for TLS listener") } innerSpec, ok := params["listener"].(map[string]interface{}) if !ok { return nil, errors.New("inner socket not specified for TLS listener") } inner, err = listener.OpenJSON(innerSpec) if err != nil { return nil, err } } if defaultServerName, ok := params["default_server_name"].(string); ok && defaultServerName != "" { getCertificate = cert.GetCertificateDefaultServerName(defaultServerName, getCertificate) } config := &tls.Config{ GetCertificate: getCertificate, NextProtos: nextProtos, } return tls.NewListener(inner, config), nil } go-listener-0.7.0/tlsutil/000077500000000000000000000000001502762136100154515ustar00rootroot00000000000000go-listener-0.7.0/tlsutil/hello.go000066400000000000000000000063151502762136100171100ustar00rootroot00000000000000// Copyright (C) 2022 Andrew Ayer // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // // Except as contained in this notice, the name(s) of the above copyright // holders shall not be used in advertising or otherwise to promote the // sale, use or other dealings in this Software without prior written // authorization. // Package tlsutil provides helper functions for working with TLS. package tlsutil // import "src.agwa.name/go-listener/tlsutil" import ( "bytes" "crypto/tls" "io" "net" "time" ) type peekedConn struct { net.Conn reader io.Reader } func (conn peekedConn) Read(p []byte) (int, error) { return conn.reader.Read(p) } func PeekClientHelloFromConn(conn net.Conn) (*tls.ClientHelloInfo, net.Conn, error) { hello, reader, err := PeekClientHello(conn) return hello, peekedConn{Conn: conn, reader: reader}, err } func PeekClientHello(reader io.Reader) (*tls.ClientHelloInfo, io.Reader, error) { peekedBytes := new(bytes.Buffer) hello, err := ReadClientHello(io.TeeReader(reader, peekedBytes)) if err != nil { return nil, io.MultiReader(peekedBytes, reader), err } return hello, io.MultiReader(peekedBytes, reader), nil } func ReadClientHello(reader io.Reader) (*tls.ClientHelloInfo, error) { var hello *tls.ClientHelloInfo err := tls.Server(readOnlyConn{reader: reader}, &tls.Config{ GetConfigForClient: func(argHello *tls.ClientHelloInfo) (*tls.Config, error) { hello = new(tls.ClientHelloInfo) *hello = *argHello return nil, nil }, }).Handshake() if hello == nil { return nil, err } return hello, nil } type readOnlyConn struct { reader io.Reader } func (conn readOnlyConn) Read(p []byte) (int, error) { return conn.reader.Read(p) } func (conn readOnlyConn) Write(p []byte) (int, error) { return 0, io.ErrClosedPipe } func (conn readOnlyConn) Close() error { return nil } func (conn readOnlyConn) LocalAddr() net.Addr { return nil } func (conn readOnlyConn) RemoteAddr() net.Addr { return nil } func (conn readOnlyConn) SetDeadline(t time.Time) error { return nil } func (conn readOnlyConn) SetReadDeadline(t time.Time) error { return nil } func (conn readOnlyConn) SetWriteDeadline(t time.Time) error { return nil } go-listener-0.7.0/unix/000077500000000000000000000000001502762136100147345ustar00rootroot00000000000000go-listener-0.7.0/unix/listener.go000066400000000000000000000070031502762136100171100ustar00rootroot00000000000000// Copyright (C) 2022 Andrew Ayer // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // // Except as contained in this notice, the name(s) of the above copyright // holders shall not be used in advertising or otherwise to promote the // sale, use or other dealings in this Software without prior written // authorization. // Package unix implements a net.Listener for UNIX domain sockets. package unix // import "src.agwa.name/go-listener/unix" import ( "fmt" "net" "os" "path/filepath" "time" ) type watchedListener struct { listener *net.UnixListener closed chan struct{} } func newWatchedListener(listener *net.UnixListener) *watchedListener { return &watchedListener{ listener: listener, closed: make(chan struct{}), } } func (wl *watchedListener) Accept() (net.Conn, error) { return wl.listener.Accept() } func (wl *watchedListener) Close() error { close(wl.closed) return wl.listener.Close() } func (wl *watchedListener) Addr() net.Addr { return wl.listener.Addr() } func (wl *watchedListener) watch(path string, info os.FileInfo) { ticker := time.NewTicker(5 * time.Second) defer ticker.Stop() for { select { case <-wl.closed: return case <-ticker.C: latestInfo, err := os.Lstat(path) if !(err == nil && os.SameFile(info, latestInfo)) { wl.listener.Close() return } } } } // Create a listening UNIX domain socket with the given path and filesystem // permissions. If a file already exists at the path, it is replaced. If // the UNIX domain socket file is removed or changed, then within 5 seconds // the net.Listener will be closed, and Accept will return an error. func Listen(path string, mode os.FileMode) (net.Listener, error) { tempDir, err := os.MkdirTemp(filepath.Dir(path), ".tmp") if err != nil { return nil, fmt.Errorf("error creating temporary directory to hold Unix socket: %w", err) } defer os.Remove(tempDir) tempPath := filepath.Join(tempDir, "socket") tempListener, err := net.ListenUnix("unix", &net.UnixAddr{Net: "unix", Name: tempPath}) if err != nil { return nil, err } tempListener.SetUnlinkOnClose(false) defer os.Remove(tempPath) defer func() { if tempListener != nil { tempListener.Close() } }() if err := os.Chmod(tempPath, mode); err != nil { return nil, err } fileInfo, err := os.Lstat(tempPath) if err != nil { return nil, err } if err := os.Rename(tempPath, path); err != nil { return nil, err } listener := newWatchedListener(tempListener) tempListener = nil go listener.watch(path, fileInfo) return listener, nil }