// hsu starts the hakurei shim as the target subordinate user.
//
// The hsu program must be installed with the setuid and setgid bit set, and
// owned by root. A configuration file must be installed at /etc/hsurc with
// permission bits 0400, and owned by root. Each line of the file specifies a
// hakurei userid to kernel uid mapping. A line consists of the decimal string
// representation of the uid of the user wishing to start hakurei containers,
// followed by a space, followed by the decimal string representation of its
// userid. Duplicate uid entries are ignored, with the first occurrence taking
// effect.
//
// For example, to map the kernel uid 1000 to the hakurei user id 0:
//
//	1000 0
//
// # Internals
//
// Hakurei and hsu holds pathnames pointing to each other set at link time. For
// this reason, a distribution of hakurei has fixed installation prefix. Since
// this program is never invoked by the user, behaviour described in the
// following paragraphs are considered an internal detail and not covered by the
// compatibility promise.
//
// Since target container environment variables are set up in shim via the
// [container] infrastructure, the environment is used for parameters from the
// parent process.
//
// HAKUREI_SHIM specifies a single byte between '3' and '9' representing the
// setup pipe file descriptor. It is passed as is to the shim process and is the
// only value in the environment of the shim process. Since hsurc is not
// accessible to the parent process, leaving this unset causes hsu to print the
// corresponding hakurei user id of the parent and terminate.
//
// HAKUREI_IDENTITY specifies the identity of the instance being started and is
// used to produce the kernel uid alongside hakurei user id looked up from hsurc.
//
// HAKUREI_GROUPS specifies supplementary groups to inherit from the credentials
// of the parent process in a ' ' separated list of decimal string
// representations of gid. This has the unfortunate consequence of allowing
// users mapped via hsurc to effectively drop group membership, so special care
// must be taken to ensure this does not lead to an increase in access. This is
// not applicable to Rosa OS since unsigned code execution is not permitted
// outside hakurei containers, and is generally nonapplicable to the security
// model of hakurei, where all untrusted code runs within containers.
package main

import (
	"bytes"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"runtime"
	"slices"
	"strconv"
	"syscall"
)

const (
	// envShim is the name of the environment variable holding a single byte
	// representing the shim setup pipe file descriptor.
	envShim = "HAKUREI_SHIM"
	// envIdentity is the name of the environment variable holding a decimal
	// string representation of the current application identity.
	envIdentity = "HAKUREI_IDENTITY"
	// envGroups holds a ' ' separated list of decimal string representations of
	// supplementary group gid. Membership requirements are enforced.
	envGroups = "HAKUREI_GROUPS"
)

// hakureiPath is the absolute path to Hakurei.
//
// This is set by the linker.
var hakureiPath string

func main() {
	const PR_SET_NO_NEW_PRIVS = 0x26
	runtime.LockOSThread()

	log.SetFlags(0)
	log.SetPrefix("hsu: ")

	if os.Geteuid() != 0 {
		log.Fatal("this program must be owned by uid 0 and have the setuid bit set")
	}
	if os.Getegid() != os.Getgid() {
		log.Fatal("this program must not have the setgid bit set")
	}

	puid := os.Getuid()
	if puid == 0 {
		log.Fatal("this program must not be started by root")
	}

	if !filepath.IsAbs(hakureiPath) {
		log.Fatal("this program is compiled incorrectly")
		return
	}

	// refuse to run if hsurc is not protected correctly
	if s, err := os.Stat(hsuConfPath); err != nil {
		log.Fatal(err)
	} else if s.Mode().Perm() != 0400 {
		log.Fatal("bad hsurc perm")
	} else if st := s.Sys().(*syscall.Stat_t); st.Uid != 0 || st.Gid != 0 {
		log.Fatal("hsurc must be owned by uid 0")
	}

	// authenticate before accepting user input
	userid := mustParseConfig(puid)

	// pass through setup fd to shim
	var shimSetupFd string
	if s, ok := os.LookupEnv(envShim); !ok {
		// hakurei requests hsurc user id
		fmt.Print(userid)
		os.Exit(0)
	} else if len(s) != 1 || s[0] > '9' || s[0] < '3' {
		log.Fatal("HAKUREI_SHIM holds an invalid value")
	} else {
		shimSetupFd = s
	}

	// start is going ahead at this point
	identity := mustReadIdentity()

	const (
		// first possible uid outcome
		uidStart = 10000
		// last possible uid outcome
		uidEnd = 999919999
	)
	uid := int(toUser(userid, identity))

	// final bounds check to catch any bugs
	if uid < uidStart || uid >= uidEnd {
		panic("uid out of bounds")
	}

	// supplementary groups
	var suppGroups, suppCurrent []int

	if gs, ok := os.LookupEnv(envGroups); ok {
		if cur, err := os.Getgroups(); err != nil {
			log.Fatalf("cannot get groups: %v", err)
		} else {
			suppCurrent = cur
		}

		// parse space-separated list of group ids
		gss := bytes.Split([]byte(gs), []byte{' '})
		suppGroups = make([]int, len(gss)+1)
		for i, s := range gss {
			if gid, err := strconv.Atoi(string(s)); err != nil {
				log.Fatalf("cannot parse %q: %v", string(s), err)
			} else if gid > 0 && gid != uid && gid != os.Getgid() && slices.Contains(suppCurrent, gid) {
				suppGroups[i] = gid
			} else {
				log.Fatalf("invalid gid %d", gid)
			}
		}
		suppGroups[len(suppGroups)-1] = uid
	} else {
		suppGroups = []int{uid}
	}

	// careful! users in the allowlist is effectively allowed to drop groups via hsu
	if err := syscall.Setresgid(uid, uid, uid); err != nil {
		log.Fatalf("cannot set gid: %v", err)
	}
	if err := syscall.Setgroups(suppGroups); err != nil {
		log.Fatalf("cannot set supplementary groups: %v", err)
	}
	if err := syscall.Setresuid(uid, uid, uid); err != nil {
		log.Fatalf("cannot set uid: %v", err)
	}

	if _, _, errno := syscall.AllThreadsSyscall(
		syscall.SYS_PRCTL,
		PR_SET_NO_NEW_PRIVS, 1,
		0,
	); errno != 0 {
		log.Fatalf("cannot set no_new_privs flag: %s", errno.Error())
	}

	if err := syscall.Exec(hakureiPath, []string{
		"hakurei",
		"shim",
	}, []string{
		envShim + "=" + shimSetupFd,
	}); err != nil {
		log.Fatalf("cannot start shim: %v", err)
	}

	panic("unreachable")
}
