182 lines
5.3 KiB
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
|
|
}
|