]> Cypherpunks.ru repositories - nncp.git/commitdiff
exec notification, freq.maxsize, paxed directories v5.1.0
authorSergey Matveev <stargrave@stargrave.org>
Sun, 24 Nov 2019 15:12:07 +0000 (18:12 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Sun, 24 Nov 2019 15:12:07 +0000 (18:12 +0300)
35 files changed:
Makefile
VERSION
doc/building.texi
doc/bundles.texi
doc/cfg.texi
doc/cmds.texi
doc/download.texi
doc/integrity.texi
doc/news.ru.texi
doc/news.texi
doc/thanks.texi
makedist.sh
ports/nncp/Makefile
ports/nncp/files/pkg-message.in
src/cfg.go
src/cmd/nncp-bundle/main.go
src/cmd/nncp-cfgenc/main.go
src/cmd/nncp-cfgnew/main.go
src/cmd/nncp-file/main.go
src/cmd/nncp-freq/main.go
src/cmd/nncp-pkt/main.go
src/cmd/nncp-reass/main.go
src/cmd/nncp-xfer/main.go
src/ctx.go
src/eblob.go
src/go.mod
src/jobs.go
src/node.go
src/pkt.go
src/pkt_test.go
src/sp.go
src/toss.go
src/toss_test.go
src/tx.go
src/tx_test.go

index 46ddcf098f5fa750c69ecf538eee0b1cca063dc7..fd65c2511333673266d6a21fc4f2cba16be61d5c 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -49,9 +49,10 @@ all: $(ALL)
 
 $(ALL):
        mkdir -p $(BIN)
-       cd $(SRC) ; GOPATH=$(GOPATH) $(GO) build -ldflags "$(LDFLAGS)" \
+       cd $(SRC) ; GOPATH=$(GOPATH) $(GO) build \
+               -o $(BIN)/$$(basename $@) \
+               -ldflags "$(LDFLAGS)" \
                $(MOD)/cmd/$$(basename $@)
-       mv $(SRC)/$$(basename $@) $(BIN)
 
 test:
        cd $(SRC) ; GOPATH=$(GOPATH) $(GO) test -failfast $(MOD)/...
diff --git a/VERSION b/VERSION
index 0062ac971805f7b700058db4bb0f5c5b771dda76..831446cbd27a6de403344b21c9fa93a25357f43d 100644 (file)
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-5.0.0
+5.1.0
index 1489294dcf8fd1f4d593d0aec3344a08bc107649..30493d116f9bf00ab66aaf56c91ae36de50d774c 100644 (file)
@@ -11,16 +11,16 @@ Make sure that Go is installed. For example to install it from packages:
 @end table
 
 @verbatim
-$ [fetch|wget] http://www.nncpgo.org/download/nncp-5.0.0.tar.xz
-$ [fetch|wget] http://www.nncpgo.org/download/nncp-5.0.0.tar.xz.sig
-$ gpg --verify nncp-5.0.0.tar.xz.sig nncp-5.0.0.tar.xz
-$ xz --decompress --stdout nncp-5.0.0.tar.xz | tar xf -
-$ make -C nncp-5.0.0 all
+$ [fetch|wget] http://www.nncpgo.org/download/nncp-5.1.0.tar.xz
+$ [fetch|wget] http://www.nncpgo.org/download/nncp-5.1.0.tar.xz.sig
+$ gpg --verify nncp-5.1.0.tar.xz.sig nncp-5.1.0.tar.xz
+$ xz --decompress --stdout nncp-5.1.0.tar.xz | tar xf -
+$ make -C nncp-5.1.0 all
 @end verbatim
 
 There is @command{install} make-target respecting @env{DESTDIR}. It will
 install binaries and info-documentation:
 
 @verbatim
-# make -C nncp-5.0.0 install PREFIX=/usr/local
+# make -C nncp-5.1.0 install PREFIX=/usr/local
 @end verbatim
index 8330cdf2565534bd480d28abf8e70ddb4b444799..21b31046a1ae1c7d1d0aec48f6f5f76757b86c4c 100644 (file)
@@ -48,8 +48,8 @@ can contain mix of various recipients.
 
 @end itemize
 
-Technically bundle is valid POSIX.1-2001 (pax)
-@url{http://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html, tar archive},
+Technically bundle is valid POSIX.1-2001
+@url{https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html#tag_20_92_13_01, pax archive}
 with directory/files hierarchy identical to that is used in
 @ref{nncp-xfer}: @file{NNCP/RECIPIENT/SENDER/PACKET}. So bundle can also
 be created by manual tar-ing of @command{nncp-xfer} resulting directory.
index 0aca8f9d5694b5a953289f895ef6fc8dd2192262..46fa5f30ca50aefe1d8b09efccd167f352986f33 100644 (file)
@@ -18,6 +18,16 @@ Example @url{https://hjson.org/, Hjson} configuration file:
       from: nncp@localhost
       to: user+freq@example.com
     }
+    exec: {
+      "*.warcer": {
+        from: nncp@localhost
+        to: user+warcer@example.com
+      }
+      "eve.warcer": {
+        from: nncp@localhost
+        to: user+warcer-overriden@example.com
+      }
+    }
   }
 
   self: {
@@ -67,9 +77,11 @@ Example @url{https://hjson.org/, Hjson} configuration file:
         warcer: ["/path/to/warcer.sh"]
         wgeter: ["/path/to/wgeter.sh"]
       }
-      freq: "/home/bob/pub"
-      freqchunked: 1024
-      freqminsize: 2048
+      freq: {
+        path: "/home/bob/pub"
+        chunked: 1024
+        minsize: 2048
+      }
       via: ["alice"]
       rxrate: 10
       txrate: 20
@@ -88,12 +100,18 @@ override their umask to specified octal mask. Useful for using with
 
 @anchor{CfgNotify}
 @strong{notify} section contains notification settings for successfully
-tossed file and freq packets. Corresponding @strong{from} and
+tossed file, freq and exec packets. Corresponding @strong{from} and
 @strong{to} fields will be substituted in notification email message.
-@emph{neigh/self/exec/sendmail} will be used as a local mailer. You can
-omit either of those two @emph{from}/@emph{to} sections to omit
+@code{neigh.self.exec.sendmail} will be used as a local mailer. You can
+omit either of those two @code{from}/@code{to} sections to omit
 corresponding notifications, or the whole section at once.
 
+@code{notify.exec} section is a mapping of exec handles and
+corresponding @code{from}/@code{to} sections. Each handle has either
+@code{NODE.HANDLE} or @code{*.HANDLE} syntax. You can override
+notification options for some node with the first type of name.
+Handle command's output will be included in notification messages.
+
 @strong{self} section contains our node's private keypairs.
 @strong{exch*} and @strong{sign*} are used during @ref{Encrypted,
 encrypted} packet creation. @strong{noise*} are used during @ref{Sync,
@@ -104,7 +122,7 @@ always has @strong{self} neighbour that is copy of our node's public
 data (public keys). It is useful for copy-paste sharing with your
 friends. Each section's key is a human-readable name of the neighbour.
 
-Except for @emph{id}, @emph{exchpub} and @emph{signpub} each neighbour
+Except for @code{id}, @code{exchpub} and @code{signpub} each neighbour
 node has the following fields:
 
 @table @strong
@@ -141,24 +159,24 @@ Full path to directory where all file uploads will be saved. May be
 omitted to forbid file uploading on that node.
 
 @anchor{CfgFreq}
-@item freq
+@item freq.path
 Full path to directory from where file requests will queue files for
 transmission. May be omitted to forbid freqing from that node.
 
-@item freqchunked
+@item freq.chunked
 If set, then enable @ref{Chunked, chunked} file transmission during
 freqing. This is the desired chunk size in KiBs.
 
-@item freqminsize
+@item freq.minsize
 If set, then apply @ref{OptMinSize, -minsize} option during file
 transmission.
 
 @anchor{CfgVia}
 @item via
 An array of node identifiers that will be used as a relay to that node.
-For example @verb{|[foo,bar]|} means that packet can reach current node
-by transitioning through @emph{foo} and then @emph{bar} nodes. May be
-omitted if direct connection exists and no relaying is required.
+For example @verb{|["foo","bar"]|} means that packet can reach current
+node by transitioning through @code{foo} and then @code{bar} nodes. May
+be omitted if direct connection exists and no relaying is required.
 
 @anchor{CfgAddrs}
 @item addrs
index 4247054b10a4c0b73f21fac3e583d52c05b5c125..1910651a74e58116c5e2b32f4c8efc6d7fc71f2b 100644 (file)
@@ -287,6 +287,9 @@ echo My message |
     /usr/sbin/sendmail -t root@localhost
 @end verbatim
 
+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.
 
 @node nncp-file
 @section nncp-file
@@ -315,6 +318,15 @@ variable. Encryption is performed in AEAD mode with
 algorithms. Data is splitted on 128 KiB blocks. Each block is encrypted
 with increasing nonce counter.
 
+If @file{SRC} points to directory, then
+@url{https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html#tag_20_92_13_01, pax archive}
+will be created on the fly with directory contents and destination
+filename @file{.tar} appended. It @strong{won't} contain any entities
+metainformation, but modification time with the names. UID/GID are set
+to zero. Directories have 777 permissions, files have 666, for being
+friendly with @command{umask}. Also each entity will have comment like
+@verb{|Autogenerated by NNCP version X.Y.Z built with goXXX|}.
+
 If @option{-chunked} is specified, then source file will be split
 @ref{Chunked, on chunks}. @option{INT} is the desired chunk size in
 KiBs. This mode is more CPU hungry. Pay attention that chunk is saved in
@@ -334,7 +346,7 @@ $ nncp-freq [options] NODE:SRC [DST]
 @end verbatim
 
 Send file request to @option{NODE}, asking it to send its @file{SRC}
-file from @ref{CfgFreq, freq} directory to our node under @file{DST}
+file from @ref{CfgFreq, freq.path} directory to our node under @file{DST}
 filename in our @ref{CfgIncoming, incoming} one. If @file{DST} is not
 specified, then last element of @file{SRC} will be used.
 
index f25c62e6b268b6aa922c3a6ae99213c743ddf5fe..1cbd5c670da2b63b3c1638665b15a396e9130840 100644 (file)
@@ -23,6 +23,11 @@ Tarballs include all necessary required libraries:
 @multitable {XXXXX} {XXXX-XX-XX} {XXXX KiB} {link sign} {xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}
 @headitem Version @tab Date @tab Size @tab Tarball @tab SHA256 checksum
 
+An entry for documentation:
+@item @ref{Release 5.0.0, 5.0.0} @tab 2019-11-15 @tab 1099 KiB
+@tab @url{download/nncp-5.0.0.tar.xz, link} @url{download/nncp-5.0.0.tar.xz.sig, sign}
+@tab @code{3696D7EE B0783E91 87E5EEF4 EFC35235 10452353 7C51FA4C 9BD3CBEE A22678B3}
+
 @item @ref{Release 4.1, 4.1} @tab 2019-05-01 @tab 1227 KiB
 @tab @url{download/nncp-4.1.tar.xz, link} @url{download/nncp-4.1.tar.xz.sig, sign}
 @tab @code{29AEC53D EC914906 D7C47194 0955A32E 2BF470E6 9B8E09D3 AF3B62D8 CC8E541E}
index cc1e587fc737428d3f1be0c08a56cf645720464d..d4bd4562200441e7b04ea42bbc6007e014cf8322 100644 (file)
@@ -31,5 +31,5 @@ $ gpg --auto-key-locate wkd --locate-keys releases at nncpgo dot org
 
 Then you could verify tarballs signature:
 @verbatim
-$ gpg --verify nncp-5.0.0.tar.xz.sig nncp-5.0.0.tar.xz
+$ gpg --verify nncp-5.1.0.tar.xz.sig nncp-5.1.0.tar.xz
 @end verbatim
index 828a3cd152d221ff7f86b6e5f9868b0f0d1a578b..3226116fe88e951c28d859c70824c28bfbe46a80 100644 (file)
@@ -1,6 +1,33 @@
 @node Новости
 @section Новости
 
+@node Релиз 5.1.0
+@subsection Релиз 5.1.0
+@itemize
+
+@item
+@command{nncp-file} может отправлять директории, автоматически на лету
+создавая pax архив.
+
+@item
+Во время создания исходящих сообщений проверяется наличие свободного
+места на файловой системе.
+
+@item
+@option{freq}, @option{freqminsize}, @option{freqchunked} опции
+конфигурационного файла заменены на структуру
+@option{freq: @{path: ..., minsize: ..., chunked: ...@}}.
+
+@item
+Добавлена @option{freq.maxsize} опция конфигурационного файл,
+запрещающая ответ на файловый запрос больше заданного размера.
+
+@item
+Возможность оповещения об успешно выполненных командах (exec) через
+@option{notify.exec} опцию конфигурационного файла.
+
+@end itemize
+
 @node Релиз 5.0.0
 @subsection Релиз 5.0.0
 @itemize
@@ -8,7 +35,7 @@
 @item
 @strong{Несовместимое} изменение формата конфигурационного файла:
 YAML заменён на Hjson, из-за его гораздо большей простоты, без
-замеÑ\82ного Ð¿Ð¾Ñ\82еÑ\80Ñ\8f функционала и удобства.
+замеÑ\82ной Ð¿Ð¾Ñ\82еÑ\80и функционала и удобства.
 
 @item
 @strong{Несовместимое} изменение формата простых пакетов. Работа со
index b57c5ea567e3d07c1724ce98c58b2613c39a17d1..bc7a6fcbe96422a7349563bec49eb201ab402ded 100644 (file)
@@ -3,6 +3,32 @@
 
 See also this page @ref{Новости, on russian}.
 
+@node Release 5.1.0
+@section Release 5.1.0
+@itemize
+
+@item
+@command{nncp-file} can send directories, automatically creating pax
+archive on the fly.
+
+@item
+Free disk space is checked during outbound packets creation.
+
+@item
+@option{freq}, @option{freqminsize}, @option{freqchunked} configuration
+file options replaced with the structure:
+@option{freq: @{path: ..., minsize: ..., chunked: ...@}}.
+
+@item
+Added @option{freq.maxsize} configuration file option, forbidding of
+freq sending larger than specified size.
+
+@item
+Ability to notify about successfully executed commands (exec) with
+@option{notify.exec} configuration file option.
+
+@end itemize
+
 @node Release 5.0.0
 @section Release 5.0.0
 @itemize
index 42f5df60a6738a13a0f1a55ece15d2857ef57a2c..d42954f34846fb0c0972c1efb4026313bfc94745 100644 (file)
@@ -6,4 +6,6 @@ There are people deserving to be thanked for helping this project:
 @itemize
 @item Shawn K. Quinn for his descriptive instructions about building
 NNCP under Ubuntu GNU/Linux distributions and bug reports.
+@item @url{mailto:jgoerzen@@complete.org, John Goerzen} for his feature
+suggestions and Debian package maintenance.
 @end itemize
index cd79f6b7455508b456d0905249e7b9e0a76029eb..e3269229ef6a201235ad0d8eb694ef21d817a39e 100755 (executable)
@@ -226,7 +226,7 @@ online TCP daemon with full-duplex resumable data transmission exists.
 
 The main improvements for that release are:
 
-$(git cat-file -p $release | sed -n '6,/^.*BEGIN/p' | sed '$d')
+$(git cat-file -p v$release | sed -n '6,/^.*BEGIN/p' | sed '$d')
 
 ------------------------ >8 ------------------------
 
@@ -274,7 +274,7 @@ NNCP (Node to Node copy) это набор утилит упрощающий б
 
 Основные усовершенствования в этом релизе:
 
-$(git cat-file -p $release | sed -n '6,/^.*BEGIN/p' | sed '$d')
+$(git cat-file -p v$release | sed -n '6,/^.*BEGIN/p' | sed '$d')
 
 ------------------------ >8 ------------------------
 
index b26f5666e6e127e206be18fd7295cee38bdebc18..6cee3efea791651d3e2cbba050f7d61c6c5ee19d 100644 (file)
@@ -1,8 +1,7 @@
-# $FreeBSD: head/net/nncp/Makefile 484628 2018-11-10 18:12:57Z bapt $
+# $FreeBSD: head/net/nncp/Makefile 517819 2019-11-17 11:51:56Z dmgk $
 
 PORTNAME=      nncp
-DISTVERSION=   5.0.0
-PORTREVISION=  1
+DISTVERSION=   5.1.0
 CATEGORIES=    net
 MASTER_SITES=  http://www.nncpgo.org/download/
 
@@ -12,19 +11,19 @@ COMMENT=    Utilities for secure store-and-forward files, mail, command exchanging
 LICENSE=       GPLv3
 LICENSE_FILE=  ${WRKSRC}/COPYING
 
-BUILD_DEPENDS= go:lang/go
+USES=          go:no_targets tar:xz
 
-USES=          tar:xz
 USE_RC_SUBR=   nncp-caller nncp-daemon nncp-toss
+
+MAKE_ARGS=     INFODIR=${STAGEDIR}${PREFIX}/${INFO_PATH}
 INSTALL_TARGET=        install-strip
 
 SUB_FILES=     pkg-message pkg-install pkg-deinstall
 
-OPTIONS_DEFINE=        DOCS
-
-PORTDOCS=      AUTHORS NEWS NEWS.RU README README.RU THANKS
 INFO=          nncp
-MAKE_ARGS=     INFODIR=${STAGEDIR}${PREFIX}/${INFO_PATH}
+PORTDOCS=      AUTHORS NEWS NEWS.RU README README.RU THANKS
+
+OPTIONS_DEFINE=        DOCS
 
 post-install:
        ${INSTALL_DATA} ${FILESDIR}/nncp.newsyslog.conf.sample ${STAGEDIR}${PREFIX}/etc/nncp.conf.sample
index 6aa49a4eac87bd153e4ca2d1381c21f8e6a13a74..3d6ddd95ec35e51503317bce3d5bb001a2dd1250 100644 (file)
@@ -1,4 +1,6 @@
-======================================================================
+[
+{ type: install
+  message: <<EOM
 - Add the following lines to /etc/rc.conf to enable nncp-daemon,
   nncp-caller and nncp-toss:
 
@@ -14,4 +16,6 @@
 
     # umask 077
     # nncp-cfgnew > %%PREFIX%%/etc/nncp.hjson
-======================================================================
+EOM
+}
+]
index a4450e0ae9ff7877d4961322b8abc0bd90d41942..edc132a37ffc403a09dd55221871ceb07fb15a27 100644 (file)
@@ -46,17 +46,15 @@ var (
 )
 
 type NodeJSON struct {
-       Id          string              `json:"id"`
-       ExchPub     string              `json:"exchpub"`
-       SignPub     string              `json:"signpub"`
-       NoisePub    *string             `json:"noisepub,omitempty"`
-       Exec        map[string][]string `json:"exec,omitempty"`
-       Incoming    *string             `json:"incoming,omitempty"`
-       Freq        *string             `json:"freq,omitempty"`
-       FreqChunked *uint64             `json:"freqchunked,omitempty"`
-       FreqMinSize *uint64             `json:"freqminsize,omitempty"`
-       Via         []string            `json:"via,omitempty"`
-       Calls       []CallJSON          `json:"calls,omitempty"`
+       Id       string              `json:"id"`
+       ExchPub  string              `json:"exchpub"`
+       SignPub  string              `json:"signpub"`
+       NoisePub *string             `json:"noisepub,omitempty"`
+       Exec     map[string][]string `json:"exec,omitempty"`
+       Incoming *string             `json:"incoming,omitempty"`
+       Freq     *NodeFreqJSON       `json:"freq,omitempty"`
+       Via      []string            `json:"via,omitempty"`
+       Calls    []CallJSON          `json:"calls,omitempty"`
 
        Addrs map[string]string `json:"addrs,omitempty"`
 
@@ -66,6 +64,13 @@ type NodeJSON struct {
        MaxOnlineTime  *uint `json:"maxonlinetime,omitempty"`
 }
 
+type NodeFreqJSON struct {
+       Path    *string `json:"path,omitempty"`
+       Chunked *uint64 `json:"chunked,omitempty"`
+       MinSize *uint64 `json:"minsize,omitempty"`
+       MaxSize *uint64 `json:"maxsize,omitempty"`
+}
+
 type CallJSON struct {
        Cron           string
        Nice           *string `json:"nice,omitempty"`
@@ -93,8 +98,9 @@ type FromToJSON struct {
 }
 
 type NotifyJSON struct {
-       File *FromToJSON `json:"file,omitempty"`
-       Freq *FromToJSON `json:"freq,omitempty"`
+       File *FromToJSON            `json:"file,omitempty"`
+       Freq *FromToJSON            `json:"freq,omitempty"`
+       Exec map[string]*FromToJSON `json:"exec,omitempty"`
 }
 
 type CfgJSON struct {
@@ -150,24 +156,31 @@ func NewNode(name string, yml NodeJSON) (*Node, error) {
                incoming = &inc
        }
 
-       var freq *string
+       var freqPath *string
+       freqChunked := int64(MaxFileSize)
+       var freqMinSize int64
+       freqMaxSize := int64(MaxFileSize)
        if yml.Freq != nil {
-               fr := path.Clean(*yml.Freq)
-               if !path.IsAbs(fr) {
-                       return nil, errors.New("Freq path must be absolute")
+               f := yml.Freq
+               if f.Path != nil {
+                       fPath := path.Clean(*f.Path)
+                       if !path.IsAbs(fPath) {
+                               return nil, errors.New("freq.path path must be absolute")
+                       }
+                       freqPath = &fPath
                }
-               freq = &fr
-       }
-       var freqChunked int64
-       if yml.FreqChunked != nil {
-               if *yml.FreqChunked == 0 {
-                       return nil, errors.New("freqchunked value must be greater than zero")
+               if f.Chunked != nil {
+                       if *f.Chunked == 0 {
+                               return nil, errors.New("freq.chunked value must be greater than zero")
+                       }
+                       freqChunked = int64(*f.Chunked) * 1024
+               }
+               if f.MinSize != nil {
+                       freqMinSize = int64(*f.MinSize) * 1024
+               }
+               if f.MaxSize != nil {
+                       freqMaxSize = int64(*f.MaxSize) * 1024
                }
-               freqChunked = int64(*yml.FreqChunked) * 1024
-       }
-       var freqMinSize int64
-       if yml.FreqMinSize != nil {
-               freqMinSize = int64(*yml.FreqMinSize) * 1024
        }
 
        defRxRate := 0
@@ -268,9 +281,10 @@ func NewNode(name string, yml NodeJSON) (*Node, error) {
                SignPub:        ed25519.PublicKey(signPub),
                Exec:           yml.Exec,
                Incoming:       incoming,
-               Freq:           freq,
+               FreqPath:       freqPath,
                FreqChunked:    freqChunked,
                FreqMinSize:    freqMinSize,
+               FreqMaxSize:    freqMaxSize,
                Calls:          calls,
                Addrs:          yml.Addrs,
                RxRate:         defRxRate,
@@ -424,6 +438,9 @@ func CfgParse(data []byte) (*Ctx, error) {
                if cfgJSON.Notify.Freq != nil {
                        ctx.NotifyFreq = cfgJSON.Notify.Freq
                }
+               if cfgJSON.Notify.Exec != nil {
+                       ctx.NotifyExec = cfgJSON.Notify.Exec
+               }
        }
        vias := make(map[NodeId][]string)
        for name, neighJSON := range cfgJSON.Neigh {
index 4f5bca1b9d72890c25f33bc6069626c48e4a9c7c..380b3917e7e5706253a0cecbd43a4fbc416aac13 100644 (file)
@@ -32,7 +32,7 @@ import (
        "strconv"
        "strings"
 
-       "github.com/davecgh/go-xdr/xdr2"
+       xdr "github.com/davecgh/go-xdr/xdr2"
        "go.cypherpunks.ru/nncp/v5"
        "golang.org/x/crypto/blake2b"
 )
index 7adc6fab94f9f1c350e54e7192e0d9fc0a048a04..055b6443c4c3a7e1ad511267ffb817c5c1e21713 100644 (file)
@@ -27,7 +27,7 @@ import (
        "log"
        "os"
 
-       "github.com/davecgh/go-xdr/xdr2"
+       xdr "github.com/davecgh/go-xdr/xdr2"
        "go.cypherpunks.ru/nncp/v5"
        "golang.org/x/crypto/blake2b"
        "golang.org/x/crypto/ssh/terminal"
index c7bb14ef571d2653141a4d7abd0f8672495974bb..e2eac99d0c8affde1cdf17aae09de357ea858499 100644 (file)
@@ -113,6 +113,19 @@ func main() {
   #     from: nncp@localhost
   #     to: user+freq@example.com
   #   }
+  #   # Send some exec commands execution notifications
+  #   exec: {
+  #     # bob neighbour's "somehandle" notification
+  #     bob.somehandle: {
+  #       from: nncp+bob@localhost
+  #       to: user+somehandle@example.com
+  #     }
+  #     # Any neighboor's "anotherhandle"
+  #     *.anotherhandle: {
+  #       from: nncp@localhost
+  #       to: user+anotherhandle@example.com
+  #     }
+  #   }
   # }
 
   self: {
@@ -148,50 +161,55 @@ func main() {
     #   noisepub: UBM5K...VI42A
     #
     #   # He is allowed to send email
-    #   exec: {sendmail: ["/usr/sbin/sendmail"]}
+    #   # exec: {sendmail: ["%s"]}
     #
     #   # Allow incoming files saving in that directory
-    #   incoming: "/home/alice/incoming"
+    #   incoming: "/home/alice/incoming"
     #
     #   # Transitional nodes path
-    #   via: ["bob", "eve"]
+    #   via: ["bob", "eve"]
     #
     #   # Inactivity timeout when session with remote peer should be terminated
-    #   onlinedeadline: 1800
+    #   onlinedeadline: 1800
     #
     #   # Maximal online session lifetime
-    #   maxonlinetime: 3600
+    #   maxonlinetime: 3600
     #
-    #   # Allow freqing from that directory
-    #   freq: "/home/bob/pub"
-    #   # Send freqed files with chunks
-    #   freqchunked: 1024
-    #   # Send freqed files with minumal chunk size
-    #   freqminsize: 2048
+    #   # If neither freq section, nor freq.path exist, then no freqing allowed
+    #   # freq: {
+    #   #   # Allow freqing from that directory
+    #   #   path: "/home/bob/pub"
+    #   #   # Send freqed files with chunks
+    #   #   # chunked: 1024
+    #   #   # Send freqed files with minumal chunk size
+    #   #   # minsize: 2048
+    #   #   # Maximal allowable freqing file size
+    #   #   # maxsize: 4096
+    #   # }
     #
     #   # Set maximal packets per second receive and transmit rates
-    #   rxrate: 10
-    #   txrate: 20
+    #   rxrate: 10
+    #   txrate: 20
     #
     #   # Address aliases
-    #   addrs: {
-    #     lan: "[fe80::1234%%igb0]:5400"
-    #     internet: alice.com:3389
-    #   }
+    #   addrs: {
+    #     lan: "[fe80::1234%%igb0]:5400"
+    #     internet: alice.com:3389
+    #   }
     #
     #   # Calls configuration
-    #   calls: [
-    #     {
-    #       cron: "*/2 * * * *"
-    #       onlinedeadline: 1800
-    #       maxonlinetime: 1750
-    #       nice: PRIORITY+10
-    #       rxrate: 10
-    #       txrate: 20
-    #       xx: rx
-    #       addr: lan
-    #     },
-    #   ]
+    #   calls: [
+    #     {
+    #       cron: "*/2 * * * *"
+    #       onlinedeadline: 1800
+    #       maxonlinetime: 1750
+    #       nice: PRIORITY+10
+    #       rxrate: 10
+    #       txrate: 20
+    #       xx: rx
+    #       addr: lan
+    #     },
+    #   ]
     # }
   }
 }`,
@@ -209,6 +227,7 @@ func main() {
                        nncp.ToBase32(nodeOur.SignPub[:]),
                        nncp.ToBase32(nodeOur.NoisePub[:]),
                        nncp.DefaultSendmailPath,
+                       nncp.DefaultSendmailPath,
                )
        }
        if _, err = nncp.CfgParse([]byte(cfgRaw)); err != nil {
index f3572ca04fc95f2b6a38aca6171324a92b9cfd71..d7c06074843642f4bc7052c2b0b10e0924264240 100644 (file)
@@ -36,7 +36,7 @@ func usage() {
        fmt.Fprint(os.Stderr, `
 If SRC equals to -, then read data from stdin to temporary file.
 
--minsize/-chunked take NODE's FreqMinSize/FreqChunked configuration
+-minsize/-chunked take NODE's freq.minsize/freq.chunked configuration
 options by default. You can forcefully turn them off by specifying 0 value.
 `)
 }
@@ -109,25 +109,15 @@ func main() {
                chunkSize = *argChunkSize * 1024
        }
 
-       if chunkSize == 0 {
-               err = ctx.TxFile(
-                       node,
-                       nice,
-                       flag.Arg(0),
-                       splitted[1],
-                       minSize,
-               )
-       } else {
-               err = ctx.TxFileChunked(
-                       node,
-                       nice,
-                       flag.Arg(0),
-                       splitted[1],
-                       minSize,
-                       chunkSize,
-               )
-       }
-       if err != nil {
+       if err = ctx.TxFile(
+               node,
+               nice,
+               flag.Arg(0),
+               splitted[1],
+               chunkSize,
+               minSize,
+               nncp.MaxFileSize,
+       ); err != nil {
                log.Fatalln(err)
        }
 }
index 4332ff1ca1906ad02db2b7d47521ffe4d3cbf1d4..25328875426a525d23035044449ccab3b784d286 100644 (file)
@@ -24,7 +24,6 @@ import (
        "log"
        "os"
        "path/filepath"
-       "strconv"
        "strings"
 
        "go.cypherpunks.ru/nncp/v5"
@@ -41,7 +40,7 @@ func main() {
        var (
                cfgPath      = flag.String("cfg", nncp.DefaultCfgPath, "Path to configuration file")
                niceRaw      = flag.String("nice", nncp.NicenessFmt(nncp.DefaultNiceFreq), "Outbound packet niceness")
-               replyNiceRaw = flag.String("replynice", strconv.Itoa(nncp.DefaultNiceFile), "Reply file packet niceness")
+               replyNiceRaw = flag.String("replynice", nncp.NicenessFmt(nncp.DefaultNiceFile), "Reply file packet niceness")
                minSize      = flag.Uint64("minsize", 0, "Minimal required resulting packet size, in KiB")
                viaOverride  = flag.String("via", "", "Override Via path to destination node")
                spoolPath    = flag.String("spool", "", "Override path to spool")
index 1808e3a61d001d11bf49d3120fe317e70bb48c86..cb9042a5c53d28809df08d089e5d00c5991f2e9d 100644 (file)
@@ -27,7 +27,7 @@ import (
        "log"
        "os"
 
-       "github.com/davecgh/go-xdr/xdr2"
+       xdr "github.com/davecgh/go-xdr/xdr2"
        "github.com/klauspost/compress/zstd"
        "go.cypherpunks.ru/nncp/v5"
 )
index 77a5f139a879c986387aa877fc55638a98368fb4..d7d79b082d327d964fc514112bb0c0a2b11c8097 100644 (file)
@@ -32,7 +32,7 @@ import (
        "strconv"
        "strings"
 
-       "github.com/davecgh/go-xdr/xdr2"
+       xdr "github.com/davecgh/go-xdr/xdr2"
        "github.com/dustin/go-humanize"
        "go.cypherpunks.ru/nncp/v5"
        "golang.org/x/crypto/blake2b"
index fde11174e9c19297aa573a8af86cd4c64d8848c3..cfe16b779c6bff49586e34e79808c12a969fba3e 100644 (file)
@@ -28,7 +28,7 @@ import (
        "path/filepath"
        "strconv"
 
-       "github.com/davecgh/go-xdr/xdr2"
+       xdr "github.com/davecgh/go-xdr/xdr2"
        "go.cypherpunks.ru/nncp/v5"
 )
 
index 3793bbba6b6808b3c3b9d0630a788682b54e615b..8a1146e2b5651f44731cfe7de792ff3388c906a9 100644 (file)
@@ -24,8 +24,9 @@ import (
        "os"
        "path/filepath"
 
-       "golang.org/x/sys/unix"
        "syscall"
+
+       "golang.org/x/sys/unix"
 )
 
 type Ctx struct {
@@ -41,6 +42,7 @@ type Ctx struct {
        Debug      bool
        NotifyFile *FromToJSON
        NotifyFreq *FromToJSON
+       NotifyExec map[string]*FromToJSON
 }
 
 func (ctx *Ctx) FindNode(id string) (*Node, error) {
index 64327901bd1f4455bf2deac59d65322bcbce2e66..0616c8402b87ffdfa6b8f3d335864902449c15a4 100644 (file)
@@ -22,7 +22,7 @@ import (
        "crypto/rand"
        "hash"
 
-       "github.com/davecgh/go-xdr/xdr2"
+       xdr "github.com/davecgh/go-xdr/xdr2"
        "go.cypherpunks.ru/balloon"
        "golang.org/x/crypto/blake2b"
        "golang.org/x/crypto/chacha20poly1305"
index 77ef98aab6641a0370b6594f84eea52eccf7982b..c7a3543f379d890d994ca197755343805544ef19 100644 (file)
@@ -15,3 +15,5 @@ require (
        golang.org/x/sys v0.0.0-20191113165036-4c7a9d0fe056
        gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
 )
+
+go 1.10
index 080ec4aabea25885fdceb83ac6d2b40634f221cd..49df07b96e8b8c1f56445a040851644ff7fa18e6 100644 (file)
@@ -23,7 +23,7 @@ import (
        "path/filepath"
        "strconv"
 
-       "github.com/davecgh/go-xdr/xdr2"
+       xdr "github.com/davecgh/go-xdr/xdr2"
 )
 
 type TRxTx string
index ac78301f802464dae9c763de4de93495c259f748..65f5e9cde30cc7e8c4931f9c54384c89b9c8e9b2 100644 (file)
@@ -42,9 +42,10 @@ type Node struct {
        NoisePub       *[32]byte
        Exec           map[string][]string
        Incoming       *string
-       Freq           *string
+       FreqPath       *string
        FreqChunked    int64
        FreqMinSize    int64
+       FreqMaxSize    int64
        Via            []*NodeId
        Addrs          map[string]string
        RxRate         int
@@ -100,10 +101,12 @@ func NewNodeGenerate() (*NodeOur, error) {
 
 func (nodeOur *NodeOur) Their() *Node {
        return &Node{
-               Name:    "self",
-               Id:      nodeOur.Id,
-               ExchPub: nodeOur.ExchPub,
-               SignPub: nodeOur.SignPub,
+               Name:        "self",
+               Id:          nodeOur.Id,
+               ExchPub:     nodeOur.ExchPub,
+               SignPub:     nodeOur.SignPub,
+               FreqChunked: MaxFileSize,
+               FreqMaxSize: MaxFileSize,
        }
 }
 
index 2fab844422486793be179dd571ef5f77386a3e1c..8324ffbc0428450850779b9c1bdf215533fa9d33 100644 (file)
@@ -25,7 +25,7 @@ import (
        "errors"
        "io"
 
-       "github.com/davecgh/go-xdr/xdr2"
+       xdr "github.com/davecgh/go-xdr/xdr2"
        "golang.org/x/crypto/blake2b"
        "golang.org/x/crypto/chacha20poly1305"
        "golang.org/x/crypto/curve25519"
index 678cbf474a420fba1d7c9aa1becee2822ad17389..c69ba96e3b951b1ed1630799b3664a052c71c83e 100644 (file)
@@ -22,7 +22,7 @@ import (
        "testing"
        "testing/quick"
 
-       "github.com/davecgh/go-xdr/xdr2"
+       xdr "github.com/davecgh/go-xdr/xdr2"
 )
 
 func TestPktEncWrite(t *testing.T) {
index 6dd724a59807b744b404763d03f8991f4cdc556f..540e0e52a22a4f016114ca8a9fed71599802ed54 100644 (file)
--- a/src/sp.go
+++ b/src/sp.go
@@ -30,7 +30,7 @@ import (
        "sync"
        "time"
 
-       "github.com/davecgh/go-xdr/xdr2"
+       xdr "github.com/davecgh/go-xdr/xdr2"
        "github.com/flynn/noise"
 )
 
index 0ce446ac807b39df6e6a067a2745b0631101ede2..586f378076746fb689338dd14f0df44b75fc7860 100644 (file)
@@ -20,6 +20,7 @@ package nncp
 import (
        "bufio"
        "bytes"
+       "encoding/base64"
        "fmt"
        "io"
        "io/ioutil"
@@ -32,7 +33,7 @@ import (
        "strconv"
        "strings"
 
-       "github.com/davecgh/go-xdr/xdr2"
+       xdr "github.com/davecgh/go-xdr/xdr2"
        "github.com/dustin/go-humanize"
        "github.com/klauspost/compress/zstd"
        "golang.org/x/crypto/blake2b"
@@ -43,13 +44,22 @@ const (
        SeenSuffix = ".seen"
 )
 
-func newNotification(fromTo *FromToJSON, subject string) io.Reader {
-       return strings.NewReader(fmt.Sprintf(
-               "From: %s\nTo: %s\nSubject: %s\n",
-               fromTo.From,
-               fromTo.To,
-               mime.BEncoding.Encode("UTF-8", subject),
-       ))
+func newNotification(fromTo *FromToJSON, subject string, body []byte) io.Reader {
+       lines := []string{
+               "From: " + fromTo.From,
+               "To: " + fromTo.To,
+               "Subject: " + mime.BEncoding.Encode("UTF-8", subject),
+       }
+       if len(body) > 0 {
+               lines = append(lines, []string{
+                       "MIME-Version: 1.0",
+                       "Content-Type: text/plain; charset=utf-8",
+                       "Content-Transfer-Encoding: base64",
+                       "",
+                       base64.StdEncoding.EncodeToString(body),
+               }...)
+       }
+       return strings.NewReader(strings.Join(lines, "\n"))
 }
 
 func (ctx *Ctx) Toss(
@@ -58,6 +68,7 @@ func (ctx *Ctx) Toss(
        dryRun, doSeen, noFile, noFreq, noExec, noTrns bool,
 ) bool {
        isBad := false
+       sendmail := ctx.Neigh[*ctx.SelfId].Exec["sendmail"]
        decompressor, err := zstd.NewReader(nil)
        if err != nil {
                panic(err)
@@ -118,9 +129,10 @@ func (ctx *Ctx) Toss(
                        for _, p := range path[1:] {
                                args = append(args, string(p))
                        }
+                       argsStr := strings.Join(append([]string{handle}, args...), " ")
                        sds := SdsAdd(sds, SDS{
                                "type": "exec",
-                               "dst":  strings.Join(append([]string{handle}, args...), " "),
+                               "dst":  argsStr,
                        })
                        sender := ctx.Neigh[*job.PktEnc.Sender]
                        cmdline, exists := sender.Exec[handle]
@@ -144,11 +156,28 @@ func (ctx *Ctx) Toss(
                                        "NNCP_NICE="+strconv.Itoa(int(pkt.Nice)),
                                )
                                cmd.Stdin = decompressor
-                               if err = cmd.Run(); err != nil {
+                               output, err := cmd.Output()
+                               if err != nil {
                                        ctx.LogE("rx", SdsAdd(sds, SDS{"err": err}), "handle")
                                        isBad = true
                                        goto Closing
                                }
+                               if len(sendmail) > 0 && ctx.NotifyExec != nil {
+                                       notify, exists := ctx.NotifyExec[sender.Name+"."+handle]
+                                       if !exists {
+                                               notify, exists = ctx.NotifyExec["*."+handle]
+                                       }
+                                       if exists {
+                                               cmd := exec.Command(
+                                                       sendmail[0],
+                                                       append(sendmail[1:len(sendmail)], notify.To)...,
+                                               )
+                                               cmd.Stdin = newNotification(notify, fmt.Sprintf(
+                                                       "Exec from %s: %s", sender.Name, argsStr,
+                                               ), output)
+                                               cmd.Run()
+                                       }
+                               }
                        }
                        ctx.LogI("rx", sds, "")
                        if !dryRun {
@@ -245,8 +274,7 @@ func (ctx *Ctx) Toss(
                                        ctx.LogE("rx", SdsAdd(sds, SDS{"err": err}), "remove")
                                        isBad = true
                                }
-                               sendmail, exists := ctx.Neigh[*ctx.SelfId].Exec["sendmail"]
-                               if exists && len(sendmail) > 0 && ctx.NotifyFile != nil {
+                               if len(sendmail) > 0 && ctx.NotifyFile != nil {
                                        cmd := exec.Command(
                                                sendmail[0],
                                                append(sendmail[1:len(sendmail)], ctx.NotifyFile.To)...,
@@ -256,7 +284,7 @@ func (ctx *Ctx) Toss(
                                                ctx.Neigh[*job.PktEnc.Sender].Name,
                                                dst,
                                                humanize.IBytes(uint64(pktSize)),
-                                       ))
+                                       ), nil)
                                        cmd.Run()
                                }
                        }
@@ -280,31 +308,22 @@ func (ctx *Ctx) Toss(
                        dst := string(dstRaw)
                        sds["dst"] = dst
                        sender := ctx.Neigh[*job.PktEnc.Sender]
-                       freq := sender.Freq
-                       if freq == nil {
+                       freqPath := sender.FreqPath
+                       if freqPath == nil {
                                ctx.LogE("rx", sds, "freqing is not allowed")
                                isBad = true
                                goto Closing
                        }
                        if !dryRun {
-                               if sender.FreqChunked == 0 {
-                                       err = ctx.TxFile(
-                                               sender,
-                                               pkt.Nice,
-                                               filepath.Join(*freq, src),
-                                               dst,
-                                               sender.FreqMinSize,
-                                       )
-                               } else {
-                                       err = ctx.TxFileChunked(
-                                               sender,
-                                               pkt.Nice,
-                                               filepath.Join(*freq, src),
-                                               dst,
-                                               sender.FreqMinSize,
-                                               sender.FreqChunked,
-                                       )
-                               }
+                               err = ctx.TxFile(
+                                       sender,
+                                       pkt.Nice,
+                                       filepath.Join(*freqPath, src),
+                                       dst,
+                                       sender.FreqChunked,
+                                       sender.FreqMinSize,
+                                       sender.FreqMaxSize,
+                               )
                                if err != nil {
                                        ctx.LogE("rx", SdsAdd(sds, SDS{"err": err}), "tx file")
                                        isBad = true
@@ -322,17 +341,14 @@ func (ctx *Ctx) Toss(
                                        ctx.LogE("rx", SdsAdd(sds, SDS{"err": err}), "remove")
                                        isBad = true
                                }
-                               sendmail, exists := ctx.Neigh[*ctx.SelfId].Exec["sendmail"]
-                               if exists && len(sendmail) > 0 && ctx.NotifyFreq != nil {
+                               if len(sendmail) > 0 && ctx.NotifyFreq != nil {
                                        cmd := exec.Command(
                                                sendmail[0],
                                                append(sendmail[1:len(sendmail)], ctx.NotifyFreq.To)...,
                                        )
                                        cmd.Stdin = newNotification(ctx.NotifyFreq, fmt.Sprintf(
-                                               "Freq from %s: %s",
-                                               ctx.Neigh[*job.PktEnc.Sender].Name,
-                                               src,
-                                       ))
+                                               "Freq from %s: %s", sender.Name, src,
+                                       ), nil)
                                        cmd.Run()
                                }
                        }
index 4ccf4ead4ca656ff8738b6773b5cf385b56339ae..3a6f29fa36334c0f8924e3c53568e73a64058b69 100644 (file)
@@ -30,7 +30,7 @@ import (
        "testing"
        "testing/quick"
 
-       "github.com/davecgh/go-xdr/xdr2"
+       xdr "github.com/davecgh/go-xdr/xdr2"
        "golang.org/x/crypto/blake2b"
 )
 
@@ -199,7 +199,9 @@ func TestTossFile(t *testing.T) {
                                DefaultNiceFile,
                                src,
                                fileName,
+                               MaxFileSize,
                                1<<15,
+                               MaxFileSize,
                        ); err != nil {
                                t.Error(err)
                                return false
@@ -273,7 +275,9 @@ func TestTossFileSameName(t *testing.T) {
                                DefaultNiceFile,
                                srcPath,
                                "samefile",
+                               MaxFileSize,
                                1<<15,
+                               MaxFileSize,
                        ); err != nil {
                                t.Error(err)
                                return false
@@ -357,7 +361,7 @@ func TestTossFreq(t *testing.T) {
                if len(dirFiles(txPath)) != 0 || len(dirFiles(rxPath)) == 0 {
                        return false
                }
-               ctx.Neigh[*nodeOur.Id].Freq = &spool
+               ctx.Neigh[*nodeOur.Id].FreqPath = &spool
                ctx.Toss(ctx.Self.Id, DefaultNiceFreq, false, false, false, false, false, false)
                if len(dirFiles(txPath)) != 0 || len(dirFiles(rxPath)) == 0 {
                        return false
index a509207422ccee4479b514b77c70736acc86e018..20853b0b27a3cd4a02eab6027a2081675bf6a11f 100644 (file)
--- a/src/tx.go
+++ b/src/tx.go
@@ -18,6 +18,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 package nncp
 
 import (
+       "archive/tar"
        "bufio"
        "bytes"
        "crypto/rand"
@@ -29,13 +30,21 @@ import (
        "path/filepath"
        "strconv"
        "strings"
+       "time"
 
-       "github.com/davecgh/go-xdr/xdr2"
+       xdr "github.com/davecgh/go-xdr/xdr2"
        "github.com/klauspost/compress/zstd"
        "golang.org/x/crypto/blake2b"
        "golang.org/x/crypto/chacha20poly1305"
 )
 
+const (
+       MaxFileSize = 1 << 62
+
+       TarBlockSize = 512
+       TarExt       = ".tar"
+)
+
 func (ctx *Ctx) Tx(
        node *Node,
        pkt *Pkt,
@@ -43,10 +52,6 @@ func (ctx *Ctx) Tx(
        size, minSize int64,
        src io.Reader,
 ) (*Node, error) {
-       tmp, err := ctx.NewTmpFileWHash()
-       if err != nil {
-               return nil, err
-       }
        hops := make([]*Node, 0, 1+len(node.Via))
        hops = append(hops, node)
        lastNode := node
@@ -62,6 +67,14 @@ func (ctx *Ctx) Tx(
        if padSize < 0 {
                padSize = 0
        }
+       if !ctx.IsEnoughSpace(size + padSize) {
+               return nil, errors.New("is not enough space")
+       }
+       tmp, err := ctx.NewTmpFileWHash()
+       if err != nil {
+               return nil, err
+       }
+
        errs := make(chan error)
        curSize := size
        pipeR, pipeW := io.Pipe()
@@ -109,122 +122,198 @@ func (ctx *Ctx) Tx(
        return lastNode, err
 }
 
-func prepareTxFile(srcPath string) (io.Reader, *os.File, int64, error) {
-       var reader io.Reader
-       var src *os.File
-       var fileSize int64
-       var err error
+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 == "-" {
-               src, err = ioutil.TempFile("", "nncp-file")
+               // Read content from stdin, saving to temporary file, encrypting
+               // on the fly
+               src, err := ioutil.TempFile("", "nncp-file")
                if err != nil {
-                       return nil, nil, 0, err
+                       rerr = err
+                       return
                }
                os.Remove(src.Name())
                tmpW := bufio.NewWriter(src)
                tmpKey := make([]byte, chacha20poly1305.KeySize)
-               if _, err = rand.Read(tmpKey[:]); err != nil {
-                       return nil, nil, 0, err
+               if _, rerr = rand.Read(tmpKey[:]); rerr != nil {
+                       return
                }
                aead, err := chacha20poly1305.New(tmpKey)
                if err != nil {
-                       return nil, nil, 0, err
+                       rerr = err
+                       return
                }
                nonce := make([]byte, aead.NonceSize())
                written, err := aeadProcess(aead, nonce, true, bufio.NewReader(os.Stdin), tmpW)
                if err != nil {
-                       return nil, nil, 0, err
+                       rerr = err
+                       return
                }
                fileSize = int64(written)
                if err = tmpW.Flush(); err != nil {
-                       return nil, nil, 0, err
+                       return
                }
                src.Seek(0, io.SeekStart)
                r, w := io.Pipe()
                go func() {
                        if _, err := aeadProcess(aead, nonce, false, bufio.NewReader(src), w); err != nil {
-                               panic(err)
+                               w.CloseWithError(err)
                        }
                }()
                reader = r
-       } else {
-               src, err = os.Open(srcPath)
-               if err != nil {
-                       return nil, nil, 0, err
-               }
-               srcStat, err := src.Stat()
+               closer = src
+               return
+       }
+
+       srcStat, err := os.Stat(srcPath)
+       if err != nil {
+               rerr = err
+               return
+       }
+       mode := srcStat.Mode()
+
+       if mode.IsRegular() {
+               // It is regular file, just send it
+               src, err := os.Open(srcPath)
                if err != nil {
-                       return nil, nil, 0, err
+                       rerr = err
+                       return
                }
                fileSize = srcStat.Size()
                reader = bufio.NewReader(src)
+               closer = src
+               return
        }
-       return reader, src, fileSize, nil
-}
 
-func (ctx *Ctx) TxFile(node *Node, nice uint8, srcPath, dstPath string, minSize int64) error {
-       if dstPath == "" {
-               if srcPath == "-" {
-                       return errors.New("Must provide destination filename")
-               }
-               dstPath = filepath.Base(srcPath)
-       }
-       dstPath = filepath.Clean(dstPath)
-       if filepath.IsAbs(dstPath) {
-               return errors.New("Relative destination path required")
-       }
-       pkt, err := NewPkt(PktTypeFile, nice, []byte(dstPath))
-       if err != nil {
-               return err
-       }
-       reader, src, fileSize, err := prepareTxFile(srcPath)
-       if src != nil {
-               defer src.Close()
+       if !mode.IsDir() {
+               rerr = errors.New("unsupported file type")
+               return
        }
+
+       // It is directory, create PAX archive with its contents
+       archived = true
+       basePath := filepath.Base(srcPath)
+       rootPath, err := filepath.Abs(srcPath)
        if err != nil {
-               return err
+               rerr = err
+               return
        }
-       _, err = ctx.Tx(node, pkt, nice, fileSize, minSize, reader)
-       sds := SDS{
-               "type": "file",
-               "node": node.Id,
-               "nice": strconv.Itoa(int(nice)),
-               "src":  srcPath,
-               "dst":  dstPath,
-               "size": strconv.FormatInt(fileSize, 10),
+       type einfo struct {
+               path    string
+               modTime time.Time
+               size    int64
        }
-       if err == nil {
-               ctx.LogI("tx", sds, "sent")
-       } else {
-               sds["err"] = err
-               ctx.LogE("tx", sds, "sent")
+       dirs := make([]einfo, 0, 1<<10)
+       files := make([]einfo, 0, 1<<10)
+       rerr = filepath.Walk(rootPath, func(path string, info os.FileInfo, err error) error {
+               if err != nil {
+                       return err
+               }
+               if info.IsDir() {
+                       // directory header, PAX record header+contents
+                       fileSize += TarBlockSize + 2*TarBlockSize
+                       dirs = append(dirs, einfo{path: path, modTime: info.ModTime()})
+               } else {
+                       // file header, PAX record header+contents, file content
+                       fileSize += TarBlockSize + 2*TarBlockSize + info.Size()
+                       if n := info.Size() % TarBlockSize; n != 0 {
+                               fileSize += TarBlockSize - n // padding
+                       }
+                       files = append(files, einfo{
+                               path:    path,
+                               modTime: info.ModTime(),
+                               size:    info.Size(),
+                       })
+               }
+               return nil
+       })
+       if rerr != nil {
+               return
        }
-       return err
+
+       r, w := io.Pipe()
+       reader = r
+       closer = DummyCloser{}
+       fileSize += 2 * TarBlockSize // termination block
+
+       go func() {
+               tarWr := tar.NewWriter(w)
+               hdr := tar.Header{
+                       Typeflag: tar.TypeDir,
+                       Mode:     0777,
+                       PAXRecords: map[string]string{
+                               "comment": "Autogenerated by " + VersionGet(),
+                       },
+                       Format: tar.FormatPAX,
+               }
+               for _, e := range dirs {
+                       hdr.Name = basePath + e.path[len(rootPath):]
+                       hdr.ModTime = e.modTime
+                       if err = tarWr.WriteHeader(&hdr); err != nil {
+                               w.CloseWithError(err)
+                       }
+               }
+               hdr.Typeflag = tar.TypeReg
+               hdr.Mode = 0666
+               for _, e := range files {
+                       hdr.Name = basePath + e.path[len(rootPath):]
+                       hdr.ModTime = e.modTime
+                       hdr.Size = e.size
+                       if err = tarWr.WriteHeader(&hdr); err != nil {
+                               w.CloseWithError(err)
+                       }
+                       fd, err := os.Open(e.path)
+                       if err != nil {
+                               w.CloseWithError(err)
+                       }
+                       _, err = io.Copy(tarWr, bufio.NewReader(fd))
+                       if err != nil {
+                               w.CloseWithError(err)
+                       }
+                       fd.Close()
+               }
+               tarWr.Close()
+               w.Close()
+       }()
+       return
 }
 
-func (ctx *Ctx) TxFileChunked(
+func (ctx *Ctx) TxFile(
        node *Node,
        nice uint8,
        srcPath, dstPath string,
-       minSize int64,
        chunkSize int64,
+       minSize, maxSize int64,
 ) error {
+       dstPathSpecified := false
        if dstPath == "" {
                if srcPath == "-" {
                        return errors.New("Must provide destination filename")
                }
                dstPath = filepath.Base(srcPath)
+       } else {
+               dstPathSpecified = true
        }
        dstPath = filepath.Clean(dstPath)
        if filepath.IsAbs(dstPath) {
                return errors.New("Relative destination path required")
        }
-       reader, src, fileSize, err := prepareTxFile(srcPath)
-       if src != nil {
-               defer src.Close()
+       reader, closer, fileSize, archived, err := prepareTxFile(srcPath)
+       if closer != nil {
+               defer closer.Close()
        }
        if err != nil {
                return err
        }
+       if fileSize > maxSize {
+               return errors.New("Too big than allowed")
+       }
+       if archived && !dstPathSpecified {
+               dstPath += TarExt
+       }
 
        if fileSize <= chunkSize {
                pkt, err := NewPkt(PktTypeFile, nice, []byte(dstPath))
@@ -243,8 +332,7 @@ func (ctx *Ctx) TxFileChunked(
                if err == nil {
                        ctx.LogI("tx", sds, "sent")
                } else {
-                       sds["err"] = err
-                       ctx.LogE("tx", sds, "sent")
+                       ctx.LogE("tx", SdsAdd(sds, SDS{"err": err}), "sent")
                }
                return err
        }
@@ -299,8 +387,7 @@ func (ctx *Ctx) TxFileChunked(
                if err == nil {
                        ctx.LogI("tx", sds, "sent")
                } else {
-                       sds["err"] = err
-                       ctx.LogE("tx", sds, "sent")
+                       ctx.LogE("tx", SdsAdd(sds, SDS{"err": err}), "sent")
                        return err
                }
                hsh.Sum(metaPkt.Checksums[chunkNum][:0])
@@ -333,8 +420,7 @@ func (ctx *Ctx) TxFileChunked(
        if err == nil {
                ctx.LogI("tx", sds, "sent")
        } else {
-               sds["err"] = err
-               ctx.LogE("tx", sds, "sent")
+               ctx.LogE("tx", SdsAdd(sds, SDS{"err": err}), "sent")
        }
        return err
 }
@@ -370,8 +456,7 @@ func (ctx *Ctx) TxFreq(
        if err == nil {
                ctx.LogI("tx", sds, "sent")
        } else {
-               sds["err"] = err
-               ctx.LogE("tx", sds, "sent")
+               ctx.LogE("tx", SdsAdd(sds, SDS{"err": err}), "sent")
        }
        return err
 }
@@ -419,8 +504,7 @@ func (ctx *Ctx) TxExec(
        if err == nil {
                ctx.LogI("tx", sds, "sent")
        } else {
-               sds["err"] = err
-               ctx.LogE("tx", sds, "sent")
+               ctx.LogE("tx", SdsAdd(sds, SDS{"err": err}), "sent")
        }
        return err
 }
@@ -433,6 +517,11 @@ func (ctx *Ctx) TxTrns(node *Node, nice uint8, size int64, src io.Reader) error
                "size": strconv.FormatInt(size, 10),
        }
        ctx.LogD("tx", sds, "taken")
+       if !ctx.IsEnoughSpace(size) {
+               err := errors.New("is not enough space")
+               ctx.LogE("tx", SdsAdd(sds, SDS{"err": err}), err.Error())
+               return err
+       }
        tmp, err := ctx.NewTmpFileWHash()
        if err != nil {
                return err
@@ -445,8 +534,7 @@ func (ctx *Ctx) TxTrns(node *Node, nice uint8, size int64, src io.Reader) error
        if err == nil {
                ctx.LogI("tx", sds, "sent")
        } else {
-               sds["err"] = err
-               ctx.LogI("tx", sds, "sent")
+               ctx.LogI("tx", SdsAdd(sds, SDS{"err": err}), "sent")
        }
        os.Symlink(nodePath, filepath.Join(ctx.Spool, node.Name))
        return err
index bde218f99043413a8be2134b533567fe1172e6a5..ca77d4c553fb19e9d303dadef6e7a85d21058bf3 100644 (file)
@@ -27,7 +27,7 @@ import (
        "testing"
        "testing/quick"
 
-       "github.com/davecgh/go-xdr/xdr2"
+       xdr "github.com/davecgh/go-xdr/xdr2"
        "golang.org/x/crypto/blake2b"
 )