arblant
firefox-vpn-client / core / internal / tunservice / client_darwin.go
client_darwin.go60 lines · 1.6 KB
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
package tunservice

import (
	"errors"
	"fmt"
	"os"
	"os/exec"
	"strings"
)

// runElevated runs this executable with args as root through AppleScript's
// "do shell script ... with administrator privileges", whose prompt asks
// for an administrator's password and shows prompt as the reason, and
// waits for it.
func runElevated(prompt string, args ...string) error {
	self, err := os.Executable()
	if err != nil {
		return err
	}
	report, remove, err := reportFile()
	if err != nil {
		return err
	}
	defer remove()
	words := []string{shellQuote(self)}
	for _, a := range append(args, "-report", report) {
		words = append(words, shellQuote(a))
	}
	script := "do shell script " + appleScriptString(strings.Join(words, " "))
	if prompt != "" {
		script += " with prompt " + appleScriptString(prompt)
	}
	script += " with administrator privileges"
	out, err := exec.Command("/usr/bin/osascript", "-e", script).CombinedOutput()
	if err == nil {
		return nil
	}
	text := strings.TrimSpace(string(out))
	// "execution error: User canceled. (-128)"
	if strings.HasSuffix(text, "(-128)") {
		return ErrCancelled
	}
	if r := readReport(report); r != "" {
		return errors.New(r)
	}
	if text != "" {
		return fmt.Errorf("running as the administrator failed: %s", text)
	}
	return fmt.Errorf("running as the administrator failed: %w", err)
}

// shellQuote quotes s for sh.
func shellQuote(s string) string {
	return "'" + strings.ReplaceAll(s, "'", `'\''`) + "'"
}

// appleScriptString quotes s as an AppleScript string.
func appleScriptString(s string) string {
	return `"` + strings.NewReplacer(`\`, `\\`, `"`, `\"`).Replace(s) + `"`
}