github.com/decred/dcrlnd@v0.7.6/tor/controller.go (about)

     1  package tor
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/hmac"
     6  	"crypto/rand"
     7  	"crypto/sha256"
     8  	"encoding/hex"
     9  	"errors"
    10  	"fmt"
    11  	"io/ioutil"
    12  	"net/textproto"
    13  	"strconv"
    14  	"strings"
    15  	"sync/atomic"
    16  )
    17  
    18  const (
    19  	// success is the Tor Control response code representing a successful
    20  	// request.
    21  	success = 250
    22  
    23  	// invalidNumOfArguments is the Tor Control response code representing
    24  	// there being an invalid number of arguments.
    25  	invalidNumOfArguments = 512
    26  
    27  	// serviceIDNotRecognized is the Tor Control response code representing
    28  	// the specified ServiceID is not recognized.
    29  	serviceIDNotRecognized = 552
    30  
    31  	// nonceLen is the length of a nonce generated by either the controller
    32  	// or the Tor server
    33  	nonceLen = 32
    34  
    35  	// cookieLen is the length of the authentication cookie.
    36  	cookieLen = 32
    37  
    38  	// ProtocolInfoVersion is the `protocolinfo` version currently supported
    39  	// by the Tor server.
    40  	ProtocolInfoVersion = 1
    41  
    42  	// MinTorVersion is the minimum supported version that the Tor server
    43  	// must be running on. This is needed in order to create v3 onion
    44  	// services through Tor's control port.
    45  	MinTorVersion = "0.3.3.6"
    46  
    47  	// authSafeCookie is the name of the SAFECOOKIE authentication method.
    48  	authSafeCookie = "SAFECOOKIE"
    49  
    50  	// authHashedPassword is the name of the HASHEDPASSWORD authentication
    51  	// method.
    52  	authHashedPassword = "HASHEDPASSWORD"
    53  
    54  	// authNull is the name of the NULL authentication method.
    55  	authNull = "NULL"
    56  )
    57  
    58  var (
    59  	// serverKey is the key used when computing the HMAC-SHA256 of a message
    60  	// from the server.
    61  	serverKey = []byte("Tor safe cookie authentication " +
    62  		"server-to-controller hash")
    63  
    64  	// controllerKey is the key used when computing the HMAC-SHA256 of a
    65  	// message from the controller.
    66  	controllerKey = []byte("Tor safe cookie authentication " +
    67  		"controller-to-server hash")
    68  
    69  	// errCodeNotMatch is used when an expected response code is not
    70  	// returned.
    71  	errCodeNotMatch = errors.New("unexpected code")
    72  
    73  	// errTCNotStarted is used when we require the tor controller to be
    74  	// started while it's not.
    75  	errTCNotStarted = errors.New("tor controller must be started")
    76  
    77  	// errTCNotStarted is used when we require the tor controller to be
    78  	// not stopped while it is.
    79  	errTCStopped = errors.New("tor controller must not be stopped")
    80  )
    81  
    82  // Controller is an implementation of the Tor Control protocol. This is used in
    83  // order to communicate with a Tor server. Its only supported method of
    84  // authentication is the SAFECOOKIE method.
    85  //
    86  // NOTE: The connection to the Tor server must be authenticated before
    87  // proceeding to send commands. Otherwise, the connection will be closed.
    88  //
    89  // TODO:
    90  //   - if adding support for more commands, extend this with a command queue?
    91  //   - place under sub-package?
    92  //   - support async replies from the server
    93  type Controller struct {
    94  	// started is used atomically in order to prevent multiple calls to
    95  	// Start.
    96  	started int32
    97  
    98  	// stopped is used atomically in order to prevent multiple calls to
    99  	// Stop.
   100  	stopped int32
   101  
   102  	// conn is the underlying connection between the controller and the
   103  	// Tor server. It provides read and write methods to simplify the
   104  	// text-based messages within the connection.
   105  	conn *textproto.Conn
   106  
   107  	// controlAddr is the host:port the Tor server is listening locally for
   108  	// controller connections on.
   109  	controlAddr string
   110  
   111  	// password, if non-empty, signals that the controller should attempt to
   112  	// authenticate itself with the backing Tor daemon through the
   113  	// HASHEDPASSWORD authentication method with this value.
   114  	password string
   115  
   116  	// version is the current version of the Tor server.
   117  	version string
   118  
   119  	// targetIPAddress is the IP address which we tell the Tor server to use
   120  	// to connect to the LND node.  This is required when the Tor server
   121  	// runs on another host, otherwise the service will not be reachable.
   122  	targetIPAddress string
   123  
   124  	// activeServiceID is the Onion ServiceID created by ADD_ONION.
   125  	activeServiceID string
   126  }
   127  
   128  // NewController returns a new Tor controller that will be able to interact with
   129  // a Tor server.
   130  func NewController(controlAddr string, targetIPAddress string,
   131  	password string) *Controller {
   132  
   133  	return &Controller{
   134  		controlAddr:     controlAddr,
   135  		targetIPAddress: targetIPAddress,
   136  		password:        password,
   137  	}
   138  }
   139  
   140  // Start establishes and authenticates the connection between the controller
   141  // and a Tor server. Once done, the controller will be able to send commands
   142  // and expect responses.
   143  func (c *Controller) Start() error {
   144  	if !atomic.CompareAndSwapInt32(&c.started, 0, 1) {
   145  		return nil
   146  	}
   147  
   148  	log.Info("Starting tor controller")
   149  
   150  	conn, err := textproto.Dial("tcp", c.controlAddr)
   151  	if err != nil {
   152  		return fmt.Errorf("unable to connect to Tor server: %v", err)
   153  	}
   154  
   155  	c.conn = conn
   156  
   157  	return c.authenticate()
   158  }
   159  
   160  // Stop closes the connection between the controller and the Tor server.
   161  func (c *Controller) Stop() error {
   162  	if !atomic.CompareAndSwapInt32(&c.stopped, 0, 1) {
   163  		return nil
   164  	}
   165  
   166  	log.Info("Stopping tor controller")
   167  
   168  	// Remove the onion service.
   169  	if err := c.DelOnion(c.activeServiceID); err != nil {
   170  		log.Errorf("DEL_ONION got error: %v", err)
   171  		return err
   172  	}
   173  
   174  	// Reset service ID.
   175  	c.activeServiceID = ""
   176  
   177  	return c.conn.Close()
   178  }
   179  
   180  // Reconnect makes a new socket connection between the tor controller and
   181  // daemon. It will attempt to close the old connection, make a new connection
   182  // and authenticate, and finally reset the activeServiceID that the controller
   183  // is aware of.
   184  //
   185  // NOTE: Any old onion services will be removed once this function is called.
   186  // In the case of a Tor daemon restart, previously created onion services will
   187  // no longer be there. If the function is called without a Tor daemon restart,
   188  // because the control connection is reset, all the onion services belonging to
   189  // the old connection will be removed.
   190  func (c *Controller) Reconnect() error {
   191  	// Require the tor controller to be running when we want to reconnect.
   192  	// This means the started flag must be 1 and the stopped flag must be
   193  	// 0.
   194  	if c.started != 1 {
   195  		return errTCNotStarted
   196  	}
   197  	if c.stopped != 0 {
   198  		return errTCStopped
   199  	}
   200  
   201  	log.Info("Re-connectting tor controller")
   202  
   203  	// If we have an old connection, try to close it. We might receive an
   204  	// error if the connection has already been closed by Tor daemon(ie,
   205  	// daemon restarted), so we ignore the error here.
   206  	if c.conn != nil {
   207  		if err := c.conn.Close(); err != nil {
   208  			log.Debugf("closing old conn got err: %v", err)
   209  		}
   210  	}
   211  
   212  	// Make a new connection and authenticate.
   213  	conn, err := textproto.Dial("tcp", c.controlAddr)
   214  	if err != nil {
   215  		return fmt.Errorf("unable to connect to Tor server: %w", err)
   216  	}
   217  
   218  	c.conn = conn
   219  
   220  	// Authenticate the connection between the controller and Tor daemon.
   221  	if err := c.authenticate(); err != nil {
   222  		return err
   223  	}
   224  
   225  	// Reset the activeServiceID. This value would only be set if a
   226  	// previous onion service was created. Because the old connection has
   227  	// been closed at this point, the old onion service is no longer
   228  	// active.
   229  	c.activeServiceID = ""
   230  
   231  	return nil
   232  }
   233  
   234  // sendCommand sends a command to the Tor server and returns its response, as a
   235  // single space-delimited string, and code.
   236  func (c *Controller) sendCommand(command string) (int, string, error) {
   237  	id, err := c.conn.Cmd(command)
   238  	if err != nil {
   239  		return 0, "", err
   240  	}
   241  
   242  	// Make sure our reader only process the response returned from the
   243  	// above command.
   244  	c.conn.StartResponse(id)
   245  	defer c.conn.EndResponse(id)
   246  
   247  	code, reply, err := c.readResponse(success)
   248  	if err != nil {
   249  		log.Debugf("sendCommand:%s got err:%v, reply:%v",
   250  			command, err, reply)
   251  		return code, reply, err
   252  	}
   253  
   254  	return code, reply, nil
   255  }
   256  
   257  // readResponse reads the replies from Tor to the controller. The reply has the
   258  // following format,
   259  //
   260  //	Reply = SyncReply / AsyncReply
   261  //	SyncReply = *(MidReplyLine / DataReplyLine) EndReplyLine
   262  //	AsyncReply = *(MidReplyLine / DataReplyLine) EndReplyLine
   263  //
   264  //	MidReplyLine = StatusCode "-" ReplyLine
   265  //	DataReplyLine = StatusCode "+" ReplyLine CmdData
   266  //	EndReplyLine = StatusCode SP ReplyLine
   267  //	ReplyLine = [ReplyText] CRLF
   268  //	ReplyText = XXXX
   269  //	StatusCode = 3DIGIT
   270  //
   271  // Unless specified otherwise, multiple lines in a single reply from Tor daemon
   272  // to the controller are guaranteed to share the same status code. Read more on
   273  // this topic:
   274  //
   275  //	https://gitweb.torproject.org/torspec.git/tree/control-spec.txt#n158
   276  //
   277  // NOTE: this code is influenced by https://github.com/Yawning/bulb.
   278  func (c *Controller) readResponse(expected int) (int, string, error) {
   279  	// Clean the buffer inside the conn. This is needed when we encountered
   280  	// an error while reading the response, the remaining lines need to be
   281  	// cleaned before next read.
   282  	defer func() {
   283  		if _, err := c.conn.R.Discard(c.conn.R.Buffered()); err != nil {
   284  			log.Errorf("clean read buffer failed: %v", err)
   285  		}
   286  	}()
   287  
   288  	reply, code := "", 0
   289  	hasMoreLines := true
   290  
   291  	for hasMoreLines {
   292  		line, err := c.conn.Reader.ReadLine()
   293  		if err != nil {
   294  			return 0, reply, err
   295  		}
   296  		log.Tracef("Reading line: %v", line)
   297  
   298  		// Line being shortter than 4 is not allowed.
   299  		if len(line) < 4 {
   300  			err = textproto.ProtocolError("short line: " + line)
   301  			return 0, reply, err
   302  		}
   303  
   304  		// Parse the status code.
   305  		code, err = strconv.Atoi(line[0:3])
   306  		if err != nil {
   307  			return code, reply, err
   308  		}
   309  
   310  		switch line[3] {
   311  		// EndReplyLine = StatusCode SP ReplyLine.
   312  		// Example: 250 OK
   313  		// This is the end of the response, so we mark hasMoreLines to
   314  		// be false to exit the loop.
   315  		case ' ':
   316  			reply += line[4:]
   317  			hasMoreLines = false
   318  
   319  		// MidReplyLine = StatusCode "-" ReplyLine.
   320  		// Example: 250-version=...
   321  		// This is a continued response, so we keep reading the next
   322  		// line.
   323  		case '-':
   324  			reply += line[4:]
   325  
   326  		// DataReplyLine = StatusCode "+" ReplyLine CmdData.
   327  		// Example: 250+config-text=
   328  		//	    line1
   329  		//	    line2
   330  		//          more lines...
   331  		//          .
   332  		// This is a data response, meaning the following multiple
   333  		// lines are the actual data, and a dot(.) in the end means the
   334  		// end of the data response. The response will be formatted as,
   335  		// 	key=line1,line2,...
   336  		// The above example will then be,
   337  		// 	config-text=line1,line2,...
   338  		case '+':
   339  			// Add the key(config-text=)
   340  			reply += line[4:]
   341  
   342  			// Add the values.
   343  			resp, err := c.conn.Reader.ReadDotLines()
   344  			if err != nil {
   345  				return code, reply, err
   346  			}
   347  			reply += strings.Join(resp, ",")
   348  
   349  		// Invalid line separator found.
   350  		default:
   351  			err = textproto.ProtocolError("invalid line: " + line)
   352  			return code, reply, err
   353  		}
   354  
   355  		// We check the code here so that the error message is parsed
   356  		// from the line.
   357  		if code != expected {
   358  			return code, reply, errCodeNotMatch
   359  		}
   360  
   361  		// Separate each line using "\n".
   362  		if hasMoreLines {
   363  			reply += "\n"
   364  		}
   365  	}
   366  
   367  	log.Tracef("Parsed reply: %v", reply)
   368  	return code, reply, nil
   369  }
   370  
   371  // parseTorReply parses the reply from the Tor server after receiving a command
   372  // from a controller. This will parse the relevant reply parameters into a map
   373  // of keys and values.
   374  func parseTorReply(reply string) map[string]string {
   375  	params := make(map[string]string)
   376  
   377  	// Replies can either span single or multiple lines, so we'll default
   378  	// to stripping whitespace and newlines in order to retrieve the
   379  	// individual contents of it. The -1 indicates that we want this to span
   380  	// across all instances of a newline.
   381  	contents := strings.Split(strings.Replace(reply, "\n", " ", -1), " ")
   382  	for _, content := range contents {
   383  		// Each parameter within the reply should be of the form
   384  		// "KEY=VALUE". If the parameter doesn't contain "=", then we
   385  		// can assume it does not provide any other relevant information
   386  		// already known.
   387  		keyValue := strings.SplitN(content, "=", 2)
   388  		if len(keyValue) != 2 {
   389  			continue
   390  		}
   391  
   392  		key := keyValue[0]
   393  		value := keyValue[1]
   394  		params[key] = value
   395  	}
   396  
   397  	return params
   398  }
   399  
   400  // authenticate authenticates the connection between the controller and the
   401  // Tor server using either of the following supported authentication methods
   402  // depending on its configuration: SAFECOOKIE, HASHEDPASSWORD, and NULL.
   403  func (c *Controller) authenticate() error {
   404  	protocolInfo, err := c.protocolInfo()
   405  	if err != nil {
   406  		return err
   407  	}
   408  
   409  	log.Debugf("received protocol info: %v", protocolInfo)
   410  
   411  	// With the version retrieved, we'll cache it now in case it needs to be
   412  	// used later on.
   413  	c.version = protocolInfo.version()
   414  
   415  	switch {
   416  	// If a password was provided, then we should attempt to use the
   417  	// HASHEDPASSWORD authentication method.
   418  	case c.password != "":
   419  		if !protocolInfo.supportsAuthMethod(authHashedPassword) {
   420  			return fmt.Errorf("%v authentication method not "+
   421  				"supported", authHashedPassword)
   422  		}
   423  
   424  		return c.authenticateViaHashedPassword()
   425  
   426  	// Otherwise, attempt to authentication via the SAFECOOKIE method as it
   427  	// provides the most security.
   428  	case protocolInfo.supportsAuthMethod(authSafeCookie):
   429  		return c.authenticateViaSafeCookie(protocolInfo)
   430  
   431  	// Fallback to the NULL method if any others aren't supported.
   432  	case protocolInfo.supportsAuthMethod(authNull):
   433  		return c.authenticateViaNull()
   434  
   435  	// No supported authentication methods, fail.
   436  	default:
   437  		return errors.New("the Tor server must be configured with " +
   438  			"NULL, SAFECOOKIE, or HASHEDPASSWORD authentication")
   439  	}
   440  }
   441  
   442  // authenticateViaNull authenticates the controller with the Tor server using
   443  // the NULL authentication method.
   444  func (c *Controller) authenticateViaNull() error {
   445  	_, _, err := c.sendCommand("AUTHENTICATE")
   446  	return err
   447  }
   448  
   449  // authenticateViaHashedPassword authenticates the controller with the Tor
   450  // server using the HASHEDPASSWORD authentication method.
   451  func (c *Controller) authenticateViaHashedPassword() error {
   452  	cmd := fmt.Sprintf("AUTHENTICATE \"%s\"", c.password)
   453  	_, _, err := c.sendCommand(cmd)
   454  	return err
   455  }
   456  
   457  // authenticateViaSafeCookie authenticates the controller with the Tor server
   458  // using the SAFECOOKIE authentication method.
   459  func (c *Controller) authenticateViaSafeCookie(info protocolInfo) error {
   460  	// Before proceeding to authenticate the connection, we'll retrieve
   461  	// the authentication cookie of the Tor server. This will be used
   462  	// throughout the authentication routine. We do this before as once the
   463  	// authentication routine has begun, it is not possible to retrieve it
   464  	// mid-way.
   465  	cookie, err := c.getAuthCookie(info)
   466  	if err != nil {
   467  		return fmt.Errorf("unable to retrieve authentication cookie: "+
   468  			"%v", err)
   469  	}
   470  
   471  	// Authenticating using the SAFECOOKIE authentication method is a two
   472  	// step process. We'll kick off the authentication routine by sending
   473  	// the AUTHCHALLENGE command followed by a hex-encoded 32-byte nonce.
   474  	clientNonce := make([]byte, nonceLen)
   475  	if _, err := rand.Read(clientNonce); err != nil {
   476  		return fmt.Errorf("unable to generate client nonce: %v", err)
   477  	}
   478  
   479  	cmd := fmt.Sprintf("AUTHCHALLENGE SAFECOOKIE %x", clientNonce)
   480  	_, reply, err := c.sendCommand(cmd)
   481  	if err != nil {
   482  		return err
   483  	}
   484  
   485  	// If successful, the reply from the server should be of the following
   486  	// format:
   487  	//
   488  	//	"250 AUTHCHALLENGE"
   489  	//		SP "SERVERHASH=" ServerHash
   490  	//		SP "SERVERNONCE=" ServerNonce
   491  	//		CRLF
   492  	//
   493  	// We're interested in retrieving the SERVERHASH and SERVERNONCE
   494  	// parameters, so we'll parse our reply to do so.
   495  	replyParams := parseTorReply(reply)
   496  
   497  	// Once retrieved, we'll ensure these values are of proper length when
   498  	// decoded.
   499  	serverHash, ok := replyParams["SERVERHASH"]
   500  	if !ok {
   501  		return errors.New("server hash not found in reply")
   502  	}
   503  	decodedServerHash, err := hex.DecodeString(serverHash)
   504  	if err != nil {
   505  		return fmt.Errorf("unable to decode server hash: %v", err)
   506  	}
   507  	if len(decodedServerHash) != sha256.Size {
   508  		return errors.New("invalid server hash length")
   509  	}
   510  
   511  	serverNonce, ok := replyParams["SERVERNONCE"]
   512  	if !ok {
   513  		return errors.New("server nonce not found in reply")
   514  	}
   515  	decodedServerNonce, err := hex.DecodeString(serverNonce)
   516  	if err != nil {
   517  		return fmt.Errorf("unable to decode server nonce: %v", err)
   518  	}
   519  	if len(decodedServerNonce) != nonceLen {
   520  		return errors.New("invalid server nonce length")
   521  	}
   522  
   523  	// The server hash above was constructed by computing the HMAC-SHA256
   524  	// of the message composed of the cookie, client nonce, and server
   525  	// nonce. We'll redo this computation ourselves to ensure the integrity
   526  	// and authentication of the message.
   527  	hmacMessage := bytes.Join(
   528  		[][]byte{cookie, clientNonce, decodedServerNonce}, []byte{},
   529  	)
   530  	computedServerHash := computeHMAC256(serverKey, hmacMessage)
   531  	if !hmac.Equal(computedServerHash, decodedServerHash) {
   532  		return fmt.Errorf("expected server hash %x, got %x",
   533  			decodedServerHash, computedServerHash)
   534  	}
   535  
   536  	// If the MAC check was successful, we'll proceed with the last step of
   537  	// the authentication routine. We'll now send the AUTHENTICATE command
   538  	// followed by a hex-encoded client hash constructed by computing the
   539  	// HMAC-SHA256 of the same message, but this time using the controller's
   540  	// key.
   541  	clientHash := computeHMAC256(controllerKey, hmacMessage)
   542  	if len(clientHash) != sha256.Size {
   543  		return errors.New("invalid client hash length")
   544  	}
   545  
   546  	cmd = fmt.Sprintf("AUTHENTICATE %x", clientHash)
   547  	if _, _, err := c.sendCommand(cmd); err != nil {
   548  		return err
   549  	}
   550  
   551  	return nil
   552  }
   553  
   554  // getAuthCookie retrieves the authentication cookie in bytes from the Tor
   555  // server. Cookie authentication must be enabled for this to work.
   556  func (c *Controller) getAuthCookie(info protocolInfo) ([]byte, error) {
   557  	// Retrieve the cookie file path from the PROTOCOLINFO reply.
   558  	cookieFilePath, ok := info["COOKIEFILE"]
   559  	if !ok {
   560  		return nil, errors.New("COOKIEFILE not found in PROTOCOLINFO " +
   561  			"reply")
   562  	}
   563  	cookieFilePath = strings.Trim(cookieFilePath, "\"")
   564  
   565  	// Read the cookie from the file and ensure it has the correct length.
   566  	cookie, err := ioutil.ReadFile(cookieFilePath)
   567  	if err != nil {
   568  		return nil, err
   569  	}
   570  
   571  	if len(cookie) != cookieLen {
   572  		return nil, errors.New("invalid authentication cookie length")
   573  	}
   574  
   575  	return cookie, nil
   576  }
   577  
   578  // computeHMAC256 computes the HMAC-SHA256 of a key and message.
   579  func computeHMAC256(key, message []byte) []byte {
   580  	mac := hmac.New(sha256.New, key)
   581  	mac.Write(message)
   582  	return mac.Sum(nil)
   583  }
   584  
   585  // supportsV3 is a helper function that parses the current version of the Tor
   586  // server and determines whether it supports creationg v3 onion services through
   587  // Tor's control port. The version string should be of the format:
   588  //
   589  //	major.minor.revision.build
   590  func supportsV3(version string) error {
   591  	// We'll split the minimum Tor version that's supported and the given
   592  	// version in order to individually compare each number.
   593  	parts := strings.Split(version, ".")
   594  	if len(parts) != 4 {
   595  		return errors.New("version string is not of the format " +
   596  			"major.minor.revision.build")
   597  	}
   598  
   599  	// It's possible that the build number (the last part of the version
   600  	// string) includes a pre-release string, e.g. rc, beta, etc., so we'll
   601  	// parse that as well.
   602  	build := strings.Split(parts[len(parts)-1], "-")
   603  	parts[len(parts)-1] = build[0]
   604  
   605  	// Ensure that each part of the version string corresponds to a number.
   606  	for _, part := range parts {
   607  		if _, err := strconv.Atoi(part); err != nil {
   608  			return err
   609  		}
   610  	}
   611  
   612  	// Once we've determined we have a proper version string of the format
   613  	// major.minor.revision.build, we can just do a string comparison to
   614  	// determine if it satisfies the minimum version supported.
   615  	if version < MinTorVersion {
   616  		return fmt.Errorf("version %v below minimum version supported "+
   617  			"%v", version, MinTorVersion)
   618  	}
   619  
   620  	return nil
   621  }
   622  
   623  // protocolInfo is encompasses the details of a response to a PROTOCOLINFO
   624  // command.
   625  type protocolInfo map[string]string
   626  
   627  // version returns the Tor version as reported by the server.
   628  func (i protocolInfo) version() string {
   629  	version := i["Tor"]
   630  	return strings.Trim(version, "\"")
   631  }
   632  
   633  // supportsAuthMethod determines whether the Tor server supports the given
   634  // authentication method.
   635  func (i protocolInfo) supportsAuthMethod(method string) bool {
   636  	methods, ok := i["METHODS"]
   637  	if !ok {
   638  		return false
   639  	}
   640  	return strings.Contains(methods, method)
   641  }
   642  
   643  // protocolInfo sends a "PROTOCOLINFO" command to the Tor server and returns its
   644  // response.
   645  func (c *Controller) protocolInfo() (protocolInfo, error) {
   646  	cmd := fmt.Sprintf("PROTOCOLINFO %d", ProtocolInfoVersion)
   647  	_, reply, err := c.sendCommand(cmd)
   648  	if err != nil {
   649  		return nil, err
   650  	}
   651  
   652  	return protocolInfo(parseTorReply(reply)), nil
   653  }