arblant
firefox-vpn-client / core / internal / tunapi / socket_unix.go
socket_unix.go165 lines · 4.1 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
//go:build linux || darwin

package tunapi

import (
	"context"
	"errors"
	"fmt"
	"io/fs"
	"net"
	"os"
	"strconv"
	"syscall"
)

// ServiceName names the service (the systemd unit, or launchd's label's
// end); ExeName is its executable, which the app also carries beside its
// core.
const (
	ServiceName = "firefox-vpn-tun"
	ExeName     = "firefox-vpn-tun"
)

// ErrNoService means no service answers: it is not installed, or it is
// not running.
var ErrNoService = errors.New("the TUN service is not running")

// ErrNotAllowed means the service runs but this user did not install it:
// installing it again, as this user, lets them in.
var ErrNotAllowed = errors.New("the TUN service was installed for another user")

// socketPath is SocketPath; tests use their own.
var socketPath = SocketPath

// serverUID is the user the socket's server must be: root. Tests serve it
// as themselves.
var serverUID = 0

// Dial connects to the service. The socket is in a folder only root may
// write to (SocketPath), and its server must be root, so no other program
// can stand in for the service.
func Dial(ctx context.Context) (net.Conn, error) {
	var d net.Dialer
	c, err := d.DialContext(ctx, "unix", socketPath)
	if err != nil {
		switch {
		case errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ECONNREFUSED):
			return nil, ErrNoService
		case errors.Is(err, fs.ErrPermission):
			return nil, ErrNotAllowed
		}
		return nil, err
	}
	p, err := peer(c)
	if err != nil {
		c.Close()
		return nil, fmt.Errorf("asking who serves the TUN service socket: %w", err)
	}
	if p.uid != uint32(serverUID) {
		c.Close()
		return nil, fmt.Errorf("the TUN service socket is served by user %d, not by root", p.uid)
	}
	return c, nil
}

// Listen creates the service's socket, which every local user may open:
// the service answers root and the users with the given user ids, and
// others only CodeNotAllowed (Allowed). A socket left by a service that
// ended is replaced.
func Listen(users []string) (net.Listener, error) {
	allowed := map[uint32]bool{}
	for _, u := range users {
		uid, err := strconv.ParseUint(u, 10, 32)
		if err != nil {
			return nil, fmt.Errorf("bad user id %q", u)
		}
		allowed[uint32(uid)] = true
	}
	_ = os.Remove(socketPath)
	ln, err := net.Listen("unix", socketPath)
	if err != nil {
		return nil, err
	}
	if err := os.Chmod(socketPath, 0o666); err != nil {
		ln.Close()
		return nil, err
	}
	return &listener{Listener: ln, allowed: allowed}, nil
}

// listener accepts every client, and notes who it is.
type listener struct {
	net.Listener
	allowed map[uint32]bool
}

func (l *listener) Accept() (net.Conn, error) {
	c, err := l.Listener.Accept()
	if err != nil {
		return nil, err
	}
	p, err := peer(c)
	if err != nil {
		// Nobody known: allowed nothing.
		return &peerConn{Conn: c}, nil
	}
	return &peerConn{Conn: c, peerInfo: p, allowed: p.uid == 0 || l.allowed[p.uid]}, nil
}

// peerInfo is who is at the other end of a Unix socket.
type peerInfo struct {
	uid uint32
	pid int32
}

// peerConn is an accepted connection and who opened it.
type peerConn struct {
	net.Conn
	peerInfo
	allowed bool
}

// Allowed reports whether the client of a connection the service accepted
// may use it: root, or a user who installed it.
func Allowed(c net.Conn) bool {
	p, ok := c.(*peerConn)
	return ok && p.allowed
}

// ClientProcess is the process at the other end of a connection the
// service accepted.
func ClientProcess(c net.Conn) (uint32, error) {
	p, ok := c.(*peerConn)
	if !ok || p.pid <= 0 {
		return 0, errors.New("the client's process is not known")
	}
	return uint32(p.pid), nil
}

// Installed reports whether the service is installed: its unit or job
// file is there.
func Installed() (bool, error) {
	_, err := os.Stat(ServiceFile)
	if errors.Is(err, fs.ErrNotExist) {
		return false, nil
	}
	return err == nil, err
}

// fdOf runs f with the connection's file descriptor.
func fdOf(c net.Conn, f func(fd int) error) error {
	sc, ok := c.(syscall.Conn)
	if !ok {
		return errors.New("not a socket")
	}
	raw, err := sc.SyscallConn()
	if err != nil {
		return err
	}
	var ferr error
	if err := raw.Control(func(fd uintptr) { ferr = f(int(fd)) }); err != nil {
		return err
	}
	return ferr
}