diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6a6444d --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Chad Retz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/examples/embeddedfileserver/main.go b/examples/embeddedfileserver/main.go new file mode 100644 index 0000000..83acda2 --- /dev/null +++ b/examples/embeddedfileserver/main.go @@ -0,0 +1,72 @@ +package main + +import ( + "context" + "flag" + "fmt" + "log" + "net/http" + "os" + "path/filepath" + "time" + + "github.com/cretz/bine/process/embedded" + "github.com/cretz/bine/tor" +) + +func main() { + if err := run(); err != nil { + log.Fatal(err) + } +} + +func run() error { + // Parse flags. By default, non-verbose served in the current working dir. + var verbose bool + flag.BoolVar(&verbose, "verbose", false, "Whether to have verbose logging") + var directory string + flag.StringVar(&directory, "dir", ".", "The directory to serve (current dir is default)") + flag.Parse() + var err error + if directory, err = filepath.Abs(directory); err != nil { + return err + } + // Start tor + startConf := &tor.StartConf{ProcessCreator: embedded.NewCreator()} + if verbose { + startConf.DebugWriter = os.Stdout + } else { + startConf.ExtraArgs = []string{"--quiet"} + } + fmt.Printf("Starting and registering onion service to serve files from %v\n", directory) + fmt.Println("Please wait a couple of minutes...") + t, err := tor.Start(nil, startConf) + if err != nil { + return err + } + defer t.Close() + // Wait at most a few minutes to publish the service + listenCtx, listenCancel := context.WithTimeout(context.Background(), 3*time.Minute) + defer listenCancel() + // Create an onion service to listen on a random local port but show as 80 + onion, err := t.Listen(listenCtx, &tor.ListenConf{RemotePorts: []int{80}}) + if err != nil { + return err + } + defer onion.Close() + // Start server asynchronously + fmt.Printf("Open Tor browser and navigate to http://%v.onion\n", onion.ID) + fmt.Println("Press enter to exit") + server := &http.Server{Handler: http.FileServer(http.Dir(directory))} + defer server.Shutdown(context.Background()) + errCh := make(chan error, 1) + go func() { errCh <- server.Serve(onion) }() + // Wait for key asynchronously + go func() { + fmt.Scanln() + errCh <- nil + }() + // Stop when one happens + defer fmt.Println("Closing") + return <-errCh +} diff --git a/examples/embeddedversion/main.go b/examples/embeddedversion/main.go new file mode 100644 index 0000000..49cb202 --- /dev/null +++ b/examples/embeddedversion/main.go @@ -0,0 +1,28 @@ +package main + +import ( + "context" + "fmt" + "log" + + "github.com/cretz/bine/process/embedded" +) + +func main() { + if err := run(); err != nil { + log.Fatal(err) + } +} + +func run() error { + p, err := embedded.NewCreator().New(context.Background(), "--version") + if err != nil { + return err + } + fmt.Printf("Starting...\n") + if err = p.Start(); err != nil { + return err + } + fmt.Printf("Waiting...\n") + return p.Wait() +} diff --git a/process/embedded/process.go b/process/embedded/process.go index 295c258..694901c 100644 --- a/process/embedded/process.go +++ b/process/embedded/process.go @@ -1,13 +1,124 @@ // Package embedded implements process interfaces for statically linked, -// embedded Tor. +// embedded Tor. Note, processes created here are not killed when a context is +// done like w/ os.Exec. // -// TODO: not finished yet +// Usage +// +// This package can be used with CGO to statically compile Tor. This package +// expects https://github.com/cretz/tor-static to be cloned at +// $GOPATH/src/github.com/cretz/tor-static as if it was fetched with go get. To +// build the needed static libs, follow the README in that project. Once the +// static libs are built, this uses CGO to statically link them here. For +// Windows this means something like http://tdm-gcc.tdragon.net/ needs to be +// present with gcc.exe on the PATH. +// +// NOTE: Other OSs besides Windows have not been tested but likely require an +// LDFLAGS setting here. Pull requests are welcomed. package embedded -import "github.com/cretz/bine/process" +import ( + "context" + "fmt" + + "github.com/cretz/bine/process" +) + +/* +#cgo CFLAGS: -I${SRCDIR}/../../../tor-static/tor/src/or +#cgo LDFLAGS: -L${SRCDIR}/../../../tor-static/tor/src/or -ltor +#cgo LDFLAGS: -L${SRCDIR}/../../../tor-static/tor/src/common -lor -lor-crypto -lcurve25519_donna -lor-ctime -lor-event +#cgo LDFLAGS: -L${SRCDIR}/../../../tor-static/tor/src/trunnel -lor-trunnel +#cgo LDFLAGS: -L${SRCDIR}/../../../tor-static/tor/src/ext/keccak-tiny -lkeccak-tiny +#cgo LDFLAGS: -L${SRCDIR}/../../../tor-static/tor/src/ext/ed25519/ref10 -led25519_ref10 +#cgo LDFLAGS: -L${SRCDIR}/../../../tor-static/tor/src/ext/ed25519/donna -led25519_donna +#cgo LDFLAGS: -L${SRCDIR}/../../../tor-static/libevent/dist/lib -levent +#cgo LDFLAGS: -L${SRCDIR}/../../../tor-static/xz/dist/lib -llzma +#cgo LDFLAGS: -L${SRCDIR}/../../../tor-static/zlib/dist/lib -lz +#cgo LDFLAGS: -L${SRCDIR}/../../../tor-static/openssl/dist/lib -lssl -lcrypto +#cgo windows LDFLAGS: -lws2_32 -lcrypt32 -lgdi32 + +#include +#include + +// Ref: https://stackoverflow.com/questions/45997786/passing-array-of-string-as-parameter-from-go-to-c-function + +static char** makeCharArray(int size) { + return calloc(sizeof(char*), size); +} + +static void setArrayString(char **a, char *s, int n) { + a[n] = s; +} + +static void freeCharArray(char **a, int size) { + int i; + for (i = 0; i < size; i++) + free(a[i]); + free(a); +} +*/ +import "C" + +type embeddedCreator struct{} // NewCreator creates a process.Creator for statically-linked Tor embedded in // the binary. func NewCreator() process.Creator { - panic("TODO: embedding not implemented yet") + return embeddedCreator{} +} + +type embeddedProcess struct { + ctx context.Context + args []string + doneCh chan int +} + +func (embeddedCreator) New(ctx context.Context, args ...string) (process.Process, error) { + return &embeddedProcess{ctx: ctx, args: args}, nil +} + +func (e *embeddedProcess) Start() error { + if e.doneCh != nil { + return fmt.Errorf("Already started") + } + // Create the char array for the args + args := append([]string{"tor"}, e.args...) + charArray := C.makeCharArray(C.int(len(args))) + for i, a := range args { + C.setArrayString(charArray, C.CString(a), C.int(i)) + } + // Build the conf + conf := C.tor_main_configuration_new() + if code := C.tor_main_configuration_set_command_line(conf, C.int(len(args)), charArray); code != 0 { + C.tor_main_configuration_free(conf) + C.freeCharArray(charArray, C.int(len(args))) + return fmt.Errorf("Failed to set command line args, code: %v", int(code)) + } + // Run it async + e.doneCh = make(chan int, 1) + go func() { + defer C.freeCharArray(charArray, C.int(len(args))) + defer C.tor_main_configuration_free(conf) + e.doneCh <- int(C.tor_run_main(conf)) + }() + return nil +} + +func (e *embeddedProcess) Wait() error { + if e.doneCh == nil { + return fmt.Errorf("Not started") + } + ctx := e.ctx + if ctx == nil { + ctx = context.Background() + } + select { + case <-ctx.Done(): + return ctx.Err() + case code := <-e.doneCh: + if code == 0 { + return nil + } + return fmt.Errorf("Command completed with error exit code: %v", code) + } } diff --git a/process/process.go b/process/process.go index 491bcad..331c34b 100644 --- a/process/process.go +++ b/process/process.go @@ -24,7 +24,7 @@ type Process interface { // analagous to os/exec.Cmd.Start. Start() error // Wait waits for the Tor process to exit and returns error if it was not a - // successful exit. It is analagous to os/exec.Cmd.Wait. + // successful exit. It is analagous to os/exec.Cmd.Wait. Wait() error } diff --git a/tor/tor.go b/tor/tor.go index 883a419..2dc407a 100644 --- a/tor/tor.go +++ b/tor/tor.go @@ -12,7 +12,6 @@ import ( "time" "github.com/cretz/bine/control" - "github.com/cretz/bine/process/embedded" "github.com/cretz/bine/process" ) @@ -53,12 +52,13 @@ type Tor struct { // default instance with no fields set is the default used for Start. type StartConf struct { // ExePath is the path to the Tor executable. If it is not present, "tor" is - // used either locally or on the PATH. + // used either locally or on the PATH. This is ignored if ProcessCreator is + // set. ExePath string - // Embedded is true if Tor is statically compiled. If true, ExePath is - // ignored. - Embedded bool + // ProcessCreator is the override to use a specific process creator. If set, + // ExePath is ignored. + ProcessCreator process.Creator // ControlPort is the port to use for the Tor controller. If it is 0, Tor // picks a port for use. @@ -159,10 +159,8 @@ func Start(ctx context.Context, conf *StartConf) (*Tor, error) { func (t *Tor) startProcess(ctx context.Context, conf *StartConf) error { // Get the creator - var creator process.Creator - if conf.Embedded { - creator = embedded.NewCreator() - } else { + creator := conf.ProcessCreator + if creator == nil { torPath := conf.ExePath if torPath == "" { torPath = "tor"