From: Sergey Matveev Date: Sat, 2 Dec 2017 16:00:05 +0000 (+0300) Subject: Merge branch 'develop' X-Git-Tag: 1.0^0 X-Git-Url: http://www.git.cypherpunks.ru/?a=commitdiff_plain;h=736b4b108020314d84fd8abf57e142738721722d;hp=08346a7ec3890df71d00653f6ec8cb1c41a03af5;p=nncp.git Merge branch 'develop' --- diff --git a/README b/README index 3445eee..dab1ca8 100644 --- a/README +++ b/README @@ -9,9 +9,9 @@ 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 and air-gapped -computers support. But online TCP daemon with full-duplex resumable data -transmission exists. +Out-of-box offline sneakernet/floppynet, dead drops, sequential and +append-only CD-ROM/tape storages, air-gapped computers support. But +online TCP daemon with full-duplex resumable data transmission exists. NNCP is copylefted free software: see the file COPYING for copying conditions. It should work on all POSIX-compatible systems. Easy diff --git a/README.RU b/README.RU index ad26251..eccba53 100644 --- a/README.RU +++ b/README.RU @@ -13,7 +13,8 @@ NNCP (Node to Node copy) это набор утилит упрощающий б поведения. Поддержка из коробки offline флоппинета, тайников для сброса информации -(dead drop) и компьютеров с "воздушным зазором" (air-gap). Но также +(dead drop), последовательных и не перезаписываемых CD-ROM/ленточных +хранилищ, компьютеров с "воздушным зазором" (air-gap). Но также существует и online TCP демон с полнодуплексной возобновляемой передачей данных. diff --git a/VERSION b/VERSION index c43e105..d3827e7 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.12 +1.0 diff --git a/common.mk b/common.mk index 327f8df..34d6404 100644 --- a/common.mk +++ b/common.mk @@ -17,6 +17,7 @@ LDFLAGS = \ -X cypherpunks.ru/nncp.DefaultLogPath=$(LOGPATH) ALL = \ + nncp-bundle \ nncp-call \ nncp-caller \ nncp-cfgenc \ @@ -37,6 +38,9 @@ ALL = \ all: $(ALL) +nncp-bundle: + GOPATH=$(GOPATH) go build -ldflags "$(LDFLAGS)" cypherpunks.ru/nncp/cmd/nncp-bundle + nncp-call: GOPATH=$(GOPATH) go build -ldflags "$(LDFLAGS)" cypherpunks.ru/nncp/cmd/nncp-call diff --git a/doc/about.ru.texi b/doc/about.ru.texi index c285e16..727402c 100644 --- a/doc/about.ru.texi +++ b/doc/about.ru.texi @@ -22,7 +22,10 @@ NNCP (Node to Node copy) это набор утилит упрощающий б @url{https://ru.wikipedia.org/wiki/%D0%A4%D0%BB%D0%BE%D0%BF%D0%BF%D0%B8%D0%BD%D0%B5%D1%82, флоппинета}, @url{https://ru.wikipedia.org/wiki/%D0%A2%D0%B0%D0%B9%D0%BD%D0%B8%D0%BA, -тайников} для сброса информации (dead drop) и компьютеров с +тайников} для сброса информации (dead drop), последовательных и не +перезаписываемых @url{https://ru.wikipedia.org/wiki/CD-ROM, +CD-ROM}/@url{https://ru.wikipedia.org/wiki/%D0%A1%D1%82%D1%80%D0%B8%D0%BC%D0%B5%D1%80, +ленточных} хранилищ, компьютеров с @url{https://ru.wikipedia.org/wiki/%D0%92%D0%BE%D0%B7%D0%B4%D1%83%D1%88%D0%BD%D1%8B%D0%B9_%D0%B7%D0%B0%D0%B7%D0%BE%D1%80_(%D1%81%D0%B5%D1%82%D0%B8_%D0%BF%D0%B5%D1%80%D0%B5%D0%B4%D0%B0%D1%87%D0%B8_%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85), воздушным зазором} (air-gap). Но также существует и online TCP демон с полнодуплексной возобновляемой передачей данных. diff --git a/doc/about.texi b/doc/about.texi index cfd5321..ffe4be0 100644 --- a/doc/about.texi +++ b/doc/about.texi @@ -17,9 +17,12 @@ 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, -dead drops} and @url{https://en.wikipedia.org/wiki/Air_gap_(networking), -air-gapped} computers support. But online TCP daemon with full-duplex -resumable data transmission exists. +dead drops}, sequential and append-only +@url{https://en.wikipedia.org/wiki/CD-ROM, +CD-ROM}/@url{https://en.wikipedia.org/wiki/Tape_drive, tape} storages, +@url{https://en.wikipedia.org/wiki/Air_gap_(networking), air-gapped} +computers support. But online TCP daemon with full-duplex resumable data +transmission exists. Look for possible @ref{Use cases, use cases}! diff --git a/doc/bundles.texi b/doc/bundles.texi new file mode 100644 index 0000000..7541bfc --- /dev/null +++ b/doc/bundles.texi @@ -0,0 +1,55 @@ +@node Bundles +@unnumbered Bundles + +Usual @ref{nncp-xfer} command requires filesystem it can operate on. +That presumes random access media storage usage, like hard drives, USB +flash drives and similar. But media like CD-ROM and especially tape +drives are sequential by nature. You can prepare intermediate directory +for recording to CD-ROM disc/tape, but that requires additional storage +and is inconvenient. + +Bundles, created with @ref{nncp-bundle} command are convenient +alternative to ordinary @command{nncp-xfer}. Bundle is just a collection +of @ref{Encrypted, encrypted packets}, stream of packets. It could be +sequentially streamed for recording and digested back. + +@itemize + +@item They do not require intermediate storage before recording on +either CD-ROM or tape drive. +@verbatim +% nncp-bundle -tx SOMENODE | cdrecord -tao - # record directly to CD +% nncp-bundle -tx SOMENODE | dd of=/dev/sa0 bs=10240 # record directly to tape + +% dd if=/dev/cd0 bs=2048 | nncp-bundle -rx # read directly from CD +% dd if=/dev/sa0 bs=10240 | nncp-bundle -rx # read directly from tape +@end verbatim + +@item They do not require filesystem existence to deal with, simplifying +administration when operating in heterogeneous systems with varying +filesystems. No @command{mount}/@command{umount}, @command{zpool +import}/@command{zpool export} and struggling with file permissions. +@verbatim +% nncp-bundle -tx SOMENODE | dd of=/dev/da0 bs=1M # record directly to + # hard/flash drive +% dd if=/dev/da0 bs=1M | nncp-bundle -rx # read directly from drive +@end verbatim + +@item This is the fastest way to record outbound packets for offline +transmission -- sequential write is always faster, when no +metainformation needs to be updated. + +@item This is convenient to use with append-only storages, just +sending/appending new bundles. + +@item Bundles could be repeatedly broadcasted in one-way transmission. +@ref{Sync, Sync protocol} requires interactive connection, but bundles +can contain mix of various recipients. + +@end itemize + +Technically bundle is valid POSIX.1-2001 (pax) +@url{http://www.freebsd.org/cgi/man.cgi?query=tar&sektion=5, tar 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. diff --git a/doc/cmds.texi b/doc/cmds.texi index 38f77ee..1de83a4 100644 --- a/doc/cmds.texi +++ b/doc/cmds.texi @@ -20,6 +20,12 @@ Nearly all commands have the following common options: 1-255 values are allowed. @item -node Process only single specified node. +@item -spool + Override path to spool directory. May be specified by + @env{NNCPSPOOL} environment variable. +@item -log + Override path to logfile. May be specified by @env{NNCPLOG} + environment variable. @item -quiet Print only errors, omit simple informational messages. In any case those messages are logged, so you can reread them using @@ -30,6 +36,53 @@ Nearly all commands have the following common options: Print warranty information (no warranty). @end table +@node nncp-bundle +@section nncp-bundle + +@verbatim +% nncp-bundle [options] -tx [-delete] NODE [NODE ...] > ... +% nncp-bundle [options] -rx -delete [-dryrun] [NODE ...] < ... +% nncp-bundle [options] -rx [-check] [-dryrun] [NODE ...] < ... +@end verbatim + +With @option{-tx} option, this command creates @ref{Bundles, bundle} of +@ref{Encrypted, encrypted packets} from the spool directory and writes +it to stdout. + +With @option{-rx} option, this command takes bundle from stdin and +copies all found packets for our node to the spool directory. Pay +attention that @strong{no} integrity checking is done by default. Modern +tape drives could easily provide too much throughput your CPU won't be +able to verify on the fly. So if you won't @ref{nncp-toss, toss} +received packets at the place, it is advisable either to run +@ref{nncp-check} utility for packets integrity verification, or to use +@option{-check} option to enable on the fly integrity check. + +You can specify multiple @option{NODE} arguments, telling for what nodes +you want to create the stream, or take it from. If no nodes are +specified for @option{-rx} mode, then all packets aimed at us will be +processed. + +When packets are sent through the stream, they are still kept in the +spool directory, because there is no assurance that they are transferred +to the media (media (CD-ROM, tape drive, raw hard drive) can end). If +you want to forcefully delete them (after they are successfully flushed +to stdout) anyway, use @option{-delete} option. + +But you can verify produced stream after, by digesting it by yourself +with @option{-rx} and @option{-delete} options -- in that mode, stream +packets integrity will be checked and they will be deleted from the +spool if everything is good. So it is advisable to recheck your streams: + +@verbatim +% nncp-bundle -tx ALICE BOB WHATEVER | cdrecord -tao - +% dd if=/dev/cd0 bs=2048 | nncp-bundle -rx -delete +@end verbatim + +@option{-dryrun} option prevents any writing to the spool. This is +useful when you need to see what packets will pass by and possibly check +their integrity. + @node nncp-call @section nncp-call @@ -352,12 +405,31 @@ Checksums: @section nncp-rm @verbatim -% nncp-rm [options] NODE PKT +% nncp-rm [options] -tmp +% nncp-rm [options] -lock +% nncp-rm [options] -node NODE -part +% nncp-rm [options] -node NODE -seen +% nncp-rm [options] -node NODE [-rx] [-tx] +% nncp-rm [options] -node NODE -pkt PKT @end verbatim -Remove specified packet (Base32 name) in @option{NODE}'s queues. This -command is useful when you want to remove the packet that is failing to -be processed. +This command is aimed to delete various files from your spool directory: + +@itemize +@item If @option{-tmp} option is specified, then it will delete all +temporary files in @file{spool/tmp} directory. Files may stay in it when +commands like @ref{nncp-file} fail for some reason. +@item If @option{-lock} option is specified, then all @file{.lock} files +will be deleted in your spool directory. +@item If @option{-pkt} option is specified, then @file{PKT} packet (its +Base32 name) will be deleted. This is useful when you see some packet +failing to be processed. +@item When either @option{-rx} or @option{-tx} options are specified +(maybe both of them), then delete all packets from that given queues. If +@option{-part} is given, then delete only @file{.part}ly downloaded +ones. If @option{-seen} option is specified, then delete only +@file{.seen} files. +@end itemize @node nncp-stat @section nncp-stat @@ -375,7 +447,7 @@ queues. @section nncp-toss @verbatim -% nncp-toss [options] [-dryrun] [-cycle INT] +% nncp-toss [options] [-dryrun] [-cycle INT] [-seen] @end verbatim Perform "tossing" operation on all inbound packets. This is the tool @@ -390,6 +462,11 @@ tells what it will do. @option{INT} seconds in an infinite loop. That can be useful when running this command as a daemon. +@option{-seen} option creates empty @file{XXX.seen} file after +successful tossing of @file{XXX} packet. @ref{nncp-xfer} and +@ref{nncp-bundle} commands skip inbound packets that has been already +seen, processed and tossed. This is helpful to defeat duplicates. + @node nncp-xfer @section nncp-xfer @@ -418,3 +495,6 @@ configuration file version without any private keys. @file{DIR} directory has the following structure: @file{RECIPIENT/SENDER/PACKET}, where @file{RECIPIENT} is Base32 encoded destination node, @file{SENDER} is Base32 encoded sender node. + +Also look for @ref{nncp-bundle}, especially if you deal with CD-ROM and +tape drives. diff --git a/doc/comparison.ru.texi b/doc/comparison.ru.texi index a4d101b..73580ae 100644 --- a/doc/comparison.ru.texi +++ b/doc/comparison.ru.texi @@ -42,7 +42,7 @@ дополнительный уровень шифрования и аутентификации для безопасного обмена данными. - FTN сложен в настройке, потому-что это совершенно другой мир + FTN сложен в настройке, потому что это совершенно другой мир программного обеспечения, по-сравнению с Unix-ом. Даже редактор почты будет какой-нибудь GoldEd, а не обычный почтовый клиент. Более того, из коробки не предоставляется никакого шифрования и сильной @@ -53,7 +53,7 @@ @item Передача новостей SMTP ничего не знает о новостях, NNTP и тому подобному. NNCP тоже не - знает, потому-что на текущий день они уже мало используются. + знает, потому что на текущий день они уже мало используются. @item Передача файлов SMTP может передавать файлы только в Base64 кодировке -- это очень diff --git a/doc/download.texi b/doc/download.texi index e325722..7edc060 100644 --- a/doc/download.texi +++ b/doc/download.texi @@ -23,6 +23,10 @@ Tarballs include all necessary required libraries: @multitable {XXXXX} {XXXX KiB} {link sign} {xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx} @headitem Version @tab Size @tab Tarball @tab SHA256 checksum +@item @ref{Release 0.12, 0.12} @tab 978 KiB +@tab @url{download/nncp-0.12.tar.xz, link} @url{download/nncp-0.12.tar.xz.sig, sign} +@tab @code{707B4005 97753B29 73A5F3E5 DAB51B92 21CC296D 690EF4BC ADE93E0D 2595A5F2} + @item @ref{Release 0.11, 0.11} @tab 1031 KiB @tab @url{download/nncp-0.11.tar.xz, link} @url{download/nncp-0.11.tar.xz.sig, sign} @tab @code{D0F73C3B ADBF6B8B 13641A61 4D34F65F 20AF4C84 90894331 BF1F1609 2D65E719} diff --git a/doc/index.texi b/doc/index.texi index 4ce8613..a01d654 100644 --- a/doc/index.texi +++ b/doc/index.texi @@ -37,6 +37,7 @@ A copy of the license is included in the section entitled "Copying conditions". * Commands:: * Niceness:: * Chunked files: Chunked. +* Bundles:: * Spool directory: Spool. * Log format: Log. * Packet format: Packet. @@ -59,6 +60,7 @@ A copy of the license is included in the section entitled "Copying conditions". @include cmds.texi @include niceness.texi @include chunked.texi +@include bundles.texi @include spool.texi @include log.texi @include pkt.texi diff --git a/doc/news.ru.texi b/doc/news.ru.texi index 98804fb..ce983f3 100644 --- a/doc/news.ru.texi +++ b/doc/news.ru.texi @@ -1,6 +1,32 @@ @node Новости @section Новости +@node Релиз 1.0 +@subsection Релиз 1.0 +@itemize +@item +@strong{Несовместимое} изменение формата зашифрованных пакетов. Работа +со старыми версиями не поддерживается. +@item +@command{nncp-bundle} команда может создавать потоки зашифрованных +пакетов или потреблять их. Это полезно когда речь идёт о stdin/stdout +методах передачи (например запись на CD-ROM без создания промежуточного +подготовленного ISO образа или работа с ленточными накопителями). +@item +@command{nncp-toss} команда может создавать @file{.seen} файлы, +предотвращая приём дублированных пакетов. +@item +В команде @command{nncp-call} разрешается иметь только одного +обработчика контрольной суммы в фоне. Это полезно когда тысячи маленьких +входящих пакетов могут создать много горутин. +@item +Возможность переопределить путь до spool директории и файла журнала +через аргумент командной строки или переменную окружения. +@item +@command{nncp-rm} команда может удалять все исходящие/входящие, +@file{.seen}, @file{.part}, @file{.lock} и временные файлы. +@end itemize + @node Релиз 0.12 @subsection Релиз 0.12 @itemize diff --git a/doc/news.texi b/doc/news.texi index 8f36b4b..189311d 100644 --- a/doc/news.texi +++ b/doc/news.texi @@ -3,6 +3,32 @@ See also this page @ref{Новости, on russian}. +@node Release 1.0 +@section Release 1.0 +@itemize +@item +@strong{Incompatible} encrypted packet format changes. Older versions +are not supported. +@item +@command{nncp-bundle} command can either create stream of encrypted +packets, or digest it. It is useful when dealing with stdin/stdout based +transmission methods (like writing to CD-ROM without intermediate +prepared ISO image and working with tape drives). +@item +@command{nncp-toss} is able to create @file{.seen} files preventing +duplicate packets receiving. +@item +Single background checksum verifier worker is allowed in +@command{nncp-call}. This is helpful when thousands of small inbound +packets could create many goroutines. +@item +Ability to override path to spool directory and logfile through either +command line argument, or environment variable. +@item +@command{nncp-rm} is able to delete outbound/inbound, @file{.seen}, +@file{.part}, @file{.lock} and temporary files. +@end itemize + @node Release 0.12 @section Release 0.12 @itemize diff --git a/doc/pkt.texi b/doc/pkt.texi index 4549505..c010399 100644 --- a/doc/pkt.texi +++ b/doc/pkt.texi @@ -70,13 +70,13 @@ Each encrypted packet has the following header: @verbatim +------------ HEADER -------------+ +-------- ENCRYPTED --------+ / \ / \ -+-------------------------------------+------------+----...-----------+------+ -| MAGIC | NICE | SENDER | EPUB | SIGN | SIZE | MAC | CIPHERTEXT | MAC | JUNK | -+------------------------------/------\------------+----...-----------+------+ - / \ - +-------------------------------------+ - | MAGIC | NICE | RCPT | SENDER | EPUB | - +-------------------------------------+ ++--------------------------------------------+------------+----...-----------+------+ +| MAGIC | NICE | SENDER | RCPT | EPUB | SIGN | SIZE | MAC | CIPHERTEXT | MAC | JUNK | ++-------------------------------------/------\------------+----...-----------+------+ + / \ + +-------------------------------------+ + | MAGIC | NICE | SENDER | RCPT | EPUB | + +-------------------------------------+ @end verbatim @multitable @columnfractions 0.2 0.3 0.5 @@ -90,6 +90,9 @@ Each encrypted packet has the following header: @item Sender @tab 32-byte, fixed length opaque data @tab Sender node's id +@item Recipient @tab + 32-byte, fixed length opaque data @tab + Recipient node's id @item Exchange public key @tab 32-byte, fixed length opaque data @tab Ephemeral curve25519 public key @@ -98,15 +101,7 @@ Each encrypted packet has the following header: ed25519 signature for that packet's header @end multitable -Signature is calculated over the following structure: - -@itemize -@item Magic number -@item Niceness -@item Recipient (32-byte recipient node's id) -@item Sender -@item Exchange public key -@end itemize +Signature is calculated over all previous fields. All following encryption is done using @url{https://www.schneier.com/academic/twofish/, Twofish} algorithm with diff --git a/doc/spool.texi b/doc/spool.texi index 53ea301..8a4ce6c 100644 --- a/doc/spool.texi +++ b/doc/spool.texi @@ -13,7 +13,9 @@ spool/2WHB...OABQ/tx.lock spool/BYRR...CG6Q/rx.lock spool/BYRR...CG6Q/rx/ spool/BYRR...CG6Q/tx.lock +spool/BYRR...CG6Q/tx/AQUT...DGNT.seen spool/BYRR...CG6Q/tx/NSYY...ZUU6 +spool/BYRR...CG6Q/tx/VCSR...3VXX.seen spool/BYRR...CG6Q/tx/ZI5U...5RRQ @end verbatim @@ -30,5 +32,9 @@ partly received file from @file{2WHB...OABQ} node. @file{tx} directory can not contain partly written files -- they are moved atomically from @file{tmp}. +When @ref{nncp-toss} utility is called with @option{-seen} option, it +will create empty @file{XXX.seen} files, telling that some kind of +packet was already tossed sometime. + Only one process can work with @file{rx}/@file{tx} directories at once, so there are corresponding lock files. diff --git a/doc/usecases.ru.texi b/doc/usecases.ru.texi index cebf333..1407c8e 100644 --- a/doc/usecases.ru.texi +++ b/doc/usecases.ru.texi @@ -7,6 +7,7 @@ * Ненадёжный/дорогой канал связи: UsecaseUnreliableRU. * Медленная/дорогая связь для больших объёмов данных, плохой QoS: UsecaseQoSRU. * Экстремальные наземные окружающие условия, нет связи: UsecaseNoLinkRU. +* Односторонняя широковещательная связь: UsecaseBroadcastRU. * Частные, изолированные MitM/Sybil-устойчивые сети: UsecaseF2FRU. * Высоко защищённые изолированные компьютеры с воздушным зазором: UsecaseAirgapRU. * Обход сетевой цензуры, здоровье: UsecaseCensorRU. @@ -113,6 +114,13 @@ NNCP поддерживает @ref{Niceness, приоритезацию траф давая возможность передачи, по сути, любых объёмов используя накопители небольших размеров. +Вы также можете использовать CD-ROM и ленточные накопители: + +@verbatim +% nncp-bundle -tx bob | cdrecord -tao - +% nncp-bundle -tx bob | dd of=/dev/sa0 bs=10240 +@end verbatim + @node UsecaseNoLinkRU @subsection Экстремальные наземные окружающие условия, нет связи @@ -153,6 +161,27 @@ NNCP поддерживает @ref{Niceness, приоритезацию траф дальнейшей обработки. @command{nncp-xfer} это единственная команда используемая с переносными устройствами хранения. +@node UsecaseBroadcastRU +@subsection Односторонняя широковещательная связь + +Иногда у вас есть ёмкий, но односторонний, канал связи, например +широковещательный сигнал со спутника. Вы не можете использовать online +@ref{Sync, протокол синхронизации}, потому что он требует двустороннего +взаимодействия. + +Вы можете использовать, так называемые, @ref{Bundles, пачки} и потоково +отсылать их. Они -- всего-лишь последовательность @ref{Encrypted, +зашифрованных пакетов}, которые вы можете принять. + +@verbatim +% nncp-bundle -tx alice bob eve ... | команда для отправки широковещательной рассылки +% команда для приёма широковещательной рассылки | nncp-bundle -rx +@end verbatim + +Встроенная возможность определять дубляжи пакетов позволит вам +переотправлять широковещательные рассылки время от времени, повышая +шансы на то, что получатель примет их, регулярно слушая рассылку. + @node UsecaseF2FRU @subsection Частные, изолированные MitM/Sybil-устойчивые сети @@ -291,7 +320,7 @@ Bluetooth и WiFi могут быть и довольно быстрыми, по необходимо запустить другую фазу: @ref{nncp-toss, распаковку}, которая использует ваши приватные криптографические ключи. То есть, даже если вы потеряете свой компьютер, устройства хранения и тому прочее -- это не -так плохо, потому-что вы не носите с собой приватные ключи (ведь так?), +так плохо, потому что вы не носите с собой приватные ключи (ведь так?), вы не "распаковываете" эти пакеты сразу же на том же самом устройстве. Распаковка (чтение этих зашифрованных пакетов с извлечением переданных файлов и почтовых сообщений) может и должна бы быть произведена на diff --git a/doc/usecases.texi b/doc/usecases.texi index 81af4bb..47307f8 100644 --- a/doc/usecases.texi +++ b/doc/usecases.texi @@ -9,6 +9,7 @@ See also this page @ref{Сценарии, on russian}. * Unreliable/expensive communication link: UsecaseUnreliable. * Slow/expensive link for high-volume data, bad QoS: UsecaseQoS. * Extreme terrestrial environments, no link: UsecaseNoLink. +* One-way broadcasting communications: UsecaseBroadcast. * Private, isolated MitM/Sybil-resistant networks: UsecaseF2F. * Highly secure isolated air-gap computers: UsecaseAirgap. * Network censorship bypassing, health: UsecaseCensor. @@ -108,6 +109,13 @@ Huge files could be split on smaller @ref{Chunked, chunks}, giving possibility to transfer virtually any volumes using small capacity storages. +You can also use CD-ROM and tape drives: + +@verbatim +% nncp-bundle -tx bob | cdrecord -tao - +% nncp-bundle -tx bob | dd of=/dev/sa0 bs=10240 +@end verbatim + @node UsecaseNoLink @section Extreme terrestrial environments, no link @@ -145,6 +153,25 @@ to find all packets related to their node and copy them locally for further processing. @command{nncp-xfer} is the only command used with removable devices. +@node UsecaseBroadcast +@section One-way broadcasting communications + +Sometimes you have got high-bandwidth but unidirectional link, for +example, satellite's broadcasting signal. You are not able to use online +@ref{Sync, synchronization protocol} because it requires mutual interaction. + +You can use @ref{Bundles, bundles} and stream them above. They are just +a sequence of @ref{Encrypted, encrypted packets} you can catch on. + +@verbatim +% nncp-bundle -tx alice bob eve ... | command to send broadcast +% command to receive broadcast | nncp-bundle -rx +@end verbatim + +With built-in packet duplicates detection ability, you can retransmit +your broadcasts from time to time, to increase chances the recipient +will catch them by regular stream listening. + @node UsecaseF2F @section Private, isolated MitM/Sybil-resistant networks diff --git a/makedist.sh b/makedist.sh index fafb226..03870da 100755 --- a/makedist.sh +++ b/makedist.sh @@ -112,9 +112,9 @@ 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 and air-gapped -computers support. But online TCP daemon with full-duplex resumable data -transmission exists. +Out-of-box offline sneakernet/floppynet, dead drops, sequential and +append-only CD-ROM/tape storages, air-gapped computers support. But +online TCP daemon with full-duplex resumable data transmission exists. ------------------------ >8 ------------------------ @@ -161,7 +161,8 @@ NNCP (Node to Node copy) это набор утилит упрощающий б поведения. Поддержка из коробки offline флоппинета, тайников для сброса информации -(dead drop) и компьютеров с "воздушным зазором" (air-gap). Но также +(dead drop), последовательных и только-для-записи CD-ROM/ленточных +хранилищ, компьютеров с "воздушным зазором" (air-gap). Но также существует и online TCP демон с полнодуплексной возобновляемой передачей данных. diff --git a/ports/nncp/Makefile b/ports/nncp/Makefile index 22b3313..c6bf12d 100644 --- a/ports/nncp/Makefile +++ b/ports/nncp/Makefile @@ -25,7 +25,8 @@ PORTDOCS= AUTHORS NEWS NEWS.RU README README.RU THANKS INFO= nncp INSTALL_TARGET= install-strip -PLIST_FILES= bin/nncp-call \ +PLIST_FILES= bin/nncp-bundle \ + bin/nncp-call \ bin/nncp-caller \ bin/nncp-cfgenc \ bin/nncp-cfgmin \ diff --git a/ports/nncp/pkg-descr b/ports/nncp/pkg-descr index d33b292..37b7091 100644 --- a/ports/nncp/pkg-descr +++ b/ports/nncp/pkg-descr @@ -9,8 +9,8 @@ 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 and air-gapped -computers support. But online TCP daemon with full-duplex resumable data -transmission exists. +Out-of-box offline sneakernet/floppynet, dead drops, sequential and +append-only CD-ROM/tape storages, air-gapped computers support. But +online TCP daemon with full-duplex resumable data transmission exists. WWW: http://www.nncpgo.org/ diff --git a/src/cypherpunks.ru/nncp/cfg.go b/src/cypherpunks.ru/nncp/cfg.go index 3a642f6..0a1b1d8 100644 --- a/src/cypherpunks.ru/nncp/cfg.go +++ b/src/cypherpunks.ru/nncp/cfg.go @@ -32,7 +32,9 @@ import ( ) const ( - CfgPathEnv = "NNCPCFG" + CfgPathEnv = "NNCPCFG" + CfgSpoolEnv = "NNCPSPOOL" + CfgLogEnv = "NNCPLOG" ) var ( @@ -414,11 +416,3 @@ func CfgParse(data []byte) (*Ctx, error) { } return &ctx, nil } - -func CfgPathFromEnv(cmdlineFlag *string) (p string) { - p = os.Getenv(CfgPathEnv) - if p == "" { - p = *cmdlineFlag - } - return -} diff --git a/src/cypherpunks.ru/nncp/cmd/nncp-bundle/main.go b/src/cypherpunks.ru/nncp/cmd/nncp-bundle/main.go new file mode 100644 index 0000000..e79f9e1 --- /dev/null +++ b/src/cypherpunks.ru/nncp/cmd/nncp-bundle/main.go @@ -0,0 +1,361 @@ +/* +NNCP -- Node to Node copy, utilities for store-and-forward data exchange +Copyright (C) 2016-2017 Sergey Matveev + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +// Create/digest stream of NNCP encrypted packets +package main + +import ( + "archive/tar" + "bufio" + "bytes" + "flag" + "fmt" + "io" + "io/ioutil" + "log" + "os" + "path/filepath" + "strconv" + "strings" + + "cypherpunks.ru/nncp" + "github.com/davecgh/go-xdr/xdr2" + "golang.org/x/crypto/blake2b" +) + +const ( + CopyBufSize = 1 << 17 +) + +func usage() { + fmt.Fprintf(os.Stderr, nncp.UsageHeader()) + fmt.Fprintln(os.Stderr, "nncp-bundle -- Create/digest stream of NNCP encrypted packets\n") + fmt.Fprintf(os.Stderr, "Usage: %s [options] -tx [-delete] NODE [NODE ...] > ...\n", os.Args[0]) + fmt.Fprintf(os.Stderr, " %s [options] -rx -delete [-dryrun] [NODE ...] < ...\n", os.Args[0]) + fmt.Fprintf(os.Stderr, " %s [options] -rx [-check] [-dryrun] [NODE ...] < ...\n", os.Args[0]) + fmt.Fprintln(os.Stderr, "Options:") + flag.PrintDefaults() +} + +func main() { + var ( + cfgPath = flag.String("cfg", nncp.DefaultCfgPath, "Path to configuration file") + niceRaw = flag.Int("nice", 255, "Minimal required niceness") + doRx = flag.Bool("rx", false, "Receive packets") + doTx = flag.Bool("tx", false, "Transfer packets") + doDelete = flag.Bool("delete", false, "Delete transferred packets") + doCheck = flag.Bool("check", false, "Check integrity while receiving") + dryRun = flag.Bool("dryrun", false, "Do not writings") + 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() + if *warranty { + fmt.Println(nncp.Warranty) + return + } + if *version { + fmt.Println(nncp.VersionGet()) + return + } + if *niceRaw < 1 || *niceRaw > 255 { + log.Fatalln("-nice must be between 1 and 255") + } + nice := uint8(*niceRaw) + if *doRx && *doTx { + log.Fatalln("-rx and -tx can not be set simultaneously") + } + if !*doRx && !*doTx { + log.Fatalln("At least one of -rx and -tx must be specified") + } + + ctx, err := nncp.CtxFromCmdline(*cfgPath, *spoolPath, *logPath, *quiet, *debug) + if err != nil { + log.Fatalln("Error during initialization:", err) + } + + nodeIds := make(map[nncp.NodeId]struct{}, flag.NArg()) + for i := 0; i < flag.NArg(); i++ { + node, err := ctx.FindNode(flag.Arg(i)) + if err != nil { + log.Fatalln("Invalid specified:", err) + } + nodeIds[*node.Id] = struct{}{} + } + + sds := nncp.SDS{} + if *doTx { + sds["xx"] = string(nncp.TTx) + var pktName string + bufStdout := bufio.NewWriter(os.Stdout) + tarWr := tar.NewWriter(bufStdout) + for nodeId, _ := range nodeIds { + sds["node"] = nodeId.String() + for job := range ctx.Jobs(&nodeId, nncp.TTx) { + pktName = filepath.Base(job.Fd.Name()) + sds["pkt"] = pktName + if job.PktEnc.Nice > nice { + ctx.LogD("nncp-bundle", sds, "too nice") + job.Fd.Close() + continue + } + if err = tarWr.WriteHeader(&tar.Header{ + Name: strings.Join([]string{ + nncp.NNCPBundlePrefix, + nodeId.String(), + ctx.SelfId.String(), + pktName, + }, "/"), + Mode: 0440, + Size: job.Size, + Typeflag: tar.TypeReg, + }); err != nil { + log.Fatalln("Error writing tar header:", err) + } + if _, err = io.Copy(tarWr, job.Fd); err != nil { + log.Fatalln("Error during copying to tar:", err) + } + job.Fd.Close() + if err = tarWr.Flush(); err != nil { + log.Fatalln("Error during tar flushing:", err) + } + if err = bufStdout.Flush(); err != nil { + log.Fatalln("Error during stdout flushing:", err) + } + if *doDelete { + if err = os.Remove(job.Fd.Name()); err != nil { + log.Fatalln("Error during deletion:", err) + } + } + ctx.LogI("nncp-bundle", nncp.SdsAdd(sds, nncp.SDS{ + "size": strconv.FormatInt(job.Size, 10), + }), "") + } + } + if err = tarWr.Close(); err != nil { + log.Fatalln("Error during tar closing:", err) + } + } else { + bufStdin := bufio.NewReaderSize(os.Stdin, CopyBufSize*2) + var peeked []byte + var prefixIdx int + var tarR *tar.Reader + var entry *tar.Header + var exists bool + pktEncBuf := make([]byte, nncp.PktEncOverhead) + var pktEnc *nncp.PktEnc + var pktName string + var selfPath string + var dstPath string + for { + peeked, err = bufStdin.Peek(CopyBufSize) + if err != nil && err != io.EOF { + log.Fatalln("Error during reading:", err) + } + prefixIdx = bytes.Index(peeked, []byte(nncp.NNCPBundlePrefix)) + if prefixIdx == -1 { + if err == io.EOF { + break + } + bufStdin.Discard(bufStdin.Buffered() - (len(nncp.NNCPBundlePrefix) - 1)) + continue + } + bufStdin.Discard(prefixIdx) + tarR = tar.NewReader(bufStdin) + sds["xx"] = string(nncp.TRx) + entry, err = tarR.Next() + if err != nil { + if err != io.EOF { + ctx.LogD( + "nncp-bundle", + nncp.SdsAdd(sds, nncp.SDS{"err": err}), + "error reading tar", + ) + } + continue + } + sds["pkt"] = entry.Name + if entry.Size < nncp.PktEncOverhead { + ctx.LogD("nncp-bundle", sds, "Too small packet") + continue + } + pktName = filepath.Base(entry.Name) + if _, err = nncp.FromBase32(pktName); err != nil { + ctx.LogD("nncp-bundle", sds, "Bad packet name") + continue + } + if _, err = io.ReadFull(tarR, pktEncBuf); err != nil { + ctx.LogD("nncp-bundle", nncp.SdsAdd(sds, nncp.SDS{"err": err}), "read") + continue + } + if _, err = xdr.Unmarshal(bytes.NewReader(pktEncBuf), &pktEnc); err != nil { + ctx.LogD("nncp-bundle", sds, "Bad packet structure") + continue + } + if pktEnc.Magic != nncp.MagicNNCPEv2 { + ctx.LogD("nncp-bundle", sds, "Bad packet magic number") + continue + } + if pktEnc.Nice > nice { + ctx.LogD("nncp-bundle", sds, "too nice") + continue + } + if *pktEnc.Sender == *ctx.SelfId && *doDelete { + if len(nodeIds) > 0 { + if _, exists = nodeIds[*pktEnc.Recipient]; !exists { + ctx.LogD("nncp-bundle", sds, "Recipient is not requested") + continue + } + } + nodeId32 := nncp.ToBase32(pktEnc.Recipient[:]) + sds["xx"] = string(nncp.TTx) + sds["node"] = nodeId32 + sds["pkt"] = pktName + dstPath = filepath.Join( + ctx.Spool, + nodeId32, + string(nncp.TTx), + pktName, + ) + if _, err = os.Stat(dstPath); err != nil { + ctx.LogD("nncp-bundle", sds, "Packet is already missing") + continue + } + hsh, err := blake2b.New256(nil) + if err != nil { + log.Fatalln("Error during hasher creation:", err) + } + if _, err = hsh.Write(pktEncBuf); err != nil { + log.Fatalln("Error during writing:", err) + } + if _, err = io.Copy(hsh, tarR); err != nil { + log.Fatalln("Error during copying:", err) + } + if nncp.ToBase32(hsh.Sum(nil)) == pktName { + ctx.LogI("nncp-bundle", sds, "removed") + if !*dryRun { + os.Remove(dstPath) + } + } else { + ctx.LogE("nncp-bundle", sds, "bad checksum") + } + continue + } + if *pktEnc.Recipient != *ctx.SelfId { + ctx.LogD("nncp-bundle", sds, "Unknown recipient") + continue + } + if len(nodeIds) > 0 { + if _, exists = nodeIds[*pktEnc.Sender]; !exists { + ctx.LogD("nncp-bundle", sds, "Sender is not requested") + continue + } + } + sds["node"] = nncp.ToBase32(pktEnc.Recipient[:]) + sds["pkt"] = pktName + selfPath = filepath.Join(ctx.Spool, ctx.SelfId.String(), string(nncp.TRx)) + dstPath = filepath.Join(selfPath, pktName) + if _, err = os.Stat(dstPath); err == nil || !os.IsNotExist(err) { + ctx.LogD("nncp-bundle", sds, "Packet already exists") + continue + } + if _, err = os.Stat(dstPath + nncp.SeenSuffix); err == nil || !os.IsNotExist(err) { + ctx.LogD("nncp-bundle", sds, "Packet already exists") + continue + } + if *doCheck { + if *dryRun { + hsh, err := blake2b.New256(nil) + if err != nil { + log.Fatalln("Error during hasher creation:", err) + } + if _, err = hsh.Write(pktEncBuf); err != nil { + log.Fatalln("Error during writing:", err) + } + if _, err = io.Copy(hsh, tarR); err != nil { + log.Fatalln("Error during copying:", err) + } + if nncp.ToBase32(hsh.Sum(nil)) != pktName { + ctx.LogE("nncp-bundle", sds, "bad checksum") + continue + } + } else { + tmp, err := ctx.NewTmpFileWHash() + if err != nil { + log.Fatalln("Error during temporary file creation:", err) + } + if _, err = tmp.W.Write(pktEncBuf); err != nil { + log.Fatalln("Error during writing:", err) + } + if _, err = io.Copy(tmp.W, tarR); err != nil { + log.Fatalln("Error during copying:", err) + } + if err = tmp.W.Flush(); err != nil { + log.Fatalln("Error during flusing:", err) + } + if nncp.ToBase32(tmp.Hsh.Sum(nil)) == pktName { + if err = tmp.Commit(selfPath); err != nil { + log.Fatalln("Error during commiting:", err) + } + } else { + ctx.LogE("nncp-bundle", sds, "bad checksum") + tmp.Cancel() + continue + } + } + } else { + if *dryRun { + if _, err = io.Copy(ioutil.Discard, tarR); err != nil { + log.Fatalln("Error during copying:", err) + } + } else { + tmp, err := ctx.NewTmpFile() + if err != nil { + log.Fatalln("Error during temporary file creation:", err) + } + bufTmp := bufio.NewWriterSize(tmp, CopyBufSize) + if _, err = bufTmp.Write(pktEncBuf); err != nil { + log.Fatalln("Error during writing:", err) + } + if _, err = io.Copy(bufTmp, tarR); err != nil { + log.Fatalln("Error during copying:", err) + } + if err = bufTmp.Flush(); err != nil { + log.Fatalln("Error during flushing:", err) + } + tmp.Sync() + tmp.Close() + if err = os.MkdirAll(selfPath, os.FileMode(0700)); err != nil { + log.Fatalln("Error during mkdir:", err) + } + if err = os.Rename(tmp.Name(), dstPath); err != nil { + log.Fatalln("Error during renaming:", err) + } + } + } + ctx.LogI("nncp-bundle", nncp.SdsAdd(sds, nncp.SDS{ + "size": strconv.FormatInt(entry.Size, 10), + }), "") + } + } +} diff --git a/src/cypherpunks.ru/nncp/cmd/nncp-call/main.go b/src/cypherpunks.ru/nncp/cmd/nncp-call/main.go index 42f761f..062593e 100644 --- a/src/cypherpunks.ru/nncp/cmd/nncp-call/main.go +++ b/src/cypherpunks.ru/nncp/cmd/nncp-call/main.go @@ -22,7 +22,6 @@ package main import ( "flag" "fmt" - "io/ioutil" "log" "os" "strings" @@ -40,14 +39,16 @@ func usage() { func main() { var ( - cfgPath = flag.String("cfg", nncp.DefaultCfgPath, "Path to configuration file") - niceRaw = flag.Int("nice", 255, "Minimal required niceness") - rxOnly = flag.Bool("rx", false, "Only receive packets") - txOnly = flag.Bool("tx", false, "Only transfer packets") - 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", 255, "Minimal required niceness") + rxOnly = flag.Bool("rx", false, "Only receive packets") + txOnly = flag.Bool("tx", false, "Only transfer packets") + 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") onlineDeadline = flag.Uint("onlinedeadline", 0, "Override onlinedeadline option") maxOnlineTime = flag.Uint("maxonlinetime", 0, "Override maxonlinetime option") @@ -74,19 +75,13 @@ func main() { log.Fatalln("-rx and -tx can not be set simultaneously") } - cfgRaw, err := ioutil.ReadFile(nncp.CfgPathFromEnv(cfgPath)) + ctx, err := nncp.CtxFromCmdline(*cfgPath, *spoolPath, *logPath, *quiet, *debug) if err != nil { - log.Fatalln("Can not read config:", err) - } - ctx, err := nncp.CfgParse(cfgRaw) - if err != nil { - log.Fatalln("Can not parse config:", err) + log.Fatalln("Error during initialization:", err) } if ctx.Self == nil { log.Fatalln("Config lacks private keys") } - ctx.Quiet = *quiet - ctx.Debug = *debug splitted := strings.SplitN(flag.Arg(0), ":", 2) node, err := ctx.FindNode(splitted[0]) diff --git a/src/cypherpunks.ru/nncp/cmd/nncp-caller/main.go b/src/cypherpunks.ru/nncp/cmd/nncp-caller/main.go index 3cbbe8f..459a623 100644 --- a/src/cypherpunks.ru/nncp/cmd/nncp-caller/main.go +++ b/src/cypherpunks.ru/nncp/cmd/nncp-caller/main.go @@ -22,7 +22,6 @@ package main import ( "flag" "fmt" - "io/ioutil" "log" "os" "strconv" @@ -42,11 +41,13 @@ func usage() { func main() { var ( - cfgPath = flag.String("cfg", nncp.DefaultCfgPath, "Path to configuration file") - 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") + 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() @@ -59,19 +60,13 @@ func main() { return } - cfgRaw, err := ioutil.ReadFile(nncp.CfgPathFromEnv(cfgPath)) + ctx, err := nncp.CtxFromCmdline(*cfgPath, *spoolPath, *logPath, *quiet, *debug) if err != nil { - log.Fatalln("Can not read config:", err) - } - ctx, err := nncp.CfgParse(cfgRaw) - if err != nil { - log.Fatalln("Can not parse config:", err) + log.Fatalln("Error during initialization:", err) } if ctx.Self == nil { log.Fatalln("Config lacks private keys") } - ctx.Quiet = *quiet - ctx.Debug = *debug var nodes []*nncp.Node if flag.NArg() > 0 { diff --git a/src/cypherpunks.ru/nncp/cmd/nncp-cfgmin/main.go b/src/cypherpunks.ru/nncp/cmd/nncp-cfgmin/main.go index 48df34e..7de3247 100644 --- a/src/cypherpunks.ru/nncp/cmd/nncp-cfgmin/main.go +++ b/src/cypherpunks.ru/nncp/cmd/nncp-cfgmin/main.go @@ -22,7 +22,6 @@ package main import ( "flag" "fmt" - "io/ioutil" "log" "os" @@ -54,13 +53,9 @@ func main() { return } - cfgRaw, err := ioutil.ReadFile(nncp.CfgPathFromEnv(cfgPath)) + ctx, err := nncp.CtxFromCmdline(*cfgPath, "", "", false, false) if err != nil { - log.Fatalln("Can not read config:", err) - } - ctx, err := nncp.CfgParse(cfgRaw) - if err != nil { - log.Fatalln("Can not parse config:", err) + log.Fatalln("Error during initialization:", err) } cfg := nncp.CfgYAML{ diff --git a/src/cypherpunks.ru/nncp/cmd/nncp-check/main.go b/src/cypherpunks.ru/nncp/cmd/nncp-check/main.go index 2c06b14..a10c8cf 100644 --- a/src/cypherpunks.ru/nncp/cmd/nncp-check/main.go +++ b/src/cypherpunks.ru/nncp/cmd/nncp-check/main.go @@ -22,7 +22,6 @@ package main import ( "flag" "fmt" - "io/ioutil" "log" "os" @@ -38,12 +37,14 @@ func usage() { func main() { var ( - cfgPath = flag.String("cfg", nncp.DefaultCfgPath, "Path to configuration file") - nodeRaw = flag.String("node", "", "Process only that node") - 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") + nodeRaw = flag.String("node", "", "Process only that 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() @@ -56,16 +57,10 @@ func main() { return } - cfgRaw, err := ioutil.ReadFile(nncp.CfgPathFromEnv(cfgPath)) + ctx, err := nncp.CtxFromCmdline(*cfgPath, *spoolPath, *logPath, *quiet, *debug) if err != nil { - log.Fatalln("Can not read config:", err) + log.Fatalln("Error during initialization:", err) } - ctx, err := nncp.CfgParse(cfgRaw) - if err != nil { - log.Fatalln("Can not parse config:", err) - } - ctx.Quiet = *quiet - ctx.Debug = *debug var nodeOnly *nncp.Node if *nodeRaw != "" { diff --git a/src/cypherpunks.ru/nncp/cmd/nncp-daemon/main.go b/src/cypherpunks.ru/nncp/cmd/nncp-daemon/main.go index e6ec2d3..de0a0b3 100644 --- a/src/cypherpunks.ru/nncp/cmd/nncp-daemon/main.go +++ b/src/cypherpunks.ru/nncp/cmd/nncp-daemon/main.go @@ -22,7 +22,6 @@ package main import ( "flag" "fmt" - "io/ioutil" "log" "net" "os" @@ -41,14 +40,16 @@ func usage() { func main() { var ( - cfgPath = flag.String("cfg", nncp.DefaultCfgPath, "Path to configuration file") - niceRaw = flag.Int("nice", 255, "Minimal required niceness") - bind = flag.String("bind", "[::]:5400", "Address to bind to") - maxConn = flag.Int("maxconn", 128, "Maximal number of simultaneous connections") - 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", 255, "Minimal required niceness") + bind = flag.String("bind", "[::]:5400", "Address to bind to") + maxConn = flag.Int("maxconn", 128, "Maximal number of simultaneous connections") + 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() @@ -65,19 +66,13 @@ func main() { } nice := uint8(*niceRaw) - cfgRaw, err := ioutil.ReadFile(nncp.CfgPathFromEnv(cfgPath)) + ctx, err := nncp.CtxFromCmdline(*cfgPath, *spoolPath, *logPath, *quiet, *debug) if err != nil { - log.Fatalln("Can not read config:", err) - } - ctx, err := nncp.CfgParse(cfgRaw) - if err != nil { - log.Fatalln("Can not parse config:", err) + log.Fatalln("Error during initialization:", err) } if ctx.Self == nil { log.Fatalln("Config lacks private keys") } - ctx.Quiet = *quiet - ctx.Debug = *debug ln, err := net.Listen("tcp", *bind) if err != nil { diff --git a/src/cypherpunks.ru/nncp/cmd/nncp-file/main.go b/src/cypherpunks.ru/nncp/cmd/nncp-file/main.go index d922882..3c35ab9 100644 --- a/src/cypherpunks.ru/nncp/cmd/nncp-file/main.go +++ b/src/cypherpunks.ru/nncp/cmd/nncp-file/main.go @@ -22,7 +22,6 @@ package main import ( "flag" "fmt" - "io/ioutil" "log" "os" "strings" @@ -46,6 +45,8 @@ func main() { niceRaw = flag.Int("nice", nncp.DefaultNiceFile, "Outbound packet niceness") minSize = flag.Uint64("minsize", 0, "Minimal required resulting packet size, in KiB") chunkSize = flag.Uint64("chunked", 0, "Split file on specified size chunks, in KiB") + 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") @@ -70,19 +71,13 @@ func main() { } nice := uint8(*niceRaw) - cfgRaw, err := ioutil.ReadFile(nncp.CfgPathFromEnv(cfgPath)) + ctx, err := nncp.CtxFromCmdline(*cfgPath, *spoolPath, *logPath, *quiet, *debug) if err != nil { - log.Fatalln("Can not read config:", err) - } - ctx, err := nncp.CfgParse(cfgRaw) - if err != nil { - log.Fatalln("Can not parse config:", err) + log.Fatalln("Error during initialization:", err) } if ctx.Self == nil { log.Fatalln("Config lacks private keys") } - ctx.Quiet = *quiet - ctx.Debug = *debug splitted := strings.SplitN(flag.Arg(1), ":", 2) if len(splitted) != 2 { diff --git a/src/cypherpunks.ru/nncp/cmd/nncp-freq/main.go b/src/cypherpunks.ru/nncp/cmd/nncp-freq/main.go index da5268f..c089abc 100644 --- a/src/cypherpunks.ru/nncp/cmd/nncp-freq/main.go +++ b/src/cypherpunks.ru/nncp/cmd/nncp-freq/main.go @@ -22,7 +22,6 @@ package main import ( "flag" "fmt" - "io/ioutil" "log" "os" "path/filepath" @@ -40,13 +39,15 @@ func usage() { func main() { var ( - cfgPath = flag.String("cfg", nncp.DefaultCfgPath, "Path to configuration file") - niceRaw = flag.Int("nice", nncp.DefaultNiceFreq, "Outbound packet niceness") - minSize = flag.Uint64("minsize", 0, "Minimal required resulting packet size, in KiB") - 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.DefaultNiceFreq, "Outbound packet niceness") + minSize = flag.Uint64("minsize", 0, "Minimal required resulting packet size, in KiB") + 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() @@ -67,19 +68,13 @@ func main() { } nice := uint8(*niceRaw) - cfgRaw, err := ioutil.ReadFile(nncp.CfgPathFromEnv(cfgPath)) + ctx, err := nncp.CtxFromCmdline(*cfgPath, *spoolPath, *logPath, *quiet, *debug) if err != nil { - log.Fatalln("Can not read config:", err) - } - ctx, err := nncp.CfgParse(cfgRaw) - if err != nil { - log.Fatalln("Can not parse config:", err) + log.Fatalln("Error during initialization:", err) } if ctx.Self == nil { log.Fatalln("Config lacks private keys") } - ctx.Quiet = *quiet - ctx.Debug = *debug splitted := strings.SplitN(flag.Arg(0), ":", 2) if len(splitted) != 2 { diff --git a/src/cypherpunks.ru/nncp/cmd/nncp-log/main.go b/src/cypherpunks.ru/nncp/cmd/nncp-log/main.go index 653f213..c045715 100644 --- a/src/cypherpunks.ru/nncp/cmd/nncp-log/main.go +++ b/src/cypherpunks.ru/nncp/cmd/nncp-log/main.go @@ -23,7 +23,6 @@ import ( "bufio" "flag" "fmt" - "io/ioutil" "log" "os" @@ -40,6 +39,7 @@ func usage() { func main() { var ( cfgPath = flag.String("cfg", nncp.DefaultCfgPath, "Path to configuration file") + logPath = flag.String("log", "", "Override path to logfile") debug = flag.Bool("debug", false, "Print debug messages") version = flag.Bool("version", false, "Print version information") warranty = flag.Bool("warranty", false, "Print warranty information") @@ -55,13 +55,9 @@ func main() { return } - cfgRaw, err := ioutil.ReadFile(nncp.CfgPathFromEnv(cfgPath)) + ctx, err := nncp.CtxFromCmdline(*cfgPath, "", *logPath, false, *debug) if err != nil { - log.Fatalln("Can not read config:", err) - } - ctx, err := nncp.CfgParse(cfgRaw) - if err != nil { - log.Fatalln("Can not parse config:", err) + log.Fatalln("Error during initialization:", err) } fd, err := os.Open(ctx.LogPath) diff --git a/src/cypherpunks.ru/nncp/cmd/nncp-mail/main.go b/src/cypherpunks.ru/nncp/cmd/nncp-mail/main.go index f5f4de7..f9d346f 100644 --- a/src/cypherpunks.ru/nncp/cmd/nncp-mail/main.go +++ b/src/cypherpunks.ru/nncp/cmd/nncp-mail/main.go @@ -40,13 +40,15 @@ func usage() { 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") - 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.DefaultNiceMail, "Outbound packet niceness") + minSize = flag.Uint64("minsize", 0, "Minimal required resulting packet size, in KiB") + 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() @@ -67,19 +69,13 @@ func main() { } nice := uint8(*niceRaw) - cfgRaw, err := ioutil.ReadFile(nncp.CfgPathFromEnv(cfgPath)) + ctx, err := nncp.CtxFromCmdline(*cfgPath, *spoolPath, *logPath, *quiet, *debug) if err != nil { - log.Fatalln("Can not read config:", err) - } - ctx, err := nncp.CfgParse(cfgRaw) - if err != nil { - log.Fatalln("Can not parse config:", err) + log.Fatalln("Error during initialization:", err) } if ctx.Self == nil { log.Fatalln("Config lacks private keys") } - ctx.Quiet = *quiet - ctx.Debug = *debug node, err := ctx.FindNode(flag.Arg(0)) if 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 93759b6..a7b346f 100644 --- a/src/cypherpunks.ru/nncp/cmd/nncp-pkt/main.go +++ b/src/cypherpunks.ru/nncp/cmd/nncp-pkt/main.go @@ -26,7 +26,6 @@ import ( "flag" "fmt" "io" - "io/ioutil" "log" "os" @@ -112,15 +111,11 @@ func main() { } var pktEnc nncp.PktEnc _, err = xdr.Unmarshal(bytes.NewReader(beginning), &pktEnc) - if err == nil && pktEnc.Magic == nncp.MagicNNCPEv1 { + if err == nil && pktEnc.Magic == nncp.MagicNNCPEv2 { if *dump { - cfgRaw, err := ioutil.ReadFile(nncp.CfgPathFromEnv(cfgPath)) + ctx, err := nncp.CtxFromCmdline(*cfgPath, "", "", false, false) if err != nil { - log.Fatalln("Can not read config:", err) - } - ctx, err := nncp.CfgParse(cfgRaw) - if err != nil { - log.Fatalln("Can not parse config:", err) + log.Fatalln("Error during initialization:", err) } if ctx.Self == nil { log.Fatalln("Config lacks private keys") @@ -143,8 +138,8 @@ func main() { return } fmt.Printf( - "Packet type: encrypted\nNiceness: %d\nSender: %s\n", - pktEnc.Nice, pktEnc.Sender, + "Packet type: encrypted\nNiceness: %d\nSender: %s\nRecipient: %s\n", + pktEnc.Nice, pktEnc.Sender, pktEnc.Recipient, ) return } diff --git a/src/cypherpunks.ru/nncp/cmd/nncp-reass/main.go b/src/cypherpunks.ru/nncp/cmd/nncp-reass/main.go index 45354d7..9c6cae2 100644 --- a/src/cypherpunks.ru/nncp/cmd/nncp-reass/main.go +++ b/src/cypherpunks.ru/nncp/cmd/nncp-reass/main.go @@ -264,17 +264,19 @@ func findMetas(ctx *nncp.Ctx, dirPath string) []string { func main() { var ( - cfgPath = flag.String("cfg", nncp.DefaultCfgPath, "Path to configuration file") - allNodes = flag.Bool("all", false, "Process all found chunked files for all nodes") - nodeRaw = flag.String("node", "", "Process all found chunked files for that node") - keep = flag.Bool("keep", false, "Do not remove chunks while assembling") - dryRun = flag.Bool("dryrun", false, "Do not assemble whole file") - dumpMeta = flag.Bool("dump", false, "Print decoded human-readable FILE.nncp.meta") - stdout = flag.Bool("stdout", false, "Output reassembled FILE to stdout") - 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") + allNodes = flag.Bool("all", false, "Process all found chunked files for all nodes") + nodeRaw = flag.String("node", "", "Process all found chunked files for that node") + keep = flag.Bool("keep", false, "Do not remove chunks while assembling") + dryRun = flag.Bool("dryrun", false, "Do not assemble whole file") + dumpMeta = flag.Bool("dump", false, "Print decoded human-readable FILE.nncp.meta") + stdout = flag.Bool("stdout", false, "Output reassembled FILE to stdout") + 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() @@ -287,16 +289,10 @@ func main() { return } - cfgRaw, err := ioutil.ReadFile(nncp.CfgPathFromEnv(cfgPath)) + ctx, err := nncp.CtxFromCmdline(*cfgPath, *spoolPath, *logPath, *quiet, *debug) if err != nil { - log.Fatalln("Can not read config:", err) + log.Fatalln("Error during initialization:", err) } - ctx, err := nncp.CfgParse(cfgRaw) - if err != nil { - log.Fatalln("Can not parse config:", err) - } - ctx.Quiet = *quiet - ctx.Debug = *debug var nodeOnly *nncp.Node if *nodeRaw != "" { diff --git a/src/cypherpunks.ru/nncp/cmd/nncp-rm/main.go b/src/cypherpunks.ru/nncp/cmd/nncp-rm/main.go index 00a13a4..b616ad8 100644 --- a/src/cypherpunks.ru/nncp/cmd/nncp-rm/main.go +++ b/src/cypherpunks.ru/nncp/cmd/nncp-rm/main.go @@ -22,10 +22,10 @@ package main import ( "flag" "fmt" - "io/ioutil" "log" "os" "path/filepath" + "strings" "cypherpunks.ru/nncp" ) @@ -33,17 +33,32 @@ import ( func usage() { fmt.Fprintf(os.Stderr, nncp.UsageHeader()) fmt.Fprintln(os.Stderr, "nncp-rm -- remove packet\n") - fmt.Fprintf(os.Stderr, "Usage: %s [options] NODE PKT\nOptions:\n", os.Args[0]) + fmt.Fprintf(os.Stderr, "Usage: %s [options] -tmp\n", os.Args[0]) + fmt.Fprintf(os.Stderr, " %s [options] -lock\n", os.Args[0]) + fmt.Fprintf(os.Stderr, " %s [options] -node NODE -part\n", os.Args[0]) + fmt.Fprintf(os.Stderr, " %s [options] -node NODE -seen\n", os.Args[0]) + fmt.Fprintf(os.Stderr, " %s [options] -node NODE {-rx|-tx}\n", os.Args[0]) + fmt.Fprintf(os.Stderr, " %s [options] -node NODE -pkt PKT\n", os.Args[0]) + fmt.Fprintln(os.Stderr, "Options:") flag.PrintDefaults() } func main() { var ( - cfgPath = flag.String("cfg", nncp.DefaultCfgPath, "Path to configuration file") - 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") + doTmp = flag.Bool("tmp", false, "Remove all temporary files") + doLock = flag.Bool("lock", false, "Remove all lock files") + nodeRaw = flag.String("node", "", "Node to remove files in") + doRx = flag.Bool("rx", false, "Process received packets") + doTx = flag.Bool("tx", false, "Process transfered packets") + doPart = flag.Bool("part", false, "Remove only .part files") + doSeen = flag.Bool("seen", false, "Remove only .seen files") + pktRaw = flag.String("pkt", "", "Packet to remove") + spoolPath = flag.String("spool", "", "Override path to spool") + 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() @@ -55,42 +70,93 @@ func main() { fmt.Println(nncp.VersionGet()) return } - if flag.NArg() != 2 { - usage() - os.Exit(1) - } - cfgRaw, err := ioutil.ReadFile(nncp.CfgPathFromEnv(cfgPath)) + ctx, err := nncp.CtxFromCmdline(*cfgPath, *spoolPath, "", *quiet, *debug) if err != nil { - log.Fatalln("Can not read config:", err) + log.Fatalln("Error during initialization:", err) } - ctx, err := nncp.CfgParse(cfgRaw) - if err != nil { - log.Fatalln("Can not parse config:", err) - } - ctx.Quiet = *quiet - ctx.Debug = *debug - node, err := ctx.FindNode(flag.Arg(0)) + if *doTmp { + err = filepath.Walk(filepath.Join(ctx.Spool, "tmp"), func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + ctx.LogI("nncp-rm", nncp.SDS{"file": path}, "") + return os.Remove(path) + }) + if err != nil { + log.Fatalln("Error during walking:", err) + } + return + } + if *doLock { + err = filepath.Walk(ctx.Spool, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + if strings.HasSuffix(info.Name(), ".lock") { + ctx.LogI("nncp-rm", nncp.SDS{"file": path}, "") + return os.Remove(path) + } + return nil + }) + if err != nil { + log.Fatalln("Error during walking:", err) + } + return + } + if *nodeRaw == "" { + usage() + os.Exit(1) + } + node, err := ctx.FindNode(*nodeRaw) if err != nil { - log.Fatalln("Invalid NODE specified:", err) + log.Fatalln("Invalid -node specified:", err) } - - pktName := flag.Arg(1) - remove := func(xx nncp.TRxTx) bool { - for job := range ctx.Jobs(node.Id, xx) { - job.Fd.Close() - if filepath.Base(job.Fd.Name()) == pktName { - if err = os.Remove(job.Fd.Name()); err != nil { - log.Fatalln("Can not remove packet:", err) - } - return true + remove := func(xx nncp.TRxTx) error { + return filepath.Walk(filepath.Join(ctx.Spool, node.Id.String(), string(xx)), func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil } + if *doSeen && strings.HasSuffix(info.Name(), nncp.SeenSuffix) { + ctx.LogI("nncp-rm", nncp.SDS{"file": path}, "") + return os.Remove(path) + } + if *doPart && strings.HasSuffix(info.Name(), nncp.PartSuffix) { + ctx.LogI("nncp-rm", nncp.SDS{"file": path}, "") + return os.Remove(path) + } + if *pktRaw != "" && filepath.Base(info.Name()) == *pktRaw { + ctx.LogI("nncp-rm", nncp.SDS{"file": path}, "") + return os.Remove(path) + } + if !*doSeen && + !*doPart && + (*doRx || *doTx) && + ((*doRx && xx == nncp.TRx) || (*doTx && xx == nncp.TTx)) { + ctx.LogI("nncp-rm", nncp.SDS{"file": path}, "") + return os.Remove(path) + } + return nil + }) + } + if *pktRaw != "" || *doRx || *doSeen || *doPart { + if err = remove(nncp.TRx); err != nil { + log.Fatalln("Can not remove:", err) } - return false } - - if !(remove(nncp.TRx) || remove(nncp.TTx)) { - log.Fatalln("Have not found specified packet") + if *pktRaw != "" || *doTx { + if err = remove(nncp.TTx); err != nil { + log.Fatalln("Can not remove:", err) + } } } diff --git a/src/cypherpunks.ru/nncp/cmd/nncp-stat/main.go b/src/cypherpunks.ru/nncp/cmd/nncp-stat/main.go index 0c27b1b..fd20e62 100644 --- a/src/cypherpunks.ru/nncp/cmd/nncp-stat/main.go +++ b/src/cypherpunks.ru/nncp/cmd/nncp-stat/main.go @@ -22,7 +22,6 @@ package main import ( "flag" "fmt" - "io/ioutil" "log" "os" "sort" @@ -40,11 +39,12 @@ func usage() { func main() { var ( - cfgPath = flag.String("cfg", nncp.DefaultCfgPath, "Path to configuration file") - nodeRaw = flag.String("node", "", "Process only that node") - 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") + nodeRaw = flag.String("node", "", "Process only that node") + spoolPath = flag.String("spool", "", "Override path to spool") + 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() @@ -57,15 +57,10 @@ func main() { return } - cfgRaw, err := ioutil.ReadFile(nncp.CfgPathFromEnv(cfgPath)) + ctx, err := nncp.CtxFromCmdline(*cfgPath, *spoolPath, "", false, *debug) if err != nil { - log.Fatalln("Can not read config:", err) + log.Fatalln("Error during initialization:", err) } - ctx, err := nncp.CfgParse(cfgRaw) - if err != nil { - log.Fatalln("Can not parse config:", err) - } - ctx.Debug = *debug var nodeOnly *nncp.Node if *nodeRaw != "" { diff --git a/src/cypherpunks.ru/nncp/cmd/nncp-toss/main.go b/src/cypherpunks.ru/nncp/cmd/nncp-toss/main.go index 71a15ee..eb2158c 100644 --- a/src/cypherpunks.ru/nncp/cmd/nncp-toss/main.go +++ b/src/cypherpunks.ru/nncp/cmd/nncp-toss/main.go @@ -22,7 +22,6 @@ package main import ( "flag" "fmt" - "io/ioutil" "log" "os" "time" @@ -39,15 +38,18 @@ func usage() { func main() { var ( - cfgPath = flag.String("cfg", nncp.DefaultCfgPath, "Path to configuration file") - nodeRaw = flag.String("node", "", "Process only that node") - niceRaw = flag.Int("nice", 255, "Minimal required niceness") - dryRun = flag.Bool("dryrun", false, "Do not actually write any tossed data") - cycle = flag.Uint("cycle", 0, "Repeat tossing after N seconds in infinite loop") - 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") + nodeRaw = flag.String("node", "", "Process only that node") + niceRaw = flag.Int("nice", 255, "Minimal required niceness") + dryRun = flag.Bool("dryrun", false, "Do not actually write any tossed data") + doSeen = flag.Bool("seen", false, "Create .seen files") + cycle = flag.Uint("cycle", 0, "Repeat tossing after N seconds in infinite loop") + 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() @@ -64,19 +66,13 @@ func main() { } nice := uint8(*niceRaw) - cfgRaw, err := ioutil.ReadFile(nncp.CfgPathFromEnv(cfgPath)) + ctx, err := nncp.CtxFromCmdline(*cfgPath, *spoolPath, *logPath, *quiet, *debug) if err != nil { - log.Fatalln("Can not read config:", err) - } - ctx, err := nncp.CfgParse(cfgRaw) - if err != nil { - log.Fatalln("Can not parse config:", err) + log.Fatalln("Error during initialization:", err) } if ctx.Self == nil { log.Fatalln("Config lacks private keys") } - ctx.Quiet = *quiet - ctx.Debug = *debug var nodeOnly *nncp.Node if *nodeRaw != "" { @@ -92,7 +88,7 @@ Cycle: if nodeOnly != nil && nodeId != *nodeOnly.Id { continue } - isBad = ctx.Toss(node.Id, nice, *dryRun) + isBad = ctx.Toss(node.Id, nice, *dryRun, *doSeen) } if *cycle > 0 { time.Sleep(time.Duration(*cycle) * time.Second) diff --git a/src/cypherpunks.ru/nncp/cmd/nncp-xfer/main.go b/src/cypherpunks.ru/nncp/cmd/nncp-xfer/main.go index bcd7625..ffd1c0c 100644 --- a/src/cypherpunks.ru/nncp/cmd/nncp-xfer/main.go +++ b/src/cypherpunks.ru/nncp/cmd/nncp-xfer/main.go @@ -43,17 +43,19 @@ func usage() { func main() { var ( - cfgPath = flag.String("cfg", nncp.DefaultCfgPath, "Path to configuration file") - nodeRaw = flag.String("node", "", "Process only that node") - niceRaw = flag.Int("nice", 255, "Minimal required niceness") - rxOnly = flag.Bool("rx", false, "Only receive packets") - txOnly = flag.Bool("tx", false, "Only transfer packets") - mkdir = flag.Bool("mkdir", false, "Create necessary outbound directories") - keep = flag.Bool("keep", false, "Do not delete transferred packets") - 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") + nodeRaw = flag.String("node", "", "Process only that node") + niceRaw = flag.Int("nice", 255, "Minimal required niceness") + rxOnly = flag.Bool("rx", false, "Only receive packets") + txOnly = flag.Bool("tx", false, "Only transfer packets") + mkdir = flag.Bool("mkdir", false, "Create necessary outbound directories") + keep = flag.Bool("keep", false, "Do not delete transferred packets") + 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() @@ -77,16 +79,10 @@ func main() { log.Fatalln("-rx and -tx can not be set simultaneously") } - cfgRaw, err := ioutil.ReadFile(nncp.CfgPathFromEnv(cfgPath)) + ctx, err := nncp.CtxFromCmdline(*cfgPath, *spoolPath, *logPath, *quiet, *debug) if err != nil { - log.Fatalln("Can not read config:", err) + log.Fatalln("Error during initialization:", err) } - ctx, err := nncp.CfgParse(cfgRaw) - if err != nil { - log.Fatalln("Can not parse config:", err) - } - ctx.Quiet = *quiet - ctx.Debug = *debug var nodeOnly *nncp.Node if *nodeRaw != "" { @@ -174,7 +170,7 @@ func main() { } var pktEnc nncp.PktEnc _, err = xdr.Unmarshal(fd, &pktEnc) - if err != nil || pktEnc.Magic != nncp.MagicNNCPEv1 { + if err != nil || pktEnc.Magic != nncp.MagicNNCPEv2 { ctx.LogD("nncp-xfer", sds, "is not a packet") fd.Close() continue @@ -285,6 +281,16 @@ Tx: job.Fd.Close() continue } + if _, err = os.Stat(filepath.Join(dstPath, pktName)); err == nil || !os.IsNotExist(err) { + ctx.LogD("nncp-xfer", sds, "already exists") + job.Fd.Close() + continue + } + if _, err = os.Stat(filepath.Join(dstPath, pktName+nncp.SeenSuffix)); err == nil || !os.IsNotExist(err) { + ctx.LogD("nncp-xfer", sds, "already exists") + job.Fd.Close() + continue + } tmp, err := ioutil.TempFile(dstPath, "nncp-xfer") if err != nil { ctx.LogE("nncp-xfer", nncp.SdsAdd(sds, nncp.SDS{"err": err}), "mktemp") diff --git a/src/cypherpunks.ru/nncp/ctx.go b/src/cypherpunks.ru/nncp/ctx.go index 21b3d6f..8e550fb 100644 --- a/src/cypherpunks.ru/nncp/ctx.go +++ b/src/cypherpunks.ru/nncp/ctx.go @@ -20,6 +20,7 @@ package nncp import ( "errors" + "io/ioutil" "os" "path/filepath" ) @@ -68,3 +69,37 @@ func (ctx *Ctx) ensureRxDir(nodeId *NodeId) error { fd.Close() return nil } + +func CtxFromCmdline(cfgPath, spoolPath, logPath string, quiet, debug bool) (*Ctx, error) { + env := os.Getenv(CfgPathEnv) + if env != "" { + cfgPath = env + } + cfgRaw, err := ioutil.ReadFile(cfgPath) + if err != nil { + return nil, err + } + ctx, err := CfgParse(cfgRaw) + if err != nil { + return nil, err + } + if spoolPath == "" { + env = os.Getenv(CfgSpoolEnv) + if env != "" { + ctx.Spool = env + } + } else { + ctx.Spool = spoolPath + } + if logPath == "" { + env = os.Getenv(CfgLogEnv) + if env != "" { + ctx.LogPath = env + } + } else { + ctx.LogPath = logPath + } + ctx.Quiet = quiet + ctx.Debug = debug + return ctx, nil +} diff --git a/src/cypherpunks.ru/nncp/humanizer.go b/src/cypherpunks.ru/nncp/humanizer.go index 7abfcab..4a79fcd 100644 --- a/src/cypherpunks.ru/nncp/humanizer.go +++ b/src/cypherpunks.ru/nncp/humanizer.go @@ -150,6 +150,27 @@ func (ctx *Ctx) Humanize(s string) string { if err, exists := sds["err"]; exists { msg += ": " + err } + case "nncp-bundle": + switch sds["xx"] { + case "rx": + msg = "Bundle transfer, received from" + case "tx": + msg = "Bundle transfer, sent to" + default: + return s + } + if nodeS != "" { + msg += " node " + nodeS + } + msg += " " + sds["pkt"] + if size != "" { + msg += fmt.Sprintf(" (%s)", size) + } + if err, exists := sds["err"]; exists { + msg += ": " + err + } + case "nncp-rm": + msg += "removing " + sds["file"] case "call-start": msg = fmt.Sprintf("Connected to %s", nodeS) case "call-finish": diff --git a/src/cypherpunks.ru/nncp/jobs.go b/src/cypherpunks.ru/nncp/jobs.go index 1f3afd1..4a59db0 100644 --- a/src/cypherpunks.ru/nncp/jobs.go +++ b/src/cypherpunks.ru/nncp/jobs.go @@ -64,7 +64,7 @@ func (ctx *Ctx) Jobs(nodeId *NodeId, xx TRxTx) chan Job { continue } var pktEnc PktEnc - if _, err = xdr.Unmarshal(fd, &pktEnc); err != nil || pktEnc.Magic != MagicNNCPEv1 { + if _, err = xdr.Unmarshal(fd, &pktEnc); err != nil || pktEnc.Magic != MagicNNCPEv2 { fd.Close() continue } diff --git a/src/cypherpunks.ru/nncp/pkt.go b/src/cypherpunks.ru/nncp/pkt.go index b35f83e..aa7368d 100644 --- a/src/cypherpunks.ru/nncp/pkt.go +++ b/src/cypherpunks.ru/nncp/pkt.go @@ -49,11 +49,13 @@ const ( DefaultNiceMail = 64 DefaultNiceFreq = 64 DefaultNiceFile = 196 + + NNCPBundlePrefix = "NNCP" ) var ( MagicNNCPPv1 [8]byte = [8]byte{'N', 'N', 'C', 'P', 'P', 0, 0, 1} - MagicNNCPEv1 [8]byte = [8]byte{'N', 'N', 'C', 'P', 'E', 0, 0, 1} + MagicNNCPEv2 [8]byte = [8]byte{'N', 'N', 'C', 'P', 'E', 0, 0, 2} BadMagic error = errors.New("Unknown magic number") BadPktType error = errors.New("Unknown packet type") @@ -71,17 +73,18 @@ type Pkt struct { type PktTbs struct { Magic [8]byte Nice uint8 - Recipient *NodeId Sender *NodeId + Recipient *NodeId ExchPub *[32]byte } type PktEnc struct { - Magic [8]byte - Nice uint8 - Sender *NodeId - ExchPub *[32]byte - Sign *[ed25519.SignatureSize]byte + Magic [8]byte + Nice uint8 + Sender *NodeId + Recipient *NodeId + ExchPub *[32]byte + Sign *[ed25519.SignatureSize]byte } func init() { @@ -102,11 +105,12 @@ func init() { panic(err) } pktEnc := PktEnc{ - Magic: MagicNNCPEv1, - Nice: 123, - Sender: dummyId, - ExchPub: new([32]byte), - Sign: new([ed25519.SignatureSize]byte), + Magic: MagicNNCPEv2, + Nice: 123, + Sender: dummyId, + Recipient: dummyId, + ExchPub: new([32]byte), + Sign: new([ed25519.SignatureSize]byte), } n, err = xdr.Marshal(&buf, pktEnc) if err != nil { @@ -157,10 +161,10 @@ func PktEncWrite(our *NodeOur, their *Node, pkt *Pkt, nice uint8, size, padSize return err } tbs := PktTbs{ - Magic: MagicNNCPEv1, + Magic: MagicNNCPEv2, Nice: nice, - Recipient: their.Id, Sender: our.Id, + Recipient: their.Id, ExchPub: pubEph, } var tbsBuf bytes.Buffer @@ -170,18 +174,19 @@ func PktEncWrite(our *NodeOur, their *Node, pkt *Pkt, nice uint8, size, padSize signature := new([ed25519.SignatureSize]byte) copy(signature[:], ed25519.Sign(our.SignPrv, tbsBuf.Bytes())) pktEnc := PktEnc{ - Magic: MagicNNCPEv1, - Nice: nice, - Sender: our.Id, - ExchPub: pubEph, - Sign: signature, + Magic: MagicNNCPEv2, + Nice: nice, + Sender: our.Id, + Recipient: their.Id, + ExchPub: pubEph, + Sign: signature, } if _, err = xdr.Marshal(out, &pktEnc); err != nil { return err } sharedKey := new([32]byte) curve25519.ScalarMult(sharedKey, prvEph, their.ExchPub) - kdf := hkdf.New(blake256, sharedKey[:], nil, MagicNNCPEv1[:]) + kdf := hkdf.New(blake256, sharedKey[:], nil, MagicNNCPEv2[:]) keyEnc := make([]byte, 32) if _, err = io.ReadFull(kdf, keyEnc); err != nil { @@ -257,10 +262,10 @@ func PktEncWrite(our *NodeOur, their *Node, pkt *Pkt, nice uint8, size, padSize func TbsVerify(our *NodeOur, their *Node, pktEnc *PktEnc) (bool, error) { tbs := PktTbs{ - Magic: MagicNNCPEv1, + Magic: MagicNNCPEv2, Nice: pktEnc.Nice, - Recipient: our.Id, Sender: their.Id, + Recipient: our.Id, ExchPub: pktEnc.ExchPub, } var tbsBuf bytes.Buffer @@ -276,13 +281,16 @@ func PktEncRead(our *NodeOur, nodes map[NodeId]*Node, data io.Reader, out io.Wri if err != nil { return nil, 0, err } - if pktEnc.Magic != MagicNNCPEv1 { + if pktEnc.Magic != MagicNNCPEv2 { return nil, 0, BadMagic } their, known := nodes[*pktEnc.Sender] if !known { return nil, 0, errors.New("Unknown sender") } + if *pktEnc.Recipient != *our.Id { + return nil, 0, errors.New("Invalid recipient") + } verified, err := TbsVerify(our, their, &pktEnc) if err != nil { return nil, 0, err @@ -292,7 +300,7 @@ func PktEncRead(our *NodeOur, nodes map[NodeId]*Node, data io.Reader, out io.Wri } sharedKey := new([32]byte) curve25519.ScalarMult(sharedKey, our.ExchPrv, pktEnc.ExchPub) - kdf := hkdf.New(blake256, sharedKey[:], nil, MagicNNCPEv1[:]) + kdf := hkdf.New(blake256, sharedKey[:], nil, MagicNNCPEv2[:]) keyEnc := make([]byte, 32) if _, err = io.ReadFull(kdf, keyEnc); err != nil { diff --git a/src/cypherpunks.ru/nncp/sp.go b/src/cypherpunks.ru/nncp/sp.go index be84510..6daab72 100644 --- a/src/cypherpunks.ru/nncp/sp.go +++ b/src/cypherpunks.ru/nncp/sp.go @@ -55,6 +55,8 @@ var ( noise.CipherChaChaPoly, noise.HashBLAKE2b, ) + + spWorkersGroup sync.WaitGroup ) type SPType uint8 @@ -838,6 +840,8 @@ func (state *SPState) ProcessSP(payload []byte) ([][]byte, error) { continue } state.RUnlock() + spWorkersGroup.Wait() + spWorkersGroup.Add(1) go func() { if err := fd.Sync(); err != nil { state.ctx.LogE("sp-file", SdsAdd(sdsp, SDS{"err": err}), "sync") @@ -859,6 +863,7 @@ func (state *SPState) ProcessSP(payload []byte) ([][]byte, error) { state.Lock() delete(state.infosTheir, *file.Hash) state.Unlock() + spWorkersGroup.Done() go func() { state.payloads <- MarshalSP(SPTypeDone, SPDone{file.Hash}) }() diff --git a/src/cypherpunks.ru/nncp/tmp.go b/src/cypherpunks.ru/nncp/tmp.go index 2ce02cb..47a20d0 100644 --- a/src/cypherpunks.ru/nncp/tmp.go +++ b/src/cypherpunks.ru/nncp/tmp.go @@ -45,7 +45,7 @@ func (ctx *Ctx) NewTmpFile() (*os.File, error) { type TmpFileWHash struct { W *bufio.Writer Fd *os.File - hsh hash.Hash + Hsh hash.Hash ctx *Ctx } @@ -61,7 +61,7 @@ func (ctx *Ctx) NewTmpFileWHash() (*TmpFileWHash, error) { return &TmpFileWHash{ W: bufio.NewWriter(io.MultiWriter(hsh, tmp)), Fd: tmp, - hsh: hsh, + Hsh: hsh, ctx: ctx, }, nil } @@ -81,9 +81,12 @@ func (tmp *TmpFileWHash) Commit(dir string) error { tmp.Fd.Close() return err } - tmp.Fd.Sync() + if err = tmp.Fd.Sync(); err != nil { + tmp.Fd.Close() + return err + } tmp.Fd.Close() - checksum := ToBase32(tmp.hsh.Sum(nil)) + checksum := ToBase32(tmp.Hsh.Sum(nil)) tmp.ctx.LogD("tmp", SDS{"src": tmp.Fd.Name(), "dst": checksum}, "commit") return os.Rename(tmp.Fd.Name(), filepath.Join(dir, checksum)) } diff --git a/src/cypherpunks.ru/nncp/toss.go b/src/cypherpunks.ru/nncp/toss.go index 962b240..7777ecd 100644 --- a/src/cypherpunks.ru/nncp/toss.go +++ b/src/cypherpunks.ru/nncp/toss.go @@ -38,6 +38,10 @@ import ( "golang.org/x/crypto/blake2b" ) +const ( + SeenSuffix = ".seen" +) + func newNotification(fromTo *FromToYAML, subject string) io.Reader { return strings.NewReader(fmt.Sprintf( "From: %s\nTo: %s\nSubject: %s\n", @@ -47,7 +51,7 @@ func newNotification(fromTo *FromToYAML, subject string) io.Reader { )) } -func (ctx *Ctx) Toss(nodeId *NodeId, nice uint8, dryRun bool) bool { +func (ctx *Ctx) Toss(nodeId *NodeId, nice uint8, dryRun, doSeen bool) bool { isBad := false for job := range ctx.Jobs(nodeId, TRx) { pktName := filepath.Base(job.Fd.Name()) @@ -113,7 +117,7 @@ func (ctx *Ctx) Toss(nodeId *NodeId, nice uint8, dryRun bool) bool { strings.Split(recipients, " ")..., )..., ) - cmd.Env = append(cmd.Env, "NNCP_SENDER=" + sender.Id.String()) + cmd.Env = append(cmd.Env, "NNCP_SENDER="+sender.Id.String()) cmd.Stdin = decompressor if err = cmd.Run(); err != nil { ctx.LogE("rx", SdsAdd(sds, SDS{"err": err}), "sendmail") @@ -123,6 +127,11 @@ func (ctx *Ctx) Toss(nodeId *NodeId, nice uint8, dryRun bool) bool { } ctx.LogI("rx", sds, "") if !dryRun { + if doSeen { + if fd, err := os.Create(job.Fd.Name() + SeenSuffix); err == nil { + fd.Close() + } + } if err = os.Remove(job.Fd.Name()); err != nil { ctx.LogE("rx", SdsAdd(sds, SDS{"err": err}), "remove") isBad = true @@ -189,6 +198,11 @@ func (ctx *Ctx) Toss(nodeId *NodeId, nice uint8, dryRun bool) bool { } ctx.LogI("rx", sds, "") if !dryRun { + if doSeen { + if fd, err := os.Create(job.Fd.Name() + SeenSuffix); err == nil { + fd.Close() + } + } if err = os.Remove(job.Fd.Name()); err != nil { ctx.LogE("rx", SdsAdd(sds, SDS{"err": err}), "remove") isBad = true @@ -258,6 +272,11 @@ func (ctx *Ctx) Toss(nodeId *NodeId, nice uint8, dryRun bool) bool { } ctx.LogI("rx", sds, "") if !dryRun { + if doSeen { + if fd, err := os.Create(job.Fd.Name() + SeenSuffix); err == nil { + fd.Close() + } + } if err = os.Remove(job.Fd.Name()); err != nil { ctx.LogE("rx", SdsAdd(sds, SDS{"err": err}), "remove") isBad = true @@ -297,6 +316,11 @@ func (ctx *Ctx) Toss(nodeId *NodeId, nice uint8, dryRun bool) bool { } ctx.LogI("rx", sds, "") if !dryRun { + if doSeen { + if fd, err := os.Create(job.Fd.Name() + SeenSuffix); err == nil { + fd.Close() + } + } if err = os.Remove(job.Fd.Name()); err != nil { ctx.LogE("rx", SdsAdd(sds, SDS{"err": err}), "remove") isBad = true diff --git a/src/cypherpunks.ru/nncp/toss_test.go b/src/cypherpunks.ru/nncp/toss_test.go index c40c9d8..47a4ba4 100644 --- a/src/cypherpunks.ru/nncp/toss_test.go +++ b/src/cypherpunks.ru/nncp/toss_test.go @@ -105,12 +105,12 @@ func TestTossEmail(t *testing.T) { if len(dirFiles(rxPath)) == 0 { continue } - ctx.Toss(ctx.Self.Id, DefaultNiceMail-1, false) + ctx.Toss(ctx.Self.Id, DefaultNiceMail-1, 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) + ctx.Toss(ctx.Self.Id, DefaultNiceMail, false, false) if len(dirFiles(rxPath)) == 0 { return false } @@ -118,7 +118,7 @@ func TestTossEmail(t *testing.T) { "/bin/sh", "-c", fmt.Sprintf("cat >> %s", filepath.Join(spool, "mbox")), } - ctx.Toss(ctx.Self.Id, DefaultNiceMail, false) + ctx.Toss(ctx.Self.Id, DefaultNiceMail, false, false) if len(dirFiles(rxPath)) != 0 { return false } @@ -190,12 +190,12 @@ func TestTossFile(t *testing.T) { } rxPath := filepath.Join(spool, ctx.Self.Id.String(), string(TRx)) os.Rename(filepath.Join(spool, ctx.Self.Id.String(), string(TTx)), rxPath) - ctx.Toss(ctx.Self.Id, DefaultNiceFile, false) + ctx.Toss(ctx.Self.Id, DefaultNiceFile, false, false) if len(dirFiles(rxPath)) == 0 { return false } ctx.Neigh[*nodeOur.Id].Incoming = &incomingPath - ctx.Toss(ctx.Self.Id, DefaultNiceFile, false) + ctx.Toss(ctx.Self.Id, DefaultNiceFile, false, false) if len(dirFiles(rxPath)) != 0 { return false } @@ -262,7 +262,7 @@ func TestTossFileSameName(t *testing.T) { rxPath := filepath.Join(spool, ctx.Self.Id.String(), string(TRx)) os.Rename(filepath.Join(spool, ctx.Self.Id.String(), string(TTx)), rxPath) ctx.Neigh[*nodeOur.Id].Incoming = &incomingPath - ctx.Toss(ctx.Self.Id, DefaultNiceFile, false) + ctx.Toss(ctx.Self.Id, DefaultNiceFile, false, false) expected := make(map[string]struct{}) expected["samefile"] = struct{}{} for i := 0; i < files-1; i++ { @@ -330,12 +330,12 @@ func TestTossFreq(t *testing.T) { txPath := filepath.Join(spool, ctx.Self.Id.String(), string(TTx)) os.Rename(txPath, rxPath) os.MkdirAll(txPath, os.FileMode(0700)) - ctx.Toss(ctx.Self.Id, DefaultNiceFreq, false) + ctx.Toss(ctx.Self.Id, DefaultNiceFreq, false, false) if len(dirFiles(txPath)) != 0 || len(dirFiles(rxPath)) == 0 { return false } ctx.Neigh[*nodeOur.Id].Freq = &spool - ctx.Toss(ctx.Self.Id, DefaultNiceFreq, false) + ctx.Toss(ctx.Self.Id, DefaultNiceFreq, false, false) if len(dirFiles(txPath)) != 0 || len(dirFiles(rxPath)) == 0 { return false } @@ -348,7 +348,7 @@ func TestTossFreq(t *testing.T) { panic(err) } } - ctx.Toss(ctx.Self.Id, DefaultNiceFreq, false) + ctx.Toss(ctx.Self.Id, DefaultNiceFreq, false, false) if len(dirFiles(txPath)) == 0 || len(dirFiles(rxPath)) != 0 { return false } @@ -441,7 +441,7 @@ func TestTossTrns(t *testing.T) { panic(err) } } - ctx.Toss(ctx.Self.Id, 123, false) + ctx.Toss(ctx.Self.Id, 123, false, false) if len(dirFiles(rxPath)) != 0 { return false } diff --git a/src/github.com/dustin/go-humanize b/src/github.com/dustin/go-humanize index 79e699c..bb3d318 160000 --- a/src/github.com/dustin/go-humanize +++ b/src/github.com/dustin/go-humanize @@ -1 +1 @@ -Subproject commit 79e699ccd02f240a1f1fbbdcee7e64c1c12e41aa +Subproject commit bb3d318650d48840a39aa21a027c6630e198e626 diff --git a/src/golang.org/x/crypto b/src/golang.org/x/crypto index 9419663..9f005a0 160000 --- a/src/golang.org/x/crypto +++ b/src/golang.org/x/crypto @@ -1 +1 @@ -Subproject commit 9419663f5a44be8b34ca85f08abc5fe1be11f8a3 +Subproject commit 9f005a07e0d31d45e6656d241bb5c0f2efd4bc94 diff --git a/src/golang.org/x/net b/src/golang.org/x/net index a04bdac..9dfe398 160000 --- a/src/golang.org/x/net +++ b/src/golang.org/x/net @@ -1 +1 @@ -Subproject commit a04bdaca5b32abe1c069418fb7088ae607de5bd0 +Subproject commit 9dfe39835686865bff950a07b394c12a98ddc811 diff --git a/src/golang.org/x/sys b/src/golang.org/x/sys index ebfc5b4..0dd5e19 160000 --- a/src/golang.org/x/sys +++ b/src/golang.org/x/sys @@ -1 +1 @@ -Subproject commit ebfc5b4631820b793c9010c87fd8fef0f39eb082 +Subproject commit 0dd5e194bbf5eb84a39666eb4c98a4d007e4203a diff --git a/src/gopkg.in/yaml.v2 b/src/gopkg.in/yaml.v2 index cd8b52f..287cf08 160000 --- a/src/gopkg.in/yaml.v2 +++ b/src/gopkg.in/yaml.v2 @@ -1 +1 @@ -Subproject commit cd8b52f8269e0feb286dfeef29f8fe4d5b397e0b +Subproject commit 287cf08546ab5e7e37d55a84f7ed3fd1db036de5