package app import ( "cwtch.im/cwtch/app/plugins" "cwtch.im/cwtch/event" "cwtch.im/cwtch/model" "cwtch.im/cwtch/peer" "cwtch.im/cwtch/protocol/connections" "cwtch.im/cwtch/storage" "fmt" "git.openprivacy.ca/cwtch.im/tapir/primitives" "git.openprivacy.ca/openprivacy/connectivity" "git.openprivacy.ca/openprivacy/log" "io/ioutil" "os" "path" "strconv" "sync" ) // AttributeTag is a const name for a peer attribute that can be set at creation time, for example for versioning info const AttributeTag = "tag" type applicationCore struct { eventBuses map[string]event.Manager directory string coremutex sync.Mutex } type application struct { applicationCore appletPeers appletACN appletPlugins storage map[string]storage.ProfileStore 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) CreatePeer(name string, password string) CreateTaggedPeer(name string, password string, tag string) DeletePeer(onion string, currentPassword string) bool 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 ListPeers() map[string]string } // LoadProfileFn is the function signature for a function in an app that loads a profile type LoadProfileFn func(profile *model.Profile, store storage.ProfileStore) 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{storage: make(map[string]storage.ProfileStore), engines: make(map[string]connections.Engine), applicationCore: *newAppCore(appDirectory), appBus: event.NewEventManager()} app.appletPeers.init() app.appletACN.init(acn, app.getACNStatusHandler()) return app } // CreatePeer creates a new Peer with a given name and core required accessories (eventbus) func (ac *applicationCore) CreatePeer(name string) (*model.Profile, error) { log.Debugf("CreatePeer(%v)\n", name) profile := storage.NewProfile(name) ac.coremutex.Lock() defer ac.coremutex.Unlock() _, exists := ac.eventBuses[profile.Onion] if exists { return nil, fmt.Errorf("error: profile for onion %v already exists", profile.Onion) } eventBus := event.NewEventManager() ac.eventBuses[profile.Onion] = eventBus return profile, nil } 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) { profile, err := app.applicationCore.CreatePeer(name) if err != nil { app.appBus.Publish(event.NewEventList(event.PeerError, event.Error, err.Error())) return } profileStore := storage.CreateProfileWriterStore(app.eventBuses[profile.Onion], path.Join(app.directory, "profiles", profile.LocalID), password, profile) app.storage[profile.Onion] = profileStore pc := app.storage[profile.Onion].GetProfileCopy(true) p := peer.FromProfile(pc) p.Init(app.eventBuses[profile.Onion]) peerAuthorizations := profile.ContactsAuthorizations() // TODO: Would be nice if ProtocolEngine did not need to explicitly be given the Private Key. identity := primitives.InitializeIdentity(profile.Name, &profile.Ed25519PrivateKey, &profile.Ed25519PublicKey) engine := connections.NewProtocolEngine(identity, profile.Ed25519PrivateKey, app.acn, app.eventBuses[profile.Onion], peerAuthorizations) app.peers[profile.Onion] = p app.engines[profile.Onion] = engine if tag != "" { p.SetAttribute(AttributeTag, tag) } app.appBus.Publish(event.NewEvent(event.NewPeer, map[event.Field]string{event.Identity: profile.Onion, event.Created: event.True})) } // CreatePeer creates a new Peer with the given name and required accessories (eventbus, storage, protocol engine) func (app *application) CreatePeer(name string, password string) { app.CreateTaggedPeer(name, password, "") } func (app *application) DeletePeer(onion string, password string) bool { log.Infof("DeletePeer called on %v\n", onion) app.appmutex.Lock() defer app.appmutex.Unlock() if app.storage[onion].CheckPassword(password) { app.appletPlugins.ShutdownPeer(onion) app.plugins.Delete(onion) app.peers[onion].Shutdown() delete(app.peers, onion) app.engines[onion].Shutdown() delete(app.engines, onion) app.storage[onion].Shutdown() app.storage[onion].Delete() delete(app.storage, 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) return true } return false } 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 { eventBus := event.NewEventManager() profileStore, err := storage.LoadProfileWriterStore(eventBus, path.Join(ac.directory, "profiles", file.Name()), password) if err != nil { continue } profile := profileStore.GetProfileCopy(timeline) _, exists := ac.eventBuses[profile.Onion] if exists { profileStore.Shutdown() eventBus.Shutdown() log.Errorf("profile for onion %v already exists", profile.Onion) continue } ac.coremutex.Lock() ac.eventBuses[profile.Onion] = eventBus ac.coremutex.Unlock() loadProfileFn(profile, profileStore) } 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 *model.Profile, profileStore storage.ProfileStore) { peer := peer.FromProfile(profile) peer.Init(app.eventBuses[profile.Onion]) peerAuthorizations := profile.ContactsAuthorizations() identity := primitives.InitializeIdentity(profile.Name, &profile.Ed25519PrivateKey, &profile.Ed25519PublicKey) engine := connections.NewProtocolEngine(identity, profile.Ed25519PrivateKey, app.acn, app.eventBuses[profile.Onion], peerAuthorizations) app.appmutex.Lock() app.peers[profile.Onion] = peer app.storage[profile.Onion] = profileStore app.engines[profile.Onion] = engine app.appmutex.Unlock() app.appBus.Publish(event.NewEvent(event.NewPeer, map[event.Field]string{event.Identity: profile.Onion, event.Created: event.False})) count++ }) 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.peerLock.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.peerLock.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.storage[onion].Shutdown() delete(app.storage, 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() app.appletPlugins.ShutdownPeer(id) app.engines[id].Shutdown() app.storage[id].Shutdown() app.eventBuses[id].Shutdown() } app.appBus.Shutdown() }