mirror of https://github.com/schollz/croc.git
1998 lines
55 KiB
Go
1998 lines
55 KiB
Go
package croc
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/rand"
|
|
"encoding/binary"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
"net"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"golang.org/x/time/rate"
|
|
|
|
"github.com/denisbrodbeck/machineid"
|
|
ignore "github.com/sabhiram/go-gitignore"
|
|
log "github.com/schollz/logger"
|
|
"github.com/schollz/pake/v3"
|
|
"github.com/schollz/peerdiscovery"
|
|
"github.com/schollz/progressbar/v3"
|
|
|
|
"github.com/schollz/croc/v9/src/comm"
|
|
"github.com/schollz/croc/v9/src/compress"
|
|
"github.com/schollz/croc/v9/src/crypt"
|
|
"github.com/schollz/croc/v9/src/message"
|
|
"github.com/schollz/croc/v9/src/models"
|
|
"github.com/schollz/croc/v9/src/tcp"
|
|
"github.com/schollz/croc/v9/src/utils"
|
|
)
|
|
|
|
var (
|
|
ipRequest = []byte("ips?")
|
|
handshakeRequest = []byte("handshake")
|
|
)
|
|
|
|
func init() {
|
|
log.SetLevel("debug")
|
|
}
|
|
|
|
// Debug toggles debug mode
|
|
func Debug(debug bool) {
|
|
if debug {
|
|
log.SetLevel("debug")
|
|
} else {
|
|
log.SetLevel("warn")
|
|
}
|
|
}
|
|
|
|
// Options specifies user specific options
|
|
type Options struct {
|
|
IsSender bool
|
|
SharedSecret string
|
|
Debug bool
|
|
RelayAddress string
|
|
RelayAddress6 string
|
|
BasePort int
|
|
TransferPorts int
|
|
RelayPassword string
|
|
Stdout bool
|
|
NoPrompt bool
|
|
NoMultiplexing bool
|
|
DisableLocal bool
|
|
OnlyLocal bool
|
|
IgnoreStdin bool
|
|
Ask bool
|
|
SendingText bool
|
|
NoCompress bool
|
|
IP string
|
|
Overwrite bool
|
|
Curve string
|
|
HashAlgorithm string
|
|
ThrottleUpload string
|
|
ZipFolder bool
|
|
TestFlag bool
|
|
GitIgnore bool
|
|
}
|
|
|
|
// Client holds the state of the croc transfer
|
|
type Client struct {
|
|
Options Options
|
|
Pake *pake.Pake
|
|
Key []byte
|
|
ExternalIP, ExternalIPConnected string
|
|
|
|
// steps involved in forming relationship
|
|
Step1ChannelSecured bool
|
|
Step2FileInfoTransferred bool
|
|
Step3RecipientRequestFile bool
|
|
Step4FileTransferred bool
|
|
Step5CloseChannels bool
|
|
SuccessfulTransfer bool
|
|
|
|
// send / receive information of all files
|
|
FilesToTransfer []FileInfo
|
|
EmptyFoldersToTransfer []FileInfo
|
|
TotalNumberOfContents int
|
|
TotalNumberFolders int
|
|
FilesToTransferCurrentNum int
|
|
FilesHasFinished map[int]struct{}
|
|
TotalFilesIgnored int
|
|
|
|
// send / receive information of current file
|
|
CurrentFile *os.File
|
|
CurrentFileChunkRanges []int64
|
|
CurrentFileChunks []int64
|
|
CurrentFileIsClosed bool
|
|
LastFolder string
|
|
|
|
TotalSent int64
|
|
TotalChunksTransferred int
|
|
chunkMap map[uint64]struct{}
|
|
limiter *rate.Limiter
|
|
|
|
// tcp connections
|
|
conn []*comm.Comm
|
|
|
|
bar *progressbar.ProgressBar
|
|
longestFilename int
|
|
firstSend bool
|
|
|
|
mutex *sync.Mutex
|
|
fread *os.File
|
|
numfinished int
|
|
quit chan bool
|
|
finishedNum int
|
|
numberOfTransferredFiles int
|
|
}
|
|
|
|
// Chunk contains information about the
|
|
// needed bytes
|
|
type Chunk struct {
|
|
Bytes []byte `json:"b,omitempty"`
|
|
Location int64 `json:"l,omitempty"`
|
|
}
|
|
|
|
// FileInfo registers the information about the file
|
|
type FileInfo struct {
|
|
Name string `json:"n,omitempty"`
|
|
FolderRemote string `json:"fr,omitempty"`
|
|
FolderSource string `json:"fs,omitempty"`
|
|
Hash []byte `json:"h,omitempty"`
|
|
Size int64 `json:"s,omitempty"`
|
|
ModTime time.Time `json:"m,omitempty"`
|
|
IsCompressed bool `json:"c,omitempty"`
|
|
IsEncrypted bool `json:"e,omitempty"`
|
|
Symlink string `json:"sy,omitempty"`
|
|
Mode os.FileMode `json:"md,omitempty"`
|
|
TempFile bool `json:"tf,omitempty"`
|
|
IsIgnored bool `json:"ig,omitempty"`
|
|
}
|
|
|
|
// RemoteFileRequest requests specific bytes
|
|
type RemoteFileRequest struct {
|
|
CurrentFileChunkRanges []int64
|
|
FilesToTransferCurrentNum int
|
|
MachineID string
|
|
}
|
|
|
|
// SenderInfo lists the files to be transferred
|
|
type SenderInfo struct {
|
|
FilesToTransfer []FileInfo
|
|
EmptyFoldersToTransfer []FileInfo
|
|
TotalNumberFolders int
|
|
MachineID string
|
|
Ask bool
|
|
SendingText bool
|
|
NoCompress bool
|
|
HashAlgorithm string
|
|
}
|
|
|
|
// New establishes a new connection for transferring files between two instances.
|
|
func New(ops Options) (c *Client, err error) {
|
|
c = new(Client)
|
|
c.FilesHasFinished = make(map[int]struct{})
|
|
|
|
// setup basic info
|
|
c.Options = ops
|
|
Debug(c.Options.Debug)
|
|
log.Debugf("options: %+v", c.Options)
|
|
|
|
if len(c.Options.SharedSecret) < 6 {
|
|
err = fmt.Errorf("code is too short")
|
|
return
|
|
}
|
|
|
|
c.conn = make([]*comm.Comm, 16)
|
|
|
|
// initialize throttler
|
|
if len(c.Options.ThrottleUpload) > 1 && c.Options.IsSender {
|
|
upload := c.Options.ThrottleUpload[:len(c.Options.ThrottleUpload)-1]
|
|
var uploadLimit int64
|
|
uploadLimit, err = strconv.ParseInt(upload, 10, 64)
|
|
if err != nil {
|
|
panic("Could not parse given Upload Limit")
|
|
}
|
|
minBurstSize := models.TCP_BUFFER_SIZE
|
|
var rt rate.Limit
|
|
switch unit := string(c.Options.ThrottleUpload[len(c.Options.ThrottleUpload)-1:]); unit {
|
|
case "g", "G":
|
|
uploadLimit = uploadLimit * 1024 * 1024 * 1024
|
|
case "m", "M":
|
|
uploadLimit = uploadLimit * 1024 * 1024
|
|
case "k", "K":
|
|
uploadLimit = uploadLimit * 1024
|
|
default:
|
|
uploadLimit, err = strconv.ParseInt(c.Options.ThrottleUpload, 10, 64)
|
|
if err != nil {
|
|
panic("Could not parse given Upload Limit")
|
|
}
|
|
}
|
|
// Somehow 4* is necessary
|
|
rt = rate.Every(time.Second / (4 * time.Duration(uploadLimit)))
|
|
if int(uploadLimit) > minBurstSize {
|
|
minBurstSize = int(uploadLimit)
|
|
}
|
|
c.limiter = rate.NewLimiter(rt, minBurstSize)
|
|
log.Debugf("Throttling Upload to %#v", c.limiter.Limit())
|
|
}
|
|
|
|
// initialize pake for recipient
|
|
if !c.Options.IsSender {
|
|
c.Pake, err = pake.InitCurve([]byte(c.Options.SharedSecret[5:]), 0, c.Options.Curve)
|
|
}
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
c.mutex = &sync.Mutex{}
|
|
return
|
|
}
|
|
|
|
// TransferOptions for sending
|
|
type TransferOptions struct {
|
|
PathToFiles []string
|
|
KeepPathInRemote bool
|
|
}
|
|
|
|
func isEmptyFolder(folderPath string) (bool, error) {
|
|
f, err := os.Open(folderPath)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
defer f.Close()
|
|
|
|
_, err = f.Readdirnames(1)
|
|
if err == io.EOF {
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
// helper function to walk each subfolder and parses against an ignore file.
|
|
// returns a hashmap Key: Absolute filepath, Value: boolean (true=ignore)
|
|
func gitWalk(dir string, gitObj *ignore.GitIgnore, files map[string]bool) {
|
|
var ignoredDir bool
|
|
var current string
|
|
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if isChild(current, path) && ignoredDir {
|
|
files[path] = true
|
|
return nil
|
|
}
|
|
if info.IsDir() && filepath.Base(path) == filepath.Base(dir) {
|
|
ignoredDir = false // Skip applying ignore rules for root directory
|
|
return nil
|
|
}
|
|
if gitObj.MatchesPath(info.Name()) {
|
|
files[path] = true
|
|
ignoredDir = true
|
|
current = path
|
|
return nil
|
|
} else {
|
|
files[path] = false
|
|
ignoredDir = false
|
|
return nil
|
|
}
|
|
})
|
|
if err != nil {
|
|
log.Errorf("filepath error")
|
|
}
|
|
}
|
|
|
|
func isChild(parentPath, childPath string) bool {
|
|
relPath, err := filepath.Rel(parentPath, childPath)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return !strings.HasPrefix(relPath, "..")
|
|
|
|
}
|
|
|
|
// This function retrieves the important file information
|
|
// for every file that will be transferred
|
|
func GetFilesInfo(fnames []string, zipfolder bool, ignoreGit bool) (filesInfo []FileInfo, emptyFolders []FileInfo, totalNumberFolders int, err error) {
|
|
// fnames: the relative/absolute paths of files/folders that will be transferred
|
|
totalNumberFolders = 0
|
|
var paths []string
|
|
for _, fname := range fnames {
|
|
// Support wildcard
|
|
if strings.Contains(fname, "*") {
|
|
matches, errGlob := filepath.Glob(fname)
|
|
if errGlob != nil {
|
|
err = errGlob
|
|
return
|
|
}
|
|
paths = append(paths, matches...)
|
|
continue
|
|
} else {
|
|
paths = append(paths, fname)
|
|
}
|
|
}
|
|
var ignoredPaths = make(map[string]bool)
|
|
if ignoreGit {
|
|
wd, wdErr := os.Stat(".gitignore")
|
|
if wdErr == nil {
|
|
gitIgnore, gitErr := ignore.CompileIgnoreFile(wd.Name())
|
|
if gitErr == nil {
|
|
for _, path := range paths {
|
|
abs, absErr := filepath.Abs(path)
|
|
if absErr != nil {
|
|
err = absErr
|
|
return
|
|
}
|
|
if gitIgnore.MatchesPath(path) {
|
|
ignoredPaths[abs] = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for _, path := range paths {
|
|
abs, absErr := filepath.Abs(path)
|
|
if absErr != nil {
|
|
err = absErr
|
|
return
|
|
}
|
|
file, fileErr := os.Stat(path)
|
|
if fileErr == nil && file.IsDir() {
|
|
_, subErr := os.Stat(filepath.Join(path, ".gitignore"))
|
|
if subErr == nil {
|
|
gitObj, gitObjErr := ignore.CompileIgnoreFile(filepath.Join(path, ".gitignore"))
|
|
if gitObjErr != nil {
|
|
err = gitObjErr
|
|
return
|
|
}
|
|
gitWalk(abs, gitObj, ignoredPaths)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for _, fpath := range paths {
|
|
stat, errStat := os.Lstat(fpath)
|
|
|
|
if errStat != nil {
|
|
err = errStat
|
|
return
|
|
}
|
|
|
|
absPath, errAbs := filepath.Abs(fpath)
|
|
|
|
if errAbs != nil {
|
|
err = errAbs
|
|
return
|
|
}
|
|
if stat.IsDir() && zipfolder {
|
|
if fpath[len(fpath)-1:] != "/" {
|
|
fpath += "/"
|
|
}
|
|
fpath = filepath.Dir(fpath)
|
|
dest := filepath.Base(fpath) + ".zip"
|
|
utils.ZipDirectory(dest, fpath)
|
|
stat, errStat = os.Lstat(dest)
|
|
if errStat != nil {
|
|
err = errStat
|
|
return
|
|
}
|
|
absPath, errAbs = filepath.Abs(dest)
|
|
if errAbs != nil {
|
|
err = errAbs
|
|
return
|
|
}
|
|
|
|
fInfo := FileInfo{
|
|
Name: stat.Name(),
|
|
FolderRemote: "./",
|
|
FolderSource: filepath.Dir(absPath),
|
|
Size: stat.Size(),
|
|
ModTime: stat.ModTime(),
|
|
Mode: stat.Mode(),
|
|
TempFile: true,
|
|
IsIgnored: ignoredPaths[absPath],
|
|
}
|
|
if fInfo.IsIgnored {
|
|
continue
|
|
}
|
|
filesInfo = append(filesInfo, fInfo)
|
|
continue
|
|
}
|
|
|
|
if stat.IsDir() {
|
|
err = filepath.Walk(absPath,
|
|
func(pathName string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
remoteFolder := strings.TrimPrefix(filepath.Dir(pathName),
|
|
filepath.Dir(absPath)+string(os.PathSeparator))
|
|
if !info.IsDir() {
|
|
fInfo := FileInfo{
|
|
Name: info.Name(),
|
|
FolderRemote: strings.ReplaceAll(remoteFolder, string(os.PathSeparator), "/") + "/",
|
|
FolderSource: filepath.Dir(pathName),
|
|
Size: info.Size(),
|
|
ModTime: info.ModTime(),
|
|
Mode: info.Mode(),
|
|
TempFile: false,
|
|
IsIgnored: ignoredPaths[pathName],
|
|
}
|
|
if fInfo.IsIgnored && ignoreGit {
|
|
return nil
|
|
} else {
|
|
filesInfo = append(filesInfo, fInfo)
|
|
}
|
|
} else {
|
|
if ignoredPaths[pathName] {
|
|
return filepath.SkipDir
|
|
}
|
|
isEmptyFolder, _ := isEmptyFolder(pathName)
|
|
totalNumberFolders++
|
|
if isEmptyFolder {
|
|
emptyFolders = append(emptyFolders, FileInfo{
|
|
// Name: info.Name(),
|
|
FolderRemote: strings.ReplaceAll(strings.TrimPrefix(pathName,
|
|
filepath.Dir(absPath)+string(os.PathSeparator)), string(os.PathSeparator), "/") + "/",
|
|
})
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
} else {
|
|
fInfo := FileInfo{
|
|
Name: stat.Name(),
|
|
FolderRemote: "./",
|
|
FolderSource: filepath.Dir(absPath),
|
|
Size: stat.Size(),
|
|
ModTime: stat.ModTime(),
|
|
Mode: stat.Mode(),
|
|
TempFile: false,
|
|
IsIgnored: ignoredPaths[absPath],
|
|
}
|
|
if fInfo.IsIgnored && ignoreGit {
|
|
continue
|
|
} else {
|
|
filesInfo = append(filesInfo, fInfo)
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (c *Client) sendCollectFiles(filesInfo []FileInfo) (err error) {
|
|
c.FilesToTransfer = filesInfo
|
|
totalFilesSize := int64(0)
|
|
|
|
for i, fileInfo := range c.FilesToTransfer {
|
|
var fullPath string
|
|
fullPath = fileInfo.FolderSource + string(os.PathSeparator) + fileInfo.Name
|
|
fullPath = filepath.Clean(fullPath)
|
|
|
|
if len(fileInfo.Name) > c.longestFilename {
|
|
c.longestFilename = len(fileInfo.Name)
|
|
}
|
|
|
|
if fileInfo.Mode&os.ModeSymlink != 0 {
|
|
log.Debugf("%s is symlink", fileInfo.Name)
|
|
c.FilesToTransfer[i].Symlink, err = os.Readlink(fullPath)
|
|
if err != nil {
|
|
log.Debugf("error getting symlink: %s", err.Error())
|
|
}
|
|
log.Debugf("%+v", c.FilesToTransfer[i])
|
|
}
|
|
|
|
if c.Options.HashAlgorithm == "" {
|
|
c.Options.HashAlgorithm = "xxhash"
|
|
}
|
|
|
|
c.FilesToTransfer[i].Hash, err = utils.HashFile(fullPath, c.Options.HashAlgorithm)
|
|
log.Debugf("hashed %s to %x using %s", fullPath, c.FilesToTransfer[i].Hash, c.Options.HashAlgorithm)
|
|
totalFilesSize += fileInfo.Size
|
|
if err != nil {
|
|
return
|
|
}
|
|
log.Debugf("file %d info: %+v", i, c.FilesToTransfer[i])
|
|
fmt.Fprintf(os.Stderr, "\r ")
|
|
fmt.Fprintf(os.Stderr, "\rSending %d files (%s)", i, utils.ByteCountDecimal(totalFilesSize))
|
|
}
|
|
log.Debugf("longestFilename: %+v", c.longestFilename)
|
|
fname := fmt.Sprintf("%d files", len(c.FilesToTransfer))
|
|
folderName := fmt.Sprintf("%d folders", c.TotalNumberFolders)
|
|
if len(c.FilesToTransfer) == 1 {
|
|
fname = fmt.Sprintf("'%s'", c.FilesToTransfer[0].Name)
|
|
}
|
|
if strings.HasPrefix(fname, "'croc-stdin-") {
|
|
fname = "'stdin'"
|
|
if c.Options.SendingText {
|
|
fname = "'text'"
|
|
}
|
|
}
|
|
|
|
fmt.Fprintf(os.Stderr, "\r ")
|
|
if c.TotalNumberFolders > 0 {
|
|
fmt.Fprintf(os.Stderr, "\rSending %s and %s (%s)\n", fname, folderName, utils.ByteCountDecimal(totalFilesSize))
|
|
} else {
|
|
fmt.Fprintf(os.Stderr, "\rSending %s (%s)\n", fname, utils.ByteCountDecimal(totalFilesSize))
|
|
}
|
|
return
|
|
}
|
|
|
|
func (c *Client) setupLocalRelay() {
|
|
// setup the relay locally
|
|
basePort := c.Options.BasePort
|
|
transferPorts := c.Options.TransferPorts
|
|
ports := make([]string, transferPorts+1)
|
|
for i := range ports {
|
|
ports[i] = strconv.Itoa(basePort + i)
|
|
}
|
|
openPorts := utils.FindOpenPorts("127.0.0.1", basePort, transferPorts+1)
|
|
if len(openPorts) < len(ports) {
|
|
panic("not enough open ports to run local relay")
|
|
}
|
|
for i, port := range openPorts {
|
|
ports[i] = fmt.Sprint(port)
|
|
}
|
|
for _, port := range ports {
|
|
go func(portStr string) {
|
|
debugString := "warn"
|
|
if c.Options.Debug {
|
|
debugString = "debug"
|
|
}
|
|
err := tcp.Run(debugString, "127.0.0.1", portStr, c.Options.RelayPassword, fmt.Sprintf(strconv.Itoa(basePort)+","+strconv.Itoa(transferPorts)))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}(port)
|
|
}
|
|
}
|
|
|
|
func (c *Client) broadcastOnLocalNetwork(useipv6 bool) {
|
|
var timeLimit time.Duration
|
|
// if we don't use an external relay, the broadcast messages need to be sent continuously
|
|
if c.Options.OnlyLocal {
|
|
timeLimit = -1 * time.Second
|
|
} else {
|
|
timeLimit = 30 * time.Second
|
|
}
|
|
// look for peers first
|
|
settings := peerdiscovery.Settings{
|
|
Limit: -1,
|
|
Payload: []byte("croc" + strconv.Itoa(c.Options.BasePort)),
|
|
Delay: 20 * time.Millisecond,
|
|
TimeLimit: timeLimit,
|
|
}
|
|
if useipv6 {
|
|
settings.IPVersion = peerdiscovery.IPv6
|
|
}
|
|
|
|
discoveries, err := peerdiscovery.Discover(settings)
|
|
log.Debugf("discoveries: %+v", discoveries)
|
|
|
|
if err != nil {
|
|
log.Debug(err)
|
|
}
|
|
}
|
|
|
|
func (c *Client) transferOverLocalRelay(errchan chan<- error) {
|
|
time.Sleep(500 * time.Millisecond)
|
|
log.Debug("establishing connection")
|
|
var banner string
|
|
conn, banner, ipaddr, err := tcp.ConnectToTCPServer("127.0.0.1:"+strconv.Itoa(c.Options.BasePort), c.Options.RelayPassword, c.Options.SharedSecret[:3])
|
|
log.Debugf("banner: %s", banner)
|
|
if err != nil {
|
|
err = fmt.Errorf("could not connect to 127.0.0.1:%s: %w", c.Options.BasePort, err)
|
|
log.Debug(err)
|
|
// not really an error because it will try to connect over the actual relay
|
|
return
|
|
}
|
|
log.Debugf("local connection established: %+v", conn)
|
|
for {
|
|
data, _ := conn.Receive()
|
|
if bytes.Equal(data, handshakeRequest) {
|
|
break
|
|
} else if bytes.Equal(data, []byte{1}) {
|
|
log.Debug("got ping")
|
|
} else {
|
|
log.Debugf("instead of handshake got: %s", data)
|
|
}
|
|
}
|
|
c.conn[0] = conn
|
|
log.Debug("exchanged header message")
|
|
c.Options.RelayAddress = "127.0.0.1"
|
|
var basePort, transferPorts int
|
|
banner_split := strings.Split(banner, ",")
|
|
if len(banner_split) != 2 {
|
|
panic(fmt.Sprintf("Expected port and number of transfer ports in banner: %v", banner))
|
|
}
|
|
basePort, err = strconv.Atoi(banner_split[0])
|
|
if err == nil {
|
|
c.Options.BasePort = basePort
|
|
} else {
|
|
panic(fmt.Sprintf("could not get transfer ports: %v", err))
|
|
}
|
|
transferPorts, err = strconv.Atoi(banner_split[1])
|
|
if err == nil {
|
|
c.Options.TransferPorts = transferPorts
|
|
} else {
|
|
panic(fmt.Sprintf("could not get transfer ports: %v", err))
|
|
}
|
|
if c.Options.NoMultiplexing {
|
|
log.Debug("no multiplexing")
|
|
c.Options.TransferPorts = 1
|
|
}
|
|
c.ExternalIP = ipaddr
|
|
errchan <- c.transfer()
|
|
}
|
|
|
|
// Send will send the specified file
|
|
func (c *Client) Send(filesInfo []FileInfo, emptyFoldersToTransfer []FileInfo, totalNumberFolders int) (err error) {
|
|
c.EmptyFoldersToTransfer = emptyFoldersToTransfer
|
|
c.TotalNumberFolders = totalNumberFolders
|
|
c.TotalNumberOfContents = len(filesInfo)
|
|
err = c.sendCollectFiles(filesInfo)
|
|
if err != nil {
|
|
return
|
|
}
|
|
flags := &strings.Builder{}
|
|
if c.Options.RelayAddress != models.DEFAULT_RELAY {
|
|
flags.WriteString("--relay " + c.Options.RelayAddress + " ")
|
|
}
|
|
if c.Options.RelayPassword != models.DEFAULT_PASSPHRASE {
|
|
flags.WriteString("--pass " + c.Options.RelayPassword + " ")
|
|
}
|
|
fmt.Fprintf(os.Stderr, "Code is: %[1]s\nOn the other computer run\n\ncroc %[2]s%[1]s\n", c.Options.SharedSecret, flags.String())
|
|
if c.Options.Ask {
|
|
machid, _ := machineid.ID()
|
|
fmt.Fprintf(os.Stderr, "\rYour machine ID is '%s'\n", machid)
|
|
}
|
|
// // c.spinner.Suffix = " waiting for recipient..."
|
|
// c.spinner.Start()
|
|
// create channel for quitting
|
|
// connect to the relay for messaging
|
|
errchan := make(chan error, 1)
|
|
|
|
if !c.Options.DisableLocal {
|
|
// add two things to the error channel
|
|
errchan = make(chan error, 2)
|
|
c.setupLocalRelay()
|
|
// broadcast on ipv4
|
|
go c.broadcastOnLocalNetwork(false)
|
|
// broadcast on ipv6
|
|
go c.broadcastOnLocalNetwork(true)
|
|
go c.transferOverLocalRelay(errchan)
|
|
}
|
|
|
|
if !c.Options.OnlyLocal {
|
|
go func() {
|
|
var ipaddr, banner string
|
|
var conn *comm.Comm
|
|
durations := []time.Duration{100 * time.Millisecond, 5 * time.Second}
|
|
for i, address := range []string{c.Options.RelayAddress6, c.Options.RelayAddress} {
|
|
if address == "" {
|
|
continue
|
|
}
|
|
host, port, _ := net.SplitHostPort(address)
|
|
log.Debugf("host: '%s', port: '%s'", host, port)
|
|
// Default port to :9009
|
|
if port == "" {
|
|
host = address
|
|
port = models.DEFAULT_PORT
|
|
}
|
|
log.Debugf("got host '%v' and port '%v'", host, port)
|
|
address = net.JoinHostPort(host, port)
|
|
log.Debugf("trying connection to %s", address)
|
|
conn, banner, ipaddr, err = tcp.ConnectToTCPServer(address, c.Options.RelayPassword, c.Options.SharedSecret[:3], durations[i])
|
|
if err == nil {
|
|
c.Options.RelayAddress = address
|
|
break
|
|
}
|
|
log.Debugf("could not establish '%s'", address)
|
|
}
|
|
if conn == nil && err == nil {
|
|
err = fmt.Errorf("could not connect")
|
|
}
|
|
if err != nil {
|
|
err = fmt.Errorf("could not connect to %s: %w", c.Options.RelayAddress, err)
|
|
log.Debug(err)
|
|
errchan <- err
|
|
return
|
|
}
|
|
log.Debugf("banner: %s", banner)
|
|
log.Debugf("connection established: %+v", conn)
|
|
for {
|
|
log.Debug("waiting for bytes")
|
|
data, errConn := conn.Receive()
|
|
if errConn != nil {
|
|
log.Debugf("[%+v] had error: %s", conn, errConn.Error())
|
|
}
|
|
if bytes.Equal(data, ipRequest) {
|
|
// recipient wants to try to connect to local ips
|
|
var ips []string
|
|
// only get local ips if the local is enabled
|
|
if !c.Options.DisableLocal {
|
|
// get list of local ips
|
|
ips, err = utils.GetLocalIPs()
|
|
if err != nil {
|
|
log.Debugf("error getting local ips: %v", err)
|
|
}
|
|
// prepend the port that is being listened to
|
|
ips = append([]string{strconv.Itoa(c.Options.BasePort)}, ips...)
|
|
}
|
|
bips, _ := json.Marshal(ips)
|
|
if err = conn.Send(bips); err != nil {
|
|
log.Errorf("error sending: %v", err)
|
|
}
|
|
} else if bytes.Equal(data, handshakeRequest) {
|
|
break
|
|
} else if bytes.Equal(data, []byte{1}) {
|
|
log.Debug("got ping")
|
|
continue
|
|
} else {
|
|
log.Debugf("[%+v] got weird bytes: %+v", conn, data)
|
|
// throttle the reading
|
|
errchan <- fmt.Errorf("gracefully refusing using the public relay")
|
|
return
|
|
}
|
|
}
|
|
|
|
c.conn[0] = conn
|
|
var basePort, transferPorts int
|
|
banner_split := strings.Split(banner, ",")
|
|
if len(banner_split) != 2 {
|
|
panic(fmt.Sprintf("Expected port and number of transfer ports in banner: %v", banner))
|
|
}
|
|
basePort, err = strconv.Atoi(banner_split[0])
|
|
if err == nil {
|
|
c.Options.BasePort = basePort
|
|
} else {
|
|
panic(fmt.Sprintf("could not get transfer ports: %v", err))
|
|
}
|
|
transferPorts, err = strconv.Atoi(banner_split[1])
|
|
if err == nil {
|
|
c.Options.TransferPorts = transferPorts
|
|
} else {
|
|
panic(fmt.Sprintf("could not get transfer ports: %v", err))
|
|
}
|
|
if c.Options.NoMultiplexing {
|
|
log.Debug("no multiplexing")
|
|
c.Options.TransferPorts = 1
|
|
}
|
|
c.ExternalIP = ipaddr
|
|
log.Debug("exchanged header message")
|
|
errchan <- c.transfer()
|
|
}()
|
|
}
|
|
|
|
err = <-errchan
|
|
if err == nil {
|
|
// return if no error
|
|
return
|
|
} else {
|
|
log.Debugf("error from errchan: %v", err)
|
|
if strings.Contains(err.Error(), "could not secure channel") {
|
|
return err
|
|
}
|
|
}
|
|
if !c.Options.DisableLocal {
|
|
if strings.Contains(err.Error(), "refusing files") || strings.Contains(err.Error(), "EOF") || strings.Contains(err.Error(), "bad password") {
|
|
errchan <- err
|
|
}
|
|
err = <-errchan
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Receive will receive a file
|
|
func (c *Client) Receive() (err error) {
|
|
fmt.Fprintf(os.Stderr, "connecting...")
|
|
// recipient will look for peers first
|
|
// and continue if it doesn't find any within 100 ms
|
|
usingLocal := false
|
|
isIPset := false
|
|
|
|
if c.Options.OnlyLocal || c.Options.IP != "" {
|
|
c.Options.RelayAddress = ""
|
|
c.Options.RelayAddress6 = ""
|
|
}
|
|
|
|
if c.Options.IP != "" {
|
|
// check ip version
|
|
if strings.Count(c.Options.IP, ":") >= 2 {
|
|
log.Debug("assume ipv6")
|
|
c.Options.RelayAddress6 = c.Options.IP
|
|
}
|
|
if strings.Contains(c.Options.IP, ".") {
|
|
log.Debug("assume ipv4")
|
|
c.Options.RelayAddress = c.Options.IP
|
|
}
|
|
isIPset = true
|
|
}
|
|
|
|
if !c.Options.DisableLocal && !isIPset {
|
|
log.Debug("attempt to discover peers")
|
|
var discoveries []peerdiscovery.Discovered
|
|
var wgDiscovery sync.WaitGroup
|
|
var dmux sync.Mutex
|
|
wgDiscovery.Add(2)
|
|
go func() {
|
|
defer wgDiscovery.Done()
|
|
ipv4discoveries, err1 := peerdiscovery.Discover(peerdiscovery.Settings{
|
|
Limit: 1,
|
|
Payload: []byte("ok"),
|
|
Delay: 20 * time.Millisecond,
|
|
TimeLimit: 200 * time.Millisecond,
|
|
})
|
|
if err1 == nil && len(ipv4discoveries) > 0 {
|
|
dmux.Lock()
|
|
err = err1
|
|
discoveries = append(discoveries, ipv4discoveries...)
|
|
dmux.Unlock()
|
|
}
|
|
}()
|
|
go func() {
|
|
defer wgDiscovery.Done()
|
|
ipv6discoveries, err1 := peerdiscovery.Discover(peerdiscovery.Settings{
|
|
Limit: 1,
|
|
Payload: []byte("ok"),
|
|
Delay: 20 * time.Millisecond,
|
|
TimeLimit: 200 * time.Millisecond,
|
|
IPVersion: peerdiscovery.IPv6,
|
|
})
|
|
if err1 == nil && len(ipv6discoveries) > 0 {
|
|
dmux.Lock()
|
|
err = err1
|
|
discoveries = append(discoveries, ipv6discoveries...)
|
|
dmux.Unlock()
|
|
}
|
|
}()
|
|
wgDiscovery.Wait()
|
|
|
|
if err == nil && len(discoveries) > 0 {
|
|
log.Debugf("all discoveries: %+v", discoveries)
|
|
for i := 0; i < len(discoveries); i++ {
|
|
log.Debugf("discovery %d has payload: %+v", i, discoveries[i])
|
|
if !bytes.HasPrefix(discoveries[i].Payload, []byte("croc")) {
|
|
log.Debug("skipping discovery")
|
|
continue
|
|
}
|
|
log.Debug("switching to local")
|
|
portToUse := string(bytes.TrimPrefix(discoveries[i].Payload, []byte("croc")))
|
|
if portToUse == "" {
|
|
portToUse = models.DEFAULT_PORT
|
|
}
|
|
address := net.JoinHostPort(discoveries[i].Address, portToUse)
|
|
errPing := tcp.PingServer(address)
|
|
if errPing == nil {
|
|
log.Debugf("successfully pinged '%s'", address)
|
|
c.Options.RelayAddress = address
|
|
c.ExternalIPConnected = c.Options.RelayAddress
|
|
c.Options.RelayAddress6 = ""
|
|
usingLocal = true
|
|
break
|
|
} else {
|
|
log.Debugf("could not ping: %+v", errPing)
|
|
}
|
|
}
|
|
}
|
|
log.Debugf("discoveries: %+v", discoveries)
|
|
log.Debug("establishing connection")
|
|
}
|
|
var banner string
|
|
durations := []time.Duration{200 * time.Millisecond, 5 * time.Second}
|
|
err = fmt.Errorf("found no addresses to connect")
|
|
for i, address := range []string{c.Options.RelayAddress6, c.Options.RelayAddress} {
|
|
if address == "" {
|
|
continue
|
|
}
|
|
var host, port string
|
|
host, port, _ = net.SplitHostPort(address)
|
|
// Default port to :9009
|
|
if port == "" {
|
|
host = address
|
|
port = models.DEFAULT_PORT
|
|
}
|
|
log.Debugf("got host '%v' and port '%v'", host, port)
|
|
address = net.JoinHostPort(host, port)
|
|
log.Debugf("trying connection to %s", address)
|
|
c.conn[0], banner, c.ExternalIP, err = tcp.ConnectToTCPServer(address, c.Options.RelayPassword, c.Options.SharedSecret[:3], durations[i])
|
|
if err == nil {
|
|
c.Options.RelayAddress = address
|
|
break
|
|
}
|
|
log.Debugf("could not establish '%s'", address)
|
|
}
|
|
if err != nil {
|
|
err = fmt.Errorf("could not connect to %s: %w", c.Options.RelayAddress, err)
|
|
log.Debug(err)
|
|
return
|
|
}
|
|
log.Debugf("receiver connection established: %+v", c.conn[0])
|
|
log.Debugf("banner: %s", banner)
|
|
|
|
if c.Options.TestFlag {
|
|
log.Debugf("TEST FLAG ENABLED, TESTING LOCAL IPS")
|
|
}
|
|
if c.Options.TestFlag || (!usingLocal && !c.Options.DisableLocal && !isIPset) {
|
|
// ask the sender for their local ips and port
|
|
// and try to connect to them
|
|
log.Debug("sending ips?")
|
|
var data []byte
|
|
if err = c.conn[0].Send(ipRequest); err != nil {
|
|
log.Errorf("ips send error: %v", err)
|
|
}
|
|
data, err = c.conn[0].Receive()
|
|
if err != nil {
|
|
return
|
|
}
|
|
log.Debugf("ips data: %s", data)
|
|
var ips []string
|
|
if err = json.Unmarshal(data, &ips); err != nil {
|
|
log.Debugf("ips unmarshal error: %v", err)
|
|
}
|
|
if len(ips) > 1 {
|
|
port := ips[0]
|
|
ips = ips[1:]
|
|
for _, ip := range ips {
|
|
ipv4Addr, ipv4Net, errNet := net.ParseCIDR(fmt.Sprintf("%s/24", ip))
|
|
log.Debugf("ipv4Add4: %+v, ipv4Net: %+v, err: %+v", ipv4Addr, ipv4Net, errNet)
|
|
localIps, _ := utils.GetLocalIPs()
|
|
haveLocalIP := false
|
|
for _, localIP := range localIps {
|
|
localIPparsed := net.ParseIP(localIP)
|
|
log.Debugf("localIP: %+v, localIPparsed: %+v", localIP, localIPparsed)
|
|
if ipv4Net.Contains(localIPparsed) {
|
|
haveLocalIP = true
|
|
log.Debugf("ip: %+v is a local IP", ip)
|
|
break
|
|
}
|
|
}
|
|
if !haveLocalIP {
|
|
log.Debugf("%s is not a local IP, skipping", ip)
|
|
continue
|
|
}
|
|
|
|
serverTry := net.JoinHostPort(ip, port)
|
|
conn, banner2, externalIP, errConn := tcp.ConnectToTCPServer(serverTry, c.Options.RelayPassword, c.Options.SharedSecret[:3], 500*time.Millisecond)
|
|
if errConn != nil {
|
|
log.Debug(errConn)
|
|
log.Debugf("could not connect to " + serverTry)
|
|
continue
|
|
}
|
|
log.Debugf("local connection established to %s", serverTry)
|
|
log.Debugf("banner: %s", banner2)
|
|
// reset to the local port
|
|
banner = banner2
|
|
c.Options.RelayAddress = serverTry
|
|
c.ExternalIP = externalIP
|
|
c.conn[0].Close()
|
|
c.conn[0] = nil
|
|
c.conn[0] = conn
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if err = c.conn[0].Send(handshakeRequest); err != nil {
|
|
log.Errorf("handshake send error: %v", err)
|
|
}
|
|
var basePort, transferPorts int
|
|
banner_split := strings.Split(banner, ",")
|
|
if len(banner_split) != 2 {
|
|
panic(fmt.Sprintf("Expected port and number of transfer ports in banner: %v", banner))
|
|
}
|
|
basePort, err = strconv.Atoi(banner_split[0])
|
|
if err == nil {
|
|
c.Options.BasePort = basePort
|
|
} else {
|
|
panic(fmt.Sprintf("could not get transfer ports: %v", err))
|
|
}
|
|
transferPorts, err = strconv.Atoi(banner_split[1])
|
|
if err == nil {
|
|
c.Options.TransferPorts = transferPorts
|
|
} else {
|
|
panic(fmt.Sprintf("could not get transfer ports: %v", err))
|
|
}
|
|
if c.Options.NoMultiplexing {
|
|
log.Debug("no multiplexing")
|
|
c.Options.TransferPorts = 1
|
|
}
|
|
log.Debug("exchanged header message")
|
|
fmt.Fprintf(os.Stderr, "\rsecuring channel...")
|
|
err = c.transfer()
|
|
if err == nil {
|
|
if c.numberOfTransferredFiles+len(c.EmptyFoldersToTransfer) == 0 {
|
|
fmt.Fprintf(os.Stderr, "\rNo files transferred.")
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (c *Client) transfer() (err error) {
|
|
// connect to the server
|
|
|
|
// quit with c.quit <- true
|
|
c.quit = make(chan bool)
|
|
|
|
// if recipient, initialize with sending pake information
|
|
log.Debug("ready")
|
|
if !c.Options.IsSender && !c.Step1ChannelSecured {
|
|
err = message.Send(c.conn[0], c.Key, message.Message{
|
|
Type: message.TypePAKE,
|
|
Bytes: c.Pake.Bytes(),
|
|
Bytes2: []byte(c.Options.Curve),
|
|
})
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
// listen for incoming messages and process them
|
|
for {
|
|
var data []byte
|
|
var done bool
|
|
data, err = c.conn[0].Receive()
|
|
if err != nil {
|
|
log.Debugf("got error receiving: %v", err)
|
|
if !c.Step1ChannelSecured {
|
|
err = fmt.Errorf("could not secure channel")
|
|
}
|
|
break
|
|
}
|
|
done, err = c.processMessage(data)
|
|
if err != nil {
|
|
log.Debugf("got error processing: %v", err)
|
|
break
|
|
}
|
|
if done {
|
|
break
|
|
}
|
|
}
|
|
// purge errors that come from successful transfer
|
|
if c.SuccessfulTransfer {
|
|
if err != nil {
|
|
log.Debugf("purging error: %s", err)
|
|
}
|
|
err = nil
|
|
}
|
|
if c.Options.IsSender && c.SuccessfulTransfer {
|
|
for _, file := range c.FilesToTransfer {
|
|
if file.TempFile {
|
|
fmt.Println("Removing " + file.Name)
|
|
os.Remove(file.Name)
|
|
}
|
|
}
|
|
}
|
|
|
|
if c.SuccessfulTransfer && !c.Options.IsSender {
|
|
for _, file := range c.FilesToTransfer {
|
|
if file.TempFile {
|
|
utils.UnzipDirectory(".", file.Name)
|
|
os.Remove(file.Name)
|
|
log.Debugf("Removing %s\n", file.Name)
|
|
}
|
|
}
|
|
}
|
|
|
|
if c.Options.Stdout && !c.Options.IsSender {
|
|
pathToFile := path.Join(
|
|
c.FilesToTransfer[c.FilesToTransferCurrentNum].FolderRemote,
|
|
c.FilesToTransfer[c.FilesToTransferCurrentNum].Name,
|
|
)
|
|
log.Debugf("pathToFile: %s", pathToFile)
|
|
// close if not closed already
|
|
if !c.CurrentFileIsClosed {
|
|
c.CurrentFile.Close()
|
|
c.CurrentFileIsClosed = true
|
|
}
|
|
if err = os.Remove(pathToFile); err != nil {
|
|
log.Warnf("error removing %s: %v", pathToFile, err)
|
|
}
|
|
fmt.Print("\n")
|
|
}
|
|
if err != nil && strings.Contains(err.Error(), "pake not successful") {
|
|
log.Debugf("pake error: %s", err.Error())
|
|
err = fmt.Errorf("password mismatch")
|
|
}
|
|
if err != nil && strings.Contains(err.Error(), "unexpected end of JSON input") {
|
|
log.Debugf("error: %s", err.Error())
|
|
err = fmt.Errorf("room not ready")
|
|
}
|
|
return
|
|
}
|
|
|
|
func (c *Client) createEmptyFolder(i int) (err error) {
|
|
err = os.MkdirAll(c.EmptyFoldersToTransfer[i].FolderRemote, os.ModePerm)
|
|
if err != nil {
|
|
return
|
|
}
|
|
fmt.Fprintf(os.Stderr, "%s\n", c.EmptyFoldersToTransfer[i].FolderRemote)
|
|
c.bar = progressbar.NewOptions64(1,
|
|
progressbar.OptionOnCompletion(func() {
|
|
c.fmtPrintUpdate()
|
|
}),
|
|
progressbar.OptionSetWidth(20),
|
|
progressbar.OptionSetDescription(" "),
|
|
progressbar.OptionSetRenderBlankState(true),
|
|
progressbar.OptionShowBytes(true),
|
|
progressbar.OptionShowCount(),
|
|
progressbar.OptionSetWriter(os.Stderr),
|
|
progressbar.OptionSetVisibility(!c.Options.SendingText),
|
|
)
|
|
c.bar.Finish()
|
|
return
|
|
}
|
|
|
|
func (c *Client) processMessageFileInfo(m message.Message) (done bool, err error) {
|
|
var senderInfo SenderInfo
|
|
err = json.Unmarshal(m.Bytes, &senderInfo)
|
|
if err != nil {
|
|
log.Debug(err)
|
|
return
|
|
}
|
|
c.Options.SendingText = senderInfo.SendingText
|
|
c.Options.NoCompress = senderInfo.NoCompress
|
|
c.Options.HashAlgorithm = senderInfo.HashAlgorithm
|
|
c.EmptyFoldersToTransfer = senderInfo.EmptyFoldersToTransfer
|
|
c.TotalNumberFolders = senderInfo.TotalNumberFolders
|
|
c.FilesToTransfer = senderInfo.FilesToTransfer
|
|
c.TotalNumberOfContents = 0
|
|
if c.FilesToTransfer != nil {
|
|
c.TotalNumberOfContents += len(c.FilesToTransfer)
|
|
}
|
|
if c.EmptyFoldersToTransfer != nil {
|
|
c.TotalNumberOfContents += len(c.EmptyFoldersToTransfer)
|
|
}
|
|
|
|
if c.Options.HashAlgorithm == "" {
|
|
c.Options.HashAlgorithm = "xxhash"
|
|
}
|
|
log.Debugf("using hash algorithm: %s", c.Options.HashAlgorithm)
|
|
if c.Options.NoCompress {
|
|
log.Debug("disabling compression")
|
|
}
|
|
if c.Options.SendingText {
|
|
c.Options.Stdout = true
|
|
}
|
|
|
|
fname := fmt.Sprintf("%d files", len(c.FilesToTransfer))
|
|
folderName := fmt.Sprintf("%d folders", c.TotalNumberFolders)
|
|
if len(c.FilesToTransfer) == 1 {
|
|
fname = fmt.Sprintf("'%s'", c.FilesToTransfer[0].Name)
|
|
}
|
|
totalSize := int64(0)
|
|
for i, fi := range c.FilesToTransfer {
|
|
totalSize += fi.Size
|
|
if len(fi.Name) > c.longestFilename {
|
|
c.longestFilename = len(fi.Name)
|
|
}
|
|
if strings.HasPrefix(fi.Name, "croc-stdin-") && c.Options.SendingText {
|
|
c.FilesToTransfer[i].Name, err = utils.RandomFileName()
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
// c.spinner.Stop()
|
|
action := "Accept"
|
|
if c.Options.SendingText {
|
|
action = "Display"
|
|
fname = "text message"
|
|
}
|
|
if !c.Options.NoPrompt || c.Options.Ask || senderInfo.Ask {
|
|
if c.Options.Ask || senderInfo.Ask {
|
|
machID, _ := machineid.ID()
|
|
fmt.Fprintf(os.Stderr, "\rYour machine id is '%s'.\n%s %s (%s) from '%s'? (Y/n) ", machID, action, fname, utils.ByteCountDecimal(totalSize), senderInfo.MachineID)
|
|
} else {
|
|
if c.TotalNumberFolders > 0 {
|
|
fmt.Fprintf(os.Stderr, "\r%s %s and %s (%s)? (Y/n) ", action, fname, folderName, utils.ByteCountDecimal(totalSize))
|
|
} else {
|
|
fmt.Fprintf(os.Stderr, "\r%s %s (%s)? (Y/n) ", action, fname, utils.ByteCountDecimal(totalSize))
|
|
}
|
|
}
|
|
choice := strings.ToLower(utils.GetInput(""))
|
|
if choice != "" && choice != "y" && choice != "yes" {
|
|
err = message.Send(c.conn[0], c.Key, message.Message{
|
|
Type: message.TypeError,
|
|
Message: "refusing files",
|
|
})
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return true, fmt.Errorf("refused files")
|
|
}
|
|
} else {
|
|
fmt.Fprintf(os.Stderr, "\rReceiving %s (%s) \n", fname, utils.ByteCountDecimal(totalSize))
|
|
}
|
|
fmt.Fprintf(os.Stderr, "\nReceiving (<-%s)\n", c.ExternalIPConnected)
|
|
|
|
for i := 0; i < len(c.EmptyFoldersToTransfer); i += 1 {
|
|
_, errExists := os.Stat(c.EmptyFoldersToTransfer[i].FolderRemote)
|
|
if os.IsNotExist(errExists) {
|
|
err = c.createEmptyFolder(i)
|
|
if err != nil {
|
|
return
|
|
}
|
|
} else {
|
|
isEmpty, _ := isEmptyFolder(c.EmptyFoldersToTransfer[i].FolderRemote)
|
|
if !isEmpty {
|
|
log.Debug("asking to overwrite")
|
|
prompt := fmt.Sprintf("\n%s already has some content in it. \nDo you want"+
|
|
" to overwrite it with an empty folder? (y/N) ", c.EmptyFoldersToTransfer[i].FolderRemote)
|
|
choice := strings.ToLower(utils.GetInput(prompt))
|
|
if choice == "y" || choice == "yes" {
|
|
err = c.createEmptyFolder(i)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// if no files are to be transferred, then we can end the file transfer process
|
|
if c.FilesToTransfer == nil {
|
|
c.SuccessfulTransfer = true
|
|
c.Step3RecipientRequestFile = true
|
|
c.Step4FileTransferred = true
|
|
errStopTransfer := message.Send(c.conn[0], c.Key, message.Message{
|
|
Type: message.TypeFinished,
|
|
})
|
|
if errStopTransfer != nil {
|
|
err = errStopTransfer
|
|
}
|
|
}
|
|
log.Debug(c.FilesToTransfer)
|
|
c.Step2FileInfoTransferred = true
|
|
return
|
|
}
|
|
|
|
func (c *Client) processMessagePake(m message.Message) (err error) {
|
|
log.Debug("received pake payload")
|
|
|
|
var salt []byte
|
|
if c.Options.IsSender {
|
|
// initialize curve based on the recipient's choice
|
|
log.Debugf("using curve %s", string(m.Bytes2))
|
|
c.Pake, err = pake.InitCurve([]byte(c.Options.SharedSecret[5:]), 1, string(m.Bytes2))
|
|
if err != nil {
|
|
log.Error(err)
|
|
return
|
|
}
|
|
|
|
// update the pake
|
|
err = c.Pake.Update(m.Bytes)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// generate salt and send it back to recipient
|
|
log.Debug("generating salt")
|
|
salt = make([]byte, 8)
|
|
if _, rerr := rand.Read(salt); err != nil {
|
|
log.Errorf("can't generate random numbers: %v", rerr)
|
|
return
|
|
}
|
|
log.Debug("sender sending pake+salt")
|
|
err = message.Send(c.conn[0], c.Key, message.Message{
|
|
Type: message.TypePAKE,
|
|
Bytes: c.Pake.Bytes(),
|
|
Bytes2: salt,
|
|
})
|
|
} else {
|
|
err = c.Pake.Update(m.Bytes)
|
|
if err != nil {
|
|
return
|
|
}
|
|
salt = m.Bytes2
|
|
}
|
|
// generate key
|
|
key, err := c.Pake.SessionKey()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.Key, _, err = crypt.New(key, salt)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log.Debugf("generated key = %+x with salt %x", c.Key, salt)
|
|
|
|
// connects to the other ports of the server for transfer
|
|
var wg sync.WaitGroup
|
|
wg.Add(c.Options.TransferPorts + 1)
|
|
for i := 0; i <= c.Options.TransferPorts; i++ {
|
|
log.Debugf("port: [%d]", c.Options.BasePort+i)
|
|
go func(j int) {
|
|
defer wg.Done()
|
|
var host string
|
|
if c.Options.RelayAddress == "127.0.0.1" {
|
|
host = c.Options.RelayAddress
|
|
} else {
|
|
host, _, err = net.SplitHostPort(c.Options.RelayAddress)
|
|
if err != nil {
|
|
log.Errorf("bad relay address %s", c.Options.RelayAddress)
|
|
return
|
|
}
|
|
}
|
|
server := net.JoinHostPort(host, strconv.Itoa(c.Options.BasePort+j))
|
|
log.Debugf("connecting to %s", server)
|
|
c.conn[j+1], _, _, err = tcp.ConnectToTCPServer(
|
|
server,
|
|
c.Options.RelayPassword,
|
|
fmt.Sprintf("%s-%d", utils.SHA256(c.Options.SharedSecret[:5])[:6], j),
|
|
)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
log.Debugf("connected to %s", server)
|
|
if !c.Options.IsSender {
|
|
go c.receiveData(j)
|
|
}
|
|
}(i)
|
|
}
|
|
wg.Wait()
|
|
|
|
if !c.Options.IsSender {
|
|
log.Debug("sending external IP")
|
|
err = message.Send(c.conn[0], c.Key, message.Message{
|
|
Type: message.TypeExternalIP,
|
|
Message: c.ExternalIP,
|
|
Bytes: m.Bytes,
|
|
})
|
|
}
|
|
return
|
|
}
|
|
|
|
func (c *Client) processExternalIP(m message.Message) (done bool, err error) {
|
|
log.Debugf("received external IP: %+v", m)
|
|
if c.Options.IsSender {
|
|
err = message.Send(c.conn[0], c.Key, message.Message{
|
|
Type: message.TypeExternalIP,
|
|
Message: c.ExternalIP,
|
|
})
|
|
if err != nil {
|
|
return true, err
|
|
}
|
|
}
|
|
if c.ExternalIPConnected == "" {
|
|
// it can be preset by the local relay
|
|
c.ExternalIPConnected = m.Message
|
|
}
|
|
log.Debugf("connected as %s -> %s", c.ExternalIP, c.ExternalIPConnected)
|
|
c.Step1ChannelSecured = true
|
|
return
|
|
}
|
|
|
|
func (c *Client) processMessage(payload []byte) (done bool, err error) {
|
|
m, err := message.Decode(c.Key, payload)
|
|
if err != nil {
|
|
err = fmt.Errorf("problem with decoding: %w", err)
|
|
log.Debug(err)
|
|
return
|
|
}
|
|
|
|
// only "pake" messages should be unencrypted
|
|
// if a non-"pake" message is received unencrypted something
|
|
// is weird
|
|
if m.Type != message.TypePAKE && c.Key == nil {
|
|
err = fmt.Errorf("unencrypted communication rejected")
|
|
done = true
|
|
return
|
|
}
|
|
|
|
switch m.Type {
|
|
case message.TypeFinished:
|
|
err = message.Send(c.conn[0], c.Key, message.Message{
|
|
Type: message.TypeFinished,
|
|
})
|
|
done = true
|
|
c.SuccessfulTransfer = true
|
|
return
|
|
case message.TypePAKE:
|
|
err = c.processMessagePake(m)
|
|
if err != nil {
|
|
err = fmt.Errorf("pake not successful: %w", err)
|
|
log.Debug(err)
|
|
}
|
|
case message.TypeExternalIP:
|
|
done, err = c.processExternalIP(m)
|
|
case message.TypeError:
|
|
// c.spinner.Stop()
|
|
fmt.Print("\r")
|
|
err = fmt.Errorf("peer error: %s", m.Message)
|
|
return true, err
|
|
case message.TypeFileInfo:
|
|
done, err = c.processMessageFileInfo(m)
|
|
case message.TypeRecipientReady:
|
|
var remoteFile RemoteFileRequest
|
|
err = json.Unmarshal(m.Bytes, &remoteFile)
|
|
if err != nil {
|
|
return
|
|
}
|
|
c.FilesToTransferCurrentNum = remoteFile.FilesToTransferCurrentNum
|
|
c.CurrentFileChunkRanges = remoteFile.CurrentFileChunkRanges
|
|
c.CurrentFileChunks = utils.ChunkRangesToChunks(c.CurrentFileChunkRanges)
|
|
log.Debugf("current file chunks: %+v", c.CurrentFileChunks)
|
|
c.mutex.Lock()
|
|
c.chunkMap = make(map[uint64]struct{})
|
|
for _, chunk := range c.CurrentFileChunks {
|
|
c.chunkMap[uint64(chunk)] = struct{}{}
|
|
}
|
|
c.mutex.Unlock()
|
|
c.Step3RecipientRequestFile = true
|
|
|
|
if c.Options.Ask {
|
|
fmt.Fprintf(os.Stderr, "Send to machine '%s'? (Y/n) ", remoteFile.MachineID)
|
|
choice := strings.ToLower(utils.GetInput(""))
|
|
if choice != "" && choice != "y" && choice != "yes" {
|
|
err = message.Send(c.conn[0], c.Key, message.Message{
|
|
Type: message.TypeError,
|
|
Message: "refusing files",
|
|
})
|
|
done = true
|
|
return
|
|
}
|
|
}
|
|
case message.TypeCloseSender:
|
|
c.bar.Finish()
|
|
log.Debug("close-sender received...")
|
|
c.Step4FileTransferred = false
|
|
c.Step3RecipientRequestFile = false
|
|
log.Debug("sending close-recipient")
|
|
err = message.Send(c.conn[0], c.Key, message.Message{
|
|
Type: message.TypeCloseRecipient,
|
|
})
|
|
case message.TypeCloseRecipient:
|
|
c.Step4FileTransferred = false
|
|
c.Step3RecipientRequestFile = false
|
|
}
|
|
if err != nil {
|
|
log.Debugf("got error from processing message: %v", err)
|
|
return
|
|
}
|
|
err = c.updateState()
|
|
if err != nil {
|
|
log.Debugf("got error from updating state: %v", err)
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
func (c *Client) updateIfSenderChannelSecured() (err error) {
|
|
if c.Options.IsSender && c.Step1ChannelSecured && !c.Step2FileInfoTransferred {
|
|
var b []byte
|
|
machID, _ := machineid.ID()
|
|
b, err = json.Marshal(SenderInfo{
|
|
FilesToTransfer: c.FilesToTransfer,
|
|
EmptyFoldersToTransfer: c.EmptyFoldersToTransfer,
|
|
MachineID: machID,
|
|
Ask: c.Options.Ask,
|
|
TotalNumberFolders: c.TotalNumberFolders,
|
|
SendingText: c.Options.SendingText,
|
|
NoCompress: c.Options.NoCompress,
|
|
HashAlgorithm: c.Options.HashAlgorithm,
|
|
})
|
|
if err != nil {
|
|
log.Error(err)
|
|
return
|
|
}
|
|
err = message.Send(c.conn[0], c.Key, message.Message{
|
|
Type: message.TypeFileInfo,
|
|
Bytes: b,
|
|
})
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
c.Step2FileInfoTransferred = true
|
|
}
|
|
return
|
|
}
|
|
|
|
func (c *Client) recipientInitializeFile() (err error) {
|
|
// start initiating the process to receive a new file
|
|
log.Debugf("working on file %d", c.FilesToTransferCurrentNum)
|
|
|
|
// recipient sets the file
|
|
pathToFile := path.Join(
|
|
c.FilesToTransfer[c.FilesToTransferCurrentNum].FolderRemote,
|
|
c.FilesToTransfer[c.FilesToTransferCurrentNum].Name,
|
|
)
|
|
folderForFile, _ := filepath.Split(pathToFile)
|
|
folderForFileBase := filepath.Base(folderForFile)
|
|
if folderForFileBase != "." && folderForFileBase != "" {
|
|
if err := os.MkdirAll(folderForFile, os.ModePerm); err != nil {
|
|
log.Errorf("can't create %s: %v", folderForFile, err)
|
|
}
|
|
}
|
|
var errOpen error
|
|
c.CurrentFile, errOpen = os.OpenFile(
|
|
pathToFile,
|
|
os.O_WRONLY, 0o666)
|
|
var truncate bool // default false
|
|
c.CurrentFileChunks = []int64{}
|
|
c.CurrentFileChunkRanges = []int64{}
|
|
if errOpen == nil {
|
|
stat, _ := c.CurrentFile.Stat()
|
|
truncate = stat.Size() != c.FilesToTransfer[c.FilesToTransferCurrentNum].Size
|
|
if !truncate {
|
|
// recipient requests the file and chunks (if empty, then should receive all chunks)
|
|
// TODO: determine the missing chunks
|
|
c.CurrentFileChunkRanges = utils.MissingChunks(
|
|
pathToFile,
|
|
c.FilesToTransfer[c.FilesToTransferCurrentNum].Size,
|
|
models.TCP_BUFFER_SIZE/2,
|
|
)
|
|
}
|
|
} else {
|
|
c.CurrentFile, errOpen = os.Create(pathToFile)
|
|
if errOpen != nil {
|
|
errOpen = fmt.Errorf("could not create %s: %w", pathToFile, errOpen)
|
|
log.Error(errOpen)
|
|
return errOpen
|
|
}
|
|
errChmod := os.Chmod(pathToFile, c.FilesToTransfer[c.FilesToTransferCurrentNum].Mode.Perm())
|
|
if errChmod != nil {
|
|
log.Error(errChmod)
|
|
}
|
|
truncate = true
|
|
}
|
|
if truncate {
|
|
err := c.CurrentFile.Truncate(c.FilesToTransfer[c.FilesToTransferCurrentNum].Size)
|
|
if err != nil {
|
|
err = fmt.Errorf("could not truncate %s: %w", pathToFile, err)
|
|
log.Error(err)
|
|
return err
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (c *Client) recipientGetFileReady(finished bool) (err error) {
|
|
if finished {
|
|
// TODO: do the last finishing stuff
|
|
log.Debug("finished")
|
|
err = message.Send(c.conn[0], c.Key, message.Message{
|
|
Type: message.TypeFinished,
|
|
})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
c.SuccessfulTransfer = true
|
|
c.FilesHasFinished[c.FilesToTransferCurrentNum] = struct{}{}
|
|
}
|
|
|
|
err = c.recipientInitializeFile()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
c.TotalSent = 0
|
|
c.CurrentFileIsClosed = false
|
|
machID, _ := machineid.ID()
|
|
bRequest, _ := json.Marshal(RemoteFileRequest{
|
|
CurrentFileChunkRanges: c.CurrentFileChunkRanges,
|
|
FilesToTransferCurrentNum: c.FilesToTransferCurrentNum,
|
|
MachineID: machID,
|
|
})
|
|
log.Debug("converting to chunk range")
|
|
c.CurrentFileChunks = utils.ChunkRangesToChunks(c.CurrentFileChunkRanges)
|
|
|
|
if !finished {
|
|
// setup the progressbar
|
|
c.setBar()
|
|
}
|
|
|
|
log.Debugf("sending recipient ready with %d chunks", len(c.CurrentFileChunks))
|
|
err = message.Send(c.conn[0], c.Key, message.Message{
|
|
Type: message.TypeRecipientReady,
|
|
Bytes: bRequest,
|
|
})
|
|
if err != nil {
|
|
return
|
|
}
|
|
c.Step3RecipientRequestFile = true
|
|
return
|
|
}
|
|
|
|
func (c *Client) createEmptyFileAndFinish(fileInfo FileInfo, i int) (err error) {
|
|
log.Debugf("touching file with folder / name")
|
|
if !utils.Exists(fileInfo.FolderRemote) {
|
|
err = os.MkdirAll(fileInfo.FolderRemote, os.ModePerm)
|
|
if err != nil {
|
|
log.Error(err)
|
|
return
|
|
}
|
|
}
|
|
pathToFile := path.Join(fileInfo.FolderRemote, fileInfo.Name)
|
|
if fileInfo.Symlink != "" {
|
|
log.Debug("creating symlink")
|
|
// remove symlink if it exists
|
|
if _, errExists := os.Lstat(pathToFile); errExists == nil {
|
|
os.Remove(pathToFile)
|
|
}
|
|
err = os.Symlink(fileInfo.Symlink, pathToFile)
|
|
if err != nil {
|
|
return
|
|
}
|
|
} else {
|
|
emptyFile, errCreate := os.Create(pathToFile)
|
|
if errCreate != nil {
|
|
log.Error(errCreate)
|
|
err = errCreate
|
|
return
|
|
}
|
|
emptyFile.Close()
|
|
}
|
|
// setup the progressbar
|
|
description := fmt.Sprintf("%-*s", c.longestFilename, c.FilesToTransfer[i].Name)
|
|
if len(c.FilesToTransfer) == 1 {
|
|
description = c.FilesToTransfer[i].Name
|
|
// description = ""
|
|
} else {
|
|
description = " " + description
|
|
}
|
|
c.bar = progressbar.NewOptions64(1,
|
|
progressbar.OptionOnCompletion(func() {
|
|
c.fmtPrintUpdate()
|
|
}),
|
|
progressbar.OptionSetWidth(20),
|
|
progressbar.OptionSetDescription(description),
|
|
progressbar.OptionSetRenderBlankState(true),
|
|
progressbar.OptionShowBytes(true),
|
|
progressbar.OptionShowCount(),
|
|
progressbar.OptionSetWriter(os.Stderr),
|
|
progressbar.OptionSetVisibility(!c.Options.SendingText),
|
|
)
|
|
c.bar.Finish()
|
|
return
|
|
}
|
|
|
|
func (c *Client) updateIfRecipientHasFileInfo() (err error) {
|
|
if c.Options.IsSender || !c.Step2FileInfoTransferred || c.Step3RecipientRequestFile {
|
|
return
|
|
}
|
|
// find the next file to transfer and send that number
|
|
// if the files are the same size, then look for missing chunks
|
|
finished := true
|
|
for i, fileInfo := range c.FilesToTransfer {
|
|
if _, ok := c.FilesHasFinished[i]; ok {
|
|
continue
|
|
}
|
|
if i < c.FilesToTransferCurrentNum {
|
|
continue
|
|
}
|
|
log.Debugf("checking %+v", fileInfo)
|
|
recipientFileInfo, errRecipientFile := os.Lstat(path.Join(fileInfo.FolderRemote, fileInfo.Name))
|
|
var errHash error
|
|
var fileHash []byte
|
|
if errRecipientFile == nil && recipientFileInfo.Size() == fileInfo.Size {
|
|
// the file exists, but is same size, so hash it
|
|
fileHash, errHash = utils.HashFile(path.Join(fileInfo.FolderRemote, fileInfo.Name), c.Options.HashAlgorithm)
|
|
}
|
|
if fileInfo.Size == 0 || fileInfo.Symlink != "" {
|
|
err = c.createEmptyFileAndFinish(fileInfo, i)
|
|
if err != nil {
|
|
return
|
|
} else {
|
|
c.numberOfTransferredFiles++
|
|
}
|
|
continue
|
|
}
|
|
log.Debugf("%s %+x %+x %+v", fileInfo.Name, fileHash, fileInfo.Hash, errHash)
|
|
if !bytes.Equal(fileHash, fileInfo.Hash) {
|
|
log.Debugf("hashed %s to %x using %s", fileInfo.Name, fileHash, c.Options.HashAlgorithm)
|
|
log.Debugf("hashes are not equal %x != %x", fileHash, fileInfo.Hash)
|
|
if errHash == nil && !c.Options.Overwrite && errRecipientFile == nil && !strings.HasPrefix(fileInfo.Name, "croc-stdin-") && !c.Options.SendingText {
|
|
|
|
missingChunks := utils.ChunkRangesToChunks(utils.MissingChunks(
|
|
path.Join(fileInfo.FolderRemote, fileInfo.Name),
|
|
fileInfo.Size,
|
|
models.TCP_BUFFER_SIZE/2,
|
|
))
|
|
percentDone := 100 - float64(len(missingChunks)*models.TCP_BUFFER_SIZE/2)/float64(fileInfo.Size)*100
|
|
|
|
log.Debug("asking to overwrite")
|
|
prompt := fmt.Sprintf("\nOverwrite '%s'? (y/N) ", path.Join(fileInfo.FolderRemote, fileInfo.Name))
|
|
if percentDone < 99 {
|
|
prompt = fmt.Sprintf("\nResume '%s' (%2.1f%%)? (y/N) ", path.Join(fileInfo.FolderRemote, fileInfo.Name), percentDone)
|
|
}
|
|
choice := strings.ToLower(utils.GetInput(prompt))
|
|
if choice != "y" && choice != "yes" {
|
|
fmt.Fprintf(os.Stderr, "skipping '%s'", path.Join(fileInfo.FolderRemote, fileInfo.Name))
|
|
continue
|
|
}
|
|
}
|
|
} else {
|
|
log.Debugf("hashes are equal %x == %x", fileHash, fileInfo.Hash)
|
|
}
|
|
if errHash != nil {
|
|
// probably can't find, its okay
|
|
log.Debug(errHash)
|
|
}
|
|
if errHash != nil || !bytes.Equal(fileHash, fileInfo.Hash) {
|
|
finished = false
|
|
c.FilesToTransferCurrentNum = i
|
|
c.numberOfTransferredFiles++
|
|
newFolder, _ := filepath.Split(fileInfo.FolderRemote)
|
|
if newFolder != c.LastFolder && len(c.FilesToTransfer) > 0 && !c.Options.SendingText && newFolder != "./" {
|
|
fmt.Fprintf(os.Stderr, "\r%s\n", newFolder)
|
|
}
|
|
c.LastFolder = newFolder
|
|
break
|
|
}
|
|
}
|
|
c.recipientGetFileReady(finished)
|
|
return
|
|
}
|
|
|
|
func (c *Client) fmtPrintUpdate() {
|
|
c.finishedNum++
|
|
if c.TotalNumberOfContents > 1 {
|
|
fmt.Fprintf(os.Stderr, " %d/%d\n", c.finishedNum, c.TotalNumberOfContents)
|
|
} else {
|
|
fmt.Fprintf(os.Stderr, "\n")
|
|
}
|
|
}
|
|
|
|
func (c *Client) updateState() (err error) {
|
|
err = c.updateIfSenderChannelSecured()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
err = c.updateIfRecipientHasFileInfo()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
if c.Options.IsSender && c.Step3RecipientRequestFile && !c.Step4FileTransferred {
|
|
log.Debug("start sending data!")
|
|
|
|
if !c.firstSend {
|
|
fmt.Fprintf(os.Stderr, "\nSending (->%s)\n", c.ExternalIPConnected)
|
|
c.firstSend = true
|
|
// if there are empty files, show them as already have been transferred now
|
|
for i := range c.FilesToTransfer {
|
|
if c.FilesToTransfer[i].Size == 0 {
|
|
// setup the progressbar and takedown the progress bar for empty files
|
|
description := fmt.Sprintf("%-*s", c.longestFilename, c.FilesToTransfer[i].Name)
|
|
if len(c.FilesToTransfer) == 1 {
|
|
description = c.FilesToTransfer[i].Name
|
|
// description = ""
|
|
}
|
|
c.bar = progressbar.NewOptions64(1,
|
|
progressbar.OptionOnCompletion(func() {
|
|
c.fmtPrintUpdate()
|
|
}),
|
|
progressbar.OptionSetWidth(20),
|
|
progressbar.OptionSetDescription(description),
|
|
progressbar.OptionSetRenderBlankState(true),
|
|
progressbar.OptionShowBytes(true),
|
|
progressbar.OptionShowCount(),
|
|
progressbar.OptionSetWriter(os.Stderr),
|
|
progressbar.OptionSetVisibility(!c.Options.SendingText),
|
|
)
|
|
c.bar.Finish()
|
|
}
|
|
}
|
|
}
|
|
c.Step4FileTransferred = true
|
|
// setup the progressbar
|
|
c.setBar()
|
|
c.TotalSent = 0
|
|
c.CurrentFileIsClosed = false
|
|
log.Debug("beginning sending comms")
|
|
pathToFile := path.Join(
|
|
c.FilesToTransfer[c.FilesToTransferCurrentNum].FolderSource,
|
|
c.FilesToTransfer[c.FilesToTransferCurrentNum].Name,
|
|
)
|
|
c.fread, err = os.Open(pathToFile)
|
|
c.numfinished = 0
|
|
if err != nil {
|
|
return
|
|
}
|
|
for i := 0; i <= c.Options.TransferPorts; i++ {
|
|
log.Debugf("starting sending over comm %d", i)
|
|
go c.sendData(i)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (c *Client) setBar() {
|
|
description := fmt.Sprintf("%-*s", c.longestFilename, c.FilesToTransfer[c.FilesToTransferCurrentNum].Name)
|
|
folder, _ := filepath.Split(c.FilesToTransfer[c.FilesToTransferCurrentNum].FolderRemote)
|
|
if folder == "./" {
|
|
description = c.FilesToTransfer[c.FilesToTransferCurrentNum].Name
|
|
} else if !c.Options.IsSender {
|
|
description = " " + description
|
|
}
|
|
c.bar = progressbar.NewOptions64(
|
|
c.FilesToTransfer[c.FilesToTransferCurrentNum].Size,
|
|
progressbar.OptionOnCompletion(func() {
|
|
c.fmtPrintUpdate()
|
|
}),
|
|
progressbar.OptionSetWidth(20),
|
|
progressbar.OptionSetDescription(description),
|
|
progressbar.OptionSetRenderBlankState(true),
|
|
progressbar.OptionShowBytes(true),
|
|
progressbar.OptionShowCount(),
|
|
progressbar.OptionSetWriter(os.Stderr),
|
|
progressbar.OptionThrottle(100*time.Millisecond),
|
|
progressbar.OptionSetVisibility(!c.Options.SendingText),
|
|
)
|
|
byteToDo := int64(len(c.CurrentFileChunks) * models.TCP_BUFFER_SIZE / 2)
|
|
if byteToDo > 0 {
|
|
bytesDone := c.FilesToTransfer[c.FilesToTransferCurrentNum].Size - byteToDo
|
|
log.Debug(byteToDo)
|
|
log.Debug(c.FilesToTransfer[c.FilesToTransferCurrentNum].Size)
|
|
log.Debug(bytesDone)
|
|
if bytesDone > 0 {
|
|
c.bar.Add64(bytesDone)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Client) receiveData(i int) {
|
|
log.Debugf("%d receiving data", i)
|
|
for {
|
|
data, err := c.conn[i+1].Receive()
|
|
if err != nil {
|
|
break
|
|
}
|
|
if bytes.Equal(data, []byte{1}) {
|
|
log.Debug("got ping")
|
|
continue
|
|
}
|
|
|
|
data, err = crypt.Decrypt(data, c.Key)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
if !c.Options.NoCompress {
|
|
data = compress.Decompress(data)
|
|
}
|
|
|
|
// get position
|
|
var position uint64
|
|
rbuf := bytes.NewReader(data[:8])
|
|
err = binary.Read(rbuf, binary.LittleEndian, &position)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
positionInt64 := int64(position)
|
|
|
|
c.mutex.Lock()
|
|
_, err = c.CurrentFile.WriteAt(data[8:], positionInt64)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
c.bar.Add(len(data[8:]))
|
|
c.TotalSent += int64(len(data[8:]))
|
|
c.TotalChunksTransferred++
|
|
// log.Debug(len(c.CurrentFileChunks), c.TotalChunksTransferred, c.TotalSent, c.FilesToTransfer[c.FilesToTransferCurrentNum].Size)
|
|
|
|
if !c.CurrentFileIsClosed && (c.TotalChunksTransferred == len(c.CurrentFileChunks) || c.TotalSent == c.FilesToTransfer[c.FilesToTransferCurrentNum].Size) {
|
|
c.CurrentFileIsClosed = true
|
|
log.Debug("finished receiving!")
|
|
if err = c.CurrentFile.Close(); err != nil {
|
|
log.Debugf("error closing %s: %v", c.CurrentFile.Name(), err)
|
|
} else {
|
|
log.Debugf("Successful closing %s", c.CurrentFile.Name())
|
|
}
|
|
if c.Options.Stdout || c.Options.SendingText {
|
|
pathToFile := path.Join(
|
|
c.FilesToTransfer[c.FilesToTransferCurrentNum].FolderRemote,
|
|
c.FilesToTransfer[c.FilesToTransferCurrentNum].Name,
|
|
)
|
|
b, _ := os.ReadFile(pathToFile)
|
|
fmt.Print(string(b))
|
|
}
|
|
log.Debug("sending close-sender")
|
|
err = message.Send(c.conn[0], c.Key, message.Message{
|
|
Type: message.TypeCloseSender,
|
|
})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
c.mutex.Unlock()
|
|
}
|
|
}
|
|
|
|
func (c *Client) sendData(i int) {
|
|
defer func() {
|
|
log.Debugf("finished with %d", i)
|
|
c.numfinished++
|
|
if c.numfinished == c.Options.TransferPorts+1 {
|
|
log.Debug("closing file")
|
|
if err := c.fread.Close(); err != nil {
|
|
log.Errorf("error closing file: %v", err)
|
|
}
|
|
}
|
|
}()
|
|
|
|
var readingPos int64
|
|
pos := uint64(0)
|
|
curi := float64(0)
|
|
for {
|
|
// Read file
|
|
data := make([]byte, models.TCP_BUFFER_SIZE/2)
|
|
// log.Debugf("%d trying to read", i)
|
|
n, errRead := c.fread.ReadAt(data, readingPos)
|
|
// log.Debugf("%d read %d bytes", i, n)
|
|
readingPos += int64(n)
|
|
if c.limiter != nil {
|
|
r := c.limiter.ReserveN(time.Now(), n)
|
|
log.Debugf("Limiting Upload for %d", r.Delay())
|
|
time.Sleep(r.Delay())
|
|
}
|
|
|
|
if math.Mod(curi, float64(c.Options.TransferPorts+1)) == float64(i) {
|
|
// check to see if this is a chunk that the recipient wants
|
|
usableChunk := true
|
|
c.mutex.Lock()
|
|
if len(c.chunkMap) != 0 {
|
|
if _, ok := c.chunkMap[pos]; !ok {
|
|
usableChunk = false
|
|
} else {
|
|
delete(c.chunkMap, pos)
|
|
}
|
|
}
|
|
c.mutex.Unlock()
|
|
if usableChunk {
|
|
// log.Debugf("sending chunk %d", pos)
|
|
posByte := make([]byte, 8)
|
|
binary.LittleEndian.PutUint64(posByte, pos)
|
|
var err error
|
|
var dataToSend []byte
|
|
if c.Options.NoCompress {
|
|
dataToSend, err = crypt.Encrypt(
|
|
append(posByte, data[:n]...),
|
|
c.Key,
|
|
)
|
|
} else {
|
|
dataToSend, err = crypt.Encrypt(
|
|
compress.Compress(
|
|
append(posByte, data[:n]...),
|
|
),
|
|
c.Key,
|
|
)
|
|
}
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
err = c.conn[i+1].Send(dataToSend)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
c.bar.Add(n)
|
|
c.TotalSent += int64(n)
|
|
// time.Sleep(100 * time.Millisecond)
|
|
}
|
|
}
|
|
|
|
curi++
|
|
pos += uint64(n)
|
|
|
|
if errRead != nil {
|
|
if errRead == io.EOF {
|
|
break
|
|
}
|
|
panic(errRead)
|
|
}
|
|
}
|
|
}
|