2018-11-09 21:33:35 +00:00
package connectivity
import (
"errors"
2018-11-30 21:04:38 +00:00
"git.openprivacy.ca/openprivacy/libricochet-go/log"
2018-11-09 21:33:35 +00:00
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
"github.com/cretz/bine/control"
"github.com/cretz/bine/tor"
2018-12-07 01:50:09 +00:00
bineed255192 "github.com/cretz/bine/torutil/ed25519"
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/sha3"
2018-11-09 21:33:35 +00:00
"net"
"net/textproto"
"os"
"os/exec"
"path"
"regexp"
"strconv"
"strings"
"sync"
2018-11-22 06:15:35 +00:00
"time"
2018-11-09 21:33:35 +00:00
)
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" )
)
2019-07-10 19:53:17 +00:00
const (
minStatusIntervalMs = 200
maxStatusIntervalMs = 2000
)
2018-11-09 21:33:35 +00:00
type onionListenService struct {
os * tor . OnionService
2018-11-26 18:55:37 +00:00
tp * torProvider
2018-11-09 21:33:35 +00:00
}
type torProvider struct {
2018-11-26 18:55:37 +00:00
t * tor . Tor
2019-07-31 21:31:35 +00:00
dialer * tor . Dialer
2018-11-26 18:55:37 +00:00
appDirectory string
bundeledTorPath string
lock sync . Mutex
breakChan chan bool
childListeners map [ string ] * onionListenService
2019-07-10 19:53:17 +00:00
statusCallback func ( int , string )
2018-11-09 21:33:35 +00:00
}
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 ( ) {
2018-11-26 18:55:37 +00:00
ols . tp . unregisterListener ( ols . AddressIdentity ( ) )
2018-11-09 21:33:35 +00:00
ols . os . Close ( )
}
2019-07-10 19:53:17 +00:00
// GetBootstrapStatus returns an int -1 on error or 0-100 on the percent the bootstrapping of the underlying network is at and an optional string message
2018-11-22 06:15:35 +00:00
func ( tp * torProvider ) GetBootstrapStatus ( ) ( int , string ) {
2019-02-05 19:54:52 +00:00
if tp . t == nil {
2019-07-10 19:53:17 +00:00
return - 1 , "error: no tor, trying to restart..."
2019-02-05 19:54:52 +00:00
}
2018-11-22 06:15:35 +00:00
kvs , err := tp . t . Control . GetInfo ( "status/bootstrap-phase" )
if err != nil {
2019-07-10 19:53:17 +00:00
return - 1 , "error"
2018-11-22 06:15:35 +00:00
}
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 )
}
}
2018-11-09 21:33:35 +00:00
func ( tp * torProvider ) Listen ( identity PrivateKey , port int ) ( ListenService , error ) {
2018-12-07 01:50:09 +00:00
var onion = ""
var privkey ed25519 . PrivateKey
2019-07-31 21:31:35 +00:00
tp . lock . Lock ( )
defer tp . lock . Unlock ( )
2019-02-04 22:12:16 +00:00
if tp . t == nil {
return nil , errors . New ( "Tor Provider closed" )
}
2018-12-07 01:50:09 +00:00
switch pk := identity . ( type ) {
case ed25519 . PrivateKey :
privkey = pk
gpubk := pk . Public ( )
switch pubk := gpubk . ( type ) {
case ed25519 . PublicKey :
onion = utils . GetTorV3Hostname ( pubk )
}
}
// Hack around tor detached onions not having a more obvious resume mechanism
// So we use deterministic ports
seedbytes := sha3 . New224 ( ) . Sum ( [ ] byte ( onion ) )
localport := int ( seedbytes [ 0 ] ) + ( int ( seedbytes [ 1 ] ) << 8 )
if localport < 1024 { // this is not uniformly random, but we don't need it to be
localport += 1024
}
localListener , err := net . Listen ( "tcp" , "127.0.0.1:" + strconv . Itoa ( localport ) )
conf := & tor . ListenConf { NoWait : true , Version3 : true , Key : identity , RemotePorts : [ ] int { port } , Detach : true , DiscardKey : true , LocalListener : localListener }
2018-11-09 21:33:35 +00:00
os , err := tp . t . Listen ( nil , conf )
2018-12-07 01:50:09 +00:00
if err != nil && strings . Contains ( err . Error ( ) , "550 Unspecified Tor error: Onion address collision" ) {
os = & tor . OnionService { Tor : tp . t , LocalListener : localListener , ID : onion , Version3 : true , Key : bineed255192 . FromCryptoPrivateKey ( privkey ) , ClientAuths : make ( map [ string ] string , 0 ) , RemotePorts : [ ] int { port } }
err = nil
}
// Not set in t.Listen if supplied, we want it to handle this however
os . CloseLocalListenerOnClose = true
2018-11-26 18:55:37 +00:00
if err != nil {
return nil , err
}
2018-12-07 01:50:09 +00:00
2018-11-26 18:55:37 +00:00
ols := & onionListenService { os : os , tp : tp }
tp . childListeners [ ols . AddressIdentity ( ) ] = ols
return ols , nil
2018-11-09 21:33:35 +00:00
}
2019-11-06 19:26:54 +00:00
func ( tp * torProvider ) Restart ( ) {
if tp . statusCallback != nil {
tp . statusCallback ( 0 , "rebooting" )
}
tp . restart ( )
}
2018-11-09 21:33:35 +00:00
func ( tp * torProvider ) Open ( hostname string ) ( net . Conn , string , error ) {
tp . lock . Lock ( )
2019-02-04 22:12:16 +00:00
2018-11-26 18:55:37 +00:00
if tp . t == nil {
2019-07-31 21:31:35 +00:00
tp . lock . Unlock ( )
2018-11-26 18:55:37 +00:00
return nil , hostname , errors . New ( "Tor is offline" )
}
2019-07-31 21:31:35 +00:00
tp . lock . Unlock ( )
2018-11-09 21:33:35 +00:00
resolvedHostname := hostname
if strings . HasPrefix ( hostname , "ricochet:" ) {
addrParts := strings . Split ( hostname , ":" )
resolvedHostname = addrParts [ 1 ]
}
2019-07-31 21:31:35 +00:00
conn , err := tp . dialer . Dial ( "tcp" , resolvedHostname + ".onion:9878" )
2018-11-09 21:33:35 +00:00
return conn , resolvedHostname , err
}
func ( tp * torProvider ) Close ( ) {
2018-11-26 18:55:37 +00:00
for _ , child := range tp . childListeners {
child . Close ( )
}
2018-11-09 21:33:35 +00:00
tp . lock . Lock ( )
defer tp . lock . Unlock ( )
2018-11-26 18:55:37 +00:00
tp . breakChan <- true
if tp . t != nil {
tp . t . Close ( )
2019-02-04 22:12:16 +00:00
tp . t = nil
2018-11-26 18:55:37 +00:00
}
2018-11-09 21:33:35 +00:00
}
2019-07-10 19:53:17 +00:00
func ( tp * torProvider ) SetStatusCallback ( callback func ( int , string ) ) {
tp . lock . Lock ( )
defer tp . lock . Unlock ( )
tp . statusCallback = callback
}
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 ) {
2019-07-10 19:53:17 +00:00
tp , err := startTor ( appDirectory , bundledTorPath )
if err == nil {
2019-07-31 21:31:35 +00:00
tp . dialer , err = tp . t . Dialer ( nil , & tor . DialConf { } )
if err == nil {
go tp . monitorRestart ( )
}
2019-07-10 19:53:17 +00:00
}
return tp , err
}
func startTor ( appDirectory string , bundledTorPath string ) ( * torProvider , error ) {
2018-11-09 21:33:35 +00:00
dataDir := path . Join ( appDirectory , "tor" )
os . MkdirAll ( dataDir , 0700 )
2019-07-10 19:53:17 +00:00
tp := & torProvider { appDirectory : appDirectory , bundeledTorPath : bundledTorPath , childListeners : make ( map [ string ] * onionListenService ) , breakChan : make ( chan bool ) , statusCallback : nil }
2018-11-09 21:33:35 +00:00
// attempt connect to system tor
2018-11-30 21:04:38 +00:00
log . Debugf ( "dialing system tor control port\n" )
2018-11-09 21:33:35 +00:00
controlport , err := dialControlPort ( 9051 )
if err == nil {
// TODO: configurable auth
err := controlport . Authenticate ( "" )
if err == nil {
2018-12-03 21:26:15 +00:00
log . Debugln ( "connected to control port" )
2018-11-09 21:33:35 +00:00
pinfo , err := controlport . ProtocolInfo ( )
if err == nil && minTorVersionReqs ( pinfo . TorVersion ) {
2018-12-03 21:26:15 +00:00
log . Debugln ( "OK version " + pinfo . TorVersion )
2018-11-26 18:55:37 +00:00
tp . t = createFromExisting ( controlport , dataDir )
return tp , nil
2018-11-09 21:33:35 +00:00
}
controlport . Close ( )
}
}
// if not, try running system tor
if checkCmdlineTorVersion ( "tor" ) {
2019-02-14 19:40:57 +00:00
t , err := tor . Start ( nil , & tor . StartConf { EnableNetwork : true , DataDir : dataDir , DebugWriter : nil } )
2018-11-09 21:33:35 +00:00
if err == nil {
2018-11-26 18:55:37 +00:00
tp . t = t
return tp , nil
2018-11-09 21:33:35 +00:00
}
2018-11-30 21:04:38 +00:00
log . Debugf ( "Error connecting to self-run system tor: %v\n" , err )
2018-11-09 21:33:35 +00:00
}
// try running bundledTor
if bundledTorPath != "" && checkCmdlineTorVersion ( bundledTorPath ) {
2018-12-03 21:26:15 +00:00
log . Debugln ( "using bundled tor '" + bundledTorPath + "'" )
2019-02-14 19:40:57 +00:00
t , err := tor . Start ( nil , & tor . StartConf { EnableNetwork : true , DataDir : dataDir , ExePath : bundledTorPath , DebugWriter : nil } )
2018-11-09 21:33:35 +00:00
if err != nil {
2018-11-30 21:04:38 +00:00
log . Debugf ( "Error running bundled tor: %v\n" , err )
2018-11-09 21:33:35 +00:00
}
2018-11-26 18:55:37 +00:00
tp . t = t
return tp , err
2018-11-09 21:33:35 +00:00
}
2018-12-07 01:50:09 +00:00
return nil , errors . New ( "Could not connect to or start Tor that met requirments (min Tor version 0.3.5.x)" )
2018-11-09 21:33:35 +00:00
}
2018-11-26 18:55:37 +00:00
func ( tp * torProvider ) unregisterListener ( id string ) {
tp . lock . Lock ( )
defer tp . lock . Unlock ( )
delete ( tp . childListeners , id )
}
func ( tp * torProvider ) monitorRestart ( ) {
2019-07-10 19:53:17 +00:00
lastBootstrapProgress := 0
interval := minStatusIntervalMs
2018-11-26 18:55:37 +00:00
for {
select {
2019-07-10 19:53:17 +00:00
case <- time . After ( time . Millisecond * time . Duration ( interval ) ) :
prog , status := tp . GetBootstrapStatus ( )
2018-11-26 18:55:37 +00:00
2019-07-10 19:53:17 +00:00
if prog == - 1 && tp . t != nil {
if tp . statusCallback != nil {
tp . statusCallback ( prog , status )
}
tp . restart ( )
interval = minStatusIntervalMs
} else if prog != lastBootstrapProgress {
if tp . statusCallback != nil {
tp . statusCallback ( prog , status )
}
interval = minStatusIntervalMs
} else {
if interval < maxStatusIntervalMs {
interval *= 2
2018-11-26 18:55:37 +00:00
}
}
2019-07-10 19:53:17 +00:00
lastBootstrapProgress = prog
2018-11-26 18:55:37 +00:00
case <- tp . breakChan :
return
}
2019-07-10 19:53:17 +00:00
}
}
func ( tp * torProvider ) restart ( ) {
for _ , child := range tp . childListeners {
child . Close ( )
}
2018-11-26 18:55:37 +00:00
2019-07-10 19:53:17 +00:00
tp . lock . Lock ( )
defer tp . lock . Unlock ( )
tp . t . Close ( )
tp . t = nil
for {
newTp , err := startTor ( tp . appDirectory , tp . bundeledTorPath )
if err == nil {
tp . t = newTp . t
2019-07-31 21:31:35 +00:00
tp . dialer , _ = tp . t . Dialer ( nil , & tor . DialConf { } )
2019-07-10 19:53:17 +00:00
return
}
2018-11-26 18:55:37 +00:00
}
}
func createFromExisting ( controlport * control . Conn , datadir string ) * tor . Tor {
2018-11-09 21:33:35 +00:00
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 )
2018-11-26 18:55:37 +00:00
return t
2018-11-09 21:33:35 +00:00
}
func checkCmdlineTorVersion ( torCmd string ) bool {
cmd := exec . Command ( torCmd , "--version" )
out , err := cmd . CombinedOutput ( )
2018-11-21 04:30:06 +00:00
re := regexp . MustCompile ( "[0-1]\\.[0-9]\\.[0-9]\\.[0-9]" )
2018-11-09 21:33:35 +00:00
sysTorVersion := re . Find ( out )
2018-11-30 21:04:38 +00:00
log . Infoln ( "tor version: " + string ( sysTorVersion ) )
2018-11-09 21:33:35 +00:00
return err == nil && minTorVersionReqs ( string ( sysTorVersion ) )
}
// returns true if supplied version meets our min requirments
2019-01-26 22:04:28 +00:00
// min requirement: 0.3.5.x
2018-11-09 21:33:35 +00:00
func minTorVersionReqs ( torversion string ) bool {
torversions := strings . Split ( torversion , "." ) //eg: 0.3.4.8 or 0.3.5.1-alpha
2018-11-30 21:04:38 +00:00
log . Debugf ( "torversions: %v" , torversions )
2018-11-09 21:33:35 +00:00
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
}