croc/src/sender/sender.go

324 lines
8.2 KiB
Go
Raw Normal View History

2018-09-22 05:17:57 +02:00
package sender
import (
"bytes"
"encoding/json"
"fmt"
"io"
2018-09-23 21:34:29 +02:00
"net"
2018-09-22 05:17:57 +02:00
"os"
2018-09-22 07:09:29 +02:00
"path/filepath"
2018-09-22 05:51:43 +02:00
"strings"
2018-09-22 05:17:57 +02:00
"time"
log "github.com/cihub/seelog"
"github.com/gorilla/websocket"
"github.com/pkg/errors"
2018-09-23 21:34:29 +02:00
"github.com/schollz/croc/src/comm"
2018-09-22 05:17:57 +02:00
"github.com/schollz/croc/src/compress"
"github.com/schollz/croc/src/crypt"
"github.com/schollz/croc/src/logger"
"github.com/schollz/croc/src/models"
"github.com/schollz/croc/src/utils"
2018-09-22 06:52:29 +02:00
"github.com/schollz/croc/src/zipper"
2018-09-22 05:17:57 +02:00
"github.com/schollz/pake"
2018-09-22 05:51:43 +02:00
"github.com/schollz/progressbar/v2"
2018-09-22 22:59:03 +02:00
"github.com/schollz/spinner"
2018-09-22 05:17:57 +02:00
"github.com/tscholl2/siec"
)
var DebugLevel string
// Send is the async call to send data
2018-09-23 14:31:55 +02:00
func Send(isLocal bool, done chan struct{}, c *websocket.Conn, fname string, codephrase string, useCompression bool, useEncryption bool) {
2018-09-22 05:17:57 +02:00
logger.SetLogLevel(DebugLevel)
2018-09-22 05:51:43 +02:00
log.Debugf("sending %s", fname)
2018-09-23 14:31:55 +02:00
err := send(isLocal, c, fname, codephrase, useCompression, useEncryption)
2018-09-22 05:17:57 +02:00
if err != nil {
2018-09-23 14:57:05 +02:00
if !strings.HasPrefix(err.Error(), "websocket: close 100") {
fmt.Fprintf(os.Stderr, "\n"+err.Error())
2018-09-22 15:20:25 +02:00
}
2018-09-22 05:17:57 +02:00
}
2018-09-23 14:57:05 +02:00
2018-09-22 05:17:57 +02:00
done <- struct{}{}
}
2018-09-23 14:31:55 +02:00
func send(isLocal bool, c *websocket.Conn, fname string, codephrase string, useCompression bool, useEncryption bool) (err error) {
var f *os.File
defer f.Close() // ignore the error if it wasn't opened :(
var fstats models.FileStats
2018-09-22 16:23:10 +02:00
var fileHash []byte
2018-09-23 15:07:05 +02:00
var otherIP string
var startTransfer time.Time
2018-09-23 21:34:29 +02:00
var tcpConnection comm.Comm
fileReady := make(chan error)
// normalize the file name
2018-09-22 07:09:29 +02:00
fname, err = filepath.Abs(fname)
2018-09-22 06:50:20 +02:00
if err != nil {
return err
}
_, filename := filepath.Split(fname)
2018-09-22 06:50:20 +02:00
// get ready to generate session key
2018-09-22 05:17:57 +02:00
var sessionKey []byte
2018-09-22 07:38:02 +02:00
// start a spinner
spin := spinner.New(spinner.CharSets[9], 100*time.Millisecond)
2018-09-22 18:57:22 +02:00
spin.Writer = os.Stderr
2018-09-22 07:38:02 +02:00
2018-09-22 05:17:57 +02:00
// pick an elliptic curve
curve := siec.SIEC255()
// both parties should have a weak key
2018-09-22 05:51:43 +02:00
pw := []byte(codephrase)
2018-09-22 05:17:57 +02:00
// initialize sender P ("0" indicates sender)
P, err := pake.Init(pw, 0, curve, 100*time.Millisecond)
if err != nil {
return
}
step := 0
for {
messageType, message, errRead := c.ReadMessage()
if errRead != nil {
return errRead
}
if messageType == websocket.PongMessage || messageType == websocket.PingMessage {
continue
}
2018-09-23 14:57:05 +02:00
if messageType == websocket.TextMessage && bytes.Equal(message, []byte("interrupt")) {
return errors.New("\rinterrupted by other party")
2018-09-23 14:39:23 +02:00
}
2018-09-22 05:17:57 +02:00
log.Debugf("got %d: %s", messageType, message)
switch step {
case 0:
2018-09-23 15:07:05 +02:00
// sender initiates communication
ip := ""
if isLocal {
ip = utils.LocalIP()
} else {
ip, _ = utils.PublicIP()
}
// send my IP address
c.WriteMessage(websocket.BinaryMessage, []byte(ip))
case 1:
// first receive the IP address from the sender
otherIP = string(message)
log.Debugf("recipient IP: %s", otherIP)
go func() {
// recipient might want file! start gathering information about file
fstat, err := os.Stat(fname)
if err != nil {
fileReady <- err
return
}
fstats = models.FileStats{
Name: filename,
Size: fstat.Size(),
ModTime: fstat.ModTime(),
IsDir: fstat.IsDir(),
SentName: fstat.Name(),
IsCompressed: useCompression,
IsEncrypted: useEncryption,
}
if fstats.IsDir {
// zip the directory
fstats.SentName, err = zipper.ZipFile(fname, true)
// remove the file when leaving
defer os.Remove(fstats.SentName)
fname = fstats.SentName
fstat, err := os.Stat(fname)
if err != nil {
fileReady <- err
return
}
// get new size
fstats.Size = fstat.Size()
}
// open the file
f, err = os.Open(fname)
if err != nil {
fileReady <- err
return
}
fileReady <- nil
}()
2018-09-22 15:10:41 +02:00
// send pake data
log.Debugf("[%d] first, P sends u to Q", step)
c.WriteMessage(websocket.BinaryMessage, P.Bytes())
// start PAKE spinnner
2018-09-22 07:38:02 +02:00
spin.Suffix = " performing PAKE..."
spin.Start()
2018-09-23 15:07:05 +02:00
case 2:
2018-09-22 05:17:57 +02:00
// P recieves H(k),v from Q
log.Debugf("[%d] P computes k, H(k), sends H(k) to Q", step)
if err := P.Update(message); err != nil {
return err
}
c.WriteMessage(websocket.BinaryMessage, P.Bytes())
sessionKey, _ = P.SessionKey()
// check(err)
log.Debugf("%x\n", sessionKey)
2018-09-22 15:27:23 +02:00
// wait for readiness
spin.Stop()
spin.Suffix = " waiting for recipient ok..."
spin.Start()
2018-09-23 15:07:05 +02:00
case 3:
2018-09-22 05:17:57 +02:00
log.Debugf("[%d] recipient declares readiness for file info", step)
if !bytes.Equal(message, []byte("ready")) {
return errors.New("recipient refused file")
}
_ = <-fileReady // block until file is ready
2018-09-22 05:17:57 +02:00
fstatsBytes, err := json.Marshal(fstats)
if err != nil {
return err
}
// encrypt the file meta data
enc := crypt.Encrypt(fstatsBytes, sessionKey)
encBytes, err := json.Marshal(enc)
if err != nil {
return err
}
// send the file meta data
c.WriteMessage(websocket.BinaryMessage, encBytes)
2018-09-23 15:07:05 +02:00
case 4:
2018-09-22 07:38:02 +02:00
spin.Stop()
2018-09-22 05:17:57 +02:00
log.Debugf("[%d] recipient declares readiness for file data", step)
if !bytes.Equal(message, []byte("ready")) {
return errors.New("recipient refused file")
}
2018-09-22 07:38:02 +02:00
2018-09-23 21:34:29 +02:00
if !isLocal {
// connection to TCP
tcpConnection, err = connectToTCPServer(utils.SHA256(fmt.Sprintf("%x", sessionKey)), "localhost:8154")
if err != nil {
log.Error(err)
return
}
}
2018-09-23 15:07:05 +02:00
fmt.Fprintf(os.Stderr, "\rSending (->%s)...\n", otherIP)
2018-09-22 05:17:57 +02:00
// send file, compure hash simultaneously
2018-09-23 15:07:05 +02:00
startTransfer = time.Now()
2018-09-23 15:27:59 +02:00
buffer := make([]byte, models.WEBSOCKET_BUFFER_SIZE/8)
2018-09-22 05:17:57 +02:00
bar := progressbar.NewOptions(
int(fstats.Size),
progressbar.OptionSetRenderBlankState(true),
progressbar.OptionSetBytes(int(fstats.Size)),
2018-09-22 07:25:01 +02:00
progressbar.OptionSetWriter(os.Stderr),
2018-09-22 05:17:57 +02:00
)
for {
bytesread, err := f.Read(buffer)
bar.Add(bytesread)
if bytesread > 0 {
// do compression
2018-09-22 19:49:33 +02:00
var compressedBytes []byte
2018-09-22 19:59:45 +02:00
if useCompression && !fstats.IsDir {
2018-09-22 19:49:33 +02:00
compressedBytes = compress.Compress(buffer[:bytesread])
} else {
compressedBytes = buffer[:bytesread]
}
2018-09-22 05:17:57 +02:00
// do encryption
2018-09-22 19:49:33 +02:00
enc := crypt.Encrypt(compressedBytes, sessionKey, !useEncryption)
2018-09-22 05:17:57 +02:00
encBytes, err := json.Marshal(enc)
if err != nil {
return err
}
2018-09-23 21:34:29 +02:00
if isLocal {
// write data to websockets
err = c.WriteMessage(websocket.BinaryMessage, encBytes)
} else {
// write data to tcp connection
_, err = tcpConnection.Write(encBytes)
}
2018-09-22 05:17:57 +02:00
if err != nil {
err = errors.Wrap(err, "problem writing message")
return err
}
}
if err != nil {
if err != io.EOF {
2018-09-22 16:23:10 +02:00
log.Error(err)
2018-09-22 05:17:57 +02:00
}
2018-09-23 21:34:29 +02:00
if !isLocal {
tcpConnection.Write([]byte("end"))
}
2018-09-22 05:17:57 +02:00
break
}
}
bar.Finish()
log.Debug("send hash to finish file")
2018-09-22 16:23:10 +02:00
fileHash, err = utils.HashFile(fname)
2018-09-22 05:17:57 +02:00
if err != nil {
return err
}
2018-09-23 15:07:05 +02:00
case 5:
transferTime := time.Since(startTransfer)
2018-09-22 16:23:10 +02:00
if !bytes.HasPrefix(message, []byte("hash:")) {
continue
}
c.WriteMessage(websocket.BinaryMessage, fileHash)
message = bytes.TrimPrefix(message, []byte("hash:"))
2018-09-22 05:17:57 +02:00
log.Debugf("[%d] determing whether it went ok", step)
2018-09-22 16:23:10 +02:00
if bytes.Equal(message, fileHash) {
2018-09-22 05:17:57 +02:00
log.Debug("file transfered successfully")
2018-09-23 15:07:05 +02:00
transferRate := float64(fstats.Size) / 1000000.0 / transferTime.Seconds()
transferType := "MB/s"
if transferRate < 1 {
transferRate = float64(fstats.Size) / 1000.0 / transferTime.Seconds()
transferType = "kB/s"
}
fmt.Fprintf(os.Stderr, "\nTransfer complete (%2.1f %s)", transferRate, transferType)
2018-09-22 05:51:43 +02:00
return nil
2018-09-22 05:17:57 +02:00
} else {
2018-09-22 15:29:11 +02:00
fmt.Fprintf(os.Stderr, "\nTransfer corrupted")
2018-09-22 05:17:57 +02:00
return errors.New("file not transfered succesfully")
}
default:
return fmt.Errorf("unknown step")
}
step++
}
}
2018-09-23 21:34:29 +02:00
func connectToTCPServer(room string, address string) (com comm.Comm, err error) {
connection, err := net.Dial("tcp", address)
if err != nil {
return
}
connection.SetReadDeadline(time.Now().Add(3 * time.Hour))
connection.SetDeadline(time.Now().Add(3 * time.Hour))
connection.SetWriteDeadline(time.Now().Add(3 * time.Hour))
com = comm.New(connection)
ok, err := com.Receive()
if err != nil {
return
}
log.Debugf("server says: %s", ok)
err = com.Send(room)
if err != nil {
return
}
ok, err = com.Receive()
log.Debugf("server says: %s", ok)
if err != nil {
return
}
if ok != "sender" {
err = errors.New(ok)
}
return
}