diff --git a/control/cmd_misc.go b/control/cmd_misc.go
index ff20e44..bc94ba2 100644
--- a/control/cmd_misc.go
+++ b/control/cmd_misc.go
@@ -46,6 +46,9 @@ func (c *Conn) GetInfo(keys ...string) ([]*KeyVal, error) {
for _, val := range resp.Data {
infoVal := &KeyVal{}
infoVal.Key, infoVal.Val, _ = util.PartitionString(val, '=')
+ if infoVal.Val, err = util.UnescapeSimpleQuotedStringIfNeeded(infoVal.Val); err != nil {
+ return nil, err
+ }
ret = append(ret, infoVal)
}
return ret, nil
diff --git a/examples/simpleclient/main.go b/examples/simpleclient/main.go
new file mode 100644
index 0000000..d345d39
--- /dev/null
+++ b/examples/simpleclient/main.go
@@ -0,0 +1,68 @@
+package main
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+ "log"
+ "net/http"
+ "strings"
+ "time"
+
+ "github.com/cretz/bine/tor"
+ "golang.org/x/net/html"
+)
+
+func main() {
+ if err := run(); err != nil {
+ log.Fatal(err)
+ }
+}
+
+func run() error {
+ // Start tor with default config (can set start conf's DebugWriter to os.Stdout for debug logs)
+ fmt.Println("Starting tor and fetching title of https://check.torproject.org, please wait a few seconds...")
+ t, err := tor.Start(nil, nil)
+ if err != nil {
+ return err
+ }
+ defer t.Close()
+ // Wait at most a minute to start network and get
+ dialCtx, dialCancel := context.WithTimeout(context.Background(), time.Minute)
+ defer dialCancel()
+ // Make connection
+ dialer, err := t.Dialer(dialCtx, nil)
+ if err != nil {
+ return err
+ }
+ httpClient := &http.Client{Transport: &http.Transport{DialContext: dialer.DialContext}}
+ // Get /
+ resp, err := httpClient.Get("https://check.torproject.org")
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+ // Grab the
+ parsed, err := html.Parse(resp.Body)
+ if err != nil {
+ return err
+ }
+ fmt.Printf("Title: %v\n", getTitle(parsed))
+ return nil
+}
+
+func getTitle(n *html.Node) string {
+ if n.Type == html.ElementNode && n.Data == "title" {
+ var title bytes.Buffer
+ if err := html.Render(&title, n.FirstChild); err != nil {
+ panic(err)
+ }
+ return strings.TrimSpace(title.String())
+ }
+ for c := n.FirstChild; c != nil; c = c.NextSibling {
+ if title := getTitle(c); title != "" {
+ return title
+ }
+ }
+ return ""
+}
diff --git a/tor/dialer.go b/tor/dialer.go
new file mode 100644
index 0000000..038bc95
--- /dev/null
+++ b/tor/dialer.go
@@ -0,0 +1,121 @@
+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. An empty auth means no auth, a nil
+ // auth means it is looked up.
+ 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"
+ }
+ // Lookup proxy auth as needed
+ proxyAuth := conf.ProxyAuth
+ if proxyAuth == nil {
+ info, err := t.Control.GetConf("Socks5ProxyUsername", "Socks5ProxyPassword")
+ if err != nil {
+ return nil, err
+ }
+ if len(info) != 2 || info[0].Key != "Socks5ProxyUsername" || info[1].Key != "Socks5ProxyPassword" {
+ return nil, fmt.Errorf("Unable to get proxy auth")
+ }
+ proxyAuth = &proxy.Auth{User: info[0].Val, Password: info[1].Val}
+ }
+ if proxyAuth.User == "" && proxyAuth.Password == "" {
+ proxyAuth = nil
+ }
+
+ dialer, err := proxy.SOCKS5(proxyNetwork, proxyAddress, 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/tor/doc.go b/tor/doc.go
index 63312d1..a58086a 100644
--- a/tor/doc.go
+++ b/tor/doc.go
@@ -1,2 +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/tor/tor.go b/tor/tor.go
index 255aa34..aa8b100 100644
--- a/tor/tor.go
+++ b/tor/tor.go
@@ -7,6 +7,7 @@ import (
"io/ioutil"
"net/textproto"
"os"
+ "path/filepath"
"strconv"
"time"
@@ -98,6 +99,9 @@ type StartConf struct {
// 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
}
// Start a Tor instance and connect to it. If ctx is nil, context.Background()
@@ -110,13 +114,16 @@ func Start(ctx context.Context, conf *StartConf) (*Tor, error) {
conf = &StartConf{}
}
tor := &Tor{DataDir: conf.DataDir, DebugWriter: conf.DebugWriter, StopProcessOnClose: true}
- // Create the data dir
+ // 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)
}
@@ -165,6 +172,9 @@ func (t *Tor) startProcess(ctx context.Context, conf *StartConf) error {
if !conf.EnableNetwork {
args = append(args, "--DisableNetwork", "1")
}
+ if !conf.NoHush {
+ args = append(args, "--hush")
+ }
// If there is no Torrc file, create a blank temp one
torrcFileName := conf.TorrcFile
if torrcFileName == "" {