]> Cypherpunks.ru repositories - nncp.git/blob - src/cypherpunks.ru/nncp/cmd/nncp-bundle/main.go
Forbid any later GNU GPL versions autousage
[nncp.git] / src / cypherpunks.ru / nncp / cmd / nncp-bundle / main.go
1 /*
2 NNCP -- Node to Node copy, utilities for store-and-forward data exchange
3 Copyright (C) 2016-2019 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         "flag"
26         "fmt"
27         "io"
28         "io/ioutil"
29         "log"
30         "os"
31         "path/filepath"
32         "strconv"
33         "strings"
34
35         "cypherpunks.ru/nncp"
36         "github.com/davecgh/go-xdr/xdr2"
37         "golang.org/x/crypto/blake2b"
38 )
39
40 const (
41         CopyBufSize = 1 << 17
42 )
43
44 func usage() {
45         fmt.Fprintf(os.Stderr, nncp.UsageHeader())
46         fmt.Fprintf(os.Stderr, "nncp-bundle -- Create/digest stream of NNCP encrypted packets\n\n")
47         fmt.Fprintf(os.Stderr, "Usage: %s [options] -tx [-delete] NODE [NODE ...] > ...\n", os.Args[0])
48         fmt.Fprintf(os.Stderr, "       %s [options] -rx -delete [-dryrun] [NODE ...] < ...\n", os.Args[0])
49         fmt.Fprintf(os.Stderr, "       %s [options] -rx [-check] [-dryrun] [NODE ...] < ...\n", os.Args[0])
50         fmt.Fprintln(os.Stderr, "Options:")
51         flag.PrintDefaults()
52 }
53
54 func main() {
55         var (
56                 cfgPath   = flag.String("cfg", nncp.DefaultCfgPath, "Path to configuration file")
57                 niceRaw   = flag.String("nice", nncp.NicenessFmt(255), "Minimal required niceness")
58                 doRx      = flag.Bool("rx", false, "Receive packets")
59                 doTx      = flag.Bool("tx", false, "Transfer packets")
60                 doDelete  = flag.Bool("delete", false, "Delete transferred packets")
61                 doCheck   = flag.Bool("check", false, "Check integrity while receiving")
62                 dryRun    = flag.Bool("dryrun", false, "Do no writes")
63                 spoolPath = flag.String("spool", "", "Override path to spool")
64                 logPath   = flag.String("log", "", "Override path to logfile")
65                 quiet     = flag.Bool("quiet", false, "Print only errors")
66                 debug     = flag.Bool("debug", false, "Print debug messages")
67                 version   = flag.Bool("version", false, "Print version information")
68                 warranty  = flag.Bool("warranty", false, "Print warranty information")
69         )
70         flag.Usage = usage
71         flag.Parse()
72         if *warranty {
73                 fmt.Println(nncp.Warranty)
74                 return
75         }
76         if *version {
77                 fmt.Println(nncp.VersionGet())
78                 return
79         }
80         nice, err := nncp.NicenessParse(*niceRaw)
81         if err != nil {
82                 log.Fatalln(err)
83         }
84         if *doRx && *doTx {
85                 log.Fatalln("-rx and -tx can not be set simultaneously")
86         }
87         if !*doRx && !*doTx {
88                 log.Fatalln("At least one of -rx and -tx must be specified")
89         }
90
91         ctx, err := nncp.CtxFromCmdline(*cfgPath, *spoolPath, *logPath, *quiet, *debug)
92         if err != nil {
93                 log.Fatalln("Error during initialization:", err)
94         }
95
96         nodeIds := make(map[nncp.NodeId]struct{}, flag.NArg())
97         for i := 0; i < flag.NArg(); i++ {
98                 node, err := ctx.FindNode(flag.Arg(i))
99                 if err != nil {
100                         log.Fatalln("Invalid specified:", err)
101                 }
102                 nodeIds[*node.Id] = struct{}{}
103         }
104
105         sds := nncp.SDS{}
106         if *doTx {
107                 sds["xx"] = string(nncp.TTx)
108                 var pktName string
109                 bufStdout := bufio.NewWriter(os.Stdout)
110                 tarWr := tar.NewWriter(bufStdout)
111                 for nodeId, _ := range nodeIds {
112                         sds["node"] = nodeId.String()
113                         for job := range ctx.Jobs(&nodeId, nncp.TTx) {
114                                 pktName = filepath.Base(job.Fd.Name())
115                                 sds["pkt"] = pktName
116                                 if job.PktEnc.Nice > nice {
117                                         ctx.LogD("nncp-bundle", sds, "too nice")
118                                         job.Fd.Close()
119                                         continue
120                                 }
121                                 if err = tarWr.WriteHeader(&tar.Header{
122                                         Format:   tar.FormatUSTAR,
123                                         Name:     nncp.NNCPBundlePrefix,
124                                         Mode:     0700,
125                                         Typeflag: tar.TypeDir,
126                                 }); err != nil {
127                                         log.Fatalln("Error writing tar header:", err)
128                                 }
129                                 if err = tarWr.WriteHeader(&tar.Header{
130                                         Format: tar.FormatPAX,
131                                         Name: strings.Join([]string{
132                                                 nncp.NNCPBundlePrefix,
133                                                 nodeId.String(),
134                                                 ctx.SelfId.String(),
135                                                 pktName,
136                                         }, "/"),
137                                         Mode:     0400,
138                                         Size:     job.Size,
139                                         Typeflag: tar.TypeReg,
140                                 }); err != nil {
141                                         log.Fatalln("Error writing tar header:", err)
142                                 }
143                                 if _, err = io.Copy(tarWr, job.Fd); err != nil {
144                                         log.Fatalln("Error during copying to tar:", err)
145                                 }
146                                 job.Fd.Close()
147                                 if err = tarWr.Flush(); err != nil {
148                                         log.Fatalln("Error during tar flushing:", err)
149                                 }
150                                 if err = bufStdout.Flush(); err != nil {
151                                         log.Fatalln("Error during stdout flushing:", err)
152                                 }
153                                 if *doDelete {
154                                         if err = os.Remove(job.Fd.Name()); err != nil {
155                                                 log.Fatalln("Error during deletion:", err)
156                                         }
157                                 }
158                                 ctx.LogI("nncp-bundle", nncp.SdsAdd(sds, nncp.SDS{
159                                         "size": strconv.FormatInt(job.Size, 10),
160                                 }), "")
161                         }
162                 }
163                 if err = tarWr.Close(); err != nil {
164                         log.Fatalln("Error during tar closing:", err)
165                 }
166         } else {
167                 bufStdin := bufio.NewReaderSize(os.Stdin, CopyBufSize*2)
168                 var peeked []byte
169                 var prefixIdx int
170                 var tarR *tar.Reader
171                 var entry *tar.Header
172                 var exists bool
173                 pktEncBuf := make([]byte, nncp.PktEncOverhead)
174                 var pktEnc *nncp.PktEnc
175                 var pktName string
176                 var selfPath string
177                 var dstPath string
178                 for {
179                         peeked, err = bufStdin.Peek(CopyBufSize)
180                         if err != nil && err != io.EOF {
181                                 log.Fatalln("Error during reading:", err)
182                         }
183                         prefixIdx = bytes.Index(peeked, []byte(nncp.NNCPBundlePrefix))
184                         if prefixIdx == -1 {
185                                 if err == io.EOF {
186                                         break
187                                 }
188                                 bufStdin.Discard(bufStdin.Buffered() - (len(nncp.NNCPBundlePrefix) - 1))
189                                 continue
190                         }
191                         bufStdin.Discard(prefixIdx)
192                         tarR = tar.NewReader(bufStdin)
193                         sds["xx"] = string(nncp.TRx)
194                         entry, err = tarR.Next()
195                         if err != nil {
196                                 if err != io.EOF {
197                                         ctx.LogD(
198                                                 "nncp-bundle",
199                                                 nncp.SdsAdd(sds, nncp.SDS{"err": err}),
200                                                 "error reading tar",
201                                         )
202                                 }
203                                 continue
204                         }
205                         if entry.Typeflag != tar.TypeDir {
206                                 ctx.LogD("nncp-bundle", sds, "Expected NNCP/")
207                                 continue
208                         }
209                         entry, err = tarR.Next()
210                         if err != nil {
211                                 if err != io.EOF {
212                                         ctx.LogD(
213                                                 "nncp-bundle",
214                                                 nncp.SdsAdd(sds, nncp.SDS{"err": err}),
215                                                 "error reading tar",
216                                         )
217                                 }
218                                 continue
219                         }
220                         sds["pkt"] = entry.Name
221                         if entry.Size < nncp.PktEncOverhead {
222                                 ctx.LogD("nncp-bundle", sds, "Too small packet")
223                                 continue
224                         }
225                         pktName = filepath.Base(entry.Name)
226                         if _, err = nncp.FromBase32(pktName); err != nil {
227                                 ctx.LogD("nncp-bundle", sds, "Bad packet name")
228                                 continue
229                         }
230                         if _, err = io.ReadFull(tarR, pktEncBuf); err != nil {
231                                 ctx.LogD("nncp-bundle", nncp.SdsAdd(sds, nncp.SDS{"err": err}), "read")
232                                 continue
233                         }
234                         if _, err = xdr.Unmarshal(bytes.NewReader(pktEncBuf), &pktEnc); err != nil {
235                                 ctx.LogD("nncp-bundle", sds, "Bad packet structure")
236                                 continue
237                         }
238                         if pktEnc.Magic != nncp.MagicNNCPEv4 {
239                                 ctx.LogD("nncp-bundle", sds, "Bad packet magic number")
240                                 continue
241                         }
242                         if pktEnc.Nice > nice {
243                                 ctx.LogD("nncp-bundle", sds, "too nice")
244                                 continue
245                         }
246                         if *pktEnc.Sender == *ctx.SelfId && *doDelete {
247                                 if len(nodeIds) > 0 {
248                                         if _, exists = nodeIds[*pktEnc.Recipient]; !exists {
249                                                 ctx.LogD("nncp-bundle", sds, "Recipient is not requested")
250                                                 continue
251                                         }
252                                 }
253                                 nodeId32 := nncp.ToBase32(pktEnc.Recipient[:])
254                                 sds["xx"] = string(nncp.TTx)
255                                 sds["node"] = nodeId32
256                                 sds["pkt"] = pktName
257                                 dstPath = filepath.Join(
258                                         ctx.Spool,
259                                         nodeId32,
260                                         string(nncp.TTx),
261                                         pktName,
262                                 )
263                                 if _, err = os.Stat(dstPath); err != nil {
264                                         ctx.LogD("nncp-bundle", sds, "Packet is already missing")
265                                         continue
266                                 }
267                                 hsh, err := blake2b.New256(nil)
268                                 if err != nil {
269                                         log.Fatalln("Error during hasher creation:", err)
270                                 }
271                                 if _, err = hsh.Write(pktEncBuf); err != nil {
272                                         log.Fatalln("Error during writing:", err)
273                                 }
274                                 if _, err = io.Copy(hsh, tarR); err != nil {
275                                         log.Fatalln("Error during copying:", err)
276                                 }
277                                 if nncp.ToBase32(hsh.Sum(nil)) == pktName {
278                                         ctx.LogI("nncp-bundle", sds, "removed")
279                                         if !*dryRun {
280                                                 os.Remove(dstPath)
281                                         }
282                                 } else {
283                                         ctx.LogE("nncp-bundle", sds, "bad checksum")
284                                 }
285                                 continue
286                         }
287                         if *pktEnc.Recipient != *ctx.SelfId {
288                                 ctx.LogD("nncp-bundle", sds, "Unknown recipient")
289                                 continue
290                         }
291                         if len(nodeIds) > 0 {
292                                 if _, exists = nodeIds[*pktEnc.Sender]; !exists {
293                                         ctx.LogD("nncp-bundle", sds, "Sender is not requested")
294                                         continue
295                                 }
296                         }
297                         sds["node"] = nncp.ToBase32(pktEnc.Recipient[:])
298                         sds["pkt"] = pktName
299                         selfPath = filepath.Join(ctx.Spool, ctx.SelfId.String(), string(nncp.TRx))
300                         dstPath = filepath.Join(selfPath, pktName)
301                         if _, err = os.Stat(dstPath); err == nil || !os.IsNotExist(err) {
302                                 ctx.LogD("nncp-bundle", sds, "Packet already exists")
303                                 continue
304                         }
305                         if _, err = os.Stat(dstPath + nncp.SeenSuffix); err == nil || !os.IsNotExist(err) {
306                                 ctx.LogD("nncp-bundle", sds, "Packet already exists")
307                                 continue
308                         }
309                         if *doCheck {
310                                 if *dryRun {
311                                         hsh, err := blake2b.New256(nil)
312                                         if err != nil {
313                                                 log.Fatalln("Error during hasher creation:", err)
314                                         }
315                                         if _, err = hsh.Write(pktEncBuf); err != nil {
316                                                 log.Fatalln("Error during writing:", err)
317                                         }
318                                         if _, err = io.Copy(hsh, tarR); err != nil {
319                                                 log.Fatalln("Error during copying:", err)
320                                         }
321                                         if nncp.ToBase32(hsh.Sum(nil)) != pktName {
322                                                 ctx.LogE("nncp-bundle", sds, "bad checksum")
323                                                 continue
324                                         }
325                                 } else {
326                                         tmp, err := ctx.NewTmpFileWHash()
327                                         if err != nil {
328                                                 log.Fatalln("Error during temporary file creation:", err)
329                                         }
330                                         if _, err = tmp.W.Write(pktEncBuf); err != nil {
331                                                 log.Fatalln("Error during writing:", err)
332                                         }
333                                         if _, err = io.Copy(tmp.W, tarR); err != nil {
334                                                 log.Fatalln("Error during copying:", err)
335                                         }
336                                         if err = tmp.W.Flush(); err != nil {
337                                                 log.Fatalln("Error during flusing:", err)
338                                         }
339                                         if nncp.ToBase32(tmp.Hsh.Sum(nil)) == pktName {
340                                                 if err = tmp.Commit(selfPath); err != nil {
341                                                         log.Fatalln("Error during commiting:", err)
342                                                 }
343                                         } else {
344                                                 ctx.LogE("nncp-bundle", sds, "bad checksum")
345                                                 tmp.Cancel()
346                                                 continue
347                                         }
348                                 }
349                         } else {
350                                 if *dryRun {
351                                         if _, err = io.Copy(ioutil.Discard, tarR); err != nil {
352                                                 log.Fatalln("Error during copying:", err)
353                                         }
354                                 } else {
355                                         tmp, err := ctx.NewTmpFile()
356                                         if err != nil {
357                                                 log.Fatalln("Error during temporary file creation:", err)
358                                         }
359                                         bufTmp := bufio.NewWriterSize(tmp, CopyBufSize)
360                                         if _, err = bufTmp.Write(pktEncBuf); err != nil {
361                                                 log.Fatalln("Error during writing:", err)
362                                         }
363                                         if _, err = io.Copy(bufTmp, tarR); err != nil {
364                                                 log.Fatalln("Error during copying:", err)
365                                         }
366                                         if err = bufTmp.Flush(); err != nil {
367                                                 log.Fatalln("Error during flushing:", err)
368                                         }
369                                         if err = tmp.Sync(); err != nil {
370                                                 log.Fatalln("Error during syncing:", err)
371                                         }
372                                         tmp.Close()
373                                         if err = os.MkdirAll(selfPath, os.FileMode(0700)); err != nil {
374                                                 log.Fatalln("Error during mkdir:", err)
375                                         }
376                                         if err = os.Rename(tmp.Name(), dstPath); err != nil {
377                                                 log.Fatalln("Error during renaming:", err)
378                                         }
379                                 }
380                         }
381                         ctx.LogI("nncp-bundle", nncp.SdsAdd(sds, nncp.SDS{
382                                 "size": strconv.FormatInt(entry.Size, 10),
383                         }), "")
384                 }
385         }
386 }