2018-05-13 04:58:15 +00:00
|
|
|
package tor
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2018-05-14 18:11:18 +00:00
|
|
|
"fmt"
|
2018-05-13 04:58:15 +00:00
|
|
|
"io"
|
2018-05-14 18:11:18 +00:00
|
|
|
"io/ioutil"
|
|
|
|
"net/textproto"
|
|
|
|
"os"
|
2018-05-15 21:49:57 +00:00
|
|
|
"path/filepath"
|
2018-05-14 18:11:18 +00:00
|
|
|
"strconv"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/cretz/bine/control"
|
2018-05-14 20:18:32 +00:00
|
|
|
"github.com/cretz/bine/process/embedded"
|
2018-05-14 18:11:18 +00:00
|
|
|
|
|
|
|
"github.com/cretz/bine/process"
|
2018-05-13 04:58:15 +00:00
|
|
|
)
|
|
|
|
|
2018-05-14 20:36:29 +00:00
|
|
|
// Tor is the wrapper around the Tor process and control port connection. It
|
|
|
|
// should be created with Start and developers should always call Close when
|
|
|
|
// done.
|
2018-05-13 04:58:15 +00:00
|
|
|
type Tor struct {
|
2018-05-14 20:18:32 +00:00
|
|
|
// Process is the Tor instance that is running.
|
2018-05-14 18:11:18 +00:00
|
|
|
Process process.Process
|
2018-05-14 20:18:32 +00:00
|
|
|
|
|
|
|
// Control is the Tor controller connection.
|
2018-05-14 18:11:18 +00:00
|
|
|
Control *control.Conn
|
|
|
|
|
2018-05-14 20:36:29 +00:00
|
|
|
// ProcessCancelFunc is the context cancellation func for the Tor process.
|
|
|
|
// It is used by Close and should not be called directly. This can be nil.
|
2018-05-14 20:18:32 +00:00
|
|
|
ProcessCancelFunc context.CancelFunc
|
|
|
|
|
|
|
|
// ControlPort is the port that Control is connected on.
|
|
|
|
ControlPort int
|
|
|
|
|
|
|
|
// DataDir is the path to the data directory that Tor is using.
|
|
|
|
DataDir string
|
|
|
|
|
2018-05-14 20:36:29 +00:00
|
|
|
// DeleteDataDirOnClose is true if, when Close is invoked, the entire
|
|
|
|
// directory will be deleted.
|
2018-05-14 18:11:18 +00:00
|
|
|
DeleteDataDirOnClose bool
|
2018-05-14 20:18:32 +00:00
|
|
|
|
2018-05-14 20:36:29 +00:00
|
|
|
// DebugWriter is the writer used for debug logs, or nil if debug logs
|
|
|
|
// should not be emitted.
|
2018-05-14 20:18:32 +00:00
|
|
|
DebugWriter io.Writer
|
|
|
|
|
|
|
|
// StopProcessOnClose, if true, will attempt to halt the process on close.
|
|
|
|
StopProcessOnClose bool
|
2018-05-13 04:58:15 +00:00
|
|
|
}
|
|
|
|
|
2018-05-14 20:36:29 +00:00
|
|
|
// StartConf is the configuration used for Start when starting a Tor instance. A
|
|
|
|
// default instance with no fields set is the default used for Start.
|
2018-05-13 04:58:15 +00:00
|
|
|
type StartConf struct {
|
2018-05-14 20:36:29 +00:00
|
|
|
// ExePath is the path to the Tor executable. If it is not present, "tor" is
|
|
|
|
// used either locally or on the PATH.
|
2018-05-13 04:58:15 +00:00
|
|
|
ExePath string
|
2018-05-14 20:18:32 +00:00
|
|
|
|
2018-05-14 20:36:29 +00:00
|
|
|
// Embedded is true if Tor is statically compiled. If true, ExePath is
|
|
|
|
// ignored.
|
2018-05-13 04:58:15 +00:00
|
|
|
Embedded bool
|
2018-05-14 20:18:32 +00:00
|
|
|
|
2018-05-14 20:36:29 +00:00
|
|
|
// ControlPort is the port to use for the Tor controller. If it is 0, Tor
|
|
|
|
// picks a port for use.
|
2018-05-13 04:58:15 +00:00
|
|
|
ControlPort int
|
2018-05-14 20:18:32 +00:00
|
|
|
|
2018-05-14 20:36:29 +00:00
|
|
|
// DataDir is the directory used by Tor. If it is empty, a temporary
|
|
|
|
// directory is created in TempDataDirBase.
|
2018-05-13 04:58:15 +00:00
|
|
|
DataDir string
|
2018-05-14 20:18:32 +00:00
|
|
|
|
2018-05-14 20:36:29 +00:00
|
|
|
// TempDataDirBase is the parent directory that a temporary data directory
|
|
|
|
// will be created under for use by Tor. This is ignored if DataDir is not
|
|
|
|
// empty. If empty it is assumed to be the current working directory.
|
2018-05-14 20:18:32 +00:00
|
|
|
TempDataDirBase string
|
|
|
|
|
2018-05-14 20:36:29 +00:00
|
|
|
// RetainTempDataDir, if true, will not set the created temporary data
|
|
|
|
// directory to be deleted on close. This is ignored if DataDir is not
|
|
|
|
// empty.
|
2018-05-14 20:18:32 +00:00
|
|
|
RetainTempDataDir bool
|
|
|
|
|
2018-05-14 20:36:29 +00:00
|
|
|
// DisableCookieAuth, if true, will not use the default SAFECOOKIE
|
|
|
|
// authentication mechanism for the Tor controller.
|
2018-05-14 18:11:18 +00:00
|
|
|
DisableCookieAuth bool
|
2018-05-14 20:18:32 +00:00
|
|
|
|
|
|
|
// DisableEagerAuth, if true, will not authenticate on Start.
|
2018-05-14 18:11:18 +00:00
|
|
|
DisableEagerAuth bool
|
2018-05-14 20:18:32 +00:00
|
|
|
|
|
|
|
// EnableNetwork, if true, will connect to the wider Tor network on start.
|
2018-05-14 18:11:18 +00:00
|
|
|
EnableNetwork bool
|
2018-05-14 20:18:32 +00:00
|
|
|
|
2018-05-14 20:36:29 +00:00
|
|
|
// ExtraArgs is the set of extra args passed to the Tor instance when
|
|
|
|
// started.
|
2018-05-13 04:58:15 +00:00
|
|
|
ExtraArgs []string
|
2018-05-14 20:18:32 +00:00
|
|
|
|
2018-05-14 20:36:29 +00:00
|
|
|
// TorrcFile is the torrc file to on start. If empty, a blank torrc is
|
|
|
|
// created in the data directory and is used instead.
|
2018-05-14 18:11:18 +00:00
|
|
|
TorrcFile string
|
2018-05-14 20:18:32 +00:00
|
|
|
|
2018-05-14 20:36:29 +00:00
|
|
|
// DebugWriter is the writer to use for debug logs, or nil for no debug
|
|
|
|
// logs.
|
2018-05-13 04:58:15 +00:00
|
|
|
DebugWriter io.Writer
|
2018-05-15 21:49:57 +00:00
|
|
|
|
|
|
|
// NoHush if true does not set --hush. By default --hush is set.
|
|
|
|
NoHush bool
|
2018-05-16 15:26:11 +00:00
|
|
|
|
|
|
|
// NoAutoSocksPort if true does not set "--SocksPort auto" as is done by
|
|
|
|
// default. This means the caller could set their own or just let it
|
|
|
|
// default to 9050.
|
|
|
|
NoAutoSocksPort bool
|
2018-05-13 04:58:15 +00:00
|
|
|
}
|
|
|
|
|
2018-05-14 20:36:29 +00:00
|
|
|
// Start a Tor instance and connect to it. If ctx is nil, context.Background()
|
|
|
|
// is used. If conf is nil, a default instance is used.
|
2018-05-14 18:11:18 +00:00
|
|
|
func Start(ctx context.Context, conf *StartConf) (*Tor, error) {
|
|
|
|
if ctx == nil {
|
|
|
|
ctx = context.Background()
|
|
|
|
}
|
|
|
|
if conf == nil {
|
|
|
|
conf = &StartConf{}
|
|
|
|
}
|
2018-05-14 20:18:32 +00:00
|
|
|
tor := &Tor{DataDir: conf.DataDir, DebugWriter: conf.DebugWriter, StopProcessOnClose: true}
|
2018-05-15 21:49:57 +00:00
|
|
|
// Create the data dir and make it absolute
|
2018-05-14 18:11:18 +00:00
|
|
|
if tor.DataDir == "" {
|
|
|
|
tempBase := conf.TempDataDirBase
|
|
|
|
if tempBase == "" {
|
|
|
|
tempBase = "."
|
|
|
|
}
|
|
|
|
var err error
|
2018-05-15 21:49:57 +00:00
|
|
|
if tempBase, err = filepath.Abs(tempBase); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-05-14 18:11:18 +00:00
|
|
|
if tor.DataDir, err = ioutil.TempDir(tempBase, "data-dir-"); err != nil {
|
|
|
|
return nil, fmt.Errorf("Unable to create temp data dir: %v", err)
|
|
|
|
}
|
|
|
|
tor.Debugf("Created temp data directory at: %v", tor.DataDir)
|
|
|
|
tor.DeleteDataDirOnClose = !conf.RetainTempDataDir
|
|
|
|
} else if err := os.MkdirAll(tor.DataDir, 0600); err != nil {
|
|
|
|
return nil, fmt.Errorf("Unable to create data dir: %v", err)
|
|
|
|
}
|
|
|
|
// From this point on, we must close tor if we error
|
|
|
|
// Start tor
|
|
|
|
err := tor.startProcess(ctx, conf)
|
|
|
|
// Connect the controller
|
|
|
|
if err == nil {
|
|
|
|
err = tor.connectController(ctx, conf)
|
|
|
|
}
|
|
|
|
// Attempt eager auth w/ no password
|
|
|
|
if err == nil && !conf.DisableEagerAuth {
|
|
|
|
err = tor.Control.Authenticate("")
|
|
|
|
}
|
|
|
|
// If there was an error, we have to try to close here but it may leave the process open
|
|
|
|
if err != nil {
|
|
|
|
if closeErr := tor.Close(); closeErr != nil {
|
|
|
|
err = fmt.Errorf("Error on start: %v (also got error trying to close: %v)", err, closeErr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return tor, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Tor) startProcess(ctx context.Context, conf *StartConf) error {
|
|
|
|
// Get the creator
|
2018-05-14 19:47:05 +00:00
|
|
|
var creator process.Creator
|
2018-05-14 18:11:18 +00:00
|
|
|
if conf.Embedded {
|
2018-05-14 20:18:32 +00:00
|
|
|
creator = embedded.NewCreator()
|
2018-05-14 18:11:18 +00:00
|
|
|
} else {
|
|
|
|
torPath := conf.ExePath
|
|
|
|
if torPath == "" {
|
|
|
|
torPath = "tor"
|
|
|
|
}
|
2018-05-14 19:47:05 +00:00
|
|
|
creator = process.NewCreator(torPath)
|
2018-05-14 18:11:18 +00:00
|
|
|
}
|
|
|
|
// Build the args
|
|
|
|
args := []string{"--DataDirectory", t.DataDir}
|
|
|
|
if !conf.DisableCookieAuth {
|
|
|
|
args = append(args, "--CookieAuthentication", "1")
|
|
|
|
}
|
|
|
|
if !conf.EnableNetwork {
|
|
|
|
args = append(args, "--DisableNetwork", "1")
|
|
|
|
}
|
2018-05-15 21:49:57 +00:00
|
|
|
if !conf.NoHush {
|
|
|
|
args = append(args, "--hush")
|
|
|
|
}
|
2018-05-16 15:26:11 +00:00
|
|
|
if !conf.NoAutoSocksPort {
|
|
|
|
args = append(args, "--SocksPort", "auto")
|
|
|
|
}
|
2018-05-14 18:11:18 +00:00
|
|
|
// If there is no Torrc file, create a blank temp one
|
|
|
|
torrcFileName := conf.TorrcFile
|
|
|
|
if torrcFileName == "" {
|
|
|
|
torrcFile, err := ioutil.TempFile(t.DataDir, "torrc-")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
torrcFileName = torrcFile.Name()
|
|
|
|
if err = torrcFile.Close(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
args = append(args, "-f", torrcFileName)
|
|
|
|
// Create file for Tor to write the control port to if it's not told to us
|
|
|
|
var controlPortFileName string
|
|
|
|
var err error
|
|
|
|
if conf.ControlPort == 0 {
|
|
|
|
controlPortFile, err := ioutil.TempFile(t.DataDir, "control-port-")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
controlPortFileName = controlPortFile.Name()
|
|
|
|
if err = controlPortFile.Close(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
args = append(args, "--ControlPort", "auto", "--ControlPortWriteToFile", controlPortFile.Name())
|
|
|
|
}
|
|
|
|
// Start process with the args
|
|
|
|
var processCtx context.Context
|
|
|
|
processCtx, t.ProcessCancelFunc = context.WithCancel(ctx)
|
|
|
|
args = append(args, conf.ExtraArgs...)
|
|
|
|
p, err := creator.New(processCtx, args...)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
t.Debugf("Starting tor with args %v", args)
|
|
|
|
if err = p.Start(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
t.Process = p
|
|
|
|
// Try a few times to read the control port file if we need to
|
|
|
|
t.ControlPort = conf.ControlPort
|
|
|
|
if t.ControlPort == 0 {
|
|
|
|
ControlPortCheck:
|
|
|
|
for i := 0; i < 10; i++ {
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
err = ctx.Err()
|
|
|
|
break ControlPortCheck
|
|
|
|
default:
|
|
|
|
// Try to read the controlport file, or wait a bit
|
|
|
|
var byts []byte
|
|
|
|
if byts, err = ioutil.ReadFile(controlPortFileName); err != nil {
|
|
|
|
break ControlPortCheck
|
|
|
|
} else if t.ControlPort, err = process.ControlPortFromFileContents(string(byts)); err == nil {
|
|
|
|
break ControlPortCheck
|
|
|
|
}
|
|
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Unable to read control port file: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
2018-05-13 04:58:15 +00:00
|
|
|
}
|
|
|
|
|
2018-05-14 18:11:18 +00:00
|
|
|
func (t *Tor) connectController(ctx context.Context, conf *StartConf) error {
|
|
|
|
t.Debugf("Connecting to control port %v", t.ControlPort)
|
|
|
|
textConn, err := textproto.Dial("tcp", "127.0.0.1:"+strconv.Itoa(t.ControlPort))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
t.Control = control.NewConn(textConn)
|
|
|
|
t.Control.DebugWriter = t.DebugWriter
|
|
|
|
return nil
|
2018-05-13 04:58:15 +00:00
|
|
|
}
|
|
|
|
|
2018-05-15 19:37:18 +00:00
|
|
|
// EnableNetwork sets DisableNetwork to 0 and optionally waits for bootstrap to
|
|
|
|
// complete. The context can be nil. If DisableNetwork isnt 1, this does
|
|
|
|
// nothing.
|
|
|
|
func (t *Tor) EnableNetwork(ctx context.Context, wait bool) error {
|
|
|
|
if ctx == nil {
|
|
|
|
ctx = context.Background()
|
|
|
|
}
|
|
|
|
// Only enable if DisableNetwork is 1
|
|
|
|
if vals, err := t.Control.GetConf("DisableNetwork"); err != nil {
|
|
|
|
return err
|
|
|
|
} else if len(vals) == 0 || vals[0].Key != "DisableNetwork" || vals[0].Val != "1" {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
// Enable the network
|
|
|
|
if err := t.Control.SetConf(control.KeyVals("DisableNetwork", "0")...); err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
// If not waiting, leave
|
|
|
|
if !wait {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
// Wait for progress to hit 100
|
|
|
|
_, err := t.Control.EventWait(ctx, []control.EventCode{control.EventCodeStatusClient},
|
|
|
|
func(evt control.Event) (bool, error) {
|
|
|
|
if status, _ := evt.(*control.StatusEvent); status != nil && status.Action == "BOOTSTRAP" {
|
|
|
|
if status.Severity == "NOTICE" && status.Arguments["PROGRESS"] == "100" {
|
|
|
|
return true, nil
|
|
|
|
} else if status.Severity != "NOTICE" {
|
|
|
|
return false, fmt.Errorf("Failing bootstrapping, Tor warning: %v", status.Arguments["WARNING"])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false, nil
|
|
|
|
})
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-05-14 20:36:29 +00:00
|
|
|
// Close sends a halt to the Tor process if it can, closes the controller
|
|
|
|
// connection, and stops the process.
|
2018-05-14 18:11:18 +00:00
|
|
|
func (t *Tor) Close() error {
|
2018-05-15 19:37:18 +00:00
|
|
|
t.Debugf("Closing Tor")
|
2018-05-14 18:11:18 +00:00
|
|
|
errs := []error{}
|
|
|
|
// If controller is authenticated, send the quit signal to the process. Otherwise, just close the controller.
|
|
|
|
sentHalt := false
|
|
|
|
if t.Control != nil {
|
2018-05-14 20:18:32 +00:00
|
|
|
if t.Control.Authenticated && t.StopProcessOnClose {
|
2018-05-14 18:11:18 +00:00
|
|
|
if err := t.Control.Signal("HALT"); err != nil {
|
|
|
|
errs = append(errs, fmt.Errorf("Unable to signal halt: %v", err))
|
|
|
|
} else {
|
|
|
|
sentHalt = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Now close the controller
|
|
|
|
if err := t.Control.Close(); err != nil {
|
|
|
|
errs = append(errs, fmt.Errorf("Unable to close contrlller: %v", err))
|
|
|
|
} else {
|
|
|
|
t.Control = nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if t.Process != nil {
|
|
|
|
// If we didn't halt, we have to force kill w/ the cancel func
|
2018-05-14 20:18:32 +00:00
|
|
|
if !sentHalt && t.StopProcessOnClose {
|
2018-05-14 18:11:18 +00:00
|
|
|
t.ProcessCancelFunc()
|
|
|
|
}
|
|
|
|
// Wait for a bit to make sure it stopped
|
|
|
|
errCh := make(chan error, 1)
|
|
|
|
var waitErr error
|
|
|
|
go func() { errCh <- t.Process.Wait() }()
|
|
|
|
select {
|
|
|
|
case waitErr = <-errCh:
|
|
|
|
if waitErr != nil {
|
|
|
|
errs = append(errs, fmt.Errorf("Process wait failed: %v", waitErr))
|
|
|
|
}
|
|
|
|
case <-time.After(300 * time.Millisecond):
|
|
|
|
errs = append(errs, fmt.Errorf("Process did not exit after 300 ms"))
|
|
|
|
}
|
|
|
|
if waitErr == nil {
|
|
|
|
t.Process = nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Get rid of the entire data dir
|
|
|
|
if t.DeleteDataDirOnClose {
|
|
|
|
if err := os.RemoveAll(t.DataDir); err != nil {
|
|
|
|
errs = append(errs, fmt.Errorf("Failed to remove data dir %v: %v", t.DataDir, err))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Combine the errors if present
|
|
|
|
if len(errs) == 0 {
|
|
|
|
return nil
|
|
|
|
} else if len(errs) == 1 {
|
2018-05-15 19:37:18 +00:00
|
|
|
t.Debugf("Error while closing Tor: %v", errs[0])
|
2018-05-14 18:11:18 +00:00
|
|
|
return errs[0]
|
|
|
|
}
|
2018-05-15 19:37:18 +00:00
|
|
|
t.Debugf("Errors while closing Tor: %v", errs)
|
2018-05-14 18:11:18 +00:00
|
|
|
return fmt.Errorf("Got %v errors while closing - %v", len(errs), errs)
|
2018-05-13 04:58:15 +00:00
|
|
|
}
|