]> Cypherpunks.ru repositories - nncp.git/blob - src/cmd/nncp-bundle/main.go
Remove huge usage headers, -warranty exists anyway
[nncp.git] / src / cmd / nncp-bundle / main.go
1 /*
2 NNCP -- Node to Node copy, utilities for store-and-forward data exchange
3 Copyright (C) 2016-2023 Sergey Matveev <stargrave@stargrave.org>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, version 3 of the License.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 // Create/digest stream of NNCP encrypted packets.
19 package main
20
21 import (
22         "archive/tar"
23         "bufio"
24         "bytes"
25         "errors"
26         "flag"
27         "fmt"
28         "io"
29         "io/fs"
30         "log"
31         "os"
32         "path/filepath"
33         "strings"
34
35         xdr "github.com/davecgh/go-xdr/xdr2"
36         "github.com/dustin/go-humanize"
37         "go.cypherpunks.ru/nncp/v8"
38 )
39
40 func usage() {
41         fmt.Fprint(os.Stderr, "nncp-bundle -- Create/digest stream of NNCP encrypted packets\n\n")
42         fmt.Fprintf(os.Stderr, "Usage: %s [options] -tx [-delete] NODE [NODE ...] > ...\n", os.Args[0])
43         fmt.Fprintf(os.Stderr, "       %s [options] -rx -delete [-dryrun] [NODE ...] < ...\n", os.Args[0])
44         fmt.Fprintf(os.Stderr, "       %s [options] -rx [-check] [-dryrun] [NODE ...] < ...\n", os.Args[0])
45         fmt.Fprintln(os.Stderr, "Options:")
46         flag.PrintDefaults()
47 }
48
49 func main() {
50         var (
51                 cfgPath   = flag.String("cfg", nncp.DefaultCfgPath, "Path to configuration file")
52                 niceRaw   = flag.String("nice", nncp.NicenessFmt(255), "Minimal required niceness")
53                 doRx      = flag.Bool("rx", false, "Receive packets")
54                 doTx      = flag.Bool("tx", false, "Transfer packets")
55                 doDelete  = flag.Bool("delete", false, "Delete transferred packets")
56                 doCheck   = flag.Bool("check", false, "Check integrity while receiving")
57                 dryRun    = flag.Bool("dryrun", false, "Do no writes")
58                 spoolPath = flag.String("spool", "", "Override path to spool")
59                 logPath   = flag.String("log", "", "Override path to logfile")
60                 quiet     = flag.Bool("quiet", false, "Print only errors")
61                 showPrgrs = flag.Bool("progress", false, "Force progress showing")
62                 omitPrgrs = flag.Bool("noprogress", false, "Omit progress showing")
63                 debug     = flag.Bool("debug", false, "Print debug messages")
64                 version   = flag.Bool("version", false, "Print version information")
65                 warranty  = flag.Bool("warranty", false, "Print warranty information")
66         )
67         log.SetFlags(log.Lshortfile)
68         flag.Usage = usage
69         flag.Parse()
70         if *warranty {
71                 fmt.Println(nncp.Warranty)
72                 return
73         }
74         if *version {
75                 fmt.Println(nncp.VersionGet())
76                 return
77         }
78         nice, err := nncp.NicenessParse(*niceRaw)
79         if err != nil {
80                 log.Fatalln(err)
81         }
82         if *doRx && *doTx {
83                 log.Fatalln("-rx and -tx can not be set simultaneously")
84         }
85         if !*doRx && !*doTx {
86                 log.Fatalln("At least one of -rx and -tx must be specified")
87         }
88
89         ctx, err := nncp.CtxFromCmdline(
90                 *cfgPath,
91                 *spoolPath,
92                 *logPath,
93                 *quiet,
94                 *showPrgrs,
95                 *omitPrgrs,
96                 *debug,
97         )
98         if err != nil {
99                 log.Fatalln("Error during initialization:", err)
100         }
101
102         nodeIds := make(map[nncp.NodeId]struct{}, flag.NArg())
103         for i := 0; i < flag.NArg(); i++ {
104                 node, err := ctx.FindNode(flag.Arg(i))
105                 if err != nil {
106                         log.Fatalln("Invalid node specified:", err)
107                 }
108                 nodeIds[*node.Id] = struct{}{}
109         }
110
111         ctx.Umask()
112
113         if *doTx {
114                 var pktName string
115                 bufStdout := bufio.NewWriter(os.Stdout)
116                 tarWr := tar.NewWriter(bufStdout)
117                 for nodeId := range nodeIds {
118                         for job := range ctx.Jobs(&nodeId, nncp.TTx) {
119                                 pktName = filepath.Base(job.Path)
120                                 les := nncp.LEs{
121                                         {K: "XX", V: string(nncp.TTx)},
122                                         {K: "Node", V: nodeId.String()},
123                                         {K: "Pkt", V: pktName},
124                                 }
125                                 if job.PktEnc.Nice > nice {
126                                         ctx.LogD("bundle-tx-too-nice", les, func(les nncp.LEs) string {
127                                                 return fmt.Sprintf(
128                                                         "Bundle transfer %s/tx/%s: too nice %s",
129                                                         ctx.NodeName(&nodeId),
130                                                         pktName,
131                                                         nncp.NicenessFmt(job.PktEnc.Nice),
132                                                 )
133                                         })
134                                         continue
135                                 }
136                                 fd, err := os.Open(job.Path)
137                                 if err != nil {
138                                         log.Fatalln("Error during opening:", err)
139                                 }
140                                 if err = tarWr.WriteHeader(&tar.Header{
141                                         Format:   tar.FormatUSTAR,
142                                         Name:     nncp.NNCPBundlePrefix,
143                                         Mode:     0700,
144                                         Typeflag: tar.TypeDir,
145                                 }); err != nil {
146                                         log.Fatalln("Error writing tar header:", err)
147                                 }
148                                 if err = tarWr.WriteHeader(&tar.Header{
149                                         Format: tar.FormatPAX,
150                                         Name: strings.Join([]string{
151                                                 nncp.NNCPBundlePrefix,
152                                                 nodeId.String(),
153                                                 ctx.SelfId.String(),
154                                                 pktName,
155                                         }, "/"),
156                                         Mode:     0400,
157                                         Size:     job.Size,
158                                         Typeflag: tar.TypeReg,
159                                 }); err != nil {
160                                         log.Fatalln("Error writing tar header:", err)
161                                 }
162                                 if _, err = nncp.CopyProgressed(
163                                         tarWr, bufio.NewReaderSize(fd, nncp.MTHBlockSize), "Tx",
164                                         append(les, nncp.LEs{
165                                                 {K: "Pkt", V: nncp.Base32Codec.EncodeToString(job.HshValue[:])},
166                                                 {K: "FullSize", V: job.Size},
167                                         }...),
168                                         ctx.ShowPrgrs,
169                                 ); err != nil {
170                                         log.Fatalln("Error during copying to tar:", err)
171                                 }
172                                 if err = fd.Close(); err != nil {
173                                         log.Fatalln("Error during closing:", err)
174                                 }
175                                 if err = tarWr.Flush(); err != nil {
176                                         log.Fatalln("Error during tar flushing:", err)
177                                 }
178                                 if err = bufStdout.Flush(); err != nil {
179                                         log.Fatalln("Error during stdout flushing:", err)
180                                 }
181                                 if *doDelete {
182                                         if err = os.Remove(job.Path); err != nil {
183                                                 log.Fatalln("Error during deletion:", err)
184                                         } else if ctx.HdrUsage {
185                                                 os.Remove(nncp.JobPath2Hdr(job.Path))
186                                         }
187                                 }
188                                 ctx.LogI(
189                                         "bundle-tx",
190                                         append(les, nncp.LE{K: "Size", V: job.Size}),
191                                         func(les nncp.LEs) string {
192                                                 return fmt.Sprintf(
193                                                         "Bundle transfer, sent to node %s %s (%s)",
194                                                         ctx.NodeName(&nodeId),
195                                                         pktName,
196                                                         humanize.IBytes(uint64(job.Size)),
197                                                 )
198                                         },
199                                 )
200                         }
201                 }
202                 if err = tarWr.Close(); err != nil {
203                         log.Fatalln("Error during tar closing:", err)
204                 }
205         } else {
206                 bufStdin := bufio.NewReaderSize(os.Stdin, nncp.MTHBlockSize*2)
207                 pktEncBuf := make([]byte, nncp.PktEncOverhead)
208                 var pktEnc *nncp.PktEnc
209                 for {
210                         peeked, err := bufStdin.Peek(nncp.MTHBlockSize)
211                         if err != nil && err != io.EOF {
212                                 log.Fatalln("Error during reading:", err)
213                         }
214                         prefixIdx := bytes.Index(peeked, []byte(nncp.NNCPBundlePrefix))
215                         if prefixIdx == -1 {
216                                 if err == io.EOF {
217                                         break
218                                 }
219                                 bufStdin.Discard(bufStdin.Buffered() - (len(nncp.NNCPBundlePrefix) - 1))
220                                 continue
221                         }
222                         if _, err = bufStdin.Discard(prefixIdx); err != nil {
223                                 panic(err)
224                         }
225                         tarR := tar.NewReader(bufStdin)
226                         entry, err := tarR.Next()
227                         if err != nil {
228                                 if err != io.EOF {
229                                         ctx.LogD(
230                                                 "bundle-rx-read-tar",
231                                                 nncp.LEs{{K: "XX", V: string(nncp.TRx)}, {K: "Err", V: err}},
232                                                 func(les nncp.LEs) string {
233                                                         return "Bundle transfer rx: reading tar"
234                                                 },
235                                         )
236                                 }
237                                 continue
238                         }
239                         if entry.Typeflag != tar.TypeDir {
240                                 ctx.LogD(
241                                         "bundle-rx-read-tar",
242                                         nncp.LEs{
243                                                 {K: "XX", V: string(nncp.TRx)},
244                                                 {K: "Err", V: errors.New("expected NNCP/")},
245                                         },
246                                         func(les nncp.LEs) string {
247                                                 return "Bundle transfer rx: reading tar"
248                                         },
249                                 )
250                                 continue
251                         }
252                         entry, err = tarR.Next()
253                         if err != nil {
254                                 if err != io.EOF {
255                                         ctx.LogD(
256                                                 "bundle-rx-read-tar",
257                                                 nncp.LEs{{K: "XX", V: string(nncp.TRx)}, {K: "Err", V: err}},
258                                                 func(les nncp.LEs) string {
259                                                         return "Bundle transfer rx: reading tar"
260                                                 },
261                                         )
262                                 }
263                                 continue
264                         }
265                         les := nncp.LEs{{K: "XX", V: string(nncp.TRx)}, {K: "Pkt", V: entry.Name}}
266                         logMsg := func(les nncp.LEs) string {
267                                 return "Bundle transfer rx/" + entry.Name
268                         }
269                         if entry.Size < nncp.PktEncOverhead {
270                                 ctx.LogD("bundle-rx-too-small", les, func(les nncp.LEs) string {
271                                         return logMsg(les) + ": too small packet"
272                                 })
273                                 continue
274                         }
275                         if !ctx.IsEnoughSpace(entry.Size) {
276                                 ctx.LogE("bundle-rx", les, errors.New("not enough spool space"), logMsg)
277                                 continue
278                         }
279                         pktName := filepath.Base(entry.Name)
280                         if _, err = nncp.Base32Codec.DecodeString(pktName); err != nil {
281                                 ctx.LogD(
282                                         "bundle-rx",
283                                         append(les, nncp.LE{K: "Err", V: "bad packet name"}),
284                                         logMsg,
285                                 )
286                                 continue
287                         }
288                         if _, err = io.ReadFull(tarR, pktEncBuf); err != nil {
289                                 ctx.LogD(
290                                         "bundle-rx",
291                                         append(les, nncp.LE{K: "Err", V: err}),
292                                         logMsg,
293                                 )
294                                 continue
295                         }
296                         if _, err = xdr.Unmarshal(bytes.NewReader(pktEncBuf), &pktEnc); err != nil {
297                                 ctx.LogD(
298                                         "bundle-rx",
299                                         append(les, nncp.LE{K: "Err", V: "Bad packet structure"}),
300                                         logMsg,
301                                 )
302                                 continue
303                         }
304                         switch pktEnc.Magic {
305                         case nncp.MagicNNCPEv1.B:
306                                 err = nncp.MagicNNCPEv1.TooOld()
307                         case nncp.MagicNNCPEv2.B:
308                                 err = nncp.MagicNNCPEv2.TooOld()
309                         case nncp.MagicNNCPEv3.B:
310                                 err = nncp.MagicNNCPEv3.TooOld()
311                         case nncp.MagicNNCPEv4.B:
312                                 err = nncp.MagicNNCPEv4.TooOld()
313                         case nncp.MagicNNCPEv5.B:
314                                 err = nncp.MagicNNCPEv5.TooOld()
315                         case nncp.MagicNNCPEv6.B:
316                         default:
317                                 err = errors.New("Bad packet magic number")
318                         }
319                         if err != nil {
320                                 ctx.LogD(
321                                         "bundle-rx",
322                                         append(les, nncp.LE{K: "Err", V: err.Error()}),
323                                         logMsg,
324                                 )
325                                 continue
326                         }
327                         if pktEnc.Nice > nice {
328                                 ctx.LogD("bundle-rx-too-nice", les, func(les nncp.LEs) string {
329                                         return logMsg(les) + ": too nice"
330                                 })
331                                 continue
332                         }
333                         if *pktEnc.Sender == *ctx.SelfId && *doDelete {
334                                 if len(nodeIds) > 0 {
335                                         if _, exists := nodeIds[*pktEnc.Recipient]; !exists {
336                                                 ctx.LogD("bundle-tx-skip", les, func(les nncp.LEs) string {
337                                                         return logMsg(les) + ": recipient is not requested"
338                                                 })
339                                                 continue
340                                         }
341                                 }
342                                 nodeId32 := nncp.Base32Codec.EncodeToString(pktEnc.Recipient[:])
343                                 les := nncp.LEs{
344                                         {K: "XX", V: string(nncp.TTx)},
345                                         {K: "Node", V: nodeId32},
346                                         {K: "Pkt", V: pktName},
347                                 }
348                                 logMsg = func(les nncp.LEs) string {
349                                         return fmt.Sprintf("Bundle transfer %s/tx/%s", nodeId32, pktName)
350                                 }
351                                 dstPath := filepath.Join(ctx.Spool, nodeId32, string(nncp.TTx), pktName)
352                                 if _, err = os.Stat(dstPath); err != nil {
353                                         ctx.LogD("bundle-tx-missing", les, func(les nncp.LEs) string {
354                                                 return logMsg(les) + ": packet is already missing"
355                                         })
356                                         continue
357                                 }
358                                 hsh := nncp.MTHNew(entry.Size, 0)
359                                 if _, err = hsh.Write(pktEncBuf); err != nil {
360                                         log.Fatalln("Error during writing:", err)
361                                 }
362                                 if _, err = nncp.CopyProgressed(
363                                         hsh, tarR, "Rx",
364                                         append(les, nncp.LE{K: "FullSize", V: entry.Size}),
365                                         ctx.ShowPrgrs,
366                                 ); err != nil {
367                                         log.Fatalln("Error during copying:", err)
368                                 }
369                                 if nncp.Base32Codec.EncodeToString(hsh.Sum(nil)) == pktName {
370                                         ctx.LogI("bundle-tx-removed", les, func(les nncp.LEs) string {
371                                                 return logMsg(les) + ": removed"
372                                         })
373                                         if !*dryRun {
374                                                 os.Remove(dstPath)
375                                                 if ctx.HdrUsage {
376                                                         os.Remove(nncp.JobPath2Hdr(dstPath))
377                                                 }
378                                         }
379                                 } else {
380                                         ctx.LogE("bundle-tx", les, errors.New("bad checksum"), logMsg)
381                                 }
382                                 continue
383                         }
384                         if *pktEnc.Recipient != *ctx.SelfId {
385                                 ctx.LogD("nncp-bundle", les, func(les nncp.LEs) string {
386                                         return logMsg(les) + ": unknown recipient"
387                                 })
388                                 continue
389                         }
390                         if len(nodeIds) > 0 {
391                                 if _, exists := nodeIds[*pktEnc.Sender]; !exists {
392                                         ctx.LogD("bundle-rx-skip", les, func(les nncp.LEs) string {
393                                                 return logMsg(les) + ": sender is not requested"
394                                         })
395                                         continue
396                                 }
397                         }
398                         sender := nncp.Base32Codec.EncodeToString(pktEnc.Sender[:])
399                         les = nncp.LEs{
400                                 {K: "XX", V: string(nncp.TRx)},
401                                 {K: "Node", V: sender},
402                                 {K: "Pkt", V: pktName},
403                                 {K: "FullSize", V: entry.Size},
404                         }
405                         logMsg = func(les nncp.LEs) string {
406                                 return fmt.Sprintf("Bundle transfer %s/rx/%s", sender, pktName)
407                         }
408                         dstDirPath := filepath.Join(ctx.Spool, sender, string(nncp.TRx))
409                         dstPath := filepath.Join(dstDirPath, pktName)
410                         if _, err = os.Stat(dstPath); err == nil || !errors.Is(err, fs.ErrNotExist) {
411                                 ctx.LogD("bundle-rx-exists", les, func(les nncp.LEs) string {
412                                         return logMsg(les) + ": packet already exists"
413                                 })
414                                 continue
415                         }
416                         if _, err = os.Stat(filepath.Join(
417                                 dstDirPath, nncp.SeenDir, pktName,
418                         )); err == nil || !errors.Is(err, fs.ErrNotExist) {
419                                 ctx.LogD("bundle-rx-seen", les, func(les nncp.LEs) string {
420                                         return logMsg(les) + ": packet already seen"
421                                 })
422                                 continue
423                         }
424                         if *doCheck {
425                                 if *dryRun {
426                                         hsh := nncp.MTHNew(entry.Size, 0)
427                                         if _, err = hsh.Write(pktEncBuf); err != nil {
428                                                 log.Fatalln("Error during writing:", err)
429                                         }
430                                         if _, err = nncp.CopyProgressed(hsh, tarR, "check", les, ctx.ShowPrgrs); err != nil {
431                                                 log.Fatalln("Error during copying:", err)
432                                         }
433                                         if nncp.Base32Codec.EncodeToString(hsh.Sum(nil)) != pktName {
434                                                 ctx.LogE("bundle-rx", les, errors.New("bad checksum"), logMsg)
435                                                 continue
436                                         }
437                                 } else {
438                                         tmp, err := ctx.NewTmpFileWHash()
439                                         if err != nil {
440                                                 log.Fatalln("Error during temporary file creation:", err)
441                                         }
442                                         if _, err = tmp.W.Write(pktEncBuf); err != nil {
443                                                 log.Fatalln("Error during writing:", err)
444                                         }
445                                         if _, err = nncp.CopyProgressed(tmp.W, tarR, "check", les, ctx.ShowPrgrs); err != nil {
446                                                 log.Fatalln("Error during copying:", err)
447                                         }
448                                         if err = tmp.W.Flush(); err != nil {
449                                                 log.Fatalln("Error during flusing:", err)
450                                         }
451                                         if nncp.Base32Codec.EncodeToString(tmp.Hsh.Sum(nil)) == pktName {
452                                                 if err = tmp.Commit(dstDirPath); err != nil {
453                                                         log.Fatalln("Error during commiting:", err)
454                                                 }
455                                         } else {
456                                                 ctx.LogE("bundle-rx", les, errors.New("bad checksum"), logMsg)
457                                                 tmp.Cancel()
458                                                 continue
459                                         }
460                                 }
461                         } else {
462                                 if *dryRun {
463                                         if _, err = nncp.CopyProgressed(io.Discard, tarR, "Rx", les, ctx.ShowPrgrs); err != nil {
464                                                 log.Fatalln("Error during copying:", err)
465                                         }
466                                 } else {
467                                         tmp, err := ctx.NewTmpFile()
468                                         if err != nil {
469                                                 log.Fatalln("Error during temporary file creation:", err)
470                                         }
471                                         bufTmp := bufio.NewWriterSize(tmp, nncp.MTHBlockSize)
472                                         if _, err = bufTmp.Write(pktEncBuf); err != nil {
473                                                 log.Fatalln("Error during writing:", err)
474                                         }
475                                         if _, err = nncp.CopyProgressed(bufTmp, tarR, "Rx", les, ctx.ShowPrgrs); err != nil {
476                                                 log.Fatalln("Error during copying:", err)
477                                         }
478                                         if err = bufTmp.Flush(); err != nil {
479                                                 log.Fatalln("Error during flushing:", err)
480                                         }
481                                         if !nncp.NoSync {
482                                                 if err = tmp.Sync(); err != nil {
483                                                         log.Fatalln("Error during syncing:", err)
484                                                 }
485                                         }
486                                         if err = tmp.Close(); err != nil {
487                                                 log.Fatalln("Error during closing:", err)
488                                         }
489                                         if err = os.MkdirAll(dstDirPath, os.FileMode(0777)); err != nil {
490                                                 log.Fatalln("Error during mkdir:", err)
491                                         }
492                                         if err = os.Rename(tmp.Name(), dstPath); err != nil {
493                                                 log.Fatalln("Error during renaming:", err)
494                                         }
495                                         if err = nncp.DirSync(dstDirPath); err != nil {
496                                                 log.Fatalln("Error during syncing:", err)
497                                         }
498                                         if ctx.HdrUsage {
499                                                 ctx.HdrWrite(pktEncBuf, dstPath)
500                                         }
501                                 }
502                         }
503                         for _, le := range les {
504                                 if le.K == "FullSize" {
505                                         les = append(les, nncp.LE{K: "Size", V: le.V})
506                                         break
507                                 }
508                         }
509                         ctx.LogI("bundle-rx", les, func(les nncp.LEs) string {
510                                 return fmt.Sprintf(
511                                         "Bundle transfer, received from %s %s (%s)",
512                                         sender, pktName, humanize.IBytes(uint64(entry.Size)),
513                                 )
514                         })
515                 }
516         }
517 }