package app import ( "cwtch.im/cwtch/app/plugins" "cwtch.im/cwtch/event" "cwtch.im/cwtch/model" "cwtch.im/cwtch/model/attr" "cwtch.im/cwtch/model/constants" "cwtch.im/cwtch/peer" "cwtch.im/cwtch/protocol/connections" "cwtch.im/cwtch/storage" "git.openprivacy.ca/openprivacy/connectivity" "git.openprivacy.ca/openprivacy/log" "io/ioutil" "os" path "path/filepath" "strconv" "sync" ) type application struct { eventBuses map[string]event.Manager directory string coremutex sync.Mutex appletPeers appletACN appletPlugins engines map[string]connections.Engine appBus event.Manager appmutex sync.Mutex } // Application is a full cwtch peer application. It allows management, usage and storage of multiple peers type Application interface { LoadProfiles(password string) CreateTaggedPeer(name string, password string, tag string) DeletePeer(onion string, currentPassword string) AddPeerPlugin(onion string, pluginID plugins.PluginID) LaunchPeers() GetPrimaryBus() event.Manager GetEventBus(onion string) event.Manager QueryACNStatus() QueryACNVersion() ShutdownPeer(string) Shutdown() GetPeer(onion string) peer.CwtchPeer ListProfiles() []string } // LoadProfileFn is the function signature for a function in an app that loads a profile type LoadProfileFn func(profile peer.CwtchPeer) // NewApp creates a new app with some environment awareness and initializes a Tor Manager func NewApp(acn connectivity.ACN, appDirectory string) Application { log.Debugf("NewApp(%v)\n", appDirectory) os.MkdirAll(path.Join(appDirectory, "profiles"), 0700) app := &application{engines: make(map[string]connections.Engine), eventBuses: make(map[string]event.Manager), directory: appDirectory, appBus: event.NewEventManager()} app.appletPeers.init() app.appletACN.init(acn, app.getACNStatusHandler()) return app } func (app *application) CreateTaggedPeer(name string, password string, tag string) { app.appmutex.Lock() defer app.appmutex.Unlock() profileDirectory := path.Join(app.directory, "profiles", model.GenerateRandomID()) profile, err := peer.CreateEncryptedStorePeer(profileDirectory, name, password) if err != nil { log.Errorf("Error Creating Peer: %v", err) app.appBus.Publish(event.NewEventList(event.PeerError, event.Error, err.Error())) return } eventBus := event.NewEventManager() app.eventBuses[profile.GetOnion()] = eventBus profile.Init(app.eventBuses[profile.GetOnion()]) app.peers[profile.GetOnion()] = profile app.engines[profile.GetOnion()], _ = profile.GenerateProtocolEngine(app.acn, app.eventBuses[profile.GetOnion()]) if tag != "" { profile.SetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Tag, tag) } app.appBus.Publish(event.NewEvent(event.NewPeer, map[event.Field]string{event.Identity: profile.GetOnion(), event.Created: event.True})) } func (app *application) DeletePeer(onion string, password string) { log.Infof("DeletePeer called on %v\n", onion) app.appmutex.Lock() defer app.appmutex.Unlock() if app.peers[onion].CheckPassword(password) { app.appletPlugins.ShutdownPeer(onion) app.plugins.Delete(onion) // Shutdown and Remove the Engine app.engines[onion].Shutdown() delete(app.engines, onion) app.peers[onion].Shutdown() app.peers[onion].Delete() delete(app.peers, onion) app.eventBuses[onion].Publish(event.NewEventList(event.ShutdownPeer, event.Identity, onion)) app.coremutex.Lock() defer app.coremutex.Unlock() app.eventBuses[onion].Shutdown() delete(app.eventBuses, onion) log.Debugf("Delete peer for %v Done\n", onion) app.appBus.Publish(event.NewEventList(event.PeerDeleted, event.Identity, onion)) return } app.appBus.Publish(event.NewEventList(event.AppError, event.Error, event.PasswordMatchError, event.Identity, onion)) } func (app *application) AddPeerPlugin(onion string, pluginID plugins.PluginID) { app.AddPlugin(onion, pluginID, app.eventBuses[onion], app.acn) } // LoadProfiles takes a password and attempts to load any profiles it can from storage with it and create Peers for them func (app *application) LoadProfiles(password string) { count := 0 migrating := false files, err := ioutil.ReadDir(path.Join(app.directory, "profiles")) if err != nil { log.Errorf("error: cannot read profiles directory: %v", err) return } for _, file := range files { // Attempt to load an encrypted database profileDirectory := path.Join(app.directory, "profiles", file.Name()) profile, err := peer.FromEncryptedDatabase(profileDirectory, password) loaded := false if err == nil { // return the load the profile... log.Infof("loading profile from new-type storage database...") loaded = app.installProfile(profile) } else { // On failure attempt to load a legacy profile profileStore, err := storage.LoadProfileWriterStore(profileDirectory, password) if err != nil { continue } log.Infof("found legacy profile. importing to new database structure...") legacyProfile := profileStore.GetProfileCopy(true) if !migrating { migrating = true app.appBus.Publish(event.NewEventList(event.StartingStorageMiragtion)) } cps, err := peer.CreateEncryptedStore(profileDirectory, password) if err != nil { log.Errorf("error creating encrypted store: %v", err) } profile := peer.ImportLegacyProfile(legacyProfile, cps) loaded = app.installProfile(profile) } if loaded { count++ } } if count == 0 { message := event.NewEventList(event.AppError, event.Error, event.AppErrLoaded0) app.appBus.Publish(message) } if migrating { app.appBus.Publish(event.NewEventList(event.DoneStorageMigration)) } } // installProfile takes a profile and if it isn't loaded in the app, installs it and returns true func (app *application) installProfile(profile peer.CwtchPeer) bool { app.appmutex.Lock() defer app.appmutex.Unlock() // Only attempt to finalize the profile if we don't have one loaded... if app.peers[profile.GetOnion()] == nil { eventBus := event.NewEventManager() app.eventBuses[profile.GetOnion()] = eventBus profile.Init(app.eventBuses[profile.GetOnion()]) app.peers[profile.GetOnion()] = profile app.engines[profile.GetOnion()], _ = profile.GenerateProtocolEngine(app.acn, app.eventBuses[profile.GetOnion()]) app.appBus.Publish(event.NewEvent(event.NewPeer, map[event.Field]string{event.Identity: profile.GetOnion(), event.Created: event.False})) return true } // Otherwise shutdown the connections profile.Shutdown() return false } // GetPrimaryBus returns the bus the Application uses for events that aren't peer specific func (app *application) GetPrimaryBus() event.Manager { return app.appBus } // GetEventBus returns a cwtchPeer's event bus func (app *application) GetEventBus(onion string) event.Manager { if manager, ok := app.eventBuses[onion]; ok { return manager } return nil } func (app *application) getACNStatusHandler() func(int, string) { return func(progress int, status string) { progStr := strconv.Itoa(progress) app.appmutex.Lock() app.appBus.Publish(event.NewEventList(event.ACNStatus, event.Progress, progStr, event.Status, status)) for _, bus := range app.eventBuses { bus.Publish(event.NewEventList(event.ACNStatus, event.Progress, progStr, event.Status, status)) } app.appmutex.Unlock() } } func (app *application) QueryACNStatus() { prog, status := app.acn.GetBootstrapStatus() app.getACNStatusHandler()(prog, status) } func (app *application) QueryACNVersion() { version := app.acn.GetVersion() app.appBus.Publish(event.NewEventList(event.ACNVersion, event.Data, version)) } // ShutdownPeer shuts down a peer and removes it from the app's management func (app *application) ShutdownPeer(onion string) { app.appmutex.Lock() defer app.appmutex.Unlock() app.eventBuses[onion].Shutdown() delete(app.eventBuses, onion) app.peers[onion].Shutdown() delete(app.peers, onion) app.engines[onion].Shutdown() delete(app.engines, onion) app.appletPlugins.Shutdown() } // Shutdown shutsdown all peers of an app and then the tormanager func (app *application) Shutdown() { for id, peer := range app.peers { peer.Shutdown() log.Debugf("Shutting Down Peer %v", id) app.appletPlugins.ShutdownPeer(id) log.Debugf("Shutting Down Engines for %v", id) app.engines[id].Shutdown() log.Debugf("Shutting Down Bus for %v", id) app.eventBuses[id].Shutdown() } log.Debugf("Shutting Down App") app.appBus.Shutdown() log.Debugf("Shut Down Complete") }