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 == "" {