Using Sudosh with Golang - go

my programs purpose is to do some tasks and then sudosh as a user all the way at the end.
However when running this with Go I get the sudosh: couldn't get your controlling terminal.
Is this because sudosh is looking for a controlling terminal and can't find it? Is this possible to do with Go? I am converting a Python script over to this Go program and it worked fine in Python.
import (
"github.com/codeskyblue/go-sh"
"fmt"
"os"
)
c, _ := sh.Command("sudo", "sudosh", "bob").Output()
fmt.Println(c)
os.Exit(0)
sudosh: couldn't get your controlling terminal.
The Fix
import "os/exec"
func sudosh(name string) {
c := exec.Command("/bin/sh", "-c", "sudo /path/to/sudosh " + name)
c.Stdin = os.Stdin
c.Stderr = os.Stderr
c.Stdout = os.Stdout
o := c.Run()
o=o
}

sudo2sh source shows that it is trying to call ttyname, which might not work in the context of a go-sh.Command calling exec.Command(cmd, args...).
You can test by calling ttyname directly, or by trying and implement a TTYname() function as in this thread.
You can also check the isatty function of go-termutil to debug the issue (as in this answer).
The bottom line is: if ttyname fails in a exec.Command session, sudosh will always return that error message.

Related

Go : Correctly running external program with arguments

