// +build linux,go1.16 aix darwin dragonfly freebsd illumos netbsd openbsd solaris

package main

import (
	"log"
	"os"
	"os/user"
	"strconv"
	"syscall"
)

type userInfo struct {
	uid int
	euid int
	gid int
	egid int
	supp_groups []int
	is_setuid bool
	is_setgid bool
	root_user bool
	root_prim_group bool
	root_supp_group bool
	need_drop bool
	unpriv_uid int
	unpriv_gid int
}

func getUserInfo(unprivUser string) (userInfo, error) {
	var ui userInfo
	ui.uid = os.Getuid()
	ui.euid = os.Geteuid()
	ui.gid = os.Getgid()
	ui.egid = os.Getegid()
	supp_groups, err := os.Getgroups()
	if err != nil {
		log.Println("Could not get supplementary groups: ", err.Error())
		return ui, err
	}
	ui.supp_groups = supp_groups
	ui.unpriv_uid = -1
	ui.unpriv_gid = -1

	ui.is_setuid = ui.uid != ui.euid
	ui.is_setgid = ui.gid != ui.egid
	ui.root_user = ui.uid == 0 || ui.euid == 0
	ui.root_prim_group = ui.gid == 0 || ui.egid == 0
	for _, gid := range ui.supp_groups {
		if gid == 0 {
			ui.root_supp_group = true
			break
		}
	}
	ui.need_drop = ui.is_setuid || ui.is_setgid || ui.root_user || ui.root_prim_group || ui.root_supp_group

	if ui.root_user || ui.root_prim_group {
		nobody_user, err := user.Lookup(unprivUser)
		if err != nil {
			log.Println("Running as root but could not lookup UID for user " + unprivUser + ": " + err.Error())
			return ui, err
		}
		ui.unpriv_uid, err = strconv.Atoi(nobody_user.Uid)
		ui.unpriv_gid, err = strconv.Atoi(nobody_user.Gid)
		if err != nil {
			log.Println("Running as root but could not lookup UID for user " + unprivUser + ": " + err.Error())
			return ui, err
		}
	}

	return ui, nil
}
func DropPrivs(ui userInfo) error {

	// If we're already unprivileged, all good
	if !ui.need_drop {
		return nil
	}

	// Drop supplementary groups
	if ui.root_supp_group {
		err := syscall.Setgroups([]int{})
		if err != nil {
			log.Println("Could not unset supplementary groups: " + err.Error())
			return err
		}
	}

	// Setgid()
	if ui.root_prim_group || ui.is_setgid {
		var target_gid int
		if ui.root_prim_group {
			target_gid = ui.unpriv_gid
		} else {
			target_gid = ui.gid
		}
		err := syscall.Setgid(target_gid)
		if err != nil {
			log.Println("Could not setgid to " + strconv.Itoa(target_gid) + ": " + err.Error())
			return err
		}
	}

	// Setuid()
	if ui.root_user || ui.is_setuid {
		var target_uid int
		if ui.root_user {
			target_uid = ui.unpriv_uid
		} else {
			target_uid = ui.uid
		}
		err := syscall.Setuid(target_uid)
		if err != nil {
			log.Println("Could not setuid to " + strconv.Itoa(target_uid) + ": " + err.Error())
			return err
		}
	}

	return nil
}
