From 2beeb7bc512e01e3b9d2ec7418dca7855df7bd87 Mon Sep 17 00:00:00 2001 From: David Stainton Date: Wed, 30 Sep 2015 18:02:47 +0200 Subject: [PATCH] Add ephemeral onion service functionality to controller API here we also: - add a TorDialer and OnionListener - complete working example of an onion echo server --- cmd_onion.go | 62 ++++++++++++ examples/onion/onion.go | 76 +++++++++++++++ utils/connection.go | 206 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 344 insertions(+) create mode 100644 cmd_onion.go create mode 100644 examples/onion/onion.go create mode 100644 utils/connection.go diff --git a/cmd_onion.go b/cmd_onion.go new file mode 100644 index 0000000..db0e8d0 --- /dev/null +++ b/cmd_onion.go @@ -0,0 +1,62 @@ +// cmd_onion.go - various onion service commands: ADD_ONION, DEL_ONION... +// +// To the extent possible under law, David Stainton waived all copyright +// and related or neighboring rights to this module of bulb, using the creative +// commons "cc0" public domain dedication. See LICENSE or +// for full details. + +package bulb + +import ( + "fmt" + "strings" +) + +// OnionInfo is the result of the AddOnion command. +type OnionInfo struct { + OnionId string + KeyType string + Key string + + RawResponse *Response +} + +// AddOnion issues an ADD_ONION command and returns the parsed response. +func (c *Conn) AddOnion(virtPort int, target, keyType, keyContent string, new bool) (*OnionInfo, error) { + var fields []string + request := "ADD_ONION " + onionInfo := OnionInfo{} + + if new { + request += "NEW:BEST" + } else { + request += fmt.Sprintf("%s:%s", keyType, keyContent) + } + request += fmt.Sprintf(" Port=%d,%s\n", virtPort, target) + fmt.Printf("DEBUG request: %s\n", request) + response, err := c.Request(request) + if err != nil { + return nil, err + } + + onionInfo.RawResponse = response + fields = strings.Split(fmt.Sprintf("%s", response.Data), "ServiceID=") + fields = strings.Split(fields[1], " ") + onionInfo.OnionId = fields[0] + + if new { + fields = strings.Split(fmt.Sprintf("%s", response.Data), "PrivateKey=") + fields = strings.Split(fields[1], ":") + onionInfo.KeyType = fields[0] + fields = strings.Split(fields[1], "\n") + onionInfo.Key = fields[0] + } + + return &onionInfo, nil +} + +func (c *Conn) DeleteOnion(serviceId string) error { + var deleteCmd string = fmt.Sprintf("DEL_ONION %s\n", serviceId) + _, err := c.Request(deleteCmd) + return err +} diff --git a/examples/onion/onion.go b/examples/onion/onion.go new file mode 100644 index 0000000..7187b8f --- /dev/null +++ b/examples/onion/onion.go @@ -0,0 +1,76 @@ +// Onion example. +// +// To the extent possible under law, David Stainton waived all copyright +// and related or neighboring rights to this bulb source file, using the creative +// commons "cc0" public domain dedication. See LICENSE or +// for full details. + +package main + +import ( + "fmt" + "io" + "log" + "net" + + "github.com/yawning/bulb" + "github.com/yawning/bulb/utils" +) + +func main() { + // Connect to a running tor instance. + // unix domain socket - c, err := bulb.Dial("unix", "/var/run/tor/control") + c, err := bulb.Dial("tcp4", "127.0.0.1:9051") + + if err != nil { + log.Fatalf("failed to connect to control port: %v", err) + } + defer c.Close() + + // See what's really going on under the hood. + // Do not enable in production. + c.Debug(true) + + // Authenticate with the control port. The password argument + // here can be "" if no password is set (CookieAuth, no auth). + if err := c.Authenticate("ExamplePassword"); err != nil { + log.Fatalf("Authentication failed: %v", err) + } + + options := utils.OnionListenerOptions{ + OnionKeyFile: "echoOnionKey", + OnionServicePort: 80, + LocalAddr: "127.0.0.1:8080", + ControlNetwork: "tcp", + ControlAddr: "127.0.0.1:9051", + } + + listener, err := utils.NewOnionListener(&options) + if err != nil { + log.Fatal(err) + } + + addr := listener.Addr() + onionAddr := addr.String() + fmt.Printf("onion echo server: listening to %s\n", onionAddr) + + defer listener.Close() + + for { + // Wait for a connection. + conn, err := listener.Accept() + if err != nil { + log.Fatal(err) + } + + // Handle the connection in a new goroutine. + // The loop then returns to accepting, so that + // multiple connections may be served concurrently. + go func(c net.Conn) { + // Echo all incoming data. + io.Copy(c, c) + // Shut down the connection. + c.Close() + }(conn) + } +} diff --git a/utils/connection.go b/utils/connection.go new file mode 100644 index 0000000..2750a63 --- /dev/null +++ b/utils/connection.go @@ -0,0 +1,206 @@ +// connection.go - A TorDialer and OnionListener... +// +// To the extent possible under law, David Stainton waived all copyright +// and related or neighboring rights to this bulb source file, using the creative +// commons "cc0" public domain dedication. See LICENSE or +// for full details. + +// Package utils implements useful utilities for dealing with Tor and it's +// control port. +package utils + +import ( + "fmt" + "golang.org/x/net/proxy" + "io/ioutil" + "net" + "strings" + + "github.com/yawning/bulb" +) + +type TorDialer struct { + dialer proxy.Dialer +} + +func NewTorDialer(network string, addr string, auth *proxy.Auth) (*TorDialer, error) { + var err error + var socksDialer proxy.Dialer + forwardDialer := net.Dialer{} + + socksDialer, err = proxy.SOCKS5(network, addr, auth, &forwardDialer) + if err != nil { + return nil, fmt.Errorf("TorDialer: Failed to create socks dialer: %v\n", err) + } + + torDialer := TorDialer{ + dialer: socksDialer, + } + + return &torDialer, nil +} + +func (t *TorDialer) Dial(network, addr string) (net.Conn, error) { + conn, err := t.dialer.Dial(network, addr) + if err != nil { + return nil, fmt.Errorf("TorDialer: failed to dial tor socks port: %s\n", err) + } + return conn, nil +} + +type OnionAddr struct { + network string + address string +} + +func (o OnionAddr) Network() string { + return o.network +} + +func (o OnionAddr) String() string { + return o.address +} + +type OnionListenerOptions struct { + // OnionKeyFile is used to persist onion service key material + OnionKeyFile string + + // OnionServicePort is the virtport of the tor onion service + OnionServicePort int + // LocalAddr is the address of the local TCP Listener + LocalAddr string + + // ControlAddr is the Tor control port address + ControlAddr string + // ControlNetwork is the network type of the Tor control port + ControlNetwork string + ControlPassword string +} + +type OnionListener struct { + options *OnionListenerOptions + controller *bulb.Conn + onionInfo *bulb.OnionInfo + localListener net.Listener +} + +func NewOnionListener(options *OnionListenerOptions) (*OnionListener, error) { + listener := OnionListener{ + options: options, + } + if options.ControlAddr == "" || options.ControlNetwork == "" { + return nil, fmt.Errorf("Tor control port address not specified.") + } + err := listener.initOnion() + if err != nil { + return nil, err + } + return &listener, nil +} + +func (o *OnionListener) controlAuth() error { + var err error + + o.controller, err = bulb.Dial(o.options.ControlNetwork, o.options.ControlAddr) + if err != nil { + return fmt.Errorf("OnionListener: Failed to connect to Tor control port: %s\n", err) + } + + err = o.controller.Authenticate(o.options.ControlPassword) + if err != nil { + return fmt.Errorf("OnionListener: Failed authenticate with Tor control port: %s\n", err) + } + + return nil +} + +// readOnionKeyFile returns the key type string, key content string and nil error +// ...otherwise error will be non-nil. +func (o *OnionListener) readOnionKeyFile() (string, string, error) { + var fields []string + onionKeyBytes := make([]byte, 100) + onionKeyBytes, err := ioutil.ReadFile(o.options.OnionKeyFile) + if err != nil { + return "", "", err + } + fields = strings.Split(string(onionKeyBytes), ":") + return fields[0], fields[1], nil +} + +func (o *OnionListener) writeOnionKeyFile(keyType, keyContent string) error { + keyData := fmt.Sprintf("%s:%s", keyType, keyContent) + err := ioutil.WriteFile(o.options.OnionKeyFile, []byte(keyData), 0644) + return err +} + +func (o *OnionListener) initOnion() error { + //var serviceId string + var keyType, keyContent string + var err error + onionKeyContent := "" + onionKeyType := "" + + if o.options.OnionKeyFile != "" { + onionKeyType, onionKeyContent, err = o.readOnionKeyFile() + } + + err = o.controlAuth() + if err != nil { + return err + } + + if onionKeyType != "" { + o.onionInfo, err = o.controller.AddOnion(o.options.OnionServicePort, o.options.LocalAddr, onionKeyType, onionKeyContent, false) + } else { + o.onionInfo, err = o.controller.AddOnion(o.options.OnionServicePort, o.options.LocalAddr, "", "", true) + } + + if err != nil { + return fmt.Errorf("OnionListener: create hidden service fail: %s\n", err) + } + + if o.options.OnionKeyFile != "" { + err = o.writeOnionKeyFile(keyType, keyContent) + if err != nil { + return fmt.Errorf("OnionListener: failed to write key file to disk: %s\n", err) + } + } + + o.localListener, err = net.Listen("tcp", o.options.LocalAddr) + if err != nil { + return fmt.Errorf("OnionListener: local TCP listen error: %s\n", err) + } + + return nil +} + +func (o *OnionListener) Accept() (net.Conn, error) { + var conn net.Conn + var err error + + if o.onionInfo.OnionId == "" { + return nil, fmt.Errorf("OnionListener: onion service not initialized.\n") + } + + conn, err = o.localListener.Accept() + if err != nil { + return nil, fmt.Errorf("OnionListener: local TCP connection Accept failure: %s\n", err) + } + + return conn, nil +} + +func (o *OnionListener) Close() error { + err := o.controller.DeleteOnion(o.onionInfo.OnionId) + if err != nil { + return fmt.Errorf("OnionListener: DeleteHiddenService failure: %s\n", err) + } + return nil +} + +func (o *OnionListener) Addr() net.Addr { + return OnionAddr{ + network: "tor", + address: fmt.Sprintf("%s.onion:%d", o.onionInfo.OnionId, o.options.OnionServicePort), + } +}