Compare commits

..

No commits in common. "main" and "v0.0.3" have entirely different histories.
main ... v0.0.3

12 changed files with 127 additions and 585 deletions

3
.gitignore vendored
View File

@ -1,3 +0,0 @@
.DS_Store
dist/
release/

View File

@ -1,50 +0,0 @@
version: 2
before:
hooks:
- go mod tidy
- go test ./...
builds:
- env:
- CGO_ENABLED=0
goos:
- linux
- windows
- darwin
- freebsd
goarch:
- arm
- arm64
- amd64
goarm:
- 6
- 7
ignore:
- goos: darwin
goarch: arm
- goos: windows
goarch: arm
- goos: windows
goarch: arm64
- goos: freebsd
goarch: arm
archives:
- formats: [tar.gz]
# this name template makes the OS and Arch compatible with the results of `uname`.
name_template: >-
{{ .ProjectName }}_
{{- title .Os }}_
{{- if eq .Arch "amd64" }}x86_64
{{- else if eq .Arch "386" }}i386
{{- else }}{{ .Arch }}{{ end }}
{{- if .Arm }}v{{ .Arm }}{{ end }}
# use zip for windows archives
format_overrides:
- goos: windows
formats: [zip]
changelog:
disable: true

13
.vscode/settings.json vendored
View File

@ -1,20 +1,9 @@
{ {
"cSpell.words": [ "cSpell.words": [
"authtoken",
"Debugf", "Debugf",
"Infof", "Infof",
"isatty", "isatty",
"netgiv", "netgiv",
"ngfs", "pflag"
"tardisx",
"ttys"
],
"cSpell.ignoreWords": [
"logrus",
"mattn",
"pflag",
"sigchan",
"sirupsen",
"verysecretvaluehere"
] ]
} }

View File

