exit: move final and early code to internal package
Exit cleanup state information is now stored in a dedicated struct and built up using methods of that struct. Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
This commit is contained in:
parent
6a6f62efa6
commit
4b7d616862
4
flag.go
4
flag.go
@ -3,7 +3,7 @@ package main
|
||||
import (
|
||||
"flag"
|
||||
|
||||
"git.ophivana.moe/cat/fortify/internal/util"
|
||||
"git.ophivana.moe/cat/fortify/internal"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -44,7 +44,7 @@ func init() {
|
||||
|
||||
func init() {
|
||||
methodHelpString := "Method of launching the child process, can be one of \"sudo\", \"bubblewrap\""
|
||||
if util.SdBootedV {
|
||||
if internal.SdBootedV {
|
||||
methodHelpString += ", \"systemd\""
|
||||
}
|
||||
|
||||
|
@ -9,8 +9,7 @@ import (
|
||||
|
||||
"git.ophivana.moe/cat/fortify/acl"
|
||||
"git.ophivana.moe/cat/fortify/dbus"
|
||||
"git.ophivana.moe/cat/fortify/internal/final"
|
||||
"git.ophivana.moe/cat/fortify/internal/state"
|
||||
"git.ophivana.moe/cat/fortify/internal"
|
||||
"git.ophivana.moe/cat/fortify/internal/util"
|
||||
"git.ophivana.moe/cat/fortify/internal/verbose"
|
||||
)
|
||||
@ -26,7 +25,7 @@ var (
|
||||
)
|
||||
|
||||
func (a *App) ShareDBus(dse, dsg *dbus.Config, log bool) {
|
||||
a.setEnablement(state.EnableDBus)
|
||||
a.setEnablement(internal.EnableDBus)
|
||||
|
||||
dbusSystem = dsg != nil
|
||||
var binPath string
|
||||
@ -41,7 +40,7 @@ func (a *App) ShareDBus(dse, dsg *dbus.Config, log bool) {
|
||||
}
|
||||
|
||||
if b, ok := util.Which("xdg-dbus-proxy"); !ok {
|
||||
final.Fatal("D-Bus: Did not find 'xdg-dbus-proxy' in PATH")
|
||||
internal.Fatal("D-Bus: Did not find 'xdg-dbus-proxy' in PATH")
|
||||
} else {
|
||||
binPath = b
|
||||
}
|
||||
@ -69,7 +68,7 @@ func (a *App) ShareDBus(dse, dsg *dbus.Config, log bool) {
|
||||
verbose.Println("D-Bus: sealing system proxy", dsg.Args(systemBus))
|
||||
}
|
||||
if err := p.Seal(dse, dsg); err != nil {
|
||||
final.Fatal("D-Bus: invalid config when sealing proxy,", err)
|
||||
internal.Fatal("D-Bus: invalid config when sealing proxy,", err)
|
||||
}
|
||||
|
||||
ready := make(chan bool, 1)
|
||||
@ -80,7 +79,7 @@ func (a *App) ShareDBus(dse, dsg *dbus.Config, log bool) {
|
||||
verbose.Printf("Starting system bus proxy '%s' for address '%s'\n", dbusAddress[1], systemBus[0])
|
||||
}
|
||||
if err := p.Start(&ready); err != nil {
|
||||
final.Fatal("D-Bus: error starting proxy,", err)
|
||||
internal.Fatal("D-Bus: error starting proxy,", err)
|
||||
}
|
||||
verbose.Println("D-Bus proxy launch:", p)
|
||||
|
||||
@ -97,24 +96,24 @@ func (a *App) ShareDBus(dse, dsg *dbus.Config, log bool) {
|
||||
}()
|
||||
|
||||
// register early to enable Fatal cleanup
|
||||
final.RegisterDBus(p, &done)
|
||||
a.exit.SealDBus(p, &done)
|
||||
|
||||
if !<-ready {
|
||||
final.Fatal("D-Bus: proxy did not start correctly")
|
||||
internal.Fatal("D-Bus: proxy did not start correctly")
|
||||
}
|
||||
|
||||
a.AppendEnv(dbusSessionBusAddress, dbusAddress[0])
|
||||
if err := acl.UpdatePerm(sessionBus[1], a.UID(), acl.Read, acl.Write); err != nil {
|
||||
final.Fatal(fmt.Sprintf("Error preparing D-Bus session proxy '%s':", dbusAddress[0]), err)
|
||||
internal.Fatal(fmt.Sprintf("Error preparing D-Bus session proxy '%s':", dbusAddress[0]), err)
|
||||
} else {
|
||||
final.RegisterRevertPath(sessionBus[1])
|
||||
a.exit.RegisterRevertPath(sessionBus[1])
|
||||
}
|
||||
if dsg != nil {
|
||||
a.AppendEnv(dbusSystemBusAddress, dbusAddress[1])
|
||||
if err := acl.UpdatePerm(systemBus[1], a.UID(), acl.Read, acl.Write); err != nil {
|
||||
final.Fatal(fmt.Sprintf("Error preparing D-Bus system proxy '%s':", dbusAddress[1]), err)
|
||||
internal.Fatal(fmt.Sprintf("Error preparing D-Bus system proxy '%s':", dbusAddress[1]), err)
|
||||
} else {
|
||||
final.RegisterRevertPath(systemBus[1])
|
||||
a.exit.RegisterRevertPath(systemBus[1])
|
||||
}
|
||||
}
|
||||
verbose.Printf("Session bus proxy '%s' for address '%s' configured\n", dbusAddress[0], sessionBus[0])
|
||||
|
@ -8,29 +8,29 @@ import (
|
||||
"path"
|
||||
|
||||
"git.ophivana.moe/cat/fortify/acl"
|
||||
"git.ophivana.moe/cat/fortify/internal/final"
|
||||
"git.ophivana.moe/cat/fortify/internal"
|
||||
"git.ophivana.moe/cat/fortify/internal/verbose"
|
||||
)
|
||||
|
||||
func (a *App) EnsureRunDir() {
|
||||
if err := os.Mkdir(a.runDirPath, 0700); err != nil && !errors.Is(err, fs.ErrExist) {
|
||||
final.Fatal("Error creating runtime directory:", err)
|
||||
internal.Fatal("Error creating runtime directory:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) EnsureRuntime() {
|
||||
if s, err := os.Stat(a.runtimePath); err != nil {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
final.Fatal("Runtime directory does not exist")
|
||||
internal.Fatal("Runtime directory does not exist")
|
||||
}
|
||||
final.Fatal("Error accessing runtime directory:", err)
|
||||
internal.Fatal("Error accessing runtime directory:", err)
|
||||
} else if !s.IsDir() {
|
||||
final.Fatal(fmt.Sprintf("Path '%s' is not a directory", a.runtimePath))
|
||||
internal.Fatal(fmt.Sprintf("Path '%s' is not a directory", a.runtimePath))
|
||||
} else {
|
||||
if err = acl.UpdatePerm(a.runtimePath, a.UID(), acl.Execute); err != nil {
|
||||
final.Fatal("Error preparing runtime directory:", err)
|
||||
internal.Fatal("Error preparing runtime directory:", err)
|
||||
} else {
|
||||
final.RegisterRevertPath(a.runtimePath)
|
||||
a.exit.RegisterRevertPath(a.runtimePath)
|
||||
}
|
||||
verbose.Printf("Runtime data dir '%s' configured\n", a.runtimePath)
|
||||
}
|
||||
@ -39,7 +39,7 @@ func (a *App) EnsureRuntime() {
|
||||
func (a *App) EnsureShare() {
|
||||
// acl is unnecessary as this directory is world executable
|
||||
if err := os.Mkdir(a.sharePath, 0701); err != nil && !errors.Is(err, fs.ErrExist) {
|
||||
final.Fatal("Error creating shared directory:", err)
|
||||
internal.Fatal("Error creating shared directory:", err)
|
||||
}
|
||||
|
||||
// workaround for launch method sudo
|
||||
@ -47,12 +47,12 @@ func (a *App) EnsureShare() {
|
||||
// ensure child runtime directory (e.g. `/tmp/fortify.%d/%d.share`)
|
||||
cr := path.Join(a.sharePath, a.Uid+".share")
|
||||
if err := os.Mkdir(cr, 0700); err != nil && !errors.Is(err, fs.ErrExist) {
|
||||
final.Fatal("Error creating child runtime directory:", err)
|
||||
internal.Fatal("Error creating child runtime directory:", err)
|
||||
} else {
|
||||
if err = acl.UpdatePerm(cr, a.UID(), acl.Read, acl.Write, acl.Execute); err != nil {
|
||||
final.Fatal("Error preparing child runtime directory:", err)
|
||||
internal.Fatal("Error preparing child runtime directory:", err)
|
||||
} else {
|
||||
final.RegisterRevertPath(cr)
|
||||
a.exit.RegisterRevertPath(cr)
|
||||
}
|
||||
a.AppendEnv("XDG_RUNTIME_DIR", cr)
|
||||
a.AppendEnv("XDG_SESSION_CLASS", "user")
|
||||
|
@ -5,11 +5,11 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"git.ophivana.moe/cat/fortify/internal/final"
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"git.ophivana.moe/cat/fortify/internal"
|
||||
"git.ophivana.moe/cat/fortify/internal/util"
|
||||
)
|
||||
|
||||
@ -20,7 +20,7 @@ func (a *App) launcherPayloadEnv() string {
|
||||
enc := base64.NewEncoder(base64.StdEncoding, r)
|
||||
|
||||
if err := gob.NewEncoder(enc).Encode(a.command); err != nil {
|
||||
final.Fatal("Error encoding launcher payload:", err)
|
||||
internal.Fatal("Error encoding launcher payload:", err)
|
||||
}
|
||||
|
||||
_ = enc.Close()
|
||||
|
@ -8,58 +8,102 @@ import (
|
||||
"path"
|
||||
|
||||
"git.ophivana.moe/cat/fortify/acl"
|
||||
"git.ophivana.moe/cat/fortify/internal/final"
|
||||
"git.ophivana.moe/cat/fortify/internal/state"
|
||||
"git.ophivana.moe/cat/fortify/internal"
|
||||
"git.ophivana.moe/cat/fortify/internal/util"
|
||||
"git.ophivana.moe/cat/fortify/internal/verbose"
|
||||
)
|
||||
|
||||
const (
|
||||
pulseServer = "PULSE_SERVER"
|
||||
pulseCookie = "PULSE_COOKIE"
|
||||
|
||||
home = "HOME"
|
||||
xdgConfigHome = "XDG_CONFIG_HOME"
|
||||
)
|
||||
|
||||
func (a *App) SharePulse() {
|
||||
a.setEnablement(state.EnablePulse)
|
||||
a.setEnablement(internal.EnablePulse)
|
||||
|
||||
// ensure PulseAudio directory ACL (e.g. `/run/user/%d/pulse`)
|
||||
pulse := path.Join(a.runtimePath, "pulse")
|
||||
pulseS := path.Join(pulse, "native")
|
||||
if s, err := os.Stat(pulse); err != nil {
|
||||
if !errors.Is(err, fs.ErrNotExist) {
|
||||
final.Fatal("Error accessing PulseAudio directory:", err)
|
||||
internal.Fatal("Error accessing PulseAudio directory:", err)
|
||||
}
|
||||
final.Fatal(fmt.Sprintf("PulseAudio dir '%s' not found", pulse))
|
||||
internal.Fatal(fmt.Sprintf("PulseAudio dir '%s' not found", pulse))
|
||||
} else {
|
||||
// add environment variable for new process
|
||||
a.AppendEnv(util.PulseServer, "unix:"+pulseS)
|
||||
a.AppendEnv(pulseServer, "unix:"+pulseS)
|
||||
if err = acl.UpdatePerm(pulse, a.UID(), acl.Execute); err != nil {
|
||||
final.Fatal("Error preparing PulseAudio:", err)
|
||||
internal.Fatal("Error preparing PulseAudio:", err)
|
||||
} else {
|
||||
final.RegisterRevertPath(pulse)
|
||||
a.exit.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) {
|
||||
final.Fatal("PulseAudio directory found but socket does not exist")
|
||||
internal.Fatal("PulseAudio directory found but socket does not exist")
|
||||
}
|
||||
final.Fatal("Error accessing PulseAudio socket:", err)
|
||||
internal.Fatal("Error accessing PulseAudio socket:", err)
|
||||
} else {
|
||||
if m := s.Mode(); m&0o006 != 0o006 {
|
||||
final.Fatal(fmt.Sprintf("Unexpected permissions on '%s':", pulseS), m)
|
||||
internal.Fatal(fmt.Sprintf("Unexpected permissions on '%s':", pulseS), m)
|
||||
}
|
||||
}
|
||||
|
||||
// Publish current user's pulse-cookie for target user
|
||||
pulseCookieSource := util.DiscoverPulseCookie()
|
||||
pulseCookieSource := discoverPulseCookie()
|
||||
pulseCookieFinal := path.Join(a.sharePath, "pulse-cookie")
|
||||
a.AppendEnv(util.PulseCookie, pulseCookieFinal)
|
||||
a.AppendEnv(pulseCookie, pulseCookieFinal)
|
||||
verbose.Printf("Publishing PulseAudio cookie '%s' to '%s'\n", pulseCookieSource, pulseCookieFinal)
|
||||
if err = util.CopyFile(pulseCookieFinal, pulseCookieSource); err != nil {
|
||||
final.Fatal("Error copying PulseAudio cookie:", err)
|
||||
internal.Fatal("Error copying PulseAudio cookie:", err)
|
||||
}
|
||||
if err = acl.UpdatePerm(pulseCookieFinal, a.UID(), acl.Read); err != nil {
|
||||
final.Fatal("Error publishing PulseAudio cookie:", err)
|
||||
internal.Fatal("Error publishing PulseAudio cookie:", err)
|
||||
} else {
|
||||
final.RegisterRevertPath(pulseCookieFinal)
|
||||
a.exit.RegisterRevertPath(pulseCookieFinal)
|
||||
}
|
||||
|
||||
verbose.Printf("PulseAudio dir '%s' configured\n", pulse)
|
||||
}
|
||||
}
|
||||
|
||||
// discoverPulseCookie try various standard methods to discover the current user's PulseAudio authentication cookie
|
||||
func discoverPulseCookie() string {
|
||||
if p, ok := os.LookupEnv(pulseCookie); ok {
|
||||
return p
|
||||
}
|
||||
|
||||
if p, ok := os.LookupEnv(home); ok {
|
||||
p = path.Join(p, ".pulse-cookie")
|
||||
if s, err := os.Stat(p); err != nil {
|
||||
if !errors.Is(err, fs.ErrNotExist) {
|
||||
internal.Fatal("Error accessing PulseAudio cookie:", err)
|
||||
// unreachable
|
||||
return p
|
||||
}
|
||||
} else if !s.IsDir() {
|
||||
return p
|
||||
}
|
||||
}
|
||||
|
||||
if p, ok := os.LookupEnv(xdgConfigHome); ok {
|
||||
p = path.Join(p, "pulse", "cookie")
|
||||
if s, err := os.Stat(p); err != nil {
|
||||
if !errors.Is(err, fs.ErrNotExist) {
|
||||
internal.Fatal("Error accessing PulseAudio cookie:", err)
|
||||
// unreachable
|
||||
return p
|
||||
}
|
||||
} else if !s.IsDir() {
|
||||
return p
|
||||
}
|
||||
}
|
||||
|
||||
internal.Fatal(fmt.Sprintf("Cannot locate PulseAudio cookie (tried $%s, $%s/pulse/cookie, $%s/.pulse-cookie)",
|
||||
pulseCookie, xdgConfigHome, home))
|
||||
return ""
|
||||
}
|
||||
|
@ -3,11 +3,11 @@ package app
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.ophivana.moe/cat/fortify/internal/final"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"git.ophivana.moe/cat/fortify/internal"
|
||||
"git.ophivana.moe/cat/fortify/internal/state"
|
||||
"git.ophivana.moe/cat/fortify/internal/util"
|
||||
"git.ophivana.moe/cat/fortify/internal/verbose"
|
||||
@ -52,28 +52,28 @@ func (a *App) Run() {
|
||||
verbose.Println("Executing:", cmd)
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
final.Fatal("Error starting process:", err)
|
||||
internal.Fatal("Error starting process:", err)
|
||||
}
|
||||
|
||||
final.RegisterEnablement(a.enablements)
|
||||
a.exit.SealEnablements(a.enablements)
|
||||
|
||||
if statePath, err := state.SaveProcess(a.Uid, cmd, a.runDirPath, a.command, a.enablements); err != nil {
|
||||
// process already started, shouldn't be fatal
|
||||
fmt.Println("Error registering process:", err)
|
||||
} else {
|
||||
final.RegisterStatePath(statePath)
|
||||
a.exit.SealStatePath(statePath)
|
||||
}
|
||||
|
||||
var r int
|
||||
if err := cmd.Wait(); err != nil {
|
||||
var exitError *exec.ExitError
|
||||
if !errors.As(err, &exitError) {
|
||||
final.Fatal("Error running process:", err)
|
||||
internal.Fatal("Error running process:", err)
|
||||
}
|
||||
}
|
||||
|
||||
verbose.Println("Process exited with exit code", r)
|
||||
final.BeforeExit()
|
||||
internal.BeforeExit()
|
||||
os.Exit(r)
|
||||
}
|
||||
|
||||
@ -101,7 +101,7 @@ func (a *App) commandBuilderSudo() (args []string) {
|
||||
|
||||
func (a *App) commandBuilderBwrap() (args []string) {
|
||||
// TODO: build bwrap command
|
||||
final.Fatal("bwrap")
|
||||
internal.Fatal("bwrap")
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
@ -129,7 +129,7 @@ func (a *App) commandBuilderMachineCtl() (args []string) {
|
||||
|
||||
// /bin/sh -c
|
||||
if sh, ok := util.Which("sh"); !ok {
|
||||
final.Fatal("Did not find 'sh' in PATH")
|
||||
internal.Fatal("Did not find 'sh' in PATH")
|
||||
} else {
|
||||
args = append(args, sh, "-c")
|
||||
}
|
||||
@ -147,9 +147,9 @@ func (a *App) commandBuilderMachineCtl() (args []string) {
|
||||
innerCommand.WriteString("; ")
|
||||
|
||||
if executable, err := os.Executable(); err != nil {
|
||||
final.Fatal("Error reading executable path:", err)
|
||||
internal.Fatal("Error reading executable path:", err)
|
||||
} else {
|
||||
if a.enablements.Has(state.EnableDBus) {
|
||||
if a.enablements.Has(internal.EnableDBus) {
|
||||
innerCommand.WriteString(dbusSessionBusAddress + "=" + "'" + dbusAddress[0] + "' ")
|
||||
if dbusSystem {
|
||||
innerCommand.WriteString(dbusSystemBusAddress + "=" + "'" + dbusAddress[1] + "' ")
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
"path"
|
||||
"strconv"
|
||||
|
||||
"git.ophivana.moe/cat/fortify/internal/state"
|
||||
"git.ophivana.moe/cat/fortify/internal"
|
||||
"git.ophivana.moe/cat/fortify/internal/util"
|
||||
"git.ophivana.moe/cat/fortify/internal/verbose"
|
||||
)
|
||||
@ -22,6 +22,8 @@ type App struct {
|
||||
env []string // modified via AppendEnv
|
||||
command []string // set on initialisation
|
||||
|
||||
exit *internal.ExitState // assigned
|
||||
|
||||
launchOptionText string // set on initialisation
|
||||
launchOption uint8 // assigned
|
||||
|
||||
@ -30,7 +32,7 @@ type App struct {
|
||||
runDirPath string // assigned
|
||||
toolPath string // assigned
|
||||
|
||||
enablements state.Enablements // set via setEnablement
|
||||
enablements internal.Enablements // set via setEnablement
|
||||
*user.User // assigned
|
||||
|
||||
// absolutely *no* method of this type is thread-safe
|
||||
@ -45,7 +47,7 @@ func (a *App) RunDir() string {
|
||||
return a.runDirPath
|
||||
}
|
||||
|
||||
func (a *App) setEnablement(e state.Enablement) {
|
||||
func (a *App) setEnablement(e internal.Enablement) {
|
||||
if a.enablements.Has(e) {
|
||||
panic("enablement " + e.String() + " set twice")
|
||||
}
|
||||
@ -53,6 +55,13 @@ func (a *App) setEnablement(e state.Enablement) {
|
||||
a.enablements |= e.Mask()
|
||||
}
|
||||
|
||||
func (a *App) SealExit(exit *internal.ExitState) {
|
||||
if a.exit != nil {
|
||||
panic("application exit state sealed twice")
|
||||
}
|
||||
a.exit = exit
|
||||
}
|
||||
|
||||
func New(userName string, args []string, launchOptionText string) *App {
|
||||
a := &App{
|
||||
command: args,
|
||||
@ -96,7 +105,7 @@ func New(userName string, args []string, launchOptionText string) *App {
|
||||
}
|
||||
|
||||
verbose.Println("Running as user", a.Username, "("+a.Uid+"),", "command:", a.command)
|
||||
if util.SdBootedV {
|
||||
if internal.SdBootedV {
|
||||
verbose.Println("System booted with systemd as init system (PID 1).")
|
||||
}
|
||||
|
||||
@ -120,7 +129,7 @@ func New(userName string, args []string, launchOptionText string) *App {
|
||||
}
|
||||
case "systemd":
|
||||
a.launchOption = LaunchMethodMachineCtl
|
||||
if !util.SdBootedV {
|
||||
if !internal.SdBootedV {
|
||||
fmt.Println("System has not been booted with systemd as init system (PID 1).")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
@ -6,8 +6,7 @@ import (
|
||||
"path"
|
||||
|
||||
"git.ophivana.moe/cat/fortify/acl"
|
||||
"git.ophivana.moe/cat/fortify/internal/final"
|
||||
"git.ophivana.moe/cat/fortify/internal/state"
|
||||
"git.ophivana.moe/cat/fortify/internal"
|
||||
"git.ophivana.moe/cat/fortify/internal/verbose"
|
||||
)
|
||||
|
||||
@ -17,19 +16,19 @@ const (
|
||||
)
|
||||
|
||||
func (a *App) ShareWayland() {
|
||||
a.setEnablement(state.EnableWayland)
|
||||
a.setEnablement(internal.EnableWayland)
|
||||
|
||||
// ensure Wayland socket ACL (e.g. `/run/user/%d/wayland-%d`)
|
||||
if w, ok := os.LookupEnv(waylandDisplay); !ok {
|
||||
final.Fatal("Wayland: WAYLAND_DISPLAY not set")
|
||||
internal.Fatal("Wayland: WAYLAND_DISPLAY not set")
|
||||
} else {
|
||||
// add environment variable for new process
|
||||
wp := path.Join(a.runtimePath, w)
|
||||
a.AppendEnv(waylandDisplay, wp)
|
||||
if err := acl.UpdatePerm(wp, a.UID(), acl.Read, acl.Write, acl.Execute); err != nil {
|
||||
final.Fatal(fmt.Sprintf("Error preparing Wayland '%s':", w), err)
|
||||
internal.Fatal(fmt.Sprintf("Error preparing Wayland '%s':", w), err)
|
||||
} else {
|
||||
final.RegisterRevertPath(wp)
|
||||
a.exit.RegisterRevertPath(wp)
|
||||
}
|
||||
verbose.Printf("Wayland socket '%s' configured\n", w)
|
||||
}
|
||||
|
@ -4,8 +4,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"git.ophivana.moe/cat/fortify/internal/final"
|
||||
"git.ophivana.moe/cat/fortify/internal/state"
|
||||
"git.ophivana.moe/cat/fortify/internal"
|
||||
"git.ophivana.moe/cat/fortify/internal/verbose"
|
||||
"git.ophivana.moe/cat/fortify/xcb"
|
||||
)
|
||||
@ -13,20 +12,20 @@ import (
|
||||
const display = "DISPLAY"
|
||||
|
||||
func (a *App) ShareX() {
|
||||
a.setEnablement(state.EnableX)
|
||||
a.setEnablement(internal.EnableX)
|
||||
|
||||
// discovery X11 and grant user permission via the `ChangeHosts` command
|
||||
if d, ok := os.LookupEnv(display); !ok {
|
||||
final.Fatal("X11: DISPLAY not set")
|
||||
internal.Fatal("X11: DISPLAY not set")
|
||||
} else {
|
||||
// add environment variable for new process
|
||||
a.AppendEnv(display, d)
|
||||
|
||||
verbose.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 {
|
||||
final.Fatal(fmt.Sprintf("Error adding XHost entry to '%s':", d), err)
|
||||
internal.Fatal(fmt.Sprintf("Error adding XHost entry to '%s':", d), err)
|
||||
} else {
|
||||
final.XcbActionComplete()
|
||||
a.exit.XcbActionComplete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
16
internal/early.go
Normal file
16
internal/early.go
Normal file
@ -0,0 +1,16 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.ophivana.moe/cat/fortify/internal/util"
|
||||
)
|
||||
|
||||
var SdBootedV = func() bool {
|
||||
if v, err := util.SdBooted(); err != nil {
|
||||
fmt.Println("warn: read systemd marker:", err)
|
||||
return false
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}()
|
@ -1,4 +1,4 @@
|
||||
package state
|
||||
package internal
|
||||
|
||||
type (
|
||||
Enablement uint8
|
||||
@ -11,10 +11,10 @@ const (
|
||||
EnableDBus
|
||||
EnablePulse
|
||||
|
||||
enableLength
|
||||
EnableLength
|
||||
)
|
||||
|
||||
var enablementString = [enableLength]string{
|
||||
var enablementString = [EnableLength]string{
|
||||
"Wayland",
|
||||
"X11",
|
||||
"D-Bus",
|
176
internal/exit.go
Normal file
176
internal/exit.go
Normal file
@ -0,0 +1,176 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/user"
|
||||
|
||||
"git.ophivana.moe/cat/fortify/acl"
|
||||
"git.ophivana.moe/cat/fortify/dbus"
|
||||
"git.ophivana.moe/cat/fortify/internal/verbose"
|
||||
"git.ophivana.moe/cat/fortify/xcb"
|
||||
)
|
||||
|
||||
// ExitState keeps track of various changes fortify made to the system
|
||||
// as well as other resources that need to be manually released.
|
||||
// NOT thread safe.
|
||||
type ExitState struct {
|
||||
// target fortified user inherited from app.App
|
||||
user *user.User
|
||||
// integer UID of targeted user
|
||||
uid int
|
||||
// returns amount of launcher states read
|
||||
launcherStateCount func() (int, error)
|
||||
|
||||
// paths to strip ACLs (of target user) from
|
||||
aclCleanupCandidate []string
|
||||
// target process capability enablements
|
||||
enablements *Enablements
|
||||
// whether the xcb.ChangeHosts action was complete
|
||||
xcbActionComplete bool
|
||||
|
||||
// reference to D-Bus proxy instance, nil if disabled
|
||||
dbusProxy *dbus.Proxy
|
||||
// D-Bus wait complete notification
|
||||
dbusDone *chan struct{}
|
||||
|
||||
// path to fortify process state information
|
||||
statePath string
|
||||
|
||||
// prevents cleanup from happening twice
|
||||
complete bool
|
||||
}
|
||||
|
||||
// RegisterRevertPath registers a path with ACLs added by fortify
|
||||
func (s *ExitState) RegisterRevertPath(p string) {
|
||||
s.aclCleanupCandidate = append(s.aclCleanupCandidate, p)
|
||||
}
|
||||
|
||||
// SealEnablements submits the child process enablements
|
||||
func (s *ExitState) SealEnablements(e Enablements) {
|
||||
if s.enablements != nil {
|
||||
panic("enablement exit state set twice")
|
||||
}
|
||||
s.enablements = &e
|
||||
}
|
||||
|
||||
// XcbActionComplete submits xcb.ChangeHosts action completion
|
||||
func (s *ExitState) XcbActionComplete() {
|
||||
if s.xcbActionComplete {
|
||||
Fatal("xcb inserted twice")
|
||||
}
|
||||
s.xcbActionComplete = true
|
||||
}
|
||||
|
||||
// SealDBus submits the child's D-Bus proxy instance
|
||||
func (s *ExitState) SealDBus(p *dbus.Proxy, done *chan struct{}) {
|
||||
if p == nil {
|
||||
Fatal("unexpected nil dbus proxy exit state submitted")
|
||||
}
|
||||
if s.dbusProxy != nil {
|
||||
Fatal("dbus proxy exit state set twice")
|
||||
}
|
||||
s.dbusProxy = p
|
||||
s.dbusDone = done
|
||||
}
|
||||
|
||||
// SealStatePath submits filesystem path to the fortify process's state file
|
||||
func (s *ExitState) SealStatePath(v string) {
|
||||
if s.statePath != "" {
|
||||
panic("statePath set twice")
|
||||
}
|
||||
|
||||
s.statePath = v
|
||||
}
|
||||
|
||||
// NewExit initialises a new ExitState containing basic, unchanging information
|
||||
// about the fortify process required during cleanup
|
||||
func NewExit(u *user.User, uid int, f func() (int, error)) *ExitState {
|
||||
return &ExitState{
|
||||
uid: uid,
|
||||
user: u,
|
||||
|
||||
launcherStateCount: f,
|
||||
}
|
||||
}
|
||||
|
||||
func Fatal(msg ...any) {
|
||||
fmt.Println(msg...)
|
||||
BeforeExit()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var exitState *ExitState
|
||||
|
||||
func SealExit(s *ExitState) {
|
||||
if exitState != nil {
|
||||
panic("exit state submitted twice")
|
||||
}
|
||||
|
||||
exitState = s
|
||||
}
|
||||
|
||||
func BeforeExit() {
|
||||
if exitState == nil {
|
||||
fmt.Println("warn: cleanup attempted before exit state submission")
|
||||
return
|
||||
}
|
||||
|
||||
exitState.beforeExit()
|
||||
}
|
||||
|
||||
func (s *ExitState) beforeExit() {
|
||||
if s.complete {
|
||||
panic("beforeExit called twice")
|
||||
}
|
||||
|
||||
if s.statePath == "" {
|
||||
verbose.Println("State path is unset")
|
||||
} else {
|
||||
if err := os.Remove(s.statePath); err != nil && !errors.Is(err, fs.ErrNotExist) {
|
||||
fmt.Println("Error removing state file:", err)
|
||||
}
|
||||
}
|
||||
|
||||
if count, err := s.launcherStateCount(); err != nil {
|
||||
fmt.Println("Error reading active launchers:", err)
|
||||
os.Exit(1)
|
||||
} else if count > 0 {
|
||||
// other launchers are still active
|
||||
verbose.Printf("Found %d active launchers, exiting without cleaning up\n", count)
|
||||
return
|
||||
}
|
||||
|
||||
verbose.Println("No other launchers active, will clean up")
|
||||
|
||||
if s.xcbActionComplete {
|
||||
verbose.Printf("X11: Removing XHost entry SI:localuser:%s\n", s.user.Username)
|
||||
if err := xcb.ChangeHosts(xcb.HostModeDelete, xcb.FamilyServerInterpreted, "localuser\x00"+s.user.Username); err != nil {
|
||||
fmt.Println("Error removing XHost entry:", err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, candidate := range s.aclCleanupCandidate {
|
||||
if err := acl.UpdatePerm(candidate, s.uid); err != nil {
|
||||
fmt.Printf("Error stripping ACL entry from '%s': %s\n", candidate, err)
|
||||
}
|
||||
verbose.Printf("Stripped ACL entry for user '%s' from '%s'\n", s.user.Username, candidate)
|
||||
}
|
||||
|
||||
if s.dbusProxy != nil {
|
||||
verbose.Println("D-Bus proxy registered, cleaning up")
|
||||
|
||||
if err := s.dbusProxy.Close(); err != nil {
|
||||
if errors.Is(err, os.ErrClosed) {
|
||||
verbose.Println("D-Bus proxy already closed")
|
||||
} else {
|
||||
fmt.Println("Error closing D-Bus proxy:", err)
|
||||
}
|
||||
}
|
||||
|
||||
// wait for Proxy.Wait to return
|
||||
<-*s.dbusDone
|
||||
}
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
package final
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
|
||||
"git.ophivana.moe/cat/fortify/acl"
|
||||
"git.ophivana.moe/cat/fortify/internal/state"
|
||||
"git.ophivana.moe/cat/fortify/internal/verbose"
|
||||
"git.ophivana.moe/cat/fortify/xcb"
|
||||
)
|
||||
|
||||
func Fatal(msg ...any) {
|
||||
fmt.Println(msg...)
|
||||
BeforeExit()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func BeforeExit() {
|
||||
if u == nil {
|
||||
fmt.Println("warn: beforeExit called before app init")
|
||||
return
|
||||
}
|
||||
|
||||
if statePath == "" {
|
||||
verbose.Println("State path is unset")
|
||||
} else {
|
||||
if err := os.Remove(statePath); err != nil && !errors.Is(err, fs.ErrNotExist) {
|
||||
fmt.Println("Error removing state file:", err)
|
||||
}
|
||||
}
|
||||
|
||||
if d, err := state.ReadLaunchers(runDirPath, u.Uid); err != nil {
|
||||
fmt.Println("Error reading active launchers:", err)
|
||||
os.Exit(1)
|
||||
} else if len(d) > 0 {
|
||||
// other launchers are still active
|
||||
verbose.Printf("Found %d active launchers, exiting without cleaning up\n", len(d))
|
||||
return
|
||||
}
|
||||
|
||||
verbose.Println("No other launchers active, will clean up")
|
||||
|
||||
if xcbActionComplete {
|
||||
verbose.Printf("X11: Removing XHost entry SI:localuser:%s\n", u.Username)
|
||||
if err := xcb.ChangeHosts(xcb.HostModeDelete, xcb.FamilyServerInterpreted, "localuser\x00"+u.Username); err != nil {
|
||||
fmt.Println("Error removing XHost entry:", err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, candidate := range cleanupCandidate {
|
||||
if err := acl.UpdatePerm(candidate, uid); err != nil {
|
||||
fmt.Printf("Error stripping ACL entry from '%s': %s\n", candidate, err)
|
||||
}
|
||||
verbose.Printf("Stripped ACL entry for user '%s' from '%s'\n", u.Username, candidate)
|
||||
}
|
||||
|
||||
if dbusProxy != nil {
|
||||
verbose.Println("D-Bus proxy registered, cleaning up")
|
||||
|
||||
if err := dbusProxy.Close(); err != nil {
|
||||
if errors.Is(err, os.ErrClosed) {
|
||||
verbose.Println("D-Bus proxy already closed")
|
||||
} else {
|
||||
fmt.Println("Error closing D-Bus proxy:", err)
|
||||
}
|
||||
}
|
||||
|
||||
// wait for Proxy.Wait to return
|
||||
<-*dbusDone
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package final
|
||||
|
||||
import "os/user"
|
||||
|
||||
var (
|
||||
u *user.User
|
||||
uid int
|
||||
|
||||
runDirPath string
|
||||
)
|
||||
|
||||
func Prepare(val user.User, d int, s string) {
|
||||
if u != nil {
|
||||
panic("final prepared twice")
|
||||
}
|
||||
|
||||
u = &val
|
||||
uid = d
|
||||
runDirPath = s
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
package final
|
||||
|
||||
import (
|
||||
"git.ophivana.moe/cat/fortify/dbus"
|
||||
"git.ophivana.moe/cat/fortify/internal/state"
|
||||
)
|
||||
|
||||
var (
|
||||
cleanupCandidate []string
|
||||
enablements *state.Enablements
|
||||
xcbActionComplete bool
|
||||
|
||||
dbusProxy *dbus.Proxy
|
||||
dbusDone *chan struct{}
|
||||
|
||||
statePath string
|
||||
)
|
||||
|
||||
func RegisterRevertPath(p string) {
|
||||
cleanupCandidate = append(cleanupCandidate, p)
|
||||
}
|
||||
|
||||
func RegisterEnablement(e state.Enablements) {
|
||||
if enablements != nil {
|
||||
panic("enablement state set twice")
|
||||
}
|
||||
enablements = &e
|
||||
}
|
||||
|
||||
func XcbActionComplete() {
|
||||
if xcbActionComplete {
|
||||
Fatal("xcb inserted twice")
|
||||
}
|
||||
xcbActionComplete = true
|
||||
}
|
||||
|
||||
func RegisterDBus(p *dbus.Proxy, done *chan struct{}) {
|
||||
dbusProxy = p
|
||||
dbusDone = done
|
||||
}
|
||||
|
||||
func RegisterStatePath(v string) {
|
||||
if statePath != "" {
|
||||
panic("statePath set twice")
|
||||
}
|
||||
|
||||
statePath = v
|
||||
}
|
@ -4,6 +4,8 @@ import (
|
||||
"encoding/gob"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"git.ophivana.moe/cat/fortify/internal"
|
||||
)
|
||||
|
||||
// we unfortunately have to assume there are never races between processes
|
||||
@ -14,10 +16,12 @@ type launcherState struct {
|
||||
Launcher string
|
||||
Argv []string
|
||||
Command []string
|
||||
Capability Enablements
|
||||
Capability internal.Enablements
|
||||
}
|
||||
|
||||
func ReadLaunchers(runDirPath, uid string) ([]*launcherState, error) {
|
||||
// ReadLaunchers reads all launcher state file entries for the requested user
|
||||
// and if decode is true decodes these launchers as well.
|
||||
func ReadLaunchers(runDirPath, uid string, decode bool) ([]*launcherState, error) {
|
||||
var f *os.File
|
||||
var r []*launcherState
|
||||
launcherPrefix := path.Join(runDirPath, uid)
|
||||
@ -39,7 +43,11 @@ func ReadLaunchers(runDirPath, uid string) ([]*launcherState, error) {
|
||||
|
||||
var s launcherState
|
||||
r = append(r, &s)
|
||||
if decode {
|
||||
return gob.NewDecoder(f).Decode(&s)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}(); err != nil {
|
||||
return nil, err
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"git.ophivana.moe/cat/fortify/internal"
|
||||
"git.ophivana.moe/cat/fortify/internal/verbose"
|
||||
)
|
||||
|
||||
@ -31,7 +32,7 @@ func MustPrintLauncherStateGlobal(w **tabwriter.Writer, runDirPath string) {
|
||||
}
|
||||
|
||||
func MustPrintLauncherState(w **tabwriter.Writer, runDirPath, uid string) {
|
||||
launchers, err := ReadLaunchers(runDirPath, uid)
|
||||
launchers, err := ReadLaunchers(runDirPath, uid, true)
|
||||
if err != nil {
|
||||
fmt.Println("Error reading launchers:", err)
|
||||
os.Exit(1)
|
||||
@ -49,7 +50,7 @@ func MustPrintLauncherState(w **tabwriter.Writer, runDirPath, uid string) {
|
||||
|
||||
for _, state := range launchers {
|
||||
enablementsDescription := strings.Builder{}
|
||||
for i := Enablement(0); i < enableLength; i++ {
|
||||
for i := internal.Enablement(0); i < internal.EnableLength; i++ {
|
||||
if state.Capability.Has(i) {
|
||||
enablementsDescription.WriteString(", " + i.String())
|
||||
}
|
||||
|
@ -8,10 +8,12 @@ import (
|
||||
"os/exec"
|
||||
"path"
|
||||
"strconv"
|
||||
|
||||
"git.ophivana.moe/cat/fortify/internal"
|
||||
)
|
||||
|
||||
// SaveProcess called after process start, before wait
|
||||
func SaveProcess(uid string, cmd *exec.Cmd, runDirPath string, command []string, enablements Enablements) (string, error) {
|
||||
func SaveProcess(uid string, cmd *exec.Cmd, runDirPath string, command []string, enablements internal.Enablements) (string, error) {
|
||||
statePath := path.Join(runDirPath, uid, strconv.Itoa(cmd.Process.Pid))
|
||||
state := launcherState{
|
||||
PID: cmd.Process.Pid,
|
||||
|
@ -1,12 +0,0 @@
|
||||
package util
|
||||
|
||||
import "fmt"
|
||||
|
||||
var SdBootedV = func() bool {
|
||||
if v, err := SdBooted(); err != nil {
|
||||
fmt.Println("warn: read systemd marker:", err)
|
||||
return false
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}()
|
@ -2,21 +2,12 @@ package util
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.ophivana.moe/cat/fortify/internal/final"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
const (
|
||||
systemdCheckPath = "/run/systemd/system"
|
||||
|
||||
home = "HOME"
|
||||
xdgConfigHome = "XDG_CONFIG_HOME"
|
||||
|
||||
PulseServer = "PULSE_SERVER"
|
||||
PulseCookie = "PULSE_COOKIE"
|
||||
)
|
||||
|
||||
// SdBooted implements https://www.freedesktop.org/software/systemd/man/sd_booted.html
|
||||
@ -31,40 +22,3 @@ func SdBooted() (bool, error) {
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// DiscoverPulseCookie try various standard methods to discover the current user's PulseAudio authentication cookie
|
||||
func DiscoverPulseCookie() string {
|
||||
if p, ok := os.LookupEnv(PulseCookie); ok {
|
||||
return p
|
||||
}
|
||||
|
||||
if p, ok := os.LookupEnv(home); ok {
|
||||
p = path.Join(p, ".pulse-cookie")
|
||||
if s, err := os.Stat(p); err != nil {
|
||||
if !errors.Is(err, fs.ErrNotExist) {
|
||||
final.Fatal("Error accessing PulseAudio cookie:", err)
|
||||
// unreachable
|
||||
return p
|
||||
}
|
||||
} else if !s.IsDir() {
|
||||
return p
|
||||
}
|
||||
}
|
||||
|
||||
if p, ok := os.LookupEnv(xdgConfigHome); ok {
|
||||
p = path.Join(p, "pulse", "cookie")
|
||||
if s, err := os.Stat(p); err != nil {
|
||||
if !errors.Is(err, fs.ErrNotExist) {
|
||||
final.Fatal("Error accessing PulseAudio cookie:", err)
|
||||
// unreachable
|
||||
return p
|
||||
}
|
||||
} else if !s.IsDir() {
|
||||
return p
|
||||
}
|
||||
}
|
||||
|
||||
final.Fatal(fmt.Sprintf("Cannot locate PulseAudio cookie (tried $%s, $%s/pulse/cookie, $%s/.pulse-cookie)",
|
||||
PulseCookie, xdgConfigHome, home))
|
||||
return ""
|
||||
}
|
||||
|
20
main.go
20
main.go
@ -10,10 +10,10 @@ import (
|
||||
"strconv"
|
||||
"syscall"
|
||||
|
||||
"git.ophivana.moe/cat/fortify/internal/final"
|
||||
|
||||
"git.ophivana.moe/cat/fortify/dbus"
|
||||
"git.ophivana.moe/cat/fortify/internal"
|
||||
"git.ophivana.moe/cat/fortify/internal/app"
|
||||
"git.ophivana.moe/cat/fortify/internal/state"
|
||||
"git.ophivana.moe/cat/fortify/internal/verbose"
|
||||
)
|
||||
|
||||
@ -21,6 +21,7 @@ var (
|
||||
Version = "impure"
|
||||
|
||||
a *app.App
|
||||
s *internal.ExitState
|
||||
|
||||
dbusSession *dbus.Config
|
||||
dbusSystem *dbus.Config
|
||||
@ -47,7 +48,12 @@ func main() {
|
||||
tryLicense()
|
||||
|
||||
a = app.New(userName, flag.Args(), launchOptionText)
|
||||
final.Prepare(*a.User, a.UID(), a.RunDir())
|
||||
s = internal.NewExit(a.User, a.UID(), func() (int, error) {
|
||||
d, err := state.ReadLaunchers(a.RunDir(), a.Uid, false)
|
||||
return len(d), err
|
||||
})
|
||||
a.SealExit(s)
|
||||
internal.SealExit(s)
|
||||
|
||||
// parse D-Bus config file if applicable
|
||||
if mustDBus {
|
||||
@ -55,10 +61,10 @@ func main() {
|
||||
dbusSession = dbus.NewConfig(dbusID, true, mpris)
|
||||
} else {
|
||||
if f, err := os.Open(dbusConfigSession); err != nil {
|
||||
final.Fatal("Error opening D-Bus proxy config file:", err)
|
||||
internal.Fatal("Error opening D-Bus proxy config file:", err)
|
||||
} else {
|
||||
if err = json.NewDecoder(f).Decode(&dbusSession); err != nil {
|
||||
final.Fatal("Error parsing D-Bus proxy config file:", err)
|
||||
internal.Fatal("Error parsing D-Bus proxy config file:", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -66,10 +72,10 @@ func main() {
|
||||
// system bus proxy is optional
|
||||
if dbusConfigSystem != "nil" {
|
||||
if f, err := os.Open(dbusConfigSystem); err != nil {
|
||||
final.Fatal("Error opening D-Bus proxy config file:", err)
|
||||
internal.Fatal("Error opening D-Bus proxy config file:", err)
|
||||
} else {
|
||||
if err = json.NewDecoder(f).Decode(&dbusSystem); err != nil {
|
||||
final.Fatal("Error parsing D-Bus proxy config file:", err)
|
||||
internal.Fatal("Error parsing D-Bus proxy config file:", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user