| // +build !windows |
| |
| package idtools |
| |
| import ( |
| "bytes" |
| "fmt" |
| "io" |
| "os" |
| "path/filepath" |
| "strings" |
| "sync" |
| |
| "github.com/docker/docker/pkg/system" |
| "github.com/opencontainers/runc/libcontainer/user" |
| ) |
| |
| var ( |
| entOnce sync.Once |
| getentCmd string |
| ) |
| |
| func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, 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 |
| if _, err := os.Stat(path); err != nil && os.IsNotExist(err) { |
| paths = []string{path} |
| } else if err == nil && chownExisting { |
| if err := os.Chown(path, ownerUID, ownerGID); err != nil { |
| return err |
| } |
| // short-circuit--we were called with an existing directory and chown was requested |
| return nil |
| } else if err == nil { |
| // nothing to do; directory path fully exists already and chown was NOT requested |
| return nil |
| } |
| |
| 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 && !os.IsExist(err) { |
| 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 := os.Chown(pathComponent, ownerUID, ownerGID); 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, uid, gid int) bool { |
| statInfo, err := system.Stat(path) |
| if err != nil { |
| return false |
| } |
| fileMode := os.FileMode(statInfo.Mode()) |
| permBits := fileMode.Perm() |
| return accessible(statInfo.UID() == uint32(uid), |
| statInfo.GID() == uint32(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(username string) (user.User, error) { |
| // first try a local system files lookup using existing capabilities |
| usr, err := user.LookupUser(username) |
| if err == nil { |
| return usr, nil |
| } |
| // local files lookup failed; attempt to call `getent` to query configured passwd dbs |
| usr, err = getentUser(fmt.Sprintf("%s %s", "passwd", username)) |
| 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(fmt.Sprintf("%s %d", "passwd", uid)) |
| } |
| |
| func getentUser(args string) (user.User, error) { |
| reader, err := callGetent(args) |
| 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", strings.Split(args, " ")[1]) |
| } |
| 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(groupname string) (user.Group, error) { |
| // first try a local system files lookup using existing capabilities |
| group, err := user.LookupGroup(groupname) |
| if err == nil { |
| return group, nil |
| } |
| // local files lookup failed; attempt to call `getent` to query configured group dbs |
| return getentGroup(fmt.Sprintf("%s %s", "group", groupname)) |
| } |
| |
| // 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(fmt.Sprintf("%s %d", "group", gid)) |
| } |
| |
| func getentGroup(args string) (user.Group, error) { |
| reader, err := callGetent(args) |
| 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", strings.Split(args, " ")[1]) |
| } |
| return groups[0], nil |
| } |
| |
| func callGetent(args 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("") |
| } |
| out, err := execCmd(getentCmd, args) |
| 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: |
| terms := strings.Split(args, " ") |
| return nil, fmt.Errorf("getent unable to find entry %q in %s database", terms[1], terms[0]) |
| case 3: |
| return nil, fmt.Errorf("getent database doesn't support enumeration") |
| default: |
| return nil, err |
| } |
| |
| } |
| return bytes.NewReader(out), nil |
| } |