diff --git a/lib/secretsscanner/authorizedkeys/users_list_darwin.go b/lib/secretsscanner/authorizedkeys/users_list_darwin.go index 9155cf4392e68..7eeb334bfbfcb 100644 --- a/lib/secretsscanner/authorizedkeys/users_list_darwin.go +++ b/lib/secretsscanner/authorizedkeys/users_list_darwin.go @@ -19,29 +19,54 @@ package authorizedkeys /* -#cgo CFLAGS: -D_POSIX_PTHREAD_SEMANTICS #include */ import "C" import ( "os/user" + "runtime" + "strconv" + + "github.com/gravitational/trace" ) -// getHostUsers returns a list of all users on the host -// from local /etc/passwd file, LDAP, or other user databases. -func getHostUsers() (results []user.User, _ error) { +// getHostUsers returns the list of all users on the host from the user +// directory (depending on system configuration this can be /etc/passwd, +// LDAP...). +func getHostUsers() ([]user.User, error) { + // on darwin the setpwent/getpwent/endpwent functions use thread-local + // storage so there's no need for a global lock but we must call the whole + // sequence from the same thread + runtime.LockOSThread() + defer runtime.UnlockOSThread() + C.setpwent() - var result *C.struct_passwd + defer C.endpwent() + + var results []user.User for { - result = C.getpwent() /* on darwin, getpwent() is reentrant */ - if result == nil { - break + result, err := C.getpwent() + // cgo error convention, check the return value before errno + if result != nil { + results = append(results, passwdC2Go(result)) + continue + } + if err != nil { + return nil, trace.Wrap(err) } - results = append(results, passwdC2Go(result)) + return results, nil } +} - C.endpwent() - - return results, nil +// passwdC2Go converts `passwd` struct from C to golang native struct +func passwdC2Go(passwdC *C.struct_passwd) user.User { + name := C.GoString(passwdC.pw_name) + return user.User{ + Name: name, + Username: name, + Uid: strconv.FormatUint(uint64(passwdC.pw_uid), 10), + Gid: strconv.FormatUint(uint64(passwdC.pw_gid), 10), + HomeDir: C.GoString(passwdC.pw_dir), + } } diff --git a/lib/secretsscanner/authorizedkeys/users_list.go b/lib/secretsscanner/authorizedkeys/users_list_linux.go similarity index 57% rename from lib/secretsscanner/authorizedkeys/users_list.go rename to lib/secretsscanner/authorizedkeys/users_list_linux.go index 70f3bc2bbdff1..9e750d2123877 100644 --- a/lib/secretsscanner/authorizedkeys/users_list.go +++ b/lib/secretsscanner/authorizedkeys/users_list_linux.go @@ -1,5 +1,3 @@ -//go:build !windows - /* * Teleport * Copyright (C) 2024 Gravitational, Inc. @@ -21,7 +19,7 @@ package authorizedkeys /* -#cgo CFLAGS: -D_POSIX_PTHREAD_SEMANTICS +#define _XOPEN_SOURCE 500 #include */ import "C" @@ -29,13 +27,46 @@ import "C" import ( "os/user" "strconv" + "sync" + + "github.com/gravitational/trace" ) +// pwentLock should be acquired when using MT-Unsafe race:pwent functions (i.e. +// setpwent/getpwent/endpwent). +var pwentLock sync.Mutex + +// getHostUsers returns the list of all users on the host from the user +// directory (depending on system configuration this can be /etc/passwd, +// LDAP...). +func getHostUsers() ([]user.User, error) { + pwentLock.Lock() + defer pwentLock.Unlock() + + C.setpwent() + defer C.endpwent() + + var results []user.User + for { + result, err := C.getpwent() + // cgo error convention, check the return value before errno + if result != nil { + results = append(results, passwdC2Go(result)) + continue + } + if err != nil { + return nil, trace.Wrap(err) + } + return results, nil + } +} + // passwdC2Go converts `passwd` struct from C to golang native struct func passwdC2Go(passwdC *C.struct_passwd) user.User { + name := C.GoString(passwdC.pw_name) return user.User{ - Name: C.GoString(passwdC.pw_name), - Username: C.GoString(passwdC.pw_name), + Name: name, + Username: name, Uid: strconv.FormatUint(uint64(passwdC.pw_uid), 10), Gid: strconv.FormatUint(uint64(passwdC.pw_gid), 10), HomeDir: C.GoString(passwdC.pw_dir), diff --git a/lib/secretsscanner/authorizedkeys/users_list_other.go b/lib/secretsscanner/authorizedkeys/users_list_other.go new file mode 100644 index 0000000000000..5355306b85027 --- /dev/null +++ b/lib/secretsscanner/authorizedkeys/users_list_other.go @@ -0,0 +1,38 @@ +// Teleport +// Copyright (C) 2025 Gravitational, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +//go:build !darwin && !linux + +package authorizedkeys + +import ( + "os/user" + + "github.com/gravitational/trace" +) + +var alwaysFalse bool + +// getHostUsers returns ErrUnsupportedPlatform because this platform is not +// supported. On supported platforms, it returns the list of all users on the +// host from the user directory. +func getHostUsers() ([]user.User, error) { + if alwaysFalse { + // thwart the well-meaning intentions of staticcheck + return nil, nil + } + return nil, trace.Wrap(ErrUnsupportedPlatform) +} diff --git a/lib/secretsscanner/authorizedkeys/users_list_unix.go b/lib/secretsscanner/authorizedkeys/users_list_unix.go deleted file mode 100644 index b8c2dc45ffa9c..0000000000000 --- a/lib/secretsscanner/authorizedkeys/users_list_unix.go +++ /dev/null @@ -1,67 +0,0 @@ -//go:build !darwin - -/* - * Teleport - * Copyright (C) 2024 Gravitational, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package authorizedkeys - -/* -#cgo CFLAGS: -D_POSIX_PTHREAD_SEMANTICS -D__USE_MISC -#include -#include -#include -#include -*/ -import "C" - -import ( - "os/user" - - "github.com/gravitational/trace" -) - -// getHostUsers returns a list of all users on the host -// from local /etc/passwd file, LDAP, or other user databases. -func getHostUsers() (results []user.User, _ error) { - - bufSize := C.sysconf(C._SC_GETPW_R_SIZE_MAX) - if bufSize == -1 { - bufSize = 16384 - } - if bufSize <= 0 || bufSize > 1<<20 { - return nil, trace.BadParameter("unreasonable _SC_GETPW_R_SIZE_MAX of %d", bufSize) - } - buf := C.malloc(C.size_t(bufSize)) - defer C.free(buf) - - C.setpwent() - - var pwdBuf C.struct_passwd - for { - var result *C.struct_passwd - rv := C.getpwent_r(&pwdBuf, (*C.char)(buf), C.size_t(bufSize), &result) - if rv != 0 || result == nil { - break - } - results = append(results, passwdC2Go(&pwdBuf)) - } - - C.endpwent() - - return results, nil -}