This repository has been archived on 2020-04-20. You can view files and clone it, but cannot push or open issues or pull requests.
libricochet-go/connectivity/torProvider.go

182 lines
5.3 KiB
Go

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"
)
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()
}
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()
}
// StartTor creates/starts a Tor mixnet and returns a usable Mixnet object
func StartTor(appDirectory string, bundledTorPath string) (Mixnet, 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")
}
func createFromExisting(controlport *control.Conn, datadir string) Mixnet {
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()
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
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
}