package utils
import (
"fmt"
"os"
"os/exec"
"os/user"
"strconv"
"syscall"
"golang.org/x/sys/unix"
)
type UnixUser struct {
Username string
UID int
GID int
Groups []int
}
func (u *UnixUser) String() string {
return fmt.Sprintf("username=%s, uid=%d, gid=%d, groups=%v", u.Username, u.UID, u.GID, u.Groups)
}
func (u *UnixUser) ToCredential() *syscall.Credential {
groups := make([]uint32, len(u.Groups))
for i, gid := range u.Groups {
groups[i] = uint32(gid)
}
return &syscall.Credential{
Uid: uint32(u.UID),
Gid: uint32(u.GID),
Groups: groups,
}
}
func toUnixUser(user *user.User) (*UnixUser, error) {
uid, err := strconv.Atoi(user.Uid)
if err != nil {
return nil, fmt.Errorf("invalid uid %s for user %s: %v", user.Uid, user.Username, err)
}
gid, err := strconv.Atoi(user.Gid)
if err != nil {
return nil, fmt.Errorf("invalid gid %s for user %s: %v", user.Gid, user.Username, err)
}
strGroups, err := user.GroupIds()
if err != nil {
return nil, fmt.Errorf("cannot get groups for %s: %v", user.Username, err)
}
groups := make([]int, len(strGroups))
for i, name := range strGroups {
groups[i], err = strconv.Atoi(name)
if err != nil {
return nil, fmt.Errorf("invalid secondary gid %s for user %s: %v", name, user.Username, err)
}
}
return &UnixUser{
Username: user.Username,
UID: uid,
GID: gid,
Groups: groups,
}, nil
}
func WriteErrorForUnwritableNode() error {
if os.Getuid() == 0 {
return unix.EPERM
}
return unix.EACCES
}
func LookupUser(name string) (*UnixUser, error) {
generic, err := user.Lookup(name)
if err != nil {
return nil, fmt.Errorf("cannot find user %s: %v", name, err)
}
return toUnixUser(generic)
}
func LookupUID(uid int) (*UnixUser, error) {
generic, err := user.LookupId(fmt.Sprintf("%d", uid))
if err != nil {
return nil, fmt.Errorf("cannot find user %d: %v", uid, err)
}
return toUnixUser(generic)
}
func LookupUserOtherThan(username ...string) (*UnixUser, error) {
var other *UnixUser
for i := 1; i < 100; i++ {
var err error
other, err = LookupUID(i)
if err != nil {
continue
}
for _, name := range username {
if other.Username == name {
continue
}
}
break
}
if other == nil {
return nil, fmt.Errorf("cannot find an unprivileged user other than %v", username)
}
return other, nil
}
func SetCredential(cmd *exec.Cmd, user *UnixUser) {
if user == nil || user.UID == os.Getuid() {
return
}
if cmd.SysProcAttr == nil {
cmd.SysProcAttr = &syscall.SysProcAttr{}
}
if cmd.SysProcAttr.Credential != nil {
panic("SetCredential invoked on a cmd object that already includes user credentials")
}
cmd.SysProcAttr.Credential = user.ToCredential()
cmd.SysProcAttr.Credential.NoSetGroups = true
}