This commit is contained in:
bluepython508 committed 2026-10-22 12:27:19 +01:00
1 parent 3530afb854
commit 6903fcff27
2146 files changed
+112256 -190703

No files matched your search

+77 -8
View File
@@ -1,4 +1,4 @@
// Copyright (c) Tailscale Inc & AUTHORS
// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
// Package osuser implements OS user lookup. It's a wrapper around os/user that
@@ -8,11 +8,14 @@ package osuser
import (
"context"
"errors"
"fmt"
"log"
"os/exec"
"os/user"
"reflect"
"runtime"
"strings"
"sync"
"time"
"unicode/utf8"
@@ -50,6 +53,12 @@ func LookupByUsername(username string) (*user.User, error) {
// lookupStd is either user.Lookup or user.LookupId.
type lookupStd func(string) (*user.User, error)
var execGetent = func(ctx context.Context, args ...string) ([]byte, error) {
return exec.CommandContext(ctx, "getent", args...).Output()
}
var getentDoubleDashSupported = sync.OnceValue(probeGetentDoubleDashSupport)
func lookup(usernameOrUID string, std lookupStd, wantShell bool) (*user.User, string, error) {
// Skip getent entirely on Non-Unix platforms that won't ever have it.
// (Using HasPrefix for "wasip1", anticipating that WASI support will
@@ -118,6 +127,12 @@ func checkGetentInput(usernameOrUID string) bool {
if len(usernameOrUID) > maxUid || len(usernameOrUID) == 0 {
return false
}
// Leading dashes aren't valid for usernames.
if strings.HasPrefix(usernameOrUID, "-") {
return false
}
for _, r := range usernameOrUID {
if r < ' ' || r == 0x7f || r == utf8.RuneError { // TODO(bradfitz): more?
return false
@@ -139,23 +154,77 @@ func userLookupGetent(usernameOrUID string, std lookupStd) (*user.User, string,
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
out, err := exec.CommandContext(ctx, "getent", "passwd", usernameOrUID).Output()
args := []string{"passwd"}
// Append "--" only if the local getent accepts it, to prevent a username or
// UID from being interpreted as an option without breaking getent variants
// that do not support end-of-options parsing here.
if getentDoubleDashSupported() {
args = append(args, "--")
}
args = append(args, usernameOrUID)
out, err := execGetent(ctx, args...)
if err != nil {
log.Printf("error calling getent for user %q: %v", usernameOrUID, err)
u, err := std(usernameOrUID)
return u, "", err
}
u, shell, err := parseGetentUser(out)
if err != nil {
log.Printf("getent for user %q returned invalid output %q: %v", usernameOrUID, out, err)
u, err := std(usernameOrUID)
return u, "", err
}
return u, shell, nil
}
func probeGetentDoubleDashSupport() bool {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
out, err := execGetent(ctx, "passwd", "root")
if err != nil {
return false
}
outDoubleDash, err := execGetent(ctx, "passwd", "--", "root")
if err != nil {
return false
}
u, shell, err := parseGetentUser(out)
if err != nil {
return false
}
uDoubleDash, shellDoubleDash, err := parseGetentUser(outDoubleDash)
if err != nil {
return false
}
// Short-circuit if the user is obviously incorrect
if uDoubleDash.Name != "root" || uDoubleDash.Uid != "0" || uDoubleDash.Gid != "0" {
return false
}
if !reflect.DeepEqual(u, uDoubleDash) || shell != shellDoubleDash {
return false
}
return true
}
func parseGetentUser(out []byte) (*user.User, string, error) {
// output is "alice:x:1001:1001:Alice Smith,,,:/home/alice:/bin/bash"
f := strings.SplitN(strings.TrimSpace(string(out)), ":", 10)
for len(f) < 7 {
f = append(f, "")
}
var mandatoryFields = []int{0, 2, 3, 5}
for _, v := range mandatoryFields {
if f[v] == "" {
log.Printf("getent for user %q returned invalid output: %q", usernameOrUID, out)
u, err := std(usernameOrUID)
return u, "", err
var mandatoryFields = map[int]string{0: "Username", 2: "Uid", 3: "Gid", 5: "HomeDir"}
for k, v := range mandatoryFields {
if f[k] == "" {
return nil, "", fmt.Errorf("missing mandatory field %q", v)
}
}
return &user.User{