2024-09-22 00:29:36 +09:00
|
|
|
package app
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io/fs"
|
|
|
|
"os"
|
|
|
|
"path"
|
|
|
|
|
|
|
|
"git.ophivana.moe/cat/fortify/internal/state"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
pulseServer = "PULSE_SERVER"
|
|
|
|
pulseCookie = "PULSE_COOKIE"
|
|
|
|
|
|
|
|
home = "HOME"
|
|
|
|
xdgConfigHome = "XDG_CONFIG_HOME"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
ErrPulseCookie = errors.New("pulse cookie not present")
|
|
|
|
ErrPulseSocket = errors.New("pulse socket not present")
|
|
|
|
ErrPulseMode = errors.New("unexpected pulse socket mode")
|
|
|
|
)
|
|
|
|
|
|
|
|
type (
|
|
|
|
PulseCookieAccessError BaseError
|
|
|
|
PulseSocketAccessError BaseError
|
|
|
|
)
|
|
|
|
|
|
|
|
func (seal *appSeal) sharePulse() error {
|
|
|
|
if !seal.et.Has(state.EnablePulse) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-10-10 12:44:08 +09:00
|
|
|
// check PulseAudio directory presence (e.g. `/run/user/%d/pulse`)
|
2024-09-22 00:29:36 +09:00
|
|
|
pd := path.Join(seal.RuntimePath, "pulse")
|
|
|
|
ps := path.Join(pd, "native")
|
|
|
|
if _, err := os.Stat(pd); err != nil {
|
|
|
|
if !errors.Is(err, fs.ErrNotExist) {
|
|
|
|
return (*PulseSocketAccessError)(wrapError(err,
|
|
|
|
fmt.Sprintf("cannot access PulseAudio directory '%s':", pd), err))
|
|
|
|
}
|
|
|
|
return (*PulseSocketAccessError)(wrapError(ErrPulseSocket,
|
|
|
|
fmt.Sprintf("PulseAudio directory '%s' not found", pd)))
|
|
|
|
}
|
|
|
|
|
2024-10-10 12:44:08 +09:00
|
|
|
// check PulseAudio socket permission (e.g. `/run/user/%d/pulse/native`)
|
2024-09-22 00:29:36 +09:00
|
|
|
if s, err := os.Stat(ps); err != nil {
|
|
|
|
if !errors.Is(err, fs.ErrNotExist) {
|
|
|
|
return (*PulseSocketAccessError)(wrapError(err,
|
|
|
|
fmt.Sprintf("cannot access PulseAudio socket '%s':", ps), err))
|
|
|
|
}
|
|
|
|
return (*PulseSocketAccessError)(wrapError(ErrPulseSocket,
|
|
|
|
fmt.Sprintf("PulseAudio directory '%s' found but socket does not exist", pd)))
|
|
|
|
} else {
|
|
|
|
if m := s.Mode(); m&0o006 != 0o006 {
|
|
|
|
return (*PulseSocketAccessError)(wrapError(ErrPulseMode,
|
|
|
|
fmt.Sprintf("unexpected permissions on '%s':", ps), m))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-10 12:44:08 +09:00
|
|
|
// hard link pulse socket into target-executable share
|
|
|
|
psi := path.Join(seal.shareLocal, "pulse")
|
2024-10-11 04:18:15 +09:00
|
|
|
p := path.Join(seal.sys.runtime, "pulse", "native")
|
2024-10-10 12:44:08 +09:00
|
|
|
seal.sys.link(ps, psi)
|
2024-10-15 02:15:55 +09:00
|
|
|
seal.sys.bwrap.Bind(psi, p)
|
2024-10-11 04:18:15 +09:00
|
|
|
seal.sys.setEnv(pulseServer, "unix:"+p)
|
2024-10-10 12:44:08 +09:00
|
|
|
|
2024-09-22 00:29:36 +09:00
|
|
|
// publish current user's pulse cookie for target user
|
|
|
|
if src, err := discoverPulseCookie(); err != nil {
|
|
|
|
return err
|
|
|
|
} else {
|
|
|
|
dst := path.Join(seal.share, "pulse-cookie")
|
2024-10-11 04:18:15 +09:00
|
|
|
seal.sys.setEnv(pulseCookie, dst)
|
2024-09-22 00:29:36 +09:00
|
|
|
seal.sys.copyFile(dst, src)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// discoverPulseCookie attempts various standard methods to discover the current user's PulseAudio authentication cookie
|
|
|
|
func discoverPulseCookie() (string, error) {
|
|
|
|
if p, ok := os.LookupEnv(pulseCookie); ok {
|
|
|
|
return p, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// dotfile $HOME/.pulse-cookie
|
|
|
|
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) {
|
|
|
|
return p, (*PulseCookieAccessError)(wrapError(err,
|
|
|
|
fmt.Sprintf("cannot access PulseAudio cookie '%s':", p), err))
|
|
|
|
}
|
|
|
|
// not found, try next method
|
|
|
|
} else if !s.IsDir() {
|
|
|
|
return p, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// $XDG_CONFIG_HOME/pulse/cookie
|
|
|
|
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) {
|
|
|
|
return p, (*PulseCookieAccessError)(wrapError(err, "cannot access PulseAudio cookie", p+":", err))
|
|
|
|
}
|
|
|
|
// not found, try next method
|
|
|
|
} else if !s.IsDir() {
|
|
|
|
return p, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return "", (*PulseCookieAccessError)(wrapError(ErrPulseCookie,
|
|
|
|
fmt.Sprintf("cannot locate PulseAudio cookie (tried $%s, $%s/pulse/cookie, $%s/.pulse-cookie)",
|
|
|
|
pulseCookie, xdgConfigHome, home)))
|
|
|
|
}
|