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" "fmt" "git.openprivacy.ca/openprivacy/connectivity" "git.openprivacy.ca/openprivacy/log" "io/ioutil" "os" path "path/filepath" "strconv" "sync" ) type applicationCore struct { eventBuses map[string]event.Manager directory string coremutex sync.Mutex } type application struct { applicationCore 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) ChangePeerPassword(onion, oldpass, newpass string) 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) func newAppCore(appDirectory string) *applicationCore { appCore := &applicationCore{eventBuses: make(map[string]event.Manager), directory: appDirectory} os.MkdirAll(path.Join(appCore.directory, "profiles"), 0700) return appCore } // 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) app := &application{engines: make(map[string]connections.Engine), applicationCore: *newAppCore(appDirectory), appBus: event.NewEventManager()} app.appletPeers.init() app.appletACN.init(acn, app.getACNStatusHandler()) return app } func (ac *applicationCore) DeletePeer(onion string) { ac.coremutex.Lock() defer ac.coremutex.Unlock() ac.eventBuses[onion].Shutdown() delete(ac.eventBuses, onion) } 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.applicationCore.DeletePeer(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) ChangePeerPassword(onion, oldpass, newpass string) { app.eventBuses[onion].Publish(event.NewEventList(event.ChangePassword, event.Password, oldpass, event.NewPassword, newpass)) } 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 (ac *applicationCore) LoadProfiles(password string, timeline bool, loadProfileFn LoadProfileFn) error { files, err := ioutil.ReadDir(path.Join(ac.directory, "profiles")) if err != nil { return fmt.Errorf("error: cannot read profiles directory: %v", err) } for _, file := range files { // Attempt to load an encrypted database profileDirectory := path.Join(ac.directory, "profiles", file.Name()) profile, err := peer.FromEncryptedDatabase(profileDirectory, password) if err == nil { // return the load the profile... log.Infof("loading profile from new-type storage database...") loadProfileFn(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(timeline) cps, err := peer.CreateEncryptedStore(profileDirectory, password) if err != nil { log.Errorf("error creating encrypted store: %v", err) } profile := peer.ImportLegacyProfile(legacyProfile, cps) loadProfileFn(profile) } } return nil } // 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 app.applicationCore.LoadProfiles(password, true, func(profile peer.CwtchPeer) { app.appmutex.Lock() // 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})) count++ } else { // Otherwise shutdown the connections profile.Shutdown() } app.appmutex.Unlock() }) if count == 0 { message := event.NewEventList(event.AppError, event.Error, event.AppErrLoaded0) app.appBus.Publish(message) } } // 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 (ac *applicationCore) GetEventBus(onion string) event.Manager { if manager, ok := ac.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") }