//package cwtch package main import "C" import ( "crypto/rand" "cwtch.im/cwtch/app" "cwtch.im/cwtch/event" "cwtch.im/cwtch/model" "cwtch.im/cwtch/model/attr" "cwtch.im/cwtch/peer" contact "git.openprivacy.ca/flutter/libcwtch-go/features/contacts" "git.openprivacy.ca/flutter/libcwtch-go/features/groups" "git.openprivacy.ca/openprivacy/connectivity" "encoding/json" "fmt" "git.openprivacy.ca/flutter/libcwtch-go/constants" "git.openprivacy.ca/flutter/libcwtch-go/utils" "encoding/base64" "git.openprivacy.ca/openprivacy/connectivity/tor" "git.openprivacy.ca/openprivacy/log" mrand "math/rand" "os" "path" "path/filepath" "time" ) const ( profileOnion = event.Field("profileOnion") ) var application app.Application var eventHandler *utils.EventHandler var acnQueue event.Queue var contactEventsQueue event.Queue var globalACN connectivity.ACN //export c_StartCwtch func c_StartCwtch(dir_c *C.char, len C.int, tor_c *C.char, torLen C.int) { dir := C.GoStringN(dir_c, len) tor := C.GoStringN(tor_c, torLen) StartCwtch(dir, tor) } func StartCwtch(appDir string, torPath string) { log.SetLevel(log.LevelInfo) // Exclude Tapir wire Messages (We need a TRACE level) log.ExcludeFromPattern("service.go") utils.InitGlobalSettingsFile(appDir, "be gay do crime") log.Infof("Loading Cwtch Directory %v and tor path: %v", appDir, torPath) 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) } log.Infof("making directory %v", appDir) os.MkdirAll(path.Join(appDir, "/.tor", "tor"), 0700) tor.NewTorrc().WithSocksPort(port).WithOnionTrafficOnly().WithControlPort(controlPort).WithHashedPassword(base64.StdEncoding.EncodeToString(key)).Build(filepath.Join(appDir, ".tor", "tor", "torrc")) acn, err := tor.NewTorACNWithAuth(path.Join(appDir, "/.tor"), torPath, controlPort, tor.HashedPasswordAuthenticator{Password: base64.StdEncoding.EncodeToString(key)}) if err != nil { log.Errorf("\nError connecting to Tor: %v\n", err) } //acn.WaitTillBootstrapped() globalACN = acn newApp := app.NewApp(acn, appDir) acnQueue = event.NewQueue() newApp.GetPrimaryBus().Subscribe(event.ACNStatus, acnQueue) newApp.GetPrimaryBus().Subscribe(utils.UpdateGlobalSettings, acnQueue) newApp.GetPrimaryBus().Subscribe(utils.SetLoggingLevel, acnQueue) newApp.GetPrimaryBus().Subscribe(event.AppError, acnQueue) eventHandler = utils.NewEventHandler(newApp) peer.DefaultEventsToHandle = []event.Type{ event.EncryptedGroupMessage, event.NewMessageFromPeer, event.PeerAcknowledgement, event.NewGroupInvite, event.PeerError, event.SendMessageToGroupError, event.NewGetValMessageFromPeer, event.PeerStateChange, event.NewRetValMessageFromPeer, event.NewGroupInvite, event.ServerStateChange, event.ProtocolEngineStopped, } settings := utils.ReadGlobalSettings() settingsJson, _ := json.Marshal(settings) newApp.LoadProfiles("be gay do crime") application = newApp // Send global settings to the UI... application.GetPrimaryBus().Publish(event.NewEvent(utils.UpdateGlobalSettings, map[event.Field]string{event.Data: string(settingsJson)})) log.Infof("libcwtch-go application launched") } //export c_ACNEvents func c_ACNEvents() *C.char { return C.CString(ACNEvents()) } func ACNEvents() string { select { case myevent := <-acnQueue.OutChan(): return fmt.Sprintf("%v", myevent) default: return "" } } //export c_SendAppEvent // A generic method for Rebroadcasting App Events from a UI func c_SendAppEvent(json_ptr *C.char, json_len C.int) { eventJson := C.GoStringN(json_ptr, json_len) SendAppEvent(eventJson) } // SendAppEvent is a generic method for Rebroadcasting App Events from a UI func SendAppEvent(eventJson string) { // Convert the Event Json back to a typed Event Struct, this will make the // rest of the logic nicer. var new_event event.Event json.Unmarshal([]byte(eventJson), &new_event) log.Infof("Event: %v", new_event) // We need to update the local cache // Ideally I think this would be pusgit hed back into Cwtch switch new_event.EventType { case utils.UpdateGlobalSettings: var globalSettings utils.GlobalSettings err := json.Unmarshal([]byte(new_event.Data[event.Data]), &globalSettings) if err != nil { log.Errorf("Error Unmarshalling Settings %v [%v]", err, new_event.Data[event.Data]) } log.Debugf("New Settings %v", globalSettings) utils.WriteGlobalSettings(globalSettings) // Group Experiment Refresh groupHandler, err := groups.ExperimentGate(utils.ReadGlobalSettings().Experiments) if err == nil { for profileOnion := range application.ListPeers() { serverListForOnion := groupHandler.GetServerInfoList(application.GetPeer(profileOnion)) serversListBytes, _ := json.Marshal(serverListForOnion) eventHandler.Push(event.NewEvent(groups.UpdateServerInfo, map[event.Field]string{"ProfileOnion": profileOnion, groups.ServerList: string(serversListBytes)})) } } // Explicitly toggle blocking/unblocking of unknown connections for profiles // that have been loaded. if utils.ReadGlobalSettings().BlockUnknownConnections { for onion := range application.ListPeers() { application.GetPeer(onion).BlockUnknownConnections() } } else { for onion := range application.ListPeers() { application.GetPeer(onion).AllowUnknownConnections() } } case utils.SetLoggingLevel: _, warn := new_event.Data[utils.Warn] _, error := new_event.Data[utils.Error] _, debug := new_event.Data[utils.Debug] _, info := new_event.Data[utils.Info] // Assign logging level in priority order. The highest logging level wins in the // event of multiple fields. if info { log.SetLevel(log.LevelInfo) } else if warn { log.SetLevel(log.LevelWarn) } else if error { log.SetLevel(log.LevelError) } else if debug { log.SetLevel(log.LevelDebug) } default: // do nothing } } //export c_SendProfileEvent // A generic method for Rebroadcasting Profile Events from a UI func c_SendProfileEvent(onion_ptr *C.char, onion_len C.int, json_ptr *C.char, json_len C.int) { onion := C.GoStringN(onion_ptr, onion_len) eventJson := C.GoStringN(json_ptr, json_len) SendProfileEvent(onion, eventJson) } const ( AddContact = event.Type("AddContact") ImportString = event.Field("ImportString") ) // SendProfileEvent is a generic method for Rebroadcasting Profile Events from a UI func SendProfileEvent(onion string, eventJson string) { // Convert the Event Json back to a typed Event Struct, this will make the // rest of the logic nicer. var new_event event.Event json.Unmarshal([]byte(eventJson), &new_event) log.Infof("Event: %v %v", onion, new_event) // Get the correct Peer peer := application.GetPeer(onion) if peer == nil { return } // We need to update the local cache // Ideally I think this would be pushed back into Cwtch switch new_event.EventType { case AddContact: // Peer Functionality is Always Enabled, so we forgo the existence check... // TODO: Combine with GroupFunctionality to make a meta-handleimportstring that can do both! pf, _ := contact.FunctionalityGate(utils.ReadGlobalSettings().Experiments) err := pf.HandleImportString(peer, new_event.Data[ImportString]) eventHandler.Push(event.NewEvent(event.AppError, map[event.Field]string{event.Data: err.Error()})) case event.SetAttribute: peer.SetAttribute(new_event.Data[event.Key], new_event.Data[event.Data]) case event.SetPeerAttribute: peer.SetContactAttribute(new_event.Data[event.RemotePeer], new_event.Data[event.Key], new_event.Data[event.Data]) case event.SetPeerAuthorization: peer.SetContactAuthorization(new_event.Data[event.RemotePeer], model.Authorization(new_event.Data[event.Authorization])) // If approved (e.g. after an unblock) we want to kick off peering again... if model.Authorization(new_event.Data[event.Authorization]) == model.AuthApproved { peer.PeerWithOnion(new_event.Data[event.RemotePeer]) } default: // rebroadcast catch all log.Infof("Received Event %v for %v but no libCwtch handler found, relaying the event directly", new_event, onion) application.GetEventBus(onion).Publish(new_event) } } //export c_GetAppBusEvent func c_GetAppBusEvent() *C.char { return C.CString(GetAppBusEvent()) } // GetAppBusEvent blocks until an event func GetAppBusEvent() string { var json = "" for json == "" { json = eventHandler.GetNextEvent() } return json } //export c_GetProfileRepaintEvent func c_GetProfileRepaintEvent() int8 { if GetProfileRepaintEvent() { return 1 } else { return 0 } } func GetProfileRepaintEvent() bool { <-acnQueue.OutChan() return true } type Profile struct { Name string `json:"name"` Onion string `json:"onion"` ImagePath string `json:"imagePath"` } //export c_GetProfiles func c_GetProfiles() *C.char { return C.CString(GetProfiles()) } func GetProfiles() string { peerList := application.ListPeers() profiles := make([]Profile, len(peerList)) i := 0 for onion := range peerList { name, _ := application.GetPeer(onion).GetAttribute(attr.GetPublicScope(constants.Name)) profiles[i] = Profile{ Name: name, Onion: onion, ImagePath: "", } i += 1 } jsonBytes, _ := json.Marshal(profiles) return string(jsonBytes) } //export c_CreateProfile func c_CreateProfile(nick_ptr *C.char, nick_len C.int, pass_ptr *C.char, pass_len C.int) { CreateProfile(C.GoStringN(nick_ptr, nick_len), C.GoStringN(pass_ptr, pass_len)) } func CreateProfile(nick, pass string) { application.CreatePeer(nick, pass) } //export c_SelectProfile func c_SelectProfile(onion_ptr *C.char, onion_len C.int) *C.char { return C.CString(SelectProfile(C.GoStringN(onion_ptr, onion_len))) } func SelectProfile(onion string) string { contactEventsQueue = event.NewQueue() application.GetEventBus(onion).Subscribe(event.PeerStateChange, contactEventsQueue) return "" } //export c_LoadProfiles func c_LoadProfiles(passwordPtr *C.char, passwordLen C.int) { LoadProfiles(C.GoStringN(passwordPtr, passwordLen)) } func LoadProfiles(pass string) { application.LoadProfiles(pass) } //export c_ContactEvents func c_ContactEvents() *C.char { return C.CString(ContactEvents()) } func ContactEvents() string { select { case myevent := <-contactEventsQueue.OutChan(): return fmt.Sprintf("%v", myevent) default: return "" } } //export c_AcceptContact func c_AcceptContact(profilePtr *C.char, profileLen C.int, handlePtr *C.char, handleLen C.int) { AcceptContact(C.GoStringN(profilePtr, profileLen), C.GoStringN(handlePtr, handleLen)) } func AcceptContact(profile, handle string) { err := application.GetPeer(profile).SetContactAuthorization(handle, model.AuthApproved) if err == nil { eventHandler.Push(event.NewEvent(event.PeerStateChange, map[event.Field]string{ profileOnion: profile, event.RemotePeer: handle, "authorization": string(model.AuthApproved), })) } else { log.Errorf("error accepting contact: %s", err.Error()) } } //export c_BlockContact func c_BlockContact(profilePtr *C.char, profileLen C.int, handlePtr *C.char, handleLen C.int) { BlockContact(C.GoStringN(profilePtr, profileLen), C.GoStringN(handlePtr, handleLen)) } func BlockContact(profile, handle string) { err := application.GetPeer(profile).SetContactAuthorization(handle, model.AuthBlocked) if err == nil { eventHandler.Push(event.NewEvent(event.PeerStateChange, map[event.Field]string{ profileOnion: profile, event.RemotePeer: handle, "authorization": string(model.AuthBlocked), })) } else { log.Errorf("error blocking contact: %s", err.Error()) } } //export c_DebugResetContact func c_DebugResetContact(profilePtr *C.char, profileLen C.int, handlePtr *C.char, handleLen C.int) { DebugResetContact(C.GoStringN(profilePtr, profileLen), C.GoStringN(handlePtr, handleLen)) } func DebugResetContact(profile, handle string) { err := application.GetPeer(profile).SetContactAuthorization(handle, model.AuthUnknown) if err == nil { application.GetPrimaryBus().Publish(event.NewEvent(event.PeerStateChange, map[event.Field]string{ profileOnion: profile, event.RemotePeer: handle, "authorization": string(model.AuthUnknown), })) } else { log.Errorf("error resetting contact: %s", err.Error()) } } //export c_NumMessages func c_NumMessages(profile_ptr *C.char, profile_len C.int, handle_ptr *C.char, handle_len C.int) (n int) { profile := C.GoStringN(profile_ptr, profile_len) handle := C.GoStringN(handle_ptr, handle_len) return (NumMessages(profile, handle)) } func NumMessages(profile, handle string) (n int) { n = len(application.GetPeer(profile).GetContact(handle).Timeline.Messages) return } //export c_GetMessage func c_GetMessage(profile_ptr *C.char, profile_len C.int, handle_ptr *C.char, handle_len C.int, message_index C.int) *C.char { profile := C.GoStringN(profile_ptr, profile_len) handle := C.GoStringN(handle_ptr, handle_len) return C.CString(GetMessage(profile, handle, int(message_index))) } func GetMessage(profileOnion, handle string, message_index int) string { profile := application.GetPeer(profileOnion) ph := utils.NewPeerHelper(profile) var message model.Message if ph.IsGroup(handle) { if len(profile.GetGroup(handle).Timeline.Messages) > message_index { message = profile.GetGroup(handle).Timeline.Messages[message_index] } } else { message = profile.GetContact(handle).Timeline.Messages[message_index] } bytes, _ := json.Marshal(message) return string(bytes) } //export c_GetMessages func c_GetMessages(profile_ptr *C.char, profile_len C.int, handle_ptr *C.char, handle_len C.int, start C.int, end C.int) *C.char { profile := C.GoStringN(profile_ptr, profile_len) handle := C.GoStringN(handle_ptr, handle_len) return C.CString(GetMessages(profile, handle, int(start), int(end))) } func GetMessages(profile, handle string, start, end int) string { messages := application.GetPeer(profile).GetContact(handle).Timeline.Messages[start:end] bytes, _ := json.Marshal(messages) return string(bytes) } //export c_SendMessage func c_SendMessage(profile_ptr *C.char, profile_len C.int, handle_ptr *C.char, handle_len C.int, msg_ptr *C.char, msg_len C.int) { profile := C.GoStringN(profile_ptr, profile_len) handle := C.GoStringN(handle_ptr, handle_len) msg := C.GoStringN(msg_ptr, msg_len) SendMessage(profile, handle, msg) } func SendMessage(profileOnion, handle, msg string) { profile := application.GetPeer(profileOnion) ph := utils.NewPeerHelper(profile) if ph.IsGroup(handle) { groupHandler, err := groups.ExperimentGate(utils.ReadGlobalSettings().Experiments) if err == nil { groupHandler.SendMessage(profile, handle, msg) } } else { contactHandler, _ := contact.FunctionalityGate(utils.ReadGlobalSettings().Experiments) contactHandler.SendMessage(profile, handle, msg) } } //export c_ResetTor func c_ResetTor() { ResetTor() } func ResetTor() { globalACN.Restart() } //export c_CreateGroup func c_CreateGroup(profile_ptr *C.char, profile_len C.int, server_ptr *C.char, server_len C.int, name_ptr *C.char, name_len C.int) { profile := C.GoStringN(profile_ptr, profile_len) server := C.GoStringN(server_ptr, server_len) name := C.GoStringN(name_ptr, name_len) CreateGroup(profile, server, name) } // CreateGroup takes in a profile and server in addition to a name and creates a new group. func CreateGroup(profile string, server string, name string) { peer := application.GetPeer(profile) _, err := groups.ExperimentGate(utils.ReadGlobalSettings().Experiments) if err == nil { gid, _, err := peer.StartGroup(server) if err == nil { log.Debugf("created group %v on %v: $v", profile, server, gid) // set the group name peer.SetGroupAttribute(gid, attr.GetPublicScope(name), name) } else { log.Errorf("error creating group or %v on server %v: %v", profile, server, err) } } } //export c_ImportBundle func c_ImportBundle(profile_ptr *C.char, profile_len C.int, bundle_ptr *C.char, bundle_len C.int) { profile := C.GoStringN(profile_ptr, profile_len) name := C.GoStringN(bundle_ptr, bundle_len) ImportBundle(profile, name) } // ImportGroupInviteString takes in a handle to a profile and an invite string which could have one of many // different formats (e.g. a group invite, a server key bundle, or a combination) // TODO: extend to also handle AddContact flow... func ImportBundle(profileOnion string, bundle string) { profile := application.GetPeer(profileOnion) groupHandler, err := groups.ExperimentGate(utils.ReadGlobalSettings().Experiments) if err == nil { err = groupHandler.HandleImportString(profile, bundle) eventHandler.Push(event.NewEvent(event.AppError, map[event.Field]string{event.Data: err.Error()})) } } //export c_SetGroupAttribute func c_SetGroupAttribute(profile_ptr *C.char, profile_len C.int, group_ptr *C.char, group_len C.int, key_ptr *C.char, key_len C.int, val_ptr *C.char, val_len C.int) { profileOnion := C.GoStringN(profile_ptr, profile_len) groupHandle := C.GoStringN(group_ptr, group_len) key := C.GoStringN(key_ptr, key_len) value := C.GoStringN(val_ptr, val_len) SetGroupAttribute(profileOnion, groupHandle, key, value) } // SetGroupAttribute provides a wrapper around profile.SetGroupAttribute, gated by global experiments... func SetGroupAttribute(profileOnion string, groupHandle string, key string, value string) { profile := application.GetPeer(profileOnion) _, err := groups.ExperimentGate(utils.ReadGlobalSettings().Experiments) if err == nil { profile.SetGroupAttribute(groupHandle, key, value) } } // Leave as is, needed by ffi func main() {}