Skip to content

Commit

Permalink
Move "pkg/user" into libcontainer and add support for GetUserGroupSup…
Browse files Browse the repository at this point in the history
…plementary to return "Home" too

Docker-DCO-1.1-Signed-off-by: Andrew Page <admwiggin@gmail.com> (github: tianon)
  • Loading branch information
tianon committed Jul 29, 2014
1 parent 7dc9dc3 commit e31771f
Show file tree
Hide file tree
Showing 4 changed files with 363 additions and 3 deletions.
13 changes: 10 additions & 3 deletions namespaces/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"strings"
"syscall"

"github.com/docker/docker/pkg/user"
"github.com/docker/libcontainer"
"github.com/docker/libcontainer/apparmor"
"github.com/docker/libcontainer/console"
Expand All @@ -21,6 +20,7 @@ import (
"github.com/docker/libcontainer/security/restrict"
"github.com/docker/libcontainer/syncpipe"
"github.com/docker/libcontainer/system"
"github.com/docker/libcontainer/user"
"github.com/docker/libcontainer/utils"
)

Expand Down Expand Up @@ -119,7 +119,7 @@ func Init(container *libcontainer.Config, uncleanRootfs, consolePath string, syn
return fmt.Errorf("restore parent death signal %s", err)
}

return system.Execv(args[0], args[0:], container.Env)
return system.Execv(args[0], args[0:], os.Environ())
}

