From a4262555b0506c1cf85b9d5d4dae58bf3922e629 Mon Sep 17 00:00:00 2001 From: Sergey Matveev Date: Sat, 17 Feb 2018 13:38:50 +0300 Subject: [PATCH] Remote command execution --- README | 15 +- README.RU | 8 +- common.mk | 8 +- doc/about.ru.texi | 5 +- doc/about.texi | 17 +- doc/cfg.texi | 43 ++-- doc/cmds.texi | 50 ++-- doc/comparison.ru.texi | 2 +- doc/comparison.texi | 2 +- doc/integration.texi | 149 +++++++----- doc/news.ru.texi | 28 ++- doc/news.texi | 27 ++- doc/niceness.texi | 2 +- doc/pkt.texi | 11 +- doc/usecases.ru.texi | 6 +- doc/usecases.texi | 6 +- doc/workflow.texi | 6 +- makedist.sh | 21 +- ports/nncp/Makefile | 4 +- ports/nncp/pkg-descr | 13 +- ports/nncp/pkg-plist | 2 +- src/cypherpunks.ru/nncp/cfg.go | 18 +- .../nncp/cmd/nncp-cfgnew/main.go | 4 +- .../nncp/cmd/{nncp-mail => nncp-exec}/main.go | 39 ++-- src/cypherpunks.ru/nncp/cmd/nncp-pkt/main.go | 11 +- src/cypherpunks.ru/nncp/cmd/nncp-toss/main.go | 4 +- src/cypherpunks.ru/nncp/humanizer.go | 12 +- src/cypherpunks.ru/nncp/node.go | 2 +- src/cypherpunks.ru/nncp/pkt.go | 13 +- src/cypherpunks.ru/nncp/pkt_test.go | 4 +- src/cypherpunks.ru/nncp/toss.go | 46 ++-- src/cypherpunks.ru/nncp/toss_test.go | 34 ++- src/cypherpunks.ru/nncp/tx.go | 214 +++++++----------- src/cypherpunks.ru/nncp/tx_test.go | 9 +- 34 files changed, 463 insertions(+), 372 deletions(-) rename src/cypherpunks.ru/nncp/cmd/{nncp-mail => nncp-exec}/main.go (61%) diff --git a/README b/README index dab1ca8..58db3ed 100644 --- a/README +++ b/README @@ -1,13 +1,14 @@ NNCP (Node to Node copy) is a collection of utilities simplifying -secure store-and-forward files and mail exchanging. +secure store-and-forward files, mail and commands exchanging. This utilities are intended to help build up small size (dozens of -nodes) ad-hoc friend-to-friend (F2F) statically routed darknet networks -for fire-and-forget secure reliable files, file requests and Internet -mail transmission. All packets are integrity checked, end-to-end -encrypted (E2EE), explicitly authenticated by known participants public -keys. Onion encryption is applied to relayed packets. Each node acts -both as a client and server, can use push and poll behaviour model. +nodes) ad-hoc friend-to-friend (F2F) statically routed darknet +delay-tolerant networks for fire-and-forget secure reliable files, file +requests, Internet mail and commands transmission. All packets are +integrity checked, end-to-end encrypted (E2EE), explicitly authenticated +by known participants public keys. Onion encryption is applied to +relayed packets. Each node acts both as a client and server, can use +push and poll behaviour model. Out-of-box offline sneakernet/floppynet, dead drops, sequential and append-only CD-ROM/tape storages, air-gapped computers support. But diff --git a/README.RU b/README.RU index eccba53..ba9829c 100644 --- a/README.RU +++ b/README.RU @@ -1,10 +1,10 @@ NNCP (Node to Node copy) это набор утилит упрощающий безопасный обмен файлами и почтой в режиме сохранить-и-переслать. -Эти утилиты предназначены помочь с построением одноранговых сетей -небольшого размера (дюжины узлов), в режиме друг-к-другу (F2F) со -статической маршрутизацией для безопасной надёжной передачи файлов, -запросов на передачу файлов и Интернет почты по принципу +Эти утилиты предназначены помочь с построением одноранговых устойчивых к +разрывам сетей небольшого размера (дюжины узлов), в режиме друг-к-другу +(F2F) со статической маршрутизацией для безопасной надёжной передачи +файлов, запросов на передачу файлов, Интернет почты и команд по принципу выстрелил-и-забыл. Все пакеты проверяются на целостность, шифруются по принципу точка-точка (E2EE), аутентифицируются известными публичными ключами участников. Луковичное (onion) шифрование применяется ко всем diff --git a/common.mk b/common.mk index 34ee30d..145e843 100644 --- a/common.mk +++ b/common.mk @@ -25,10 +25,10 @@ ALL = \ nncp-cfgnew \ nncp-check \ nncp-daemon \ + nncp-exec \ nncp-file \ nncp-freq \ nncp-log \ - nncp-mail \ nncp-pkt \ nncp-reass \ nncp-rm \ @@ -62,6 +62,9 @@ nncp-check: nncp-daemon: GOPATH=$(GOPATH) go build -ldflags "$(LDFLAGS)" cypherpunks.ru/nncp/cmd/nncp-daemon +nncp-exec: + GOPATH=$(GOPATH) go build -ldflags "$(LDFLAGS)" cypherpunks.ru/nncp/cmd/nncp-exec + nncp-file: GOPATH=$(GOPATH) go build -ldflags "$(LDFLAGS)" cypherpunks.ru/nncp/cmd/nncp-file @@ -71,9 +74,6 @@ nncp-freq: nncp-log: GOPATH=$(GOPATH) go build -ldflags "$(LDFLAGS)" cypherpunks.ru/nncp/cmd/nncp-log -nncp-mail: - GOPATH=$(GOPATH) go build -ldflags "$(LDFLAGS)" cypherpunks.ru/nncp/cmd/nncp-mail - nncp-pkt: GOPATH=$(GOPATH) go build -ldflags "$(LDFLAGS)" cypherpunks.ru/nncp/cmd/nncp-pkt diff --git a/doc/about.ru.texi b/doc/about.ru.texi index 727402c..526d37f 100644 --- a/doc/about.ru.texi +++ b/doc/about.ru.texi @@ -4,11 +4,12 @@ NNCP (Node to Node copy) это набор утилит упрощающий безопасный обмен файлами и почтой в режиме сохранить-и-переслать. -Эти утилиты предназначены помочь с построением однораговых сетей +Эти утилиты предназначены помочь с построением однораговых +@url{https://ru.wikipedia.org/wiki/DTN, устойчивых к разрывам} сетей небольшого размера (дюжины узлов), в режиме @url{https://ru.wikipedia.org/wiki/Friend-to-friend, друг-к-другу} (F2F) со статической маршрутизацией для безопасной надёжной передачи файлов, -запросов на передачу файлов и Интернет почты по принципу +запросов на передачу файлов, Интернет почты и команд по принципу выстрелил-и-забыл. Все пакеты проверяются на целостность, шифруются по принципу @url{https://en.wikipedia.org/wiki/End-to-end_encryption, точка-точка}, аутентифицируются известными публичными ключами diff --git a/doc/about.texi b/doc/about.texi index ffe4be0..4a7ab05 100644 --- a/doc/about.texi +++ b/doc/about.texi @@ -6,14 +6,15 @@ See also this page @ref{Об утилитах, on russian}. This utilities are intended to help build up small size (dozens of nodes) ad-hoc @url{https://en.wikipedia.org/wiki/Friend-to-friend, friend-to-friend} (F2F) statically routed -@url{https://en.wikipedia.org/wiki/Darknet, darknet} networks for -fire-and-forget secure reliable files, file requests and Internet mail -transmission. All packets are integrity checked, -@url{https://en.wikipedia.org/wiki/End-to-end_encryption, end-to-end} -encrypted, explicitly authenticated by known participants public keys. -@url{https://en.wikipedia.org/wiki/Onion_routing, Onion encryption} is -applied to relayed packets. Each node acts both as a client and server, -can use push and poll behaviour model. +@url{https://en.wikipedia.org/wiki/Darknet, darknet} +@url{https://en.wikipedia.org/wiki/Delay-tolerant_networking, delay-tolerant} +networks for fire-and-forget secure reliable files, file requests, +Internet mail and commands transmission. All packets are integrity +checked, @url{https://en.wikipedia.org/wiki/End-to-end_encryption, +end-to-end} encrypted, explicitly authenticated by known participants +public keys. @url{https://en.wikipedia.org/wiki/Onion_routing, Onion +encryption} is applied to relayed packets. Each node acts both as a +client and server, can use push and poll behaviour model. Out-of-box offline @url{https://en.wikipedia.org/wiki/Sneakernet, sneakernet/floppynet}, @url{https://en.wikipedia.org/wiki/Dead_drop, diff --git a/doc/cfg.texi b/doc/cfg.texi index 3483f11..5be0382 100644 --- a/doc/cfg.texi +++ b/doc/cfg.texi @@ -27,13 +27,15 @@ neigh: exchpub: CYVGQ...PSEWQ signpub: 2NMVC...CMH5Q noisepub: KIBKK...ESM7Q - sendmail: [/usr/sbin/sendmail] + exec: + sendmail: [/usr/sbin/sendmail] alice: id: XJZBK...65IJQ exchpub: MJACJ...FAI6A signpub: T4AFC...N2FRQ noisepub: UBM5K...VI42A - sendmail: ["/bin/sh", "-c", "false"] + exec: + flag: ["/usr/bin/touch", "-t"] incoming: /home/alice/incoming onlinedeadline: 1800 maxonlinetime: 3600 @@ -47,7 +49,10 @@ neigh: id: 2IZNP...UYGYA exchpub: WFLMZ...B7NHA signpub: GTGXG...IE3OA - sendmail: [/usr/sbin/sendmail] + exec: + sendmail: [/usr/sbin/sendmail] + warcer: [/path/to/warcer.sh] + wgeter: [/path/to/wgeter.sh] freq: /home/bob/pub freqchunked: 1024 freqminsize: 2048 @@ -62,9 +67,9 @@ log} file. @strong{notify} section contains notification settings for successfully tossed file and freq packets. Corresponding @strong{from} and @strong{to} fields will be substituted in notification email message. -@emph{neigh/self/sendmail} will be used as a local mailer. You can omit -either of those two @emph{from}/@emph{to} sections to omit corresponding -notifications, or the whole section at once. +@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 +corresponding notifications, or the whole section at once. @strong{self} section contains our node's private keypairs. @strong{exch*} and @strong{sign*} are used during @ref{Encrypted, @@ -85,13 +90,25 @@ node has the following fields: If present, then node can be online called using @ref{Sync, synchronization protocol}. Contains authentication public key. -@anchor{CfgSendmail} -@item sendmail -An array containing path to executable and its command line arguments -that is called for mail sending. If it is empty, then no mail processing -will be performed from that node. Sendmail command -@command{["/bin/foo", "bar"]} called the following way: -@command{NNCP_NICE=NICE NNCP_SENDER=NODEID /bin/foo bar RCPT1 RCPT2 ... < MSG}. +@anchor{CfgExec} +@item exec +Dictionary consisting of handles and corresponding command line +arguments. In example above there are @command{sendmail} handles, +@command{warcer}, @command{wgeter} and @command{flag} one. Remote node +can queue some handle execution with providing additional command line +arguments and the body fed to command's stdin. + +@verb{|sendmail: ["/usr/sbin/sendmail", "-t"]|} handle, when called by +@verb{|echo hello world | nncp-exec OURNODE sendmail ARG0 ARG1 ARG2|} +command, will execute: + +@verbatim +echo hello world | + NNCP_SELF=OURNODE \ + NNCP_SENDER=REMOTE \ + NNCP_NICE=64 \ + /usr/sbin/sendmail -t ARG0 ARG1 ARG2 +@end verbatim @anchor{CfgIncoming} @item incoming diff --git a/doc/cmds.texi b/doc/cmds.texi index 49a34c6..e7090af 100644 --- a/doc/cmds.texi +++ b/doc/cmds.texi @@ -18,6 +18,10 @@ Nearly all commands have the following common options: @item -nice Set desired outgoing packet @ref{Niceness, niceness level}. 1-255 values are allowed. +@item -replynice + Set desired reply packet @ref{Niceness, niceness level}. Only freq + and exec packets look at that niceness level. 1-255 values are + allowed. @item -node Process only single specified node. @item -via @@ -233,6 +237,39 @@ time to time. can handle. @option{-bind} option specifies @option{addr:port} it must bind to and listen. +@node nncp-exec +@section nncp-exec + +@verbatim +% nncp-exec [options] NODE HANDLE [ARG0 ARG1 ...] +@end verbatim + +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*} +appended and decompressed body fed to command's stdin. + +For example, if remote side has following configuration file for your +node: + +@verbatim +exec: + sendmail: [/usr/sbin/sendmail, "-t"] + appender: ["/bin/sh", "-c", "cat >> /append"] +@end verbatim + +then executing @verb{|echo My message | nncp-exec -replynice 123 REMOTE +sendmail root@localhost|} will lead to executing of: + +@verbatim +echo My message | + NNCP_SELF=REMOTE \ + NNCP_SENDER=OurNodeId \ + NNCP_NICE=123 \ + /usr/sbin/sendmail -t root@localhost +@end verbatim + + @node nncp-file @section nncp-file @@ -295,19 +332,6 @@ queuing. Parse @ref{Log, log} file and print out its records in human-readable form. -@node nncp-mail -@section nncp-mail - -@verbatim -% nncp-mail [options] NODE USER ... -@end verbatim - -Send mail, that is read from stdin, to @option{NODE} and specified -@option{USER}s. Mail message will be compressed. After receiving, remote -side will execute specified @ref{CfgSendmail, sendmail} command with -@option{USER}s appended as a command line argument and feed decompressed -mail body to that command's stdin. - @node nncp-pkt @section nncp-pkt diff --git a/doc/comparison.ru.texi b/doc/comparison.ru.texi index 73580ae..5c90671 100644 --- a/doc/comparison.ru.texi +++ b/doc/comparison.ru.texi @@ -15,7 +15,7 @@ @item Передача новостей @tab @strong{Да} @tab @strong{Да} @tab Нет @tab Нет @item Передача файлов @tab @strong{Да} @tab @strong{Да} @tab @strong{Да} @tab Нет @item Разбиение файлов на части @tab Нет @tab @strong{Да} @tab @strong{Да} @tab Нет -@item Удалённое исполнение команд @tab @strong{Да} @tab Нет @tab Нет @tab Нет +@item Удалённое исполнение команд @tab @strong{Да} @tab Нет @tab @strong{Да} @tab Нет @item Возобновляемое скачивание @tab @strong{Да} @tab @strong{Да} @tab @strong{Да} @tab Нет @item Приоритезация пакетов @tab @strong{Да} @tab Нет @tab @strong{Да} @tab Нет @item Сжатие почты @tab Нет @tab @strong{Да} @tab @strong{Да} @tab Нет diff --git a/doc/comparison.texi b/doc/comparison.texi index e8edddd..03087c1 100644 --- a/doc/comparison.texi +++ b/doc/comparison.texi @@ -14,7 +14,7 @@ FidoNet} Technology Networks) and @url{https://en.wikipedia.org/wiki/SMTP, SMTP} @item News transmission @tab @strong{Yes} @tab @strong{Yes} @tab No @tab No @item File transmission @tab @strong{Yes} @tab @strong{Yes} @tab @strong{Yes} @tab No @item Chunked files @tab No @tab @strong{Yes} @tab @strong{Yes} @tab No -@item Remote command execution @tab @strong{Yes} @tab No @tab No @tab No +@item Remote command execution @tab @strong{Yes} @tab No @tab @strong{Yes} @tab No @item Resumable downloads @tab @strong{Yes} @tab @strong{Yes} @tab @strong{Yes} @tab No @item Packets prioritizing @tab @strong{Yes} @tab No @tab @strong{Yes} @tab No @item Mail compression @tab No @tab @strong{Yes} @tab @strong{Yes} @tab No diff --git a/doc/integration.texi b/doc/integration.texi index 55e154c..62c0933 100644 --- a/doc/integration.texi +++ b/doc/integration.texi @@ -9,6 +9,7 @@ making them store-and-forward friendly. * Web feeds: Feeds. * Web pages: WARCs. * BitTorrent and huge files: BitTorrent. +* Downloading service: DownloadService. * Git:: * Multimedia streaming: Multimedia. @end menu @@ -27,7 +28,7 @@ mail to a LAN that is connected via NNCP. @itemize -@item You need an @ref{nncp-mail} program that extracts the sender +@item You need an @ref{nncp-exec} program that extracts the sender address from mail that arrives via NNCP, and that feeds the mail into the Postfix @command{sendmail} command. @@ -36,13 +37,13 @@ delivery via NNCP: @verbatim /usr/local/etc/postfix/master.cf: nncp unix - n n - - pipe - flags=F user=nncp argv=nncp-mail -quiet $nexthop $recipient + flags=F user=nncp argv=nncp-exec -quiet $nexthop sendmail $recipient @end verbatim -This runs the @command{nncp-mail} command to place outgoing mail into +This runs the @command{nncp-exec} command to place outgoing mail into the NNCP queue after replacing @var{$nexthop} by the the receiving NNCP node and after replacing @var{$recipient} by the recipients. The -@command{pipe(8)} delivery agent executes the @command{nncp-mail} +@command{pipe(8)} delivery agent executes the @command{nncp-exec} command without assistance from the shell, so there are no problems with shell meta characters in command-line parameters. @@ -89,7 +90,7 @@ Here is how to relay mail from a LAN via NNCP to the Internet. @itemize -@item You need an @ref{nncp-mail} program that extracts the sender +@item You need an @ref{nncp-exec} program that extracts the sender address from mail that arrives via NNCP, and that feeds the mail into the Postfix @command{sendmail} command. @@ -115,13 +116,13 @@ mail delivery via NNCP: @verbatim /usr/local/etc/postfix/master.cf: nncp unix - n n - - pipe - flags=F user=nncp argv=nncp-mail -quiet $nexthop $recipient + flags=F user=nncp argv=nncp-exec -quiet $nexthop sendmail $recipient @end verbatim -This runs the @command{nncp-mail} command to place outgoing mail into +This runs the @command{nncp-exec} command to place outgoing mail into the NNCP queue. It substitutes the hostname (@emph{nncp-gateway}, or whatever you specified) and the recipients before executing the command. -The @command{nncp-mail} command is executed without assistance from the +The @command{nncp-exec} command is executed without assistance from the shell, so there are no problems with shell meta characters. @item Execute the command @command{postfix reload} to make the changes @@ -229,59 +230,6 @@ utility, producing usual directory hierarchy: --progress @end verbatim -Also you can create separate NNCP node those mail receiver will be the -script downloading website's page and send you its WARC representation -as a file. You can configure @option{sendmail} option like this: - -@verbatim -% cat /usr/local/etc/nncp.yaml -[...] - stargrave.org: - [...] - sendmail: ["/bin/sh", "/path/to/warcer.sh"] -[...] -@end verbatim - -And @file{warcer.sh} contents are: - -@verbatim -#!/bin/sh -ex - -user_agent="Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27" - -name="$1" -read cmdline - -tmp=$(mktemp -d) -cd $tmp -warc_name=$name-$(date '+%Y%M%d%H%m%S') -wget \ - --page-requisites \ - --convert-links \ - --adjust-extension \ - --restrict-file-names=ascii \ - --span-hosts \ - --random-wait \ - --execute robots=off \ - --user-agent "$user_agent" \ - --reject '*.woff*,*.ttf,*.eot,*.js' \ - --tries 10 \ - --warc-file $warc_name \ - --no-warc-compression \ - --no-warc-keep-log \ - $cmdline || : -xz -9 "$warc_name".warc -nncp-file "$warc_name".warc.xz $NNCP_SENDER: -rm -r $tmp -@end verbatim - -Now you can queueu that node to send you some website's page: - -@verbatim -% echo http://www.nncpgo.org/Postfix.html | - nncp-mail remote.node nncp-postfix-page -@end verbatim - @node BitTorrent @section BitTorrent and huge files @@ -347,6 +295,85 @@ http://www.nncpgo.org/download/nncp-0.11.tar.xz.sig and all that downloaded (@file{nncp.txz}, @file{nncp.txz.sig}) files will be sent to @file{remote.node} when finished. +@node DownloadService +@section Downloading service + +Previous sections tell about manual downloading and sending results to +remote node. But one wish to remotely initiate downloading. That can be +easily solved with @ref{CfgExec, exec} handles. + +@verbatim +exec: + warcer: ["/bin/sh", "/path/to/warcer.sh"] + wgeter: ["/bin/sh", "/path/to/wgeter.sh"] + aria2c: [ + "/usr/local/bin/aria2c", + "--on-download-complete", "send-downloaded.sh", + "--on-bt-download-complete", "send-downloaded.sh" + ] +@end verbatim + +@file{warcer.sh} contents: + +@verbatim +#!/bin/sh -ex + +user_agent="Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27" + +name="$1" +read cmdline + +tmp=$(mktemp -d) +cd $tmp +warc_name=$name-$(date '+%Y%M%d%H%m%S') +wget \ + --page-requisites \ + --convert-links \ + --adjust-extension \ + --restrict-file-names=ascii \ + --span-hosts \ + --random-wait \ + --execute robots=off \ + --user-agent "$user_agent" \ + --reject '*.woff*,*.ttf,*.eot,*.js' \ + --tries 10 \ + --warc-file $warc_name \ + --no-warc-compression \ + --no-warc-keep-log \ + $cmdline || : +xz -9 "$warc_name".warc +nncp-file -nice $NNCP_NICE "$warc_name".warc.xz $NNCP_SENDER: +rm -r $tmp +@end verbatim + +@file{wgeter.sh} contents: + +@verbatim +#!/bin/sh -ex + +name="$1" +read cmdline +tmp=$(mktemp) +wget --output-document=$tmp $cmdline +xz -9 $tmp +nncp-file -nice $NNCP_NICE $tmp.xz $NNCP_SENDER:$name.xz +rm $tmp.xz +@end verbatim + +Now you can queue that node to send you some website's page, file or +BitTorrents: + +@verbatim +% echo http://www.nncpgo.org/Postfix.html | + nncp-exec remote.node warcer postfix-whole-page +% echo http://www.nncpgo.org/Postfix.html | + nncp-exec remote.node wgeter postfix-html-page +% echo \ + http://www.nncpgo.org/download/nncp-0.11.tar.xz + http://www.nncpgo.org/download/nncp-0.11.tar.xz.sig | + nncp-exec remote.node aria2c +@end verbatim + @node Git @section Integration with Git diff --git a/doc/news.ru.texi b/doc/news.ru.texi index 84b15c4..ea8606a 100644 --- a/doc/news.ru.texi +++ b/doc/news.ru.texi @@ -5,18 +5,32 @@ @subsection Релиз 3.0 @itemize @item +@strong{Несовместимое} изменение формата простых пакетов. Работа со +старыми версиями не поддерживается. +@item +Добавлена возможность удалённого исполнения команд, путём +конфигурирования @option{exec} опции конфигурационного файла и +использования команды @command{nncp-exec}: + @itemize + @item + Команда @command{nncp-mail} заменена более гибкой и широкой + @command{nncp-exec}. Вместо вызова @verb{|nncp-mail NODE RECIPIENT|} + нужно использовать @verb{|nncp-exec NODE sendmail RECIPIENT|}. + @item + @option{sendmail} опция конфигурационного файла заменена на более гибкую + @option{exec}. @verb{|sendmail: [...]|} нужно заменить на @verb{|exec: + sendmail: [...]|}. + @end itemize +@item Возможность переопределить @option{via} опцию конфигурации для целевого узла через @option{-via} опцию командной строки для следующих команд: -@command{nncp-file}, @command{nncp-freq}, @command{nncp-mail}. +@command{nncp-file}, @command{nncp-freq}, @command{nncp-exec}. @item Chunked файлы, меньшего размера чем указанный chunk, отправляются просто в виде одного файла. @item -@strong{Несовместимое} изменение формата простых пакетов. Добавлено поле -@emph{NICE}. Работа со старыми версиями не поддерживается. -@item -Sendmail команда вызывается с дополнительной переменной окружения -@env{NNCP_NICE} содержащая значение приоритета пакета с сообщением. +Exec команды вызываются с дополнительными переменными окружения +@env{NNCP_NICE} и @env{NNCP_SELF}. @item Отправляемые файлы в ответ на запрос имеют приоритет указанный в запросе. Указать их желаемый приоритет во время вызова @command{nncp-freq} можно @@ -24,7 +38,7 @@ Sendmail команда вызывается с дополнительной п @item Команде @command{nncp-toss} можно сказать не обрабатывать определённые типы пакетов, за счёт опций @option{-nofile}, @option{-nofreq}, -@option{-nomail}, @option{-notrns}. +@option{-noexec}, @option{-notrns}. @item По-умолчанию @command{nncp-file} команда для @option{-minsize}/@option{-chunked} опций использует diff --git a/doc/news.texi b/doc/news.texi index d1a89b1..4d9e7dc 100644 --- a/doc/news.texi +++ b/doc/news.texi @@ -7,25 +7,38 @@ See also this page @ref{Новости, on russian}. @section Release 3.0 @itemize @item +@strong{Incompatible} plain packet format changes. Older versions are +not supported. +@item +Ability to queue remote command execution, by configuring @option{exec} +option in configuration file and using @command{nncp-exec} command: + @itemize + @item + @command{nncp-mail} command is replaced with more flexible + @command{nncp-exec}. Instead of @verb{|nncp-mail NODE RECIPIENT|} + you must use @verb{|nncp-exec NODE sendmail RECIPIENT|}. + @item + @option{sendmail} configuration file option is replaced with + @option{exec}. @verb{|sendmail: [...]|} must be replaced with + @verb{|exec: sendmail: [...]|}. + @end itemize +@item Ability to override @option{via} configuration option for destination node via @option{-via} command line option for following commands: -@command{nncp-file}, @command{nncp-freq}, @command{nncp-mail}. +@command{nncp-file}, @command{nncp-freq}, @command{nncp-exec}. @item Chunked files, having size less than specified chunk size, will be sent as an ordinary single file. @item -@strong{Incompatible} plain packet format changes. @emph{NICE} field is -added. Older versions are not supported. -@item -Sendmail command is invoked with additional @env{NNCP_FILE} environment -variable containing niceness level from incoming message packet. +Exec commands are invoked with additional @env{NNCP_NICE} and +@env{NNCP_SELF} environment variables. @item Files, that are sent as a reply to freq, have niceness level taken from the freq packet. You can set desired niceness during @command{nncp-freq} invocation using @option{-replynice} option. @item @command{nncp-toss} command can ignore specified packet types during -processing: @option{-nofile}, @option{-nofreq}, @option{-nomail}, +processing: @option{-nofile}, @option{-nofreq}, @option{-noexec}, @option{-notrns}. @item @command{nncp-file} command uses diff --git a/doc/niceness.texi b/doc/niceness.texi index 50f392b..b0f99e9 100644 --- a/doc/niceness.texi +++ b/doc/niceness.texi @@ -10,6 +10,6 @@ Send big files with higher nicer level! That will guarantee you that higher priority packets, like mail messages, will pass first, even when lower priority packet was already been partly downloaded. -There are default niceness levels built-in for @ref{nncp-mail}, +There are default niceness levels built-in for @ref{nncp-exec}, @ref{nncp-file} and @ref{nncp-freq} commands. But pay attention that it can give information about underlying payload to the adversary! diff --git a/doc/pkt.texi b/doc/pkt.texi index 9c8c32e..b1c2c38 100644 --- a/doc/pkt.texi +++ b/doc/pkt.texi @@ -13,7 +13,7 @@ All packets are @section Plain packet Plain packet contains either the whole file, or file request (freq), or -transition packet or email message. It is called "plain", because it +transition packet or exec message. It is called "plain", because it contains plaintext, but plain packets would never be stored on your hard drive. @@ -31,7 +31,7 @@ drive. @verb{|N N C P P 0x00 0x00 0x01|} @item Payload type @tab unsigned integer @tab - 0 (file), 1 (freq), 2 (mail), 3 (transition) + 0 (file), 1 (freq), 2 (exec), 3 (transition) @item Niceness @tab unsigned integer @tab 1-255, preferred packet @ref{Niceness, niceness} level @@ -43,7 +43,7 @@ drive. @itemize @item UTF-8 encoded destination path for file transfer @item UTF-8 encoded source path for file request - @item UTF-8 encoded, space separated, email recipients list + @item UTF-8 encoded, zero byte separated, exec's arguments @item Node's id the transition packet must be relayed on @end itemize @end multitable @@ -58,7 +58,7 @@ Depending on the packet's type, payload could store: @itemize @item File contents @item Destination path for freq -@item @url{http://zlib.net/, zlib} compressed email +@item @url{http://zlib.net/, zlib} compressed exec body @item Whole encrypted packet we need to relay on @end itemize @@ -66,8 +66,7 @@ Also depending on packet's type, niceness level means: @itemize @item Preferable niceness level for files sent by freq -@item @env{NNCP_NICE} variable's value passed during - @ref{CfgSendmail} invocation. +@item @env{NNCP_NICE} variable's value passed during @ref{CfgExec} invocation. @end itemize @node Encrypted diff --git a/doc/usecases.ru.texi b/doc/usecases.ru.texi index 1407c8e..3e3caba 100644 --- a/doc/usecases.ru.texi +++ b/doc/usecases.ru.texi @@ -35,7 +35,7 @@ Postfix} SMTP сервер подключённый к Интернету. Но KISS}! Просто скажите вашим обоим Postfix-ам (на сервере и ноутбуке) отправлять -сообщения через NNCP (@ref{nncp-mail}) на заданный узел. Это делается +сообщения через NNCP (@ref{nncp-exec}) на заданный узел. Это делается аналогично тому как с UUCP, и описано в @url{http://www.postfix.org/UUCP_README.html, документации Postfix}. @@ -49,7 +49,7 @@ KISS}! @ref{nncp-daemon} может быть соединён с @ref{nncp-caller} длительное время -- он создаёт TCP соединение на многие часы. Когда SMTP сервер -получает письмо, то вызывает @ref{nncp-mail} для создания исходящего +получает письмо, то вызывает @ref{nncp-exec} для создания исходящего зашифрованного пакета. Демон ежесекундно проверяет исходящую директорию и сразу же посылает оповещение о недоставленных пакетах противоположной стороне, которая сразу же их может скачать. @@ -70,7 +70,7 @@ IMAP4, как правило, нет). У вас легковесный, сжа проблематично получить. Более того, каждый обрыв может приводить к отсылке данных с самого начала, что не всегда по карману. -Просто отправьте вашу @ref{nncp-mail, почту} и @ref{nncp-file, файлы} +Просто отправьте вашу @ref{nncp-exec, почту} и @ref{nncp-file, файлы} через NNCP. Вы сможете использовать или offline методы доставки -- читайте о них в следующем разделе, либо использовать поставляемый NNCP @ref{nncp-daemon, TCP демон}. diff --git a/doc/usecases.texi b/doc/usecases.texi index 47307f8..c276435 100644 --- a/doc/usecases.texi +++ b/doc/usecases.texi @@ -34,7 +34,7 @@ overcomplicated and bloated for the simple task. Not an option. @url{https://en.wikipedia.org/wiki/KISS_principle, KISS}! Just tell both of your Postfixes (on the server and notebook) to drop -email as a mail via NNCP (@ref{nncp-mail}) to specified node. This is +email as a mail via NNCP (@ref{nncp-exec}) to specified node. This is done similarly as with UUCP and as written in @url{http://www.postfix.org/UUCP_README.html, Postfix documentation}. @@ -48,7 +48,7 @@ that happened on the same machine. @ref{nncp-daemon} can be connected with @ref{nncp-caller} for a long time -- it can create TCP connection that lasts for many hours. When -SMTP server receives mail, it will call @ref{nncp-mail} creating an +SMTP server receives mail, it will call @ref{nncp-exec} creating an outbound encrypted packet. Daemon checks outbound directory each second and immediately sends notification about undelivered packets to remote side, that also downloads it at once. @@ -68,7 +68,7 @@ download continuation. SMTP does not support resuming at all and heavy messages is problematic to retrieve. Moreover, each disconnect leads to the same data retransmission again, that can not be afforded sometimes. -Just send your @ref{nncp-mail, mail} and @ref{nncp-file, files} through +Just send your @ref{nncp-exec, mail} and @ref{nncp-file, files} through NNCP. You can use either offline delivery methods -- read about them in the next section, or you can use included NNCP @ref{nncp-daemon, TCP daemon}. diff --git a/doc/workflow.texi b/doc/workflow.texi index 27a7ea9..def2929 100644 --- a/doc/workflow.texi +++ b/doc/workflow.texi @@ -12,9 +12,9 @@ following workflow: neighbours. Add their keys to your configuration file and do any other required configuration about their reachability, permissions of file or freq transmission. -@item Use @ref{nncp-file}, @ref{nncp-freq}, @ref{nncp-mail} +@item Use @ref{nncp-file}, @ref{nncp-freq}, @ref{nncp-exec} (@ref{Postfix, look how} Postfix SMTP server could be configured) -commands to queue file, freq and mail transmissions. Repeat as +commands to queue file, freq and exec transmissions. Repeat as many times any time as you wish. @item Depending on connection methods, either: @itemize @@ -29,7 +29,7 @@ many times any time as you wish. @end itemize @item After successful packet exchanging (or just simply from time to time), run @ref{nncp-toss} for tossing (decrypting and processing) all -inbound queues to receive mail messages, files, file requests and relay +inbound queues to receive exec messages, files, file requests and relay transition packets to other nodes. @end enumerate diff --git a/makedist.sh b/makedist.sh index 22b42d6..a81bdc7 100755 --- a/makedist.sh +++ b/makedist.sh @@ -103,12 +103,13 @@ NNCP (Node to Node copy) is a collection of utilities simplifying secure store-and-forward files and mail exchanging. This utilities are intended to help build up small size (dozens of -nodes) ad-hoc friend-to-friend (F2F) statically routed darknet networks -for fire-and-forget secure reliable files, file requests and Internet -mail transmission. All packets are integrity checked, end-to-end -encrypted (E2EE), explicitly authenticated by known participants public -keys. Onion encryption is applied to relayed packets. Each node acts -both as a client and server, can use push and poll behaviour model. +nodes) ad-hoc friend-to-friend (F2F) statically routed darknet +delay-tolerant networks for fire-and-forget secure reliable files, file +requests, Internet mail and commands transmission. All packets are +integrity checked, end-to-end encrypted (E2EE), explicitly authenticated +by known participants public keys. Onion encryption is applied to +relayed packets. Each node acts both as a client and server, can use +push and poll behaviour model. Out-of-box offline sneakernet/floppynet, dead drops, sequential and append-only CD-ROM/tape storages, air-gapped computers support. But @@ -147,10 +148,10 @@ Subject: [RU] Состоялся релиз NNCP $release NNCP (Node to Node copy) это набор утилит упрощающий безопасный обмен файлами и почтой в режиме сохранить-и-переслать. -Эти утилиты предназначены помочь с построением одноранговых сетей -небольшого размера (дюжины узлов), в режиме друг-к-другу (F2F) со -статической маршрутизацией для безопасной надёжной передачи файлов, -запросов на передачу файлов и Интернет почты по принципу +Эти утилиты предназначены помочь с построением одноранговых устойчивых к +разрывам сетей небольшого размера (дюжины узлов), в режиме друг-к-другу +(F2F) со статической маршрутизацией для безопасной надёжной передачи +файлов, запросов на передачу файлов, Интернет почты и команд по принципу выстрелил-и-забыл. Все пакеты проверяются на целостность, шифруются по принципу точка-точка (E2EE), аутентифицируются известными публичными ключами участников. Луковичное (onion) шифрование применяется ко всем diff --git a/ports/nncp/Makefile b/ports/nncp/Makefile index 4c53918..ce2e217 100644 --- a/ports/nncp/Makefile +++ b/ports/nncp/Makefile @@ -1,12 +1,12 @@ # $FreeBSD: head/net/nncp/Makefile 460314 2018-01-29 16:17:45Z yuri $ PORTNAME= nncp -DISTVERSION= 2.0 +DISTVERSION= 3.0 CATEGORIES= net MASTER_SITES= http://www.nncpgo.org/download/ MAINTAINER= stargrave@stargrave.org -COMMENT= Utilities for secure store-and-forward files and mail exchanging +COMMENT= Utilities for secure store-and-forward files, mail and command exchanging LICENSE= GPLv3+ LICENSE_FILE= ${WRKSRC}/COPYING diff --git a/ports/nncp/pkg-descr b/ports/nncp/pkg-descr index 37b7091..ce972a9 100644 --- a/ports/nncp/pkg-descr +++ b/ports/nncp/pkg-descr @@ -2,12 +2,13 @@ NNCP (Node to Node copy) is a collection of utilities simplifying secure store-and-forward files and mail exchanging. This utilities are intended to help build up small size (dozens of -nodes) ad-hoc friend-to-friend (F2F) statically routed darknet networks -for fire-and-forget secure reliable files, file requests and Internet -mail transmission. All packets are integrity checked, end-to-end -encrypted (E2EE), explicitly authenticated by known participants public -keys. Onion encryption is applied to relayed packets. Each node acts -both as a client and server, can use push and poll behaviour model. +nodes) ad-hoc friend-to-friend (F2F) statically routed darknet +delay-tolerant networks for fire-and-forget secure reliable files, file +requests, Internet mail and commands transmission. All packets are +integrity checked, end-to-end encrypted (E2EE), explicitly authenticated +by known participants public keys. Onion encryption is applied to +relayed packets. Each node acts both as a client and server, can use +push and poll behaviour model. Out-of-box offline sneakernet/floppynet, dead drops, sequential and append-only CD-ROM/tape storages, air-gapped computers support. But diff --git a/ports/nncp/pkg-plist b/ports/nncp/pkg-plist index 5da0b92..e98d84e 100644 --- a/ports/nncp/pkg-plist +++ b/ports/nncp/pkg-plist @@ -6,10 +6,10 @@ bin/nncp-cfgmin bin/nncp-cfgnew bin/nncp-check bin/nncp-daemon +bin/nncp-exec bin/nncp-file bin/nncp-freq bin/nncp-log -bin/nncp-mail bin/nncp-pkt bin/nncp-reass bin/nncp-rm diff --git a/src/cypherpunks.ru/nncp/cfg.go b/src/cypherpunks.ru/nncp/cfg.go index e6605e4..b751961 100644 --- a/src/cypherpunks.ru/nncp/cfg.go +++ b/src/cypherpunks.ru/nncp/cfg.go @@ -48,14 +48,14 @@ type NodeYAML struct { Id string ExchPub string SignPub string - NoisePub *string `noisepub,omitempty` - Sendmail []string `sendmail,omitempty` - Incoming *string `incoming,omitempty` - Freq *string `freq,omitempty` - FreqChunked *uint64 `freqchunked,omitempty` - FreqMinSize *uint64 `freqminsize,omitempty` - Via []string `via,omitempty` - Calls []CallYAML `calls,omitempty` + NoisePub *string `noisepub,omitempty` + Exec map[string][]string `exec,omitempty` + Incoming *string `incoming,omitempty` + Freq *string `freq,omitempty` + FreqChunked *uint64 `freqchunked,omitempty` + FreqMinSize *uint64 `freqminsize,omitempty` + Via []string `via,omitempty` + Calls []CallYAML `calls,omitempty` Addrs map[string]string `addrs,omitempty` @@ -232,7 +232,7 @@ func NewNode(name string, yml NodeYAML) (*Node, error) { Id: nodeId, ExchPub: new([32]byte), SignPub: ed25519.PublicKey(signPub), - Sendmail: yml.Sendmail, + Exec: yml.Exec, Incoming: incoming, Freq: freq, FreqChunked: freqChunked, diff --git a/src/cypherpunks.ru/nncp/cmd/nncp-cfgnew/main.go b/src/cypherpunks.ru/nncp/cmd/nncp-cfgnew/main.go index eb1b9ac..a119e38 100644 --- a/src/cypherpunks.ru/nncp/cmd/nncp-cfgnew/main.go +++ b/src/cypherpunks.ru/nncp/cmd/nncp-cfgnew/main.go @@ -70,7 +70,9 @@ func main() { ExchPub: nncp.ToBase32(nodeOur.ExchPub[:]), SignPub: nncp.ToBase32(nodeOur.SignPub[:]), NoisePub: &noisePub, - Sendmail: []string{nncp.DefaultSendmailPath}, + Exec: map[string][]string{ + "sendmail": []string{nncp.DefaultSendmailPath}, + }, }, }, Spool: nncp.DefaultSpoolPath, diff --git a/src/cypherpunks.ru/nncp/cmd/nncp-mail/main.go b/src/cypherpunks.ru/nncp/cmd/nncp-exec/main.go similarity index 61% rename from src/cypherpunks.ru/nncp/cmd/nncp-mail/main.go rename to src/cypherpunks.ru/nncp/cmd/nncp-exec/main.go index 13830d2..8e7aec6 100644 --- a/src/cypherpunks.ru/nncp/cmd/nncp-mail/main.go +++ b/src/cypherpunks.ru/nncp/cmd/nncp-exec/main.go @@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -// Send email via NNCP +// Send execution command via NNCP package main import ( @@ -33,23 +33,24 @@ import ( func usage() { fmt.Fprintf(os.Stderr, nncp.UsageHeader()) - fmt.Fprintf(os.Stderr, "nncp-mail -- send email\n\n") - fmt.Fprintf(os.Stderr, "Usage: %s [options] NODE USER ...\nOptions:\n", os.Args[0]) + fmt.Fprintf(os.Stderr, "nncp-exec -- send execution command\n\n") + fmt.Fprintf(os.Stderr, "Usage: %s [options] NODE HANDLE [ARG0 ARG1 ...]\nOptions:\n", os.Args[0]) flag.PrintDefaults() } func main() { var ( - cfgPath = flag.String("cfg", nncp.DefaultCfgPath, "Path to configuration file") - niceRaw = flag.Int("nice", nncp.DefaultNiceMail, "Outbound 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") - logPath = flag.String("log", "", "Override path to logfile") - quiet = flag.Bool("quiet", false, "Print only errors") - debug = flag.Bool("debug", false, "Print debug messages") - version = flag.Bool("version", false, "Print version information") - warranty = flag.Bool("warranty", false, "Print warranty information") + cfgPath = flag.String("cfg", nncp.DefaultCfgPath, "Path to configuration file") + niceRaw = flag.Int("nice", nncp.DefaultNiceExec, "Outbound packet niceness") + replyNiceRaw = flag.Int("replynice", nncp.DefaultNiceFile, "Possible reply 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") + logPath = flag.String("log", "", "Override path to logfile") + quiet = flag.Bool("quiet", false, "Print only errors") + debug = flag.Bool("debug", false, "Print debug messages") + version = flag.Bool("version", false, "Print version information") + warranty = flag.Bool("warranty", false, "Print warranty information") ) flag.Usage = usage flag.Parse() @@ -69,6 +70,10 @@ func main() { log.Fatalln("-nice must be between 1 and 255") } nice := uint8(*niceRaw) + if *replyNiceRaw < 1 || *replyNiceRaw > 255 { + log.Fatalln("-replynice must be between 1 and 255") + } + replyNice := uint8(*replyNiceRaw) ctx, err := nncp.CtxFromCmdline(*cfgPath, *spoolPath, *logPath, *quiet, *debug) if err != nil { @@ -97,13 +102,15 @@ func main() { body, err := ioutil.ReadAll(bufio.NewReader(os.Stdin)) if err != nil { - log.Fatalln("Can not read mail body from stdin:", err) + log.Fatalln("Can not read body from stdin:", err) } - if err = ctx.TxMail( + if err = ctx.TxExec( node, nice, - strings.Join(flag.Args()[1:], " "), + replyNice, + flag.Args()[1], + flag.Args()[2:], body, int64(*minSize)*1024, ); err != nil { diff --git a/src/cypherpunks.ru/nncp/cmd/nncp-pkt/main.go b/src/cypherpunks.ru/nncp/cmd/nncp-pkt/main.go index d9c79ba..f4de406 100644 --- a/src/cypherpunks.ru/nncp/cmd/nncp-pkt/main.go +++ b/src/cypherpunks.ru/nncp/cmd/nncp-pkt/main.go @@ -94,13 +94,20 @@ func main() { payloadType = "file" case nncp.PktTypeFreq: payloadType = "file request" - case nncp.PktTypeMail: - payloadType = "mail" + case nncp.PktTypeExec: + payloadType = "exec" case nncp.PktTypeTrns: payloadType = "transitional" } var path string switch pkt.Type { + case nncp.PktTypeExec: + path = string(bytes.Replace( + pkt.Path[:pkt.PathLen], + []byte{0}, + []byte(" "), + -1, + )) case nncp.PktTypeTrns: path = nncp.ToBase32(pkt.Path[:pkt.PathLen]) default: diff --git a/src/cypherpunks.ru/nncp/cmd/nncp-toss/main.go b/src/cypherpunks.ru/nncp/cmd/nncp-toss/main.go index 08e82ce..cc3ca67 100644 --- a/src/cypherpunks.ru/nncp/cmd/nncp-toss/main.go +++ b/src/cypherpunks.ru/nncp/cmd/nncp-toss/main.go @@ -46,7 +46,7 @@ func main() { cycle = flag.Uint("cycle", 0, "Repeat tossing after N seconds in infinite loop") noFile = flag.Bool("nofile", false, "Do not process packets with type: file") noFreq = flag.Bool("nofreq", false, "Do not process packets with type: freq") - noMail = flag.Bool("nomail", false, "Do not process packets with type: mail") + noExec = flag.Bool("noexec", false, "Do not process packets with type: exec") noTrns = flag.Bool("notrns", false, "Do not process packets with type: transitional") spoolPath = flag.String("spool", "", "Override path to spool") logPath = flag.String("log", "", "Override path to logfile") @@ -99,7 +99,7 @@ Cycle: *doSeen, *noFile, *noFreq, - *noMail, + *noExec, *noTrns, ) } diff --git a/src/cypherpunks.ru/nncp/humanizer.go b/src/cypherpunks.ru/nncp/humanizer.go index 53ce6a0..cea9546 100644 --- a/src/cypherpunks.ru/nncp/humanizer.go +++ b/src/cypherpunks.ru/nncp/humanizer.go @@ -84,10 +84,10 @@ func (ctx *Ctx) Humanize(s string) string { "File request from %s:%s to %s: %s", nodeS, sds["src"], sds["dst"], rem, ) - case "mail": + case "exec": msg = fmt.Sprintf( - "Mail to %s@%s (%s): %s", - nodeS, strings.Replace(sds["dst"], " ", ",", -1), size, rem, + "Exec to %s@%s (%s): %s", + nodeS, sds["dst"], size, rem, ) case "trns": msg = fmt.Sprintf( @@ -102,10 +102,10 @@ func (ctx *Ctx) Humanize(s string) string { } case "rx": switch sds["type"] { - case "mail": + case "exec": msg = fmt.Sprintf( - "Got mail from %s to %s (%s)", - nodeS, strings.Replace(sds["dst"], " ", ",", -1), size, + "Got exec from %s to %s (%s)", + nodeS, sds["dst"], size, ) case "file": msg = fmt.Sprintf("Got file %s (%s) from %s", sds["dst"], size, nodeS) diff --git a/src/cypherpunks.ru/nncp/node.go b/src/cypherpunks.ru/nncp/node.go index 68e2fd4..9cd4605 100644 --- a/src/cypherpunks.ru/nncp/node.go +++ b/src/cypherpunks.ru/nncp/node.go @@ -42,7 +42,7 @@ type Node struct { ExchPub *[32]byte SignPub ed25519.PublicKey NoisePub *[32]byte - Sendmail []string + Exec map[string][]string Incoming *string Freq *string FreqChunked int64 diff --git a/src/cypherpunks.ru/nncp/pkt.go b/src/cypherpunks.ru/nncp/pkt.go index 0e98d4b..b1c506a 100644 --- a/src/cypherpunks.ru/nncp/pkt.go +++ b/src/cypherpunks.ru/nncp/pkt.go @@ -42,12 +42,12 @@ const ( PktTypeFile PktType = iota PktTypeFreq PktType = iota - PktTypeMail PktType = iota + PktTypeExec PktType = iota PktTypeTrns PktType = iota MaxPathSize = 1<<8 - 1 - DefaultNiceMail = 64 + DefaultNiceExec = 64 DefaultNiceFreq = 64 DefaultNiceFile = 196 @@ -121,19 +121,18 @@ func init() { PktEncOverhead = int64(n) } -func NewPkt(typ PktType, nice uint8, path string) (*Pkt, error) { - pb := []byte(path) - if len(pb) > MaxPathSize { +func NewPkt(typ PktType, nice uint8, path []byte) (*Pkt, error) { + if len(path) > MaxPathSize { return nil, errors.New("Too long path") } pkt := Pkt{ Magic: MagicNNCPPv2, Type: typ, Nice: nice, - PathLen: uint8(len(pb)), + PathLen: uint8(len(path)), Path: new([MaxPathSize]byte), } - copy(pkt.Path[:], pb) + copy(pkt.Path[:], path) return &pkt, nil } diff --git a/src/cypherpunks.ru/nncp/pkt_test.go b/src/cypherpunks.ru/nncp/pkt_test.go index 6f8506a..e0765b3 100644 --- a/src/cypherpunks.ru/nncp/pkt_test.go +++ b/src/cypherpunks.ru/nncp/pkt_test.go @@ -41,7 +41,7 @@ func TestPktEncWrite(t *testing.T) { if len(path) > int(pathSize) { path = path[:int(pathSize)] } - pkt, err := NewPkt(PktTypeFile, 123, path) + pkt, err := NewPkt(PktTypeFile, 123, []byte(path)) if err != nil { panic(err) } @@ -87,7 +87,7 @@ func TestPktEncRead(t *testing.T) { if len(path) > int(pathSize) { path = path[:int(pathSize)] } - pkt, err := NewPkt(PktTypeFile, 123, path) + pkt, err := NewPkt(PktTypeFile, 123, []byte(path)) if err != nil { panic(err) } diff --git a/src/cypherpunks.ru/nncp/toss.go b/src/cypherpunks.ru/nncp/toss.go index a207785..86d154b 100644 --- a/src/cypherpunks.ru/nncp/toss.go +++ b/src/cypherpunks.ru/nncp/toss.go @@ -52,7 +52,7 @@ func newNotification(fromTo *FromToYAML, subject string) io.Reader { )) } -func (ctx *Ctx) Toss(nodeId *NodeId, nice uint8, dryRun, doSeen, noFile, noFreq, noMail, noTrns bool) bool { +func (ctx *Ctx) Toss(nodeId *NodeId, nice uint8, dryRun, doSeen, noFile, noFreq, noExec, noTrns bool) bool { isBad := false for job := range ctx.Jobs(nodeId, TRx) { pktName := filepath.Base(job.Fd.Name()) @@ -93,39 +93,45 @@ func (ctx *Ctx) Toss(nodeId *NodeId, nice uint8, dryRun, doSeen, noFile, noFreq, sds["size"] = strconv.FormatInt(pktSize, 10) ctx.LogD("rx", sds, "taken") switch pkt.Type { - case PktTypeMail: - if noMail { + case PktTypeExec: + if noExec { goto Closing } - recipients := make([]string, 0) - for _, recipient := range bytes.Split(pkt.Path[:int(pkt.PathLen)], []byte{0}) { - recipients = append(recipients, string(recipient)) + path := bytes.Split(pkt.Path[:int(pkt.PathLen)], []byte{0}) + handle := string(path[0]) + args := make([]string, 0, len(path)-1) + for _, p := range path[1:] { + args = append(args, string(p)) } sds := SdsAdd(sds, SDS{ - "type": "mail", - "dst": strings.Join(recipients, " "), + "type": "exec", + "dst": strings.Join(append([]string{handle}, args...), " "), }) decompressor, err := zlib.NewReader(pipeR) if err != nil { log.Fatalln(err) } sender := ctx.Neigh[*job.PktEnc.Sender] - sendmail := sender.Sendmail - if len(sendmail) == 0 { - ctx.LogE("rx", SdsAdd(sds, SDS{"err": "No sendmail configured"}), "") + cmdline, exists := sender.Exec[handle] + if !exists || len(cmdline) == 0 { + ctx.LogE("rx", SdsAdd(sds, SDS{"err": "No handle found"}), "") isBad = true goto Closing } if !dryRun { cmd := exec.Command( - sendmail[0], - append(sendmail[1:len(sendmail)], recipients...)..., + cmdline[0], + append(cmdline[1:len(cmdline)], args...)..., + ) + cmd.Env = append( + cmd.Env, + "NNCP_SELF="+ctx.Self.Id.String(), + "NNCP_SENDER="+sender.Id.String(), + "NNCP_NICE="+strconv.Itoa(int(pkt.Nice)), ) - cmd.Env = append(cmd.Env, "NNCP_SENDER="+sender.Id.String()) - cmd.Env = append(cmd.Env, "NNCP_NICE="+strconv.Itoa(int(pkt.Nice))) cmd.Stdin = decompressor if err = cmd.Run(); err != nil { - ctx.LogE("rx", SdsAdd(sds, SDS{"err": err}), "sendmail") + ctx.LogE("rx", SdsAdd(sds, SDS{"err": err}), "handle") isBad = true goto Closing } @@ -215,8 +221,8 @@ func (ctx *Ctx) Toss(nodeId *NodeId, nice uint8, dryRun, doSeen, noFile, noFreq, ctx.LogE("rx", SdsAdd(sds, SDS{"err": err}), "remove") isBad = true } - sendmail := ctx.Neigh[*ctx.SelfId].Sendmail - if ctx.NotifyFile != nil { + sendmail, exists := ctx.Neigh[*ctx.SelfId].Exec["sendmail"] + if exists && len(sendmail) > 0 && ctx.NotifyFile != nil { cmd := exec.Command( sendmail[0], append(sendmail[1:len(sendmail)], ctx.NotifyFile.To)..., @@ -292,8 +298,8 @@ func (ctx *Ctx) Toss(nodeId *NodeId, nice uint8, dryRun, doSeen, noFile, noFreq, ctx.LogE("rx", SdsAdd(sds, SDS{"err": err}), "remove") isBad = true } - if ctx.NotifyFreq != nil { - sendmail := ctx.Neigh[*ctx.SelfId].Sendmail + sendmail, exists := ctx.Neigh[*ctx.SelfId].Exec["sendmail"] + if exists && len(sendmail) > 0 && ctx.NotifyFreq != nil { cmd := exec.Command( sendmail[0], append(sendmail[1:len(sendmail)], ctx.NotifyFreq.To)..., diff --git a/src/cypherpunks.ru/nncp/toss_test.go b/src/cypherpunks.ru/nncp/toss_test.go index 037c777..7ff2164 100644 --- a/src/cypherpunks.ru/nncp/toss_test.go +++ b/src/cypherpunks.ru/nncp/toss_test.go @@ -51,8 +51,8 @@ func dirFiles(path string) []string { return names } -func TestTossEmail(t *testing.T) { - f := func(recipients [16]uint8) bool { +func TestTossExec(t *testing.T) { + f := func(replyNice uint8, handle string, recipients [16]uint8) bool { for i, recipient := range recipients { recipients[i] = recipient % 8 } @@ -90,10 +90,12 @@ func TestTossEmail(t *testing.T) { ctx.Neigh[*our.Id] = our.Their() } for _, recipient := range recipients { - if err := ctx.TxMail( + if err := ctx.TxExec( ctx.Neigh[*privates[recipient].Id], - DefaultNiceMail, - "recipient", + DefaultNiceExec, + replyNice, + handle, + []string{"arg0", "arg1"}, []byte{123}, 1<<15, ); err != nil { @@ -108,20 +110,25 @@ func TestTossEmail(t *testing.T) { if len(dirFiles(rxPath)) == 0 { continue } - ctx.Toss(ctx.Self.Id, DefaultNiceMail-1, false, false, false, false, false, false) + ctx.Toss(ctx.Self.Id, DefaultNiceExec-1, false, false, false, false, false, false) if len(dirFiles(rxPath)) == 0 { return false } - ctx.Neigh[*nodeOur.Id].Sendmail = []string{"/bin/sh", "-c", "false"} - ctx.Toss(ctx.Self.Id, DefaultNiceMail, false, false, false, false, false, false) + ctx.Neigh[*nodeOur.Id].Exec = make(map[string][]string) + ctx.Neigh[*nodeOur.Id].Exec[handle] = []string{"/bin/sh", "-c", "false"} + ctx.Toss(ctx.Self.Id, DefaultNiceExec, false, false, false, false, false, false) if len(dirFiles(rxPath)) == 0 { return false } - ctx.Neigh[*nodeOur.Id].Sendmail = []string{ + ctx.Neigh[*nodeOur.Id].Exec[handle] = []string{ "/bin/sh", "-c", - fmt.Sprintf("cat >> %s", filepath.Join(spool, "mbox")), + fmt.Sprintf( + "echo $NNCP_NICE $@ >> %s ; cat >> %s", + filepath.Join(spool, "mbox"), + filepath.Join(spool, "mbox"), + ), } - ctx.Toss(ctx.Self.Id, DefaultNiceMail, false, false, false, false, false, false) + ctx.Toss(ctx.Self.Id, DefaultNiceExec, false, false, false, false, false, false) if len(dirFiles(rxPath)) != 0 { return false } @@ -132,6 +139,10 @@ func TestTossEmail(t *testing.T) { } expected := make([]byte, 0, 16) for i := 0; i < 16; i++ { + expected = append( + expected, + []byte(fmt.Sprintf("%d arg0 arg1\n", replyNice))..., + ) expected = append(expected, 123) } return bytes.Compare(mbox, expected) == 0 @@ -471,7 +482,6 @@ func TestTossTrns(t *testing.T) { for k, data := range datum { if bytes.Compare(dataRead, data) == 0 { delete(datum, k) - break } } } diff --git a/src/cypherpunks.ru/nncp/tx.go b/src/cypherpunks.ru/nncp/tx.go index a7e8b15..4319813 100644 --- a/src/cypherpunks.ru/nncp/tx.go +++ b/src/cypherpunks.ru/nncp/tx.go @@ -68,13 +68,7 @@ func (ctx *Ctx) Tx(node *Node, pkt *Pkt, nice uint8, size, minSize int64, src io var pipeRPrev io.Reader for i := 1; i < len(hops); i++ { - pktTrans := Pkt{ - Magic: MagicNNCPPv2, - Type: PktTypeTrns, - PathLen: blake2b.Size256, - Path: new([MaxPathSize]byte), - } - copy(pktTrans.Path[:], hops[i-1].Id[:]) + pktTrns, _ := NewPkt(PktTypeTrns, 0, hops[i-1].Id[:]) curSize += PktOverhead + PktEncOverhead pipeRPrev = pipeR pipeR, pipeW = io.Pipe() @@ -86,7 +80,7 @@ func (ctx *Ctx) Tx(node *Node, pkt *Pkt, nice uint8, size, minSize int64, src io }, "trns wrote") errs <- PktEncWrite(ctx.Self, node, pkt, nice, size, 0, src, dst) dst.Close() - }(hops[i], &pktTrans, curSize, pipeRPrev, pipeW) + }(hops[i], pktTrns, curSize, pipeRPrev, pipeW) } go func() { _, err := io.Copy(tmp.W, pipeR) @@ -157,7 +151,7 @@ func (ctx *Ctx) TxFile(node *Node, nice uint8, srcPath, dstPath string, minSize if filepath.IsAbs(dstPath) { return errors.New("Relative destination path required") } - pkt, err := NewPkt(PktTypeFile, nice, dstPath) + pkt, err := NewPkt(PktTypeFile, nice, []byte(dstPath)) if err != nil { return err } @@ -169,25 +163,19 @@ func (ctx *Ctx) TxFile(node *Node, nice uint8, srcPath, dstPath string, minSize return err } _, 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), + } if err == nil { - ctx.LogI("tx", SDS{ - "type": "file", - "node": node.Id, - "nice": strconv.Itoa(int(nice)), - "src": srcPath, - "dst": dstPath, - "size": strconv.FormatInt(fileSize, 10), - }, "sent") + ctx.LogI("tx", sds, "sent") } else { - ctx.LogE("tx", SDS{ - "type": "file", - "node": node.Id, - "nice": strconv.Itoa(int(nice)), - "src": srcPath, - "dst": dstPath, - "size": strconv.FormatInt(fileSize, 10), - "err": err, - }, "sent") + sds["err"] = err + ctx.LogE("tx", sds, "sent") } return err } @@ -212,30 +200,24 @@ func (ctx *Ctx) TxFileChunked(node *Node, nice uint8, srcPath, dstPath string, m } if fileSize <= chunkSize { - pkt, err := NewPkt(PktTypeFile, nice, dstPath) + pkt, err := NewPkt(PktTypeFile, nice, []byte(dstPath)) if err != nil { return err } _, 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), + } if err == nil { - ctx.LogI("tx", SDS{ - "type": "file", - "node": node.Id, - "nice": strconv.Itoa(int(nice)), - "src": srcPath, - "dst": dstPath, - "size": strconv.FormatInt(fileSize, 10), - }, "sent") + ctx.LogI("tx", sds, "sent") } else { - ctx.LogE("tx", SDS{ - "type": "file", - "node": node.Id, - "nice": strconv.Itoa(int(nice)), - "src": srcPath, - "dst": dstPath, - "size": strconv.FormatInt(fileSize, 10), - "err": err, - }, "sent") + sds["err"] = err + ctx.LogE("tx", sds, "sent") } return err } @@ -263,7 +245,7 @@ func (ctx *Ctx) TxFileChunked(node *Node, nice uint8, srcPath, dstPath string, m sizeToSend = chunkSize } path = dstPath + ChunkedSuffixPart + strconv.Itoa(chunkNum) - pkt, err = NewPkt(PktTypeFile, nice, path) + pkt, err = NewPkt(PktTypeFile, nice, []byte(path)) if err != nil { return err } @@ -279,25 +261,19 @@ func (ctx *Ctx) TxFileChunked(node *Node, nice uint8, srcPath, dstPath string, m minSize, io.TeeReader(reader, hsh), ) + sds := SDS{ + "type": "file", + "node": node.Id, + "nice": strconv.Itoa(int(nice)), + "src": srcPath, + "dst": path, + "size": strconv.FormatInt(sizeToSend, 10), + } if err == nil { - ctx.LogI("tx", SDS{ - "type": "file", - "node": node.Id, - "nice": strconv.Itoa(int(nice)), - "src": srcPath, - "dst": path, - "size": strconv.FormatInt(sizeToSend, 10), - }, "sent") + ctx.LogI("tx", sds, "sent") } else { - ctx.LogE("tx", SDS{ - "type": "file", - "node": node.Id, - "nice": strconv.Itoa(int(nice)), - "src": srcPath, - "dst": path, - "size": strconv.FormatInt(sizeToSend, 10), - "err": err, - }, "sent") + sds["err"] = err + ctx.LogE("tx", sds, "sent") return err } hsh.Sum(metaPkt.Checksums[chunkNum][:0]) @@ -313,31 +289,25 @@ func (ctx *Ctx) TxFileChunked(node *Node, nice uint8, srcPath, dstPath string, m return err } path = dstPath + ChunkedSuffixMeta - pkt, err = NewPkt(PktTypeFile, nice, path) + pkt, err = NewPkt(PktTypeFile, nice, []byte(path)) if err != nil { return err } metaPktSize := int64(metaBuf.Len()) _, err = ctx.Tx(node, pkt, nice, metaPktSize, minSize, &metaBuf) + sds := SDS{ + "type": "file", + "node": node.Id, + "nice": strconv.Itoa(int(nice)), + "src": srcPath, + "dst": path, + "size": strconv.FormatInt(metaPktSize, 10), + } if err == nil { - ctx.LogI("tx", SDS{ - "type": "file", - "node": node.Id, - "nice": strconv.Itoa(int(nice)), - "src": srcPath, - "dst": path, - "size": strconv.FormatInt(metaPktSize, 10), - }, "sent") + ctx.LogI("tx", sds, "sent") } else { - ctx.LogE("tx", SDS{ - "type": "file", - "node": node.Id, - "nice": strconv.Itoa(int(nice)), - "src": srcPath, - "dst": path, - "size": strconv.FormatInt(metaPktSize, 10), - "err": err, - }, "sent") + sds["err"] = err + ctx.LogE("tx", sds, "sent") } return err } @@ -351,36 +321,37 @@ func (ctx *Ctx) TxFreq(node *Node, nice, replyNice uint8, srcPath, dstPath strin if filepath.IsAbs(srcPath) { return errors.New("Relative source path required") } - pkt, err := NewPkt(PktTypeFreq, replyNice, srcPath) + pkt, err := NewPkt(PktTypeFreq, replyNice, []byte(srcPath)) if err != nil { return err } src := strings.NewReader(dstPath) size := int64(src.Len()) _, err = ctx.Tx(node, pkt, nice, size, minSize, src) + sds := SDS{ + "type": "freq", + "node": node.Id, + "nice": strconv.Itoa(int(nice)), + "replynice": strconv.Itoa(int(replyNice)), + "src": srcPath, + "dst": dstPath, + } if err == nil { - ctx.LogI("tx", SDS{ - "type": "freq", - "node": node.Id, - "nice": strconv.Itoa(int(nice)), - "src": srcPath, - "dst": dstPath, - }, "sent") + ctx.LogI("tx", sds, "sent") } else { - ctx.LogE("tx", SDS{ - "type": "freq", - "node": node.Id, - "nice": strconv.Itoa(int(nice)), - "src": srcPath, - "dst": dstPath, - "err": err, - }, "sent") + sds["err"] = err + ctx.LogE("tx", sds, "sent") } return err } -func (ctx *Ctx) TxMail(node *Node, nice uint8, recipient string, body []byte, minSize int64) error { - pkt, err := NewPkt(PktTypeMail, nice, recipient) +func (ctx *Ctx) TxExec(node *Node, nice, replyNice uint8, handle string, args []string, body []byte, minSize int64) 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 } @@ -395,34 +366,31 @@ func (ctx *Ctx) TxMail(node *Node, nice uint8, recipient string, body []byte, mi compressor.Close() size := int64(compressed.Len()) _, err = ctx.Tx(node, pkt, nice, size, minSize, &compressed) + sds := SDS{ + "type": "exec", + "node": node.Id, + "nice": strconv.Itoa(int(nice)), + "replynice": strconv.Itoa(int(replyNice)), + "dst": strings.Join(append([]string{handle}, args...), " "), + "size": strconv.FormatInt(size, 10), + } if err == nil { - ctx.LogI("tx", SDS{ - "type": "mail", - "node": node.Id, - "nice": strconv.Itoa(int(nice)), - "dst": recipient, - "size": strconv.FormatInt(size, 10), - }, "sent") + ctx.LogI("tx", sds, "sent") } else { - ctx.LogE("tx", SDS{ - "type": "mail", - "node": node.Id, - "nice": strconv.Itoa(int(nice)), - "dst": recipient, - "size": strconv.FormatInt(size, 10), - "err": err, - }, "sent") + sds["err"] = err + ctx.LogE("tx", sds, "sent") } return err } func (ctx *Ctx) TxTrns(node *Node, nice uint8, size int64, src io.Reader) error { - ctx.LogD("tx", SDS{ + sds := SDS{ "type": "trns", "node": node.Id, "nice": strconv.Itoa(int(nice)), "size": strconv.FormatInt(size, 10), - }, "taken") + } + ctx.LogD("tx", sds, "taken") tmp, err := ctx.NewTmpFileWHash() if err != nil { return err @@ -433,20 +401,10 @@ func (ctx *Ctx) TxTrns(node *Node, nice uint8, size int64, src io.Reader) error nodePath := filepath.Join(ctx.Spool, node.Id.String()) err = tmp.Commit(filepath.Join(nodePath, string(TTx))) if err == nil { - ctx.LogI("tx", SDS{ - "type": "trns", - "node": node.Id, - "nice": strconv.Itoa(int(nice)), - "size": strconv.FormatInt(size, 10), - }, "sent") + ctx.LogI("tx", sds, "sent") } else { - ctx.LogI("tx", SDS{ - "type": "trns", - "node": node.Id, - "nice": strconv.Itoa(int(nice)), - "size": strconv.FormatInt(size, 10), - "err": err, - }, "sent") + sds["err"] = err + ctx.LogI("tx", sds, "sent") } os.Symlink(nodePath, filepath.Join(ctx.Spool, node.Name)) return err diff --git a/src/cypherpunks.ru/nncp/tx_test.go b/src/cypherpunks.ru/nncp/tx_test.go index 0a4485f..6ea7ef2 100644 --- a/src/cypherpunks.ru/nncp/tx_test.go +++ b/src/cypherpunks.ru/nncp/tx_test.go @@ -33,7 +33,7 @@ import ( ) func TestTx(t *testing.T) { - f := func(hops uint8, pathSrc, data string, nice uint8, padSize int16) bool { + f := func(hops uint8, pathSrc, data string, nice, replyNice uint8, padSize int16) bool { if len(pathSrc) > int(MaxPathSize) { pathSrc = pathSrc[:MaxPathSize] } @@ -76,7 +76,7 @@ func TestTx(t *testing.T) { privates[*node.Id] = node nodeTgt.Via = append(nodeTgt.Via, node.Id) } - pkt, err := NewPkt(PktTypeMail, 123, pathSrc) + pkt, err := NewPkt(PktTypeExec, replyNice, []byte(pathSrc)) src := strings.NewReader(data) dstNode, err := ctx.Tx( nodeTgt, @@ -121,7 +121,10 @@ func TestTx(t *testing.T) { return false } if *hopId == *nodeTgt.Id { - if pkt.Type != PktTypeMail { + if pkt.Type != PktTypeExec { + return false + } + if pkt.Nice != replyNice { return false } if !bytes.HasPrefix(pkt.Path[:], []byte(pathSrc)) { -- 2.44.0