package command import ( "bytes" "encoding/base64" "fmt" "io" "os" "os/exec" "strings" "time" "github.com/HikariKnight/quickpassthrough/internal/common" "github.com/HikariKnight/quickpassthrough/internal/logger" ) // Run a command and return STDOUT func Run(binary string, args ...string) ([]string, error) { var stdout, stderr bytes.Buffer // Configure the ls-iommu c--ommand cmd := exec.Command(binary, args...) cmd.Stderr = &stderr cmd.Stdout = &stdout // Execute the command err := cmd.Run() // Read the output output, _ := io.ReadAll(&stdout) // Get the output outputs := make([]string, 0, 1) outputs = append(outputs, string(output)) // Return our list of items return outputs, err } // RunErr is just like command.Run() but also returns STDERR func RunErr(binary string, args ...string) ([]string, []string, error) { var stdout, stderr bytes.Buffer // Configure the ls-iommu c--ommand cmd := exec.Command(binary, args...) cmd.Stderr = &stderr cmd.Stdout = &stdout // Execute the command err := cmd.Run() // Read the output output, _ := io.ReadAll(&stdout) outerr, _ := io.ReadAll(&stderr) // Get the output var outputs, outerrs []string outputs = append(outputs, string(output)) outerrs = append(outerrs, string(outerr)) // Return our list of items return outputs, outerrs, err } func RunErrSudo(isRoot bool, binary string, args ...string) ([]string, []string, error) { if !isRoot && binary != "sudo" { args = append([]string{binary}, args...) binary = "sudo" } logger.Printf("Executing (elevated): %s %s\n", binary, strings.Join(args, " ")) fmt.Printf("Executing (elevated): %s %s\n", binary, strings.Join(args, " ")) return RunErr(binary, args...) } // Elevate elevates this functions runs the command "sudo -Sk -- echo", // this forces sudo to re-authenticate and lets us enter the password to STDIN // giving us the ability to run sudo commands func Elevate(password string) { // Do a simple sudo command to just authenticate with sudo cmd := exec.Command("sudo", "-S", "--", "echo") // Wait for 500ms, if the password is correct, sudo will return immediately cmd.WaitDelay = 1000 * time.Millisecond // Open STDIN stdin, err := cmd.StdinPipe() common.ErrorCheck(err, "\nFailed to get sudo STDIN") // Start the authentication err = cmd.Start() common.ErrorCheck(err, "\nFailed to start sudo command") // Get the passed password pw, _ := base64.StdEncoding.DecodeString(password) _, err = stdin.Write([]byte(string(pw) + "\n")) common.ErrorCheck(err, "\nFailed at typing to STDIN") // Clear the password pw = nil password = "" _ = stdin.Close() // Wait for the sudo prompt (If the correct password was given, it will not stay behind) err = cmd.Wait() common.ErrorCheck(err, "\nError, password given was wrong") } // Clear clears the terminal. func Clear() { c := exec.Command("clear") c.Stdout = os.Stdout _ = c.Run() } // ExecAndLogSudo executes an elevated command and logs the output. // // * if we're root, the command is executed directly // * if we're not root, the command is prefixed with "sudo" // // - noisy determines if we should print the command to the user // noisy isn't set to true by our copy caller, as it logs differently, // but other callers set it. func ExecAndLogSudo(isRoot, noisy bool, exe string, args ...string) error { if !isRoot && exe != "sudo" { og := exe exe = "sudo" newArgs := make([]string, 0) newArgs = append(newArgs, og) newArgs = append(newArgs, args...) args = newArgs } // Write to logger logger.Printf("Executing (elevated): %s %s\n", exe, strings.Join(args, " ")) if noisy { // Print to the user fmt.Printf("Executing (elevated): %s %s\nSee debug.log for detailed output\n", exe, strings.Join(args, " ")) } wd, err := os.Getwd() if err != nil { return err } r := exec.Command(exe, args...) r.Dir = wd cmdCombinedOut, err := r.CombinedOutput() outStr := string(cmdCombinedOut) // Write to logger, tabulate output // tabulation denotes it's hierarchy as a child of the command outStr = strings.ReplaceAll(outStr, "\n", "\n\t") logger.Printf("\t" + string(cmdCombinedOut) + "\n") if noisy { // Print to the user fmt.Printf("%s\n", outStr) } if err != nil { err = fmt.Errorf("failed to execute %s: %w\n%s", exe, err, outStr) } return err }