From 45ec939076353c978fe2e9cb81501bc182d71b79 Mon Sep 17 00:00:00 2001 From: Sergey Matveev Date: Thu, 7 Jan 2021 00:25:03 +0300 Subject: [PATCH] nncp-exec -nocompress/-use-tmp options --- doc/cmds.texi | 10 +- doc/news.ru.texi | 5 + doc/news.texi | 5 + doc/pkt.texi | 3 +- src/cmd/nncp-exec/main.go | 4 + src/cmd/nncp-pkt/main.go | 4 +- src/pkt.go | 9 +- src/toss.go | 14 ++- src/toss_test.go | 3 +- src/tx.go | 189 ++++++++++++++++++++++++++------------ 10 files changed, 174 insertions(+), 72 deletions(-) diff --git a/doc/cmds.texi b/doc/cmds.texi index 2fb0374..75835ac 100644 --- a/doc/cmds.texi +++ b/doc/cmds.texi @@ -262,12 +262,14 @@ uucp stream tcp6 nowait nncpuser /usr/local/bin/nncp-daemon nncp-daemon -quiet - @section nncp-exec @example -$ nncp-exec [options] NODE HANDLE [ARG0 ARG1 ...] +$ nncp-exec [options] [-use-tmp] [-nocompress] NODE HANDLE [ARG0 ARG1 ...] @end example Send execution command to @option{NODE} for specified @option{HANDLE}. -Body is read from stdin and compressed. After receiving, remote side -will execute specified @ref{CfgExec, handle} command with @option{ARG*} +Body is read from stdin (either into memory, or into encrypted temporary +file if @option{-use-tmp} is specified) and compressed (unless +@option{-nocompress} is specified}. After receiving, remote side will +execute specified @ref{CfgExec, handle} command with @option{ARG*} appended and decompressed body fed to command's stdin. For example, if remote side has following configuration file for your @@ -295,7 +297,7 @@ If @ref{CfgNotify, notification} is enabled on the remote side for exec handles, then it will sent simple letter after successful command execution with its output in message body. -@strong{Pay attention} that packet generated with this command can not +@strong{Pay attention} that packet generated with this command won't be be chunked. @node nncp-file diff --git a/doc/news.ru.texi b/doc/news.ru.texi index f9ff365..10c3f51 100644 --- a/doc/news.ru.texi +++ b/doc/news.ru.texi @@ -12,6 +12,11 @@ @item У команды @command{nncp-rm} появились @option{-dryrun} и @option{-older} опции. +@item +У команды @command{nncp-exec} появились @option{-use-tmp} и +@option{-nocompress} опции. Несжатые пакеты не совместимы с предыдущими +версиями NNCP. + @item Обновлены зависимые библиотеки. diff --git a/doc/news.texi b/doc/news.texi index 30f1c99..cb232e4 100644 --- a/doc/news.texi +++ b/doc/news.texi @@ -14,6 +14,11 @@ Bugfixes in @command{nncp-call(er)}/@command{nncp-daemon}, @item @command{nncp-rm} has @option{-dryrun} and @option{-older} options now. +@item +@command{nncp-exec} has @option{-use-tmp} and @option{-nocompress} +options now. Uncompressed packets are not compatible with previous NNCP +versions. + @item Updated dependencies. diff --git a/doc/pkt.texi b/doc/pkt.texi index 33c2f28..93841e1 100644 --- a/doc/pkt.texi +++ b/doc/pkt.texi @@ -31,7 +31,7 @@ drive. @verb{|N N C P P 0x00 0x00 0x03|} @item Payload type @tab unsigned integer @tab - 0 (file), 1 (freq), 2 (exec), 3 (transition) + 0 (file), 1 (freq), 2 (exec), 3 (transition), 4 (exec-fat) @item Niceness @tab unsigned integer @tab 1-255, preferred packet @ref{Niceness, niceness} level @@ -61,6 +61,7 @@ Depending on the packet's type, payload could store: @item Destination path for freq @item @url{https://facebook.github.io/zstd/, Zstandard} compressed exec body @item Whole encrypted packet we need to relay on +@item Uncompressed exec body @end itemize Also depending on packet's type, niceness level means: diff --git a/src/cmd/nncp-exec/main.go b/src/cmd/nncp-exec/main.go index d0cb537..fe559ba 100644 --- a/src/cmd/nncp-exec/main.go +++ b/src/cmd/nncp-exec/main.go @@ -37,6 +37,8 @@ func usage() { func main() { var ( + useTmp = flag.Bool("use-tmp", false, "Use temporary file, instead of memory buffer") + noCompress = flag.Bool("nocompress", false, "Do not compress input data") cfgPath = flag.String("cfg", nncp.DefaultCfgPath, "Path to configuration file") niceRaw = flag.String("nice", nncp.NicenessFmt(nncp.DefaultNiceExec), "Outbound packet niceness") replyNiceRaw = flag.String("replynice", nncp.NicenessFmt(nncp.DefaultNiceFile), "Possible reply packet niceness") @@ -106,6 +108,8 @@ func main() { flag.Args()[2:], bufio.NewReader(os.Stdin), int64(*minSize)*1024, + *useTmp, + *noCompress, ); err != nil { log.Fatalln(err) } diff --git a/src/cmd/nncp-pkt/main.go b/src/cmd/nncp-pkt/main.go index 0e4d429..145b30b 100644 --- a/src/cmd/nncp-pkt/main.go +++ b/src/cmd/nncp-pkt/main.go @@ -105,12 +105,14 @@ func main() { payloadType = "file request" case nncp.PktTypeExec: payloadType = "exec" + case nncp.PktTypeExecFat: + payloadType = "exec uncompressed" case nncp.PktTypeTrns: payloadType = "transitional" } var path string switch pkt.Type { - case nncp.PktTypeExec: + case nncp.PktTypeExec, nncp.PktTypeExecFat: path = string(bytes.Replace( pkt.Path[:pkt.PathLen], []byte{0}, diff --git a/src/pkt.go b/src/pkt.go index 5fdaad7..4dea1bf 100644 --- a/src/pkt.go +++ b/src/pkt.go @@ -40,10 +40,11 @@ const ( EncBlkSize = 128 * (1 << 10) KDFXOFSize = chacha20poly1305.KeySize * 2 - PktTypeFile PktType = iota - PktTypeFreq PktType = iota - PktTypeExec PktType = iota - PktTypeTrns PktType = iota + PktTypeFile PktType = iota + PktTypeFreq PktType = iota + PktTypeExec PktType = iota + PktTypeTrns PktType = iota + PktTypeExecFat PktType = iota MaxPathSize = 1<<8 - 1 diff --git a/src/toss.go b/src/toss.go index ecc60ed..f70ffd3 100644 --- a/src/toss.go +++ b/src/toss.go @@ -126,7 +126,7 @@ func (ctx *Ctx) Toss( sds["size"] = pktSize ctx.LogD("rx", sds, "taken") switch pkt.Type { - case PktTypeExec: + case PktTypeExec, PktTypeExecFat: if noExec { goto Closing } @@ -148,8 +148,10 @@ func (ctx *Ctx) Toss( isBad = true goto Closing } - if err = decompressor.Reset(pipeR); err != nil { - log.Fatalln(err) + if pkt.Type == PktTypeExec { + if err = decompressor.Reset(pipeR); err != nil { + log.Fatalln(err) + } } if !dryRun { cmd := exec.Command( @@ -162,7 +164,11 @@ func (ctx *Ctx) Toss( "NNCP_SENDER="+sender.Id.String(), "NNCP_NICE="+strconv.Itoa(int(pkt.Nice)), ) - cmd.Stdin = decompressor + if pkt.Type == PktTypeExec { + cmd.Stdin = decompressor + } else { + cmd.Stdin = pipeR + } output, err := cmd.Output() if err != nil { ctx.LogE("rx", sds, err, "handle") diff --git a/src/toss_test.go b/src/toss_test.go index d269141..8a16dd9 100644 --- a/src/toss_test.go +++ b/src/toss_test.go @@ -99,6 +99,8 @@ func TestTossExec(t *testing.T) { []string{"arg0", "arg1"}, strings.NewReader("BODY\n"), 1<<15, + false, + false, ); err != nil { t.Error(err) return false @@ -449,7 +451,6 @@ func TestTossTrns(t *testing.T) { Magic: MagicNNCPPv3, Type: PktTypeTrns, PathLen: blake2b.Size256, - Path: new([MaxPathSize]byte), } copy(pktTrans.Path[:], nodeOur.Id[:]) var dst bytes.Buffer diff --git a/src/tx.go b/src/tx.go index d00ba61..3352f86 100644 --- a/src/tx.go +++ b/src/tx.go @@ -131,49 +131,63 @@ type DummyCloser struct{} func (dc DummyCloser) Close() error { return nil } -func prepareTxFile(srcPath string) (reader io.Reader, closer io.Closer, fileSize int64, archived bool, rerr error) { - if srcPath == "-" { - // Read content from stdin, saving to temporary file, encrypting - // on the fly - src, err := ioutil.TempFile("", "nncp-file") - if err != nil { - rerr = err - return - } - os.Remove(src.Name()) // #nosec G104 - tmpW := bufio.NewWriter(src) - tmpKey := make([]byte, chacha20poly1305.KeySize) - if _, rerr = rand.Read(tmpKey[:]); rerr != nil { - return - } - aead, err := chacha20poly1305.New(tmpKey) - if err != nil { - rerr = err - return - } - nonce := make([]byte, aead.NonceSize()) - written, err := aeadProcess(aead, nonce, true, bufio.NewReader(os.Stdin), tmpW) - if err != nil { - rerr = err - return - } - fileSize = int64(written) - if err = tmpW.Flush(); err != nil { - rerr = err - return - } - if _, err = src.Seek(0, io.SeekStart); err != nil { - rerr = err - return +func throughTmpFile(r io.Reader) ( + reader io.Reader, + closer io.Closer, + fileSize int64, + rerr error, +) { + src, err := ioutil.TempFile("", "nncp-file") + if err != nil { + rerr = err + return + } + os.Remove(src.Name()) // #nosec G104 + tmpW := bufio.NewWriter(src) + tmpKey := make([]byte, chacha20poly1305.KeySize) + if _, rerr = rand.Read(tmpKey[:]); rerr != nil { + return + } + aead, err := chacha20poly1305.New(tmpKey) + if err != nil { + rerr = err + return + } + nonce := make([]byte, aead.NonceSize()) + written, err := aeadProcess(aead, nonce, true, r, tmpW) + if err != nil { + rerr = err + return + } + fileSize = int64(written) + if err = tmpW.Flush(); err != nil { + rerr = err + return + } + if _, err = src.Seek(0, io.SeekStart); err != nil { + rerr = err + return + } + r, w := io.Pipe() + go func() { + if _, err := aeadProcess(aead, nonce, false, bufio.NewReader(src), w); err != nil { + w.CloseWithError(err) // #nosec G104 } - r, w := io.Pipe() - go func() { - if _, err := aeadProcess(aead, nonce, false, bufio.NewReader(src), w); err != nil { - w.CloseWithError(err) // #nosec G104 - } - }() - reader = r - closer = src + }() + reader = r + closer = src + return +} + +func prepareTxFile(srcPath string) ( + reader io.Reader, + closer io.Closer, + fileSize int64, + archived bool, + rerr error, +) { + if srcPath == "-" { + reader, closer, fileSize, rerr = throughTmpFile(bufio.NewReader(os.Stdin)) return } @@ -481,33 +495,94 @@ func (ctx *Ctx) TxExec( args []string, in io.Reader, minSize int64, + useTmp bool, + noCompress bool, ) error { path := make([][]byte, 0, 1+len(args)) path = append(path, []byte(handle)) for _, arg := range args { path = append(path, []byte(arg)) } - pkt, err := NewPkt(PktTypeExec, replyNice, bytes.Join(path, []byte{0})) - if err != nil { - return err + pktType := PktTypeExec + if noCompress { + pktType = PktTypeExecFat } - var compressed bytes.Buffer - compressor, err := zstd.NewWriter( - &compressed, - zstd.WithEncoderLevel(zstd.SpeedDefault), - ) + pkt, err := NewPkt(pktType, replyNice, bytes.Join(path, []byte{0})) if err != nil { return err } - if _, err = io.Copy(compressor, in); err != nil { - compressor.Close() // #nosec G104 - return err + var size int64 + + if !noCompress && !useTmp { + var compressed bytes.Buffer + compressor, err := zstd.NewWriter( + &compressed, + zstd.WithEncoderLevel(zstd.SpeedDefault), + ) + if err != nil { + return err + } + if _, err = io.Copy(compressor, in); err != nil { + compressor.Close() // #nosec G104 + return err + } + if err = compressor.Close(); err != nil { + return err + } + size = int64(compressed.Len()) + _, err = ctx.Tx(node, pkt, nice, size, minSize, &compressed, handle) } - if err = compressor.Close(); err != nil { - return err + if noCompress && !useTmp { + var data bytes.Buffer + if _, err = io.Copy(&data, in); err != nil { + return err + } + size = int64(data.Len()) + _, err = ctx.Tx(node, pkt, nice, size, minSize, &data, handle) } - size := int64(compressed.Len()) - _, err = ctx.Tx(node, pkt, nice, size, minSize, &compressed, handle) + if !noCompress && useTmp { + r, w := io.Pipe() + compressor, err := zstd.NewWriter(w, zstd.WithEncoderLevel(zstd.SpeedDefault)) + if err != nil { + return err + } + copyErr := make(chan error) + go func() { + _, err := io.Copy(compressor, in) + if err != nil { + compressor.Close() // #nosec G104 + copyErr <- err + } + err = compressor.Close() + w.Close() + copyErr <- err + }() + tmpReader, closer, fileSize, err := throughTmpFile(r) + if closer != nil { + defer closer.Close() + } + if err != nil { + return err + } + err = <-copyErr + if err != nil { + return err + } + size = fileSize + _, err = ctx.Tx(node, pkt, nice, size, minSize, tmpReader, handle) + } + if noCompress && useTmp { + tmpReader, closer, fileSize, err := throughTmpFile(in) + if closer != nil { + defer closer.Close() + } + if err != nil { + return err + } + size = fileSize + _, err = ctx.Tx(node, pkt, nice, size, minSize, tmpReader, handle) + } + sds := SDS{ "type": "exec", "node": node.Id, -- 2.44.0