summaryrefslogtreecommitdiff
path: root/vendor/github.com/cretz/bine/tor
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/cretz/bine/tor')
-rw-r--r--vendor/github.com/cretz/bine/tor/dialer.go111
-rw-r--r--vendor/github.com/cretz/bine/tor/doc.go7
-rw-r--r--vendor/github.com/cretz/bine/tor/listen.go343
-rw-r--r--vendor/github.com/cretz/bine/tor/log.go16
-rw-r--r--vendor/github.com/cretz/bine/tor/tor.go453
5 files changed, 930 insertions, 0 deletions
diff --git a/vendor/github.com/cretz/bine/tor/dialer.go b/vendor/github.com/cretz/bine/tor/dialer.go
new file mode 100644
index 0000000..9ef211e
--- /dev/null
+++ b/vendor/github.com/cretz/bine/tor/dialer.go
@@ -0,0 +1,111 @@
+package tor
+
+import (
+ "context"
+ "fmt"
+ "net"
+ "strings"
+
+ "golang.org/x/net/proxy"
+)
+
+// Dialer is a wrapper around a proxy.Dialer for dialing connections.
+type Dialer struct {
+ proxy.Dialer
+}
+
+// DialConf is the configuration used for Dialer.
+type DialConf struct {
+ // ProxyAddress is the address for the SOCKS5 proxy. If empty, it is looked
+ // up.
+ ProxyAddress string
+
+ // ProxyNetwork is the network for the SOCKS5 proxy. If ProxyAddress is
+ // empty, this value is ignored and overridden by what is looked up. If this
+ // is empty and ProxyAddress is not empty, it defaults to "tcp".
+ ProxyNetwork string
+
+ // ProxyAuth is the auth for the proxy. Since Tor's SOCKS5 proxy is
+ // unauthenticated, this is rarely needed. It can be used when
+ // IsolateSOCKSAuth is set to ensure separate circuits.
+ //
+ // This should not be confused with downstream SOCKS proxy authentication
+ // which is set via Tor values for Socks5ProxyUsername and
+ // Socks5ProxyPassword when Socks5Proxy is set.
+ ProxyAuth *proxy.Auth
+
+ // SkipEnableNetwork, if true, will skip the enable network step in Dialer.
+ SkipEnableNetwork bool
+
+ // Forward is the dialer to forward to. If nil, just uses normal net dialer.
+ Forward proxy.Dialer
+}
+
+// Dialer creates a new Dialer for the given configuration. Context can be nil.
+// If conf is nil, a default is used.
+func (t *Tor) Dialer(ctx context.Context, conf *DialConf) (*Dialer, error) {
+ if ctx == nil {
+ ctx = context.Background()
+ }
+ if conf == nil {
+ conf = &DialConf{}
+ }
+ // Enable the network if requested
+ if !conf.SkipEnableNetwork {
+ if err := t.EnableNetwork(ctx, true); err != nil {
+ return nil, err
+ }
+ }
+ // Lookup proxy address as needed
+ proxyNetwork := conf.ProxyNetwork
+ proxyAddress := conf.ProxyAddress
+ if proxyAddress == "" {
+ info, err := t.Control.GetInfo("net/listeners/socks")
+ if err != nil {
+ return nil, err
+ }
+ if len(info) != 1 || info[0].Key != "net/listeners/socks" {
+ return nil, fmt.Errorf("Unable to get socks proxy address")
+ }
+ proxyAddress = info[0].Val
+ if strings.HasPrefix(proxyAddress, "unix:") {
+ proxyAddress = proxyAddress[5:]
+ proxyNetwork = "unix"
+ } else {
+ proxyNetwork = "tcp"
+ }
+ } else if proxyNetwork == "" {
+ proxyNetwork = "tcp"
+ }
+
+ dialer, err := proxy.SOCKS5(proxyNetwork, proxyAddress, conf.ProxyAuth, conf.Forward)
+ if err != nil {
+ return nil, err
+ }
+ return &Dialer{dialer}, nil
+}
+
+// DialContext is the equivalent of net.DialContext.
+//
+// TODO: Remove when https://github.com/golang/go/issues/17759 is released.
+func (d *Dialer) DialContext(ctx context.Context, network string, addr string) (net.Conn, error) {
+ errCh := make(chan error, 1)
+ connCh := make(chan net.Conn, 1)
+ go func() {
+ if conn, err := d.Dial(network, addr); err != nil {
+ errCh <- err
+ } else if ctx.Err() != nil {
+ conn.Close()
+ } else {
+ connCh <- conn
+ }
+ }()
+ select {
+ case err := <-errCh:
+ return nil, err
+ case conn := <-connCh:
+ return conn, nil
+ case <-ctx.Done():
+ return nil, ctx.Err()
+ }
+}
diff --git a/vendor/github.com/cretz/bine/tor/doc.go b/vendor/github.com/cretz/bine/tor/doc.go
new file mode 100644
index 0000000..a58086a
--- /dev/null
+++ b/vendor/github.com/cretz/bine/tor/doc.go
@@ -0,0 +1,7 @@
+// Package tor is the high-level client for Tor.
+//
+// The Tor type is a combination of a Tor instance and a connection to it.
+// Use Start to create Tor. Then Dialer or Listener can be used.
+//
+// Some of this code is lifted from https://github.com/yawning/bulb with thanks.
+package tor
diff --git a/vendor/github.com/cretz/bine/tor/listen.go b/vendor/github.com/cretz/bine/tor/listen.go
new file mode 100644
index 0000000..6b142d2
--- /dev/null
+++ b/vendor/github.com/cretz/bine/tor/listen.go
@@ -0,0 +1,343 @@
+package tor
+
+import (
+ "context"
+ "crypto"
+ "crypto/rsa"
+ "fmt"
+ "net"
+ "strconv"
+
+ "github.com/cretz/bine/control"
+ "github.com/cretz/bine/torutil/ed25519"
+ othered25519 "golang.org/x/crypto/ed25519"
+)
+
+// OnionService implements net.Listener and net.Addr for an onion service.
+type OnionService struct {
+ // ID is the service ID for this onion service.
+ ID string
+
+ // Key is the private key for this service. It is either the set key, the
+ // generated key, or nil if asked to discard the key. If present, it is
+ // *crypto/rsa.PrivateKey (1024 bit) when Version3 is false or
+ // github.com/cretz/bine/torutil/ed25519.KeyPair when Version3 is true.
+ Key crypto.PrivateKey
+
+ // Version3 says whether or not this service is a V3 service.
+ Version3 bool
+
+ // ClientAuths is the credential set for clients. The keys are username and
+ // the values are credentials. The credentials will always be present even
+ // if Tor had to generate them.
+ ClientAuths map[string]string
+
+ // LocalListener is the local TCP listener. This is always present.
+ LocalListener net.Listener
+
+ // RemotePorts is the set of remote ports that are forwarded to the local
+ // listener. This will always have at least one value.
+ RemotePorts []int
+
+ // CloseLocalListenerOnClose is true if the local listener should be closed
+ // on Close. This is set to true if a listener was created by Listen and set
+ // to false of an existing LocalListener was provided to Listen.
+ CloseLocalListenerOnClose bool
+
+ // The Tor object that created this. Needed for Close.
+ Tor *Tor
+}
+
+// ListenConf is the configuration for Listen calls.
+type ListenConf struct {
+ // LocalPort is the local port to create a TCP listener on. If the port is
+ // 0, it is automatically chosen. This is ignored if LocalListener is set.
+ LocalPort int
+
+ // LocalListener is the specific local listener to back the onion service.
+ // If this is nil (the default), then a listener is created with LocalPort.
+ LocalListener net.Listener
+
+ // RemotePorts are the remote ports to serve the onion service on. If empty,
+ // it is the same as the local port or local listener. This must have at
+ // least one value if the local listener is not a *net.TCPListener.
+ RemotePorts []int
+
+ // Key is the private key to use. If not present, a key is generated based
+ // on whether Version3 is true or false. If present, it must be a
+ // *crypto/rsa.PrivateKey (1024 bit), a
+ // github.com/cretz/bine/torutil/ed25519.KeyPair, a
+ // golang.org/x/crypto/ed25519.PrivateKey, or a
+ // github.com/cretz/bine/control.Key.
+ Key crypto.PrivateKey
+
+ // Version3 determines whether, when Key is nil, a version 2 or version 3
+ // service/key will be generated. If true it is version 3 (an ed25519 key
+ // and v3 onion service) and if false it is version 2 (a RSA-1024 key and v2
+ // onion service). If Key is not nil, this value is ignored.
+ Version3 bool
+
+ // ClientAuths is the set of usernames and credentials for client
+ // authentication. The keys are usernames and the values are credentials. If
+ // a username is present but the credential is empty, a credential is
+ // generated by Tor for that user. If this is empty there is no
+ // authentication.
+ ClientAuths map[string]string
+
+ // MaxStreams is the maximum number of streams the service will accept. 0
+ // means unlimited.
+ MaxStreams int
+
+ // DiscardKey, if true and Key is nil (meaning a private key is generated),
+ // tells Tor not to return the generated private key. This value is ignored
+ // if Key is not nil.
+ DiscardKey bool
+
+ // Detach, if true, prevents the default behavior of the onion service being
+ // deleted when this controller connection is closed.
+ Detach bool
+
+ // NonAnonymous must be true if Tor options HiddenServiceSingleHopMode and
+ // HiddenServiceNonAnonymousMode are set. Otherwise, it must be false.
+ NonAnonymous bool
+
+ // MaxStreamsCloseCircuit determines whether to close the circuit when the
+ // maximum number of streams is exceeded. If true, the circuit is closed. If
+ // false, the stream is simply not connected but the circuit stays open.
+ MaxStreamsCloseCircuit bool
+
+ // NoWait if true will not wait until the onion service is published. If
+ // false, the network will be enabled if it's not and then we will wait
+ // until the onion service is published.
+ NoWait bool
+}
+
+// Listen creates an onion service and local listener. The context can be nil.
+// If conf is nil, the default struct value is used. Note, if this errors, any
+// listeners created here are closed but if a LocalListener is provided it may remain open.
+func (t *Tor) Listen(ctx context.Context, conf *ListenConf) (*OnionService, error) {
+ if ctx == nil {
+ ctx = context.Background()
+ }
+ // Create the service up here and make sure we close it no matter the error within
+ svc := &OnionService{Tor: t, CloseLocalListenerOnClose: conf.LocalListener == nil}
+ var err error
+
+ // Create the local listener if necessary
+ svc.LocalListener = conf.LocalListener
+ if svc.LocalListener == nil {
+ if svc.LocalListener, err = net.Listen("tcp", "127.0.0.1:"+strconv.Itoa(conf.LocalPort)); err != nil {
+ return nil, err
+ }
+ }
+
+ // Henceforth, any error requires we close the svc
+
+ // Build the onion request
+ req := &control.AddOnionRequest{MaxStreams: conf.MaxStreams, ClientAuths: conf.ClientAuths}
+ // Set flags
+ if conf.DiscardKey {
+ req.Flags = append(req.Flags, "DiscardPK")
+ }
+ if conf.Detach {
+ req.Flags = append(req.Flags, "Detach")
+ }
+ if len(conf.ClientAuths) > 0 {
+ req.Flags = append(req.Flags, "BasicAuth")
+ }
+ if conf.NonAnonymous {
+ req.Flags = append(req.Flags, "NonAnonymous")
+ }
+ if conf.MaxStreamsCloseCircuit {
+ req.Flags = append(req.Flags, "MaxStreamsCloseCircuit")
+ }
+ // Set the key
+ switch key := conf.Key.(type) {
+ case nil:
+ svc.Version3 = conf.Version3
+ if conf.Version3 {
+ req.Key = control.GenKey(control.KeyAlgoED25519V3)
+ } else {
+ req.Key = control.GenKey(control.KeyAlgoRSA1024)
+ }
+ case control.GenKey:
+ svc.Version3 = conf.Version3
+ req.Key = key
+ case *rsa.PrivateKey:
+ svc.Key = key
+ svc.Version3 = false
+ if key.N == nil || key.N.BitLen() != 1024 {
+ err = fmt.Errorf("RSA key must be 1024 bits")
+ } else {
+ req.Key = &control.RSAKey{PrivateKey: key}
+ }
+ case *control.RSAKey:
+ svc.Key = key.PrivateKey
+ svc.Version3 = false
+ if key.N == nil || key.N.BitLen() != 1024 {
+ err = fmt.Errorf("RSA key must be 1024 bits")
+ } else {
+ req.Key = key
+ }
+ case ed25519.KeyPair:
+ svc.Key = key
+ svc.Version3 = true
+ req.Key = &control.ED25519Key{key}
+ case othered25519.PrivateKey:
+ properKey := ed25519.FromCryptoPrivateKey(key)
+ svc.Key = properKey
+ svc.Version3 = true
+ req.Key = &control.ED25519Key{properKey}
+ case *control.ED25519Key:
+ svc.Key = key.KeyPair
+ svc.Version3 = true
+ req.Key = key
+ default:
+ err = fmt.Errorf("Unrecognized key type: %T", key)
+ }
+
+ // Apply the remote ports
+ if err == nil {
+ if len(conf.RemotePorts) == 0 {
+ tcpAddr, ok := svc.LocalListener.Addr().(*net.TCPAddr)
+ if !ok {
+ err = fmt.Errorf("Unable to derive local TCP port")
+ } else {
+ svc.RemotePorts = []int{tcpAddr.Port}
+ }
+ } else {
+ svc.RemotePorts = make([]int, len(conf.RemotePorts))
+ copy(svc.RemotePorts, conf.RemotePorts)
+ }
+ }
+ // Apply the local ports with the remote ports
+ if err == nil {
+ localAddr := svc.LocalListener.Addr().String()
+ if _, ok := svc.LocalListener.(*net.UnixListener); ok {
+ localAddr = "unix:" + localAddr
+ }
+ for _, remotePort := range svc.RemotePorts {
+ req.Ports = append(req.Ports, &control.KeyVal{Key: strconv.Itoa(remotePort), Val: localAddr})
+ }
+ }
+
+ // Create the onion service
+ var resp *control.AddOnionResponse
+ if err == nil {
+ resp, err = t.Control.AddOnion(req)
+ }
+
+ // Apply the response to the service
+ if err == nil {
+ svc.ID = resp.ServiceID
+ switch key := resp.Key.(type) {
+ case nil:
+ // Do nothing
+ case *control.RSAKey:
+ svc.Key = key.PrivateKey
+ case *control.ED25519Key:
+ svc.Key = key.KeyPair
+ default:
+ err = fmt.Errorf("Unrecognized result key type: %T", key)
+ }
+ // Client auths are the conf and then overridden by results
+ if len(conf.ClientAuths) > 0 {
+ svc.ClientAuths = make(map[string]string, len(conf.ClientAuths))
+ for k, v := range conf.ClientAuths {
+ svc.ClientAuths[k] = v
+ }
+ for k, v := range resp.ClientAuths {
+ svc.ClientAuths[k] = v
+ }
+ }
+ }
+
+ // Wait if necessary
+ if err == nil && !conf.NoWait {
+ t.Debugf("Enabling network before waiting for publication")
+ // First make sure network is enabled
+ if err = t.EnableNetwork(ctx, true); err == nil {
+ t.Debugf("Waiting for publication")
+ // Now we'll take a similar approach to Stem. Several UPLOADs are sent out, so we count em. If we see
+ // UPLOADED, we succeeded. If we see failed, we count those. If there are as many failures as uploads, they
+ // all failed and it's a failure. NOTE: unlike Stem's comments that say they don't, we are actually seeing
+ // the service IDs for UPLOADED so we don't keep a map.
+ uploadsAttempted := 0
+ failures := []string{}
+ _, err = t.Control.EventWait(ctx, []control.EventCode{control.EventCodeHSDesc},
+ func(evt control.Event) (bool, error) {
+ hs, _ := evt.(*control.HSDescEvent)
+ if hs != nil && hs.Address == svc.ID {
+ switch hs.Action {
+ case "UPLOAD":
+ uploadsAttempted++
+ case "FAILED":
+ failures = append(failures,
+ fmt.Sprintf("Failed uploading to dir %v - reason: %v", hs.HSDir, hs.Reason))
+ if len(failures) == uploadsAttempted {
+ return false, fmt.Errorf("Failed all uploads, reasons: %v", failures)
+ }
+ case "UPLOADED":
+ return true, nil
+ }
+ }
+ return false, nil
+ })
+ }
+ }
+
+ // Give back err and close if there is an err
+ if err != nil {
+ if closeErr := svc.Close(); closeErr != nil {
+ err = fmt.Errorf("Error on listen: %v (also got error trying to close: %v)", err, closeErr)
+ }
+ return nil, err
+ }
+ return svc, nil
+}
+
+// Accept implements net.Listener.Accept.
+func (o *OnionService) Accept() (net.Conn, error) {
+ return o.LocalListener.Accept()
+}
+
+// Addr implements net.Listener.Addr just returning this object.
+func (o *OnionService) Addr() net.Addr {
+ return o
+}
+
+// Network implements net.Addr.Network always returning "tcp".
+func (o *OnionService) Network() string {
+ return "tcp"
+}
+
+// String implements net.Addr.String and returns "<serviceID>.onion:<virtport>".
+func (o *OnionService) String() string {
+ return fmt.Sprintf("%v.onion:%v", o.ID, o.RemotePorts[0])
+}
+
+// Close implements net.Listener.Close and deletes the onion service and closes
+// the LocalListener if CloseLocalListenerOnClose is true.
+func (o *OnionService) Close() (err error) {
+ o.Tor.Debugf("Closing onion %v", o)
+ // Delete the onion first
+ if o.ID != "" {
+ err = o.Tor.Control.DelOnion(o.ID)
+ o.ID = ""
+ }
+ // Now if the local one needs to be closed, do it
+ if o.CloseLocalListenerOnClose && o.LocalListener != nil {
+ if closeErr := o.LocalListener.Close(); closeErr != nil {
+ if err != nil {
+ err = fmt.Errorf("Unable to close onion: %v (also unable to close local listener: %v)", err, closeErr)
+ } else {
+ err = closeErr
+ }
+ }
+ o.LocalListener = nil
+ }
+ if err != nil {
+ o.Tor.Debugf("Failed closing onion: %v", err)
+ }
+ return
+}
diff --git a/vendor/github.com/cretz/bine/tor/log.go b/vendor/github.com/cretz/bine/tor/log.go
new file mode 100644
index 0000000..b53f318
--- /dev/null
+++ b/vendor/github.com/cretz/bine/tor/log.go
@@ -0,0 +1,16 @@
+package tor
+
+import "fmt"
+
+// DebugEnabled returns true if there is a DebugWriter.
+func (t *Tor) DebugEnabled() bool {
+ return t.DebugWriter != nil
+}
+
+// Debugf writes the formatted string with a newline appended to the DebugWriter
+// if present.
+func (t *Tor) Debugf(format string, args ...interface{}) {
+ if w := t.DebugWriter; w != nil {
+ fmt.Fprintf(w, format+"\n", args...)
+ }
+}
diff --git a/vendor/github.com/cretz/bine/tor/tor.go b/vendor/github.com/cretz/bine/tor/tor.go
new file mode 100644
index 0000000..0edd241
--- /dev/null
+++ b/vendor/github.com/cretz/bine/tor/tor.go
@@ -0,0 +1,453 @@
+package tor
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net/textproto"
+ "os"
+ "path/filepath"
+ "strconv"
+ "time"
+
+ "github.com/cretz/bine/control"
+
+ "github.com/cretz/bine/process"
+)
+
+// 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.
+type Tor struct {
+ // Process is the Tor instance that is running.
+ Process process.Process
+
+ // Control is the Tor controller connection.
+ Control *control.Conn
+
+ // 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.
+ ProcessCancelFunc context.CancelFunc
+
+ // ControlPort is the port that Control is connected on. It is 0 if the
+ // connection is an embedded control connection.
+ ControlPort int
+
+ // DataDir is the path to the data directory that Tor is using.
+ DataDir string
+
+ // DeleteDataDirOnClose is true if, when Close is invoked, the entire
+ // directory will be deleted.
+ DeleteDataDirOnClose bool
+
+ // DebugWriter is the writer used for debug logs, or nil if debug logs
+ // should not be emitted.
+ DebugWriter io.Writer
+
+ // StopProcessOnClose, if true, will attempt to halt the process on close.
+ StopProcessOnClose bool
+
+ // GeoIPCreatedFile is the path, relative to DataDir, that was created from
+ // StartConf.GeoIPFileReader. It is empty if no file was created.
+ GeoIPCreatedFile string
+
+ // GeoIPv6CreatedFile is the path, relative to DataDir, that was created
+ // from StartConf.GeoIPFileReader. It is empty if no file was created.
+ GeoIPv6CreatedFile string
+}
+
+// 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.
+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. This is ignored if ProcessCreator is
+ // set.
+ ExePath string
+
+ // ProcessCreator is the override to use a specific process creator. If set,
+ // ExePath is ignored.
+ ProcessCreator process.Creator
+
+ // UseEmbeddedControlConn can be set to true to use
+ // process.Process.EmbeddedControlConn() instead of creating a connection
+ // via ControlPort. Note, this only works when ProcessCreator is an
+ // embedded Tor creator with version >= 0.3.5.x.
+ UseEmbeddedControlConn bool
+
+ // ControlPort is the port to use for the Tor controller. If it is 0, Tor
+ // picks a port for use. This is ignored if UseEmbeddedControlConn is true.
+ ControlPort int
+
+ // DataDir is the directory used by Tor. If it is empty, a temporary
+ // directory is created in TempDataDirBase.
+ DataDir string
+
+ // 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.
+ TempDataDirBase string
+
+ // RetainTempDataDir, if true, will not set the created temporary data
+ // directory to be deleted on close. This is ignored if DataDir is not
+ // empty.
+ RetainTempDataDir bool
+
+ // DisableCookieAuth, if true, will not use the default SAFECOOKIE
+ // authentication mechanism for the Tor controller.
+ DisableCookieAuth bool
+
+ // DisableEagerAuth, if true, will not authenticate on Start.
+ DisableEagerAuth bool
+
+ // EnableNetwork, if true, will connect to the wider Tor network on start.
+ EnableNetwork bool
+
+ // ExtraArgs is the set of extra args passed to the Tor instance when
+ // started.
+ ExtraArgs []string
+
+ // TorrcFile is the torrc file to set on start. If empty, a blank torrc is
+ // created in the data directory and is used instead.
+ TorrcFile string
+
+ // DebugWriter is the writer to use for debug logs, or nil for no debug
+ // logs.
+ DebugWriter io.Writer
+
+ // NoHush if true does not set --hush. By default --hush is set.
+ NoHush bool
+
+ // 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
+
+ // GeoIPReader, if present, is called before start to copy geo IP files to
+ // the data directory. Errors are propagated. If the ReadCloser is present,
+ // it is copied to the data dir, overwriting as necessary, and then closed
+ // and the appropriate command line argument is added to reference it. If
+ // both the ReadCloser and error are nil, no copy or command line argument
+ // is used for that version. This is called twice, once with false and once
+ // with true for ipv6.
+ //
+ // This can be set to torutil/geoipembed.GeoIPReader to use an embedded
+ // source.
+ GeoIPFileReader func(ipv6 bool) (io.ReadCloser, error)
+}
+
+// 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.
+func Start(ctx context.Context, conf *StartConf) (*Tor, error) {
+ if ctx == nil {
+ ctx = context.Background()
+ }
+ if conf == nil {
+ conf = &StartConf{}
+ }
+ tor := &Tor{DataDir: conf.DataDir, DebugWriter: conf.DebugWriter, StopProcessOnClose: true}
+ // Create the data dir and make it absolute
+ if tor.DataDir == "" {
+ tempBase := conf.TempDataDirBase
+ if tempBase == "" {
+ tempBase = "."
+ }
+ var err error
+ if tempBase, err = filepath.Abs(tempBase); err != nil {
+ return nil, err
+ }
+ 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, 0700); err != nil {
+ return nil, fmt.Errorf("Unable to create data dir: %v", err)
+ }
+
+ // !!!! From this point on, we must close tor if we error !!!!
+
+ // Copy geoip stuff if necessary
+ err := tor.copyGeoIPFiles(conf)
+ // Start tor
+ if err == nil {
+ 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) copyGeoIPFiles(conf *StartConf) error {
+ if conf.GeoIPFileReader == nil {
+ return nil
+ }
+ if r, err := conf.GeoIPFileReader(false); err != nil {
+ return fmt.Errorf("Unable to read geoip file: %v", err)
+ } else if r != nil {
+ t.GeoIPCreatedFile = "geoip"
+ if err := createFile(filepath.Join(t.DataDir, "geoip"), r); err != nil {
+ return fmt.Errorf("Unable to create geoip file: %v", err)
+ }
+ }
+ if r, err := conf.GeoIPFileReader(true); err != nil {
+ return fmt.Errorf("Unable to read geoip6 file: %v", err)
+ } else if r != nil {
+ t.GeoIPv6CreatedFile = "geoip6"
+ if err := createFile(filepath.Join(t.DataDir, "geoip6"), r); err != nil {
+ return fmt.Errorf("Unable to create geoip6 file: %v", err)
+ }
+ }
+ return nil
+}
+
+func createFile(to string, from io.ReadCloser) error {
+ f, err := os.Create(to)
+ if err == nil {
+ _, err = io.Copy(f, from)
+ if closeErr := f.Close(); err == nil {
+ err = closeErr
+ }
+ }
+ if closeErr := from.Close(); err == nil {
+ err = closeErr
+ }
+ return err
+}
+
+func (t *Tor) startProcess(ctx context.Context, conf *StartConf) error {
+ // Get the creator
+ creator := conf.ProcessCreator
+ if creator == nil {
+ torPath := conf.ExePath
+ if torPath == "" {
+ torPath = "tor"
+ }
+ creator = process.NewCreator(torPath)
+ }
+ // Build the args
+ args := []string{"--DataDirectory", t.DataDir}
+ if !conf.DisableCookieAuth {
+ args = append(args, "--CookieAuthentication", "1")
+ }
+ if !conf.EnableNetwork {
+ args = append(args, "--DisableNetwork", "1")
+ }
+ if !conf.NoHush {
+ args = append(args, "--hush")
+ }
+ if !conf.NoAutoSocksPort {
+ args = append(args, "--SocksPort", "auto")
+ }
+ if t.GeoIPCreatedFile != "" {
+ args = append(args, "--GeoIPFile", filepath.Join(t.DataDir, t.GeoIPCreatedFile))
+ }
+ if t.GeoIPv6CreatedFile != "" {
+ args = append(args, "--GeoIPv6File", filepath.Join(t.DataDir, t.GeoIPv6CreatedFile))
+ }
+ // 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 and we're not embedded
+ var controlPortFileName string
+ var err error
+ if !conf.UseEmbeddedControlConn {
+ 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())
+ } else {
+ args = append(args, "--ControlPort", strconv.Itoa(conf.ControlPort))
+ }
+ }
+ // Create process creator with 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
+ }
+ // Use the embedded conn if requested
+ if conf.UseEmbeddedControlConn {
+ t.Debugf("Using embedded control connection")
+ conn, err := p.EmbeddedControlConn()
+ if err != nil {
+ return fmt.Errorf("Unable to get embedded control conn: %v", err)
+ }
+ t.Control = control.NewConn(textproto.NewConn(conn))
+ t.Control.DebugWriter = t.DebugWriter
+ }
+ // Start process with the args
+ t.Debugf("Starting tor with args %v", args)
+ if err = p.Start(); err != nil {
+ return err
+ }
+ t.Process = p
+ // If not embedded, try a few times to read the control port file if we need to
+ if !conf.UseEmbeddedControlConn {
+ 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
+}
+
+func (t *Tor) connectController(ctx context.Context, conf *StartConf) error {
+ // This doesn't apply if already connected (e.g. using embedded conn)
+ if t.Control != nil {
+ return nil
+ }
+ 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
+}
+
+// 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 == "ERR" {
+ return false, fmt.Errorf("Failing bootstrapping, Tor warning: %v", status.Arguments["WARNING"])
+ }
+ }
+ return false, nil
+ })
+ return err
+}
+
+// Close sends a halt to the Tor process if it can, closes the controller
+// connection, and stops the process.
+func (t *Tor) Close() error {
+ t.Debugf("Closing Tor")
+ errs := []error{}
+ // If controller is authenticated, send the quit signal to the process. Otherwise, just close the controller.
+ sentHalt := false
+ if t.Control != nil {
+ if t.Control.Authenticated && t.StopProcessOnClose {
+ 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
+ if !sentHalt && t.StopProcessOnClose {
+ 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 {
+ t.Debugf("Error while closing Tor: %v", errs[0])
+ return errs[0]
+ }
+ t.Debugf("Errors while closing Tor: %v", errs)
+ return fmt.Errorf("Got %v errors while closing - %v", len(errs), errs)
+}