mirror of
https://github.com/gravitational/teleport.git
synced 2026-11-01 21:52:02 +08:00
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/
234 lines
7.3 KiB
Go
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)
|
|
}
|