Compare commits

..

4 Commits

Author SHA1 Message Date
brent saner
e5f7296d2e
v1.13.3
ADDED:
* envs.GetEnvErr(), envs.GetEnvErrNoBlank(), envs.EnvErrNoVal
  This allows error-returned env vars for nonexistent/empty values.
2025-07-12 15:10:38 -04:00
brent saner
82f58d4fbf
v1.13.2
ADDED:
* paths.RealPathJoin() and paths.RealPathJoinSys()
2025-07-09 16:27:48 -04:00
brent saner
772324247a
v1.13.1
ADDED:
* ispriv, which returns some information useful for determining if
  running with extra permissions, in sudo, etc.
2025-06-10 11:32:07 -04:00
brent saner
7b0156775c
v1.13.0
ADDED:
* Convenience functions to determine if a process is running in an
  elevated/dropped privileges context
2025-04-21 02:29:24 -04:00
18 changed files with 1187 additions and 121 deletions

7
TODO
View File

@ -1,8 +1,9 @@
- refactor the elevation detection stuff. I'm not terribly happy with it.
- password generator utility/library - password generator utility/library
-- incorporate with r00t2.io/pwgen
-- incorporate with https://github.com/tredoe/osutil ? -- incorporate with https://github.com/tredoe/osutil ?
-- cli flag to dump flat hashes too -- cli flag to dump flat hashes too (https://github.com/hlandau/passlib and others soon in pwgen)
--- https://github.com/hlandau/passlib
-- incoprporated separately; https://git.r00t2.io/r00t2/PWGen (import r00t2.io/pwgen)
- auger needs to be build-constrained to linux. - auger needs to be build-constrained to linux.

View File

@ -1,27 +1,27 @@
package envs package envs
import ( import (
`bytes` "bytes"
`errors` "errors"
`fmt` "fmt"
`io/ioutil` "io/ioutil"
`os` "os"
`reflect` "reflect"
`strings` "strings"
`sync` "sync"
`r00t2.io/goutils/multierr` "r00t2.io/goutils/multierr"
`r00t2.io/goutils/structutils` "r00t2.io/goutils/structutils"
`r00t2.io/sysutils/errs` "r00t2.io/sysutils/errs"
`r00t2.io/sysutils/internal` "r00t2.io/sysutils/internal"
`r00t2.io/sysutils/paths` "r00t2.io/sysutils/paths"
) )
/* /*
DefEnv operates like Python's .get() method on dicts (maps); DefEnv operates like Python's .get() method on dicts (maps);
if the environment variable specified by key does not exist/is not specified, if the environment variable specified by key does not exist/is not specified,
then the value specified by fallback will be returned instead then the value specified by fallback will be returned instead
otherwise key's value is returned. otherwise key's value is returned.
*/ */
func DefEnv(key, fallback string) (value string) { func DefEnv(key, fallback string) (value string) {
@ -45,6 +45,54 @@ func DefEnvBlank(key, fallback string) (value string) {
return return
} }
// GetEnvErr returns the value of key if it exists. If it does not exist, err will be an EnvErrNoVal.
func GetEnvErr(key string) (value string, err error) {
var exists bool
if value, exists = os.LookupEnv(key); !exists {
err = &EnvErrNoVal{
VarName: key,
}
return
}
return
}
/*
GetEnvErrNoBlank behaves exactly like GetEnvErr with the
additional stipulation that the value must not be empty.
An error for a value that is non-empty but whitespace only (e.g. VARNM="\t")
can be returned if ignoreWhitespace == true.
(If it is, an EnvErrNoVal will also be returned.)
*/
func GetEnvErrNoBlank(key string, ignoreWhitespace bool) (value string, err error) {
var exists bool
var e *EnvErrNoVal = &EnvErrNoVal{
VarName: key,
WasRequiredNonEmpty: true,
IgnoreWhiteSpace: ignoreWhitespace,
}
if value, exists = os.LookupEnv(key); !exists {
err = e
return
} else {
e.WasFound = true
e.WasWhitespace = (strings.TrimSpace(value) == "") && (value != "")
if ignoreWhitespace && e.WasWhitespace {
err = e
return
}
}
return
}
// GetEnvMap returns a map of all environment variables. All values are strings. // GetEnvMap returns a map of all environment variables. All values are strings.
func GetEnvMap() (envVars map[string]string) { func GetEnvMap() (envVars map[string]string) {
@ -56,18 +104,18 @@ func GetEnvMap() (envVars map[string]string) {
} }
/* /*
GetEnvMapNative returns a map of all environment variables, but attempts to "nativize" them. GetEnvMapNative returns a map of all environment variables, but attempts to "nativize" them.
All values are interfaces. It is up to the caller to typeswitch them to proper types. All values are interfaces. It is up to the caller to typeswitch them to proper types.
Note that the PATH/Path environment variable (for *Nix and Windows, respectively) will be Note that the PATH/Path environment variable (for *Nix and Windows, respectively) will be
a []string (as per GetPathEnv). No other env vars, even if they contain os.PathListSeparator, a []string (as per GetPathEnv). No other env vars, even if they contain os.PathListSeparator,
will be transformed to a slice or the like. will be transformed to a slice or the like.
If an error occurs during parsing the path env var, it will be rendered as a string. If an error occurs during parsing the path env var, it will be rendered as a string.
All number types will attempt to be their 64-bit version (i.e. int64, uint64, float64, etc.). All number types will attempt to be their 64-bit version (i.e. int64, uint64, float64, etc.).
If a type cannot be determined for a value, its string form will be used If a type cannot be determined for a value, its string form will be used
(as it would be found in GetEnvMap). (as it would be found in GetEnvMap).
*/ */
func GetEnvMapNative() (envMap map[string]interface{}) { func GetEnvMapNative() (envMap map[string]interface{}) {
@ -79,24 +127,24 @@ func GetEnvMapNative() (envMap map[string]interface{}) {
} }
/* /*
GetFirst gets the first instance if populated/set occurrence of varNames. GetFirst gets the first instance if populated/set occurrence of varNames.
For example, if you have three potential env vars, FOO, FOOBAR, FOOBARBAZ, For example, if you have three potential env vars, FOO, FOOBAR, FOOBARBAZ,
and want to follow the logic flow of: and want to follow the logic flow of:
1.) Check if FOO is set. If not, 1.) Check if FOO is set. If not,
2.) Check if FOOBAR is set. If not, 2.) Check if FOOBAR is set. If not,
3.) Check if FOOBARBAZ is set. 3.) Check if FOOBARBAZ is set.
Then this would be specified as: Then this would be specified as:
GetFirst([]string{"FOO", "FOOBAR", "FOOBARBAZ"}) GetFirst([]string{"FOO", "FOOBAR", "FOOBARBAZ"})
If val is "" and ok is true, this means that one of the specified variable names IS If val is "" and ok is true, this means that one of the specified variable names IS
set but is set to an empty value. If ok is false, none of the specified variables set but is set to an empty value. If ok is false, none of the specified variables
are set. are set.
It is a thin wrapper around GetFirstWithRef. It is a thin wrapper around GetFirstWithRef.
*/ */
func GetFirst(varNames []string) (val string, ok bool) { func GetFirst(varNames []string) (val string, ok bool) {
@ -106,14 +154,14 @@ func GetFirst(varNames []string) (val string, ok bool) {
} }
/* /*
GetFirstWithRef behaves exactly like GetFirst, but with an additional returned value, idx, GetFirstWithRef behaves exactly like GetFirst, but with an additional returned value, idx,
which specifies the index in varNames in which a set variable was found. e.g. if: which specifies the index in varNames in which a set variable was found. e.g. if:
GetFirstWithRef([]string{"FOO", "FOOBAR", "FOOBAZ"}) GetFirstWithRef([]string{"FOO", "FOOBAR", "FOOBAZ"})
is called and FOO is not set but FOOBAR is, idx will be 1. is called and FOO is not set but FOOBAR is, idx will be 1.
If ok is false, idx will always be -1 and should be ignored. If ok is false, idx will always be -1 and should be ignored.
*/ */
func GetFirstWithRef(varNames []string) (val string, ok bool, idx int) { func GetFirstWithRef(varNames []string) (val string, ok bool, idx int) {
@ -148,8 +196,8 @@ func GetPathEnv() (pathList []string, err error) {
} }
/* /*
GetPidEnvMap will only work on *NIX-like systems with procfs. GetPidEnvMap will only work on *NIX-like systems with procfs.
It gets the environment variables of a given process' PID. It gets the environment variables of a given process' PID.
*/ */
func GetPidEnvMap(pid uint32) (envMap map[string]string, err error) { func GetPidEnvMap(pid uint32) (envMap map[string]string, err error) {
@ -187,10 +235,10 @@ func GetPidEnvMap(pid uint32) (envMap map[string]string, err error) {
} }
/* /*
GetPidEnvMapNative, like GetEnvMapNative, returns a map of all environment variables, but attempts to "nativize" them. GetPidEnvMapNative, like GetEnvMapNative, returns a map of all environment variables, but attempts to "nativize" them.
All values are interfaces. It is up to the caller to typeswitch them to proper types. All values are interfaces. It is up to the caller to typeswitch them to proper types.
See the documentation for GetEnvMapNative for details. See the documentation for GetEnvMapNative for details.
*/ */
func GetPidEnvMapNative(pid uint32) (envMap map[string]interface{}, err error) { func GetPidEnvMapNative(pid uint32) (envMap map[string]interface{}, err error) {
@ -206,11 +254,11 @@ func GetPidEnvMapNative(pid uint32) (envMap map[string]interface{}, err error) {
} }
/* /*
HasEnv is much like os.LookupEnv, but only returns a boolean for HasEnv is much like os.LookupEnv, but only returns a boolean for
if the environment variable key exists or not. if the environment variable key exists or not.
This is useful anywhere you may need to set a boolean in a func call This is useful anywhere you may need to set a boolean in a func call
depending on the *presence* of an env var or not. depending on the *presence* of an env var or not.
*/ */
func HasEnv(key string) (envIsSet bool) { func HasEnv(key string) (envIsSet bool) {
@ -220,28 +268,28 @@ func HasEnv(key string) (envIsSet bool) {
} }
/* /*
Interpolate takes one of: Interpolate takes one of:
- a string (pointer only) - a string (pointer only)
- a struct (pointer only) - a struct (pointer only)
- a map (applied to both keys *and* values) - a map (applied to both keys *and* values)
- a slice - a slice
and performs variable substitution on strings from environment variables. and performs variable substitution on strings from environment variables.
It supports both UNIX/Linux/POSIX syntax formats (e.g. $VARNAME, ${VARNAME}) and, It supports both UNIX/Linux/POSIX syntax formats (e.g. $VARNAME, ${VARNAME}) and,
if on Windows, it *additionally* supports the EXPAND_SZ format (e.g. %VARNAME%). if on Windows, it *additionally* supports the EXPAND_SZ format (e.g. %VARNAME%).
For structs, the tag name used can be changed by setting the StructTagInterpolate For structs, the tag name used can be changed by setting the StructTagInterpolate
variable in this submodule; the default is `envsub`. variable in this submodule; the default is `envsub`.
If the tag value is "-", the field will be skipped. If the tag value is "-", the field will be skipped.
For map fields within structs etc., the default is to apply interpolation to both keys and values. For map fields within structs etc., the default is to apply interpolation to both keys and values.
All other tag value(s) are ignored. All other tag value(s) are ignored.
For maps and slices, Interpolate will recurse into values (e.g. [][]string will work as expected). For maps and slices, Interpolate will recurse into values (e.g. [][]string will work as expected).
If s is nil, no interpolation will be performed. No error will be returned. If s is nil, no interpolation will be performed. No error will be returned.
If s is not a valid/supported type, no interpolation will be performed. No error will be returned. If s is not a valid/supported type, no interpolation will be performed. No error will be returned.
*/ */
func Interpolate[T any](s T) (err error) { func Interpolate[T any](s T) (err error) {
@ -293,16 +341,16 @@ func Interpolate[T any](s T) (err error) {
} }
/* /*
InterpolateString takes (a pointer to) a struct or string and performs variable substitution on it InterpolateString takes (a pointer to) a struct or string and performs variable substitution on it
from environment variables. from environment variables.
It supports both UNIX/Linux/POSIX syntax formats (e.g. $VARNAME, ${VARNAME}) and, It supports both UNIX/Linux/POSIX syntax formats (e.g. $VARNAME, ${VARNAME}) and,
if on Windows, it *additionally* supports the EXPAND_SZ format (e.g. %VARNAME%). if on Windows, it *additionally* supports the EXPAND_SZ format (e.g. %VARNAME%).
If s is nil, nothing will be done and err will be ErrNilPtr. If s is nil, nothing will be done and err will be ErrNilPtr.
This is a standalone function that is much more performant than Interpolate This is a standalone function that is much more performant than Interpolate
at the cost of rigidity. at the cost of rigidity.
*/ */
func InterpolateString(s *string) (err error) { func InterpolateString(s *string) (err error) {

27
envs/funcs_enverrnoval.go Normal file
View File

@ -0,0 +1,27 @@
package envs
import (
"strings"
)
// Error conforms to a stdlib error interface.
func (e *EnvErrNoVal) Error() (errStr string) {
var sb *strings.Builder = new(strings.Builder)
sb.WriteString("the variable '")
sb.WriteString(e.VarName)
sb.WriteString("' was ")
if sb.WasFound {
sb.WriteString("found")
} else {
sb.WriteString("not found")
}
if e.WasRequiredNonEmpty && e.WasFound {
sb.WriteString(" but is empty and was required to be non-empty")
}
errStr = sb.String()
return
}

20
envs/types.go Normal file
View File

@ -0,0 +1,20 @@
package envs
type (
/*
EnvErrNoVal is an error containing the variable that does not exist
(and information surrounding the errored state).
*/
EnvErrNoVal struct {
// VarName is the variable name/key name originally specified in the function call.
VarName string `json:"var" toml:"VariableName" yaml:"Variable Name/Key" xml:"key,attr"`
// WasFound is only used for GetEnvErrNoBlank(). It is true if the variable was found/populated.
WasFound bool `json:"found" toml:"Found" yaml:"Found" xml:"found,attr"`
// WasRequiredNonEmpty indicates that this error was returned in a context where a variable was required to be non-empty (e.g. via GetEnvErrNoBlank()) but was empty.
WasRequiredNonEmpty bool `json:"reqd_non_empty" toml:"RequiredNonEmpty" yaml:"Required Non-Empty" xml:"reqNonEmpty,attr"`
// IgnoreWhitespace is true if the value was found but its evaluation was done against a whitestripped version.
IgnoreWhitespace bool `json:"ignore_ws" toml:"IgnoreWhitespace" yaml:"Ignore Whitespace" xml:"ignoreWhitespace,attr"`
// WasWhitespace is true if the value was whitespace-only.
WasWhitespace bool `json:"was_ws" toml:"WasWhitespace" yaml:"Was Whitespace Only" xml:"wasWhitespace,attr"`
}
)

163
funcs_idstate.go Normal file
View File

@ -0,0 +1,163 @@
package sysutils
// Checked consolidates all the provided checked functions.
func (i *IDState) Checked() (checked bool) {
if i == nil {
return
}
checked = i.uidsChecked &&
i.gidsChecked &&
i.sudoChecked &&
i.ppidUidChecked &&
i.ppidGidChecked
return
}
/*
IsReal consolidates all the elevation/dropped-privs checks into a single method.
It will only return true if no sudo was detected and *all* UIDs/GIDs match.
*/
func (i *IDState) IsReal(real bool) {
if i == nil {
return
}
real = true
for _, b := range []bool{
i.IsSuid(),
i.IsSgid(),
i.IsSudoUser(),
i.IsSudoGroup(),
} {
if b {
real = false
return
}
}
return
}
/*
IsSudoGroup is true if any of the group sudo env vars are set,
or the parent process has a different group (and is not PID 1).
It will always return false if SudoChecked returns false oor PPIDGIDsChecked returns false.
*/
func (i *IDState) IsSudoGroup() (sudo bool) {
if i == nil || !i.sudoChecked || !i.ppidGidChecked {
return
}
sudo = i.SudoEnvGroup || !i.PPIDGidMatch
return
}
/*
IsSudoUser is true if any of the user sudo env vars are set,
or the parent process has a different owner (and is not PID 1).
It will always return false if SudoChecked returns false or PPIDUIDsChecked returns false.
*/
func (i *IDState) IsSudoUser() (sudo bool) {
if i == nil || !i.sudoChecked || !i.ppidUidChecked {
return
}
sudo = i.SudoEnvUser || !i.PPIDUidMatch
return
}
// IsSuid is true if the RUID does not match EUID or SUID. It will always return false if UIDsChecked returns false.
func (i *IDState) IsSuid() (suid bool) {
if i == nil || !i.uidsChecked {
return
}
suid = i.RUID != i.EUID || i.RUID != i.SUID
return
}
// IsSgid is true if the RGID does not match EGID or SGID. It will always return false if GIDsChecked returns false.
func (i *IDState) IsSgid() (sgid bool) {
if i == nil || !i.gidsChecked {
return
}
sgid = i.RGID != i.EGID || i.RGID != i.SGID
return
}
// GIDsChecked is true if the GIDs presented can be trusted.
func (i *IDState) GIDsChecked() (checked bool) {
if i == nil {
return
}
checked = i.gidsChecked
return
}
// PPIDGIDsChecked is true if PPIDGidMatch can be trusted.
func (i *IDState) PPIDGIDsChecked() (checked bool) {
if i == nil {
return
}
checked = i.ppidGidChecked
return
}
// PPIDUIDsChecked is true if PPIDUidMatch can be trusted.
func (i *IDState) PPIDUIDsChecked() (checked bool) {
if i == nil {
return
}
checked = i.ppidUidChecked
return
}
// SudoChecked is true if SudoEnvVars can be trusted
func (i *IDState) SudoChecked() (checked bool) {
if i == nil {
return
}
checked = i.sudoChecked
return
}
// UIDsChecked is true if the UIDs presented can be trusted.
func (i *IDState) UIDsChecked() (checked bool) {
if i == nil {
return
}
checked = i.uidsChecked
return
}

50
funcs_linux.go Normal file
View File

@ -0,0 +1,50 @@
package sysutils
import (
`fmt`
`os`
`golang.org/x/sys/unix`
`r00t2.io/sysutils/envs`
)
// GetIDState returns current ID/elevation information. An IDState should *not* be explicitly created/defined.
func GetIDState() (ids IDState) {
var err error
ids.RUID, ids.EUID, ids.SUID = unix.Getresuid()
ids.uidsChecked = true
ids.RGID, ids.EGID, ids.SGID = unix.Getresgid()
ids.gidsChecked = true
ids.SudoEnvCmd = envs.HasEnv("SUDO_COMMAND")
ids.SudoEnvHome = envs.HasEnv("SUDO_HOME")
ids.SudoEnvGroup = envs.HasEnv("SUDO_GID")
ids.SudoEnvUser = envs.HasEnv("SUDO_UID") || envs.HasEnv("SUDO_USER")
if ids.SudoEnvCmd || ids.SudoEnvHome || ids.SudoEnvGroup || ids.SudoEnvUser {
ids.SudoEnvVars = true
}
ids.sudoChecked = true
// PID 1 will *always* be root, so that can return a false positive for sudo.
if os.Getppid() != 1 {
ids.stat = new(unix.Stat_t)
if err = unix.Stat(
fmt.Sprintf("/proc/%d/stat", os.Getppid()),
ids.stat,
); err != nil {
err = nil
} else {
ids.PPIDUidMatch = ids.RUID == int(ids.stat.Uid)
ids.ppidUidChecked = true
ids.PPIDGidMatch = ids.RGID == int(ids.stat.Gid)
ids.ppidGidChecked = true
}
} else {
ids.ppidUidChecked = true
ids.ppidGidChecked = true
}
return
}

17
go.mod
View File

@ -6,8 +6,19 @@ require (
github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew v1.1.1
github.com/djherbis/times v1.6.0 github.com/djherbis/times v1.6.0
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
golang.org/x/sync v0.9.0 github.com/shirou/gopsutil/v4 v4.25.5
golang.org/x/sys v0.26.0 golang.org/x/sync v0.15.0
golang.org/x/sys v0.33.0
honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8 honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8
r00t2.io/goutils v1.7.1 r00t2.io/goutils v1.8.1
)
require (
github.com/ebitengine/purego v0.8.4 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/tklauser/go-sysconf v0.3.15 // indirect
github.com/tklauser/numcpus v0.10.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
) )

40
go.sum
View File

@ -3,17 +3,45 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c= github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0= github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 h1:PpXWgLPs+Fqr325bN2FD2ISlRRztXibcX6e8f5FR5Dc=
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/shirou/gopsutil/v4 v4.25.5 h1:rtd9piuSMGeU8g1RMXjZs9y9luK5BwtnG7dZaQUJAsc=
github.com/shirou/gopsutil/v4 v4.25.5/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8 h1:FW42yWB1sGClqswyHIB68wo0+oPrav1IuQ+Tdy8Qp8E= honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8 h1:FW42yWB1sGClqswyHIB68wo0+oPrav1IuQ+Tdy8Qp8E=
honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8/go.mod h1:44w9OfBSQ9l3o59rc2w3AnABtE44bmtNnRMNC7z+oKE= honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8/go.mod h1:44w9OfBSQ9l3o59rc2w3AnABtE44bmtNnRMNC7z+oKE=
r00t2.io/goutils v1.7.1 h1:Yzl9rxX1sR9WT0FcjK60qqOgBoFBOGHYKZVtReVLoQc= r00t2.io/goutils v1.8.1 h1:TQcUycPKsYn0QI4uCqb56utmvu/vVSxlblBg98iXStg=
r00t2.io/goutils v1.7.1/go.mod h1:9ObJI9S71wDLTOahwoOPs19DhZVYrOh4LEHmQ8SW4Lk= r00t2.io/goutils v1.8.1/go.mod h1:9ObJI9S71wDLTOahwoOPs19DhZVYrOh4LEHmQ8SW4Lk=
r00t2.io/sysutils v1.1.1/go.mod h1:Wlfi1rrJpoKBOjWiYM9rw2FaiZqraD6VpXyiHgoDo/o= r00t2.io/sysutils v1.1.1/go.mod h1:Wlfi1rrJpoKBOjWiYM9rw2FaiZqraD6VpXyiHgoDo/o=

14
ispriv/consts_nix.go Normal file
View File

@ -0,0 +1,14 @@
//go:build unix
package ispriv
const (
sudoEnvPfx string = "SUDO_"
sudoUidEnv string = sudoEnvPfx + "UID"
sudoGidEnv string = sudoEnvPfx + "GID"
sudoUnameEnv string = sudoEnvPfx + "USER"
)
const (
curLoginUidFile string = "/proc/self/loginuid"
)

7
ispriv/doc_nix.go Normal file
View File

@ -0,0 +1,7 @@
//go:build unix
/*
ispriv provides functions and a method to determine if a process is being run SUID/SGID, under sudo, etc.
*/
package ispriv

7
ispriv/doc_windows.go Normal file
View File

@ -0,0 +1,7 @@
//go:build windows
/*
ispriv provides functions on Windows to determine the currentl privilege status.
*/
package ispriv

68
ispriv/funcs_nix.go Normal file
View File

@ -0,0 +1,68 @@
//go:build unix
package ispriv
import (
`os`
`github.com/shirou/gopsutil/v4/process`
)
/*
GetProcIDs returns a ProcIDs from a given PID. An error will be raised if the process ID doesn't exist.
A negative value indicates "self" (see also GetProcIDsSelf).
Note that if you are not EUID == 0 (root) or you/the sudo target user does not own the process,
the returning ProcIDs is HIGHLY LIKELY to be very inaccurate.
*/
func GetProcIDs(pid int32) (p *ProcIDs, err error) {
var proc ProcIDs
var ids []uint32
if pid < 0 {
pid = int32(os.Getpid())
}
if proc.proc, err = process.NewProcess(pid); err != nil {
return
}
if ids, err = proc.proc.Gids(); err != nil {
return
}
p.gids = &IdInfo{
real: uint(ids[0]),
effective: uint(ids[1]),
savedSet: uint(ids[2]),
filesystem: nil,
}
if len(ids) == 4 {
p.gids.filesystem = new(uint)
*p.gids.filesystem = uint(ids[3])
}
if ids, err = proc.proc.Uids(); err != nil {
return
}
p.uids = &IdInfo{
real: uint(ids[0]),
effective: uint(ids[1]),
savedSet: uint(ids[2]),
filesystem: nil,
}
if len(ids) == 4 {
p.uids.filesystem = new(uint)
*p.uids.filesystem = uint(ids[3])
}
p = &proc
return
}
// GetProcIDsSelf returns a ProcIDs from the current process' PID.
func GetProcIDsSelf() (p *ProcIDs, err error) {
p, err = GetProcIDs(int32(os.Getpid()))
return
}

426
ispriv/funcs_procids_nix.go Normal file
View File

@ -0,0 +1,426 @@
//go:build unix
package ispriv
import (
`errors`
`os`
`os/user`
`strconv`
`strings`
`github.com/shirou/gopsutil/v4/process`
`golang.org/x/sys/unix`
`r00t2.io/sysutils/envs`
`r00t2.io/sysutils/paths`
)
// GetEffective returns the EUID/EGID.
func (p *ProcIDs) GetEffective() (euid, egid uint) {
euid = p.uids.effective
egid = p.gids.effective
return
}
// GetFS returns the FSUID/FSGID. Not all platforms have this, in which case they'll be nil.
func (p *ProcIDs) GetFS() (fsuid, fsgid *uint) {
if p.uids.filesystem != nil {
fsuid = new(uint)
*fsuid = *p.uids.filesystem
}
if p.gids.filesystem != nil {
fsgid = new(uint)
*fsgid = *p.gids.filesystem
}
return
}
/*
GetGids returms a set of a ProcIDs GIDs.
fs will be nil if unsupported on the platform.
If invoked with SGID, "savedSet" will be the SGID GID.
*/
func (p *ProcIDs) GetGids() (real, effective, savedSet uint, fs *uint) {
real = p.gids.real
effective = p.gids.effective
savedSet = p.gids.savedSet
if p.gids.filesystem != nil {
fs = new(uint)
*fs = *p.gids.filesystem
}
return
}
// GetReal returns the (R)UID/(R)GID.
func (p *ProcIDs) GetReal() (ruid, rgid uint) {
ruid = p.uids.real
rgid = p.gids.real
return
}
// GetSaved returns the SUID/SGID.
func (p *ProcIDs) GetSaved() (suid, sgid uint) {
suid = p.uids.savedSet
sgid = p.gids.savedSet
return
}
/*
GetUids returms a set of a ProcIDs UIDs.
fs will be nil if unsupported on the platform.
If invoked with SUID, "savedSet" will be the SUID UID.
*/
func (p *ProcIDs) GetUids() (real, effective, savedSet uint, fs *uint) {
real = p.uids.real
effective = p.uids.effective
savedSet = p.uids.savedSet
if p.uids.filesystem != nil {
fs = new(uint)
*fs = *p.uids.filesystem
}
return
}
/*
IsSGID returns true if the process is Set GID/SGID.
Note that it will return false if invoked by a group with the same GID as an SGID that's set.
*/
func (p *ProcIDs) IsSGID() (isSgid bool) {
isSgid = p.gids.real != p.gids.savedSet
return
}
/*
IsSUID returns true if the process is Set UID/SUID.
Note that it will return false if invoked by a user with the same UID as an SUID that's set.
*/
func (p *ProcIDs) IsSUID() (isSuid bool) {
isSuid = p.uids.real != p.uids.savedSet
return
}
/*
IsSudo does a very fast (and potentially inaccurate) evaluation of whether the process is running under sudo.
DO NOT use this function for security-sensitive uses, fully accurate results, or critical implementations!
Use IsSudoWithConfidence instead for those cases.
IsSudo only does the most basic of checking, which can be easily and completely overridden by a non-privileged user.
*/
func (p *ProcIDs) IsSudo() (isSudo bool) {
// This is how every other Joe Blow does this. It's an extremely dumb way to do it. The caller has been warned.
for k, _ := range envs.GetEnvMap() {
if strings.HasPrefix(k, sudoEnvPfx) {
isSudo = true
return
}
}
return
}
/*
IsSudoDetailed returns true for a very fast evaluation of whether the process is running under sudo,
and information about that context.
(If isSudo is false, originalUid/originalGid will both be -1 and originalUser will be nil.)
DO NOT use this function for security-sensitive uses, fully accurate results, or critical implementations!
Use IsSudoWithConfidenceDetailed instead for those cases.
IsSudoDetailed only does the most basic of checking, which can be easily and completely overridden by a non-privileged user.
*/
func (p *ProcIDs) IsSudoDetailed() (isSudo bool, originalUid, originalGid int, originalUser *user.User, err error) {
if originalUid, originalGid, originalUser, err = p.getSudoInfoEnv(); err != nil {
return
}
if originalUid >= 0 || originalGid >= 0 || originalUser != nil {
isSudo = true
}
return
}
/*
IsSudoWithConfidence is like IsSudo, but is *much* more throrough.
It not only returns isSudo, which is true if *any* indicators pass,
but also:
* a confidence value (which indicates *how many* indicators *passed*)
* a maxConfidence value (which indicates how many indicators were *tested*)
* a score value (which is a float indicating overall confidence on a fixed and weighted scale; higher is more confident, 1.0 indicates 100% confidence)
*/
func (p *ProcIDs) IsSudoWithConfidence() (isSudo bool, confidence, maxConfidence uint, score float64, err error) {
// confidence/maxConfidence are not used directly; they're unweighted counters.
var scoreConf uint
var scoreMaxConf uint
score = float64(scoreConf) / float64(scoreMaxConf)
return
}
/*
IsSudoWithConfidenceDetailed is like IsSudoDetailed, but is *much* more throrough.
It not only returns the same results as IsSudoDetailed, but includes the same scoring values/system as IsSudoWithConfidence.
*/
func (p *ProcIDs) IsSudoWithConfidenceDetailed() (isSudo bool, confidence, maxConfidence uint, score float64, originalUid, originalGid int, originalUser *user.User, err error) {
var b []byte
var ok bool
var permErr bool
var envUid int
var envGid int
var scoreConf uint
var scoreMaxConf uint
var curUser *user.User
var envUser *user.User
var curUid uint64
var fstat unix.Stat_t
var fsUid int
var procFiles []process.OpenFilesStat
var loginUidFile string = curLoginUidFile
if curUser, err = user.Current(); err != nil {
return
}
if curUid, err = strconv.ParseUint(curUser.Uid, 10, 32); err != nil {
return
}
if procFiles, err = p.proc.OpenFiles(); err != nil {
return
}
// Env vars; only score 1x/each.
maxConfidence += 3
scoreMaxConf += 3
if envUid, envGid, envUser, err = p.getSudoInfoEnv(); err != nil {
return
}
originalUid, originalGid, originalUser = envUid, envGid, envUser
if envUid >= 0 {
confidence++
scoreConf++
}
if envGid >= 0 {
confidence++
scoreConf++
}
if envUser != nil {
confidence++
scoreConf++
}
/*
TTY/PTY ownership. We (can) only check this if we're running in an interactive session.
Typically this is done via (golang.org/x/term).IsTerminal(),
That pulls in a bunch of stuff I don't need, though, so I'll just replicate (...).IsTerminal() here;
it's just a wrapped single function call.
*/
// procFiles[0] is always STDIN. Whether it's a pipe, or TTY/PTY, or file, etc.
// (likewise, procFiles[1] is always STDOUT, procFiles[2] is always STDERR); however...
if _, err = unix.IoctlGetTermios(int(procFiles[0].Fd), unix.TCGETS); err == nil {
// Interactive
maxConfidence++
// This is only worth 2. It's pretty hard to fake unless origin user is root,
// but it's ALSO usually set to the target user.
scoreMaxConf += 2
fstat = unix.Stat_t{}
if err = unix.Fstat(int(procFiles[0].Fd), &fstat); err != nil {
return
}
if uint64(fstat.Uid) != curUid {
// This is a... *potential* indicator, if a lateral sudo was done (user1 => user2),
// or root used sudo to *drop* privs to a regular user.
// We mark it as a pass for confidence since it IS a terminal, and it's permission-related.
confidence++
scoreConf += 2
originalUid = int(fstat.Uid)
}
} else {
// err is OK; just means non-interactive. No counter or score/max score increase; basically a NO-OP.
err = nil
}
// /proc/self/loginuid
// This is a REALLY good indicator. Probably the strongest next to reverse-walking the proc tree. It depends on PAM and auditd support, I think,
// BUT if it's present it's *really* really strong.
if ok, err = paths.RealPathExists(&loginUidFile); err != nil {
return
}
if ok {
maxConfidence++
scoreMaxConf += 5
if b, err = os.ReadFile(loginUidFile); err != nil {
return
}
if fsUid, err = strconv.Atoi(strings.TrimSpace(string(b))); err != nil {
return
}
if uint64(fsUid) != curUid {
confidence++
scoreConf += 5
originalUid = fsUid
}
}
// proc tree reverse walking.
// This is, by far, the most reliable method.
// There are some valid conditions in which this would fail due to permissions
// (e.g. lateral sudo: user1 => user2), but if it's a permission error it's *probably*
// a lateral move anyways.
if isSudo, permErr, originalUid, originalGid, originalUser, err = p.revProcWalk(); err != nil {
return
}
maxConfidence++
scoreMaxConf += 10
if permErr {
confidence++
scoreConf += 5
} else if isSudo {
confidence++
scoreConf += 10
}
score = float64(scoreConf) / float64(scoreMaxConf)
return
}
/*
getSudoInfoEnv returns env var driven sudo information.
These are in no way guaranteed to be accurate as the user can remove or override them.
*/
func (p *ProcIDs) getSudoInfoEnv() (uid, gid int, u *user.User, err error) {
var ok bool
var val string
var envMap map[string]string = envs.GetEnvMap()
uid = -1
gid = -1
if val, ok = envMap[sudoUnameEnv]; ok {
if u, err = user.Lookup(val); err != nil {
return
}
}
if val, ok = envMap[sudoUidEnv]; ok {
if uid, err = strconv.Atoi(val); err != nil {
return
}
}
if val, ok = envMap[sudoGidEnv]; ok {
if gid, err = strconv.Atoi(val); err != nil {
return
}
}
return
}
/*
revProcWalk walks up the process tree ("proctree") until it either:
* finds a process invoked with sudo (true)
* hits PID == 1 (false)
* hits a permission error (true-ish)
*/
func (p *ProcIDs) revProcWalk() (sudoFound, isPermErr bool, origUid, origGid int, origUser *user.User, err error) {
var cmd []string
var parent *ProcIDs
var parentPid int32
var parentUname string
var parentUids []uint32
var parentGids []uint32
origUid = -1
origGid = -1
parent = p
for {
if parent == nil || parent.proc.Pid == 1 {
break
}
if cmd, err = parent.proc.CmdlineSlice(); err != nil {
if errors.Is(err, os.ErrPermission) {
isPermErr = true
err = nil
}
return
}
if cmd[0] == "sudo" {
sudoFound = true
if parentUname, err = parent.proc.Username(); err != nil {
if errors.Is(err, os.ErrPermission) {
isPermErr = true
err = nil
}
return
}
if parentUids, err = parent.proc.Uids(); err != nil {
if errors.Is(err, os.ErrPermission) {
isPermErr = true
err = nil
}
return
}
if parentGids, err = parent.proc.Gids(); err != nil {
if errors.Is(err, os.ErrPermission) {
isPermErr = true
err = nil
}
return
}
if origUser, err = user.Lookup(parentUname); err != nil {
return
}
origUid = int(parentUids[0])
origGid = int(parentGids[0])
}
if sudoFound {
break
}
if parentPid, err = parent.proc.Ppid(); err != nil {
if errors.Is(err, os.ErrPermission) {
isPermErr = true
err = nil
}
return
}
if parent, err = GetProcIDs(parentPid); err != nil {
if errors.Is(err, os.ErrPermission) {
isPermErr = true
err = nil
}
return
}
}
return
}

60
ispriv/funcs_windows.go Normal file
View File

@ -0,0 +1,60 @@
//go:build windows
package ispriv
import (
`golang.org/x/sys/windows`
)
// IsAdmin returns true if currently running with Administrator privileges.
func IsAdmin() (admin bool, err error) {
var sid *windows.SID
var tok windows.Token
if err = windows.AllocateAndInitializeSid(
&windows.SECURITY_NT_AUTHORITY, // identAuth
2, // subAuth
windows.SECURITY_BUILTIN_DOMAIN_RID, // subAuth0
windows.DOMAIN_ALIAS_RID_ADMINS, // subAuth1
0, 0, 0, 0, 0, 0, // subAuth2-10
&sid, // sid
); err != nil {
return
}
defer windows.FreeSid(sid)
tok = windows.Token(0)
if admin, err = tok.IsMember(sid); err != nil {
return
}
return
}
// IsElevated returns true if running in an elevated ("Run as Administrator") context.
func IsElevated() (elevated bool) {
var tok windows.Token = windows.Token(0)
elevated = tok.IsElevated()
return
}
/*
IsPrivileged indicates that the current security context is running both
with Administrator priviliges AND is elevated.
*/
func IsPrivileged() (privileged bool, err error) {
if privileged, err = IsAdmin(); err != nil {
return
}
if privileged {
privileged = IsElevated()
}
return
}

19
ispriv/types_nix.go Normal file
View File

@ -0,0 +1,19 @@
//go:build unix
package ispriv
import (
`github.com/shirou/gopsutil/v4/process`
)
type ProcIDs struct {
proc *process.Process
uids *IdInfo
gids *IdInfo
}
type IdInfo struct {
real uint
effective uint
savedSet uint
filesystem *uint
}

1
paths/TODO Normal file
View File

@ -0,0 +1 @@
- search criteria should *also* support a timestamp range (e.g. so a search can be restricted to both older than AND newer than; e.g. older than 00:00, newer than 01:00)

View File

@ -19,22 +19,23 @@
package paths package paths
import ( import (
`context` "context"
"errors" "errors"
"fmt" "fmt"
"io/fs" "io/fs"
"os" "os"
"os/user" "os/user"
"path"
"path/filepath" "path/filepath"
`sort` "sort"
"strings" "strings"
`sync` "sync"
`time` "time"
// "syscall" // "syscall"
`github.com/djherbis/times` "github.com/djherbis/times"
`r00t2.io/goutils/bitmask` "r00t2.io/goutils/bitmask"
) )
/* /*
@ -91,21 +92,21 @@ func ExpandHome(path *string) (err error) {
} }
/* /*
GetFirst is the file equivalent of envs.GetFirst. GetFirst is the file equivalent of envs.GetFirst.
It iterates through paths, normalizing them along the way It iterates through paths, normalizing them along the way
(so abstracted paths such as ~/foo/bar.txt and relative paths (so abstracted paths such as ~/foo/bar.txt and relative paths
such as bar/baz.txt will still work), and returns the content such as bar/baz.txt will still work), and returns the content
of the first found existing file. If the first found path of the first found existing file. If the first found path
is a directory, content will be nil but isDir will be true is a directory, content will be nil but isDir will be true
(as will ok). (as will ok).
If no path exists, ok will be false. If no path exists, ok will be false.
As always, results are not guaranteed due to permissions, etc. As always, results are not guaranteed due to permissions, etc.
potentially returning an inaccurate result. potentially returning an inaccurate result.
This is a thin wrapper around GetFirstWithRef. This is a thin wrapper around GetFirstWithRef.
*/ */
func GetFirst(paths []string) (content []byte, isDir, ok bool) { func GetFirst(paths []string) (content []byte, isDir, ok bool) {
@ -115,13 +116,13 @@ func GetFirst(paths []string) (content []byte, isDir, ok bool) {
} }
/* /*
GetFirstWithRef is the file equivalent of envs.GetFirstWithRef. GetFirstWithRef is the file equivalent of envs.GetFirstWithRef.
It behaves exactly like GetFirst, but with an additional returned value, idx, It behaves exactly like GetFirst, but with an additional returned value, idx,
which specifies the index in paths in which a path was found. which specifies the index in paths in which a path was found.
As always, results are not guaranteed due to permissions, etc. As always, results are not guaranteed due to permissions, etc.
potentially returning an inaccurate result. potentially returning an inaccurate result.
*/ */
func GetFirstWithRef(paths []string) (content []byte, isDir, ok bool, idx int) { func GetFirstWithRef(paths []string) (content []byte, isDir, ok bool, idx int) {
@ -221,6 +222,68 @@ func RealPath(path *string) (err error) {
return return
} }
/*
RealPathJoin combines RealPath with (path).Join.
If dst is nil, then rootPath will be updated with the new value.
You probably don't want that.
*/
func RealPathJoin(rootPath, dst *string, subPaths ...string) (err error) {
var newPath string
var realDst *string
if err = RealPath(rootPath); err != nil {
return
}
if dst == nil {
realDst = rootPath
} else {
realDst = dst
}
newPath = path.Join(append([]string{*rootPath}, subPaths...)...)
if err = RealPath(&newPath); err != nil {
return
}
*realDst = newPath
return
}
/*
RealPathJoinSys combines RealPath with (path/filepath).Join.
If dst is nil, then path will be updated with the new value.
You probably don't want that.
*/
func RealPathJoinSys(path, dst *string, subPaths ...string) (err error) {
var newPath string
var realDst *string
if err = RealPath(path); err != nil {
return
}
if dst == nil {
realDst = path
} else {
realDst = dst
}
newPath = filepath.Join(append([]string{*path}, subPaths...)...)
if err = RealPath(&newPath); err != nil {
return
}
*realDst = newPath
return
}
/* /*
RealPathExists is like RealPath, but will also return a boolean as to whether the path RealPathExists is like RealPath, but will also return a boolean as to whether the path
actually exists or not. actually exists or not.
@ -327,14 +390,14 @@ func SearchFsPaths(matcher FsSearchCriteria) (found, miss []*FsSearchResult, err
} }
/* /*
SearchFsPathsAsync is exactly like SearchFsPaths, but dispatches off concurrent SearchFsPathsAsync is exactly like SearchFsPaths, but dispatches off concurrent
workers for the filtering logic instead of performing iteratively/recursively. workers for the filtering logic instead of performing iteratively/recursively.
It may, in some cases, be *slightly more* performant and *slightly less* in others. It may, in some cases, be *slightly more* performant and *slightly less* in others.
Note that unlike SearchFsPaths, the results written to the Note that unlike SearchFsPaths, the results written to the
FsSearchCriteriaAsync.ResChan are not guaranteed to be in any predictable order. FsSearchCriteriaAsync.ResChan are not guaranteed to be in any predictable order.
All channels are expected to have already been initialized by the caller. All channels are expected to have already been initialized by the caller.
They will not be closed by this function. They will not be closed by this function.
*/ */
func SearchFsPathsAsync(matcher FsSearchCriteriaAsync) { func SearchFsPathsAsync(matcher FsSearchCriteriaAsync) {
@ -436,11 +499,11 @@ func SearchFsPathsAsync(matcher FsSearchCriteriaAsync) {
} }
/* /*
filterTimes checks a times.Timespec of a file using: filterTimes checks a times.Timespec of a file using:
* an age specified by the caller - an age specified by the caller
* an ageType bitmask for types of times to compare - an ageType bitmask for types of times to compare
* an olderThan bool (if false, the file must be younger than) - an olderThan bool (if false, the file must be younger than)
* an optional "now" timestamp for the age derivation. - an optional "now" timestamp for the age derivation.
*/ */
func filterTimes(tspec times.Timespec, age *time.Duration, ageType *pathTimeType, olderThan bool, now *time.Time) (include bool) { func filterTimes(tspec times.Timespec, age *time.Duration, ageType *pathTimeType, olderThan bool, now *time.Time) (include bool) {

53
types_linux.go Normal file
View File

@ -0,0 +1,53 @@
package sysutils
import (
`golang.org/x/sys/unix`
)
/*
IDState collects information about the current running process.
It should only be used as returned from GetIDState().
Its methods WILL return false information if any of these values are altered.
FSUID/FSGID are not supported.
*/
type IDState struct {
// RUID: Real UID
RUID int
// EUID: Effective UID
EUID int
// SUID: Saved Set UID
SUID int
// RGID: Real GID
RGID int
// EGID: Effective GID
EGID int
// SGID: Saved Set GID
SGID int
// SudoEnvUser is true if SUDO_USER or SUDO_UID is set.
SudoEnvUser bool
// SudoEnvGroup is true if SUDO_GID is set.
SudoEnvGroup bool
// SudoEnvCmd is true if SUDO_COMMAND is set.
SudoEnvCmd bool
// SudoEnvHome is true if SUDO_HOME is set.
SudoEnvHome bool
// SudoEnvVars is true if any of the "well-known" sudo environment variables are set.
SudoEnvVars bool
// PPIDUidMatch is true if the parent PID UID matches the current process UID (mismatch usually indicates sudo invocation).
PPIDUidMatch bool
// PPIDGidMatch is true if the parent PID GID matches the current process GID (mismatch usually indicates sudo invocation).
PPIDGidMatch bool
// uidsChecked is true if the RUID, EUID, and SUID have been populated. (They will be 0 if unset OR if root.)
uidsChecked bool
// gidsChecked is true if the RGID, EGID, and SGID have been populated. (They will be 0 if unset OR if root.)
gidsChecked bool
// sudoChecked is true if the SudoEnvVars is set.
sudoChecked bool
// ppidUidChecked is true if the PPIDUidMatch is set.
ppidUidChecked bool
// ppidGidChecked is true if the PPIDGidMatch is set.
ppidGidChecked bool
// stat holds the stat information for the parent PID.
stat *unix.Stat_t
}