Update
This commit is contained in:
1 parent
3530afb854
commit
6903fcff27
2146 files changed
+112256
-190703
No files matched your search
+77
-8
@@ -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{
|
||||
|
||||
Reference in new issue
Block a user