croc/src/client.go

317 lines
6.9 KiB
Go
Raw Normal View History

2018-06-29 15:23:00 +02:00
package croc
import (
2018-06-30 05:12:19 +02:00
"net"
2018-06-29 15:23:00 +02:00
"net/url"
"os"
"os/signal"
2018-06-30 05:12:19 +02:00
"sync"
2018-06-29 15:23:00 +02:00
"time"
log "github.com/cihub/seelog"
"github.com/gorilla/websocket"
2018-06-30 05:12:19 +02:00
"github.com/pkg/errors"
2018-06-30 04:34:21 +02:00
"github.com/schollz/croc/src/pake"
2018-06-29 15:23:00 +02:00
)
2018-06-30 04:34:21 +02:00
func (c *Croc) client(role int, codePhrase string) (err error) {
2018-06-29 15:23:00 +02:00
defer log.Flush()
// initialize the channel data for this client
c.cs.Lock()
2018-06-30 04:34:21 +02:00
2018-06-29 16:35:25 +02:00
c.cs.channel.codePhrase = codePhrase
2018-06-30 02:29:11 +02:00
if len(codePhrase) > 0 {
if len(codePhrase) < 4 {
err = errors.New("code phrase must be more than 4 characters")
return
}
c.cs.channel.Channel = codePhrase[:3]
c.cs.channel.passPhrase = codePhrase[3:]
2018-06-30 04:34:21 +02:00
} else {
// TODO
// generate code phrase
c.cs.channel.Channel = "chou"
c.cs.channel.passPhrase = codePhrase[3:]
2018-06-30 02:29:11 +02:00
}
2018-06-30 04:34:21 +02:00
channel := c.cs.channel.Channel
2018-06-29 15:23:00 +02:00
c.cs.Unlock()
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
// connect to the websocket
// TODO:
// use predefined host and HTTPS, if exists
u := url.URL{Scheme: "ws", Host: "localhost:8003", Path: "/"}
log.Debugf("connecting to %s", u.String())
ws, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
if err != nil {
log.Error("dial:", err)
return
}
defer ws.Close()
// read in the messages and process them
done := make(chan struct{})
go func() {
defer close(done)
for {
var cd channelData
err := ws.ReadJSON(&cd)
if err != nil {
log.Debugf("sender read error:", err)
return
}
2018-06-29 16:35:25 +02:00
log.Debugf("recv: %s", cd)
2018-06-30 04:34:21 +02:00
err = c.processState(ws, cd)
2018-06-29 15:23:00 +02:00
if err != nil {
log.Warn(err)
return
}
}
}()
// initialize by joining as corresponding role
// TODO:
// allowing suggesting a channel
2018-06-30 04:34:21 +02:00
p := channelData{
2018-06-29 16:35:25 +02:00
Open: true,
Role: role,
Channel: channel,
}
log.Debugf("sending opening payload: %+v", p)
err = ws.WriteJSON(p)
2018-06-29 15:23:00 +02:00
if err != nil {
log.Errorf("problem opening: %s", err.Error())
return
}
2018-06-30 05:12:19 +02:00
var wg sync.WaitGroup
wg.Add(1)
go func(wg *sync.WaitGroup) {
defer wg.Done()
for {
2018-06-29 15:23:00 +02:00
select {
case <-done:
2018-06-30 05:12:19 +02:00
return
case <-interrupt:
// send Close signal to relay on interrupt
log.Debugf("interrupt")
c.cs.Lock()
channel := c.cs.channel.Channel
uuid := c.cs.channel.UUID
c.cs.Unlock()
// Cleanly close the connection by sending a close message and then
// waiting (with timeout) for the server to close the connection.
log.Debug("sending close signal")
errWrite := ws.WriteJSON(channelData{
Channel: channel,
UUID: uuid,
Close: true,
})
if errWrite != nil {
log.Debugf("write close:", err)
return
}
select {
case <-done:
case <-time.After(time.Second):
}
return
2018-06-29 15:23:00 +02:00
}
}
2018-06-30 05:12:19 +02:00
}(&wg)
wg.Wait()
c.cs.Lock()
if c.cs.channel.FileReceived {
log.Info("file recieved!")
2018-06-29 15:23:00 +02:00
}
2018-06-30 05:12:19 +02:00
c.cs.Unlock()
2018-06-29 15:23:00 +02:00
return
}
2018-06-30 04:34:21 +02:00
func (c *Croc) processState(ws *websocket.Conn, cd channelData) (err error) {
2018-06-29 15:23:00 +02:00
c.cs.Lock()
defer c.cs.Unlock()
// first check if there is relay reported error
if cd.Error != "" {
err = errors.New(cd.Error)
return
}
// TODO:
// check if the state is not aligned (i.e. have h(k) but no hh(k))
// throw error if not aligned so it can exit
2018-06-30 05:12:19 +02:00
// if file received, then you are all done
if cd.FileReceived {
c.cs.channel.FileReceived = true
log.Debug("file recieved!")
log.Debug("sending close signal")
c.cs.channel.Close = true
ws.WriteJSON(c.cs.channel)
return
}
// if transfer ready then send file
if cd.TransferReady {
c.cs.channel.TransferReady = true
return
}
2018-06-29 15:23:00 +02:00
// first update the channel data
// initialize if has UUID
if cd.UUID != "" {
c.cs.channel.UUID = cd.UUID
c.cs.channel.Channel = cd.Channel
c.cs.channel.Role = cd.Role
2018-06-30 04:34:21 +02:00
c.cs.channel.Curve = cd.Curve
c.cs.channel.Pake, err = pake.Init([]byte(c.cs.channel.passPhrase), cd.Role, getCurve(cd.Curve))
c.cs.channel.Update = true
log.Debugf("updating channel")
errWrite := ws.WriteJSON(c.cs.channel)
if errWrite != nil {
log.Error(errWrite)
}
c.cs.channel.Update = false
2018-06-29 15:23:00 +02:00
log.Debugf("initialized client state")
2018-06-29 16:35:25 +02:00
return
2018-06-29 15:23:00 +02:00
}
// copy over the rest of the state
2018-06-29 16:35:25 +02:00
c.cs.channel.Ports = cd.Ports
2018-06-30 05:12:19 +02:00
// update the Pake
2018-06-30 04:34:21 +02:00
if cd.Pake != nil && cd.Pake.Role != c.cs.channel.Role {
log.Debugf("updating pake from %d", cd.Pake.Role)
if c.cs.channel.Pake.HkA == nil {
err = c.cs.channel.Pake.Update(cd.Pake.Bytes())
if err != nil {
log.Error(err)
log.Debug("sending close signal")
c.cs.channel.Close = true
c.cs.channel.Error = err.Error()
ws.WriteJSON(c.cs.channel)
return
}
c.cs.channel.Update = true
log.Debugf("updating channel")
errWrite := ws.WriteJSON(c.cs.channel)
if errWrite != nil {
log.Error(errWrite)
}
c.cs.channel.Update = false
}
}
2018-06-30 05:16:15 +02:00
if c.cs.channel.Role == 0 && c.cs.channel.Pake.IsVerified() {
go func() {
// encrypt the files
// TODO
c.cs.Lock()
c.cs.channel.fileReady = true
c.cs.Unlock()
}()
}
2018-06-29 15:23:00 +02:00
2018-06-30 05:12:19 +02:00
// TODO
2018-06-29 15:23:00 +02:00
// process the client state
2018-06-30 05:12:19 +02:00
if c.cs.channel.Pake.IsVerified() && !c.cs.channel.isReady {
// spawn TCP connections
c.cs.channel.isReady = true
go func(role int) {
err = c.dialUp()
if err == nil {
if role == 1 {
c.cs.Lock()
c.cs.channel.Update = true
c.cs.channel.FileReceived = true
log.Debugf("got file successfully")
errWrite := ws.WriteJSON(c.cs.channel)
if errWrite != nil {
log.Error(errWrite)
}
c.cs.channel.Update = false
c.cs.Unlock()
}
} else {
log.Error(err)
}
}(c.cs.channel.Role)
}
return
}
func (c *Croc) dialUp() (err error) {
c.cs.Lock()
ports := c.cs.channel.Ports
channel := c.cs.channel.Channel
uuid := c.cs.channel.UUID
role := c.cs.channel.Role
c.cs.Unlock()
errorChan := make(chan error)
for i, port := range ports {
go func(channel, uuid, port string, i int) {
if i == 0 {
log.Debug("dialing up")
}
log.Debugf("connecting to %s", "localhost:"+port)
connection, err := net.Dial("tcp", "localhost:"+port)
if err != nil {
errorChan <- err
return
}
defer connection.Close()
connection.SetReadDeadline(time.Now().Add(1 * time.Hour))
connection.SetDeadline(time.Now().Add(1 * time.Hour))
connection.SetWriteDeadline(time.Now().Add(1 * time.Hour))
message, err := receiveMessage(connection)
if err != nil {
errorChan <- err
return
}
log.Debugf("relay says: %s", message)
err = sendMessage(channel, connection)
if err != nil {
errorChan <- err
return
}
err = sendMessage(uuid, connection)
if err != nil {
errorChan <- err
return
}
// wait for transfer to be ready
for {
c.cs.RLock()
ready := c.cs.channel.TransferReady
2018-06-30 05:16:15 +02:00
if role == 0 {
ready = ready && c.cs.channel.fileReady
}
2018-06-30 05:12:19 +02:00
c.cs.RUnlock()
if ready {
break
}
time.Sleep(10 * time.Millisecond)
}
if role == 0 {
log.Debug("send file")
} else {
log.Debug("receive file")
}
time.Sleep(3 * time.Second)
errorChan <- nil
}(channel, uuid, port, i)
}
// collect errors
for i := 0; i < len(ports); i++ {
errOne := <-errorChan
if errOne != nil {
log.Warn(errOne)
}
}
2018-06-29 15:23:00 +02:00
return
}