diff --git a/go.mod b/go.mod index 342ee8f..0264091 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,13 @@ module github.com/schollz/croc -require github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 +require ( + github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 + github.com/gorilla/websocket v1.4.0 + github.com/pkg/errors v0.8.0 + github.com/schollz/pake v1.0.2 + github.com/schollz/progressbar v1.0.0 + github.com/schollz/progressbar/v2 v2.5.3 + github.com/tscholl2/siec v0.0.0-20180721101609-21667da05937 + github.com/urfave/cli v1.20.0 + golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b +) diff --git a/main.go b/main.go index a33ce79..43ea0cb 100644 --- a/main.go +++ b/main.go @@ -1,131 +1,127 @@ package main import ( - log "github.com/cihub/seelog" - "github.com/schollz/croc/src/logger" + "errors" + "fmt" + "os" + "time" + + "github.com/schollz/croc/src/croc" + "github.com/urfave/cli" ) +var version string +var codePhrase string + +var cr *croc.Croc + func main() { - defer log.Flush() - logger.SetLogLevel("debug") - log.Debug("hi") + app := cli.NewApp() + app.Name = "croc" + if version == "" { + version = "dev" + } + + app.Version = version + app.Compiled = time.Now() + app.Usage = "easily and securely transfer stuff from one computer to another" + app.UsageText = "croc allows any two computers to directly and securely transfer files" + // app.ArgsUsage = "[args and such]" + app.Commands = []cli.Command{ + { + Name: "send", + Usage: "send a file", + Description: "send a file over the relay", + ArgsUsage: "[filename]", + Flags: []cli.Flag{ + cli.BoolFlag{Name: "no-compress, o", Usage: "disable compression"}, + cli.BoolFlag{Name: "no-encrypt, e", Usage: "disable encryption"}, + cli.StringFlag{Name: "code, c", Usage: "codephrase used to connect to relay"}, + }, + HelpName: "croc send", + Action: func(c *cli.Context) error { + return send(c) + }, + }, + { + Name: "relay", + Usage: "start a croc relay", + Description: "the croc relay will handle websocket and TCP connections", + Flags: []cli.Flag{ + cli.StringFlag{Name: "port", Value: "8152", Usage: "port that the websocket listens on"}, + cli.StringFlag{Name: "curve", Value: "siec", Usage: "specify elliptic curve to use (p224, p256, p384, p521, siec)"}, + }, + HelpName: "croc relay", + Action: func(c *cli.Context) error { + return relay(c) + }, + }, + } + app.Flags = []cli.Flag{ + cli.StringFlag{Name: "relay", Value: "ws://localhost:8152"}, + cli.BoolFlag{Name: "no-local", Usage: "disable local mode"}, + cli.BoolFlag{Name: "local", Usage: "use only local mode"}, + cli.BoolFlag{Name: "debug", Usage: "increase verbosity (a lot)"}, + cli.BoolFlag{Name: "yes", Usage: "automatically agree to all prompts"}, + cli.BoolFlag{Name: "stdout", Usage: "redirect file to stdout"}, + } + app.EnableBashCompletion = true + app.HideHelp = false + app.HideVersion = false + app.BashComplete = func(c *cli.Context) { + fmt.Fprintf(c.App.Writer, "send\nreceive\relay") + } + app.Action = func(c *cli.Context) error { + return receive(c) + } + app.Before = func(c *cli.Context) error { + cr = croc.Init(c.GlobalBool("debug")) + cr.AllowLocalDiscovery = true + cr.WebsocketAddress = c.GlobalString("relay") + cr.Yes = c.GlobalBool("yes") + cr.Stdout = c.GlobalBool("stdout") + cr.LocalOnly = c.GlobalBool("local") + cr.NoLocal = c.GlobalBool("no-local") + return nil + } + + err := app.Run(os.Args) + if err != nil { + fmt.Printf("\nerror: %s", err.Error()) + } } -// var version string -// var codePhrase string +func send(c *cli.Context) error { + stat, _ := os.Stdin.Stat() + var fname string + if (stat.Mode() & os.ModeCharDevice) == 0 { + fname = "stdin" + } else { + fname = c.Args().First() + } + if fname == "" { + return errors.New("must specify file: croc send [filename]") + } + cr.UseCompression = !c.Bool("no-compress") + cr.UseEncryption = !c.Bool("no-encrypt") + if c.String("code") != "" { + codePhrase = c.String("code") + } + return cr.Send(fname, codePhrase) +} -// var cr *croc.Croc +func receive(c *cli.Context) error { + if c.GlobalString("code") != "" { + codePhrase = c.GlobalString("code") + } + if c.Args().First() != "" { + codePhrase = c.Args().First() + } + return cr.Receive(codePhrase) +} -// func main() { -// app := cli.NewApp() -// app.Name = "croc" -// if version == "" { -// version = "dev" -// } - -// app.Version = version -// app.Compiled = time.Now() -// app.Usage = "easily and securely transfer stuff from one computer to another" -// app.UsageText = "croc allows any two computers to directly and securely transfer files" -// // app.ArgsUsage = "[args and such]" -// app.Commands = []cli.Command{ -// { -// Name: "send", -// Usage: "send a file", -// Description: "send a file over the relay", -// ArgsUsage: "[filename]", -// Flags: []cli.Flag{ -// cli.BoolFlag{Name: "no-compress, o", Usage: "disable compression"}, -// cli.BoolFlag{Name: "no-encrypt, e", Usage: "disable encryption"}, -// cli.StringFlag{Name: "code, c", Usage: "codephrase used to connect to relay"}, -// }, -// HelpName: "croc send", -// Action: func(c *cli.Context) error { -// return send(c) -// }, -// }, -// { -// Name: "relay", -// Usage: "start a croc relay", -// Description: "the croc relay will handle websocket and TCP connections", -// Flags: []cli.Flag{ -// cli.StringFlag{Name: "tcp", Value: "27130,27131,27132,27133", Usage: "ports for the tcp connections"}, -// cli.StringFlag{Name: "port", Value: "8130", Usage: "port that the websocket listens on"}, -// cli.StringFlag{Name: "curve", Value: "siec", Usage: "specify elliptic curve to use (p224, p256, p384, p521, siec)"}, -// }, -// HelpName: "croc relay", -// Action: func(c *cli.Context) error { -// return relay(c) -// }, -// }, -// } -// app.Flags = []cli.Flag{ -// cli.StringFlag{Name: "relay", Value: "wss://croc3.schollz.com"}, -// cli.BoolFlag{Name: "no-local", Usage: "disable local mode"}, -// cli.BoolFlag{Name: "local", Usage: "use only local mode"}, -// cli.BoolFlag{Name: "debug", Usage: "increase verbosity (a lot)"}, -// cli.BoolFlag{Name: "yes", Usage: "automatically agree to all prompts"}, -// cli.BoolFlag{Name: "stdout", Usage: "redirect file to stdout"}, -// } -// app.EnableBashCompletion = true -// app.HideHelp = false -// app.HideVersion = false -// app.BashComplete = func(c *cli.Context) { -// fmt.Fprintf(c.App.Writer, "send\nreceive\relay") -// } -// app.Action = func(c *cli.Context) error { -// return receive(c) -// } -// app.Before = func(c *cli.Context) error { -// cr = croc.Init() -// cr.AllowLocalDiscovery = true -// cr.WebsocketAddress = c.GlobalString("relay") -// cr.SetDebug(c.GlobalBool("debug")) -// cr.Yes = c.GlobalBool("yes") -// cr.Stdout = c.GlobalBool("stdout") -// cr.LocalOnly = c.GlobalBool("local") -// cr.NoLocal = c.GlobalBool("no-local") -// return nil -// } - -// err := app.Run(os.Args) -// if err != nil { -// fmt.Printf("\nerror: %s", err.Error()) -// } -// } - -// func send(c *cli.Context) error { -// stat, _ := os.Stdin.Stat() -// var fname string -// if (stat.Mode() & os.ModeCharDevice) == 0 { -// fname = "stdin" -// } else { -// fname = c.Args().First() -// } -// if fname == "" { -// return errors.New("must specify file: croc send [filename]") -// } -// cr.UseCompression = !c.Bool("no-compress") -// cr.UseEncryption = !c.Bool("no-encrypt") -// if c.String("code") != "" { -// codePhrase = c.String("code") -// } -// return cr.Send(fname, codePhrase) -// } - -// func receive(c *cli.Context) error { -// if c.GlobalString("code") != "" { -// codePhrase = c.GlobalString("code") -// } -// if c.Args().First() != "" { -// codePhrase = c.Args().First() -// } -// return cr.Receive(codePhrase) -// } - -// func relay(c *cli.Context) error { -// cr.TcpPorts = strings.Split(c.String("tcp"), ",") -// cr.ServerPort = c.String("port") -// cr.CurveType = c.String("curve") -// return cr.Relay() -// } +func relay(c *cli.Context) error { + cr.ServerPort = c.String("port") + cr.CurveType = c.String("curve") + return cr.Relay() +} diff --git a/src/croc/croc.go b/src/croc/croc.go index a1632d6..373ded4 100644 --- a/src/croc/croc.go +++ b/src/croc/croc.go @@ -1,6 +1,13 @@ package croc -import "time" +import ( + "time" + + "github.com/schollz/croc/src/logger" + "github.com/schollz/croc/src/recipient" + "github.com/schollz/croc/src/relay" + "github.com/schollz/croc/src/sender" +) // Croc options type Croc struct { @@ -24,6 +31,10 @@ type Croc struct { Yes bool Stdout bool + // Parameters for file transfer + Filename string + Codephrase string + // private variables // localIP address @@ -32,3 +43,23 @@ type Croc struct { isLocal bool normalFinish bool } + +// Init will initiate with the default parameters +func Init(debug bool) (c *Croc) { + c = new(Croc) + c.ServerPort = "8152" + c.CurveType = "siec" + c.UseCompression = true + c.UseEncryption = true + c.AllowLocalDiscovery = true + debugLevel := "info" + if debug { + debugLevel = "debug" + c.Debug = true + } + logger.SetLogLevel(debugLevel) + sender.DebugLevel = debugLevel + recipient.DebugLevel = debugLevel + relay.DebugLevel = debugLevel + return +} diff --git a/src/croc/sending.go b/src/croc/sending.go new file mode 100644 index 0000000..1dced75 --- /dev/null +++ b/src/croc/sending.go @@ -0,0 +1,81 @@ +package croc + +import ( + "os" + "os/signal" + "time" + + log "github.com/cihub/seelog" + "github.com/gorilla/websocket" + "github.com/schollz/croc/src/recipient" + "github.com/schollz/croc/src/relay" + "github.com/schollz/croc/src/sender" +) + +// Send the file +func (c *Croc) Send(fname, codephrase string) (err error) { + log.Debugf("sending %s", fname) + return c.sendReceive(fname, codephrase, true) +} + +// Receive the file +func (c *Croc) Receive(codephrase string) (err error) { + return c.sendReceive("", codephrase, false) +} + +func (c *Croc) sendReceive(fname, codephrase string, isSender bool) (err error) { + defer log.Flush() + + // allow interrupts + interrupt := make(chan os.Signal, 1) + signal.Notify(interrupt, os.Interrupt) + + // connect to server + log.Debugf("connecting to %s", c.WebsocketAddress) + sock, _, err := websocket.DefaultDialer.Dial(c.WebsocketAddress+"/ws", nil) + if err != nil { + return + } + defer sock.Close() + + done := make(chan struct{}) + + // tell the websockets we are connected + err = sock.WriteMessage(websocket.BinaryMessage, []byte("connected")) + if err != nil { + return err + } + + if isSender { + go sender.Send(done, sock, fname, codephrase) + } else { + go recipient.Receive(done, sock, codephrase) + } + + for { + select { + case <-done: + return nil + case <-interrupt: + log.Debug("interrupt") + + // Cleanly close the connection by sending a close message and then + // waiting (with timeout) for the server to close the connection. + err := sock.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) + if err != nil { + log.Debug("write close:", err) + return nil + } + select { + case <-done: + case <-time.After(time.Second): + } + return nil + } + } +} + +// Relay will start a relay on the specified port +func (c *Croc) Relay() (err error) { + return relay.Run(c.ServerPort) +} diff --git a/src/recipient/recipient.go b/src/recipient/recipient.go index 4f338cf..dbfb618 100644 --- a/src/recipient/recipient.go +++ b/src/recipient/recipient.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "os" + "strings" "time" log "github.com/cihub/seelog" @@ -16,31 +17,33 @@ import ( "github.com/schollz/croc/src/models" "github.com/schollz/croc/src/utils" "github.com/schollz/pake" - "github.com/schollz/progressbar" + "github.com/schollz/progressbar/v2" "github.com/tscholl2/siec" ) var DebugLevel string // Receive is the async operation to receive a file -func Receive(done chan struct{}, c *websocket.Conn) { +func Receive(done chan struct{}, c *websocket.Conn, codephrase string) { logger.SetLogLevel(DebugLevel) - - err := receive(c) + err := receive(c, codephrase) if err != nil { + if strings.HasPrefix(err.Error(), "websocket: close 1005") { + return + } log.Error(err) } done <- struct{}{} } -func receive(c *websocket.Conn) (err error) { +func receive(c *websocket.Conn, codephrase string) (err error) { var fstats models.FileStats var sessionKey []byte // pick an elliptic curve curve := siec.SIEC255() // both parties should have a weak key - pw := []byte{1, 2, 3} + pw := []byte(codephrase) // initialize recipient Q ("1" indicates recipient) Q, err := pake.Init(pw, 1, curve, 100*time.Millisecond) diff --git a/src/relay/conn.go b/src/relay/conn.go index 4bc139f..59171fd 100644 --- a/src/relay/conn.go +++ b/src/relay/conn.go @@ -99,7 +99,8 @@ func (s *subscription) writePump() { func serveWs(w http.ResponseWriter, r *http.Request) { ws, err := upgrader.Upgrade(w, r, nil) if err != nil { - log.ErrorStr(err) + log.Error(err) + return } vals := r.URL.Query() room := "default" diff --git a/src/relay/relay.go b/src/relay/relay.go index 6a9368b..becaa77 100644 --- a/src/relay/relay.go +++ b/src/relay/relay.go @@ -14,7 +14,7 @@ func Run(port string) (err error) { logger.SetLogLevel(DebugLevel) go h.run() - log.Debug("running") + log.Info("running relay on " + port) http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { serveWs(w, r) }) diff --git a/src/sender/sender.go b/src/sender/sender.go index b06f738..4587925 100644 --- a/src/sender/sender.go +++ b/src/sender/sender.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "os" + "strings" "time" log "github.com/cihub/seelog" @@ -17,23 +18,27 @@ import ( "github.com/schollz/croc/src/models" "github.com/schollz/croc/src/utils" "github.com/schollz/pake" - "github.com/schollz/progressbar" + "github.com/schollz/progressbar/v2" "github.com/tscholl2/siec" ) var DebugLevel string // Send is the async call to send data -func Send(done chan struct{}, c *websocket.Conn, fname string) { +func Send(done chan struct{}, c *websocket.Conn, fname string, codephrase string) { logger.SetLogLevel(DebugLevel) - err := send(c, fname) + log.Debugf("sending %s", fname) + err := send(c, fname, codephrase) if err != nil { + if strings.HasPrefix(err.Error(), "websocket: close 1005") { + return + } log.Error(err) } done <- struct{}{} } -func send(c *websocket.Conn, fname string) (err error) { +func send(c *websocket.Conn, fname string, codephrase string) (err error) { var f *os.File var fstats models.FileStats var sessionKey []byte @@ -41,7 +46,7 @@ func send(c *websocket.Conn, fname string) (err error) { // pick an elliptic curve curve := siec.SIEC255() // both parties should have a weak key - pw := []byte{1, 2, 3} + pw := []byte(codephrase) // initialize sender P ("0" indicates sender) P, err := pake.Init(pw, 0, curve, 100*time.Millisecond) if err != nil { @@ -147,6 +152,7 @@ func send(c *websocket.Conn, fname string) (err error) { log.Debugf("[%d] determing whether it went ok", step) if bytes.Equal(message, []byte("ok")) { log.Debug("file transfered successfully") + return nil } else { return errors.New("file not transfered succesfully") }