diff options
Diffstat (limited to 'vendor/github.com/cretz/bine/tor')
-rw-r--r-- | vendor/github.com/cretz/bine/tor/dialer.go | 111 | ||||
-rw-r--r-- | vendor/github.com/cretz/bine/tor/doc.go | 7 | ||||
-rw-r--r-- | vendor/github.com/cretz/bine/tor/listen.go | 343 | ||||
-rw-r--r-- | vendor/github.com/cretz/bine/tor/log.go | 16 | ||||
-rw-r--r-- | vendor/github.com/cretz/bine/tor/tor.go | 453 |
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) +} |