aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--cli.go26
-rw-r--r--internal/app/dbus.go14
-rw-r--r--internal/app/launch.go83
-rw-r--r--internal/app/pulse.go68
-rw-r--r--internal/app/run.go178
-rw-r--r--internal/app/setup.go77
-rw-r--r--internal/app/wayland.go39
-rw-r--r--internal/app/x.go35
-rw-r--r--internal/state/enablement.go34
-rw-r--r--internal/state/exit.go2
-rw-r--r--internal/state/register.go7
-rw-r--r--internal/state/track.go57
-rw-r--r--internal/system/value.go5
-rw-r--r--main.go109
14 files changed, 457 insertions, 277 deletions
diff --git a/cli.go b/cli.go
index e5466f0d..4f937f91 100644
--- a/cli.go
+++ b/cli.go
@@ -3,21 +3,33 @@ package main
import (
"flag"
- "git.ophivana.moe/cat/fortify/internal/system"
+ "git.ophivana.moe/cat/fortify/internal/app"
)
var (
- userName string
- printVersion bool
- mustPulse bool
+ userName string
+
+ mustWayland bool
+ mustX bool
+ mustDBus bool
+ mustPulse bool
+
flagVerbose bool
+ printVersion bool
)
func init() {
flag.StringVar(&userName, "u", "chronos", "Specify a username")
- flag.BoolVar(&system.MethodFlags[0], "sudo", false, "Use 'sudo' to change user")
- flag.BoolVar(&system.MethodFlags[1], "bare", false, "Use 'machinectl' but skip xdg-desktop-portal setup")
- flag.BoolVar(&mustPulse, "pulse", false, "Treat unavailable PulseAudio as fatal")
+
+ flag.BoolVar(&mustWayland, "wayland", false, "Share Wayland socket")
+ flag.BoolVar(&mustX, "X", false, "Share X11 socket and allow connection")
+ flag.BoolVar(&mustDBus, "dbus", false, "Proxy D-Bus connection")
+ flag.BoolVar(&mustPulse, "pulse", false, "Share PulseAudio socket and cookie")
+
+ flag.BoolVar(&app.LaunchOptions[app.LaunchMethodSudo], "sudo", false, "Use 'sudo' to switch user")
+ flag.BoolVar(&app.LaunchOptions[app.LaunchMethodMachineCtl], "machinectl", true, "Use 'machinectl' to switch user")
+ flag.BoolVar(&app.LaunchOptions[app.LaunchBare], "bare", false, "Only set environment variables for child")
+
flag.BoolVar(&flagVerbose, "v", false, "Verbose output")
flag.BoolVar(&printVersion, "V", false, "Print version")
}
diff --git a/internal/app/dbus.go b/internal/app/dbus.go
new file mode 100644
index 00000000..89394f15
--- /dev/null
+++ b/internal/app/dbus.go
@@ -0,0 +1,14 @@
+package app
+
+import (
+ "fmt"
+
+ "git.ophivana.moe/cat/fortify/internal/state"
+)
+
+func (a *App) ShareDBus() {
+ a.setEnablement(state.EnableDBus)
+
+ // TODO: start xdg-dbus-proxy
+ fmt.Println("warn: dbus proxy not implemented")
+}
diff --git a/internal/app/launch.go b/internal/app/launch.go
index ab48baad..02204aae 100644
--- a/internal/app/launch.go
+++ b/internal/app/launch.go
@@ -10,14 +10,10 @@ import (
"syscall"
"git.ophivana.moe/cat/fortify/internal/state"
- "git.ophivana.moe/cat/fortify/internal/system"
"git.ophivana.moe/cat/fortify/internal/util"
)
-const (
- sudoAskPass = "SUDO_ASKPASS"
- launcherPayload = "FORTIFY_LAUNCHER_PAYLOAD"
-)
+const launcherPayload = "FORTIFY_LAUNCHER_PAYLOAD"
func (a *App) launcherPayloadEnv() string {
r := &bytes.Buffer{}
@@ -75,80 +71,3 @@ func Early(printVersion bool) {
}
}
}
-
-func (a *App) launchBySudo() (args []string) {
- args = make([]string, 0, 4+len(a.env)+len(a.command))
-
- // -Hiu $USER
- args = append(args, "-Hiu", a.Username)
-
- // -A?
- if _, ok := os.LookupEnv(sudoAskPass); ok {
- if system.V.Verbose {
- fmt.Printf("%s set, adding askpass flag\n", sudoAskPass)
- }
- args = append(args, "-A")
- }
-
- // environ
- args = append(args, a.env...)
-
- // -- $@
- args = append(args, "--")
- args = append(args, a.command...)
-
- return
-}
-
-func (a *App) launchByMachineCtl(bare bool) (args []string) {
- args = make([]string, 0, 9+len(a.env))
-
- // shell --uid=$USER
- args = append(args, "shell", "--uid="+a.Username)
-
- // --quiet
- if !system.V.Verbose {
- args = append(args, "--quiet")
- }
-
- // environ
- envQ := make([]string, len(a.env)+1)
- for i, e := range a.env {
- envQ[i] = "-E" + e
- }
- envQ[len(a.env)] = "-E" + a.launcherPayloadEnv()
- args = append(args, envQ...)
-
- // -- .host
- args = append(args, "--", ".host")
-
- // /bin/sh -c
- if sh, ok := util.Which("sh"); !ok {
- state.Fatal("Did not find 'sh' in PATH")
- } else {
- args = append(args, sh, "-c")
- }
-
- if len(a.command) == 0 { // execute shell if command is not provided
- a.command = []string{"$SHELL"}
- }
-
- innerCommand := strings.Builder{}
-
- if !bare {
- innerCommand.WriteString("dbus-update-activation-environment --systemd")
- for _, e := range a.env {
- innerCommand.WriteString(" " + strings.SplitN(e, "=", 2)[0])
- }
- innerCommand.WriteString("; systemctl --user start xdg-desktop-portal-gtk; ")
- }
-
- if executable, err := os.Executable(); err != nil {
- state.Fatal("Error reading executable path:", err)
- } else {
- innerCommand.WriteString("exec " + executable + " -V")
- }
- args = append(args, innerCommand.String())
-
- return
-}
diff --git a/internal/app/pulse.go b/internal/app/pulse.go
new file mode 100644
index 00000000..848ac717
--- /dev/null
+++ b/internal/app/pulse.go
@@ -0,0 +1,68 @@
+package app
+
+import (
+ "errors"
+ "fmt"
+ "io/fs"
+ "os"
+ "path"
+
+ "git.ophivana.moe/cat/fortify/internal/acl"
+ "git.ophivana.moe/cat/fortify/internal/state"
+ "git.ophivana.moe/cat/fortify/internal/system"
+ "git.ophivana.moe/cat/fortify/internal/util"
+)
+
+func (a *App) SharePulse() {
+ a.setEnablement(state.EnablePulse)
+
+ // ensure PulseAudio directory ACL (e.g. `/run/user/%d/pulse`)
+ pulse := path.Join(system.V.Runtime, "pulse")
+ pulseS := path.Join(pulse, "native")
+ if s, err := os.Stat(pulse); err != nil {
+ if !errors.Is(err, fs.ErrNotExist) {
+ state.Fatal("Error accessing PulseAudio directory:", err)
+ }
+ state.Fatal(fmt.Sprintf("PulseAudio dir '%s' not found", pulse))
+ } else {
+ // add environment variable for new process
+ a.AppendEnv(util.PulseServer, "unix:"+pulseS)
+ if err = acl.UpdatePerm(pulse, a.UID(), acl.Execute); err != nil {
+ state.Fatal("Error preparing PulseAudio:", err)
+ } else {
+ state.RegisterRevertPath(pulse)
+ }
+
+ // ensure PulseAudio socket permission (e.g. `/run/user/%d/pulse/native`)
+ if s, err = os.Stat(pulseS); err != nil {
+ if errors.Is(err, fs.ErrNotExist) {
+ state.Fatal("PulseAudio directory found but socket does not exist")
+ }
+ state.Fatal("Error accessing PulseAudio socket:", err)
+ } else {
+ if m := s.Mode(); m&0o006 != 0o006 {
+ state.Fatal(fmt.Sprintf("Unexpected permissions on '%s':", pulseS), m)
+ }
+ }
+
+ // Publish current user's pulse-cookie for target user
+ pulseCookieSource := util.DiscoverPulseCookie()
+ pulseCookieFinal := path.Join(system.V.Share, "pulse-cookie")
+ a.AppendEnv(util.PulseCookie, pulseCookieFinal)
+ if system.V.Verbose {
+ fmt.Printf("Publishing PulseAudio cookie '%s' to '%s'\n", pulseCookieSource, pulseCookieFinal)
+ }
+ if err = util.CopyFile(pulseCookieFinal, pulseCookieSource); err != nil {
+ state.Fatal("Error copying PulseAudio cookie:", err)
+ }
+ if err = acl.UpdatePerm(pulseCookieFinal, a.UID(), acl.Read); err != nil {
+ state.Fatal("Error publishing PulseAudio cookie:", err)
+ } else {
+ state.RegisterRevertPath(pulseCookieFinal)
+ }
+
+ if system.V.Verbose {
+ fmt.Printf("PulseAudio dir '%s' configured\n", pulse)
+ }
+ }
+}
diff --git a/internal/app/run.go b/internal/app/run.go
new file mode 100644
index 00000000..bd3bd9bf
--- /dev/null
+++ b/internal/app/run.go
@@ -0,0 +1,178 @@
+package app
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "os/exec"
+ "strings"
+
+ "git.ophivana.moe/cat/fortify/internal/state"
+ "git.ophivana.moe/cat/fortify/internal/system"
+ "git.ophivana.moe/cat/fortify/internal/util"
+)
+
+const (
+ term = "TERM"
+ sudoAskPass = "SUDO_ASKPASS"
+)
+const (
+ LaunchMethodSudo = iota
+ LaunchMethodMachineCtl
+
+ LaunchBare
+ launchOptionLength
+)
+
+var (
+ // LaunchOptions is set in main's cli.go
+ LaunchOptions [launchOptionLength]bool
+)
+
+func (a *App) Run() {
+ // pass $TERM to launcher
+ if t, ok := os.LookupEnv(term); ok {
+ a.AppendEnv(term, t)
+ }
+
+ commandBuilder := a.commandBuilderSudo
+
+ var toolPath string
+
+ // dependency checks
+ const sudoFallback = "Falling back to 'sudo', some desktop integration features may not work"
+ if LaunchOptions[LaunchMethodMachineCtl] && !LaunchOptions[LaunchMethodSudo] { // sudo argument takes priority
+ if !util.SdBooted() {
+ fmt.Println("This system was not booted through systemd")
+ fmt.Println(sudoFallback)
+ } else if machineCtlPath, ok := util.Which("machinectl"); !ok {
+ fmt.Println("Did not find 'machinectl' in PATH")
+ fmt.Println(sudoFallback)
+ } else {
+ toolPath = machineCtlPath
+ commandBuilder = a.commandBuilderMachineCtl
+ }
+ } else if sudoPath, ok := util.Which("sudo"); !ok {
+ state.Fatal("Did not find 'sudo' in PATH")
+ } else {
+ toolPath = sudoPath
+ }
+
+ if system.V.Verbose {
+ fmt.Printf("Selected launcher '%s' bare=%t\n", toolPath, LaunchOptions[LaunchBare])
+ }
+
+ cmd := exec.Command(toolPath, commandBuilder(LaunchOptions[LaunchBare])...)
+ cmd.Env = a.env
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ cmd.Dir = system.V.RunDir
+
+ if system.V.Verbose {
+ fmt.Println("Executing:", cmd)
+ }
+
+ if err := cmd.Start(); err != nil {
+ state.Fatal("Error starting process:", err)
+ }
+
+ state.RegisterEnablement(a.enablements)
+
+ if err := state.SaveProcess(a.Uid, cmd); err != nil {
+ // process already started, shouldn't be fatal
+ fmt.Println("Error registering process:", err)
+ }
+
+ var r int
+ if err := cmd.Wait(); err != nil {
+ var exitError *exec.ExitError
+ if !errors.As(err, &exitError) {
+ state.Fatal("Error running process:", err)
+ }
+ }
+
+ if system.V.Verbose {
+ fmt.Println("Process exited with exit code", r)
+ }
+ state.BeforeExit()
+ os.Exit(r)
+}
+
+func (a *App) commandBuilderSudo(bare bool) (args []string) {
+ args = make([]string, 0, 4+len(a.env)+len(a.command))
+
+ // -Hiu $USER
+ args = append(args, "-Hiu", a.Username)
+
+ // -A?
+ if _, ok := os.LookupEnv(sudoAskPass); ok {
+ if system.V.Verbose {
+ fmt.Printf("%s set, adding askpass flag\n", sudoAskPass)
+ }
+ args = append(args, "-A")
+ }
+
+ // environ
+ args = append(args, a.env...)
+
+ // -- $@
+ args = append(args, "--")
+ args = append(args, a.command...)
+
+ return
+}
+
+func (a *App) commandBuilderMachineCtl(bare bool) (args []string) {
+ args = make([]string, 0, 9+len(a.env))
+
+ // shell --uid=$USER
+ args = append(args, "shell", "--uid="+a.Username)
+
+ // --quiet
+ if !system.V.Verbose {
+ args = append(args, "--quiet")
+ }
+
+ // environ
+ envQ := make([]string, len(a.env)+1)
+ for i, e := range a.env {
+ envQ[i] = "-E" + e
+ }
+ envQ[len(a.env)] = "-E" + a.launcherPayloadEnv()
+ args = append(args, envQ...)
+
+ // -- .host
+ args = append(args, "--", ".host")
+
+ // /bin/sh -c
+ if sh, ok := util.Which("sh"); !ok {
+ state.Fatal("Did not find 'sh' in PATH")
+ } else {
+ args = append(args, sh, "-c")
+ }
+
+ if len(a.command) == 0 { // execute shell if command is not provided
+ a.command = []string{"$SHELL"}
+ }
+
+ innerCommand := strings.Builder{}
+
+ if !bare {
+ innerCommand.WriteString("dbus-update-activation-environment --systemd")
+ for _, e := range a.env {
+ innerCommand.WriteString(" " + strings.SplitN(e, "=", 2)[0])
+ }
+ innerCommand.WriteString("; ")
+ //innerCommand.WriteString("systemctl --user start xdg-desktop-portal-gtk; ")
+ }
+
+ if executable, err := os.Executable(); err != nil {
+ state.Fatal("Error reading executable path:", err)
+ } else {
+ innerCommand.WriteString("exec " + executable + " -V")
+ }
+ args = append(args, innerCommand.String())
+
+ return
+}
diff --git a/internal/app/setup.go b/internal/app/setup.go
index 7b5cd2ec..82765dfb 100644
--- a/internal/app/setup.go
+++ b/internal/app/setup.go
@@ -4,13 +4,11 @@ import (
"errors"
"fmt"
"os"
- "os/exec"
"os/user"
"strconv"
"git.ophivana.moe/cat/fortify/internal/state"
"git.ophivana.moe/cat/fortify/internal/system"
- "git.ophivana.moe/cat/fortify/internal/util"
)
type App struct {
@@ -18,78 +16,19 @@ type App struct {
env []string
command []string
+ enablements state.Enablements
*user.User
-}
-
-func (a *App) Run() {
- f := a.launchBySudo
- m, b := false, false
- switch {
- case system.MethodFlags[0]: // sudo
- case system.MethodFlags[1]: // bare
- m, b = true, true
- default: // machinectl
- m, b = true, false
- }
-
- var toolPath string
-
- // dependency checks
- const sudoFallback = "Falling back to 'sudo', some desktop integration features may not work"
- if m {
- if !util.SdBooted() {
- fmt.Println("This system was not booted through systemd")
- fmt.Println(sudoFallback)
- } else if tp, ok := util.Which("machinectl"); !ok {
- fmt.Println("Did not find 'machinectl' in PATH")
- fmt.Println(sudoFallback)
- } else {
- toolPath = tp
- f = func() []string { return a.launchByMachineCtl(b) }
- }
- } else if tp, ok := util.Which("sudo"); !ok {
- state.Fatal("Did not find 'sudo' in PATH")
- } else {
- toolPath = tp
- }
-
- if system.V.Verbose {
- fmt.Printf("Selected launcher '%s' bare=%t\n", toolPath, b)
- }
- cmd := exec.Command(toolPath, f()...)
- cmd.Env = a.env
- cmd.Stdin = os.Stdin
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- cmd.Dir = system.V.RunDir
-
- if system.V.Verbose {
- fmt.Println("Executing:", cmd)
- }
-
- if err := cmd.Start(); err != nil {
- state.Fatal("Error starting process:", err)
- }
-
- if err := state.SaveProcess(a.Uid, cmd); err != nil {
- // process already started, shouldn't be fatal
- fmt.Println("Error registering process:", err)
- }
+ // absolutely *no* method of this type is thread-safe
+ // so don't treat it as if it is
+}
- var r int
- if err := cmd.Wait(); err != nil {
- var exitError *exec.ExitError
- if !errors.As(err, &exitError) {
- state.Fatal("Error running process:", err)
- }
+func (a *App) setEnablement(e state.Enablement) {
+ if a.enablements.Has(e) {
+ panic("enablement " + e.String() + " set twice")
}
- if system.V.Verbose {
- fmt.Println("Process exited with exit code", r)
- }
- state.BeforeExit()
- os.Exit(r)
+ a.enablements |= e.Mask()
}
func New(userName string, args []string) *App {
diff --git a/internal/app/wayland.go b/internal/app/wayland.go
new file mode 100644
index 00000000..1cabdbdb
--- /dev/null
+++ b/internal/app/wayland.go
@@ -0,0 +1,39 @@
+package app
+
+import (
+ "fmt"
+ "os"
+ "path"
+
+ "git.ophivana.moe/cat/fortify/internal/acl"
+ "git.ophivana.moe/cat/fortify/internal/state"
+ "git.ophivana.moe/cat/fortify/internal/system"
+)
+
+const (
+ // https://manpages.debian.org/experimental/libwayland-doc/wl_display_connect.3.en.html
+ waylandDisplay = "WAYLAND_DISPLAY"
+)
+
+func (a *App) ShareWayland() {
+ a.setEnablement(state.EnableWayland)
+
+ // ensure Wayland socket ACL (e.g. `/run/user/%d/wayland-%d`)
+ if w, ok := os.LookupEnv(waylandDisplay); !ok {
+ if system.V.Verbose {
+ fmt.Println("Wayland: WAYLAND_DISPLAY not set, skipping")
+ }
+ } else {
+ // add environment variable for new process
+ wp := path.Join(system.V.Runtime, w)
+ a.AppendEnv(waylandDisplay, wp)
+ if err := acl.UpdatePerm(wp, a.UID(), acl.Read, acl.Write, acl.Execute); err != nil {
+ state.Fatal(fmt.Sprintf("Error preparing Wayland '%s':", w), err)
+ } else {
+ state.RegisterRevertPath(wp)
+ }
+ if system.V.Verbose {
+ fmt.Printf("Wayland socket '%s' configured\n", w)
+ }
+ }
+}
diff --git a/internal/app/x.go b/internal/app/x.go
new file mode 100644
index 00000000..02c40a68
--- /dev/null
+++ b/internal/app/x.go
@@ -0,0 +1,35 @@
+package app
+
+import (
+ "fmt"
+ "os"
+
+ "git.ophivana.moe/cat/fortify/internal/state"
+ "git.ophivana.moe/cat/fortify/internal/system"
+ "git.ophivana.moe/cat/fortify/internal/xcb"
+)
+
+const display = "DISPLAY"
+
+func (a *App) ShareX() {
+ a.setEnablement(state.EnableX)
+
+ // discovery X11 and grant user permission via the `ChangeHosts` command
+ if d, ok := os.LookupEnv(display); !ok {
+ if system.V.Verbose {
+ fmt.Println("X11: DISPLAY not set, skipping")
+ }
+ } else {
+ // add environment variable for new process
+ a.AppendEnv(display, d)
+
+ if system.V.Verbose {
+ fmt.Printf("X11: Adding XHost entry SI:localuser:%s to display '%s'\n", a.Username, d)
+ }
+ if err := xcb.ChangeHosts(xcb.HostModeInsert, xcb.FamilyServerInterpreted, "localuser\x00"+a.Username); err != nil {
+ state.Fatal(fmt.Sprintf("Error adding XHost entry to '%s':", d), err)
+ } else {
+ state.XcbActionComplete()
+ }
+ }
+}
diff --git a/internal/state/enablement.go b/internal/state/enablement.go
new file mode 100644
index 00000000..9358f432
--- /dev/null
+++ b/internal/state/enablement.go
@@ -0,0 +1,34 @@
+package state
+
+type (
+ Enablement uint8
+ Enablements uint64
+)
+
+const (
+ EnableWayland Enablement = iota
+ EnableX
+ EnableDBus
+ EnablePulse
+
+ enableLength
+)
+
+var enablementString = [enableLength]string{
+ "Wayland",
+ "X11",
+ "D-Bus",
+ "PulseAudio",
+}
+
+func (e Enablement) String() string {
+ return enablementString[e]
+}
+
+func (e Enablement) Mask() Enablements {
+ return 1 << e
+}
+
+func (es Enablements) Has(e Enablement) bool {
+ return es&e.Mask() != 0
+}
diff --git a/internal/state/exit.go b/internal/state/exit.go
index e0e2c04a..3da1f48e 100644
--- a/internal/state/exit.go
+++ b/internal/state/exit.go
@@ -33,7 +33,7 @@ func BeforeExit() {
}
}
- if d, err := readLaunchers(); err != nil {
+ if d, err := readLaunchers(u.Uid); err != nil {
fmt.Println("Error reading active launchers:", err)
os.Exit(1)
} else if len(d) > 0 {
diff --git a/internal/state/register.go b/internal/state/register.go
index 2fff565e..25c22894 100644
--- a/internal/state/register.go
+++ b/internal/state/register.go
@@ -4,6 +4,13 @@ func RegisterRevertPath(p string) {
cleanupCandidate = append(cleanupCandidate, p)
}
+func RegisterEnablement(e Enablements) {
+ if enablements != nil {
+ panic("enablement state set twice")
+ }
+ enablements = &e
+}
+
func XcbActionComplete() {
if xcbActionComplete {
Fatal("xcb inserted twice")
diff --git a/internal/state/track.go b/internal/state/track.go
index 913d684d..96e9353d 100644
--- a/internal/state/track.go
+++ b/internal/state/track.go
@@ -10,6 +10,8 @@ import (
"os/exec"
"path"
"strconv"
+ "strings"
+ "text/tabwriter"
"git.ophivana.moe/cat/fortify/internal/system"
)
@@ -22,13 +24,15 @@ var (
statePath string
cleanupCandidate []string
xcbActionComplete bool
+ enablements *Enablements
)
type launcherState struct {
- PID int
- Launcher string
- Argv []string
- Command []string
+ PID int
+ Launcher string
+ Argv []string
+ Command []string
+ Capability Enablements
}
func init() {
@@ -40,15 +44,41 @@ func Early() {
return
}
- launchers, err := readLaunchers()
+ launchers, err := readLaunchers(u.Uid)
if err != nil {
fmt.Println("Error reading launchers:", err)
os.Exit(1)
}
- fmt.Println("\tPID\tLauncher")
+ stdout := tabwriter.NewWriter(os.Stdout, 0, 1, 4, ' ', 0)
+ if !system.V.Verbose {
+ _, _ = fmt.Fprintln(stdout, "\tPID\tEnablements\tLauncher\tCommand")
+ } else {
+ _, _ = fmt.Fprintln(stdout, "\tPID\tArgv")
+ }
+
for _, state := range launchers {
- fmt.Printf("\t%d\t%s\nCommand: %s\nArgv: %s\n", state.PID, state.Launcher, state.Command, state.Argv)
+ enablementsDescription := strings.Builder{}
+ for i := Enablement(0); i < enableLength; i++ {
+ if state.Capability.Has(i) {
+ enablementsDescription.WriteString(", " + i.String())
+ }
+ }
+ if enablementsDescription.Len() == 0 {
+ enablementsDescription.WriteString("none")
+ }
+
+ if !system.V.Verbose {
+ _, _ = fmt.Fprintf(stdout, "\t%d\t%s\t%s\t%s\n",
+ state.PID, strings.TrimPrefix(enablementsDescription.String(), ", "), state.Launcher,
+ state.Command)
+ } else {
+ _, _ = fmt.Fprintf(stdout, "\t%d\t%s\n",
+ state.PID, state.Argv)
+ }
+ }
+ if err = stdout.Flush(); err != nil {
+ fmt.Println("warn: error formatting output:", err)
}
os.Exit(0)
@@ -58,10 +88,11 @@ func Early() {
func SaveProcess(uid string, cmd *exec.Cmd) error {
statePath = path.Join(system.V.RunDir, uid, strconv.Itoa(cmd.Process.Pid))
state := launcherState{
- PID: cmd.Process.Pid,
- Launcher: cmd.Path,
- Argv: cmd.Args,
- Command: command,
+ PID: cmd.Process.Pid,
+ Launcher: cmd.Path,
+ Argv: cmd.Args,
+ Command: command,
+ Capability: *enablements,
}
if err := os.Mkdir(path.Join(system.V.RunDir, uid), 0700); err != nil && !errors.Is(err, fs.ErrExist) {
@@ -81,10 +112,10 @@ func SaveProcess(uid string, cmd *exec.Cmd) error {
}
}
-func readLaunchers() ([]*launcherState, error) {
+func readLaunchers(uid string) ([]*launcherState, error) {
var f *os.File
var r []*launcherState
- launcherPrefix := path.Join(system.V.RunDir, u.Uid)
+ launcherPrefix := path.Join(system.V.RunDir, uid)
if pl, err := os.ReadDir(launcherPrefix); err != nil {
return nil, err
diff --git a/internal/system/value.go b/internal/system/value.go
index aa4da518..fa5458fc 100644
--- a/internal/system/value.go
+++ b/internal/system/value.go
@@ -11,7 +11,4 @@ type Values struct {
Verbose bool
}
-var (
- V *Values
- MethodFlags [2]bool
-)
+var V *Values
diff --git a/main.go b/main.go
index 102252b4..90276602 100644
--- a/main.go
+++ b/main.go
@@ -6,7 +6,6 @@ import (
"fmt"
"io/fs"
"os"
- "path"
"strconv"
"syscall"
@@ -14,8 +13,6 @@ import (
"git.ophivana.moe/cat/fortify/internal/app"
"git.ophivana.moe/cat/fortify/internal/state"
"git.ophivana.moe/cat/fortify/internal/system"
- "git.ophivana.moe/cat/fortify/internal/util"
- "git.ophivana.moe/cat/fortify/internal/xcb"
)
var (
@@ -30,14 +27,6 @@ func tryVersion() {
}
}
-const (
- term = "TERM"
- display = "DISPLAY"
-
- // https://manpages.debian.org/experimental/libwayland-doc/wl_display_connect.3.en.html
- waylandDisplay = "WAYLAND_DISPLAY"
-)
-
func main() {
flag.Parse()
@@ -105,102 +94,20 @@ func main() {
}
}
- // ensure Wayland socket ACL (e.g. `/run/user/%d/wayland-%d`)
- if w, ok := os.LookupEnv(waylandDisplay); !ok {
- if system.V.Verbose {
- fmt.Println("Wayland: WAYLAND_DISPLAY not set, skipping")
- }
- } else {
- // add environment variable for new process
- wp := path.Join(system.V.Runtime, w)
- a.AppendEnv(waylandDisplay, wp)
- if err := acl.UpdatePerm(wp, a.UID(), acl.Read, acl.Write, acl.Execute); err != nil {
- state.Fatal(fmt.Sprintf("Error preparing Wayland '%s':", w), err)
- } else {
- state.RegisterRevertPath(wp)
- }
- if system.V.Verbose {
- fmt.Printf("Wayland socket '%s' configured\n", w)
- }
+ if mustWayland {
+ a.ShareWayland()
}
- // discovery X11 and grant user permission via the `ChangeHosts` command
- if d, ok := os.LookupEnv(display); !ok {
- if system.V.Verbose {
- fmt.Println("X11: DISPLAY not set, skipping")
- }
- } else {
- // add environment variable for new process
- a.AppendEnv(display, d)
-
- if system.V.Verbose {
- fmt.Printf("X11: Adding XHost entry SI:localuser:%s to display '%s'\n", a.Username, d)
- }
- if err := xcb.ChangeHosts(xcb.HostModeInsert, xcb.FamilyServerInterpreted, "localuser\x00"+a.Username); err != nil {
- state.Fatal(fmt.Sprintf("Error adding XHost entry to '%s':", d), err)
- } else {
- state.XcbActionComplete()
- }
+ if mustX {
+ a.ShareX()
}
- // ensure PulseAudio directory ACL (e.g. `/run/user/%d/pulse`)
- pulse := path.Join(system.V.Runtime, "pulse")
- pulseS := path.Join(pulse, "native")
- if s, err := os.Stat(pulse); err != nil {
- if !errors.Is(err, fs.ErrNotExist) {
- state.Fatal("Error accessing PulseAudio directory:", err)
- }
- if mustPulse {
- state.Fatal("PulseAudio is unavailable")
- }
- if system.V.Verbose {
- fmt.Printf("PulseAudio dir '%s' not found, skipping\n", pulse)
- }
- } else {
- // add environment variable for new process
- a.AppendEnv(util.PulseServer, "unix:"+pulseS)
- if err = acl.UpdatePerm(pulse, a.UID(), acl.Execute); err != nil {
- state.Fatal("Error preparing PulseAudio:", err)
- } else {
- state.RegisterRevertPath(pulse)
- }
-
- // ensure PulseAudio socket permission (e.g. `/run/user/%d/pulse/native`)
- if s, err = os.Stat(pulseS); err != nil {
- if errors.Is(err, fs.ErrNotExist) {
- state.Fatal("PulseAudio directory found but socket does not exist")
- }
- state.Fatal("Error accessing PulseAudio socket:", err)
- } else {
- if m := s.Mode(); m&0o006 != 0o006 {
- state.Fatal(fmt.Sprintf("Unexpected permissions on '%s':", pulseS), m)
- }
- }
-
- // Publish current user's pulse-cookie for target user
- pulseCookieSource := util.DiscoverPulseCookie()
- pulseCookieFinal := path.Join(system.V.Share, "pulse-cookie")
- a.AppendEnv(util.PulseCookie, pulseCookieFinal)
- if system.V.Verbose {
- fmt.Printf("Publishing PulseAudio cookie '%s' to '%s'\n", pulseCookieSource, pulseCookieFinal)
- }
- if err = util.CopyFile(pulseCookieFinal, pulseCookieSource); err != nil {
- state.Fatal("Error copying PulseAudio cookie:", err)
- }
- if err = acl.UpdatePerm(pulseCookieFinal, a.UID(), acl.Read); err != nil {
- state.Fatal("Error publishing PulseAudio cookie:", err)
- } else {
- state.RegisterRevertPath(pulseCookieFinal)
- }
-
- if system.V.Verbose {
- fmt.Printf("PulseAudio dir '%s' configured\n", pulse)
- }
+ if mustDBus {
+ a.ShareDBus()
}
- // pass $TERM to launcher
- if t, ok := os.LookupEnv(term); ok {
- a.AppendEnv(term, t)
+ if mustPulse {
+ a.SharePulse()
}
a.Run()