Good evening,
I'm working on converting some tools written in python to Go in order to better understand it.
I need the program to call an external .exe with some arguments in order for it to correctly format some data. In the windows shell I can do C:\path_to_exe\file.exe arg1 arg2 "C:\path_to_output\output.txt"
I believe the correct method to do this in Go would be using exec.Command, but I'm not getting any...meaningful results.
out, err := exec.Command("cmd", "C:\\path\\tools\\util\\Utility.exe C:\\file_Location \"select * from TABLE\" C:\\output_path\\output.txt").Output()
fmt.Printf("\n", string(out))
if err != nil {
println(" Error running decomp ", err)
}
This appears to be running command, as the output I am receiving is:
%!(EXTRA string=Microsoft Windows [Version 10.0.22000.739]
(c) Microsoft Corporation. All rights reserved.
Process finished with the exit code 0
Just for giggles I tried breaking up the arguments, but the same result was achieved
out, err := exec.Command("cmd", exPath, utilPath, statement, textOutputPath+"test.txt").Output()
I'm expecting the executed program to run, parse the correct file based on the input, and output the specified txt file. I am left with no .txt file, and the go program runs much faster then the parsing should take.
There must be something I'm missing, could someone please provide some insight on the correct usage of exec.Command, because every example I can find appears to show that this should work.
Why are you spawning cmd.exe and having it run your utility.exe?
You can just spawn utility on its own.
For instance, suppose you have two binaries, hello and say-hello living in the same directory, compiled from
hello.go → hello:
package main
import (
"fmt"
"os"
)
func main() {
argv := os.Args[1:]
if len(argv) == 0 {
argv = []string{"world"}
}
for _, arg := range argv {
fmt.Printf("Hello, %s!\n", arg)
}
}
say-hello.go → say-hello:
package main
import (
"fmt"
"os"
"os/exec"
)
func main() {
process := exec.Command("./hello", os.Args[1:]...)
process.Stdin = os.Stdin
process.Stdout = os.Stdout
process.Stderr = os.Stderr
if err := process.Run(); err != nil {
fmt.Printf("Command failed with exit code %d\n", process.ProcessState.ExitCode())
fmt.Println(err)
}
}
You can then run the command:
$ ./say-hello Arawn Gywdion Sarah Hannah
And get back the expected
Hello, Arawn!
Hello, Gwydion!
Hello, Sarah!
Hello, Hannah!
It appears to be working correctly according to the outputs in your question.
A few suggestions:
It might be useful to print the command out as a string before running it, to check it's what you want.
You may find backticks useful when you have a string containing backslashes and quotation marks.
You have not supplied any format to fmt.Printf, hence the EXTRA in that output.
Using println to print the error will not stringify it, so use fmt.Printf for that too.
package main
import (
"fmt"
"os/exec"
)
func main() {
cmd := exec.Command("cmd", `C:\path\tools\util\Utility.exe C:\file_Location "select * from TABLE" C:\output_path\output.txt`)
fmt.Printf("%s\n", cmd.String())
out, err := cmd.Output()
fmt.Printf("%s\n", string(out))
if err != nil {
fmt.Printf(" Error running decomp %s\n", err)
}
}
Playground: https://go.dev/play/p/3t0aOxAZRtU

Called program fails if run through os.exec

I am not sure if this is a problem of the called program or if the problem is caused by the way I call the program. Because of this I start at the source code.
I need to call ssh from a program (if you are interested in the reasons I will mention them below) but ssh silently exits.
When I call ssh -v user#remotehost from shell this succeeds:
the wanted debug output on stderr is shown
I am asked for the password
I can see the remote hosts shell
But when I do the same from within my program (myssh -v user#remotehost only this happens:
I am asked for the password
Neither the debug output on stderr is shown nor do I reach the remote hosts shell.
This is my sourcecode:
package main
import (
"fmt"
"log"
"os"
"os/exec"
)
func main() {
params := os.Args[1:]
fmt.Printf("passing this to ssh: %s\n", params)
cmd := exec.Command("ssh", params...)
err := cmd.Run()
if err != nil {
log.Fatal(err)
}
}
Reason why I wrote this code: I use Ansible which calls ssh. I need to "manipulate" the parameters that Ansible passes to ssh. So far I asked on the OpenSSH and Ansible mailing lists, there is no means in OpenSSH and Ansible to change the parameters (for others it is, but not those I need). The best suggestion I got and that I want to implement is to provide an alternative ssh command to Ansible, use that to receive and modify the parameters and pass them on to the real ssh.
Are you capturing Stdout and Stderr from cmd? This is where the respective outputs of the command is sent to. The documentation for exec.Command has a really good example on this.
In your case, you'll also want to setup Stdin so that you can pass the password for example.
Here's a really basic example based on your code:
package main
import (
"bytes"
"fmt"
"log"
"os"
"os/exec"
)
func main() {
params := os.Args[1:]
fmt.Println("Passing this to ssh: %s", params)
var stdin bytes.Buffer
var stdout bytes.Buffer
var stderr bytes.Bufer
cmd := exec.Command("ssh", params...)
cmd.Stdin = &stdin
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
log.Fatal(err)
}
fmt.Println("Stdout: %s", stdout.String())
fmt.Println("stderr: %s", stderr.String())
}
Since stdin, stdout, and stderr are all bytes.Buffers, you can read and write from them just like any other buffer.
You might also want to consider using the golang.org/x/crypto/ssh package which provides a native SSH interface for Go instead of using sub-processes.

Go command output by default to stdout?

I started learning and playing around with Go to see what it is like to make some more complex console/cli type tools instead of using shells or Python. I want to execute commands and display the output. I figured out how to print the output like this:
out, err := exec.Command("pwd").Output()
print(string(out))
Is there a way to execute the commands and have it default to stdout like a shell script, or do I need to make a helper function for this?
Update: After getting IntelliJ and the Go plugin, I poked around in the Go source and agree there is currently no way to do with without a helper method.
It is not possible to reuse a Cmd object as per this comment in the exec.go source code:
// A Cmd cannot be reused after calling its Run, Output or CombinedOutput
// methods.
I did incorporate the stdout option into my own helper, including other options like shell integration. I will try turn that into open source if I can make it useful. An interesting first day of Go.
The solution
Actually, it is pretty easy. You can set the stdout of the command to os.Stdout and Bob's your uncle:
package main
import (
"os"
"os/exec"
)
func main() {
cmd := exec.Command("pwd")
cmd.Stdout = os.Stdout
err := cmd.Run()
if err != nil {
panic(err)
}
}
What's happening here?
By default, the output of a command is stored in a bytes.Buffer if cmd.Stdout is not set to another io.Writer. The call of cmd.Output() then runs the command and saves the output to said buffer.
Since os.Stdout implements io.Writer interface, we simply set cmd.Stdout to be os.Stdout. Now when .Run() is called, the output of the command gets written to the io.Writer defined in cmd.Stdout, which happens to be os.Stdout and the output gets written in the shell.
EDIT: As per comment, if all commands should write to os.Stdout, there of course is no way to prevent some helper. I'd do it like this:
package main
import (
"os"
"os/exec"
)
func CmdToStdout( c string ) (err error){
cmd := exec.Command(c)
cmd.Stdout = os.Stdout
err = cmd.Run()
return
}
func main() {
err := CmdToStdout("pwd")
if err != nil {
panic(err)
}
}
You have to create a helper if you need this often (and 5 lines looks too much). Based on the documentation this is a recommended way:
package main
import (
"fmt"
"log"
"os/exec"
)
func main() {
out, err := exec.Command("date").Output()
if err != nil {
log.Fatal(err)
}
fmt.Printf("The date is %s\n", out)
}

Getting output from `exec.Cmd` in "real-time"

This question is similar to Golang - Copy Exec output to Log except it is concerned with the buffering of output from exec commands.
I have the following test program:
package main
import (
"fmt"
"log"
"os/exec"
)
func main() {
cmd := exec.Command("python", "inf_loop.py")
var out outstream
cmd.Stdout = out
if err := cmd.Start(); err != nil {
log.Fatal(err)
}
fmt.Println(cmd.Wait())
}
type outstream struct{}
func (out outstream) Write(p []byte) (int, error) {
fmt.Println(string(p))
return len(p), nil
}
inf_loop.py, which the above refers to, simply contains:
print "hello"
while True:
pass
The go program hangs when I run it and doesn't output anything, but if I use os.Stdout instead of out then it outputs "hello" before it hangs. Why is there a discrepancy between the two io.Writers and how can it be fixed?
Some more diagnostic information:
When the loop is removed from inf_loop.py then "hello" is output from both programs, as expected.
When using yes as the program instead of the python script and outputting len(p) in outstream.Write then there is output, and the output is usually 16384 or 32768. This indicates to me that this is a buffering issue, as I originally anticipated, but I still don't understand why the outstream structure is being blocked by buffering but os.Stdout isn't. One possibility is that the behaviour is the result of the way that exec passes the io.Writer directly to os.StartProcess if it is an os.File (see source for details), otherwise it creates an os.Pipe() between the process and the io.Writer, and this pipe may be causing the buffering. However, the operation and possible buffering of os.Pipe() is too low-level for me to investigate.
Python buffers stdout by default. Try this program:
import sys
print "hello"
sys.stdout.flush()
while True:
pass
or run Python with unbuffered stdout and stderr:
cmd := exec.Command("python", "-u", "foo.py")
Note the -u flag.
You see different results when using cmd.Stout = os.Stdout because Python uses line buffering when stdout is a terminal.

How to start vim from go?

I've got a command line tool written in Golang and I need to start vim from it. However it's not working, and there's not any error or much else to work with. I've reduced the code to just this:
package main
import (
"fmt"
"os/exec"
)
func main() {
cmd := exec.Command("vim", "test.txt")
err := cmd.Run()
fmt.Println(err)
}
When I run this, I can see the vim process for a 2-3 seconds but the application doesn't actually open. Then the program simply exits (and the vim process closes) with an "exit status 1".
I've also tried this to capture stderr:
package main
import (
"bytes"
"fmt"
"os/exec"
)
func main() {
cmd := exec.Command("vim", "test.txt")
var stderr bytes.Buffer
cmd.Stderr = &stderr
err := cmd.Run()
fmt.Println(err)
fmt.Println(stderr)
}
But in this case, the program gets stuck indefinitely.
Any idea what could be the issue?
Pass on stdin and stdout from the calling program which, provided it was run from a terminal (likely for a command line program) will start vim for you and return control when the user has finished editing the file.
package main
import (
"fmt"
"os"
"os/exec"
)
func main() {
cmd := exec.Command("vim", "test.txt")
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
err := cmd.Run()
fmt.Println(err)
}
VIM needs a proper terminal and detects the absence of one.
If you use the StderrPipe and read it while vim is running you will see this:
2014/02/02 20:25:49 Vim: Warning: Output is not to a terminal
2014/02/02 20:25:49 Vim: Warning: Input is not from a terminal
Example for reading stderr while executing (on play):
func logger(pipe io.ReadCloser) {
reader := bufio.NewReader(pipe)
for {
output, err := reader.ReadString('\n')
if err != nil {
log.Println(err)
return
}
log.Print(string(output))
}
}
pipe, err := cmd.StderrPipe()
go logger(pipe)
cmd.Run()
For vim to run you probably need to emulate a terminal.
Maybe goat (doc) can help you out:
tty := term.NewTTY(os.Stdin)
cmd := exec.Command("vim", "test.txt")
cmd.Stdin = t
cmd.Stdout = t
// ...

Resources