2018-03-11 18:49:10 +00:00
|
|
|
package peer
|
2018-03-09 20:44:13 +00:00
|
|
|
|
|
|
|
import (
|
2018-05-30 17:41:02 +00:00
|
|
|
"crypto/rsa"
|
2018-05-28 18:05:06 +00:00
|
|
|
"cwtch.im/cwtch/model"
|
|
|
|
"cwtch.im/cwtch/peer/connections"
|
|
|
|
"cwtch.im/cwtch/peer/peer"
|
|
|
|
"cwtch.im/cwtch/protocol"
|
2018-03-09 20:44:13 +00:00
|
|
|
"encoding/json"
|
2018-03-30 21:16:51 +00:00
|
|
|
"errors"
|
2018-05-01 21:36:03 +00:00
|
|
|
"fmt"
|
2018-03-09 20:44:13 +00:00
|
|
|
"github.com/s-rah/go-ricochet/application"
|
|
|
|
"github.com/s-rah/go-ricochet/channels"
|
|
|
|
"github.com/s-rah/go-ricochet/connection"
|
|
|
|
"io/ioutil"
|
2018-05-01 20:44:45 +00:00
|
|
|
"log"
|
2018-03-09 20:44:13 +00:00
|
|
|
"sync"
|
|
|
|
)
|
|
|
|
|
2018-05-16 21:31:06 +00:00
|
|
|
// CwtchPeer manages incoming and outgoing connections and all processing for a Cwtch Peer
|
2018-03-09 20:44:13 +00:00
|
|
|
type CwtchPeer struct {
|
|
|
|
connection.AutoConnectionHandler
|
2018-03-30 21:16:51 +00:00
|
|
|
Profile *model.Profile
|
|
|
|
mutex sync.Mutex
|
|
|
|
Log chan string `json:"-"`
|
2018-03-15 20:53:22 +00:00
|
|
|
connectionsManager *connections.Manager
|
2018-05-01 20:44:45 +00:00
|
|
|
profilefile string
|
2018-03-09 20:44:13 +00:00
|
|
|
}
|
|
|
|
|
2018-05-01 20:44:45 +00:00
|
|
|
func (cp *CwtchPeer) setup() {
|
2018-03-14 22:23:35 +00:00
|
|
|
cp.Log = make(chan string)
|
2018-03-15 20:53:22 +00:00
|
|
|
cp.connectionsManager = connections.NewConnectionsManager()
|
2018-03-14 22:23:35 +00:00
|
|
|
cp.Init()
|
2018-05-01 20:44:45 +00:00
|
|
|
|
|
|
|
go cp.connectionsManager.AttemptReconnections()
|
|
|
|
|
2018-05-03 19:23:02 +00:00
|
|
|
for onion, profile := range cp.Profile.Contacts {
|
|
|
|
if profile.Trusted && !profile.Blocked {
|
|
|
|
cp.PeerWithOnion(onion)
|
|
|
|
}
|
2018-05-01 20:44:45 +00:00
|
|
|
}
|
|
|
|
|
2018-05-03 06:01:15 +00:00
|
|
|
for _, group := range cp.Profile.Groups {
|
2018-05-03 04:12:45 +00:00
|
|
|
if group.Accepted || group.Owner == "self" {
|
|
|
|
cp.JoinServer(group.GroupServer)
|
|
|
|
}
|
|
|
|
}
|
2018-05-01 20:44:45 +00:00
|
|
|
}
|
|
|
|
|
2018-05-16 21:31:06 +00:00
|
|
|
// NewCwtchPeer creates and returns a new CwtchPeer with the given name.
|
2018-05-01 20:44:45 +00:00
|
|
|
func NewCwtchPeer(name string) *CwtchPeer {
|
|
|
|
cp := new(CwtchPeer)
|
|
|
|
cp.Profile = model.GenerateNewProfile(name)
|
|
|
|
cp.setup()
|
2018-03-14 22:23:35 +00:00
|
|
|
return cp
|
2018-03-09 20:44:13 +00:00
|
|
|
}
|
|
|
|
|
2018-05-16 21:31:06 +00:00
|
|
|
// Save saves the CwtchPeer profile state to a file.
|
2018-03-09 20:44:13 +00:00
|
|
|
func (cp *CwtchPeer) Save(profilefile string) error {
|
|
|
|
cp.mutex.Lock()
|
2018-03-14 22:03:53 +00:00
|
|
|
bytes, _ := json.Marshal(cp)
|
2018-03-09 20:44:13 +00:00
|
|
|
err := ioutil.WriteFile(profilefile, bytes, 0600)
|
2018-05-01 20:44:45 +00:00
|
|
|
cp.profilefile = profilefile
|
2018-03-09 20:44:13 +00:00
|
|
|
cp.mutex.Unlock()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-05-16 21:31:06 +00:00
|
|
|
// LoadCwtchPeer loads an existing CwtchPeer from a file.
|
2018-03-09 20:44:13 +00:00
|
|
|
func LoadCwtchPeer(profilefile string) (*CwtchPeer, error) {
|
|
|
|
bytes, _ := ioutil.ReadFile(profilefile)
|
2018-03-14 22:23:35 +00:00
|
|
|
cp := new(CwtchPeer)
|
|
|
|
err := json.Unmarshal(bytes, &cp)
|
2018-05-01 20:44:45 +00:00
|
|
|
cp.setup()
|
|
|
|
cp.profilefile = profilefile
|
2018-03-14 22:23:35 +00:00
|
|
|
return cp, err
|
2018-03-09 20:44:13 +00:00
|
|
|
}
|
|
|
|
|
2018-05-16 21:31:06 +00:00
|
|
|
// PeerWithOnion is the entry point for CwtchPeer relationships
|
2018-03-15 20:53:22 +00:00
|
|
|
func (cp *CwtchPeer) PeerWithOnion(onion string) {
|
|
|
|
cp.connectionsManager.ManagePeerConnection(onion, cp.Profile)
|
2018-03-09 20:44:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// InviteOnionToGroup kicks off the invite process
|
2018-03-15 20:53:22 +00:00
|
|
|
func (cp *CwtchPeer) InviteOnionToGroup(onion string, groupid string) error {
|
2018-05-03 19:23:02 +00:00
|
|
|
|
2018-05-16 20:53:09 +00:00
|
|
|
group := cp.Profile.GetGroupByGroupID(groupid)
|
2018-05-01 21:36:03 +00:00
|
|
|
if group != nil {
|
|
|
|
fmt.Printf("Constructing invite for group: %v\n", group)
|
2018-05-16 20:18:47 +00:00
|
|
|
invite, err := group.Invite()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-03-15 20:53:22 +00:00
|
|
|
ppc := cp.connectionsManager.GetPeerPeerConnectionForOnion(onion)
|
2018-05-03 19:23:02 +00:00
|
|
|
if ppc == nil {
|
|
|
|
return errors.New("peer connection not setup for onion. peers must be trusted before sending")
|
|
|
|
}
|
|
|
|
if ppc.GetState() == connections.AUTHENTICATED {
|
|
|
|
fmt.Printf("Got connection for group: %v - Sending Invite\n", ppc)
|
|
|
|
ppc.SendGroupInvite(invite)
|
|
|
|
} else {
|
|
|
|
return errors.New("cannot send invite to onion: peer connection is not ready")
|
|
|
|
}
|
2018-05-03 04:12:45 +00:00
|
|
|
return nil
|
2018-03-15 20:53:22 +00:00
|
|
|
}
|
|
|
|
return errors.New("group id could not be found")
|
2018-03-09 20:44:13 +00:00
|
|
|
}
|
|
|
|
|
2018-05-16 21:31:06 +00:00
|
|
|
// ReceiveGroupMessage is a callback function that processes GroupMessages from a given server
|
2018-03-30 21:16:51 +00:00
|
|
|
func (cp *CwtchPeer) ReceiveGroupMessage(server string, gm *protocol.GroupMessage) {
|
|
|
|
cp.Profile.AttemptDecryption(gm.Ciphertext)
|
|
|
|
}
|
|
|
|
|
2018-05-16 21:31:06 +00:00
|
|
|
// JoinServer manages a new server connection with the given onion address
|
2018-03-09 20:44:13 +00:00
|
|
|
func (cp *CwtchPeer) JoinServer(onion string) {
|
2018-03-30 21:16:51 +00:00
|
|
|
cp.connectionsManager.ManageServerConnection(onion, cp.ReceiveGroupMessage)
|
2018-03-09 20:44:13 +00:00
|
|
|
}
|
|
|
|
|
2018-05-16 21:31:06 +00:00
|
|
|
// SendMessageToGroup attemps to sent the given message to the given group id.
|
2018-05-03 04:12:45 +00:00
|
|
|
func (cp *CwtchPeer) SendMessageToGroup(groupid string, message string) error {
|
2018-05-16 20:53:09 +00:00
|
|
|
group := cp.Profile.GetGroupByGroupID(groupid)
|
2018-05-03 04:12:45 +00:00
|
|
|
if group == nil {
|
|
|
|
return errors.New("group does not exit")
|
|
|
|
}
|
2018-03-30 21:16:51 +00:00
|
|
|
psc := cp.connectionsManager.GetPeerServerConnectionForOnion(group.GroupServer)
|
2018-05-03 19:23:02 +00:00
|
|
|
if psc == nil {
|
2018-05-09 19:09:00 +00:00
|
|
|
return errors.New("could not find server connection to send message to")
|
|
|
|
}
|
|
|
|
ct, err := cp.Profile.EncryptMessageToGroup(message, groupid)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2018-05-03 19:23:02 +00:00
|
|
|
}
|
2018-03-30 21:16:51 +00:00
|
|
|
gm := &protocol.GroupMessage{
|
|
|
|
Ciphertext: ct,
|
|
|
|
}
|
2018-05-09 19:09:00 +00:00
|
|
|
err = psc.SendGroupMessage(gm)
|
|
|
|
return err
|
2018-03-09 20:44:13 +00:00
|
|
|
}
|
|
|
|
|
2018-05-16 21:31:06 +00:00
|
|
|
// GetPeers returns a list of peer connections.
|
2018-05-01 20:44:45 +00:00
|
|
|
func (cp *CwtchPeer) GetPeers() map[string]connections.ConnectionState {
|
|
|
|
return cp.connectionsManager.GetPeers()
|
|
|
|
}
|
|
|
|
|
2018-05-16 21:31:06 +00:00
|
|
|
// GetServers returns a list of server connections
|
2018-05-03 04:12:45 +00:00
|
|
|
func (cp *CwtchPeer) GetServers() map[string]connections.ConnectionState {
|
|
|
|
return cp.connectionsManager.GetServers()
|
|
|
|
}
|
|
|
|
|
2018-05-16 21:31:06 +00:00
|
|
|
// TrustPeer sets an existing peer relationship to trusted
|
2018-05-03 19:23:02 +00:00
|
|
|
func (cp *CwtchPeer) TrustPeer(peer string) error {
|
2018-05-30 17:41:02 +00:00
|
|
|
err := cp.Profile.TrustPeer(peer)
|
|
|
|
if err == nil {
|
|
|
|
cp.PeerWithOnion(peer)
|
2018-05-03 19:23:02 +00:00
|
|
|
}
|
2018-05-30 17:41:02 +00:00
|
|
|
return err
|
2018-05-03 19:23:02 +00:00
|
|
|
}
|
|
|
|
|
2018-05-16 21:31:06 +00:00
|
|
|
// BlockPeer blocks an existing peer relationship.
|
2018-05-03 19:23:02 +00:00
|
|
|
func (cp *CwtchPeer) BlockPeer(peer string) error {
|
2018-05-30 17:41:02 +00:00
|
|
|
err := cp.Profile.BlockPeer(peer)
|
|
|
|
cp.connectionsManager.TearDownConnection(peer)
|
|
|
|
return err
|
2018-05-03 19:23:02 +00:00
|
|
|
}
|
|
|
|
|
2018-05-16 21:31:06 +00:00
|
|
|
// AcceptInvite accepts a given existing group invite
|
2018-05-03 04:12:45 +00:00
|
|
|
func (cp *CwtchPeer) AcceptInvite(groupID string) error {
|
2018-05-30 17:41:02 +00:00
|
|
|
return cp.Profile.AcceptInvite(groupID)
|
2018-05-03 04:12:45 +00:00
|
|
|
}
|
|
|
|
|
2018-05-16 21:31:06 +00:00
|
|
|
// RejectInvite rejects a given group invite.
|
2018-05-30 17:41:02 +00:00
|
|
|
func (cp *CwtchPeer) RejectInvite(groupID string) {
|
|
|
|
cp.Profile.RejectInvite(groupID)
|
|
|
|
}
|
|
|
|
|
|
|
|
// LookupContact returns that a contact is known and allowed to communicate for all cases.
|
|
|
|
func (cp *CwtchPeer) LookupContact(hostname string, publicKey rsa.PublicKey) (allowed, known bool) {
|
|
|
|
blocked := cp.Profile.IsBlocked(hostname)
|
|
|
|
return !blocked, true
|
|
|
|
}
|
|
|
|
|
|
|
|
// ContactRequest needed to implement ContactRequestHandler Interface
|
|
|
|
func (cp *CwtchPeer) ContactRequest(name string, message string) string {
|
|
|
|
return "Accepted"
|
2018-05-03 04:12:45 +00:00
|
|
|
}
|
|
|
|
|
2018-05-16 21:31:06 +00:00
|
|
|
// Listen sets up an onion listener to process incoming cwtch messages
|
2018-03-09 20:44:13 +00:00
|
|
|
func (cp *CwtchPeer) Listen() error {
|
|
|
|
cwtchpeer := new(application.RicochetApplication)
|
|
|
|
l, err := application.SetupOnion("127.0.0.1:9051", "tcp4", "", cp.Profile.OnionPrivateKey, 9878)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
af := application.ApplicationInstanceFactory{}
|
|
|
|
af.Init()
|
|
|
|
af.AddHandler("im.cwtch.peer", func(rai *application.ApplicationInstance) func() channels.Handler {
|
|
|
|
cpi := new(CwtchPeerInstance)
|
|
|
|
cpi.Init(rai, cwtchpeer)
|
|
|
|
return func() channels.Handler {
|
2018-03-14 22:03:53 +00:00
|
|
|
cpc := new(peer.CwtchPeerChannel)
|
|
|
|
cpc.Handler = &CwtchPeerHandler{Onion: rai.RemoteHostname, Peer: cp}
|
|
|
|
return cpc
|
2018-03-09 20:44:13 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2018-05-30 17:41:02 +00:00
|
|
|
cwtchpeer.Init(cp.Profile.Name, cp.Profile.OnionPrivateKey, af, cp)
|
2018-05-01 20:44:45 +00:00
|
|
|
log.Printf("Running cwtch peer on %v", l.Addr().String())
|
2018-03-09 20:44:13 +00:00
|
|
|
cwtchpeer.Run(l)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-05-16 21:31:06 +00:00
|
|
|
// CwtchPeerInstance encapsulates incoming peer connections
|
2018-03-15 20:53:22 +00:00
|
|
|
type CwtchPeerInstance struct {
|
|
|
|
rai *application.ApplicationInstance
|
|
|
|
ra *application.RicochetApplication
|
|
|
|
}
|
|
|
|
|
2018-05-16 21:31:06 +00:00
|
|
|
// Init sets up a CwtchPeerInstance
|
2018-03-15 20:53:22 +00:00
|
|
|
func (cpi *CwtchPeerInstance) Init(rai *application.ApplicationInstance, ra *application.RicochetApplication) {
|
|
|
|
cpi.rai = rai
|
|
|
|
cpi.ra = ra
|
2018-03-09 20:44:13 +00:00
|
|
|
}
|
|
|
|
|
2018-05-16 21:31:06 +00:00
|
|
|
// CwtchPeerHandler encapsulates handling of incoming CwtchPackets
|
2018-03-09 20:44:13 +00:00
|
|
|
type CwtchPeerHandler struct {
|
|
|
|
Onion string
|
|
|
|
Peer *CwtchPeer
|
|
|
|
}
|
|
|
|
|
2018-05-16 21:31:06 +00:00
|
|
|
// ClientIdentity handles incoming ClientIdentity packets
|
2018-03-09 20:44:13 +00:00
|
|
|
func (cph *CwtchPeerHandler) ClientIdentity(ci *protocol.CwtchIdentity) {
|
2018-05-01 20:44:45 +00:00
|
|
|
log.Printf("Received Client Identity from %v %v\n", cph.Onion, ci.String())
|
2018-03-14 22:03:53 +00:00
|
|
|
cph.Peer.Profile.AddCwtchIdentity(cph.Onion, ci)
|
2018-05-01 20:44:45 +00:00
|
|
|
cph.Peer.Save(cph.Peer.profilefile)
|
2018-03-09 20:44:13 +00:00
|
|
|
}
|
|
|
|
|
2018-05-16 21:31:06 +00:00
|
|
|
// HandleGroupInvite handles incoming GroupInvites
|
2018-03-14 22:03:53 +00:00
|
|
|
func (cph *CwtchPeerHandler) HandleGroupInvite(gci *protocol.GroupChatInvite) {
|
2018-05-01 21:36:03 +00:00
|
|
|
log.Printf("Received GroupID from %v %v\n", cph.Onion, gci.String())
|
2018-03-15 20:53:22 +00:00
|
|
|
cph.Peer.Profile.ProcessInvite(gci, cph.Onion)
|
2018-03-09 20:44:13 +00:00
|
|
|
}
|
2018-03-10 21:26:19 +00:00
|
|
|
|
2018-05-16 21:31:06 +00:00
|
|
|
// GetClientIdentityPacket returns our ClientIdentity packet so it can be sent to the connected peer.
|
2018-05-01 20:44:45 +00:00
|
|
|
func (cph *CwtchPeerHandler) GetClientIdentityPacket() []byte {
|
|
|
|
return cph.Peer.Profile.GetCwtchIdentityPacket()
|
|
|
|
}
|