From 71a597d67862084f066befd5b9127a807e1a209c Mon Sep 17 00:00:00 2001 From: Arsen Musayelyan Date: Thu, 8 Jul 2021 13:14:48 -0700 Subject: [PATCH] Fix gitignore --- .gitignore | 4 +- cmd/opensend/main.go | 314 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 316 insertions(+), 2 deletions(-) create mode 100644 cmd/opensend/main.go diff --git a/.gitignore b/.gitignore index da3187c..c308f08 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ .idea/ -opensend -opensend-* +/opensend +/opensend-* diff --git a/cmd/opensend/main.go b/cmd/opensend/main.go new file mode 100644 index 0000000..43b57b0 --- /dev/null +++ b/cmd/opensend/main.go @@ -0,0 +1,314 @@ +/* + Copyright © 2021 Arsen Musayelyan + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package main + +import ( + "bufio" + "crypto/rand" + "encoding/hex" + "fmt" + "io" + "os" + "os/signal" + "strconv" + "strings" + "syscall" + "time" + + "github.com/rs/zerolog/log" + flag "github.com/spf13/pflag" + "go.arsenm.dev/opensend/internal/config" + "go.arsenm.dev/opensend/internal/crypto" + "go.arsenm.dev/opensend/internal/logging" + "go.arsenm.dev/opensend/internal/serialization" + "go.arsenm.dev/opensend/internal/transfer" +) + +func init() { + log.Logger = logging.Logger +} + +var workDir *string +var destDir *string + +func main() { + // Use ConsoleWriter logger + + // Create --send-to flag to send to a specific IP + sendTo := flag.String("send-to", "", "Use IP address of receiver instead of mDNS") + // Create --dest-dir flag to save to a specified folder + destDir = flag.String("dest-dir", "", "Destination directory for files or dirs sent over opensend") + // Create --work-dir flag to perform operations in a specified directory + workDir = flag.String("work-dir", "", "Working directory for opensend") + // Create --config to select config file to use + givenCfgPath := flag.String("config", "", "Opensend config to use") + // Create --skip-mdns to skip service registration + skipMdns := flag.Bool("skip-mdns", false, "Skip zeroconf service registration (use if mdns fails)") + // Create -t flag for type + actionType := flag.StringP("type", "t", "", "Type of data being sent") + // Create -d flag for data + actionData := flag.StringP("data", "d", "", "Data to send") + // Create -s flag for sending + sendFlag := flag.BoolP("send", "s", false, "Send data") + // Create -r flag for receiving + recvFlag := flag.BoolP("receive", "r", false, "Receive data") + targetFlag := flag.StringP("target", "T", "", "Target as defined in opensend.toml") + loopFlag := flag.BoolP("loop", "L", false, "Continuously wait for connections and handle them concurrently") + // Parse flags + flag.Parse() + + // Declare config variable + var cfg *config.Config + // If config flag not provided + if *givenCfgPath == "" { + // Get config path + confPath := config.GetConfigPath() + // Read config at path + cfg = config.NewConfig(confPath) + } else { + // Otherwise, read config at provided path + cfg = config.NewConfig(*givenCfgPath) + } + + // If work directory flag not provided + if *workDir == "" { + // If send flag provided + if *sendFlag { + // Set work directory to sender as defined in config + *workDir = config.ExpandPath(cfg.Sender.WorkDir) + } else { + // Otherwise set work directory to receiver as defined in config + *workDir = config.ExpandPath(cfg.Receiver.WorkDir) + } + } + + // If destination directory flag not provided + if *destDir == "" { + // If receiver flag provided + if *recvFlag { + // Set destination directory to receiver as defined in config + *destDir = config.ExpandPath(cfg.Receiver.DestDir) + } + } + + // If target flag provided + if *targetFlag != "" { + // Set IP to target's IP + *sendTo = cfg.Targets[*targetFlag].IP + } + + // Create channel for signals + sig := make(chan os.Signal, 1) + // Send message on channel upon reception of SIGINT or SIGTERM + signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) + // Intercept signal + go func() { + signal := <-sig + // Warn user that a signal has been received and that opensend is shutting down + log.Warn().Str("signal", signal.String()).Msg("Signal received. Shutting down.") + // Remove opensend directory to avoid future conflicts + _ = os.RemoveAll(*workDir) + // Exit with code 0 + os.Exit(0) + }() + + // Create opensend dir ignoring errors + _ = os.Mkdir(*workDir, 0755) + // If -s given + if *sendFlag { + if *actionType == "" || *actionData == "" { + log.Fatal().Msg("Valid action type and data is required to send") + } + // Create 32 byte buffer + sharedKeyBytes := make([]byte, 32) + // Read random bytes into buffer + _, err := io.ReadFull(rand.Reader, sharedKeyBytes) + if err != nil { + log.Fatal().Err(err).Msg("Error generating random bytes") + } + // Encode random bytes to hexadecimal + sharedKey := hex.EncodeToString(sharedKeyBytes) + // Notify user a key has been created + log.Info().Msg("Generated random shared key") + // Create variable to store chosen IP + var choiceIP string + // If IP is provided via --send-to + if *sendTo != "" { + // Notify user that provided IP is being used + log.Info().Msg("IP provided. Skipping discovery.") + // Set chosen IP to provided + choiceIP = *sendTo + // Otherwise + } else { + // Notify user device discovery is beginning + log.Info().Msg("Discovering opensend receivers") + // Discover all _opensend._tcp.local. mDNS services + discoveredReceivers, discoveredIPs := transfer.DiscoverReceivers() + // Create reader for STDIN + reader := bufio.NewReader(os.Stdin) + // Print hostnames of each receiver + for index, receiver := range discoveredReceivers { + // Print hostname and index+1 + fmt.Println("["+strconv.Itoa(index+1)+"]", receiver) + } + // Prompt user for choice + fmt.Print("Choose a receiver: ") + choiceStr, _ := reader.ReadString('\n') + // Convert input to int after trimming spaces + choiceInt, err := strconv.Atoi(strings.TrimSpace(choiceStr)) + if err != nil { + log.Fatal().Err(err).Msg("Error converting choice to int") + } + // Set choiceIndex to choiceInt-1 to allow for indexing + choiceIndex := choiceInt - 1 + // Get IP of chosen receiver + choiceIP = discoveredIPs[choiceIndex] + } + // Instantiate Config object + parameters := serialization.NewParameters(*actionType, *actionData) + // Validate data in config struct + parameters.Validate() + // Collect any files that may be required for transaction into opensend directory + parameters.CollectFiles(*workDir) + // Create config file in opensend directory + parameters.CreateFile(*workDir) + // Notify user of key exchange + log.Info().Msg("Performing key exchange") + // Exchange RSA keys with receiver + rawKey := crypto.SenderKeyExchange(choiceIP) + // Inform user receiver key has been received + log.Info().Msg("Receiver key received") + // Encrypt shared key using RSA public key + key := crypto.EncryptKey(sharedKey, rawKey) + // Save encrypted key in opensend directory as key.aes + transfer.SaveEncryptedKey(key, *workDir+"/key.aes") + // Notify user file encryption is beginning + log.Info().Msg("Encrypting files") + // Encrypt all files in opensend directory using shared key + crypto.EncryptFiles(*workDir, sharedKey) + // Notify user server has started + log.Info().Msg("Server started on port 9898") + // Send all files in opensend directory using an HTTP server on port 9898 + transfer.SendFiles(*workDir) + } else if *recvFlag && *loopFlag { + // Declare zeroconf shutdown variable + var zeroconfShutdown func() + for { + // Create opensend dir ignoring errors + _ = os.Mkdir(*workDir, 0755) + // If --skip-mdns is not given + if !*skipMdns { + // Register {hostname}._opensend._tcp.local. mDNS service and pass shutdown function + zeroconfShutdown = transfer.RegisterService() + } + // Notify user keypair is being generated + log.Info().Msg("Generating RSA keypair") + // Generate keypair + privateKey, publicKey := crypto.GenerateRSAKeypair() + // Notify user opensend is waiting for key exchange + log.Info().Msg("Waiting for sender key exchange") + // Exchange keys with sender + senderIP := crypto.ReceiverKeyExchange(publicKey) + // If --skip-mdns is not given + if !*skipMdns { + // Shutdown zeroconf service as connection will be unavailable during transfer + zeroconfShutdown() + } + // Sleep 300ms to allow sender time to start HTTP server + time.Sleep(300 * time.Millisecond) + // Notify user files are being received + log.Info().Msg("Receiving files from server (This may take a while)") + // Connect to sender's TCP socket + sender := transfer.NewSender(senderIP) + // Get files from sender and place them into the opensend directory + transfer.RecvFiles(sender, *workDir) + // Get encrypted shared key from sender + encryptedKey := crypto.GetKey(sender) + // Send stop signal to sender's HTTP server + transfer.SendSrvStopSignal(sender) + // Decrypt shared key + sharedKey := crypto.DecryptKey(encryptedKey, privateKey) + // Notify user file decryption is beginning + log.Info().Msg("Decrypting files") + // Decrypt all files in opensend directory using shared key + crypto.DecryptFiles(*workDir, sharedKey) + // Instantiate Config + parameters := &serialization.Parameters{} + // Read config file in opensend directory + parameters.ReadFile(*workDir + "/parameters.msgpack") + // Notify user that action is being executed + log.Info().Msg("Executing action") + // Execute MessagePack action using files within opensend directory + parameters.ExecuteAction(*workDir, *destDir) + // Remove opensend directory + err := os.RemoveAll(*workDir) + if err != nil { + log.Fatal().Err(err).Msg("Error removing opensend directory") + } + } + } else if *recvFlag { + // If --skip-mdns is not given + if !*skipMdns { + // Register {hostname}._opensend._tcp.local. mDNS service and pass shutdown function + zeroconfShutdown := transfer.RegisterService() + // Shutdown zeroconf server at the end of main() + defer zeroconfShutdown() + } + // Notify user keypair is being generated + log.Info().Msg("Generating RSA keypair") + // Generate keypair + privateKey, publicKey := crypto.GenerateRSAKeypair() + // Notify user opensend is waiting for key exchange + log.Info().Msg("Waiting for sender key exchange") + // Exchange keys with sender + senderIP := crypto.ReceiverKeyExchange(publicKey) + // Sleep 300ms to allow sender time to start HTTP server + time.Sleep(300 * time.Millisecond) + // Notify user files are being received + log.Info().Msg("Receiving files from server (This may take a while)") + // Connect to sender's TCP socket + sender := transfer.NewSender(senderIP) + // Get files from sender and place them into the opensend directory + transfer.RecvFiles(sender, *workDir) + // Get encrypted shared key from sender + encryptedKey := crypto.GetKey(sender) + // Send stop signal to sender's HTTP server + transfer.SendSrvStopSignal(sender) + // Decrypt shared key + sharedKey := crypto.DecryptKey(encryptedKey, privateKey) + // Notify user file decryption is beginning + log.Info().Msg("Decrypting files") + // Decrypt all files in opensend directory using shared key + crypto.DecryptFiles(*workDir, sharedKey) + // Instantiate Config + parameters := &serialization.Parameters{} + // Read config file in opensend directory + parameters.ReadFile(*workDir + "/parameters.msgpack") + // Notify user that action is being executed + log.Info().Msg("Executing Action") + // Execute MessagePack action using files within opensend directory + parameters.ExecuteAction(*workDir, *destDir) + } else { + flag.Usage() + log.Fatal().Msg("You must choose sender or receiver mode using -s or -r") + } + // Remove opensend directory + err := os.RemoveAll(*workDir) + if err != nil { + log.Fatal().Err(err).Msg("Error removing opensend directory") + } +}