I'm trying to cross compile a Go program that will execute a bash script. Is it possible to embed the bash script in the binary?
I've referenced:
Golang serve static files from memory
Not sure if this applies to executing bash scripts though. Am I missing something here? Some clarification or pointers will be very helpful, thanks!
Since bash can execute scripts from stdin, you just send your script to the bash command via command.Stdin.
An example without go embed:
package main
import (
"bytes"
"fmt"
"os/exec"
"strings"
)
var script = `
echo $PWD
pwd
echo "-----------------------------------"
# print process
ps aux | grep code
`
func main() {
c := exec.Command("bash")
c.Stdin = strings.NewReader(script)
b, e := c.Output()
if e != nil {
fmt.Println(e)
}
fmt.Println(string(b))
}
With go 1.16 embed (https://golang.org/pkg/embed/):
package main
import (
"bytes"
_ "embed"
"fmt"
"os/exec"
"strings"
)
//go:embed script.sh
var script string
func main() {
c := exec.Command("bash")
c.Stdin = strings.NewReader(script)
b, e := c.Output()
if e != nil {
fmt.Println(e)
}
fmt.Println(string(b))
}
Bonus
Passing parameters to your script with -s -.
The following example will pass -la /etc to the script.
package main
import (
"fmt"
"os/exec"
"strings"
)
func main() {
// pass parameters to your script as a safe way.
c := exec.Command("sh", "-s", "-", "-la", "/etc")
// use $1, $2, ... $# as usual
c.Stdin = strings.NewReader(`
echo $#
ls $1 "$2"
`)
b, e := c.Output()
if e != nil {
fmt.Println(e)
}
fmt.Println(string(b))
}
Playground: https://go.dev/play/p/T1lMSrXcOIL
You can actually directly interface with the system shell in Go. Depending on what's in your bash script you can probably convert everything completely to go. For example things like handling files, extracting archives, outputting text, asking for user input, downloading files, and so much more can be done natively in Go. For anything you absolutely need the shell for you can always use golang.org/pkg/os/exec.
I wrote a snippet that demonstrates a really simple Go based command shell. Basically it pipes input, output, and error between the user and the shell. It can be used interactively or to directly run most shell commands. I'm mentioning it here mostly to demonstrate Go's OS capabilities. Check it out: github.com/lee8oi/goshell.go
Did you try writing the stream-data (according to the reference go-bindata provides a function that returns []byte) into a temporary file?
see: http://golang.org/pkg/io/ioutil/#TempFile
you can then execute it with a syscall
http://golang.org/pkg/syscall/#Exec
where the first argument needs to be a shell.
Related
I am writing a personal tool which gives me a prompt and lets me execute useful commands. Like bash but its not a shell program
I want an input prompt like Bash
If you enter nothing, just print a new line and scan for input again
If you enter the hotkey Ctrl + D, terminate the program
It should be like this
[user#computer ~]$ go build
[user#computer ~]$ ./myapp
$
$ command
.. do command
$ (Ctrl + D hotkey)
[user#computer ~]$
I tried using Scanln but it didn't accept spaces which was a huge problem
EDIT: here is what I implemented
func main() {
var input string
fmt.Println(banners.Gecko1)
fmt.Print("$ ")
fmt.Scanln(&input)
}
Here is the console:
[user#pc MyTool]$ go build
[user#pc MyTool]$ ./MyTool
-----ENTER COMMANDS
$ term1 term2 term3 term4 term5
[user#pc MyTool]$ erm2 term3 term4 term5
bash: erm2: command not found
[user#pc MyTool]$
as you can see, everything after term1 is ignored and somehow passed to bash...
I think that's how scanln is supposed to work. The docs state that it "scans text from standard input, storing successive space-separated values into successive arguments." Alternatively, use the bufio package if you want to read one line at a time. You can refer to the sample code below:
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
func main() {
input_reader := bufio.NewReader(os.Stdin)
for {
fmt.Print("$ ")
line, _ := input_reader.ReadString('\n')
line = strings.Replace(line, "\n", "", -1)
fmt.Println(line)
}
}
My goal is for "init", "init -site=test", both versions of init and also the standalone "debug" command to be accepted at the command line, and to treat anything left over as a filename.
What actually happens is that in the case of "init -site=test" for some reason the "-site=test" is also accepted as a filename. How can I stop that from happening?
package main
import (
"flag"
"fmt"
"os"
)
func main() {
initCmd := flag.NewFlagSet("init", flag.ExitOnError)
initSiteName := initCmd.String("site", "", "Main name for your site")
flag.Parse()
for pos, cmd := range os.Args {
switch cmd {
case "debug":
fmt.Printf("debug\n")
case "init":
initCmd.Parse(os.Args[pos+1:])
fmt.Printf("init\n site name:%v\n", *initSiteName)
default:
fmt.Printf("Filename: %v\n", cmd);
}
}
}
It's not very convenient using the flag package. From the doc:
Flag parsing stops just before the first non-flag argument ("-" is a non-flag argument) or after the terminator "--".
You would have to do it manually:
After parsing, the arguments following the flags are available as the slice flag.Args() or individually as flag.Arg(i).
Or you can use another package.
i try to create integrity protection of my application , this is my actual code :
package main
import (
"os"
"io"
"crypto/sha256"
"fmt"
)
var OriginalSign string
func checkSUM() string {
hasher := sha256.New()
f, err := os.Open(os.Args[0])
if err != nil {
os.Exit(0)
}
defer f.Close()
if _, err = io.Copy(hasher, f); err != nil {
os.Exit(0)
}
return fmt.Sprintf("%x", hasher.Sum(nil))
}
func main() {
signature := checkSUM()
fmt.Println(OriginalSign)
fmt.Println(signature)
if signature != OriginalSign {
fmt.Println("binary is compromised")
}
}
i compiled with this command :
C:\Users\admin\go\src\localhost\lic>go build -ldflags="-s -w -X main.OriginalSig
n=8636cdeef255e52c6fd3f391fd7d75fbaf7c6e830e0e7ac66a645093c7efcbc7" -o checksum.
exe checksum.go
C:\Users\admin\go\src\localhost\lic>checksum.exe
8636cdeef255e52c6fd3f391fd7d75fbaf7c6e830e0e7ac66a645093c7efcbc7
d29440d3467f6176a6af0dcb61ea696cb318db3a6f1680b5b8f7890e165d8d7e
binary is compromised
how i can do this corectly in go ? i need to know signature of final binary file and check if is compromited.
I can't see how to hook into tool buildid in a program but it can (kind of) detect changes to a binary
buildid does seem to store a "contentid" of the binary which is the essence of the original question
Here's a bash script that shows this (sorry I don't do MS Windows)
#
# delete any old binaries
rm -f t
# do an initial build
go build t.go
# show it works
./t
# get the original buildid
ORIG=$(go tool buildid t)
# now tamper with it!
perl -p -i -e 's/testing/porkpie/' t
# run again, show the tamper
./t
# now regenerate the buildid
go tool buildid -w t
# get the buildid after the regeneration
LATER=$(go tool buildid t)
# show the original and the post-tampering buildid - they are different
echo "$ORIG"
echo "$LATER"
Here's the do nothing t.go
package main
import (
"fmt"
)
func main() {
fmt.Println("testing 123")
}
Here's the output
testing 123
porkpie 123
koB1H61TwQSHTQGiI4PP/-o93sSzqt1ltMhBJn4pR/2wvL4J9vF4vGUGjdbsyd/y-0uRBmxfJdrbAfsE1lr
koB1H61TwQSHTQGiI4PP/-o93sSzqt1ltMhBJn4pR/2wvL4J9vF4vGUGjdbsyd/UeLetY1pBF54B_4Y8-Nj
So the go tool buildid can store a hash in with the binary and (kind of) detect tampering. But I couldn't work out how to get the contentid from a normal call inside a normal program
I'm facing with a weird golang issue. The following code will clarify:
package main
import (
"os/exec"
"io"
"fmt"
"os"
)
var (
pw io.WriteCloser
pr io.ReadCloser
)
func main() {
term := exec.Command("/bin/sh")
// Get stdin writer pipe
pw, _ = term.StdinPipe()
pr, _ = term.StdoutPipe()
term.Start()
run("cd ~")
pwd := run("pwd");
// Do something with pwd output
...
term.Wait()
}
func run(c string) string {
io.WriteString(pw, fmt.Sprintln(c))
buf := make([]byte, 32 * 1024)
pr.Read(buf)
return string(buf)
}
I'd like to run some commands in a shell env and read their output. There's no problem on write/run command but it seems that there're some limitations while reading:
you can't know if a command doesn't output anything or not;
there's no way to check if stdout is ready to be read or not.
The pr.Read(dest) method will block the code flow until something is read from stdout. As said, the goal is to read sequentially (without using a go routine and/or an infinite loop). This means that if we send a cd command the func end is never reached.
Setting the non-block flag through unix.SetNonblock on stdout file descriptor seems to solve the above issue but you can't know prior if it's ready or not and an error saying "resource temporary not available" is returned from .Read call.
As Cerise Limón mentioned go functions whould be the way to go here, since these sorts of interactive scripting exercises are traditionally done with expect.
You can wrap the the parrellel execution into a library to it might still look like sequencial code, so this might be helpful: https://github.com/ThomasRooney/gexpect
From the readme:
child, err := gexpect.Spawn("python")
if err != nil { panic(err) }
child.Expect(">>>")
child.SendLine("print 'Hello World'")
child.Interact()
child.Close()
When in a source file $PWD/dir/src.go I use
os.Open("myfile.txt")
it looks for myfile.txt in $PWD (which looks normal).
Is there way to tell Go to look for myfile.txt in the same directory as src.go ? I need something like __FILE__ in Ruby.
Go is not an interpreted language so looking for a file in the same location as the source file doesn't make any sense. The go binary is compiled and the source file doesn't need to be present for the binary to run. Because of that Go doesn't come with an equivalent to FILE. The runtime.Caller function returns the file name at the time the binary was compiled.
I think perhaps if we understood why you actually wanted this functionality we could advise you better.
A possible substitute skeleton:
func __FILE__() (fn string) {
_, fn, _, _ = runtime.Caller(0)
return
}
Details here.
Use package osext
It's providing function ExecutableFolder() that returns an absolute path to folder where the currently running program executable reside (useful for cron jobs). It's cross platform.
Online documentation
package main
import (
"github.com/kardianos/osext"
"fmt"
"log"
)
func main() {
folderPath, err := osext.ExecutableFolder()
if err != nil {
log.Fatal(err)
}
fmt.Println(folderPath)
}
You can also get full executable path (similar to __FILE__):
package main
import (
"github.com/kardianos/osext"
"fmt"
)
func main() {
exeAbsolutePath, _ := osext.Executable()
fmt.Println(exeAbsolutePath)
}