Compare commits
31 Commits
Author | SHA1 | Date | |
---|---|---|---|
6956d7f2f6 | |||
1df08bff4c | |||
07b6f6b43c | |||
cf154d93b1 | |||
80f10a3949 | |||
|
a1e3c205f9 | ||
b2c6f313bc | |||
b9f4c25f39 | |||
531abdafde | |||
9f0d81e7f1 | |||
ed9d45d343 | |||
a7b0a5d113 | |||
91be76b82e | |||
95c4a1738b | |||
607466dd65 | |||
e3e80d450b | |||
e995d436c2 | |||
0330d2d17f | |||
58a078b9f9 | |||
3baccaccac | |||
74123021be | |||
80d23b514c | |||
1026cc5dd9 | |||
cb34fe2883 | |||
b7558e9d01 | |||
767d8be277 | |||
239a6d6804 | |||
1491ad9351 | |||
96de06e4e0 | |||
8f9eb05ddb | |||
c471f9f4cc |
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
.DS_Store
|
||||
dist/
|
||||
release/
|
50
.goreleaser.yaml
Normal file
50
.goreleaser.yaml
Normal file
@ -0,0 +1,50 @@
|
||||
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
13
.vscode/settings.json
vendored
@ -1,9 +1,20 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"authtoken",
|
||||
"Debugf",
|
||||
"Infof",
|
||||
"isatty",
|
||||
"netgiv",
|
||||
"pflag"
|
||||
"ngfs",
|
||||
"tardisx",
|
||||
"ttys"
|
||||
],
|
||||
"cSpell.ignoreWords": [
|
||||
"logrus",
|
||||
"mattn",
|
||||
"pflag",
|
||||
"sigchan",
|
||||
"sirupsen",
|
||||
"verysecretvaluehere"
|
||||
]
|
||||
}
|
42
CHANGELOG.md
Normal file
42
CHANGELOG.md
Normal file
@ -0,0 +1,42 @@
|
||||
# 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
|
||||
|
21
LICENSE.txt
Normal file
21
LICENSE.txt
Normal file
@ -0,0 +1,21 @@
|
||||
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
162
README.md
@ -1,3 +1,163 @@
|
||||
# netgiv
|
||||
|
||||
TBD
|
||||
## What is this?
|
||||
|
||||
`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
|
||||
|
@ -1,40 +0,0 @@
|
||||
#!/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/*" );
|
||||
}
|
58
client.go
58
client.go
@ -22,6 +22,7 @@ type Client struct {
|
||||
port int
|
||||
list bool
|
||||
send bool
|
||||
burnNum int
|
||||
receiveNum int
|
||||
authToken string
|
||||
}
|
||||
@ -50,7 +51,8 @@ func (c *Client) Connect() error {
|
||||
enc := gob.NewEncoder(&secureConnection)
|
||||
dec := gob.NewDecoder(&secureConnection)
|
||||
|
||||
if c.list {
|
||||
switch {
|
||||
case c.list:
|
||||
log.Debugf("requesting file list")
|
||||
|
||||
err := c.connectToServer(secure.OperationTypeList, enc, dec)
|
||||
@ -59,6 +61,7 @@ func (c *Client) Connect() error {
|
||||
}
|
||||
|
||||
// now we expect to get stuff back until we don't
|
||||
numFiles := 0
|
||||
for {
|
||||
listPacket := secure.PacketListData{}
|
||||
err := dec.Decode(&listPacket)
|
||||
@ -68,12 +71,13 @@ func (c *Client) Connect() error {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Printf("%d: %s (%s)\n", listPacket.Id, listPacket.Kind, humanize.Bytes(uint64(listPacket.FileSize)))
|
||||
fmt.Printf("%d: %s (%s) - %s\n", listPacket.Id, listPacket.Kind, humanize.Bytes(uint64(listPacket.FileSize)), listPacket.Timestamp)
|
||||
numFiles++
|
||||
}
|
||||
fmt.Printf("total: %d files\n", numFiles)
|
||||
conn.Close()
|
||||
log.Debugf("done listing")
|
||||
|
||||
} else if c.receiveNum >= 0 {
|
||||
case c.receiveNum >= 0:
|
||||
log.Debugf("receiving file %d", c.receiveNum)
|
||||
|
||||
err := c.connectToServer(secure.OperationTypeReceive, enc, dec)
|
||||
@ -95,7 +99,8 @@ func (c *Client) Connect() error {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if res.Status == secure.ReceiveDataStartResponseOK {
|
||||
switch res.Status {
|
||||
case secure.ReceiveDataStartResponseOK:
|
||||
for {
|
||||
res := secure.PacketReceiveDataNext{}
|
||||
err = dec.Decode(&res)
|
||||
@ -108,14 +113,14 @@ func (c *Client) Connect() error {
|
||||
}
|
||||
}
|
||||
log.Debugf("finished")
|
||||
} else if res.Status == secure.ReceiveDataStartResponseNotFound {
|
||||
case secure.ReceiveDataStartResponseNotFound:
|
||||
log.Error("ngf not found")
|
||||
} else {
|
||||
default:
|
||||
panic("unknown status")
|
||||
}
|
||||
|
||||
conn.Close()
|
||||
} else if c.send {
|
||||
case c.send:
|
||||
// send mode
|
||||
|
||||
err := c.connectToServer(secure.OperationTypeSend, enc, dec)
|
||||
@ -166,21 +171,50 @@ func (c *Client) Connect() error {
|
||||
log.Debugf("Sent %s in %d chunks", humanize.Bytes(uint64(nBytes)), nChunks)
|
||||
|
||||
conn.Close()
|
||||
case c.burnNum >= 0:
|
||||
log.Debugf("burning file %d", c.burnNum)
|
||||
|
||||
} else {
|
||||
err := c.connectToServer(secure.OperationTypeBurn, enc, dec)
|
||||
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")
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (c *Client) connectToServer(op secure.OperationTypeEnum, enc *gob.Encoder, dec *gob.Decoder) error {
|
||||
|
||||
// list mode
|
||||
startPacket := secure.PacketStartRequest{
|
||||
OperationType: op,
|
||||
ClientName: "",
|
||||
ProtocolVersion: "1.0",
|
||||
ProtocolVersion: ProtocolVersion,
|
||||
AuthToken: c.authToken,
|
||||
}
|
||||
err := enc.Encode(startPacket)
|
||||
|
85
main.go
85
main.go
@ -13,39 +13,37 @@ import (
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var CurrentVersion = "v0.0.3"
|
||||
const ProtocolVersion = "1.1"
|
||||
|
||||
type PasteValue struct {
|
||||
PasteRequired bool
|
||||
PasteNumber uint
|
||||
type ListValue struct {
|
||||
Required bool
|
||||
Number uint
|
||||
}
|
||||
|
||||
func (v *PasteValue) String() string {
|
||||
if v.PasteRequired {
|
||||
return fmt.Sprintf("YES: %d", v.PasteNumber)
|
||||
func (v *ListValue) String() string {
|
||||
if v.Required {
|
||||
return fmt.Sprintf("YES: %d", v.Number)
|
||||
}
|
||||
return "0"
|
||||
}
|
||||
|
||||
func (v *PasteValue) Set(s string) error {
|
||||
v.PasteRequired = true
|
||||
func (v *ListValue) Set(s string) error {
|
||||
v.Required = true
|
||||
num, err := strconv.ParseUint(s, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v.PasteNumber = uint(num)
|
||||
v.Number = uint(num)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *PasteValue) Type() string {
|
||||
func (v *ListValue) Type() string {
|
||||
return "int"
|
||||
|
||||
}
|
||||
|
||||
func getAuthTokenFromTerminal() string {
|
||||
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0755)
|
||||
|
||||
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0o755)
|
||||
if err != nil {
|
||||
log.Printf("cannot open /dev/tty to read authtoken: %v", err)
|
||||
return ""
|
||||
@ -57,7 +55,9 @@ func getAuthTokenFromTerminal() string {
|
||||
log.Printf("cannot set /dev/tty to raw mode: %v", err)
|
||||
return ""
|
||||
}
|
||||
defer term.Restore(fd, oldState)
|
||||
defer func() {
|
||||
_ = term.Restore(fd, oldState)
|
||||
}()
|
||||
|
||||
t := term.NewTerminal(tty, "")
|
||||
pass, err := t.ReadPassword("Enter auth token: ")
|
||||
@ -69,17 +69,27 @@ func getAuthTokenFromTerminal() string {
|
||||
return pass
|
||||
}
|
||||
|
||||
var (
|
||||
version = "dev"
|
||||
commit = "none"
|
||||
date = "unknown"
|
||||
)
|
||||
|
||||
func main() {
|
||||
isServer := flag.Bool("server", false, "Run netgiv in server mode")
|
||||
|
||||
// client mode flags
|
||||
isList := flag.BoolP("list", "l", false, "Returns a list of current items on the server")
|
||||
isSend := flag.BoolP("copy", "c", false, "sending stdin to netgiv server (copy)")
|
||||
isSend := flag.BoolP("copy", "c", false, "send stdin to netgiv server (copy)")
|
||||
|
||||
pasteFlag := PasteValue{}
|
||||
flag.VarP(&pasteFlag, "paste", "p", "receive from netgiv server to stdout (paste), with optional number (see --list)")
|
||||
pasteFlag := ListValue{}
|
||||
flag.VarP(&pasteFlag, "paste", "p", "receive from netgiv server to stdout (paste), with optional id (see --list)")
|
||||
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")
|
||||
flag.String("address", "", "IP address/hostname of the netgiv server")
|
||||
|
||||
@ -89,14 +99,27 @@ func main() {
|
||||
flag.String("authtoken", "", "Authentication token")
|
||||
flag.Int("port", 0, "Port")
|
||||
|
||||
versionFlag := flag.BoolP("version", "v", false, "show version and exit")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
receiveNum := int(pasteFlag.PasteNumber)
|
||||
if !pasteFlag.PasteRequired {
|
||||
if versionFlag != nil && *versionFlag {
|
||||
fmt.Print(versionInfo(true))
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
receiveNum := int(pasteFlag.Number)
|
||||
if !pasteFlag.Required {
|
||||
receiveNum = -1
|
||||
}
|
||||
|
||||
viper.AddConfigPath("$HOME/.netgiv/") // call multiple times to add many search paths
|
||||
burnNum := int(burnFlag.Number)
|
||||
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.SetDefault("port", 4512)
|
||||
@ -110,11 +133,10 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
flag.Parse()
|
||||
viper.BindPFlags(flag.CommandLine)
|
||||
_ = viper.BindPFlags(flag.CommandLine)
|
||||
|
||||
viper.SetEnvPrefix("NETGIV")
|
||||
viper.BindEnv("authtoken")
|
||||
_ = viper.BindEnv("authtoken")
|
||||
|
||||
// pull the various things into local variables
|
||||
port := viper.GetInt("port") // retrieve value from viper
|
||||
@ -168,11 +190,12 @@ environment variable. This may be preferable in some environments.
|
||||
log.Fatal("an address must be provided on the command line, or configuration")
|
||||
}
|
||||
|
||||
log.Debugf("protocol version: %s", ProtocolVersion)
|
||||
if *isServer {
|
||||
s := Server{port: port, authToken: authtoken}
|
||||
s.Run()
|
||||
} else {
|
||||
if !*isList && !*isSend && receiveNum == -1 {
|
||||
if !*isList && !*isSend && burnNum == -1 && receiveNum == -1 {
|
||||
// try to work out the intent based on whether or not stdin/stdout
|
||||
// are ttys
|
||||
stdinTTY := isatty.IsTerminal(os.Stdin.Fd())
|
||||
@ -191,10 +214,20 @@ environment variable. This may be preferable in some environments.
|
||||
|
||||
}
|
||||
|
||||
c := Client{port: port, address: address, list: *isList, send: *isSend, receiveNum: receiveNum, authToken: authtoken}
|
||||
c := Client{port: port, address: address, list: *isList, send: *isSend, burnNum: burnNum, receiveNum: receiveNum, authToken: authtoken}
|
||||
err := c.Connect()
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
@ -128,7 +129,7 @@ func (s *SecureConnection) Write(p []byte) (int, error) {
|
||||
var nonce [24]byte
|
||||
|
||||
// Create a new nonce for each message sent
|
||||
rand.Read(nonce[:])
|
||||
_, _ = rand.Read(nonce[:])
|
||||
|
||||
encryptedMessage := box.SealAfterPrecomputation(nil, p, &nonce, s.SharedKey)
|
||||
sm := SecureMessage{Msg: encryptedMessage, Nonce: nonce}
|
||||
@ -144,10 +145,10 @@ func Handshake(conn *net.TCPConn) *[32]byte {
|
||||
|
||||
publicKey, privateKey, _ := box.GenerateKey(rand.Reader)
|
||||
|
||||
conn.Write(publicKey[:])
|
||||
_, _ = conn.Write(publicKey[:])
|
||||
|
||||
peerKeyArray := make([]byte, 32)
|
||||
conn.Read(peerKeyArray)
|
||||
_, _ = conn.Read(peerKeyArray)
|
||||
copy(peerKey[:], peerKeyArray)
|
||||
|
||||
box.Precompute(&sharedKey, &peerKey, privateKey)
|
||||
@ -161,10 +162,11 @@ const (
|
||||
OperationTypeSend OperationTypeEnum = iota
|
||||
OperationTypeList
|
||||
OperationTypeReceive
|
||||
OperationTypeBurn
|
||||
)
|
||||
|
||||
// PacketStartRequest is sent from the client to the server at the beginning
|
||||
// to authenticate and annonce the requested particular operation
|
||||
// to authenticate and announce the requested particular operation
|
||||
type PacketStartRequest struct {
|
||||
OperationType OperationTypeEnum
|
||||
ClientName string
|
||||
@ -226,8 +228,26 @@ type PacketReceiveDataNext struct {
|
||||
}
|
||||
|
||||
type PacketListData struct {
|
||||
Id uint32
|
||||
Filename string
|
||||
FileSize uint32
|
||||
Kind string
|
||||
Id uint32
|
||||
Filename string
|
||||
FileSize uint32
|
||||
Timestamp time.Time
|
||||
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
|
||||
)
|
||||
|
@ -2,27 +2,19 @@ package secure
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestPacketBasic(t *testing.T) {
|
||||
// pSrc := PacketStart{
|
||||
// OperationType: 0,
|
||||
// ClientName: "test1",
|
||||
// ProtocolVersion: "test2",
|
||||
// AuthToken: "test3",
|
||||
// }
|
||||
// pDst := PacketStart{}
|
||||
|
||||
// buf := bytes.Buffer{}
|
||||
|
||||
func TestBasic(t *testing.T) {
|
||||
srcConn, dstConn := net.Pipe()
|
||||
|
||||
srcSecConn := SecureConnection{
|
||||
Conn: srcConn,
|
||||
SharedKey: &[32]byte{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
|
||||
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,
|
||||
@ -32,7 +24,8 @@ func TestPacketBasic(t *testing.T) {
|
||||
|
||||
dstSecConn := SecureConnection{
|
||||
Conn: dstConn,
|
||||
SharedKey: &[32]byte{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
|
||||
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,
|
||||
@ -54,7 +47,7 @@ func TestPacketBasic(t *testing.T) {
|
||||
for _, b := range testData {
|
||||
|
||||
go func() {
|
||||
srcSecConn.Write(b)
|
||||
_, _ = srcSecConn.Write(b)
|
||||
}()
|
||||
|
||||
time.Sleep(time.Second)
|
||||
@ -71,15 +64,16 @@ func TestPacketBasic(t *testing.T) {
|
||||
t.Errorf("%v not equal to %v", out[:n], b)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func BenchmarkPPS(b *testing.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,
|
||||
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,
|
||||
@ -89,7 +83,66 @@ func BenchmarkPPS(b *testing.B) {
|
||||
|
||||
dstSecConn := SecureConnection{
|
||||
Conn: dstConn,
|
||||
SharedKey: &[32]byte{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
|
||||
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) {
|
||||
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,
|
||||
@ -107,12 +160,11 @@ func BenchmarkPPS(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
|
||||
go func() {
|
||||
srcSecConn.Write(testdata)
|
||||
_, _ = srcSecConn.Write(testdata)
|
||||
}()
|
||||
|
||||
out := make([]byte, 16384)
|
||||
n, err := dstSecConn.Read(out)
|
||||
|
||||
if err != nil {
|
||||
b.Errorf("got error %v", err)
|
||||
}
|
||||
@ -123,5 +175,4 @@ func BenchmarkPPS(b *testing.B) {
|
||||
b.Errorf("%v not equal to %v", out[:n], testdata)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
113
server.go
113
server.go
@ -34,10 +34,15 @@ type NGF struct {
|
||||
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 globalId uint32
|
||||
|
||||
func (s *Server) Run() {
|
||||
log.Info(versionInfo(false))
|
||||
log.Infof("starting server on :%d", s.port)
|
||||
address := fmt.Sprintf(":%d", s.port)
|
||||
networkAddress, _ := net.ResolveTCPAddr("tcp", address)
|
||||
@ -58,7 +63,7 @@ func (s *Server) Run() {
|
||||
log.Printf("removing file: %s", ngf.StorePath)
|
||||
err := os.Remove(ngf.StorePath)
|
||||
if err != nil {
|
||||
log.Printf("could not remove %s: %v", ngf.StorePath, err)
|
||||
log.Errorf("could not remove %s: %v", ngf.StorePath, err)
|
||||
}
|
||||
}
|
||||
os.Exit(0)
|
||||
@ -68,7 +73,6 @@ func (s *Server) Run() {
|
||||
|
||||
for {
|
||||
conn, err := listener.AcceptTCP()
|
||||
|
||||
if err != nil {
|
||||
fmt.Print(err)
|
||||
}
|
||||
@ -80,7 +84,7 @@ func (s *Server) Run() {
|
||||
func (s *Server) handleConnection(conn *net.TCPConn) {
|
||||
defer conn.Close()
|
||||
|
||||
conn.SetDeadline(time.Now().Add(time.Second * 5))
|
||||
_ = conn.SetDeadline(time.Now().Add(time.Second * 5))
|
||||
|
||||
sharedKey := secure.Handshake(conn)
|
||||
secureConnection := secure.SecureConnection{Conn: conn, SharedKey: sharedKey, Buffer: &bytes.Buffer{}}
|
||||
@ -105,30 +109,31 @@ func (s *Server) handleConnection(conn *net.TCPConn) {
|
||||
return
|
||||
}
|
||||
|
||||
// tell teh client the dealio
|
||||
// tell the client if the connection is ok.
|
||||
startResponse := secure.PacketStartResponse{}
|
||||
|
||||
if start.ProtocolVersion != "1.0" {
|
||||
if start.ProtocolVersion != ProtocolVersion {
|
||||
log.Errorf("bad protocol version")
|
||||
startResponse.Response = secure.PacketStartResponseEnumWrongProtocol
|
||||
enc.Encode(startResponse)
|
||||
_ = enc.Encode(startResponse)
|
||||
return
|
||||
}
|
||||
|
||||
if start.AuthToken != s.authToken {
|
||||
log.Errorf("bad authtoken")
|
||||
startResponse.Response = secure.PacketStartResponseEnumBadAuthToken
|
||||
enc.Encode(startResponse)
|
||||
_ = enc.Encode(startResponse)
|
||||
return
|
||||
}
|
||||
|
||||
// otherwise we are good to continue, tell the client that
|
||||
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))
|
||||
|
||||
if start.OperationType == secure.OperationTypeSend {
|
||||
switch start.OperationType {
|
||||
case secure.OperationTypeSend:
|
||||
log.Debugf("file incoming")
|
||||
|
||||
sendStart := secure.PacketSendDataStart{}
|
||||
@ -160,7 +165,7 @@ func (s *Server) handleConnection(conn *net.TCPConn) {
|
||||
sendData := secure.PacketSendDataNext{}
|
||||
determinedKind := false
|
||||
for {
|
||||
conn.SetDeadline(time.Now().Add(time.Second * 5))
|
||||
_ = conn.SetDeadline(time.Now().Add(time.Second * 5))
|
||||
err = dec.Decode(&sendData)
|
||||
if err == io.EOF {
|
||||
break
|
||||
@ -190,7 +195,7 @@ func (s *Server) handleConnection(conn *net.TCPConn) {
|
||||
determinedKind = true
|
||||
}
|
||||
|
||||
file.Write(sendData.Data)
|
||||
_, _ = file.Write(sendData.Data)
|
||||
}
|
||||
info, err := file.Stat()
|
||||
if err != nil {
|
||||
@ -204,13 +209,13 @@ func (s *Server) handleConnection(conn *net.TCPConn) {
|
||||
log.Printf("done receiving file: %v", ngf)
|
||||
|
||||
return
|
||||
} else if start.OperationType == secure.OperationTypeReceive {
|
||||
case secure.OperationTypeReceive:
|
||||
log.Printf("client requesting file receive")
|
||||
// wait for them to send the request
|
||||
req := secure.PacketReceiveDataStartRequest{}
|
||||
err := dec.Decode(&req)
|
||||
if err != nil {
|
||||
log.Printf("error expecting PacketReceiveDataStartRequest: %v", err)
|
||||
log.Errorf("error expecting PacketReceiveDataStartRequest: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -242,7 +247,7 @@ func (s *Server) handleConnection(conn *net.TCPConn) {
|
||||
}
|
||||
err = enc.Encode(res)
|
||||
if err != nil {
|
||||
log.Printf("could not send NotFound: %v", err)
|
||||
log.Errorf("could not send NotFound: %v", err)
|
||||
}
|
||||
|
||||
return
|
||||
@ -297,8 +302,7 @@ func (s *Server) handleConnection(conn *net.TCPConn) {
|
||||
}
|
||||
log.Printf("sending done")
|
||||
return
|
||||
|
||||
} else if start.OperationType == secure.OperationTypeList {
|
||||
case secure.OperationTypeList:
|
||||
log.Debugf("client requesting file list")
|
||||
|
||||
for _, ngf := range ngfs {
|
||||
@ -307,15 +311,84 @@ func (s *Server) handleConnection(conn *net.TCPConn) {
|
||||
p.Kind = ngf.Kind
|
||||
p.Id = ngf.Id
|
||||
p.Filename = ngf.Filename
|
||||
enc.Encode(p)
|
||||
p.Timestamp = ngf.Timestamp
|
||||
_ = enc.Encode(p)
|
||||
}
|
||||
log.Debugf("done sending list, closing connection")
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
} else {
|
||||
log.Debugf("The client asked for %v to be burned", req)
|
||||
|
||||
// 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")
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user