Use raw TCP instead of HTTP to transfer files

This commit is contained in:
Elara 2020-12-07 17:15:38 -08:00
parent 8331e6b543
commit 1f1bb67431
5 changed files with 243 additions and 146 deletions

View File

@ -8,6 +8,7 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"io" "io"
"io/ioutil" "io/ioutil"
"net/url"
"os" "os"
"path/filepath" "path/filepath"
"strconv" "strconv"
@ -25,6 +26,24 @@ func NewConfig(actionType string, actionData string) *Config {
return &Config{ActionType: actionType, ActionData: actionData} return &Config{ActionType: actionType, ActionData: actionData}
} }
func (config *Config) Validate() {
// Parse URL in config
urlParser, err := url.Parse(config.ActionData)
// If there was an error parsing
if err != nil {
// Alert user of invalid url
log.Fatal().Err(err).Msg("Invalid URL")
// If scheme is not detected
} else if urlParser.Scheme == "" {
// Alert user of invalid scheme
log.Fatal().Msg("Invalid URL scheme")
// If host is not detected
} else if urlParser.Host == "" {
// Alert user of invalid host
log.Fatal().Msg("Invalid URL host")
}
}
// Create config file // Create config file
func (config *Config) CreateFile(dir string) { func (config *Config) CreateFile(dir string) {
// Use ConsoleWriter logger // Use ConsoleWriter logger
@ -137,8 +156,23 @@ func (config *Config) ExecuteAction(srcDir string, destDir string) {
if err != nil { log.Fatal().Err(err).Msg("Error copying data to file") } if err != nil { log.Fatal().Err(err).Msg("Error copying data to file") }
// If action is url // If action is url
} else if config.ActionType == "url" { } else if config.ActionType == "url" {
// Parse received URL
urlParser, err := url.Parse(config.ActionData)
// If there was an error parsing
if err != nil {
// Alert user of invalid url
log.Fatal().Err(err).Msg("Invalid URL")
// If scheme is not detected
} else if urlParser.Scheme == "" {
// Alert user of invalid scheme
log.Fatal().Msg("Invalid URL scheme")
// If host is not detected
} else if urlParser.Host == "" {
// Alert user of invalid host
log.Fatal().Msg("Invalid URL host")
}
// Attempt to open URL in browser // Attempt to open URL in browser
err := browser.OpenURL(config.ActionData) err = browser.OpenURL(config.ActionData)
if err != nil { log.Fatal().Err(err).Msg("Error opening browser") } if err != nil { log.Fatal().Err(err).Msg("Error opening browser") }
// If action is dir // If action is dir
} else if config.ActionType == "dir" { } else if config.ActionType == "dir" {

273
files.go
View File

@ -1,19 +1,20 @@
package main package main
import ( import (
"context" "bufio"
"bytes"
"encoding/hex"
"errors"
"fmt" "fmt"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"io" "io"
"io/ioutil" "io/ioutil"
"net" "net"
"net/http"
"os" "os"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"time"
) )
// Save encrypted key to file // Save encrypted key to file
@ -34,147 +35,179 @@ func SaveEncryptedKey(encryptedKey []byte, filePath string) {
// Create HTTP server to transmit files // Create HTTP server to transmit files
func SendFiles(dir string) { func SendFiles(dir string) {
// Use ConsoleWriter logger // Use ConsoleWriter logger with normal FatalHook
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Hook(FatalHook{}) log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Hook(FatalHook{})
// Instantiate http.Server struct // Create TCP listener on port 9898
srv := &http.Server{}
// Listen on all ipv4 addresses on port 9898
listener, err := net.Listen("tcp", ":9898") listener, err := net.Listen("tcp", ":9898")
if err != nil { log.Fatal().Err(err).Msg("Error starting listener") } if err != nil { log.Fatal().Err(err).Msg("Error starting listener") }
// Accept connection on listener
// If client connects to /:filePath connection, err := listener.Accept()
http.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) { if err != nil { log.Fatal().Err(err).Msg("Error accepting connection") }
// Set file to first path components of URL, excluding first / // Close connection at the end of this function
file := req.URL.Path[1:] defer connection.Close()
// Read file at specified location // Create for loop to listen for messages on connection
fileData, err := ioutil.ReadFile(dir + "/" + file) connectionLoop: for {
// If there was an error reading // Use ConsoleWriter logger with TCPFatalHook
if err != nil { log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Hook(TCPFatalHook{conn: connection})
// Warn user of error // Attempt to read new message on connection
log.Warn().Err(err).Msg("Error reading file") data, err := bufio.NewReader(connection).ReadString('\n')
// Otherwise // If no message detected, try again
} else { if err != nil && err.Error() == "EOF" { continue }
// Inform user client has requested a file // If non-EOF error, fatally log
log.Info().Str("file", file).Msg("GET File") if err != nil { log.Fatal().Err(err).Msg("Error reading data") }
} // Process received data
// Write file to ResponseWriter processedData := strings.Split(strings.TrimSpace(data), ";")
_, err = fmt.Fprint(res, string(fileData)) // If processedData is empty, alert the user of invalid data
if err != nil { log.Fatal().Err(err).Msg("Error writing response") } if len(processedData) < 1 { log.Fatal().Str("data", data).Msg("Received data invalid") }
}) switch processedData[0] {
case "key":
// If client connects to /index // Inform user client has requested key
http.HandleFunc("/index", func(res http.ResponseWriter, req *http.Request) { log.Info().Msg("Key requested")
// Inform user a client has requested the file index // Read saved key
log.Info().Msg("GET Index") key, err := ioutil.ReadFile(dir + "/key.aes")
// Get directory listing if err != nil { log.Fatal().Err(err).Msg("Error reading key") }
dirListing, err := ioutil.ReadDir(dir) // Write saved key to ResponseWriter
if err != nil { log.Fatal().Err(err).Msg("Error reading directory") } _, err = fmt.Fprintln(connection, "OK;" + hex.EncodeToString(key) + ";")
// Create new slice to house filenames for index if err != nil { log.Fatal().Err(err).Msg("Error writing response") }
var indexSlice []string case "index":
// For each file in listing // Inform user a client has requested the file index
for _, file := range dirListing { log.Info().Msg("Index requested")
// If the file is not the key // Get directory listing
if !strings.Contains(file.Name(), "key.aes") { dirListing, err := ioutil.ReadDir(dir)
// Append the file path to indexSlice if err != nil { log.Fatal().Err(err).Msg("Error reading directory") }
indexSlice = append(indexSlice, file.Name()) // Create new slice to house filenames for index
var indexSlice []string
// For each file in listing
for _, file := range dirListing {
// If the file is not the key
if !strings.Contains(file.Name(), "key.aes") {
// Append the file path to indexSlice
indexSlice = append(indexSlice, file.Name())
}
} }
// Join index slice into string
indexStr := strings.Join(indexSlice, "|")
// Write index to ResponseWriter
_, err = fmt.Fprintln(connection, "OK;" + indexStr + ";")
if err != nil { log.Fatal().Err(err).Msg("Error writing response") }
case "file":
// If processedData only has one entry
if len(processedData) == 1 {
// Warn user of unexpected end of line
log.Warn().Err(errors.New("unexpected eol")).Msg("Invalid file request")
// Send error to connection
_, _ = fmt.Fprintln(connection, "ERR;")
// Break out of switch
break
}
// Set file to first path components of URL, excluding first /
file := processedData[1]
// Read file at specified location
fileData, err := ioutil.ReadFile(dir + "/" + file)
// If there was an error reading
if err != nil {
// Warn user of error
log.Warn().Err(err).Msg("Error reading file")
// Otherwise
} else {
// Inform user client has requested a file
log.Info().Str("file", file).Msg("File requested")
}
// Write file as hex to connection
_, err = fmt.Fprintln(connection, "OK;" + hex.EncodeToString(fileData) + ";")
if err != nil { log.Fatal().Err(err).Msg("Error writing response") }
case "stop":
// Alert user that stop signal has been received
log.Info().Msg("Received stop signal")
// Print ok message to connection
_, _ = fmt.Fprintln(connection, "OK;")
// Break out of connectionLoop
break connectionLoop
} }
// Join index slice into string }
indexStr := strings.Join(indexSlice, ";") }
// Write index to ResponseWriter
_, err = fmt.Fprint(res, indexStr)
if err != nil { log.Fatal().Err(err).Msg("Error writing response") }
})
// If client connects to /key func ConnectToSender(senderAddr string) net.Conn {
http.HandleFunc("/key", func(res http.ResponseWriter, req *http.Request) { // Get server address by getting the IP without the port, and appending :9898
// Inform user a client has requested the key serverAddr := strings.Split(senderAddr, ":")[0] + ":9898"
log.Info().Msg("GET Key") // Create error variable
// Read saved key var err error
key, err := ioutil.ReadFile(dir + "/key.aes") // Create connection variable
if err != nil { log.Fatal().Err(err).Msg("Error reading key") } var connection net.Conn
// Write saved key to ResponseWriter // Until break
_, err = fmt.Fprint(res, string(key)) for {
if err != nil { log.Fatal().Err(err).Msg("Error writing response") } // Try connecting to sender
}) connection, err = net.Dial("tcp", serverAddr)
// If connection refused
// If client connects to /stop if err != nil && strings.Contains(err.Error(), "connection refused") {
http.HandleFunc("/stop", func(res http.ResponseWriter, req *http.Request) { // Continue loop (retry)
// Inform user a client has requested server shutdown continue
log.Info().Msg("GET Stop") // If error other than connection refused
log.Info().Msg("Stop signal received") } else if err != nil {
// Shutdown server and send to empty context // Fatally log
err := srv.Shutdown(context.Background()) log.Fatal().Err(err).Msg("Error connecting to sender")
if err != nil { log.Fatal().Err(err).Msg("Error stopping server") } // If no error
}) } else {
// Break out of loop
// Start HTTP Server break
_ = srv.Serve(listener) }
}
// Returned created connection
return connection
} }
// Get files from sender // Get files from sender
func RecvFiles(senderAddr string) { func RecvFiles(connection net.Conn) {
// Use ConsoleWriter logger // Use ConsoleWriter logger
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Hook(FatalHook{}) log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Hook(FatalHook{})
// Get server address by getting the IP without the port, prepending http:// and appending :9898 // Request index from sender
serverAddr := "http://" + strings.Split(senderAddr, ":")[0] + ":9898" _, err := fmt.Fprintln(connection, "index;")
var response *http.Response if err != nil { log.Fatal().Err(err).Msg("Error sending index request") }
// GET /index on sender's HTTP server // Read received message
response, err := http.Get(serverAddr + "/index") message, err := bufio.NewReader(connection).ReadString('\n')
// If error occurred, retry every 500ms if err != nil { log.Fatal().Err(err).Msg("Error getting index") }
if err != nil { // Process received message
// Set index failed to true procMessage := strings.Split(strings.TrimSpace(message), ";")
indexGetFailed := true // If non-ok code returned, fatally log
for indexGetFailed { if procMessage[0] != "OK" { log.Fatal().Err(err).Msg("Sender reported error") }
// GET /index on sender's HTTP server // Get index from message
response, err = http.Get(serverAddr + "/index") index := strings.Split(strings.TrimSpace(procMessage[1]), "|")
// If no error, set index failed to false
if err == nil { indexGetFailed = false }
// Wait 500ms
time.Sleep(500*time.Millisecond)
}
}
// Close response body at the end of this function
defer response.Body.Close()
// Create index slice for storage of file index
var index []string
// If server responded with 200 OK
if response.StatusCode == http.StatusOK {
// Read response body
body, err := ioutil.ReadAll(response.Body)
if err != nil { log.Fatal().Err(err).Msg("Error reading HTTP response") }
// Get string from body
bodyStr := string(body)
// Split string to form index
index = strings.Split(bodyStr, ";")
}
// For each file in the index
for _, file := range index { for _, file := range index {
// GET current file in index // Get current file in index
response, err := http.Get(serverAddr + "/" + filepath.Base(file)) _, err = fmt.Fprintln(connection, "file;" + file + ";")
if err != nil { log.Fatal().Err(err).Msg("Error sending file request") }
// Read received message
message, err := bufio.NewReader(connection).ReadString('\n')
if err != nil { log.Fatal().Err(err).Msg("Error getting file") } if err != nil { log.Fatal().Err(err).Msg("Error getting file") }
// If server responded with 200 OK // Process received message
if response.StatusCode == http.StatusOK { procMessage := strings.Split(message, ";")
// If non-ok code returned
if procMessage[0] != "OK" {
// fatally log
log.Fatal().Err(err).Msg("Sender reported error")
// Otherwise
} else {
// Create new file at index filepath // Create new file at index filepath
newFile, err := os.Create(opensendDir + "/" + file) newFile, err := os.Create(opensendDir + "/" + file)
if err != nil { log.Fatal().Err(err).Msg("Error creating file") } if err != nil { log.Fatal().Err(err).Msg("Error creating file") }
// Decode file data from hex string
fileData, err := hex.DecodeString(strings.TrimSpace(procMessage[1]))
if err != nil { log.Fatal().Err(err).Msg("Error decoding hex") }
// Copy response body to new file // Copy response body to new file
bytesWritten, err := io.Copy(newFile, response.Body) bytesWritten, err := io.Copy(newFile, bytes.NewBuffer(fileData))
if err != nil { log.Fatal().Err(err).Msg("Error writing to file") } if err != nil { log.Fatal().Err(err).Msg("Error writing to file") }
// Log bytes written // Log bytes written
log.Info().Str("file", filepath.Base(file)).Msg("Wrote " + strconv.Itoa(int(bytesWritten)) + " bytes") log.Info().Str("file", filepath.Base(file)).Msg("Wrote " + strconv.Itoa(int(bytesWritten)) + " bytes")
// Close new file // Close new file
newFile.Close() newFile.Close()
} }
// Close response body
response.Body.Close()
} }
} }
// Send stop signal to sender's HTTP server // Send stop signal to sender
func SendSrvStopSignal(senderAddr string) { func SendSrvStopSignal(connection net.Conn) {
// Get server address by getting the IP without the port, prepending http:// and appending :9898 // Send stop signal to connection
serverAddr := "http://" + strings.Split(senderAddr, ":")[0] + ":9898" _, _ = fmt.Fprintln(connection, "stop;")
// GET /stop on sender's HTTP servers ignoring any errors // Close connection
_, _ = http.Get(serverAddr + "/stop") _ = connection.Close()
} }

View File

@ -1,13 +1,15 @@
package main package main
import ( import (
"bufio"
"crypto/rand" "crypto/rand"
"crypto/rsa" "crypto/rsa"
"crypto/sha256" "crypto/sha256"
"encoding/hex"
"fmt"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"io/ioutil" "net"
"net/http"
"os" "os"
"strings" "strings"
) )
@ -26,27 +28,28 @@ func GenerateRSAKeypair() (*rsa.PrivateKey, *rsa.PublicKey) {
} }
// Get public key from sender // Get public key from sender
func GetKey(senderAddr string) []byte { func GetKey(connection net.Conn) []byte {
// Use ConsoleWriter logger // Use ConsoleWriter logger
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Hook(FatalHook{}) log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Hook(FatalHook{})
// Get server address by getting the IP without the port, prepending http:// and appending :9898 // Send key request to connection
serverAddr := "http://" + strings.Split(senderAddr, ":")[0] + ":9898" _, err := fmt.Fprintln(connection, "key;")
// GET /key on the sender's HTTP server if err != nil { log.Fatal().Err(err).Msg("Error sending key request") }
response, err := http.Get(serverAddr + "/key") // Read received message
message, err := bufio.NewReader(connection).ReadString('\n')
if err != nil { log.Fatal().Err(err).Msg("Error getting key") } if err != nil { log.Fatal().Err(err).Msg("Error getting key") }
// Close response body at the end of this function // Process received message
defer response.Body.Close() procMessage := strings.Split(strings.TrimSpace(message), ";")
// If server responded with 200 OK // If ok code returned
if response.StatusCode == http.StatusOK { if procMessage[0] == "OK" {
// Read response body into key // Decode received hex string into key
key, err := ioutil.ReadAll(response.Body) key, err := hex.DecodeString(procMessage[1])
if err != nil { log.Fatal().Err(err).Msg("Error reading HTTP response") } if err != nil { log.Fatal().Err(err).Msg("Error reading key") }
// Return key // Return key
return key return key
// Otherwise // Otherwise
} else { } else {
// Fatally log status code // Fatally log
if err != nil { log.Fatal().Int("code", response.StatusCode).Msg("HTTP Error Response Code Received") } if err != nil { log.Fatal().Msg("Server reported error") }
} }
// Return nil if all else fails // Return nil if all else fails
return nil return nil

View File

@ -1,12 +1,16 @@
package main package main
import ( import (
"fmt"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"net"
"os" "os"
) )
// Fatal hook to run in case of Fatal error
type FatalHook struct {} type FatalHook struct {}
// Run function on trigger
func (hook FatalHook) Run(_ *zerolog.Event, level zerolog.Level, _ string) { func (hook FatalHook) Run(_ *zerolog.Event, level zerolog.Level, _ string) {
// If log event is fatal // If log event is fatal
if level == zerolog.FatalLevel { if level == zerolog.FatalLevel {
@ -14,3 +18,22 @@ func (hook FatalHook) Run(_ *zerolog.Event, level zerolog.Level, _ string) {
_ = os.RemoveAll(opensendDir) _ = os.RemoveAll(opensendDir)
} }
} }
// TCP Fatal hook to run in case of Fatal error with open TCP connection
type TCPFatalHook struct {
conn net.Conn
}
// Run function on trigger
func (hook TCPFatalHook) Run(_ *zerolog.Event, level zerolog.Level, _ string) {
// If log event is fatal
if level == zerolog.FatalLevel {
// Send error to connection
_, _ = fmt.Fprintln(hook.conn, "ERR;")
// Close connection
_ = hook.conn.Close()
// Attempt removal of opensend directory
_ = os.RemoveAll(opensendDir)
}
}

22
main.go
View File

@ -112,6 +112,14 @@ func main() {
// Get IP of chosen receiver // Get IP of chosen receiver
choiceIP = discoveredIPs[choiceIndex] choiceIP = discoveredIPs[choiceIndex]
} }
// Instantiate Config object
config := NewConfig(*actionType, *actionData)
// Validate data in config struct
config.Validate()
// Collect any files that may be required for transaction into opensend directory
config.CollectFiles(opensendDir)
// Create config file in opensend directory
config.CreateFile(opensendDir)
// Notify user of key exchange // Notify user of key exchange
log.Info().Msg("Performing key exchange") log.Info().Msg("Performing key exchange")
// Exchange RSA keys with receiver // Exchange RSA keys with receiver
@ -122,12 +130,6 @@ func main() {
key := EncryptKey(sharedKey, rawKey) key := EncryptKey(sharedKey, rawKey)
// Save encrypted key in opensend directory as key.aes // Save encrypted key in opensend directory as key.aes
SaveEncryptedKey(key, opensendDir + "/key.aes") SaveEncryptedKey(key, opensendDir + "/key.aes")
// Instantiate Config object
config := NewConfig(*actionType, *actionData)
// Collect any files that may be required for transaction into opensend directory
config.CollectFiles(opensendDir)
// Create config file in opensend directory
config.CreateFile(opensendDir)
// Notify user file encryption is beginning // Notify user file encryption is beginning
log.Info().Msg("Encrypting files") log.Info().Msg("Encrypting files")
// Encrypt all files in opensend directory using shared key // Encrypt all files in opensend directory using shared key
@ -157,12 +159,14 @@ func main() {
time.Sleep(300*time.Millisecond) time.Sleep(300*time.Millisecond)
// Notify user files are being received // Notify user files are being received
log.Info().Msg("Receiving files from server (This may take a while)") log.Info().Msg("Receiving files from server (This may take a while)")
// Connect to sender's TCP socket
connection := ConnectToSender(senderIP)
// Get files from sender and place them into the opensend directory // Get files from sender and place them into the opensend directory
RecvFiles(senderIP) RecvFiles(connection)
// Get encrypted shared key from sender // Get encrypted shared key from sender
encryptedKey := GetKey(senderIP) encryptedKey := GetKey(connection)
// Send stop signal to sender's HTTP server // Send stop signal to sender's HTTP server
SendSrvStopSignal(senderIP) SendSrvStopSignal(connection)
// Decrypt shared key // Decrypt shared key
sharedKey := DecryptKey(encryptedKey, privateKey) sharedKey := DecryptKey(encryptedKey, privateKey)
// Notify user file decryption is beginning // Notify user file decryption is beginning