package session import ( "crypto/rand" "encoding/hex" "fmt" "os" "os/exec" "os/signal" "path/filepath" "syscall" "time" "latchd/config" ) func setupX(uid, gid uint32, groups []uint32, cfg *config.Config) (*exec.Cmd, error) { display := os.Getenv("DISPLAY") vtnr := os.Getenv("XDG_VTNR") home := os.Getenv("HOME") if display == "" || vtnr == "" || home == "" { return nil, fmt.Errorf("missing DISPLAY/XDG_VTNR/HOME env vars") } xauthPath := filepath.Join(home, ".Xauthority") os.Remove(xauthPath) cookie, err := mCookie() if err != nil { return nil, err } xauthCmd := exec.Command(cfg.X11.XauthPath, "add", display, ".", cookie) xauthCmd.SysProcAttr = &syscall.SysProcAttr{ Credential: &syscall.Credential{Uid: uid, Gid: gid, Groups: groups}, } if out, err := xauthCmd.CombinedOutput(); err != nil { return nil, fmt.Errorf("xauth: %w — %s", err, string(out)) } os.Setenv("XAUTHORITY", xauthPath) dd := vtnr if len(dd) == 1 { dd = "0" + dd } // Xorg sends SIGUSR1 only when the parent ignores the signal during startup. signal.Ignore(syscall.SIGUSR1) cmd := exec.Command(cfg.SystemShell, "-c", fmt.Sprintf("%s %s vt%s", cfg.X11.ServerPath, display, dd)) if logF := openLog(cfg.X11.ServerLogPath); logF != nil { lw := &limitWriter{w: logF, max: logLimit} cmd.Stdout = lw cmd.Stderr = lw defer logF.Close() } if err := cmd.Start(); err != nil { signal.Reset(syscall.SIGUSR1) return nil, fmt.Errorf("X server: %w", err) } signal.Reset(syscall.SIGUSR1) ch := make(chan os.Signal, 1) signal.Notify(ch, syscall.SIGUSR1) defer signal.Stop(ch) timeout := time.Duration(cfg.X11.ServerTimeoutSecs) * time.Second deadline := time.After(timeout) tick := time.NewTicker(100 * time.Millisecond) defer tick.Stop() for { select { case <-ch: return cmd, nil case <-deadline: if cfg.X11.ServerTimeoutSecs == 0 { continue } cmd.Process.Signal(syscall.SIGTERM) cmd.Wait() return nil, fmt.Errorf("X server timed out after %ds", cfg.X11.ServerTimeoutSecs) case <-tick.C: var ws syscall.WaitStatus if pid, err := syscall.Wait4(cmd.Process.Pid, &ws, syscall.WNOHANG, nil); err == nil && pid == cmd.Process.Pid { return nil, fmt.Errorf("X server exited with status %d", ws.ExitStatus()) } } } } func mCookie() (string, error) { var b [16]byte if _, err := rand.Read(b[:]); err != nil { return "", err } return hex.EncodeToString(b[:]), nil }