package app import ( "crypto/rand" "cwtch.im/cwtch/connectivity/tor" "cwtch.im/cwtch/peer" "cwtch.im/cwtch/storage" "encoding/hex" "fmt" "io/ioutil" "log" "os" "path" "path/filepath" "sync" ) type application struct { peers map[string]peer.CwtchPeer torManager *tor.Manager directory string mutex sync.Mutex primaryonion string storage map[string]storage.ProfileStore } // Application is a full cwtch peer application. It allows management, usage and storage of multiple peers type Application interface { SaveProfile(cwtchPeer peer.CwtchPeer) LoadProfiles(password string) error CreatePeer(name string, password string) (peer.CwtchPeer, error) PrimaryIdentity() peer.CwtchPeer GetPeer(onion string) peer.CwtchPeer ListPeers() map[string]string GetTorStatus() (map[string]string, error) Shutdown() } // NewApp creates a new app with some environment awareness and initializes a Tor Manager func NewApp(appDirectory string, torPath string) (Application, error) { log.Printf("NewApp(%v, %v)\n", appDirectory, torPath) app := &application{peers: make(map[string]peer.CwtchPeer), storage: make(map[string]storage.ProfileStore), directory: appDirectory} os.MkdirAll(path.Join(appDirectory, "tor"), 0700) os.Mkdir(path.Join(app.directory, "profiles"), 0700) err := app.startTor(torPath) if err != nil { return nil, err } return app, nil } func generateRandomFilename() string { randBytes := make([]byte, 16) rand.Read(randBytes) return filepath.Join(hex.EncodeToString(randBytes)) } func (app *application) SaveProfile(p peer.CwtchPeer) { app.mutex.Lock() defer app.mutex.Unlock() app.peers[p.GetProfile().Onion] = p app.storage[p.GetProfile().Onion].Save(p) } // NewProfile creates a new cwtchPeer with a given name. func (app *application) CreatePeer(name string, password string) (peer.CwtchPeer, error) { log.Printf("CreatePeer(%v)\n", name) randomFileName := generateRandomFilename() fileStore := storage.CreateFileProfileStore(path.Join(app.directory, "profiles", randomFileName), password) p := peer.NewCwtchPeer(name) err := fileStore.Save(p) if err != nil { return nil, err } app.startPeer(p) _, exists := app.peers[p.GetProfile().Onion] if exists { p.Shutdown() return nil, fmt.Errorf("Error: profile for onion %v already exists", p.GetProfile().Onion) } app.mutex.Lock() app.peers[p.GetProfile().Onion] = p app.storage[p.GetProfile().Onion] = fileStore app.mutex.Unlock() return p, nil } func (app *application) LoadProfiles(password string) error { files, err := ioutil.ReadDir(path.Join(app.directory, "profiles")) if err != nil { return fmt.Errorf("Error: cannot read profiles directory: %v", err) } for _, file := range files { fileStore := storage.CreateFileProfileStore(path.Join(app.directory, "profiles", file.Name()), password) p, err := fileStore.Load() if err != nil { continue } _, exists := app.peers[p.GetProfile().Onion] if exists { p.Shutdown() log.Printf("Error: profile for onion %v already exists", p.GetProfile().Onion) continue } app.startPeer(p) app.mutex.Lock() app.peers[p.GetProfile().Onion] = p app.storage[p.GetProfile().Onion] = fileStore if app.primaryonion == "" { app.primaryonion = p.GetProfile().Onion } app.mutex.Unlock() } return nil } // startTor will create a local torrc if needed func (app *application) startTor(torPath string) error { // Creating a local cwtch tor server config for the user // creating $app.directory/torrc file // SOCKSPort socksPort // ControlPort controlPort torrc := path.Join(app.directory, "tor", "torrc") if _, err := os.Stat(torrc); os.IsNotExist(err) { log.Printf("writing torrc to: %v\n", torrc) file, err := os.Create(torrc) if err != nil { return err } fmt.Fprintf(file, "SOCKSPort %d\nControlPort %d\nCookieAuthentication 0\nSafeSocks 1\n", 9050, 9051) file.Close() } tm, err := tor.NewTorManager(9050, 9051, torPath, torrc) if err != nil { return err } app.torManager = tm return nil } func (app *application) startPeer(peer peer.CwtchPeer) { go func() { e := peer.Listen() if e != nil { log.Fatalf("ERROR: peer %v has crashed with: %v\n", peer.GetProfile().Onion, e) } }() } // ListPeers returns a map of onions to their profile's Name func (app *application) ListPeers() map[string]string { keys := map[string]string{} for k, p := range app.peers { keys[k] = p.GetProfile().Name } return keys } // PrimaryIdentity returns a cwtchPeer for a given onion address func (app *application) PrimaryIdentity() peer.CwtchPeer { return app.peers[app.primaryonion] } // GetPeer returns a cwtchPeer for a given onion address func (app *application) GetPeer(onion string) peer.CwtchPeer { if peer, ok := app.peers[onion]; ok { return peer } return nil } // GetTorStatus returns tor control port bootstrap-phase status info in a map func (app *application) GetTorStatus() (map[string]string, error) { return app.torManager.GetStatus() } // Shutdown shutsdown all peers of an app and then the tormanager func (app *application) Shutdown() { for _, peer := range app.peers { peer.Shutdown() } app.torManager.Shutdown() }