1//go:build unix
2
3package mox
4
5import (
6 "log/slog"
7 "os"
8 "os/signal"
9 "strings"
10 "syscall"
11)
12
13// Fork and exec as unprivileged user.
14//
15// We don't use just setuid because it is hard to guarantee that no other
16// privileged go worker processes have been started before we get here. E.g. init
17// functions in packages can start goroutines.
18func ForkExecUnprivileged() {
19 prog, err := os.Executable()
20 if err != nil {
21 pkglog.Fatalx("finding executable for exec", err)
22 }
23
24 files := []*os.File{os.Stdin, os.Stdout, os.Stderr}
25 var addrs []string
26 for addr, f := range passedListeners {
27 files = append(files, f)
28 addrs = append(addrs, addr)
29 }
30 var paths []string
31 for path, fl := range passedFiles {
32 for _, f := range fl {
33 files = append(files, f)
34 paths = append(paths, path)
35 }
36 }
37 env := os.Environ()
38 env = append(env, "MOX_SOCKETS="+strings.Join(addrs, ","), "MOX_FILES="+strings.Join(paths, ","))
39
40 p, err := os.StartProcess(prog, os.Args, &os.ProcAttr{
41 Env: env,
42 Files: files,
43 Sys: &syscall.SysProcAttr{
44 Credential: &syscall.Credential{
45 Uid: Conf.Static.UID,
46 Gid: Conf.Static.GID,
47 },
48 },
49 })
50 if err != nil {
51 pkglog.Fatalx("fork and exec", err)
52 }
53 CleanupPassedFiles()
54
55 // If we get a interrupt/terminate signal, pass it on to the child. For interrupt,
56 // the child probably already got it.
57 // todo: see if we tie up child and root process so a kill -9 of the root process
58 // kills the child process too.
59 sigc := make(chan os.Signal, 1)
60 signal.Notify(sigc, os.Interrupt, syscall.SIGTERM)
61 go func() {
62 sig := <-sigc
63 p.Signal(sig)
64 }()
65
66 st, err := p.Wait()
67 if err != nil {
68 pkglog.Fatalx("wait", err)
69 }
70 code := st.ExitCode()
71 pkglog.Print("stopping after child exit", slog.Int("exitcode", code))
72 os.Exit(code)
73}
74