@ -1,42 +0,0 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## v1.0.0 - 2025-04-26
### Added
* burn mode - remove copied files from the server (by [@jsnfwlr](https://github.com/jsnfwlr))
## v0.0.5 - 2025-04-26
### Changed
* minor refactoring
* start using goreleaser
## v0.0.4 - 2022-01-26
### Added
* timestamps on list output
## v0.0.3 - 2022-01-25
### Added
* get authtoken from the terminal if not available elsewhere
## v0.0.2 - 2022-01-17
### Added
* add -p to paste from a file instead of stdin
## v0.0.1 - 2022-01-16
Initial version

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2022 Justin Hawkins
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

162
README.md
View File

@ -1,163 +1,3 @@
# netgiv # netgiv
## What is this? TBD
`netgiv` is a single binary client and server to facilitate sending files across
your local network quickly and easily.
It uses a familiar unix pipeline paradigm, so files can be moved between machines
as part of a pipeline, obviating the need for dealing with temporary files.
`netgiv` automatically detects "copy" (stdin is a pipe) or "paste" (stdout is a
pipe) modes, allowing intuitive use like:
hostA$ pg_dumpall | netgiv
hostB$ netgiv | psql restoredb
Note that since netgiv uses a persistent server, there is no need to setup both ends
of the pipeline in advance (compared to netcat or similar tools).
This also means that you could "copy" once and "paste" multiple times, on
multiple different machines.
All data is encrypted in flight (though not in the temporary files on the server)
Access to the server is granted by an authentication token (preshared key) of your
choice.
## Install
### Binary release
Grab the appropriate version from https://github.com/tardisx/netgiv/releases, unzip
and place the binary somewhere on your $PATH.
### Compiling from source
go install github.com/tardisx/netgiv@latest
`netgiv` should end up on your go binary path.
### Compiling from source
Clone this repository, run `go build`.
## Configuration
Configuration of `netgiv` is via a YAML configuration file in
`$HOME/.netgiv/config.yaml`.
Run `netgiv --help-config` to see a sample config file.
The server requires the 'authtoken' and 'port' configuration keys to be set.
The client requires the 'authtoken', 'port' and 'address' configuration keys to be
set.
* `authtoken` - this is any arbitrary string, you should choose something not easy to
guess
* `port` - this is the TCP port the server will listen on (and that the client will
connect to)
* `address` - the IP address or hostname of the `netgiv` server
## Running
### Server
To run a server, just run:
netgiv --server
`netgiv` will run in the foreground and log accesses to it.
### Client
#### Copy
On any client, run:
$ echo "Hello" | netgiv
To check for success, try:
$ netgiv | cat
You should see "hello" echoed on your terminal.
#### List
To check the list of files on the server:
$ netgiv -l
1: UTF-8 text (6 B)
2: application/x-mach-binary (6.5 MB)
3: video/quicktime (14 MB)
4: image/png (1.5 MB)
Note that netgiv tries to identify each file based on file magic heuristics.
#### Paste
If you would like to fetch (paste) a particular file:
netgiv -p 3 > file.mov
Where '3' comes from the information provided in the `-l` output.
Note that providing no `-p` option is the same as `-p X` where X is the highest
numbered upload (most recent).
#### Burn
If you would like to remove/delete (burn) a particular file:
netgiv -b 3
Where '3' comes from the information provided in the `-l` output.
### Notes on output
Since netgiv is designed to be used in a pipeline, it does not provide any
output on successful execution (apart from your actual data on stdout of course!)
If you'd like to see debugging information, use the `--debug` flag.
Note that `netgiv` will send error logs to stderr in cases of problems.
### Alternative ways of providing the authtoken
It's possible that you do not trust the hosts you are running the `netgiv` client on,
or otherwise not want to store your authtoken in a file on there. If that is the case
there are a couple of alternate options:
#### ENV var
The environment variable NETGIV_AUTHTOKEN can be used to provide the authtoken. A
common way to leverage this is to send it when you ssh to a remote host via the
`SendEnv` option (see your ssh_config man page).
#### Interactive
If the authtoken has not been set by any of the above methods, it will be prompted
for interactively (it will not be echoed to the screen). Note that this only applies
to the client - the server must have a config file with an authtoken specified.
# Other notes
## Temporary file storage
The `netgiv` server will store files in your normal system temporary dir. These files
are *not* encrypted. They will be deleted when the server shuts down (SIGTERM). If you
want or need to remove the files before the server shuts down, you can use the
[burn](#burn) flag.
## Window support
Windows support is marginal, at best, mostly because of the lack of POSIX style
pipes. Bug reports and suggestions for workarounds are welcome.
# Acknowledgements
* thanks to tengig for the name
* thanks to [@jsnfwlr](https://github.com/jsnfwlr) for the burn feature

40
build_release.pl Executable file
View File

@ -0,0 +1,40 @@
#!/usr/bin/env perl
use strict;
use warnings;
open my $fh, "<", "main.go" || die $!;
my $version;
while (<$fh>) {
# CurrentVersion: "v0.04"
$version = $1 if /CurrentVersion\s*=\s*"(v[\d\.]+)"/;
}
close $fh;
die "no version?" unless defined $version;
# quit if tests fail
system("go test ./...") && die "not building release with failing tests";
# so lazy
system "rm", "-rf", "release", "dist";
system "mkdir", "release";
system "mkdir", "dist";
my %build = (
win => { env => { GOOS => 'windows', GOARCH => 'amd64' }, filename => 'netgiv.exe' },
linux => { env => { GOOS => 'linux', GOARCH => 'amd64' }, filename => 'netgiv' },
mac => { env => { GOOS => 'darwin', GOARCH => 'amd64' }, filename => 'netgiv' },
);
foreach my $type (keys %build) {
mkdir "release/$type";
}
foreach my $type (keys %build) {
local $ENV{GOOS} = $build{$type}->{env}->{GOOS};
local $ENV{GOARCH} = $build{$type}->{env}->{GOARCH};
system "go", "build", "-o", "release/$type/" . $build{$type}->{filename};
system "zip", "-j", "dist/netgiv-$type-$version.zip", ( glob "release/$type/*" );
}

View File

@ -22,7 +22,6 @@ type Client struct {
port int port int
list bool list bool
send bool send bool
burnNum int
receiveNum int receiveNum int
authToken string authToken string
} }
@ -51,8 +50,7 @@ func (c *Client) Connect() error {
enc := gob.NewEncoder(&secureConnection) enc := gob.NewEncoder(&secureConnection)
dec := gob.NewDecoder(&secureConnection) dec := gob.NewDecoder(&secureConnection)
switch { if c.list {
case c.list:
log.Debugf("requesting file list") log.Debugf("requesting file list")
err := c.connectToServer(secure.OperationTypeList, enc, dec) err := c.connectToServer(secure.OperationTypeList, enc, dec)
@ -61,7 +59,6 @@ func (c *Client) Connect() error {
} }
// now we expect to get stuff back until we don't // now we expect to get stuff back until we don't
numFiles := 0
for { for {
listPacket := secure.PacketListData{} listPacket := secure.PacketListData{}
err := dec.Decode(&listPacket) err := dec.Decode(&listPacket)
@ -71,13 +68,12 @@ func (c *Client) Connect() error {
if err != nil { if err != nil {
panic(err) panic(err)
} }
fmt.Printf("%d: %s (%s) - %s\n", listPacket.Id, listPacket.Kind, humanize.Bytes(uint64(listPacket.FileSize)), listPacket.Timestamp) fmt.Printf("%d: %s (%s)\n", listPacket.Id, listPacket.Kind, humanize.Bytes(uint64(listPacket.FileSize)))
numFiles++
} }
fmt.Printf("total: %d files\n", numFiles)
conn.Close() conn.Close()
log.Debugf("done listing") log.Debugf("done listing")
case c.receiveNum >= 0:
} else if c.receiveNum >= 0 {
log.Debugf("receiving file %d", c.receiveNum) log.Debugf("receiving file %d", c.receiveNum)
err := c.connectToServer(secure.OperationTypeReceive, enc, dec) err := c.connectToServer(secure.OperationTypeReceive, enc, dec)
@ -99,8 +95,7 @@ func (c *Client) Connect() error {
panic(err) panic(err)
} }
switch res.Status { if res.Status == secure.ReceiveDataStartResponseOK {
case secure.ReceiveDataStartResponseOK:
for { for {
res := secure.PacketReceiveDataNext{} res := secure.PacketReceiveDataNext{}
err = dec.Decode(&res) err = dec.Decode(&res)
@ -113,14 +108,14 @@ func (c *Client) Connect() error {
} }
} }
log.Debugf("finished") log.Debugf("finished")
case secure.ReceiveDataStartResponseNotFound: } else if res.Status == secure.ReceiveDataStartResponseNotFound {
log.Error("ngf not found") log.Error("ngf not found")
default: } else {
panic("unknown status") panic("unknown status")
} }
conn.Close() conn.Close()
case c.send: } else if c.send {
// send mode // send mode
err := c.connectToServer(secure.OperationTypeSend, enc, dec) err := c.connectToServer(secure.OperationTypeSend, enc, dec)
@ -171,50 +166,21 @@ func (c *Client) Connect() error {
log.Debugf("Sent %s in %d chunks", humanize.Bytes(uint64(nBytes)), nChunks) log.Debugf("Sent %s in %d chunks", humanize.Bytes(uint64(nBytes)), nChunks)
conn.Close() conn.Close()
case c.burnNum >= 0:
log.Debugf("burning file %d", c.burnNum)
err := c.connectToServer(secure.OperationTypeBurn, enc, dec) } else {
if err != nil {
return fmt.Errorf("could not connect and auth: %v", err)
}
req := secure.PacketBurnRequest{
Id: uint32(c.burnNum),
}
err = enc.Encode(req)
if err != nil {
panic(err)
}
// expect a response telling us if we can go ahead
res := secure.PacketBurnResponse{}
err = dec.Decode(&res)
if err != nil {
panic(err)
}
switch res.Status {
case secure.BurnResponseOK:
log.Debugf("finished")
case secure.BurnResponseNotFound:
log.Error("ngf not found")
default:
panic("unknown status")
}
conn.Close()
default:
panic("no client mode set") panic("no client mode set")
} }
return nil return nil
} }
func (c *Client) connectToServer(op secure.OperationTypeEnum, enc *gob.Encoder, dec *gob.Decoder) error { func (c *Client) connectToServer(op secure.OperationTypeEnum, enc *gob.Encoder, dec *gob.Decoder) error {
// list mode // list mode
startPacket := secure.PacketStartRequest{ startPacket := secure.PacketStartRequest{
OperationType: op, OperationType: op,
ClientName: "", ClientName: "",
ProtocolVersion: ProtocolVersion, ProtocolVersion: "1.0",
AuthToken: c.authToken, AuthToken: c.authToken,
} }
err := enc.Encode(startPacket) err := enc.Encode(startPacket)

85
main.go
View File

@ -13,37 +13,39 @@ import (
"github.com/spf13/viper" "github.com/spf13/viper"
) )
const ProtocolVersion = "1.1" var CurrentVersion = "v0.0.3"
type ListValue struct { type PasteValue struct {
Required bool PasteRequired bool
Number uint PasteNumber uint
} }
func (v *ListValue) String() string { func (v *PasteValue) String() string {
if v.Required { if v.PasteRequired {
return fmt.Sprintf("YES: %d", v.Number) return fmt.Sprintf("YES: %d", v.PasteNumber)
} }
return "0" return "0"
} }
func (v *ListValue) Set(s string) error { func (v *PasteValue) Set(s string) error {
v.Required = true v.PasteRequired = true
num, err := strconv.ParseUint(s, 10, 64) num, err := strconv.ParseUint(s, 10, 64)
if err != nil { if err != nil {
return err return err
} }
v.Number = uint(num) v.PasteNumber = uint(num)
return nil return nil
} }
func (v *ListValue) Type() string { func (v *PasteValue) Type() string {
return "int" return "int"
} }
func getAuthTokenFromTerminal() string { func getAuthTokenFromTerminal() string {
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0o755) tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0755)
if err != nil { if err != nil {
log.Printf("cannot open /dev/tty to read authtoken: %v", err) log.Printf("cannot open /dev/tty to read authtoken: %v", err)
return "" return ""
@ -55,9 +57,7 @@ func getAuthTokenFromTerminal() string {
log.Printf("cannot set /dev/tty to raw mode: %v", err) log.Printf("cannot set /dev/tty to raw mode: %v", err)
return "" return ""
} }
defer func() { defer term.Restore(fd, oldState)
_ = term.Restore(fd, oldState)
}()
t := term.NewTerminal(tty, "") t := term.NewTerminal(tty, "")
pass, err := t.ReadPassword("Enter auth token: ") pass, err := t.ReadPassword("Enter auth token: ")
@ -69,27 +69,17 @@ func getAuthTokenFromTerminal() string {
return pass return pass
} }
var (
version = "dev"
commit = "none"
date = "unknown"
)
func main() { func main() {
isServer := flag.Bool("server", false, "Run netgiv in server mode") isServer := flag.Bool("server", false, "Run netgiv in server mode")
// client mode flags // client mode flags
isList := flag.BoolP("list", "l", false, "Returns a list of current items on the server") isList := flag.BoolP("list", "l", false, "Returns a list of current items on the server")
isSend := flag.BoolP("copy", "c", false, "send stdin to netgiv server (copy)") isSend := flag.BoolP("copy", "c", false, "sending stdin to netgiv server (copy)")
pasteFlag := ListValue{} pasteFlag := PasteValue{}
flag.VarP(&pasteFlag, "paste", "p", "receive from netgiv server to stdout (paste), with optional id (see --list)") flag.VarP(&pasteFlag, "paste", "p", "receive from netgiv server to stdout (paste), with optional number (see --list)")
flag.Lookup("paste").NoOptDefVal = "0" flag.Lookup("paste").NoOptDefVal = "0"
burnFlag := ListValue{}
flag.VarP(&burnFlag, "burn", "b", "burn (remove/delete) the item on the netgiv server, with optional id (see --list)")
flag.Lookup("burn").NoOptDefVal = "0"
debug := flag.Bool("debug", false, "turn on debug logging") debug := flag.Bool("debug", false, "turn on debug logging")
flag.String("address", "", "IP address/hostname of the netgiv server") flag.String("address", "", "IP address/hostname of the netgiv server")
@ -99,27 +89,14 @@ func main() {
flag.String("authtoken", "", "Authentication token") flag.String("authtoken", "", "Authentication token")
flag.Int("port", 0, "Port") flag.Int("port", 0, "Port")
versionFlag := flag.BoolP("version", "v", false, "show version and exit")
flag.Parse() flag.Parse()
if versionFlag != nil && *versionFlag { receiveNum := int(pasteFlag.PasteNumber)
fmt.Print(versionInfo(true)) if !pasteFlag.PasteRequired {
os.Exit(0)
}
receiveNum := int(pasteFlag.Number)
if !pasteFlag.Required {
receiveNum = -1 receiveNum = -1
} }
burnNum := int(burnFlag.Number) viper.AddConfigPath("$HOME/.netgiv/") // call multiple times to add many search paths
if !burnFlag.Required {
burnNum = -1
}
viper.AddConfigPath("$HOME/.netgiv/")
viper.AddConfigPath("$HOME/.config/netgiv/") // calling multiple times adds to search paths
viper.SetConfigType("yaml") viper.SetConfigType("yaml")
viper.SetDefault("port", 4512) viper.SetDefault("port", 4512)
@ -133,10 +110,11 @@ func main() {
} }
} }
_ = viper.BindPFlags(flag.CommandLine) flag.Parse()
viper.BindPFlags(flag.CommandLine)
viper.SetEnvPrefix("NETGIV") viper.SetEnvPrefix("NETGIV")
_ = viper.BindEnv("authtoken") viper.BindEnv("authtoken")
// pull the various things into local variables // pull the various things into local variables
port := viper.GetInt("port") // retrieve value from viper port := viper.GetInt("port") // retrieve value from viper
@ -190,12 +168,11 @@ environment variable. This may be preferable in some environments.
log.Fatal("an address must be provided on the command line, or configuration") log.Fatal("an address must be provided on the command line, or configuration")
} }
log.Debugf("protocol version: %s", ProtocolVersion)
if *isServer { if *isServer {
s := Server{port: port, authToken: authtoken} s := Server{port: port, authToken: authtoken}
s.Run() s.Run()
} else { } else {
if !*isList && !*isSend && burnNum == -1 && receiveNum == -1 { if !*isList && !*isSend && receiveNum == -1 {
// try to work out the intent based on whether or not stdin/stdout // try to work out the intent based on whether or not stdin/stdout
// are ttys // are ttys
stdinTTY := isatty.IsTerminal(os.Stdin.Fd()) stdinTTY := isatty.IsTerminal(os.Stdin.Fd())
@ -214,20 +191,10 @@ environment variable. This may be preferable in some environments.
} }
c := Client{port: port, address: address, list: *isList, send: *isSend, burnNum: burnNum, receiveNum: receiveNum, authToken: authtoken} c := Client{port: port, address: address, list: *isList, send: *isSend, receiveNum: receiveNum, authToken: authtoken}
err := c.Connect() err := c.Connect()
if err != nil { if err != nil {
fmt.Print(err) fmt.Print(err)
} }
} }
} }
func versionInfo(verbose bool) string {
out := ""
out += fmt.Sprintf("netgiv %s, built at %s\n", version, date)
if verbose {
out += fmt.Sprintf("commit: %s\n", commit)
out += fmt.Sprintf("http://github.com/tardisx/netgiv\n")
}
return out
}

