diff --git a/helper/users/lookup.go b/helper/users/lookup.go index a8e846c4d..bc0a7e70e 100644 --- a/helper/users/lookup.go +++ b/helper/users/lookup.go @@ -24,6 +24,29 @@ func Lookup(username string) (*user.User, error) { return globalCache.GetUser(username) } +// LookupUnix returns the UID, GID, and home directory for username or returns +// an error. ID values are int to work well with Go library functions. +// +// Will always fail on Windows and Plan 9. +func LookupUnix(username string) (int, int, string, error) { + u, err := Lookup(username) + if err != nil { + return 0, 0, "", fmt.Errorf("error looking up user %q: %w", username, err) + } + + uid, err := strconv.Atoi(u.Uid) + if err != nil { + return 0, 0, "", fmt.Errorf("error parsing uid: %w", err) + } + + gid, err := strconv.Atoi(u.Gid) + if err != nil { + return 0, 0, "", fmt.Errorf("error parsing gid: %w", err) + } + + return uid, gid, u.HomeDir, nil +} + // lock is used to serialize all user lookup at the process level, because // some NSS implementations are not concurrency safe var lock sync.Mutex @@ -43,23 +66,6 @@ func Current() (*user.User, error) { return user.Current() } -// UIDforUser returns the UID for the specified username or returns an error. -// -// Will always fail on Windows and Plan 9. -func UIDforUser(username string) (int, error) { - u, err := Lookup(username) - if err != nil { - return 0, err - } - - uid, err := strconv.Atoi(u.Uid) - if err != nil { - return 0, fmt.Errorf("error parsing uid: %w", err) - } - - return uid, nil -} - // WriteFileFor is like os.WriteFile except if possible it chowns the file to // the specified user (possibly from Task.User) and sets the permissions to // 0o600. @@ -98,7 +104,7 @@ func WriteFileFor(path string, contents []byte, username string) error { } func writeFileFor(path string, contents []byte, username string) error { - uid, err := UIDforUser(username) + uid, _, _, err := LookupUnix(username) if err != nil { return err } @@ -154,7 +160,7 @@ func SocketFileFor(logger hclog.Logger, path, username string) (net.Listener, er } func setSocketOwner(path, username string) error { - uid, err := UIDforUser(username) + uid, _, _, err := LookupUnix(username) if err != nil { return err } diff --git a/helper/users/lookup_linux_test.go b/helper/users/lookup_linux_test.go index 45d12081a..ecb42b7fa 100644 --- a/helper/users/lookup_linux_test.go +++ b/helper/users/lookup_linux_test.go @@ -114,3 +114,19 @@ func TestSocketFileFor_Linux(t *testing.T) { must.Eq(t, 0o666, int(stat.Mode().Perm())) } } + +func TestLookupUnix_root(t *testing.T) { + uid, gid, home, err := LookupUnix("root") + must.NoError(t, err) + must.Zero(t, uid) // linux + must.Zero(t, gid) // linux + must.Eq(t, "/root", home) // ubuntu specific +} + +func TestLookupUnix_nobody(t *testing.T) { + uid, gid, home, err := LookupUnix("nobody") + must.NoError(t, err) + must.Eq(t, 65534, uid) // systemd specific + must.Eq(t, 65534, gid) // systemd specific + must.Eq(t, "/nonexistent", home) // ubuntu specific +}