Files
teleport/lib/sshutils/x11/display.go
T
Alan Parra a75fcc21d8 Update golangci-lint to 1.49.0 (#16507)
Update metalinter, fix a few lint warnings and replace deprecated linters.

`deadcode`, `structcheck` and `varcheck` are abandoned and now replaced by [`unused`][1].

Since 1.19, `go fmt` reformats godocs according to https://go.dev/doc/comment. I've done a bulk-reformatting of the codebase to keep the linter happy. Backporting is mostly harmless (the exception being `lib/services/role_test.go`, that for some reason breaks the _old_ linter using the new format).

[1]: https://golangci-lint.run/usage/linters/

* Bump golangci-lint version
* Replace abandoned linters
* Fix bodyclose on lib/auth/github.com
* Fix bodyclose on lib/kube/proxy/streamproto/proto_test.go
* Fix bodyclose on lib/srv/alpnproxy/proxy_test.go
* Fix bodyclose on lib/web/conn_upgrade_test.go
* Silence staticcheck on lib/kube/proxy/forwarder_test.go
* Silence staticcheck on lib/utils/certs_test.go
* Address BuildNameToCertificate deprecation warnings
* Run `go fmt ./...`
* Run `go fmt ./...` on api/
* Ignore formatting in role_test.go
* Remove redundant initializers in lib/srv/uacc/
* Update e/
2022-09-19 22:38:59 +00:00

234 lines
7.3 KiB
Go

// Copyright 2022 Gravitational, Inc
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package x11
import (
"errors"
"fmt"
"math"
"net"
"os"
"path/filepath"
"strconv"
"strings"
"syscall"
"unicode"
"github.com/gravitational/trace"
)
const (
// DefaultDisplayOffset is the default display offset when
// searching for an open XServer unix socket.
DefaultDisplayOffset = 10
// DefaultMaxDisplays is the default maximum number of displays
// supported when searching for an open XServer unix socket.
DefaultMaxDisplays = 1000
// MaxDisplay is the theoretical max display value which
// X Clients and serverwill be able to parse into a unix socket.
MaxDisplayNumber = math.MaxInt32
// DisplayEnv is an environment variable used to determine what
// local display should be connected to during X11 forwarding.
DisplayEnv = "DISPLAY"
// x11BasePort is the base port used for XServer tcp addresses.
// e.g. DISPLAY=localhost:10 -> net.Dial("tcp", "localhost:6010")
// Used by some XServer clients, such as openSSH and MobaXTerm.
x11BasePort = 6000
// x11SocketDirName is the name of the directory where X11 unix sockets are kept.
x11SocketDirName = ".X11-unix"
)
// Display is an XServer display.
type Display struct {
// HostName is the the display's hostname. For tcp display sockets, this will be
// an ip address. For unix display sockets, this will be empty or "unix".
HostName string `json:"hostname"`
// DisplayNumber is a number representing an x display.
DisplayNumber int `json:"display_number"`
// ScreenNumber is a specific screen number of an x display.
ScreenNumber int `json:"screen_number"`
}
// String returns the string representation of the display.
func (d *Display) String() string {
return fmt.Sprintf("%s:%d.%d", d.HostName, d.DisplayNumber, d.ScreenNumber)
}
// Dial opens an XServer connection to the display
func (d *Display) Dial() (XServerConn, error) {
var conn XServerConn
unixSock, unixErr := d.unixSocket()
if unixErr == nil {
if conn, unixErr = net.DialUnix("unix", nil, unixSock); unixErr == nil {
return conn, nil
}
}
tcpSock, tcpErr := d.tcpSocket()
if tcpErr == nil {
if conn, tcpErr = net.DialTCP("tcp", nil, tcpSock); tcpErr == nil {
return conn, nil
}
}
return nil, trace.NewAggregate(unixErr, tcpErr)
}
// Listen opens an XServer listener. It will attempt to listen on the display
// address for both tcp and unix and return an aggregate error, unless one
// results in an addr in use error.
func (d *Display) Listen() (XServerListener, error) {
unixSock, unixErr := d.unixSocket()
if unixErr == nil {
var l *net.UnixListener
if l, unixErr = net.ListenUnix("unix", unixSock); unixErr == nil {
return &xserverUnixListener{l}, nil
} else if errors.Is(unixErr, syscall.EADDRINUSE) {
return nil, trace.Wrap(unixErr)
}
}
tcpSock, tcpErr := d.tcpSocket()
if tcpErr == nil {
var l *net.TCPListener
if l, tcpErr = net.ListenTCP("tcp", tcpSock); tcpErr == nil {
return &xserverTCPListener{l}, nil
} else if errors.Is(tcpErr, syscall.EADDRINUSE) {
return nil, trace.Wrap(tcpErr)
}
}
return nil, trace.NewAggregate(unixErr, tcpErr)
}
// xserverUnixSocket returns the display's associated unix socket.
func (d *Display) unixSocket() (*net.UnixAddr, error) {
// If hostname is "unix" or empty, then the actual unix socket
// for the display is "/tmp/.X11-unix/X<display_number>"
if d.HostName == "unix" || d.HostName == "" {
sockName := filepath.Join(x11SockDir(), fmt.Sprintf("X%d", d.DisplayNumber))
return net.ResolveUnixAddr("unix", sockName)
}
// It's possible that the display is actually the full path
// to an open XServer socket, such as with xquartz on OSX:
// "/private/tmp/com.apple.com/launchd.xxx/org.xquartz.com:0"
if d.HostName[0] == '/' {
sockName := d.String()
if _, err := os.Stat(sockName); err == nil {
return net.ResolveUnixAddr("unix", sockName)
}
// The socket might not include the screen number.
sockName = fmt.Sprintf("%s:%d", d.HostName, d.DisplayNumber)
if _, err := os.Stat(sockName); err == nil {
return net.ResolveUnixAddr("unix", sockName)
}
}
return nil, trace.BadParameter("display is not a unix socket")
}
// xserverTCPSocket returns the display's associated tcp socket.
// e.g. "hostname:<6000+display_number>"
func (d *Display) tcpSocket() (*net.TCPAddr, error) {
if d.HostName == "" {
return nil, trace.BadParameter("display is not a tcp socket, hostname can't be empty")
}
port := fmt.Sprint(d.DisplayNumber + x11BasePort)
rawAddr := net.JoinHostPort(d.HostName, port)
addr, err := net.ResolveTCPAddr("tcp", rawAddr)
if err != nil {
return nil, trace.Wrap(err)
}
return addr, nil
}
// GetXDisplay retrieves and validates the local XServer display
// set in the environment variable $DISPLAY.
func GetXDisplay() (Display, error) {
displayString := os.Getenv(DisplayEnv)
if displayString == "" {
return Display{}, trace.BadParameter("$DISPLAY not set")
}
display, err := ParseDisplay(displayString)
if err != nil {
return Display{}, trace.Wrap(err)
}
return display, nil
}
// ParseDisplay parses the given display value and returns the host,
// display number, and screen number, or a parsing error. display must be
// in one of the following formats - hostname:d[.s], unix:d[.s], :d[.s], ::d[.s].
func ParseDisplay(displayString string) (Display, error) {
if displayString == "" {
return Display{}, trace.BadParameter("display cannot be an empty string")
}
// check the display for illegal characters in case of code injection attempt
allowedSpecialChars := ":/.-_" // chars used for hostname or display delimiters.
for _, c := range displayString {
if !(unicode.IsLetter(c) || unicode.IsNumber(c) || strings.ContainsRune(allowedSpecialChars, c)) {
return Display{}, trace.BadParameter("display contains invalid character %q", c)
}
}
// Parse hostname.
colonIdx := strings.LastIndex(displayString, ":")
if colonIdx == -1 || len(displayString) == colonIdx+1 {
return Display{}, trace.BadParameter("display value is missing display number")
}
var display Display
if displayString[0] == ':' {
display.HostName = ""
} else {
display.HostName = displayString[:colonIdx]
}
// Parse display number and screen number
splitDot := strings.Split(displayString[colonIdx+1:], ".")
displayNumber, err := strconv.ParseUint(splitDot[0], 10, 0)
if err != nil {
return Display{}, trace.Wrap(err)
}
display.DisplayNumber = int(displayNumber)
if len(splitDot) < 2 {
return display, nil
}
screenNumber, err := strconv.ParseUint(splitDot[1], 10, 0)
if err != nil {
return Display{}, trace.Wrap(err)
}
display.ScreenNumber = int(screenNumber)
return display, nil
}
func x11SockDir() string {
// We use "/tmp" instead of "os.TempDir" because X11
// is not OS aware and uses "/tmp" regardless of OS.
return filepath.Join("/tmp", x11SocketDirName)
}