netgiv/client.go
Jason Fowler a1e3c205f9
add burn operation to the client, server, and protocol (#2)
* add burn operation to the client, server, and protocol

this provides a method for removing files from the server remotely without needing to restart the server
example use case for this is if your server is publicly accessible but you don't expose SSH publicly and you're transferring data between two cloud servers and don't want the data to be stored on the server any longer than it has to be

* updating documentation
2025-04-25 15:51:43 +09:30

243 lines
5.1 KiB
Go

package main
import (
"bufio"
"bytes"
"encoding/gob"
"errors"
"fmt"
"io"
"net"
"os"
"time"
log "github.com/sirupsen/logrus"
"github.com/dustin/go-humanize"
"github.com/tardisx/netgiv/secure"
)
type Client struct {
address string
port int
list bool
send bool
burnNum int
receiveNum int
authToken string
}
func (c *Client) Connect() error {
address := fmt.Sprintf("%s:%d", c.address, c.port)
d := net.Dialer{Timeout: 5 * time.Second}
conn, err := d.Dial("tcp", address)
if err != nil {
return fmt.Errorf("problem connecting to server, is it running?: %v", err)
}
defer conn.Close()
log.Debugf("established connection on %s", address)
tcpConn, ok := conn.(*net.TCPConn)
if !ok {
log.Fatal("could not assert")
}
sharedKey := secure.Handshake(tcpConn)
secureConnection := secure.SecureConnection{Conn: conn, SharedKey: sharedKey, Buffer: &bytes.Buffer{}}
enc := gob.NewEncoder(&secureConnection)
dec := gob.NewDecoder(&secureConnection)
switch {
case c.list:
log.Debugf("requesting file list")
err := c.connectToServer(secure.OperationTypeList, enc, dec)
if err != nil {
return fmt.Errorf("could not connect and auth: %v", err)
}
// now we expect to get stuff back until we don't
numFiles := 0
for {
listPacket := secure.PacketListData{}
err := dec.Decode(&listPacket)
if err == io.EOF {
break
}
if err != nil {
panic(err)
}
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")
case c.receiveNum >= 0:
log.Debugf("receiving file %d", c.receiveNum)
err := c.connectToServer(secure.OperationTypeReceive, enc, dec)
if err != nil {
return fmt.Errorf("could not connect and auth: %v", err)
}
req := secure.PacketReceiveDataStartRequest{
Id: uint32(c.receiveNum),
}
err = enc.Encode(req)
if err != nil {
panic(err)
}
// expect a response telling us if we can go ahead
res := secure.PacketReceiveDataStartResponse{}
err = dec.Decode(&res)
if err != nil {
panic(err)
}
switch res.Status {
case secure.ReceiveDataStartResponseOK:
for {
res := secure.PacketReceiveDataNext{}
err = dec.Decode(&res)
if err != nil {
panic(err)
}
os.Stdout.Write(res.Data[:res.Size])
if res.Last {
break
}
}
log.Debugf("finished")
case secure.ReceiveDataStartResponseNotFound:
log.Error("ngf not found")
default:
panic("unknown status")
}
conn.Close()
case c.send:
// send mode
err := c.connectToServer(secure.OperationTypeSend, enc, dec)
if err != nil {
return fmt.Errorf("could not connect and auth: %v", err)
}
data := secure.PacketSendDataStart{
Filename: "",
TotalSize: 0,
}
err = enc.Encode(data)
if err != nil {
panic(err)
}
nBytes, nChunks := int64(0), int64(0)
reader := bufio.NewReader(os.Stdin)
buf := make([]byte, 0, 1024)
for {
n, err := reader.Read(buf[:cap(buf)])
buf = buf[:n]
if n == 0 {
if err == nil {
continue
}
if err == io.EOF {
break
}
log.Fatal(err)
}
nChunks++
nBytes += int64(len(buf))
send := secure.PacketSendDataNext{
Size: 5000,
Data: buf,
}
err = enc.Encode(send)
// time.Sleep(time.Second)
if err != nil {
log.Fatal(err)
}
}
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)
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: ProtocolVersion,
AuthToken: c.authToken,
}
err := enc.Encode(startPacket)
if err != nil {
return fmt.Errorf("could not send start packet: %v", err)
}
// check the response is ok
response := secure.PacketStartResponse{}
err = dec.Decode(&response)
if err != nil {
return fmt.Errorf("could not receive start packet response: %v", err)
}
if response.Response == secure.PacketStartResponseEnumWrongProtocol {
log.Print("wrong protocol version")
return errors.New("protocol version mismatch")
}
if response.Response == secure.PacketStartResponseEnumBadAuthToken {
log.Print("bad authtoken")
return errors.New("bad authtoken")
}
return nil
}