123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295 |
- // +build !windows
- package idtools // import "github.com/docker/docker/pkg/idtools"
- import (
- "bytes"
- "fmt"
- "io"
- "os"
- "path/filepath"
- "strconv"
- "sync"
- "syscall"
- "github.com/docker/docker/pkg/system"
- "github.com/opencontainers/runc/libcontainer/user"
- "github.com/pkg/errors"
- )
- var (
- entOnce sync.Once
- getentCmd string
- )
- func mkdirAs(path string, mode os.FileMode, owner Identity, mkAll, chownExisting bool) error {
- // make an array containing the original path asked for, plus (for mkAll == true)
- // all path components leading up to the complete path that don't exist before we MkdirAll
- // so that we can chown all of them properly at the end. If chownExisting is false, we won't
- // chown the full directory path if it exists
- var paths []string
- stat, err := system.Stat(path)
- if err == nil {
- if !stat.IsDir() {
- return &os.PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR}
- }
- if !chownExisting {
- return nil
- }
- // short-circuit--we were called with an existing directory and chown was requested
- return setPermissions(path, mode, owner.UID, owner.GID, stat)
- }
- if os.IsNotExist(err) {
- paths = []string{path}
- }
- if mkAll {
- // walk back to "/" looking for directories which do not exist
- // and add them to the paths array for chown after creation
- dirPath := path
- for {
- dirPath = filepath.Dir(dirPath)
- if dirPath == "/" {
- break
- }
- if _, err := os.Stat(dirPath); err != nil && os.IsNotExist(err) {
- paths = append(paths, dirPath)
- }
- }
- if err := system.MkdirAll(path, mode); err != nil {
- return err
- }
- } else {
- if err := os.Mkdir(path, mode); err != nil && !os.IsExist(err) {
- return err
- }
- }
- // even if it existed, we will chown the requested path + any subpaths that
- // didn't exist when we called MkdirAll
- for _, pathComponent := range paths {
- if err := setPermissions(pathComponent, mode, owner.UID, owner.GID, nil); err != nil {
- return err
- }
- }
- return nil
- }
- // CanAccess takes a valid (existing) directory and a uid, gid pair and determines
- // if that uid, gid pair has access (execute bit) to the directory
- func CanAccess(path string, pair Identity) bool {
- statInfo, err := system.Stat(path)
- if err != nil {
- return false
- }
- fileMode := os.FileMode(statInfo.Mode())
- permBits := fileMode.Perm()
- return accessible(statInfo.UID() == uint32(pair.UID),
- statInfo.GID() == uint32(pair.GID), permBits)
- }
- func accessible(isOwner, isGroup bool, perms os.FileMode) bool {
- if isOwner && (perms&0100 == 0100) {
- return true
- }
- if isGroup && (perms&0010 == 0010) {
- return true
- }
- if perms&0001 == 0001 {
- return true
- }
- return false
- }
- // LookupUser uses traditional local system files lookup (from libcontainer/user) on a username,
- // followed by a call to `getent` for supporting host configured non-files passwd and group dbs
- func LookupUser(name string) (user.User, error) {
- // first try a local system files lookup using existing capabilities
- usr, err := user.LookupUser(name)
- if err == nil {
- return usr, nil
- }
- // local files lookup failed; attempt to call `getent` to query configured passwd dbs
- usr, err = getentUser(name)
- if err != nil {
- return user.User{}, err
- }
- return usr, nil
- }
- // LookupUID uses traditional local system files lookup (from libcontainer/user) on a uid,
- // followed by a call to `getent` for supporting host configured non-files passwd and group dbs
- func LookupUID(uid int) (user.User, error) {
- // first try a local system files lookup using existing capabilities
- usr, err := user.LookupUid(uid)
- if err == nil {
- return usr, nil
- }
- // local files lookup failed; attempt to call `getent` to query configured passwd dbs
- return getentUser(strconv.Itoa(uid))
- }
- func getentUser(name string) (user.User, error) {
- reader, err := callGetent("passwd", name)
- if err != nil {
- return user.User{}, err
- }
- users, err := user.ParsePasswd(reader)
- if err != nil {
- return user.User{}, err
- }
- if len(users) == 0 {
- return user.User{}, fmt.Errorf("getent failed to find passwd entry for %q", name)
- }
- return users[0], nil
- }
- // LookupGroup uses traditional local system files lookup (from libcontainer/user) on a group name,
- // followed by a call to `getent` for supporting host configured non-files passwd and group dbs
- func LookupGroup(name string) (user.Group, error) {
- // first try a local system files lookup using existing capabilities
- group, err := user.LookupGroup(name)
- if err == nil {
- return group, nil
- }
- // local files lookup failed; attempt to call `getent` to query configured group dbs
- return getentGroup(name)
- }
- // LookupGID uses traditional local system files lookup (from libcontainer/user) on a group ID,
- // followed by a call to `getent` for supporting host configured non-files passwd and group dbs
- func LookupGID(gid int) (user.Group, error) {
- // first try a local system files lookup using existing capabilities
- group, err := user.LookupGid(gid)
- if err == nil {
- return group, nil
- }
- // local files lookup failed; attempt to call `getent` to query configured group dbs
- return getentGroup(strconv.Itoa(gid))
- }
- func getentGroup(name string) (user.Group, error) {
- reader, err := callGetent("group", name)
- if err != nil {
- return user.Group{}, err
- }
- groups, err := user.ParseGroup(reader)
- if err != nil {
- return user.Group{}, err
- }
- if len(groups) == 0 {
- return user.Group{}, fmt.Errorf("getent failed to find groups entry for %q", name)
- }
- return groups[0], nil
- }
- func callGetent(database, key string) (io.Reader, error) {
- entOnce.Do(func() { getentCmd, _ = resolveBinary("getent") })
- // if no `getent` command on host, can't do anything else
- if getentCmd == "" {
- return nil, fmt.Errorf("unable to find getent command")
- }
- out, err := execCmd(getentCmd, database, key)
- if err != nil {
- exitCode, errC := system.GetExitCode(err)
- if errC != nil {
- return nil, err
- }
- switch exitCode {
- case 1:
- return nil, fmt.Errorf("getent reported invalid parameters/database unknown")
- case 2:
- return nil, fmt.Errorf("getent unable to find entry %q in %s database", key, database)
- case 3:
- return nil, fmt.Errorf("getent database doesn't support enumeration")
- default:
- return nil, err
- }
- }
- return bytes.NewReader(out), nil
- }
- // setPermissions performs a chown/chmod only if the uid/gid don't match what's requested
- // Normally a Chown is a no-op if uid/gid match, but in some cases this can still cause an error, e.g. if the
- // dir is on an NFS share, so don't call chown unless we absolutely must.
- // Likewise for setting permissions.
- func setPermissions(p string, mode os.FileMode, uid, gid int, stat *system.StatT) error {
- if stat == nil {
- var err error
- stat, err = system.Stat(p)
- if err != nil {
- return err
- }
- }
- if os.FileMode(stat.Mode()).Perm() != mode.Perm() {
- if err := os.Chmod(p, mode.Perm()); err != nil {
- return err
- }
- }
- if stat.UID() == uint32(uid) && stat.GID() == uint32(gid) {
- return nil
- }
- return os.Chown(p, uid, gid)
- }
- // NewIdentityMapping takes a requested username and
- // using the data from /etc/sub{uid,gid} ranges, creates the
- // proper uid and gid remapping ranges for that user/group pair
- func NewIdentityMapping(name string) (*IdentityMapping, error) {
- usr, err := LookupUser(name)
- if err != nil {
- return nil, fmt.Errorf("Could not get user for username %s: %v", name, err)
- }
- subuidRanges, err := lookupSubUIDRanges(usr)
- if err != nil {
- return nil, err
- }
- subgidRanges, err := lookupSubGIDRanges(usr)
- if err != nil {
- return nil, err
- }
- return &IdentityMapping{
- uids: subuidRanges,
- gids: subgidRanges,
- }, nil
- }
- func lookupSubUIDRanges(usr user.User) ([]IDMap, error) {
- rangeList, err := parseSubuid(strconv.Itoa(usr.Uid))
- if err != nil {
- return nil, err
- }
- if len(rangeList) == 0 {
- rangeList, err = parseSubuid(usr.Name)
- if err != nil {
- return nil, err
- }
- }
- if len(rangeList) == 0 {
- return nil, errors.Errorf("no subuid ranges found for user %q", usr.Name)
- }
- return createIDMap(rangeList), nil
- }
- func lookupSubGIDRanges(usr user.User) ([]IDMap, error) {
- rangeList, err := parseSubgid(strconv.Itoa(usr.Uid))
- if err != nil {
- return nil, err
- }
- if len(rangeList) == 0 {
- rangeList, err = parseSubgid(usr.Name)
- if err != nil {
- return nil, err
- }
- }
- if len(rangeList) == 0 {
- return nil, errors.Errorf("no subgid ranges found for user %q", usr.Name)
- }
- return createIDMap(rangeList), nil
- }
|