diff --git a/acn.go b/acn.go index ac03ae0..f4e3077 100644 --- a/acn.go +++ b/acn.go @@ -38,6 +38,8 @@ type ACN interface { WaitTillBootstrapped() error // Sets the callback function to be called when ACN status changes SetStatusCallback(callback func(int, string)) + // Sets the callback function to be called when ACN reboots to emit the version + SetVersionCallback(callback func(string)) // Restarts the underlying connection Restart() diff --git a/error_acn.go b/error_acn.go index ea035f9..b1c5e68 100644 --- a/error_acn.go +++ b/error_acn.go @@ -11,7 +11,8 @@ const acnError = "error initializing anonymous communication network" // ErrorACN - a status-callback safe errored ACN. Use this when ACN construction goes wrong // and you need a safe substitute that can later be replaced with a working ACN without impacting calling clients. type ErrorACN struct { - statusCallbackCache func(int, string) + statusCallbackCache func(int, string) + versionCallbackCache func(string) } func (e ErrorACN) Callback() func(int, string) { @@ -34,6 +35,10 @@ func (e *ErrorACN) SetStatusCallback(callback func(int, string)) { e.statusCallbackCache = callback } +func (e *ErrorACN) SetVersionCallback(callback func(string)) { + e.versionCallbackCache = callback +} + func (e ErrorACN) Restart() { } diff --git a/go.mod b/go.mod index 7d0bf24..b071c74 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.13 require ( filippo.io/edwards25519 v1.0.0-rc.1 git.openprivacy.ca/openprivacy/bine v0.0.4 - git.openprivacy.ca/openprivacy/log v1.0.2 + git.openprivacy.ca/openprivacy/log v1.0.3 golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect ) diff --git a/go.sum b/go.sum index f9bd8bf..cd776a2 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ git.openprivacy.ca/openprivacy/bine v0.0.4 h1:CO7EkGyz+jegZ4ap8g5NWRuDHA/56KKvGy git.openprivacy.ca/openprivacy/bine v0.0.4/go.mod h1:13ZqhKyqakDsN/ZkQkIGNULsmLyqtXc46XBcnuXm/mU= git.openprivacy.ca/openprivacy/log v1.0.2 h1:HLP4wsw4ljczFAelYnbObIs821z+jgMPCe8uODPnGQM= git.openprivacy.ca/openprivacy/log v1.0.2/go.mod h1:gGYK8xHtndRLDymFtmjkG26GaMQNgyhioNS82m812Iw= +git.openprivacy.ca/openprivacy/log v1.0.3 h1:E/PMm4LY+Q9s3aDpfySfEDq/vYQontlvNj/scrPaga0= +git.openprivacy.ca/openprivacy/log v1.0.3/go.mod h1:gGYK8xHtndRLDymFtmjkG26GaMQNgyhioNS82m812Iw= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/localProvider.go b/localProvider.go index 1964a79..1052894 100644 --- a/localProvider.go +++ b/localProvider.go @@ -51,6 +51,10 @@ func (lp *localProvider) SetStatusCallback(callback func(int, string)) { // nop } +func (lp *localProvider) SetVersionCallback(callback func(string)) { + // nop +} + func (lp *localProvider) GetPID() (int, error) { return 0, nil } diff --git a/proxy_acn.go b/proxy_acn.go index c5ebab7..910e48a 100644 --- a/proxy_acn.go +++ b/proxy_acn.go @@ -49,6 +49,10 @@ func (p *ProxyACN) SetStatusCallback(callback func(int, string)) { p.acn.SetStatusCallback(callback) } +func (p *ProxyACN) SetVersionCallback(callback func(string)) { + p.acn.SetVersionCallback(callback) +} + func (p *ProxyACN) Restart() { p.acn.Restart() } diff --git a/tor/sysProcAttr_rest.go b/tor/sysProcAttr_rest.go index 2d9fca9..821da5a 100644 --- a/tor/sysProcAttr_rest.go +++ b/tor/sysProcAttr_rest.go @@ -1,3 +1,4 @@ +//go:build !windows // +build !windows package tor diff --git a/tor/sysProcAttr_win.go b/tor/sysProcAttr_win.go index 1de2be0..e0536b4 100644 --- a/tor/sysProcAttr_win.go +++ b/tor/sysProcAttr_win.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package tor diff --git a/tor/torProvider.go b/tor/torProvider.go index 35dbc24..494e96a 100644 --- a/tor/torProvider.go +++ b/tor/torProvider.go @@ -70,10 +70,12 @@ type torProvider struct { breakChan chan bool childListeners map[string]*onionListenService statusCallback func(int, string) + versionCallback func(string) lastRestartTime time.Time authenticator tor.Authenticator isClosed bool dataDir string + version string } func (ols *onionListenService) AddressFull() string { @@ -209,17 +211,7 @@ func (tp *torProvider) GetBootstrapStatus() (int, string) { func (tp *torProvider) GetVersion() string { tp.lock.Lock() defer tp.lock.Unlock() - - if tp.t == nil { - return "No Tor" - } - - pinfo, err := tp.t.Control.ProtocolInfo() - - if err == nil { - return pinfo.TorVersion - } - return "No Tor" + return tp.version } func (tp *torProvider) closed() bool { @@ -324,6 +316,7 @@ func (tp *torProvider) restart() { // preserve status callback after shutdown statusCallback := tp.statusCallback + versionCallback := tp.versionCallback tp.t = nil log.Debugf("Restarting Tor Process") @@ -334,6 +327,10 @@ func (tp *torProvider) restart() { tp.t = newTp.t tp.dialer = newTp.dialer tp.statusCallback = statusCallback + tp.versionCallback = versionCallback + if tp.versionCallback != nil { + tp.versionCallback(tp.version) + } tp.lastRestartTime = time.Now() tp.isClosed = false go tp.monitorRestart() @@ -391,6 +388,12 @@ func (tp *torProvider) SetStatusCallback(callback func(int, string)) { tp.statusCallback = callback } +func (tp *torProvider) SetVersionCallback(callback func(string)) { + tp.lock.Lock() + defer tp.lock.Unlock() + tp.versionCallback = callback +} + func (tp *torProvider) Callback() func(int, string) { tp.lock.Lock() defer tp.lock.Unlock() @@ -399,10 +402,10 @@ func (tp *torProvider) Callback() func(int, string) { func (tp *torProvider) callStatusCallback(prog int, status string) { tp.lock.Lock() + defer tp.lock.Unlock() if tp.statusCallback != nil { tp.statusCallback(prog, status) } - tp.lock.Unlock() } // NewTorACNWithAuth creates/starts a Tor ACN and returns a usable ACN object @@ -429,7 +432,7 @@ func newHideCmd(exePath string) process.Creator { }) } -func (tp *torProvider) checkVersion() error { +func (tp *torProvider) checkVersion() (string, error) { // attempt connect to system tor log.Debugf("dialing tor control port") controlport, err := dialControlPort(tp.controlPort) @@ -442,13 +445,13 @@ func (tp *torProvider) checkVersion() error { if err == nil { if minTorVersionReqs(pinfo.TorVersion) { log.Debugln("OK version " + pinfo.TorVersion) - return nil + return pinfo.TorVersion, nil } - return fmt.Errorf("tor version not supported: %v", pinfo.TorVersion) + return pinfo.TorVersion, fmt.Errorf("tor version not supported: %v", pinfo.TorVersion) } } } - return err + return "", err } func startTor(appDirectory string, bundledTorPath string, dataDir string, controlPort int, authenticator tor.Authenticator) (*torProvider, error) { @@ -456,10 +459,11 @@ func startTor(appDirectory string, bundledTorPath string, dataDir string, contro os.MkdirAll(torDir, 0700) - tp := &torProvider{authenticator: authenticator, controlPort: controlPort, appDirectory: appDirectory, bundeledTorPath: bundledTorPath, childListeners: make(map[string]*onionListenService), breakChan: make(chan bool, 1), statusCallback: nil, lastRestartTime: time.Now().Add(-restartCooldown)} + tp := &torProvider{authenticator: authenticator, controlPort: controlPort, appDirectory: appDirectory, bundeledTorPath: bundledTorPath, childListeners: make(map[string]*onionListenService), breakChan: make(chan bool, 1), statusCallback: nil, versionCallback: nil, lastRestartTime: time.Now().Add(-restartCooldown)} log.Debugf("checking if there is a running system tor") - if err := tp.checkVersion(); err == nil { + if version, err := tp.checkVersion(); err == nil { + tp.version = version controlport, err := dialControlPort(tp.controlPort) if err == nil { log.Debugf("creating tor handler from system tor") @@ -476,16 +480,9 @@ func startTor(appDirectory string, bundledTorPath string, dataDir string, contro return nil, err } - // if not, try running system tor - log.Debugln("checking if we can run system installed tor or bundled tor") - if checkCmdlineTorVersion("tor") { - t, err := tor.Start(context.TODO(), &tor.StartConf{ControlPort: tp.controlPort, DisableCookieAuth: true, UseEmbeddedControlConn: false, DisableEagerAuth: true, EnableNetwork: true, DataDir: dataDir, TorrcFile: path.Join(torDir, "torrc"), DebugWriter: nil, ProcessCreator: newHideCmd("tor")}) - if err != nil { - log.Debugf("Error connecting to self-run system tor: %v\n", err) - return nil, err - } - tp.t = t - } else if bundledTorPath != "" && checkCmdlineTorVersion(bundledTorPath) { + // if not, try bundled tor, then running system tor + log.Debugln("checking if we can run bundled tor or system installed tor") + if version, pass := checkCmdlineTorVersion(bundledTorPath); pass { log.Debugln("bundled tor appears viable, attempting to use '" + bundledTorPath + "'") t, err := tor.Start(context.TODO(), &tor.StartConf{ControlPort: tp.controlPort, DisableCookieAuth: true, UseEmbeddedControlConn: false, DisableEagerAuth: true, EnableNetwork: true, DataDir: dataDir, TorrcFile: path.Join(torDir, "torrc"), ExePath: bundledTorPath, DebugWriter: nil, ProcessCreator: newHideCmd(bundledTorPath)}) if err != nil { @@ -493,15 +490,25 @@ func startTor(appDirectory string, bundledTorPath string, dataDir string, contro return nil, err } tp.t = t + tp.version = version + } else if version, pass := checkCmdlineTorVersion("tor"); pass { + t, err := tor.Start(context.TODO(), &tor.StartConf{ControlPort: tp.controlPort, DisableCookieAuth: true, UseEmbeddedControlConn: false, DisableEagerAuth: true, EnableNetwork: true, DataDir: dataDir, TorrcFile: path.Join(torDir, "torrc"), DebugWriter: nil, ProcessCreator: newHideCmd("tor")}) + if err != nil { + log.Debugf("Error connecting to self-run system tor: %v\n", err) + return nil, err + } + tp.t = t + tp.version = version } else { log.Debugln("Could not find a viable tor running or to run") return nil, fmt.Errorf("could not connect to or start Tor that met requirements (min Tor version 0.3.5.x)") } - err := tp.checkVersion() + version, err := tp.checkVersion() if err == nil { tp.t.DeleteDataDirOnClose = false // caller is responsible for dealing with cached information... tp.dialer, err = tp.t.Dialer(context.TODO(), &tor.DialConf{Authenticator: tp.authenticator}) + tp.version = version return tp, err } return nil, fmt.Errorf("could not connect to running tor: %v", err) @@ -572,7 +579,10 @@ func createFromExisting(controlport *control.Conn, datadir string) *tor.Tor { return t } -func checkCmdlineTorVersion(torCmd string) bool { +func checkCmdlineTorVersion(torCmd string) (string, bool) { + if torCmd == "" { + return "", false + } // ideally we would use CommandContext with Timeout here // but it doesn't work with programs that may launch other processes via scripts e.g. exec // and the workout is more complex than just implementing the logic ourselves... @@ -599,13 +609,13 @@ func checkCmdlineTorVersion(torCmd string) bool { if err != nil { log.Debugf("tor process timed out") - return false + return "", false } re := regexp.MustCompile(`[0-1]\.[0-9]\.[0-9]\.[0-9]`) sysTorVersion := re.Find(outb.Bytes()) log.Infof("tor version: %v", string(sysTorVersion)) - return minTorVersionReqs(string(sysTorVersion)) + return string(sysTorVersion), minTorVersionReqs(string(sysTorVersion)) } // returns true if supplied version meets our min requirments diff --git a/tor/torProvider_test.go b/tor/torProvider_test.go index 5827eb3..38dd726 100644 --- a/tor/torProvider_test.go +++ b/tor/torProvider_test.go @@ -19,11 +19,19 @@ func getStatusCallback(progChan chan int) func(int, string) { } } +func getVersionCallback(verChan chan string) func(string) { + return func(version string) { + fmt.Printf("version: %v\n", version) + verChan <- version + } +} + func TestTorProvider(t *testing.T) { goRoutineStart := runtime.NumGoroutine() progChan := make(chan int, 10) + verChan := make(chan string, 10) log.SetLevel(log.LevelDebug) torpath := path.Join("..", "tmp/tor") @@ -43,6 +51,7 @@ func TestTorProvider(t *testing.T) { return } acn.SetStatusCallback(getStatusCallback(progChan)) + acn.SetVersionCallback(getVersionCallback(verChan)) progress := 0 for progress < 100 { @@ -57,6 +66,13 @@ func TestTorProvider(t *testing.T) { progress = <-progChan t.Logf("progress: %v", progress) } + log.Debugf("Pulling tor version from version callback chan...\n") + version := <-verChan + if version == "" { + t.Errorf("failed to get tor version, got empty string\n") + } else { + log.Debugf("Tor version: %v\n", version) + } // Test opening the OP Server _, _, err = acn.Open("isbr2t6bflul2zyi6hjtnuezb2xvfr42svzjg2q3gyqfgg3wmnrbkkqd")