Deprecated QT-based UI for Cwtch: For the new Flutter UI go to: https://git.openprivacy.ca/cwtch.im/cwtch-ui https://cwtch.im
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.
 
 
 
 
 
 

367 lines
12 KiB

package main
import (
"crypto/rand"
libapp "cwtch.im/cwtch/app"
"cwtch.im/cwtch/event"
"cwtch.im/cwtch/event/bridge"
"cwtch.im/cwtch/peer"
"cwtch.im/ui/go/features/servers"
"cwtch.im/ui/go/handlers"
os2 "cwtch.im/ui/go/os"
"cwtch.im/ui/go/the"
"cwtch.im/ui/go/ui"
"encoding/base64"
"flag"
"git.openprivacy.ca/openprivacy/connectivity/tor"
"git.openprivacy.ca/openprivacy/log"
"github.com/therecipe/qt/androidextras"
"github.com/therecipe/qt/core"
"github.com/therecipe/qt/gui"
"github.com/therecipe/qt/network"
"github.com/therecipe/qt/qml"
"github.com/therecipe/qt/quickcontrols2"
mrand "math/rand"
"os"
"os/user"
"path"
"path/filepath"
"runtime"
"time"
)
const androidBaseDir = "/data/data/ca.openprivacy.cwtch.ui/"
var (
buildVer string
buildDate string
)
func init() {
// make go-defined types available in qml
ui.GrandCentralDispatcher_QmlRegisterType2("CustomQmlTypes", 1, 0, "GrandCentralDispatcher")
ui.MessageWrapper_QmlRegisterType2("CustomQmlTypes", 1, 0, "MessageWrapper")
}
func main() {
if os.Getenv("CWTCH_FOLDER") != "" {
the.CwtchDir = os.Getenv("CWTCH_FOLDER")
} else if runtime.GOOS == "android" {
the.CwtchDir = filepath.Join(androidBaseDir, "files")
} else {
usr, err := user.Current()
if err != nil {
log.Errorf("\nerror: could not load current user: %v\n", err)
os.Exit(1)
}
the.CwtchDir = filepath.Join(usr.HomeDir, ".cwtch")
}
// suppress event.NewMessageFromPeer so we can handle it ourselves
peer.DefaultEventsToHandle = []event.Type{
event.EncryptedGroupMessage,
event.PeerAcknowledgement,
event.NewGroupInvite,
event.PeerError,
event.SendMessageToGroupError,
event.NewGetValMessageFromPeer,
}
logfileDefault := "cwtch_log.txt"
flagDebug := flag.Bool("debug", false, "turn on extra debug level logging. WARNING: THIS MAY EXPOSE PRIVATE INFORMATION IN CONSOLE OUTPUT!")
flagLogFile := flag.Bool("logfile", false, "instead of console output, log to $HOME/.cwtch/"+logfileDefault)
flagLocal := flag.Bool("local", false, "load user interface from the local folder \"qml\" instead of the built-in UI")
flagService := flag.Bool("service", false, "run this process as an android service")
flagClientUI := flag.Bool("clientui", false, "start the UI as a client of a service app instead of a full app")
flag.Parse()
if *flagLogFile {
filelogger, err := log.NewFile(log.LevelInfo, filepath.Join(the.CwtchDir, logfileDefault))
if err == nil {
log.SetStd(filelogger)
}
}
if *flagDebug {
log.SetLevel(log.LevelDebug)
} else {
log.SetLevel(log.LevelInfo)
}
if !(*flagDebug && buildVer == "") {
// PF Onions
log.AddPrivacyFilter(func(s string) bool {
return len(s) == 56
})
// PF Group ids
log.AddPrivacyFilter(func(s string) bool {
return len(s) == 32
})
// Replace in prod
if buildVer != "" {
log.SetPrivacyFilterReplace(true)
}
}
log.ExcludeFromPattern("connection/connection")
log.ExcludeFromPattern("event/eventmanager")
log.ExcludeFromPattern("service.go")
log.ExcludeFromPattern("tor/BaseOnionService.go")
log.ExcludeFromPattern("applications/auth.go")
log.ExcludeFromPattern("connections/engine.go")
log.ExcludeFromPattern("bridge/pipeBridge.go")
log.ExcludeFromPattern("app/appBridge.go")
log.Infoln("ui main()")
if buildVer == "" && os.Getenv("CWTCH_FOLDER") == "" {
log.Infoln("Development build: using dev directory for dev profiles")
the.CwtchDir = filepath.Join(the.CwtchDir, "dev")
}
the.ACN = nil
the.Peer = nil
the.IPCBridge = nil
the.CwtchApp = nil
the.CwtchService = nil
os.MkdirAll(the.CwtchDir, 0700)
os.MkdirAll(path.Join(the.CwtchDir, "tor"), 0700)
if *flagService {
mainService()
} else {
clientUI := *flagClientUI || runtime.GOOS == "android"
mainUi(*flagLocal, clientUI)
}
if the.ACN != nil {
the.ACN.Close()
}
}
// QRunnable is a shim for QAndroidService and QGuiApplication
type QRunnable interface {
Exec() int
}
func mainService() {
log.Infoln("I am the service")
log.Infoln("Starting a cwtch app...")
go loadNetworkingAndFiles(nil, true, false)
var app QRunnable
if runtime.GOOS == "android" {
log.Infoln("Making QAndroidService...")
app = androidextras.NewQAndroidService(len(os.Args), os.Args)
} else {
log.Infoln("Making QGuiApplication...")
app = gui.NewQGuiApplication(len(os.Args), os.Args)
}
log.Infoln("Cwtch Service starting app.Exec")
app.Exec()
}
func mainUi(flagLocal bool, flagClientUI bool) {
log.Infof("I am the UI (client:%v)\n", flagClientUI)
app := gui.NewQGuiApplication(len(os.Args), os.Args)
// our globals
err := ui.InitGlobalSettingsFile(filepath.Join(the.CwtchDir, "global"), the.AppPassword)
if err != nil {
log.Errorf("Could not access global ui config: %v\n", err)
os.Exit(-1)
}
gcd := ui.NewGrandCentralDispatcher(nil)
gcd.SetOs(runtime.GOOS)
dir := core.QCoreApplication_ApplicationDirPath()
log.Infof("core.QCoreApplication_ApplicationDirPath(): %v\n", dir)
if runtime.GOOS == "android" {
gcd.SetAssetPath("assets:/")
} else if runtime.GOOS == "windows" {
// all of these access are QML based, and QML takes URIs which use forward slashes and translates them to local OS sperators
// also windows paths need to be like /c:/PATH
dir = "/" + dir
// QML uses '/' regardless of platform (so we use path.Join here not filepath.Join)
gcd.SetAssetPath("file://" + path.Join(dir, "assets") + "/")
} else {
if buildVer == "" || flagLocal {
if _, err := os.Stat(path.Join(dir, "assets")); !os.IsNotExist(err) {
gcd.SetAssetPath("file://" + path.Join(dir, "assets") + "/")
}
} else {
usr, err := user.Current()
if err != nil {
log.Errorf("\nerror: could not load current user: %v\n", err)
os.Exit(1)
}
localCwtch := path.Join(usr.HomeDir, ".local/share/cwtch")
if _, err := os.Stat(localCwtch); !os.IsNotExist(err) {
gcd.SetAssetPath("file://" + path.Join(localCwtch, "assets") + "/")
} else if _, err := os.Stat("/usr/share/cwtch"); !os.IsNotExist(err) {
gcd.SetAssetPath("file://" + "/usr/share/cwtch/assets/")
} else if _, err := os.Stat("/usr/local/share/cwtch/"); !os.IsNotExist(err) {
gcd.SetAssetPath("file://" + "/usr/local/share/cwtch/assets/")
} else if _, err := os.Stat(path.Join(dir, "assets")); !os.IsNotExist(err) {
gcd.SetAssetPath("file://" + path.Join(dir, "assets") + "/")
}
}
if gcd.AssetPath() == "" {
log.Errorf("Could not find assets folder")
os.Exit(-1)
}
}
log.Infof("gcd.assetPath = '%v'\n", gcd.AssetPath())
if buildVer != "" {
gcd.SetVersion(buildVer)
gcd.SetBuildDate(buildDate)
} else {
gcd.SetVersion("development")
gcd.SetBuildDate("now")
}
// this is to load local qml files quickly when developing
var qmlSource *core.QUrl
if flagLocal {
qmlSource = core.QUrl_FromLocalFile("./qml/main.qml")
} else {
qmlSource = core.NewQUrl3("qrc:/qml/main.qml", 0)
}
app.SetWindowIcon(gui.NewQIcon5(":/qml/images/cwtch-icon.png"))
// load english first so it becomes the default in case we don't have a .ts for the user's locale, or if it contains unfinished strings
translator := core.NewQTranslator(nil)
translator.Load("translation_en", ":/i18n/", "", "")
core.QCoreApplication_InstallTranslator(translator)
opaqueTranslator := core.NewQTranslator(nil)
opaqueTranslator.Load("translation_en", ":/qml/opaque/i18n/", "", "")
core.QCoreApplication_InstallTranslator(opaqueTranslator)
core.QCoreApplication_SetAttribute(core.Qt__AA_EnableHighDpiScaling, true)
quickcontrols2.QQuickStyle_SetStyle("Universe")
engine := qml.NewQQmlApplicationEngine(nil)
gcd.QMLEngine = engine
gcd.SetLocale(gcd.GlobalSettings.Locale)
// prevent qt from initiating network connections (possible deanon attempts!)
factory := qml.NewQQmlNetworkAccessManagerFactory()
factory.ConnectCreate(func(parent *core.QObject) *network.QNetworkAccessManager {
nam := network.NewQNetworkAccessManager(parent)
nam.SetNetworkAccessible(network.QNetworkAccessManager__NotAccessible)
proxy := network.NewQNetworkProxy()
proxy.SetHostName("0.0.0.0")
nam.SetProxy(proxy)
return nam
})
engine.SetNetworkAccessManagerFactory(factory)
engine.RootContext().SetContextProperty("gcd", gcd)
gcd.TimelineInterface = ui.NewMessageModel(nil)
engine.RootContext().SetContextProperty("mm", gcd.TimelineInterface)
engine.RootContext().SetContextProperty("androidCwtchActivity", gcd.AndroidCwtchActivity)
engine.Load(qmlSource)
go loadNetworkingAndFiles(gcd, false, flagClientUI)
log.Infoln("Cwtch App starting app.Exec")
app.Exec()
}
func loadACN() {
torpath := "tor"
if runtime.GOOS == "android" {
torpath = filepath.Join(androidBaseDir, "lib/libtor.so")
} else if runtime.GOOS == "windows" {
ex, err := os.Executable()
if err != nil {
ex = ""
}
exPath := filepath.Dir(ex)
torpath = filepath.Join(exPath, "tor-0.4.4.6", "Tor", "tor.exe")
} else {
dir, _ := filepath.Abs(filepath.Dir(os.Args[0]))
if _, err := os.Stat(filepath.Join(dir, "tor")); os.IsNotExist(err) {
if _, err := os.Stat(filepath.Join(dir, "deploy", "linux", "tor")); os.IsNotExist(err) {
log.Warnln("Cannot find bundled Tor")
} else {
torpath = filepath.Join(dir, "deploy", "linux", "tor")
}
} else {
torpath = filepath.Join(dir, "tor")
}
}
// generate a random socks and control port (not real random...these are port numbers...)
mrand.Seed(int64(time.Now().Nanosecond()))
port := mrand.Intn(1000) + 9600
controlPort := port + 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)
}
// generate torrc on the fly
// TODO if we have been configured for it, use system tor (like orbot) - we need a way to config this in the UI first
tor.NewTorrc().WithSocksPort(port).WithOnionTrafficOnly().WithControlPort(controlPort).WithHashedPassword(base64.StdEncoding.EncodeToString(key)).Build(filepath.Join(the.CwtchDir, "tor", "torrc"))
the.ACN, err = tor.NewTorACNWithAuth(the.CwtchDir, torpath, controlPort, tor.HashedPasswordAuthenticator{base64.StdEncoding.EncodeToString(key)})
if err != nil {
// TODO: turn into UI error: status panel?
log.Errorf("Could not start Tor: %v", err)
os.Exit(1)
}
}
func loadNetworkingAndFiles(gcd *ui.GrandCentralDispatcher, service bool, clientUI bool) {
if service || clientUI || runtime.GOOS == "android" {
clientIn := filepath.Join(the.CwtchDir, "clientIn")
serviceIn := filepath.Join(the.CwtchDir, "serviceIn")
if service {
loadACN()
serviceBridge := bridge.NewPipeBridgeService(serviceIn, clientIn)
log.Infoln("Creating New App Service")
the.CwtchService = libapp.NewAppService(the.ACN, the.CwtchDir, serviceBridge)
} else {
clientBridge := bridge.NewPipeBridgeClient(clientIn, serviceIn)
log.Infoln("Creating New App Client")
the.CwtchApp = libapp.NewAppClient(the.CwtchDir, clientBridge)
}
} else {
if gcd.GlobalSettings.PreviousPid != -1 {
// Todo: Check if there is an older version of the ACN running...
log.Debugf("checking to see if we shutdown the previous ACN (%v)", gcd.GlobalSettings.PreviousPid)
os2.CheckProcessAndKill(uint64(gcd.GlobalSettings.PreviousPid), "tor")
}
loadACN()
pid, err := the.ACN.GetPID()
if err == nil {
gcd.GlobalSettings.PreviousPid = int64(pid)
ui.WriteGlobalSettings(gcd.GlobalSettings)
} else {
log.Errorf("error fetching pid from ACN %v", err)
}
log.Infoln("Creating New App")
the.CwtchApp = libapp.NewApp(the.ACN, the.CwtchDir)
}
if !service {
the.AppBus = the.CwtchApp.GetPrimaryBus()
subscribed := make(chan bool)
go handlers.App(gcd, subscribed, clientUI)
go servers.LaunchServiceManager(gcd, the.ACN, path.Join(the.CwtchDir, "servers"))
<-subscribed
}
}