1//go:build darwin
2
3package app
4
5import (
6 "crypto/sha256"
7 "errors"
8 "fmt"
9 "net"
10 "os"
11 "path/filepath"
12 "syscall"
13 "time"
14
15 "golang.org/x/sys/unix"
16
17 "github.com/bnema/vev/internal/platform"
18 "github.com/bnema/vev/pkg/safedir"
19)
20
21// Darwin has no abstract Unix sockets, so the control interface binds a
22// 0600 socket file in a private per-user directory. Peer credentials, not
23// the discoverability of this path, protect the local control interface.
24func webControlAddress() string {
25 path, _ := filepath.Abs(platform.StateDir())
26 sum := sha256.Sum256([]byte(path))
27 dir := filepath.Join(os.TempDir(), fmt.Sprintf("vev-web-%d", os.Getuid()))
28 return filepath.Join(dir, fmt.Sprintf("%x.sock", sum[:8]))
29}
30
31func sameWebUID(conn *net.UnixConn) bool {
32 raw, err := conn.SyscallConn()
33 if err != nil {
34 return false
35 }
36 var credential *unix.Xucred
37 var peerErr error
38 err = raw.Control(func(fd uintptr) {
39 credential, peerErr = unix.GetsockoptXucred(int(fd), unix.SOL_LOCAL, unix.LOCAL_PEERCRED)
40 })
41 return err == nil && peerErr == nil && credential != nil && credential.Uid == uint32(os.Geteuid())
42}
43
44func bindWebControl() (*net.UnixListener, error) {
45 path := webControlAddress()
46 if err := safedir.EnsurePrivate(filepath.Dir(path)); err != nil {
47 return nil, fmt.Errorf("securing web control directory: %w", err)
48 }
49 listener, err := net.ListenUnix("unix", &net.UnixAddr{Name: path, Net: "unix"})
50 if err != nil {
51 if !errors.Is(err, syscall.EADDRINUSE) {
52 return nil, err
53 }
54 // Distinguish a live gateway from a stale socket left by a crash.
55 probeConn, probeErr := net.DialTimeout("unix", path, time.Second)
56 if probeErr == nil {
57 _ = probeConn.Close()
58 return nil, errors.New("web gateway control is already bound")
59 }
60 if !errors.Is(probeErr, syscall.ECONNREFUSED) && !errors.Is(probeErr, syscall.ENOENT) {
61 return nil, fmt.Errorf("probing web control socket: %w", probeErr)
62 }
63 if rmErr := os.Remove(path); rmErr != nil && !os.IsNotExist(rmErr) {
64 return nil, rmErr
65 }
66 listener, err = net.ListenUnix("unix", &net.UnixAddr{Name: path, Net: "unix"})
67 if err != nil {
68 return nil, err
69 }
70 }
71 return listener, secureWebControlSocket(listener, path)
72}
73
74func secureWebControlSocket(listener *net.UnixListener, path string) error {
75 if err := os.Chmod(path, 0o600); err != nil {
76 _ = listener.Close()
77 return fmt.Errorf("securing web control socket: %w", err)
78 }
79 return nil
80}