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 RelayPorts []string 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 firstPort, _ := strconv.Atoi(c.Options.RelayPorts[0]) openPorts := utils.FindOpenPorts("127.0.0.1", firstPort, len(c.Options.RelayPorts)) if len(openPorts) < len(c.Options.RelayPorts) { panic("not enough open ports to run local relay") } for i, port := range openPorts { c.Options.RelayPorts[i] = fmt.Sprint(port) } for _, port := range c.Options.RelayPorts { go func(portStr string) { debugString := "warn" if c.Options.Debug { debugString = "debug" } err := tcp.Run(debugString, "127.0.0.1", portStr, c.Options.RelayPassword, strings.Join(c.Options.RelayPorts[1:], ",")) 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" + c.Options.RelayPorts[0]), 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:"+c.Options.RelayPorts[0], 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.RelayPorts[0], 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" c.Options.RelayPorts = strings.Split(banner, ",") if c.Options.NoMultiplexing { log.Debug("no multiplexing") c.Options.RelayPorts = []string{c.Options.RelayPorts[0]} } 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{c.Options.RelayPorts[0]}, 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 c.Options.RelayPorts = strings.Split(banner, ",") if c.Options.NoMultiplexing { log.Debug("no multiplexing") c.Options.RelayPorts = []string{c.Options.RelayPorts[0]} } 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) } c.Options.RelayPorts = strings.Split(banner, ",") if c.Options.NoMultiplexing { log.Debug("no multiplexing") c.Options.RelayPorts = []string{c.Options.RelayPorts[0]} } 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(len(c.Options.RelayPorts)) for i := 0; i < len(c.Options.RelayPorts); i++ { log.Debugf("port: [%s]", c.Options.RelayPorts[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, c.Options.RelayPorts[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 < len(c.Options.RelayPorts); 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 == len(c.Options.RelayPorts) { 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(len(c.Options.RelayPorts))) == 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 = append(posByte, data[:n]...) } 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) } } }