View File

@ -7,7 +7,6 @@ import (
"errors" "errors"
"io" "io"
"net" "net"
"time"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@ -129,7 +128,7 @@ func (s *SecureConnection) Write(p []byte) (int, error) {
var nonce [24]byte var nonce [24]byte
// Create a new nonce for each message sent // Create a new nonce for each message sent
_, _ = rand.Read(nonce[:]) rand.Read(nonce[:])
encryptedMessage := box.SealAfterPrecomputation(nil, p, &nonce, s.SharedKey) encryptedMessage := box.SealAfterPrecomputation(nil, p, &nonce, s.SharedKey)
sm := SecureMessage{Msg: encryptedMessage, Nonce: nonce} sm := SecureMessage{Msg: encryptedMessage, Nonce: nonce}
@ -145,10 +144,10 @@ func Handshake(conn *net.TCPConn) *[32]byte {
publicKey, privateKey, _ := box.GenerateKey(rand.Reader) publicKey, privateKey, _ := box.GenerateKey(rand.Reader)
_, _ = conn.Write(publicKey[:]) conn.Write(publicKey[:])
peerKeyArray := make([]byte, 32) peerKeyArray := make([]byte, 32)
_, _ = conn.Read(peerKeyArray) conn.Read(peerKeyArray)
copy(peerKey[:], peerKeyArray) copy(peerKey[:], peerKeyArray)
box.Precompute(&sharedKey, &peerKey, privateKey) box.Precompute(&sharedKey, &peerKey, privateKey)
@ -162,11 +161,10 @@ const (
OperationTypeSend OperationTypeEnum = iota OperationTypeSend OperationTypeEnum = iota
OperationTypeList OperationTypeList
OperationTypeReceive OperationTypeReceive
OperationTypeBurn
) )
// PacketStartRequest is sent from the client to the server at the beginning // PacketStartRequest is sent from the client to the server at the beginning
// to authenticate and announce the requested particular operation // to authenticate and annonce the requested particular operation
type PacketStartRequest struct { type PacketStartRequest struct {
OperationType OperationTypeEnum OperationType OperationTypeEnum
ClientName string ClientName string
@ -228,26 +226,8 @@ type PacketReceiveDataNext struct {
} }
type PacketListData struct { type PacketListData struct {
Id uint32 Id uint32
Filename string Filename string
FileSize uint32 FileSize uint32
Timestamp time.Time Kind string
Kind string
} }
type PacketBurnRequest struct {
Id uint32
}
type PacketBurnResponse struct {
Status PacketBurnResponseEnum
}
type PacketBurnResponseEnum byte
const (
// File has been deleted
BurnResponseOK PacketBurnResponseEnum = iota
// No such file by index
BurnResponseNotFound
)

View File

@ -2,19 +2,27 @@ package secure
import ( import (
"bytes" "bytes"
"encoding/gob"
"net" "net"
"testing" "testing"
"time" "time"
) )
func TestBasic(t *testing.T) { func TestPacketBasic(t *testing.T) {
// pSrc := PacketStart{
// OperationType: 0,
// ClientName: "test1",
// ProtocolVersion: "test2",
// AuthToken: "test3",
// }
// pDst := PacketStart{}
// buf := bytes.Buffer{}
srcConn, dstConn := net.Pipe() srcConn, dstConn := net.Pipe()
srcSecConn := SecureConnection{ srcSecConn := SecureConnection{
Conn: srcConn, Conn: srcConn,
SharedKey: &[32]byte{ SharedKey: &[32]byte{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
@ -24,8 +32,7 @@ func TestBasic(t *testing.T) {
dstSecConn := SecureConnection{ dstSecConn := SecureConnection{
Conn: dstConn, Conn: dstConn,
SharedKey: &[32]byte{ SharedKey: &[32]byte{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
@ -47,7 +54,7 @@ func TestBasic(t *testing.T) {
for _, b := range testData { for _, b := range testData {
go func() { go func() {
_, _ = srcSecConn.Write(b) srcSecConn.Write(b)
}() }()
time.Sleep(time.Second) time.Sleep(time.Second)
@ -64,65 +71,7 @@ func TestBasic(t *testing.T) {
t.Errorf("%v not equal to %v", out[:n], b) t.Errorf("%v not equal to %v", out[:n], b)
} }
} }
}
func TestPacketBasic(t *testing.T) {
// test encoding/decoding of packets over the encrypted wire
srcConn, dstConn := net.Pipe()
srcSecConn := SecureConnection{
Conn: srcConn,
SharedKey: &[32]byte{
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
},
Buffer: &bytes.Buffer{},
}
dstSecConn := SecureConnection{
Conn: dstConn,
SharedKey: &[32]byte{
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
},
Buffer: &bytes.Buffer{},
}
enc := gob.NewEncoder(&srcSecConn)
dec := gob.NewDecoder(&dstSecConn)
packet := PacketStartRequest{
OperationType: OperationTypeReceive,
ClientName: "foo",
ProtocolVersion: "1.1",
AuthToken: "abc123",
}
go func() {
_ = enc.Encode(packet)
}()
recvPacket := PacketStartRequest{}
_ = dec.Decode(&recvPacket)
if recvPacket.OperationType != OperationTypeReceive {
t.Error("bad OperationType")
}
if recvPacket.ClientName != "foo" {
t.Error("bad ClientName")
}
if recvPacket.ClientName != "foo" {
t.Error("bad ClientName")
}
if recvPacket.AuthToken != "abc123" {
t.Error("bad AuthToken")
}
if recvPacket.ProtocolVersion != "1.1" {
t.Error("bad ProtocolVersion")
}
} }
func BenchmarkPPS(b *testing.B) { func BenchmarkPPS(b *testing.B) {
@ -130,8 +79,7 @@ func BenchmarkPPS(b *testing.B) {
srcSecConn := SecureConnection{ srcSecConn := SecureConnection{
Conn: srcConn, Conn: srcConn,
SharedKey: &[32]byte{ SharedKey: &[32]byte{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
@ -141,8 +89,7 @@ func BenchmarkPPS(b *testing.B) {
dstSecConn := SecureConnection{ dstSecConn := SecureConnection{
Conn: dstConn, Conn: dstConn,
SharedKey: &[32]byte{ SharedKey: &[32]byte{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
@ -160,11 +107,12 @@ func BenchmarkPPS(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
go func() { go func() {
_, _ = srcSecConn.Write(testdata) srcSecConn.Write(testdata)
}() }()
out := make([]byte, 16384) out := make([]byte, 16384)
n, err := dstSecConn.Read(out) n, err := dstSecConn.Read(out)
if err != nil { if err != nil {
b.Errorf("got error %v", err) b.Errorf("got error %v", err)
} }
@ -175,4 +123,5 @@ func BenchmarkPPS(b *testing.B) {
b.Errorf("%v not equal to %v", out[:n], testdata) b.Errorf("%v not equal to %v", out[:n], testdata)
} }
} }
} }

113
server.go
View File

@ -34,15 +34,10 @@ type NGF struct {
Timestamp time.Time Timestamp time.Time
} }
func (ngf NGF) String() string {
return fmt.Sprintf("id: %d, stored: %s, size: %d, kind: %s", ngf.Id, ngf.StorePath, ngf.Size, ngf.Kind)
}
var ngfs []NGF var ngfs []NGF
var globalId uint32 var globalId uint32
func (s *Server) Run() { func (s *Server) Run() {
log.Info(versionInfo(false))
log.Infof("starting server on :%d", s.port) log.Infof("starting server on :%d", s.port)
address := fmt.Sprintf(":%d", s.port) address := fmt.Sprintf(":%d", s.port)
networkAddress, _ := net.ResolveTCPAddr("tcp", address) networkAddress, _ := net.ResolveTCPAddr("tcp", address)
@ -63,7 +58,7 @@ func (s *Server) Run() {
log.Printf("removing file: %s", ngf.StorePath) log.Printf("removing file: %s", ngf.StorePath)
err := os.Remove(ngf.StorePath) err := os.Remove(ngf.StorePath)
if err != nil { if err != nil {
log.Errorf("could not remove %s: %v", ngf.StorePath, err) log.Printf("could not remove %s: %v", ngf.StorePath, err)
} }
} }
os.Exit(0) os.Exit(0)
@ -73,6 +68,7 @@ func (s *Server) Run() {
for { for {
conn, err := listener.AcceptTCP() conn, err := listener.AcceptTCP()
if err != nil { if err != nil {
fmt.Print(err) fmt.Print(err)
} }
@ -84,7 +80,7 @@ func (s *Server) Run() {
func (s *Server) handleConnection(conn *net.TCPConn) { func (s *Server) handleConnection(conn *net.TCPConn) {
defer conn.Close() defer conn.Close()
_ = conn.SetDeadline(time.Now().Add(time.Second * 5)) conn.SetDeadline(time.Now().Add(time.Second * 5))
sharedKey := secure.Handshake(conn) sharedKey := secure.Handshake(conn)
secureConnection := secure.SecureConnection{Conn: conn, SharedKey: sharedKey, Buffer: &bytes.Buffer{}} secureConnection := secure.SecureConnection{Conn: conn, SharedKey: sharedKey, Buffer: &bytes.Buffer{}}
@ -109,31 +105,30 @@ func (s *Server) handleConnection(conn *net.TCPConn) {
return return
} }
// tell the client if the connection is ok. // tell teh client the dealio
startResponse := secure.PacketStartResponse{} startResponse := secure.PacketStartResponse{}
if start.ProtocolVersion != ProtocolVersion { if start.ProtocolVersion != "1.0" {
log.Errorf("bad protocol version") log.Errorf("bad protocol version")
startResponse.Response = secure.PacketStartResponseEnumWrongProtocol startResponse.Response = secure.PacketStartResponseEnumWrongProtocol
_ = enc.Encode(startResponse) enc.Encode(startResponse)
return return
} }
if start.AuthToken != s.authToken { if start.AuthToken != s.authToken {
log.Errorf("bad authtoken") log.Errorf("bad authtoken")
startResponse.Response = secure.PacketStartResponseEnumBadAuthToken startResponse.Response = secure.PacketStartResponseEnumBadAuthToken
_ = enc.Encode(startResponse) enc.Encode(startResponse)
return return
} }
// otherwise we are good to continue, tell the client that // otherwise we are good to continue, tell the client that
startResponse.Response = secure.PacketStartResponseEnumOK startResponse.Response = secure.PacketStartResponseEnumOK
_ = enc.Encode(startResponse) enc.Encode(startResponse)
_ = conn.SetDeadline(time.Now().Add(time.Second * 5)) conn.SetDeadline(time.Now().Add(time.Second * 5))
switch start.OperationType { if start.OperationType == secure.OperationTypeSend {
case secure.OperationTypeSend:
log.Debugf("file incoming") log.Debugf("file incoming")
sendStart := secure.PacketSendDataStart{} sendStart := secure.PacketSendDataStart{}
@ -165,7 +160,7 @@ func (s *Server) handleConnection(conn *net.TCPConn) {
sendData := secure.PacketSendDataNext{} sendData := secure.PacketSendDataNext{}
determinedKind := false determinedKind := false
for { for {
_ = conn.SetDeadline(time.Now().Add(time.Second * 5)) conn.SetDeadline(time.Now().Add(time.Second * 5))
err = dec.Decode(&sendData) err = dec.Decode(&sendData)
if err == io.EOF { if err == io.EOF {
break break
@ -195,7 +190,7 @@ func (s *Server) handleConnection(conn *net.TCPConn) {
determinedKind = true determinedKind = true
} }
_, _ = file.Write(sendData.Data) file.Write(sendData.Data)
} }
info, err := file.Stat() info, err := file.Stat()
if err != nil { if err != nil {
@ -209,13 +204,13 @@ func (s *Server) handleConnection(conn *net.TCPConn) {
log.Printf("done receiving file: %v", ngf) log.Printf("done receiving file: %v", ngf)
return return
case secure.OperationTypeReceive: } else if start.OperationType == secure.OperationTypeReceive {
log.Printf("client requesting file receive") log.Printf("client requesting file receive")
// wait for them to send the request // wait for them to send the request
req := secure.PacketReceiveDataStartRequest{} req := secure.PacketReceiveDataStartRequest{}
err := dec.Decode(&req) err := dec.Decode(&req)
if err != nil { if err != nil {
log.Errorf("error expecting PacketReceiveDataStartRequest: %v", err) log.Printf("error expecting PacketReceiveDataStartRequest: %v", err)
return return
} }
@ -247,7 +242,7 @@ func (s *Server) handleConnection(conn *net.TCPConn) {
} }
err = enc.Encode(res) err = enc.Encode(res)
if err != nil { if err != nil {
log.Errorf("could not send NotFound: %v", err) log.Printf("could not send NotFound: %v", err)
} }
return return
@ -302,7 +297,8 @@ func (s *Server) handleConnection(conn *net.TCPConn) {
} }
log.Printf("sending done") log.Printf("sending done")
return return
case secure.OperationTypeList:
} else if start.OperationType == secure.OperationTypeList {
log.Debugf("client requesting file list") log.Debugf("client requesting file list")
for _, ngf := range ngfs { for _, ngf := range ngfs {
@ -311,84 +307,15 @@ func (s *Server) handleConnection(conn *net.TCPConn) {
p.Kind = ngf.Kind p.Kind = ngf.Kind
p.Id = ngf.Id p.Id = ngf.Id
p.Filename = ngf.Filename p.Filename = ngf.Filename
p.Timestamp = ngf.Timestamp enc.Encode(p)
_ = enc.Encode(p)
} }
log.Debugf("done sending list, closing connection") log.Debugf("done sending list, closing connection")
return return
case secure.OperationTypeBurn:
log.Debugf("client requesting burn")
// wait for them to send the request
req := secure.PacketBurnRequest{}
err := dec.Decode(&req)
if err != nil {
log.Errorf("error expecting PacketBurnRequest: %v", err)
return
}
log.Debugf("The client asked for %v to be burned", req) } else {
// do we have this ngf by id?
var requestedNGF NGF
if len(ngfs) > 0 {
if req.Id == 0 {
// they want the most recent one
requestedNGF = ngfs[len(ngfs)-1]
} else {
for _, ngf := range ngfs {
if ngf.Id == req.Id {
requestedNGF = ngf
}
}
}
}
log.Debugf("going to burn %v", requestedNGF)
if requestedNGF.Id == 0 {
// not found
log.Errorf("user requested burning %d, not found", req.Id)
res := secure.PacketBurnResponse{
Status: secure.BurnResponseNotFound,
}
err = enc.Encode(res)
if err != nil {
log.Errorf("could not send NotFound: %v", err)
}
return
}
// remove the file
err = os.Remove(requestedNGF.StorePath)
if err != nil {
log.Errorf("could not remove file %s: %v", requestedNGF.StorePath, err)
return
}
// remove the ngf from the list
for i, ngf := range ngfs {
if ngf.Id == requestedNGF.Id {
ngfs = append(ngfs[:i], ngfs[i+1:]...)
break
}
}
res := secure.PacketBurnResponse{
Status: secure.BurnResponseOK,
}
err = enc.Encode(res)
if err != nil {
log.Errorf("error sending PacketBurnResponse: %v", err)
return
}
log.Printf("burn complete")
return
default:
log.Errorf("bad operation") log.Errorf("bad operation")
return return
} }
} }