libricochet-go/connectivity/torProvider.go

218 lines
6.2 KiB
Go
Raw Normal View History

package connectivity
import (
"errors"
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
"github.com/cretz/bine/control"
"github.com/cretz/bine/tor"
"log"
"net"
"net/textproto"
"os"
"os/exec"
"path"
"regexp"
"strconv"
"strings"
"sync"
"time"
)
const (
// CannotResolveLocalTCPAddressError is thrown when a local ricochet connection has the wrong format.
CannotResolveLocalTCPAddressError = utils.Error("CannotResolveLocalTCPAddressError")
// CannotDialLocalTCPAddressError is thrown when a connection to a local ricochet address fails.
CannotDialLocalTCPAddressError = utils.Error("CannotDialLocalTCPAddressError")
// CannotDialRicochetAddressError is thrown when a connection to a ricochet address fails.
CannotDialRicochetAddressError = utils.Error("CannotDialRicochetAddressError")
)
type onionListenService struct {
os *tor.OnionService
}
type torProvider struct {
t *tor.Tor
lock sync.Mutex
}
func (ols *onionListenService) AddressFull() string {
return ols.os.Addr().String()
}
func (ols *onionListenService) AddressIdentity() string {
return ols.os.Addr().String()[:56]
}
func (ols *onionListenService) Accept() (net.Conn, error) {
return ols.os.Accept()
}
func (ols *onionListenService) Close() {
ols.os.Close()
}
// GetBootstrapStatus returns an int 0-100 on the percent the bootstrapping of the underlying network is at and an optional string message
func (tp *torProvider) GetBootstrapStatus() (int, string) {
kvs, err := tp.t.Control.GetInfo("status/bootstrap-phase")
if err != nil {
return 0, "error"
}
progress := 0
status := ""
if len(kvs) > 0 {
progRe := regexp.MustCompile("PROGRESS=([0-9]*)")
sumRe := regexp.MustCompile("SUMMARY=\"(.*)\"$")
if progMatches := progRe.FindStringSubmatch(kvs[0].Val); len(progMatches) > 1 {
progress, _ = strconv.Atoi(progMatches[1])
}
if statusMatches := sumRe.FindStringSubmatch(kvs[0].Val); len(statusMatches) > 1 {
status = statusMatches[1]
}
}
return progress, status
}
// WaitTillBootstrapped Blocks until underlying network is bootstrapped
func (tp *torProvider) WaitTillBootstrapped() {
for true {
progress, _ := tp.GetBootstrapStatus()
if progress == 100 {
break
}
time.Sleep(100 * time.Millisecond)
}
}
func (tp *torProvider) Listen(identity PrivateKey, port int) (ListenService, error) {
tp.lock.Lock()
defer tp.lock.Unlock()
conf := &tor.ListenConf{NoWait: true, Version3: true, Key: identity, RemotePorts: []int{port}, Detach: true, DiscardKey: true}
os, err := tp.t.Listen(nil, conf)
return &onionListenService{os}, err
}
func (tp *torProvider) Open(hostname string) (net.Conn, string, error) {
tp.lock.Lock()
defer tp.lock.Unlock()
torDailer, err := tp.t.Dialer(nil, &tor.DialConf{})
if err != nil {
return nil, "", err
}
resolvedHostname := hostname
if strings.HasPrefix(hostname, "ricochet:") {
addrParts := strings.Split(hostname, ":")
resolvedHostname = addrParts[1]
}
conn, err := torDailer.Dial("tcp", resolvedHostname+".onion:9878")
// if there was an error, we may have been cycling too fast
// clear the tor cache and try one more time
if err != nil {
tp.t.Control.Signal("NEWNYM")
conn, err = torDailer.Dial("tcp", resolvedHostname+".onion:9878")
}
return conn, resolvedHostname, err
}
func (tp *torProvider) Close() {
tp.lock.Lock()
defer tp.lock.Unlock()
tp.t.Close()
}
2018-11-21 23:07:57 +00:00
// StartTor creates/starts a Tor ACN and returns a usable ACN object
func StartTor(appDirectory string, bundledTorPath string) (ACN, error) {
dataDir := path.Join(appDirectory, "tor")
os.MkdirAll(dataDir, 0700)
// attempt connect to system tor
log.Printf("dialing system tor control port\n")
controlport, err := dialControlPort(9051)
if err == nil {
// TODO: configurable auth
err := controlport.Authenticate("")
if err == nil {
log.Printf("connected to control port")
pinfo, err := controlport.ProtocolInfo()
if err == nil && minTorVersionReqs(pinfo.TorVersion) {
log.Println("OK version " + pinfo.TorVersion)
return createFromExisting(controlport, dataDir), nil
}
controlport.Close()
}
}
// if not, try running system tor
if checkCmdlineTorVersion("tor") {
t, err := tor.Start(nil, &tor.StartConf{DataDir: dataDir, DebugWriter: nil})
if err == nil {
return &torProvider{t: t}, err
}
log.Printf("Error connecting to self-run system tor: %v\n", err)
}
// try running bundledTor
if bundledTorPath != "" && checkCmdlineTorVersion(bundledTorPath) {
log.Println("using bundled tor '" + bundledTorPath + "'")
t, err := tor.Start(nil, &tor.StartConf{DataDir: dataDir, ExePath: bundledTorPath, DebugWriter: nil})
if err != nil {
log.Printf("Error running bundled tor: %v\n", err)
}
return &torProvider{t: t}, err
}
return nil, errors.New("Could not connect to or start Tor that met requirments")
}
2018-11-21 23:07:57 +00:00
func createFromExisting(controlport *control.Conn, datadir string) ACN {
t := &tor.Tor{
Process: nil,
Control: controlport,
ProcessCancelFunc: nil,
DataDir: datadir,
DeleteDataDirOnClose: false,
DebugWriter: nil,
StopProcessOnClose: false,
GeoIPCreatedFile: "",
GeoIPv6CreatedFile: "",
}
t.Control.DebugWriter = t.DebugWriter
t.EnableNetwork(nil, true)
return &torProvider{t: t}
}
func checkCmdlineTorVersion(torCmd string) bool {
cmd := exec.Command(torCmd, "--version")
out, err := cmd.CombinedOutput()
2018-11-21 04:30:06 +00:00
log.Println("cmdline tor version: " + string(out))
re := regexp.MustCompile("[0-1]\\.[0-9]\\.[0-9]\\.[0-9]")
sysTorVersion := re.Find(out)
log.Println("cmdline tor version: " + string(sysTorVersion))
return err == nil && minTorVersionReqs(string(sysTorVersion))
}
// returns true if supplied version meets our min requirments
// min requirment 0.3.5.x
func minTorVersionReqs(torversion string) bool {
torversions := strings.Split(torversion, ".") //eg: 0.3.4.8 or 0.3.5.1-alpha
2018-11-21 04:30:06 +00:00
log.Printf("torversions: %v", torversions)
tva, _ := strconv.Atoi(torversions[0])
tvb, _ := strconv.Atoi(torversions[1])
tvc, _ := strconv.Atoi(torversions[2])
return tva > 0 || (tva == 0 && (tvb > 3 || (tvb == 3 && tvc >= 5)))
}
func dialControlPort(port int) (*control.Conn, error) {
textConn, err := textproto.Dial("tcp", "127.0.0.1:"+strconv.Itoa(port))
if err != nil {
return nil, err
}
return control.NewConn(textConn), nil
}