// RestoreParentDeathSignal sets the parent death signal to old.
Expand Down Expand Up @@ -152,7 +152,7 @@ func RestoreParentDeathSignal(old int) error {

// SetupUser changes the groups, gid, and uid for the user inside the container
func SetupUser(u string) error {
uid, gid, suppGids, err := user.GetUserGroupSupplementary(u, syscall.Getuid(), syscall.Getgid())
uid, gid, suppGids, home, err := user.GetUserGroupSupplementaryHome(u, syscall.Getuid(), syscall.Getgid(), "/")
if err != nil {
return fmt.Errorf("get supplementary groups %s", err)
}
Expand All @@ -169,6 +169,13 @@ func SetupUser(u string) error {
return fmt.Errorf("setuid %s", err)
}

// if we didn't get HOME already, set it based on the user's HOME
if envHome := os.Getenv("HOME"); envHome == "" {
if err := os.Setenv("HOME", home); err != nil {
return fmt.Errorf("set HOME %s", err)
}
}

return nil
}

Expand Down
1 change: 1 addition & 0 deletions user/MAINTAINERS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Tianon Gravi <admwiggin@gmail.com> (@tianon)
258 changes: 258 additions & 0 deletions user/user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
package user

import (
"bufio"
"fmt"
"io"
"os"
"strconv"
"strings"
)

const (
minId = 0
maxId = 1<<31 - 1 //for 32-bit systems compatibility
)

var (
ErrRange = fmt.Errorf("Uids and gids must be in range %d-%d", minId, maxId)
)

type User struct {
Name string
Pass string
Uid int
Gid int
Gecos string
Home string
Shell string
}

type Group struct {
Name string
Pass string
Gid int
List []string
}

func parseLine(line string, v ...interface{}) {
if line == "" {
return
}

parts := strings.Split(line, ":")
for i, p := range parts {
if len(v) <= i {
// if we have more "parts" than we have places to put them, bail for great "tolerance" of naughty configuration files
break
}

switch e := v[i].(type) {
case *string:
// "root", "adm", "/bin/bash"
*e = p
case *int:
// "0", "4", "1000"
// ignore string to int conversion errors, for great "tolerance" of naughty configuration files
*e, _ = strconv.Atoi(p)
case *[]string:
// "", "root", "root,adm,daemon"
if p != "" {
*e = strings.Split(p, ",")
} else {
*e = []string{}
}
default:
// panic, because this is a programming/logic error, not a runtime one
panic("parseLine expects only pointers! argument " + strconv.Itoa(i) + " is not a pointer!")
}
}
}

func ParsePasswd() ([]*User, error) {
return ParsePasswdFilter(nil)
}

func ParsePasswdFilter(filter func(*User) bool) ([]*User, error) {
f, err := os.Open("/etc/passwd")
if err != nil {
return nil, err
}
defer f.Close()
return parsePasswdFile(f, filter)
}

func parsePasswdFile(r io.Reader, filter func(*User) bool) ([]*User, error) {
var (
s = bufio.NewScanner(r)
out = []*User{}
)

for s.Scan() {
if err := s.Err(); err != nil {
return nil, err
}

text := strings.TrimSpace(s.Text())
if text == "" {
continue
}

// see: man 5 passwd
// name:password:UID:GID:GECOS:directory:shell
// Name:Pass:Uid:Gid:Gecos:Home:Shell
// root:x:0:0:root:/root:/bin/bash
// adm:x:3:4:adm:/var/adm:/bin/false
p := &User{}
parseLine(
text,
&p.Name, &p.Pass, &p.Uid, &p.Gid, &p.Gecos, &p.Home, &p.Shell,
)

if filter == nil || filter(p) {
out = append(out, p)
}
}

return out, nil
}

func ParseGroup() ([]*Group, error) {
return ParseGroupFilter(nil)
}

func ParseGroupFilter(filter func(*Group) bool) ([]*Group, error) {
f, err := os.Open("/etc/group")
if err != nil {
return nil, err
}
defer f.Close()
return parseGroupFile(f, filter)
}

func parseGroupFile(r io.Reader, filter func(*Group) bool) ([]*Group, error) {
var (
s = bufio.NewScanner(r)
out = []*Group{}
)

for s.Scan() {
if err := s.Err(); err != nil {
return nil, err
}

text := s.Text()
if text == "" {
continue
}

// see: man 5 group
// group_name:password:GID:user_list
// Name:Pass:Gid:List
// root:x:0:root
// adm:x:4:root,adm,daemon
p := &Group{}
parseLine(
text,
&p.Name, &p.Pass, &p.Gid, &p.List,
)

if filter == nil || filter(p) {
out = append(out, p)
}
}

return out, nil
}

// Given a string like "user", "1000", "user:group", "1000:1000", returns the uid, gid, list of supplementary group IDs, and home directory, if available and/or applicable.
func GetUserGroupSupplementaryHome(userSpec string, defaultUid, defaultGid int, defaultHome string) (int, int, []int, string, error) {
var (
uid = defaultUid
gid = defaultGid
suppGids = []int{}
home = defaultHome

userArg, groupArg string
)

// allow for userArg to have either "user" syntax, or optionally "user:group" syntax
parseLine(userSpec, &userArg, &groupArg)

users, err := ParsePasswdFilter(func(u *User) bool {
if userArg == "" {
return u.Uid == uid
}
return u.Name == userArg || strconv.Itoa(u.Uid) == userArg
})
if err != nil && !os.IsNotExist(err) {
if userArg == "" {
userArg = strconv.Itoa(uid)
}
return 0, 0, nil, "", fmt.Errorf("Unable to find user %v: %v", userArg, err)
}

haveUser := users != nil && len(users) > 0
if haveUser {
// if we found any user entries that matched our filter, let's take the first one as "correct"
uid = users[0].Uid
gid = users[0].Gid
home = users[0].Home
} else if userArg != "" {
// we asked for a user but didn't find them... let's check to see if we wanted a numeric user
uid, err = strconv.Atoi(userArg)
if err != nil {
// not numeric - we have to bail
return 0, 0, nil, "", fmt.Errorf("Unable to find user %v", userArg)
}
if uid < minId || uid > maxId {
return 0, 0, nil, "", ErrRange
}

// if userArg couldn't be found in /etc/passwd but is numeric, just roll with it - this is legit
}

if groupArg != "" || (haveUser && users[0].Name != "") {
groups, err := ParseGroupFilter(func(g *Group) bool {
if groupArg != "" {
return g.Name == groupArg || strconv.Itoa(g.Gid) == groupArg
}
for _, u := range g.List {
if u == users[0].Name {
return true
}
}
return false
})
if err != nil && !os.IsNotExist(err) {
return 0, 0, nil, "", fmt.Errorf("Unable to find groups for user %v: %v", users[0].Name, err)
}

haveGroup := groups != nil && len(groups) > 0
if groupArg != "" {
if haveGroup {
// if we found any group entries that matched our filter, let's take the first one as "correct"
gid = groups[0].Gid
} else {
// we asked for a group but didn't find id... let's check to see if we wanted a numeric group
gid, err = strconv.Atoi(groupArg)
if err != nil {
// not numeric - we have to bail
return 0, 0, nil, "", fmt.Errorf("Unable to find group %v", groupArg)
}
if gid < minId || gid > maxId {
return 0, 0, nil, "", ErrRange
}

// if groupArg couldn't be found in /etc/group but is numeric, just roll with it - this is legit
}
} else if haveGroup {
suppGids = make([]int, len(groups))
for i, group := range groups {
suppGids[i] = group.Gid
}
}
}

return uid, gid, suppGids, home, nil
}

0 comments on commit e31771f

Please sign in to comment.