620 lines
21 KiB
Go
620 lines
21 KiB
Go
//package cwtch
|
|
|
|
package main
|
|
|
|
// //Needed to invoke C.free
|
|
// #include <stdlib.h>
|
|
import "C"
|
|
|
|
import (
|
|
"cwtch.im/cwtch/event"
|
|
"cwtch.im/cwtch/model/attr"
|
|
"cwtch.im/cwtch/model/constants"
|
|
"cwtch.im/cwtch/settings"
|
|
"encoding/json"
|
|
"fmt"
|
|
"git.openprivacy.ca/cwtch.im/cwtch-autobindings/utils"
|
|
"git.openprivacy.ca/openprivacy/connectivity/tor"
|
|
"git.openprivacy.ca/openprivacy/log"
|
|
"os"
|
|
"os/user"
|
|
path "path/filepath"
|
|
"runtime"
|
|
"runtime/pprof"
|
|
"strings"
|
|
"strconv"
|
|
"time"
|
|
mrand "math/rand"
|
|
"crypto/rand"
|
|
"encoding/base64"
|
|
"unsafe"
|
|
_ "github.com/mutecomm/go-sqlcipher/v4"
|
|
"cwtch.im/cwtch/app"
|
|
"git.openprivacy.ca/openprivacy/connectivity"
|
|
"sync"
|
|
|
|
{{IMPORTS}}
|
|
)
|
|
|
|
// supplied by make
|
|
var (
|
|
buildVer string
|
|
buildDate string
|
|
)
|
|
|
|
var application app.Application
|
|
var globalAppDir string
|
|
var globalTorPath string
|
|
var eventHandler *utils.EventHandler
|
|
var globalACN connectivity.ProxyACN
|
|
|
|
// Dangerous function. Should only be used as documented in `MEMORY.md`
|
|
//
|
|
//export c_FreePointer
|
|
func c_FreePointer(ptr *C.char) {
|
|
C.free(unsafe.Pointer(ptr))
|
|
}
|
|
|
|
//export c_Started
|
|
func c_Started() C.int {
|
|
return C.int(Started())
|
|
}
|
|
|
|
// Started returns 1 if application is initialized and 0 if it is null
|
|
func Started() int {
|
|
if application == nil {
|
|
return 0
|
|
}
|
|
return 1
|
|
}
|
|
|
|
//export c_StartCwtch
|
|
func c_StartCwtch(dir_c *C.char, len C.int, tor_c *C.char, torLen C.int) C.int {
|
|
applicationDirectory := C.GoStringN(dir_c, len)
|
|
torDirectory := C.GoStringN(tor_c, torLen)
|
|
return C.int(StartCwtch(applicationDirectory, torDirectory))
|
|
}
|
|
|
|
// StartCwtch starts cwtch in the library and initlaizes all data structures
|
|
//
|
|
// GetAppbusEvents is always safe to use
|
|
// the rest of functions are unsafe until the CwtchStarted event has been received indicating StartCwtch has completed
|
|
// returns:
|
|
// message: CwtchStarted when start up is complete and app is safe to use
|
|
// CwtchStartError message when start up fails (includes event.Error data field)
|
|
func StartCwtch(appDir string, torPath string) int {
|
|
if logfile := os.Getenv("LOG_FILE"); logfile != "" {
|
|
filelog, err := log.NewFile(log.LevelInfo, logfile)
|
|
if err == nil {
|
|
filelog.SetUseColor(false)
|
|
log.SetStd(filelog)
|
|
} else {
|
|
// not so likely to be seen since we're usually creating file log in situations we can't access console logs...
|
|
log.Errorf("could not create file log: %v\n", err)
|
|
}
|
|
}
|
|
if runtime.GOOS == "android" {
|
|
log.SetUseColor(false)
|
|
}
|
|
log.SetLevel(log.LevelInfo)
|
|
if logLevel := os.Getenv("LOG_LEVEL"); strings.ToLower(logLevel) == "debug" {
|
|
log.SetLevel(log.LevelDebug)
|
|
}
|
|
log.Infof("StartCwtch(...)")
|
|
log.Debugf("builddate: %v buildver: %v", buildDate, buildVer)
|
|
|
|
// Quick hack check that we're being called with the correct params
|
|
// On android a stale worker could be calling us with "last apps" directory. Best to abort fast so the app can make a new worker
|
|
if runtime.GOOS == "android" {
|
|
fh, err := os.Open(torPath)
|
|
if err != nil {
|
|
log.Errorf("%v", err)
|
|
log.Errorf("failed to stat tor, skipping StartCwtch(). potentially normal if the app was reinstalled or the device was restarted; this workorder should get canceled soon")
|
|
return 1
|
|
}
|
|
_ = fh.Close()
|
|
}
|
|
go _startCwtch(appDir, torPath)
|
|
return 0
|
|
}
|
|
|
|
func _startCwtch(appDir string, torPath string) {
|
|
log.Infof("application: %v eventHandler: %v", application, eventHandler)
|
|
|
|
if application != nil {
|
|
log.Infof("_startCwtch detected existing application; resuming instead of relaunching")
|
|
ReconnectCwtchForeground()
|
|
return
|
|
}
|
|
|
|
log.Infof("Creating new EventHandler()")
|
|
eventHandler = utils.NewEventHandler()
|
|
|
|
|
|
// Exclude Tapir wire Messages
|
|
//(We need a TRACE level)
|
|
log.ExcludeFromPattern("service.go")
|
|
|
|
// Environment variables don't get '~' expansion so if CWTCH_DIR was set, it likely needs manual handling
|
|
usr, _ := user.Current()
|
|
homeDir := usr.HomeDir
|
|
if appDir == "~" {
|
|
appDir = homeDir
|
|
} else if strings.HasPrefix(appDir, "~/") {
|
|
appDir = path.Join(homeDir, appDir[2:])
|
|
}
|
|
|
|
// Ensure that the application directory exists...and then initialize settings..
|
|
err := os.MkdirAll(appDir, 0700)
|
|
if err != nil {
|
|
log.Errorf("Error creating appDir %v: %v\n", appDir, err)
|
|
eventHandler.Push(event.NewEventList(CwtchStartError, event.Error, fmt.Sprintf("Error creating appDir %v: %v", appDir, err)))
|
|
return
|
|
}
|
|
|
|
log.Infof("Loading Cwtch Directory %v and tor path: %v", appDir, torPath)
|
|
settings.InitGlobalSettingsFile(appDir, app.DefactoPasswordForUnencryptedProfiles)
|
|
|
|
log.Infof("making directory %v", appDir)
|
|
err = os.MkdirAll(path.Join(appDir, "tor"), 0700)
|
|
|
|
if err != nil {
|
|
log.Errorf("error creating tor data directory: %v. Aborting app start up", err)
|
|
eventHandler.Push(event.NewEventList(CwtchStartError, event.Error, fmt.Sprintf("Error connecting to Tor: %v", err)))
|
|
return
|
|
}
|
|
|
|
// Allow the user of a custom torrc
|
|
globalAppDir = appDir
|
|
globalTorPath = torPath
|
|
settingsFile := app.LoadAppSettings(appDir)
|
|
// start with an Error ACN
|
|
erracn := connectivity.NewErrorACN(fmt.Errorf("initializing tor"))
|
|
globalACN = connectivity.NewProxyACN(&erracn)
|
|
application = app.NewApp(&globalACN, appDir, settingsFile)
|
|
|
|
// Subscribe to all App Events...
|
|
eventHandler.HandleApp(application)
|
|
|
|
// FIXME: This code exists to allow the Splash Screen test in the new UI integration tests to pass
|
|
// it doesn't actually fix the problem in theory, and we should get around to ensuring that application
|
|
// is safe to access even if shutdown is called concurrently...
|
|
if application == nil {
|
|
log.Errorf("startCwtch: primary application object has gone away. assuming application is closing.")
|
|
return
|
|
}
|
|
// Send global settings to the UI...
|
|
globalSettings := application.ReadSettings()
|
|
settingsJson, _ := json.Marshal(globalSettings)
|
|
application.GetPrimaryBus().Publish(event.NewEvent(settings.UpdateGlobalSettings, map[event.Field]string{event.Data: string(settingsJson)}))
|
|
log.Infof("libcwtch-go application launched")
|
|
application.GetPrimaryBus().Publish(event.NewEvent(settings.CwtchStarted, map[event.Field]string{}))
|
|
application.QueryACNVersion()
|
|
|
|
{{EXPERIMENT_REGISTER}}
|
|
|
|
|
|
// Finally attempt to set up a proper Tor
|
|
// Note: ResetTor launches an internal goroutine so this is non-blocking...
|
|
ResetTor()
|
|
}
|
|
|
|
// the pointer returned from this function **must** be freed using c_Free
|
|
//
|
|
//export c_GetAppBusEvent
|
|
func c_GetAppBusEvent() *C.char {
|
|
return C.CString(GetAppBusEvent())
|
|
}
|
|
|
|
// GetAppBusEvent blocks until an event
|
|
func GetAppBusEvent() string {
|
|
for eventHandler == nil {
|
|
log.Debugf("waiting for eventHandler != nil")
|
|
time.Sleep(time.Second)
|
|
}
|
|
|
|
var json = ""
|
|
for json == "" {
|
|
json = eventHandler.GetNextEvent()
|
|
}
|
|
return json
|
|
}
|
|
|
|
//export c_ReconnectCwtchForeground
|
|
func c_ReconnectCwtchForeground() {
|
|
ReconnectCwtchForeground()
|
|
}
|
|
|
|
// Like StartCwtch, but StartCwtch has already been called so we don't need to restart Tor etc (probably)
|
|
// Do need to re-send initial state tho, eg profiles that are already loaded
|
|
func ReconnectCwtchForeground() {
|
|
log.Infof("Reconnecting cwtch foreground")
|
|
if application == nil {
|
|
log.Errorf("ReconnectCwtchForeground: Application is nil, presuming stale thread, EXITING Reconnect\n")
|
|
return
|
|
}
|
|
|
|
// Repopulate the UI on Android by iterating through all loaded profiles and treating them as New Peer loads. The ReloadEvent field
|
|
// suppresses any listen/connection actions and simply sends updated attributes to the UI.
|
|
// The UI is designed in such a way that "NewPeer" events are treated as updates.
|
|
// TODO: if/when we break apart NewPeer into smaller chunks for the UI to fetch, the need to do this here will go away.
|
|
peerList := application.ListProfiles()
|
|
for _, onion := range peerList {
|
|
eventHandler.Push(event.NewEvent(event.NewPeer, map[event.Field]string{event.Identity: onion, utils.ReloadEvent: event.True}))
|
|
}
|
|
|
|
|
|
|
|
settingsJson, _ := json.Marshal(application.ReadSettings())
|
|
application.GetPrimaryBus().Publish(event.NewEvent(settings.UpdateGlobalSettings, map[event.Field]string{event.Data: string(settingsJson)}))
|
|
application.GetPrimaryBus().Publish(event.NewEvent(settings.CwtchStarted, map[event.Field]string{utils.ReloadEvent: event.True}))
|
|
application.QueryACNStatus()
|
|
application.QueryACNVersion()
|
|
}
|
|
|
|
//export c_ShutdownCwtch
|
|
func c_ShutdownCwtch() {
|
|
ShutdownCwtch()
|
|
}
|
|
|
|
// ShutdownCwtch is a safe way to shutdown any active cwtch applications and associated ACNs
|
|
func ShutdownCwtch() {
|
|
if application != nil {
|
|
// Kill the isolate
|
|
eventHandler.Push(event.NewEvent(event.Shutdown, map[event.Field]string{}))
|
|
// Allow for the shutdown events to go through and then purge everything else...
|
|
log.Infof("Shutting Down Application...")
|
|
application.Shutdown()
|
|
log.Infof("Shutting Down ACN...")
|
|
globalACN.Close()
|
|
log.Infof("Library Shutdown Complete!")
|
|
// do not remove - important for state checks elsewhere
|
|
application = nil
|
|
eventHandler = nil
|
|
}
|
|
}
|
|
|
|
// TODO: At some point these functions should also be autogenerated
|
|
|
|
// Attribute is a struct to return the dual values of an attempt at a Get*Attribute API call, meant to be json serialized
|
|
type Attribute struct {
|
|
Exists bool
|
|
Value string
|
|
}
|
|
|
|
//export c_SetProfileAttribute
|
|
func c_SetProfileAttribute(profile_ptr *C.char, profile_len C.int, key_ptr *C.char, key_len C.int, val_ptr *C.char, val_len C.int) {
|
|
profileOnion := C.GoStringN(profile_ptr, profile_len)
|
|
key := C.GoStringN(key_ptr, key_len)
|
|
value := C.GoStringN(val_ptr, val_len)
|
|
SetProfileAttribute(profileOnion, key, value)
|
|
}
|
|
|
|
// SetProfileAttribute provides a wrapper around profile.SetScopedZonedAttribute
|
|
// WARNING: Because this function is potentially dangerous all keys and zones must be added
|
|
// explicitly. If you are attempting to added behaviour to the UI that requires the existence of new keys
|
|
// you probably want to be building out functionality/subsystem in the UI itself.
|
|
// Key must have the format zone.key where Zone is defined in Cwtch. Unknown zones are not permitted.
|
|
func SetProfileAttribute(profileOnion string, key string, value string) {
|
|
profile := application.GetPeer(profileOnion)
|
|
if profile != nil {
|
|
zone, key := attr.ParseZone(key)
|
|
|
|
// TODO We only allow certain public.profile.key to be set for now.
|
|
// All other scopes and zones need to be added explicitly or handled by Cwtch.
|
|
if zone == attr.ProfileZone && key == constants.Name {
|
|
profile.SetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.Name, value)
|
|
} else if zone == attr.ProfileZone && key == constants.ProfileAttribute1 {
|
|
profile.SetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.ProfileAttribute1, value)
|
|
} else if zone == attr.ProfileZone && key == constants.ProfileAttribute2 {
|
|
profile.SetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.ProfileAttribute2, value)
|
|
} else if zone == attr.ProfileZone && key == constants.ProfileAttribute3 {
|
|
profile.SetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.ProfileAttribute3, value)
|
|
} else if zone == attr.ProfileZone && key == constants.ProfileStatus {
|
|
profile.SetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.ProfileStatus, value)
|
|
} else if zone == attr.ProfileZone && key == constants.PeerAutostart {
|
|
profile.SetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.PeerAutostart, value)
|
|
} else if zone == attr.ProfileZone && key == constants.PeerAppearOffline {
|
|
profile.SetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.PeerAppearOffline, value)
|
|
} else {
|
|
log.Errorf("attempted to set an attribute with an unknown zone: %v", key)
|
|
}
|
|
}
|
|
}
|
|
|
|
//export c_GetProfileAttribute
|
|
func c_GetProfileAttribute(profile_ptr *C.char, profile_len C.int, key_ptr *C.char, key_len C.int) *C.char {
|
|
profileOnion := C.GoStringN(profile_ptr, profile_len)
|
|
key := C.GoStringN(key_ptr, key_len)
|
|
return C.CString(GetProfileAttribute(profileOnion, key))
|
|
}
|
|
|
|
// GetProfileAttribute provides a wrapper around profile.GetScopedZonedAttribute
|
|
// Key must have the format zone.key where Zone is defined in Cwtch. Unknown zones are not permitted.
|
|
// Currently forcing the Public Scope
|
|
// Returns json of Attribute
|
|
func GetProfileAttribute(profileOnion string, key string) string {
|
|
profile := application.GetPeer(profileOnion)
|
|
if profile != nil {
|
|
zone, key := attr.ParseZone(key)
|
|
|
|
res, exists := profile.GetScopedZonedAttribute(attr.PublicScope, zone, key)
|
|
attr := Attribute{exists, res}
|
|
json, _ := json.Marshal(attr)
|
|
return string(json)
|
|
|
|
}
|
|
empty := Attribute{false, ""}
|
|
json, _ := json.Marshal(empty)
|
|
return (string(json))
|
|
}
|
|
|
|
//export c_SetConversationAttribute
|
|
func c_SetConversationAttribute(profile_ptr *C.char, profile_len C.int, conversation_id C.int, key_ptr *C.char, key_len C.int, val_ptr *C.char, val_len C.int) {
|
|
profileOnion := C.GoStringN(profile_ptr, profile_len)
|
|
key := C.GoStringN(key_ptr, key_len)
|
|
value := C.GoStringN(val_ptr, val_len)
|
|
SetConversationAttribute(profileOnion, int(conversation_id), key, value)
|
|
}
|
|
|
|
// SetConversationAttribute provides a wrapper around profile.SetProfileAttribute
|
|
// key is of format Zone.Key, and the API forces the Local Scope
|
|
func SetConversationAttribute(profileOnion string, conversationID int, key string, value string) {
|
|
profile := application.GetPeer(profileOnion)
|
|
zone, key := attr.ParseZone(key)
|
|
profile.SetConversationAttribute(conversationID, attr.LocalScope.ConstructScopedZonedPath(zone.ConstructZonedPath(key)), value)
|
|
}
|
|
|
|
//export c_GetConversationAttribute
|
|
func c_GetConversationAttribute(profile_ptr *C.char, profile_len C.int, conversation_id C.int, key_ptr *C.char, key_len C.int) *C.char {
|
|
profileOnion := C.GoStringN(profile_ptr, profile_len)
|
|
key := C.GoStringN(key_ptr, key_len)
|
|
return C.CString(GetConversationAttribute(profileOnion, int(conversation_id), key))
|
|
}
|
|
|
|
// GetGonversationAttribute provides a wrapper around profile.GetGonversationAttribute
|
|
// key is of format Scope.Zone.Key
|
|
// Returns json of an Attribute
|
|
func GetConversationAttribute(profileOnion string, conversationID int, key string) string {
|
|
profile := application.GetPeer(profileOnion)
|
|
if profile != nil {
|
|
scope, zonekey := attr.ParseScope(key)
|
|
zone, key := attr.ParseZone(zonekey)
|
|
|
|
res, err := profile.GetConversationAttribute(conversationID, scope.ConstructScopedZonedPath(zone.ConstructZonedPath(key)))
|
|
attr := Attribute{err == nil, res}
|
|
json, _ := json.Marshal(attr)
|
|
return string(json)
|
|
}
|
|
empty := Attribute{false, ""}
|
|
json, _ := json.Marshal(empty)
|
|
return (string(json))
|
|
}
|
|
|
|
|
|
//export c_ResetTor
|
|
func c_ResetTor() {
|
|
ResetTor()
|
|
}
|
|
|
|
|
|
var torLock sync.Mutex
|
|
|
|
func ResetTor() {
|
|
go func() {
|
|
// prevent concurrent calls to this method...
|
|
torLock.Lock()
|
|
defer torLock.Unlock()
|
|
log.Infof("Replacing ACN with new Tor...")
|
|
settings := application.ReadSettings()
|
|
|
|
globalACN.Close() // we need to close first if dateDir is the same, otherwise buildACN can't launch tor.
|
|
newAcn, settings, err := buildACN(settings, globalTorPath, globalAppDir)
|
|
// only update settings if successful.
|
|
if err == nil {
|
|
// Only update Tor specific settings...
|
|
currentSettings := application.ReadSettings()
|
|
currentSettings.TorCacheDir = settings.TorCacheDir
|
|
currentSettings.CustomControlPort = settings.CustomControlPort
|
|
currentSettings.CustomSocksPort = settings.CustomSocksPort
|
|
currentSettings.CustomTorrc = settings.CustomTorrc
|
|
application.UpdateSettings(currentSettings)
|
|
|
|
// We need to update settings on reset as buildACN can alter settings, otherwise the next reset will be broken...
|
|
settings = application.ReadSettings()
|
|
settingsJson, _ := json.Marshal(settings)
|
|
application.GetPrimaryBus().Publish(event.NewEvent(UpdateGlobalSettings, map[event.Field]string{event.Data: string(settingsJson)}))
|
|
}
|
|
|
|
// replace ACN regardlesss
|
|
globalACN.ReplaceACN(newAcn)
|
|
application.QueryACNStatus()
|
|
application.QueryACNVersion()
|
|
log.Infof("Restarted")
|
|
}()
|
|
}
|
|
|
|
const (
|
|
CwtchStarted = event.Type("CwtchStarted")
|
|
CwtchStartError = event.Type("CwtchStartError")
|
|
UpdateGlobalSettings = event.Type("UpdateGlobalSettings")
|
|
)
|
|
|
|
func buildACN(globalSettings settings.GlobalSettings, torPath string, appDir string) (connectivity.ACN, settings.GlobalSettings, error) {
|
|
|
|
socksPort := mrand.Intn(1000) + 9600
|
|
controlPort := socksPort + 1
|
|
|
|
// generate a random password (actually random, stored in memory, for the control port)
|
|
key := make([]byte, 64)
|
|
_, err := rand.Read(key)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
log.Infof("making directory %v", appDir)
|
|
err = os.MkdirAll(path.Join(appDir, "tor"), 0700)
|
|
|
|
if err != nil {
|
|
log.Errorf("error creating tor data directory: %v. Aborting app start up", err)
|
|
eventHandler.Push(event.NewEventList(CwtchStartError, event.Error, fmt.Sprintf("Error connecting to Tor: %v", err)))
|
|
erracn := connectivity.NewErrorACN(err)
|
|
return &erracn, globalSettings, err
|
|
}
|
|
|
|
if globalSettings.AllowAdvancedTorConfig {
|
|
controlPort = globalSettings.CustomControlPort
|
|
socksPort = globalSettings.CustomSocksPort
|
|
}
|
|
|
|
// Override Ports if on Tails...
|
|
if cwtchTails := os.Getenv("CWTCH_TAILS"); strings.ToLower(cwtchTails) == "true" {
|
|
log.Infof("CWTCH_TAILS environment variable set... overriding tor config...")
|
|
|
|
controlPort = 9051
|
|
|
|
// In tails 5.13 the control port was changed to 951
|
|
// so read the Tails Version File and if it exists...
|
|
b, err := os.ReadFile("/etc/amnesia/version")
|
|
if err == nil {
|
|
// the file should start with the version followed
|
|
// by a space...
|
|
versionEnd := strings.Index(string(b), " ")
|
|
versionStr := string(b)[:versionEnd]
|
|
version, err := strconv.ParseFloat(versionStr, 64)
|
|
if err == nil {
|
|
log.Infof("Confirming Tails Version: %v", version)
|
|
// assert the control port if we are at the dedicated version...
|
|
// we know this change happened sometime after 5.11
|
|
if version >= 5.13 {
|
|
controlPort = 951
|
|
}
|
|
} else {
|
|
log.Errorf("Unable to confirm Tails version. CWTCH_TAILS options may not function correctly.")
|
|
}
|
|
}
|
|
|
|
socksPort = 9050
|
|
globalSettings.CustomControlPort = controlPort
|
|
globalSettings.CustomSocksPort = socksPort
|
|
globalSettings.AllowAdvancedTorConfig = true
|
|
}
|
|
|
|
torrc := tor.NewTorrc().WithSocksPort(socksPort).WithOnionTrafficOnly().WithControlPort(controlPort).WithHashedPassword(base64.StdEncoding.EncodeToString(key))
|
|
// torrc.WithLog(path.Join(appDir, "tor", "tor.log"), tor.TorLogLevelNotice)
|
|
if globalSettings.UseCustomTorrc {
|
|
customTorrc := globalSettings.CustomTorrc
|
|
torrc.WithCustom(strings.Split(customTorrc, "\n"))
|
|
} else {
|
|
// Fallback to showing the freshly generated torrc for this session.
|
|
globalSettings.CustomTorrc = torrc.Preview()
|
|
globalSettings.CustomControlPort = controlPort
|
|
globalSettings.CustomSocksPort = socksPort
|
|
}
|
|
|
|
err = torrc.Build(path.Join(appDir, "tor", "torrc"))
|
|
|
|
if err != nil {
|
|
log.Errorf("error constructing torrc: %v", err)
|
|
eventHandler.Push(event.NewEventList(CwtchStartError, event.Error, fmt.Sprintf("Error connecting to Tor: %v", err)))
|
|
erracn := connectivity.NewErrorACN(err)
|
|
return &erracn, globalSettings, err
|
|
}
|
|
|
|
dataDir := globalSettings.TorCacheDir
|
|
if !globalSettings.UseTorCache {
|
|
|
|
// purge data dir directories if we are not using them for a cache
|
|
torDir := path.Join(appDir, "tor")
|
|
files, err := path.Glob(path.Join(torDir, "data-dir-*"))
|
|
if err != nil {
|
|
log.Errorf("could not construct filesystem glob: %v", err)
|
|
}
|
|
for _, f := range files {
|
|
if err := os.RemoveAll(f); err != nil {
|
|
log.Errorf("could not remove data-dir: %v", err)
|
|
}
|
|
}
|
|
|
|
if dataDir, err = os.MkdirTemp(torDir, "data-dir-"); err != nil {
|
|
eventHandler.Push(event.NewEventList(CwtchStartError, event.Error, fmt.Sprintf("Error connecting to Tor: %v", err)))
|
|
erracn := connectivity.NewErrorACN(err)
|
|
return &erracn, globalSettings, err
|
|
}
|
|
}
|
|
|
|
// Persist Current Data Dir as Tor Cache...
|
|
globalSettings.TorCacheDir = dataDir
|
|
|
|
acn, err := tor.NewTorACNWithAuth(appDir, torPath, dataDir, controlPort, tor.HashedPasswordAuthenticator{Password: base64.StdEncoding.EncodeToString(key)})
|
|
if err != nil {
|
|
log.Errorf("Error connecting to Tor replacing with ErrorACN: %v\n", err)
|
|
eventHandler.Push(event.NewEventList(CwtchStartError, event.Error, fmt.Sprintf("Error connecting to Tor: %v", err)))
|
|
erracn := connectivity.NewErrorACN(err)
|
|
acn = &erracn
|
|
}
|
|
return acn, globalSettings, err
|
|
}
|
|
|
|
|
|
//export c_UpdateSettings
|
|
func c_UpdateSettings(json_ptr *C.char, json_len C.int) {
|
|
settingsJson := C.GoStringN(json_ptr, json_len)
|
|
UpdateSettings(settingsJson)
|
|
}
|
|
|
|
func UpdateSettings(settingsJson string) {
|
|
var newSettings settings.GlobalSettings
|
|
json.Unmarshal([]byte(settingsJson), &newSettings)
|
|
application.UpdateSettings(newSettings)
|
|
{{EXPERIMENT_UPDATESETTINGS}}
|
|
}
|
|
|
|
//export c_GetDebugInfo
|
|
func c_GetDebugInfo() *C.char {
|
|
return C.CString(GetDebugInfo())
|
|
}
|
|
|
|
type DebugInfo struct {
|
|
BuildVersion string
|
|
BuildDate string
|
|
HeapAllocated float64
|
|
HeapInUse float64
|
|
HeapReleased float64
|
|
HeapObjects uint64
|
|
NumThreads uint64
|
|
SystemMemory float64
|
|
}
|
|
|
|
func GetDebugInfo() string {
|
|
|
|
var memstats runtime.MemStats
|
|
runtime.ReadMemStats(&memstats)
|
|
|
|
const MegaByte = 1024.0 * 1024.0
|
|
|
|
debugInfo := new(DebugInfo)
|
|
debugInfo.BuildVersion = buildVer
|
|
debugInfo.BuildDate = buildDate
|
|
debugInfo.HeapAllocated = float64(memstats.HeapAlloc) / MegaByte
|
|
debugInfo.HeapObjects = memstats.HeapObjects
|
|
debugInfo.NumThreads = uint64(runtime.NumGoroutine())
|
|
debugInfo.HeapReleased = float64(memstats.HeapReleased) / MegaByte
|
|
debugInfo.HeapInUse = float64(memstats.HeapInuse) / MegaByte
|
|
debugInfo.SystemMemory = float64(memstats.Sys) / MegaByte
|
|
|
|
if os.Getenv("CWTCH_PROFILE") == "1" {
|
|
pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
|
|
f, _ := os.Create("mem.prof")
|
|
pprof.WriteHeapProfile(f)
|
|
}
|
|
|
|
data, _ := json.Marshal(debugInfo)
|
|
return string(data)
|
|
}
|
|
|
|
{{BINDINGS}}
|
|
|
|
// Leave as is, needed by ffi
|
|
func main() {}
|