mirror of
https://github.com/Jguer/yay
synced 2024-10-31 04:12:51 +00:00
feat(su): use alternative privilege elevators when sudo is not available
When sudobin/custom wrapper is not available try the following in order: - sudo - doas - pkexec - su
This commit is contained in:
parent
e43c712c84
commit
c8fcdeae5b
5 changed files with 212 additions and 11 deletions
10
main.go
10
main.go
|
@ -105,7 +105,7 @@ func main() {
|
||||||
config, err = settings.NewConfig(yayVersion)
|
config, err = settings.NewConfig(yayVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if str := err.Error(); str != "" {
|
if str := err.Error(); str != "" {
|
||||||
fmt.Fprintln(os.Stderr, str)
|
text.Errorln(str)
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = 1
|
ret = 1
|
||||||
|
@ -117,7 +117,7 @@ func main() {
|
||||||
|
|
||||||
if err = config.ParseCommandLine(cmdArgs); err != nil {
|
if err = config.ParseCommandLine(cmdArgs); err != nil {
|
||||||
if str := err.Error(); str != "" {
|
if str := err.Error(); str != "" {
|
||||||
fmt.Fprintln(os.Stderr, str)
|
text.Errorln(str)
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = 1
|
ret = 1
|
||||||
|
@ -127,7 +127,7 @@ func main() {
|
||||||
|
|
||||||
if config.Runtime.SaveConfig {
|
if config.Runtime.SaveConfig {
|
||||||
if errS := config.Save(config.Runtime.ConfigPath); errS != nil {
|
if errS := config.Save(config.Runtime.ConfigPath); errS != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
text.Errorln(errS)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +136,7 @@ func main() {
|
||||||
config.Runtime.PacmanConf, useColor, err = initAlpm(cmdArgs, config.PacmanConf)
|
config.Runtime.PacmanConf, useColor, err = initAlpm(cmdArgs, config.PacmanConf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if str := err.Error(); str != "" {
|
if str := err.Error(); str != "" {
|
||||||
fmt.Fprintln(os.Stderr, str)
|
text.Errorln(str)
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = 1
|
ret = 1
|
||||||
|
@ -151,7 +151,7 @@ func main() {
|
||||||
dbExecutor, err := ialpm.NewExecutor(config.Runtime.PacmanConf)
|
dbExecutor, err := ialpm.NewExecutor(config.Runtime.PacmanConf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if str := err.Error(); str != "" {
|
if str := err.Error(); str != "" {
|
||||||
fmt.Fprintln(os.Stderr, str)
|
text.Errorln(str)
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = 1
|
ret = 1
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -147,6 +148,28 @@ func (c *Configuration) String() string {
|
||||||
return buf.String()
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check privilege elevator exists otherwise try to find another one.
|
||||||
|
func (c *Configuration) setPrivilegeElevator() error {
|
||||||
|
for _, bin := range [...]string{c.SudoBin, "sudo"} {
|
||||||
|
if _, err := exec.LookPath(bin); err == nil {
|
||||||
|
c.SudoBin = bin
|
||||||
|
return nil // wrapper or sudo command existing. Retrocompatiblity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.SudoFlags = ""
|
||||||
|
c.SudoLoop = false
|
||||||
|
|
||||||
|
for _, bin := range [...]string{"doas", "pkexec", "su"} {
|
||||||
|
if _, err := exec.LookPath(bin); err == nil {
|
||||||
|
c.SudoBin = bin
|
||||||
|
return nil // command existing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ErrPrivilegeElevatorNotFound{confValue: c.SudoBin}
|
||||||
|
}
|
||||||
|
|
||||||
func DefaultConfig() *Configuration {
|
func DefaultConfig() *Configuration {
|
||||||
return &Configuration{
|
return &Configuration{
|
||||||
AURURL: "https://aur.archlinux.org",
|
AURURL: "https://aur.archlinux.org",
|
||||||
|
@ -208,6 +231,11 @@ func NewConfig(version string) (*Configuration, error) {
|
||||||
|
|
||||||
newConfig.expandEnv()
|
newConfig.expandEnv()
|
||||||
|
|
||||||
|
errPE := newConfig.setPrivilegeElevator()
|
||||||
|
if errPE != nil {
|
||||||
|
return nil, errPE
|
||||||
|
}
|
||||||
|
|
||||||
newConfig.Runtime = &Runtime{
|
newConfig.Runtime = &Runtime{
|
||||||
ConfigPath: configPath,
|
ConfigPath: configPath,
|
||||||
Mode: parser.ModeAny,
|
Mode: parser.ModeAny,
|
||||||
|
@ -277,7 +305,7 @@ func (c *Configuration) CmdBuilder(runner exe.Runner) exe.ICmdBuilder {
|
||||||
MakepkgBin: c.MakepkgBin,
|
MakepkgBin: c.MakepkgBin,
|
||||||
SudoBin: c.SudoBin,
|
SudoBin: c.SudoBin,
|
||||||
SudoFlags: strings.Fields(c.SudoFlags),
|
SudoFlags: strings.Fields(c.SudoFlags),
|
||||||
SudoLoopEnabled: false,
|
SudoLoopEnabled: c.SudoLoop,
|
||||||
PacmanBin: c.PacmanBin,
|
PacmanBin: c.PacmanBin,
|
||||||
PacmanConfigPath: c.PacmanConf,
|
PacmanConfigPath: c.PacmanConf,
|
||||||
PacmanDBPath: "",
|
PacmanDBPath: "",
|
||||||
|
|
153
pkg/settings/config_test.go
Normal file
153
pkg/settings/config_test.go
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GIVEN default config
|
||||||
|
// WHEN setPrivilegeElevator gets called
|
||||||
|
// THEN sudobin should stay as "sudo" (given sudo exists)
|
||||||
|
func TestConfiguration_setPrivilegeElevator(t *testing.T) {
|
||||||
|
oldPath := os.Getenv("PATH")
|
||||||
|
|
||||||
|
path, err := os.MkdirTemp("", "yay-test")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
doas := filepath.Join(path, "sudo")
|
||||||
|
_, err = os.Create(doas)
|
||||||
|
os.Chmod(doas, 0o755)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
defer os.RemoveAll(path)
|
||||||
|
|
||||||
|
config := DefaultConfig()
|
||||||
|
config.SudoLoop = true
|
||||||
|
config.SudoFlags = "-v"
|
||||||
|
|
||||||
|
os.Setenv("PATH", path)
|
||||||
|
err = config.setPrivilegeElevator()
|
||||||
|
os.Setenv("PATH", oldPath)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, "sudo", config.SudoBin)
|
||||||
|
assert.Equal(t, "-v", config.SudoFlags)
|
||||||
|
assert.True(t, config.SudoLoop)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GIVEN default config and sudo loop enabled
|
||||||
|
// GIVEN only su in path
|
||||||
|
// WHEN setPrivilegeElevator gets called
|
||||||
|
// THEN sudobin should be changed to "su"
|
||||||
|
func TestConfiguration_setPrivilegeElevator_su(t *testing.T) {
|
||||||
|
oldPath := os.Getenv("PATH")
|
||||||
|
|
||||||
|
path, err := os.MkdirTemp("", "yay-test")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
doas := filepath.Join(path, "su")
|
||||||
|
_, err = os.Create(doas)
|
||||||
|
os.Chmod(doas, 0o755)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
defer os.RemoveAll(path)
|
||||||
|
|
||||||
|
config := DefaultConfig()
|
||||||
|
config.SudoLoop = true
|
||||||
|
config.SudoFlags = "-v"
|
||||||
|
|
||||||
|
os.Setenv("PATH", path)
|
||||||
|
err = config.setPrivilegeElevator()
|
||||||
|
os.Setenv("PATH", oldPath)
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "su", config.SudoBin)
|
||||||
|
assert.Equal(t, "", config.SudoFlags)
|
||||||
|
assert.False(t, config.SudoLoop)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GIVEN default config and sudo loop enabled
|
||||||
|
// GIVEN no sudo in path
|
||||||
|
// WHEN setPrivilegeElevator gets called
|
||||||
|
// THEN sudobin should be changed to "su"
|
||||||
|
func TestConfiguration_setPrivilegeElevator_no_path(t *testing.T) {
|
||||||
|
oldPath := os.Getenv("PATH")
|
||||||
|
|
||||||
|
os.Setenv("PATH", "")
|
||||||
|
config := DefaultConfig()
|
||||||
|
config.SudoLoop = true
|
||||||
|
config.SudoFlags = "-v"
|
||||||
|
|
||||||
|
err := config.setPrivilegeElevator()
|
||||||
|
os.Setenv("PATH", oldPath)
|
||||||
|
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, "sudo", config.SudoBin)
|
||||||
|
assert.Equal(t, "", config.SudoFlags)
|
||||||
|
assert.False(t, config.SudoLoop)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GIVEN default config and sudo loop enabled
|
||||||
|
// GIVEN doas in path
|
||||||
|
// WHEN setPrivilegeElevator gets called
|
||||||
|
// THEN sudobin should be changed to "doas"
|
||||||
|
func TestConfiguration_setPrivilegeElevator_doas(t *testing.T) {
|
||||||
|
oldPath := os.Getenv("PATH")
|
||||||
|
|
||||||
|
path, err := os.MkdirTemp("", "yay-test")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
doas := filepath.Join(path, "doas")
|
||||||
|
_, err = os.Create(doas)
|
||||||
|
os.Chmod(doas, 0o755)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
defer os.RemoveAll(path)
|
||||||
|
|
||||||
|
config := DefaultConfig()
|
||||||
|
config.SudoLoop = true
|
||||||
|
config.SudoFlags = "-v"
|
||||||
|
|
||||||
|
os.Setenv("PATH", path)
|
||||||
|
err = config.setPrivilegeElevator()
|
||||||
|
os.Setenv("PATH", oldPath)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "doas", config.SudoBin)
|
||||||
|
assert.Equal(t, "", config.SudoFlags)
|
||||||
|
assert.False(t, config.SudoLoop)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GIVEN config with wrapper and sudo loop enabled
|
||||||
|
// GIVEN wrapper is in path
|
||||||
|
// WHEN setPrivilegeElevator gets called
|
||||||
|
// THEN sudobin should be kept as the wrapper
|
||||||
|
func TestConfiguration_setPrivilegeElevator_custom_script(t *testing.T) {
|
||||||
|
oldPath := os.Getenv("PATH")
|
||||||
|
|
||||||
|
path, err := os.MkdirTemp("", "yay-test")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
wrapper := filepath.Join(path, "custom-wrapper")
|
||||||
|
_, err = os.Create(wrapper)
|
||||||
|
os.Chmod(wrapper, 0o755)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
defer os.RemoveAll(path)
|
||||||
|
|
||||||
|
config := DefaultConfig()
|
||||||
|
config.SudoLoop = true
|
||||||
|
config.SudoBin = wrapper
|
||||||
|
config.SudoFlags = "-v"
|
||||||
|
|
||||||
|
os.Setenv("PATH", path)
|
||||||
|
err = config.setPrivilegeElevator()
|
||||||
|
os.Setenv("PATH", oldPath)
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, wrapper, config.SudoBin)
|
||||||
|
assert.Equal(t, "-v", config.SudoFlags)
|
||||||
|
assert.True(t, config.SudoLoop)
|
||||||
|
}
|
11
pkg/settings/errors.go
Normal file
11
pkg/settings/errors.go
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
package settings
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type ErrPrivilegeElevatorNotFound struct {
|
||||||
|
confValue string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ErrPrivilegeElevatorNotFound) Error() string {
|
||||||
|
return fmt.Sprintf("unable to find a privilege elevator, config value: %s", e.confValue)
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/leonelquinteros/gotext"
|
"github.com/leonelquinteros/gotext"
|
||||||
|
@ -91,15 +92,22 @@ func (c *CmdBuilder) SetPacmanDBPath(dbPath string) {
|
||||||
c.PacmanDBPath = dbPath
|
c.PacmanDBPath = dbPath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *CmdBuilder) buildPrivilegeElevatorCommand(ctx context.Context, ogArgs []string) *exec.Cmd {
|
||||||
|
if c.SudoBin == "su" {
|
||||||
|
return exec.CommandContext(ctx, c.SudoBin, "-c", strings.Join(ogArgs, " "))
|
||||||
|
}
|
||||||
|
|
||||||
|
argArr := make([]string, 0, len(c.SudoFlags)+len(ogArgs))
|
||||||
|
argArr = append(argArr, c.SudoFlags...)
|
||||||
|
argArr = append(argArr, ogArgs...)
|
||||||
|
|
||||||
|
return exec.CommandContext(ctx, c.SudoBin, argArr...)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *CmdBuilder) BuildPacmanCmd(ctx context.Context, args *parser.Arguments, mode parser.TargetMode, noConfirm bool) *exec.Cmd {
|
func (c *CmdBuilder) BuildPacmanCmd(ctx context.Context, args *parser.Arguments, mode parser.TargetMode, noConfirm bool) *exec.Cmd {
|
||||||
argArr := make([]string, 0, 32)
|
argArr := make([]string, 0, 32)
|
||||||
needsRoot := args.NeedRoot(mode)
|
needsRoot := args.NeedRoot(mode)
|
||||||
|
|
||||||
if needsRoot {
|
|
||||||
argArr = append(argArr, c.SudoBin)
|
|
||||||
argArr = append(argArr, c.SudoFlags...)
|
|
||||||
}
|
|
||||||
|
|
||||||
argArr = append(argArr, c.PacmanBin)
|
argArr = append(argArr, c.PacmanBin)
|
||||||
argArr = append(argArr, args.FormatGlobals()...)
|
argArr = append(argArr, args.FormatGlobals()...)
|
||||||
argArr = append(argArr, args.FormatArgs()...)
|
argArr = append(argArr, args.FormatArgs()...)
|
||||||
|
@ -113,6 +121,7 @@ func (c *CmdBuilder) BuildPacmanCmd(ctx context.Context, args *parser.Arguments,
|
||||||
|
|
||||||
if needsRoot {
|
if needsRoot {
|
||||||
waitLock(c.PacmanDBPath)
|
waitLock(c.PacmanDBPath)
|
||||||
|
return c.buildPrivilegeElevatorCommand(ctx, argArr)
|
||||||
}
|
}
|
||||||
|
|
||||||
return exec.CommandContext(ctx, argArr[0], argArr[1:]...)
|
return exec.CommandContext(ctx, argArr[0], argArr[1:]...)
|
||||||
|
|
Loading…
Reference in a new issue