package main import ( "encoding/base64" "encoding/hex" "encoding/json" "fmt" "io" "io/ioutil" "net" "os" "os/signal" "path" "path/filepath" "runtime" "strconv" "strings" "sync" "syscall" "time" "github.com/briandowns/spinner" "github.com/dustin/go-humanize" "github.com/fatih/color" homedir "github.com/mitchellh/go-homedir" "github.com/schollz/croc/keypair" "github.com/schollz/croc/randomstring" "github.com/schollz/peerdiscovery" "github.com/schollz/progressbar" tarinator "github.com/schollz/tarinator-go" log "github.com/cihub/seelog" "github.com/pkg/errors" ) type Connection struct { Server string File FileMetaData NumberOfConnections int Code string HashedCode string Path string IsSender bool AskPath bool NoLocal bool Debug bool DontEncrypt bool Yes bool Local bool UseStdout bool Wait bool bar *progressbar.ProgressBar rate int keypair keypair.KeyPair encryptedPassword string spinner *spinner.Spinner knownKeys map[string]struct{} partnerKey string } type FileMetaData struct { Name string Size int Hash string Path string IsDir bool IsEncrypted bool DeleteAfterSending bool } const ( crocReceiveDir = "croc_received" tmpTarGzFileName = "to_send.tmp.tar.gz" ) func NewConnection(config *AppConfig) (*Connection, error) { defer log.Flush() c := new(Connection) c.Debug = config.Debug c.DontEncrypt = config.DontEncrypt c.Wait = config.Wait c.Server = config.Server c.Code = config.Code c.NumberOfConnections = config.NumberOfConnections c.UseStdout = config.UseStdout c.Yes = config.Yes c.rate = config.Rate c.Local = config.Local c.NoLocal = config.Local c.partnerKey = "" // make or load keypair homedir, err := homedir.Dir() if err != nil { return c, err } pathToCrocConfig := path.Join(homedir, ".config", "croc", "keys") if _, errExists := os.Stat(pathToCrocConfig); os.IsNotExist(errExists) { // path/to/whatever does not exist os.MkdirAll(path.Join(homedir, ".config", "croc"), 0700) keys, _ := keypair.New() err = ioutil.WriteFile(pathToCrocConfig, []byte(keys.String()), 0644) if err != nil { return c, err } } pathToCrocKnownKeys := path.Join(homedir, ".config", "croc", "known") if _, errExists := os.Stat(pathToCrocKnownKeys); os.IsNotExist(errExists) { // path/to/whatever does not exist knownKeys := make(map[string]struct{}) bKnownKeys, err := json.MarshalIndent(knownKeys, "", " ") if err != nil { return c, err } err = ioutil.WriteFile(pathToCrocKnownKeys, bKnownKeys, 0644) if err != nil { return c, err } } keypairBytes, err := ioutil.ReadFile(pathToCrocConfig) if err != nil { return c, err } c.keypair, err = keypair.Load(string(keypairBytes)) if err != nil { return c, err } knownKeyBytes, err := ioutil.ReadFile(pathToCrocKnownKeys) if err != nil { return c, err } err = json.Unmarshal(knownKeyBytes, &c.knownKeys) if err != nil { return c, err } if c.Debug { SetLogLevel("debug") } else { SetLogLevel("error") } if c.Local { c.Yes = true c.DontEncrypt = true } stat, _ := os.Stdin.Stat() if (stat.Mode() & os.ModeCharDevice) == 0 { config.File = "stdin" } if len(config.File) > 0 { config.File = filepath.Clean(config.File) log.Debugf("config.File: %s", config.File) if config.File == "stdin" { c.Yes = true f, err := ioutil.TempFile(".", "croc-stdin-") if err != nil { return c, err } _, err = io.Copy(f, os.Stdin) if err != nil { return c, err } config.File = f.Name() err = f.Close() if err != nil { return c, err } c.File.DeleteAfterSending = true } // check wether the file is a dir info, err := os.Stat(config.File) if err != nil { log.Error(err) return c, err } if info.Mode().IsDir() { // if our file is a dir c.spinner = spinner.New(spinner.CharSets[24], 100*time.Millisecond) // Build our new spinner c.spinner.Suffix = " Compressing folder.." c.spinner.Start() // we "tarify" the file log.Debugf("compressing %s to %s", config.File, path.Base(config.File)+".tar") err = tarinator.Tarinate([]string{config.File}, path.Base(config.File)+".tar") if err != nil { return c, err } // now, we change the target file name to match the new archive created config.File = path.Base(config.File) + ".tar" // we set the value IsDir to true c.File.IsDir = true c.spinner.Stop() } c.File.Path, c.File.Name = path.Split(filepath.ToSlash(config.File)) c.File.Size, _ = FileSize(config.File) c.IsSender = true } else { c.IsSender = false c.AskPath = config.PathSpec c.Path = config.Path } c.File.IsEncrypted = true if c.DontEncrypt { c.File.IsEncrypted = false } log.Debug("made new connection") return c, nil } func (c *Connection) cleanup() { log.Debug("cleaning") if c.Debug { return } for id := 0; id <= 8; id++ { err := os.Remove(path.Join(c.Path, c.File.Name+".enc."+strconv.Itoa(id))) if err == nil { log.Debugf("removed %s", path.Join(c.Path, c.File.Name+".enc."+strconv.Itoa(id))) } } os.Remove(path.Join(c.Path, c.File.Name+".enc")) } func (c *Connection) Run() error { defer log.Flush() // catch the Ctl+C catchCtlC := make(chan os.Signal, 2) signal.Notify(catchCtlC, os.Interrupt, syscall.SIGTERM) go func() { <-catchCtlC c.cleanup() fmt.Println("\nExiting") os.Exit(1) }() defer c.cleanup() // calculate number of threads c.NumberOfConnections = MAX_NUMBER_THREADS if c.IsSender { fsize, err := FileSize(path.Join(c.File.Path, c.File.Name)) if err != nil { log.Error(err) return err } if fsize < MAX_NUMBER_THREADS*BUFFERSIZE { c.NumberOfConnections = 1 log.Debug("forcing single thread") } } runClientError := make(chan error) if c.IsSender { if c.Code == "" { c.Code = GetRandomName() } if c.File.IsDir { fmt.Fprintf(os.Stderr, "Sending %s folder named '%s'\n", humanize.Bytes(uint64(c.File.Size)), c.File.Name[:len(c.File.Name)-4]) } else { fmt.Fprintf(os.Stderr, "Sending %s file named '%s'\n", humanize.Bytes(uint64(c.File.Size)), c.File.Name) } fmt.Fprintf(os.Stderr, "Code is: ") if runtime.GOOS == "windows" { color.New(color.FgHiGreen, color.Bold).Fprint(color.Output, c.Code) } else { color.New(color.FgHiGreen, color.Bold).Fprint(os.Stderr, c.Code) } fmt.Fprintf(os.Stderr, "\n\n") c.spinner = spinner.New(spinner.CharSets[24], 100*time.Millisecond) // Build our new spinner c.spinner.Suffix = " Waiting for recipient.." c.spinner.Start() log.Debug("starting relay in case local connections") relay := NewRelay(&AppConfig{ Debug: c.Debug, }) go relay.Run() time.Sleep(200 * time.Millisecond) // get file hash var err error c.File.Hash, err = HashFile(path.Join(c.File.Path, c.File.Name)) if err != nil { log.Error(err) return err } // get file size c.File.Size, err = FileSize(path.Join(c.File.Path, c.File.Name)) if err != nil { log.Error(err) return err } if c.Server != "localhost" && !c.NoLocal { // broadcast local connection from sender log.Debug("settings payload to ", c.Code) go func() { log.Debug("listening for local croc relay...") go peerdiscovery.Discover(peerdiscovery.Settings{ Limit: 1, TimeLimit: 600 * time.Second, Delay: 50 * time.Millisecond, Payload: []byte(c.Code), }) runClientError <- c.runClient("localhost") }() } } log.Debug("checking code validity") if len(c.Code) == 0 && !c.IsSender { log.Debug("Finding local croc relay...") discovered, errDiscover := peerdiscovery.Discover(peerdiscovery.Settings{ Limit: 1, TimeLimit: 1 * time.Second, Delay: 50 * time.Millisecond, Payload: []byte(c.Code), }) if errDiscover != nil { log.Debug(errDiscover) } if len(discovered) > 0 { log.Debugf("discovered %s on %s", discovered[0].Payload, discovered[0].Address) _, connectTimeout := net.DialTimeout("tcp", discovered[0].Address+":27001", 1*time.Second) if connectTimeout == nil { log.Debug("connected") c.Server = discovered[0].Address log.Debug(discovered[0].Address) c.Code = string(discovered[0].Payload) time.Sleep(200 * time.Millisecond) } else { log.Debug("could not connect") c.Code = getInput("Enter receive code: ") } } else { c.Code = getInput("Enter receive code: ") log.Debug("changed code to ", c.Code) } } if !c.Local { go func() { runClientError <- c.runClient(c.Server) }() } return <-runClientError } // runClient spawns threads for parallel uplink/downlink via TCP func (c *Connection) runClient(serverName string) error { c.HashedCode = Hash(c.Code) c.NumberOfConnections = MAX_NUMBER_THREADS var wg sync.WaitGroup wg.Add(c.NumberOfConnections) if !c.Debug { c.bar = progressbar.NewOptions(c.File.Size, progressbar.OptionSetWriter(os.Stderr)) } type responsesStruct struct { gotTimeout bool gotOK bool gotResponse bool gotConnectionInUse bool notPresent bool startTime time.Time sync.RWMutex } responses := new(responsesStruct) responses.Lock() responses.startTime = time.Now() responses.Unlock() var okToContinue bool fileTransfered := false for id := 0; id < c.NumberOfConnections; id++ { go func(id int) { defer wg.Done() port := strconv.Itoa(27001 + id) log.Debugf("connecting to %s", serverName+":"+port) connection, err := net.Dial("tcp", serverName+":"+port) if err != nil { if serverName == "cowyo.com" { fmt.Fprintf(os.Stderr, "\nCheck http://bit.ly/croc-relay to see if the public server is down or contact the webmaster: @yakczar") } else { fmt.Fprintf(os.Stderr, "\nCould not connect to relay %s\n", serverName) } fmt.Fprintf(os.Stderr, "Use --local to run locally") os.Exit(1) } defer connection.Close() err = connection.SetReadDeadline(time.Now().Add(1 * time.Hour)) if err != nil { log.Warn(err) } err = connection.SetDeadline(time.Now().Add(1 * time.Hour)) if err != nil { log.Warn(err) } err = connection.SetWriteDeadline(time.Now().Add(1 * time.Hour)) if err != nil { log.Warn(err) } message := receiveMessage(connection) log.Debugf("relay says: %s", message) if c.IsSender { log.Debugf("telling relay (%s): %s", c.Server, "s."+c.Code) metaData, err := json.Marshal(c.File) if err != nil { log.Error(err) } encryptedMetaData, salt, iv := Encrypt(metaData, c.Code) sendMessage("s."+c.keypair.Public+"."+c.HashedCode+"."+hex.EncodeToString(encryptedMetaData)+"-"+salt+"-"+iv, connection) } else { log.Debugf("telling relay (%s): %s", c.Server, "r."+c.Code) if c.Wait { // tell server to wait for sender sendMessage("r."+c.keypair.Public+"."+c.HashedCode+".0.0.0", connection) } else { // tell server to cancel if sender doesn't exist sendMessage("c."+c.keypair.Public+"."+c.HashedCode+".0.0.0", connection) } } if c.IsSender { // this is a sender log.Debugf("[%d] waiting for ok from relay", id) message = receiveMessage(connection) if message == "timeout" { responses.Lock() responses.gotTimeout = true responses.Unlock() fmt.Println("You've just exceeded limit waiting time.") return } if message == "no" { if id == 0 { fmt.Println("The specifed code is already in use by a sender.") } responses.Lock() responses.gotConnectionInUse = true responses.Unlock() } else { // message is IP address, lets check next message log.Debugf("[%d] got ok from relay: %s", id, message) publicKeyRecipient := receiveMessage(connection) c.partnerKey = publicKeyRecipient // check if okay again if id == 0 { c.spinner.Stop() getOK := "y" if _, knownKey := c.knownKeys[publicKeyRecipient]; !knownKey { fmt.Fprintf(os.Stderr, "\nYour public key: ") if runtime.GOOS == "windows" { color.New(color.FgHiRed).Fprintln(color.Output, HashWords(c.keypair.Public)) } else { color.New(color.FgHiRed).Fprintln(os.Stderr, HashWords(c.keypair.Public)) } fmt.Fprintf(os.Stderr, "Recipient public key: ") if runtime.GOOS == "windows" { color.New(color.FgHiYellow).Fprintln(color.Output, HashWords(publicKeyRecipient)) } else { color.New(color.FgHiYellow).Fprintln(os.Stderr, HashWords(publicKeyRecipient)) } if !c.Yes { getOK = getInput("ok? (y/n): ") } } responses.Lock() responses.gotOK = true responses.Unlock() if getOK == "y" { okToContinue = true } else { okToContinue = false } } for { responses.RLock() ok := responses.gotOK responses.RUnlock() if ok { break } time.Sleep(10 * time.Millisecond) } if okToContinue { sendMessage("ok", connection) } else { sendMessage("no", connection) return } if id == 0 { passphraseString, err := randomstring.GenerateRandomString(30) if err != nil { panic(err) } log.Debugf("passphrase: [%s]", passphraseString) encryptedPassword, err := c.keypair.Encrypt([]byte(passphraseString), publicKeyRecipient) if err != nil { panic(err) } // encrypt files if c.DontEncrypt { // don't encrypt CopyFile(path.Join(c.File.Path, c.File.Name), c.File.Name+".enc") c.File.IsEncrypted = false } else { // encrypt c.spinner = spinner.New(spinner.CharSets[24], 100*time.Millisecond) // Build our new spinner c.spinner.Suffix = " Encrypting..." c.spinner.Start() log.Debugf("encrypting %s with passphrase [%s]", path.Join(c.File.Path, c.File.Name), passphraseString) if err := EncryptFile(path.Join(c.File.Path, c.File.Name), c.File.Name+".enc", passphraseString); err != nil { panic(err) } c.File.IsEncrypted = true c.spinner.Stop() } // split file into pieces to send if err := SplitFile(c.File.Name+".enc", c.NumberOfConnections); err != nil { panic(err) } // remove the file now since we still have pieces if err := os.Remove(c.File.Name + ".enc"); err != nil { panic(err) } // remove compressed archive if c.File.IsDir { log.Debug("removing archive: " + c.File.Name) if err := os.Remove(c.File.Name); err != nil { log.Error(err) } } c.encryptedPassword = base64.StdEncoding.EncodeToString(encryptedPassword) } log.Debugf("[%d] waiting for 0 thread to encrypt", id) for { if c.encryptedPassword != "" { break } time.Sleep(10 * time.Millisecond) } log.Debugf("sending encrypted passphrase: %s", c.encryptedPassword) sendMessage(c.encryptedPassword, connection) // wait for relay go if id == 0 { c.spinner = spinner.New(spinner.CharSets[24], 100*time.Millisecond) // Build our new spinner c.spinner.Suffix = " Waiting on recipient..." c.spinner.Start() } receiveMessage(connection) if id == 0 { c.spinner.Stop() fmt.Fprintf(os.Stderr, "\nSending (->@%s)..\n", message) } // wait for pipe to be made time.Sleep(100 * time.Millisecond) // Write data from file log.Debug("send file") responses.Lock() responses.startTime = time.Now() responses.Unlock() if !c.Debug { c.bar.Reset() } if err := c.sendFile(id, connection); err != nil { log.Warn(err) } else { fileTransfered = true } } } else { // this is a receiver log.Debug("waiting for meta data from sender") message = receiveMessage(connection) log.Debugf("message from server: %s", message) if message == "no" { if id == 0 { fmt.Println("The specifed code is already in use by a sender.") } responses.Lock() responses.gotConnectionInUse = true responses.Unlock() } else { m := strings.Split(message, "-") encryptedData, salt, iv, sendersAddress := m[0], m[1], m[2], m[3] if sendersAddress == "0.0.0.0" { responses.Lock() responses.notPresent = true responses.Unlock() time.Sleep(1 * time.Second) return } else if strings.Split(sendersAddress, ":")[0] == "127.0.0.1" { sendersAddress = strings.Replace(sendersAddress, "127.0.0.1", c.Server, 1) } // now get public key publicKeySender := receiveMessage(connection) c.partnerKey = publicKeySender // have the main thread ask for the okay if id == 0 { encryptedBytes, err := hex.DecodeString(encryptedData) if err != nil { log.Error(err) return } decryptedBytes, _ := Decrypt(encryptedBytes, c.Code, salt, iv, c.DontEncrypt) err = json.Unmarshal(decryptedBytes, &c.File) if err != nil { log.Error(err) return } log.Debugf("meta data received: %v", c.File) fType := "file" c.Path = "." fName := path.Join(c.Path, c.File.Name) if c.File.IsDir { fType = "folder" fName = fName[:len(fName)-4] } if _, err := os.Stat(path.Join(c.Path, c.File.Name)); os.IsNotExist(err) { fmt.Fprintf(os.Stderr, "Receiving %s (%s) into: '%s'\n", fType, humanize.Bytes(uint64(c.File.Size)), fName) } else { fmt.Fprintf(os.Stderr, "Overwriting %s '%s' (%s)\n", fType, fName, humanize.Bytes(uint64(c.File.Size))) } var sentFileNames []string if c.AskPath { getPath := getInput("path: ") if len(getPath) > 0 { c.Path = path.Clean(getPath) } } if fileAlreadyExists(sentFileNames, c.File.Name) { fmt.Fprintf(os.Stderr, "Will not overwrite file!") os.Exit(1) } getOK := "y" if _, knownKey := c.knownKeys[publicKeySender]; !knownKey { fmt.Fprintf(os.Stderr, "\nYour public key: ") if runtime.GOOS == "windows" { color.New(color.FgHiRed).Fprintln(color.Output, HashWords(c.keypair.Public)) } else { color.New(color.FgHiRed).Fprintln(os.Stderr, HashWords(c.keypair.Public)) } fmt.Fprintf(os.Stderr, "Sender public key: ") if runtime.GOOS == "windows" { color.New(color.FgHiYellow).Fprintln(color.Output, HashWords(publicKeySender)) } else { color.New(color.FgHiYellow).Fprintln(os.Stderr, HashWords(publicKeySender)) } if !c.Yes { getOK = getInput("ok? (y/n): ") } } if getOK == "y" { responses.Lock() responses.gotOK = true responses.Unlock() sentFileNames = append(sentFileNames, c.File.Name) } responses.Lock() responses.gotResponse = true responses.Unlock() } // wait for the main thread to get the okay for limit := 0; limit < 1000; limit++ { responses.Lock() gotResponse := responses.gotResponse responses.Unlock() if gotResponse { break } time.Sleep(10 * time.Millisecond) } responses.RLock() gotOK := responses.gotOK responses.RUnlock() if !gotOK { sendMessage("not ok", connection) } else { sendMessage("ok", connection) encryptedPassword := receiveMessage(connection) log.Debugf("[%d] got encrypted passphrase: %s", id, encryptedPassword) if encryptedPassword == "" { return } encryptedPasswordBytes, err := base64.StdEncoding.DecodeString(encryptedPassword) if err != nil { panic(err) } if publicKeySender == "" { return } decryptedPassphrase, err := c.keypair.Decrypt(encryptedPasswordBytes, publicKeySender) if err != nil { log.Warn(err) return } c.encryptedPassword = string(decryptedPassphrase) log.Debugf("decrypted password to: %s", c.encryptedPassword) if err != nil { panic(err) } sendMessage("ok", connection) log.Debug("receive file") if id == 0 { fmt.Fprintf(os.Stderr, "\nReceiving (<-@%s)..\n", sendersAddress) } responses.Lock() responses.startTime = time.Now() responses.Unlock() if !c.Debug && id == 0 { c.bar = progressbar.NewOptions(c.File.Size, progressbar.OptionSetWriter(os.Stderr)) } else { // try to let the first thread start first time.Sleep(10 * time.Millisecond) } if err := c.receiveFile(id, connection); err != nil { log.Debug(errors.Wrap(err, "no file to recieve")) } else { fileTransfered = true } } } } }(id) } wg.Wait() log.Debugf("moving on") responses.Lock() defer responses.Unlock() if responses.gotConnectionInUse { return nil // connection was in use, just quit cleanly } timeSinceStart := time.Since(responses.startTime).Nanoseconds() if c.IsSender { if responses.gotTimeout { fmt.Println("Timeout waiting for receiver") return nil } else if !fileTransfered { fmt.Fprintf(os.Stderr, "\nNo mutual consent") return nil } else { c.bar.Finish() } fileOrFolder := "File" if c.File.IsDir { fileOrFolder = "Folder" } fmt.Fprintf(os.Stderr, "\n%s sent", fileOrFolder) } else { // Is a Receiver if responses.notPresent { fmt.Println("Either code is incorrect or sender is not ready. Use -wait to wait until sender connects.") return nil } else if !fileTransfered { fmt.Fprintf(os.Stderr, "\nNo mutual consent") return nil } else { c.bar.Finish() } if !responses.gotOK { return errors.New("Transfer interrupted") } if err := c.catFile(); err != nil { return err } log.Debugf("Code: [%s]", c.Code) if c.DontEncrypt { if err := CopyFile(path.Join(c.Path, c.File.Name+".enc"), path.Join(c.Path, c.File.Name)); err != nil { log.Error(err) return err } } else { log.Debugf("is encrypted: %+v", c.File.IsEncrypted) if c.File.IsEncrypted { fmt.Fprintf(os.Stderr, "\n") c.spinner = spinner.New(spinner.CharSets[24], 100*time.Millisecond) // Build our new spinner c.spinner.Suffix = " Decrypting file.." c.spinner.Start() log.Debugf("decrypting file with [%s]", c.encryptedPassword) err := DecryptFile(path.Join(c.Path, c.File.Name+".enc"), path.Join(c.Path, c.File.Name), c.encryptedPassword) c.spinner.Stop() if err != nil { log.Error(err) return errors.Wrap(err, "Problem decrypting file") } } else { if err := CopyFile(path.Join(c.Path, c.File.Name+".enc"), path.Join(c.Path, c.File.Name)); err != nil { log.Error(err) return errors.Wrap(err, "Problem copying file") } } } if !c.Debug { os.Remove(path.Join(c.Path, c.File.Name+".enc")) } fileHash, err := HashFile(path.Join(c.Path, c.File.Name)) if err != nil { log.Error(err) } log.Debugf("\n\n\ndownloaded hash: [%s]", fileHash) log.Debugf("\n\n\nrelayed hash: [%s]", c.File.Hash) if c.File.Hash != fileHash { log.Flush() return fmt.Errorf("\nUh oh! %s is corrupted! Sorry, try again.\n", c.File.Name) } if c.File.IsDir { // if the file was originally a dir fmt.Print("\nDecompressing folder...") log.Debug("untarring " + c.File.Name) err := tarinator.UnTarinate(c.Path, path.Join(c.Path, c.File.Name)) if err != nil { log.Debug("problem untarring: " + err.Error()) return err } // we remove the old tar.gz filels log.Debug("removing old tar file: " + c.File.Name) err = os.Remove(path.Join(c.Path, c.File.Name)) if err != nil { return err } fmt.Fprintf(os.Stderr, "Received folder written to '%s'", path.Join(c.Path, c.File.Name[:len(c.File.Name)-4])) } else { outputStream := path.Join(c.Path, c.File.Name) if c.UseStdout { outputStream = "stdout" } fmt.Fprintf(os.Stderr, "\nReceived file written to '%s'", outputStream) if c.UseStdout { defer os.Remove(path.Join(c.Path, c.File.Name)) b, _ := ioutil.ReadFile(path.Join(c.Path, c.File.Name)) fmt.Printf("%s", b) } } } if c.partnerKey != "" { // save the partner for next time homedir, _ := homedir.Dir() pathToCrocKnownKeys := path.Join(homedir, ".config", "croc", "known") _, haveKey := c.knownKeys[c.partnerKey] if !haveKey { c.knownKeys[c.partnerKey] = struct{}{} bKnownKeys, err := json.MarshalIndent(c.knownKeys, "", " ") if err != nil { return err } err = ioutil.WriteFile(pathToCrocKnownKeys, bKnownKeys, 0644) if err != nil { return err } } } fmt.Fprintf(os.Stderr, " (%s/s)\n", humanize.Bytes(uint64(float64(1000000000)*float64(c.File.Size)/float64(timeSinceStart)))) return nil } func fileAlreadyExists(s []string, f string) bool { for _, a := range s { if a == f { return true } } return false } func (c *Connection) catFile() error { // cat the file files := make([]string, c.NumberOfConnections) i := 0 for id := 0; id < len(files); id++ { files[i] = path.Join(c.Path, c.File.Name+".enc."+strconv.Itoa(id)) if _, err := os.Stat(files[id]); os.IsNotExist(err) { break } log.Debug(files[i]) i++ } files = files[:i] log.Debug(files) toRemove := !c.Debug return CatFiles(files, path.Join(c.Path, c.File.Name+".enc"), toRemove) } func (c *Connection) receiveFile(id int, connection net.Conn) error { log.Debug("waiting for chunk size from sender") fileSizeBuffer := make([]byte, 10) connection.Read(fileSizeBuffer) fileDataString := strings.Trim(string(fileSizeBuffer), ":") fileSizeInt, _ := strconv.Atoi(fileDataString) chunkSize := int64(fileSizeInt) log.Debugf("chunk size: %d", chunkSize) if chunkSize == 0 { log.Debug(fileSizeBuffer) return errors.New("chunk size is empty!") } os.Remove(path.Join(c.Path, c.File.Name+".enc."+strconv.Itoa(id))) log.Debug("Making " + c.File.Name + ".enc." + strconv.Itoa(id)) newFile, err := os.Create(path.Join(c.Path, c.File.Name+".enc."+strconv.Itoa(id))) if err != nil { panic(err) } defer newFile.Close() log.Debug(id, "waiting for file") var receivedBytes int64 receivedFirstBytes := false for { if (chunkSize - receivedBytes) < BUFFERSIZE { log.Debugf("%d at the end: %d < %d", id, (chunkSize - receivedBytes), BUFFERSIZE) io.CopyN(newFile, connection, (chunkSize - receivedBytes)) // Empty the remaining bytes that we don't need from the network buffer if (receivedBytes+BUFFERSIZE)-chunkSize < BUFFERSIZE { log.Debug(id, "empty remaining bytes from network buffer") connection.Read(make([]byte, (receivedBytes+BUFFERSIZE)-chunkSize)) } if !c.Debug { c.bar.Add(int((chunkSize - receivedBytes))) } break } written, _ := io.CopyN(newFile, connection, BUFFERSIZE) receivedBytes += written if !receivedFirstBytes { receivedFirstBytes = true log.Debug(id, "Receieved first bytes!") } if !c.Debug { c.bar.Add(int(written)) } } log.Debug(id, "received file") return nil } func (c *Connection) sendFile(id int, connection net.Conn) error { defer connection.Close() // open encrypted file chunk, if it exists log.Debug("opening encrypted file chunk: " + c.File.Name + ".enc." + strconv.Itoa(id)) file, err := os.Open(c.File.Name + ".enc." + strconv.Itoa(id)) if err != nil { log.Debug(err) return nil } defer file.Close() // determine and send the file size to client fi, err := file.Stat() if err != nil { return err } log.Debugf("sending chunk size: %d", fi.Size()) _, err = connection.Write([]byte(fillString(strconv.FormatInt(int64(fi.Size()), 10), 10))) if err != nil { return errors.Wrap(err, "Problem sending chunk data: ") } // rate limit the bandwidth log.Debug("determining rate limiting") bufferSizeInKilobytes := BUFFERSIZE / 1024 rate := float64(c.rate) / float64(c.NumberOfConnections*bufferSizeInKilobytes) throttle := time.NewTicker(time.Second / time.Duration(rate)) log.Debugf("rate: %+v", rate) defer throttle.Stop() // send the file sendBuffer := make([]byte, BUFFERSIZE) totalBytesSent := 0 for range throttle.C { _, err := file.Read(sendBuffer) written, errWrite := connection.Write(sendBuffer) totalBytesSent += written if !c.Debug { c.bar.Add(int(written)) } if errWrite != nil { return errWrite } if err == io.EOF { //End of file reached, break out of for loop log.Debug("EOF") break } } log.Debug("file is sent") log.Debug("removing piece") if !c.Debug { file.Close() err = os.Remove(c.File.Name + ".enc." + strconv.Itoa(id)) } if err != nil && c.File.DeleteAfterSending { err = os.Remove(path.Join(c.File.Path, c.File.Name)) } // wait until client breaks connection for range throttle.C { _, errWrite := connection.Write([]byte(".")) if errWrite != nil { break } } return err }