diff --git a/safesocket/safesocket_darwin.go b/safesocket/safesocket_darwin.go index d8a0372be..7809c8c0b 100644 --- a/safesocket/safesocket_darwin.go +++ b/safesocket/safesocket_darwin.go @@ -7,6 +7,7 @@ import ( "bufio" "bytes" + "errors" "fmt" "io/ioutil" "os" @@ -20,6 +21,32 @@ func init() { localTCPPortAndToken = localTCPPortAndTokenDarwin } +// localTCPPortAndTokenMacsys returns the localhost TCP port number and auth token +// from the directory dir, if dir is for the "macsys" variant. +// +// 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(dir string) (port int, token string, err error) { + 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 @@ -27,6 +54,11 @@ func localTCPPortAndTokenDarwin() (port int, token string, err error) { // 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 port, token, err := localTCPPortAndTokenMacsys(dir); 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. @@ -51,7 +83,7 @@ func localTCPPortAndTokenDarwin() (port int, token string, err error) { } // The current process is running outside the sandbox, so use - // lsof to find the IPNExtension: + // lsof to find the IPNExtension (the Mac App Store variant). cmd := exec.Command("lsof", "-n", // numeric sockets; don't do DNS lookups, etc @@ -62,6 +94,12 @@ func localTCPPortAndTokenDarwin() (port int, token string, err error) { ) 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("/Library/Tailscale"); 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)) @@ -83,5 +121,11 @@ func localTCPPortAndTokenDarwin() (port int, token string, err error) { } 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("/Library/Tailscale"); err == nil { + return port, token, nil + } return 0, "", ErrTokenNotFound }