1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
|
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package safesocket
import (
"bufio"
"bytes"
"errors"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
)
func init() {
localTCPPortAndToken = localTCPPortAndTokenDarwin
}
// localTCPPortAndTokenMacsys returns the localhost TCP port number and auth token
// from /Library/Tailscale.
//
// In that case the files are:
// /Library/Tailscale/ipnport => $port (symlink with localhost port number target)
// /Library/Tailscale/sameuserproof-$port is a file with auth
func localTCPPortAndTokenMacsys() (port int, token string, err error) {
const dir = "/Library/Tailscale"
portStr, err := os.Readlink(filepath.Join(dir, "ipnport"))
if err != nil {
return 0, "", err
}
port, err = strconv.Atoi(portStr)
if err != nil {
return 0, "", err
}
authb, err := os.ReadFile(filepath.Join(dir, "sameuserproof-"+portStr))
if err != nil {
return 0, "", err
}
auth := strings.TrimSpace(string(authb))
if auth == "" {
return 0, "", errors.New("empty auth token in sameuserproof file")
}
return port, auth, nil
}
func localTCPPortAndTokenDarwin() (port int, token string, err error) {
// There are two ways this binary can be run: as the Mac App Store sandboxed binary,
// or a normal binary that somebody built or download and are being run from outside
// the sandbox. Detect which way we're running and then figure out how to connect
// to the local daemon.
if dir := os.Getenv("TS_MACOS_CLI_SHARED_DIR"); dir != "" {
// First see if we're running as the non-AppStore "macsys" variant.
if strings.Contains(os.Getenv("HOME"), "/Containers/io.tailscale.ipn.macsys/") {
if port, token, err := localTCPPortAndTokenMacsys(); err == nil {
return port, token, nil
}
}
// The current binary (this process) is sandboxed. The user is
// running the CLI via /Applications/Tailscale.app/Contents/MacOS/Tailscale
// which sets the TS_MACOS_CLI_SHARED_DIR environment variable.
fis, err := ioutil.ReadDir(dir)
if err != nil {
return 0, "", err
}
for _, fi := range fis {
name := filepath.Base(fi.Name())
// Look for name like "sameuserproof-61577-2ae2ec9e0aa2005784f1"
// to extract out the port number and token.
if strings.HasPrefix(name, "sameuserproof-") {
f := strings.SplitN(name, "-", 3)
if len(f) == 3 {
if port, err := strconv.Atoi(f[1]); err == nil {
return port, f[2], nil
}
}
}
}
return 0, "", fmt.Errorf("failed to find sandboxed sameuserproof-* file in TS_MACOS_CLI_SHARED_DIR %q", dir)
}
// The current process is running outside the sandbox, so use
// lsof to find the IPNExtension (the Mac App Store variant).
cmd := exec.Command("lsof",
"-n", // numeric sockets; don't do DNS lookups, etc
"-a", // logical AND remaining options
fmt.Sprintf("-u%d", os.Getuid()), // process of same user only
"-c", "IPNExtension", // starting with IPNExtension
"-F", // machine-readable output
)
out, err := cmd.Output()
if err != nil {
// Before returning an error, see if we're running the
// macsys variant at the normal location.
if port, token, err := localTCPPortAndTokenMacsys(); err == nil {
return port, token, nil
}
return 0, "", fmt.Errorf("failed to run '%s' looking for IPNExtension: %w", cmd, err)
}
bs := bufio.NewScanner(bytes.NewReader(out))
subStr := []byte(".tailscale.ipn.macos/sameuserproof-")
for bs.Scan() {
line := bs.Bytes()
i := bytes.Index(line, subStr)
if i == -1 {
continue
}
f := strings.SplitN(string(line[i+len(subStr):]), "-", 2)
if len(f) != 2 {
continue
}
portStr, token := f[0], f[1]
port, err := strconv.Atoi(portStr)
if err != nil {
return 0, "", fmt.Errorf("invalid port %q found in lsof", portStr)
}
return port, token, nil
}
// Before returning an error, see if we're running the
// macsys variant at the normal location.
if port, token, err := localTCPPortAndTokenMacsys(); err == nil {
return port, token, nil
}
return 0, "", ErrTokenNotFound
}
|