$(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)/...
@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
@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.
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: {
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
@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,
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
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
/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
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
@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.
@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}
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
@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
@item
@strong{Несовместимое} изменение формата конфигурационного файла:
YAML заменён на Hjson, из-за его гораздо большей простоты, без
-замеÑ\82ного поÑ\82еÑ\80Ñ\8f функционала и удобства.
+замеÑ\82ной поÑ\82еÑ\80и функционала и удобства.
@item
@strong{Несовместимое} изменение формата простых пакетов. Работа со
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
@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
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 ------------------------
Основные усовершенствования в этом релизе:
-$(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 ------------------------
-# $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/
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
-======================================================================
+[
+{ type: install
+ message: <<EOM
- Add the following lines to /etc/rc.conf to enable nncp-daemon,
nncp-caller and nncp-toss:
# umask 077
# nncp-cfgnew > %%PREFIX%%/etc/nncp.hjson
-======================================================================
+EOM
+}
+]
)
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"`
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"`
}
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 {
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
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,
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 {
"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"
)
"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"
# 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: {
# 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
+ # # },
+ # # ]
# }
}
}`,
nncp.ToBase32(nodeOur.SignPub[:]),
nncp.ToBase32(nodeOur.NoisePub[:]),
nncp.DefaultSendmailPath,
+ nncp.DefaultSendmailPath,
)
}
if _, err = nncp.CfgParse([]byte(cfgRaw)); err != nil {
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.
`)
}
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)
}
}
"log"
"os"
"path/filepath"
- "strconv"
"strings"
"go.cypherpunks.ru/nncp/v5"
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")
"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"
)
"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"
"path/filepath"
"strconv"
- "github.com/davecgh/go-xdr/xdr2"
+ xdr "github.com/davecgh/go-xdr/xdr2"
"go.cypherpunks.ru/nncp/v5"
)
"os"
"path/filepath"
- "golang.org/x/sys/unix"
"syscall"
+
+ "golang.org/x/sys/unix"
)
type Ctx struct {
Debug bool
NotifyFile *FromToJSON
NotifyFreq *FromToJSON
+ NotifyExec map[string]*FromToJSON
}
func (ctx *Ctx) FindNode(id string) (*Node, error) {
"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"
golang.org/x/sys v0.0.0-20191113165036-4c7a9d0fe056
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
)
+
+go 1.10
"path/filepath"
"strconv"
- "github.com/davecgh/go-xdr/xdr2"
+ xdr "github.com/davecgh/go-xdr/xdr2"
)
type TRxTx string
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
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,
}
}
"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"
"testing"
"testing/quick"
- "github.com/davecgh/go-xdr/xdr2"
+ xdr "github.com/davecgh/go-xdr/xdr2"
)
func TestPktEncWrite(t *testing.T) {
"sync"
"time"
- "github.com/davecgh/go-xdr/xdr2"
+ xdr "github.com/davecgh/go-xdr/xdr2"
"github.com/flynn/noise"
)
import (
"bufio"
"bytes"
+ "encoding/base64"
"fmt"
"io"
"io/ioutil"
"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"
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(
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)
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]
"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 {
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)...,
ctx.Neigh[*job.PktEnc.Sender].Name,
dst,
humanize.IBytes(uint64(pktSize)),
- ))
+ ), nil)
cmd.Run()
}
}
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
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()
}
}
"testing"
"testing/quick"
- "github.com/davecgh/go-xdr/xdr2"
+ xdr "github.com/davecgh/go-xdr/xdr2"
"golang.org/x/crypto/blake2b"
)
DefaultNiceFile,
src,
fileName,
+ MaxFileSize,
1<<15,
+ MaxFileSize,
); err != nil {
t.Error(err)
return false
DefaultNiceFile,
srcPath,
"samefile",
+ MaxFileSize,
1<<15,
+ MaxFileSize,
); err != nil {
t.Error(err)
return false
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
package nncp
import (
+ "archive/tar"
"bufio"
"bytes"
"crypto/rand"
"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,
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
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()
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))
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
}
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])
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
}
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
}
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
}
"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
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
"testing"
"testing/quick"
- "github.com/davecgh/go-xdr/xdr2"
+ xdr "github.com/davecgh/go-xdr/xdr2"
"golang.org/x/crypto/blake2b"
)