summaryrefslogtreecommitdiff
path: root/vendor/github.com/cretz/bine/tor/listen.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/cretz/bine/tor/listen.go')
-rw-r--r--vendor/github.com/cretz/bine/tor/listen.go343
1 files changed, 343 insertions, 0 deletions
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
+}