]> Cypherpunks.ru repositories - nncp.git/blob - src/cmd/nncp-bundle/main.go
60bb1130c83d09559084b633d7f5c3f9cea1c5d7
[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-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         xdr "github.com/davecgh/go-xdr/xdr2"
36         "go.cypherpunks.ru/nncp/v5"
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         ctx.Umask()
106
107         sds := nncp.SDS{}
108         if *doTx {
109                 sds["xx"] = string(nncp.TTx)
110                 var pktName string
111                 bufStdout := bufio.NewWriter(os.Stdout)
112                 tarWr := tar.NewWriter(bufStdout)
113                 for nodeId, _ := range nodeIds {
114                         sds["node"] = nodeId.String()
115                         for job := range ctx.Jobs(&nodeId, nncp.TTx) {
116                                 pktName = filepath.Base(job.Fd.Name())
117                                 sds["pkt"] = pktName
118                                 if job.PktEnc.Nice > nice {
119                                         ctx.LogD("nncp-bundle", sds, "too nice")
120                                         job.Fd.Close()
121                                         continue
122                                 }
123                                 if err = tarWr.WriteHeader(&tar.Header{
124                                         Format:   tar.FormatUSTAR,
125                                         Name:     nncp.NNCPBundlePrefix,
126                                         Mode:     0700,
127                                         Typeflag: tar.TypeDir,
128                                 }); err != nil {
129                                         log.Fatalln("Error writing tar header:", err)
130                                 }
131                                 if err = tarWr.WriteHeader(&tar.Header{
132                                         Format: tar.FormatPAX,
133                                         Name: strings.Join([]string{
134                                                 nncp.NNCPBundlePrefix,
135                                                 nodeId.String(),
136                                                 ctx.SelfId.String(),
137                                                 pktName,
138                                         }, "/"),
139                                         Mode:     0400,
140                                         Size:     job.Size,
141                                         Typeflag: tar.TypeReg,
142                                 }); err != nil {
143                                         log.Fatalln("Error writing tar header:", err)
144                                 }
145                                 if _, err = io.Copy(tarWr, job.Fd); err != nil {
146                                         log.Fatalln("Error during copying to tar:", err)
147                                 }
148                                 job.Fd.Close()
149                                 if err = tarWr.Flush(); err != nil {
150                                         log.Fatalln("Error during tar flushing:", err)
151                                 }
152                                 if err = bufStdout.Flush(); err != nil {
153                                         log.Fatalln("Error during stdout flushing:", err)
154                                 }
155                                 if *doDelete {
156                                         if err = os.Remove(job.Fd.Name()); err != nil {
157                                                 log.Fatalln("Error during deletion:", err)
158                                         }
159                                 }
160                                 ctx.LogI("nncp-bundle", nncp.SdsAdd(sds, nncp.SDS{
161                                         "size": strconv.FormatInt(job.Size, 10),
162                                 }), "")
163                         }
164                 }
165                 if err = tarWr.Close(); err != nil {
166                         log.Fatalln("Error during tar closing:", err)
167                 }
168         } else {
169                 bufStdin := bufio.NewReaderSize(os.Stdin, CopyBufSize*2)
170                 var peeked []byte
171                 var prefixIdx int
172                 var tarR *tar.Reader
173                 var entry *tar.Header
174                 var exists bool
175                 pktEncBuf := make([]byte, nncp.PktEncOverhead)
176                 var pktEnc *nncp.PktEnc
177                 var pktName string
178                 var selfPath string
179                 var dstPath string
180                 for {
181                         peeked, err = bufStdin.Peek(CopyBufSize)
182                         if err != nil && err != io.EOF {
183                                 log.Fatalln("Error during reading:", err)
184                         }
185                         prefixIdx = bytes.Index(peeked, []byte(nncp.NNCPBundlePrefix))
186                         if prefixIdx == -1 {
187                                 if err == io.EOF {
188                                         break
189                                 }
190                                 bufStdin.Discard(bufStdin.Buffered() - (len(nncp.NNCPBundlePrefix) - 1))
191                                 continue
192                         }
193                         bufStdin.Discard(prefixIdx)
194                         tarR = tar.NewReader(bufStdin)
195                         sds["xx"] = string(nncp.TRx)
196                         entry, err = tarR.Next()
197                         if err != nil {
198                                 if err != io.EOF {
199                                         ctx.LogD(
200                                                 "nncp-bundle",
201                                                 nncp.SdsAdd(sds, nncp.SDS{"err": err}),
202                                                 "error reading tar",
203                                         )
204                                 }
205                                 continue
206                         }
207                         if entry.Typeflag != tar.TypeDir {
208                                 ctx.LogD("nncp-bundle", sds, "Expected NNCP/")
209                                 continue
210                         }
211                         entry, err = tarR.Next()
212                         if err != nil {
213                                 if err != io.EOF {
214                                         ctx.LogD(
215                                                 "nncp-bundle",
216                                                 nncp.SdsAdd(sds, nncp.SDS{"err": err}),
217                                                 "error reading tar",
218                                         )
219                                 }
220                                 continue
221                         }
222                         sds["pkt"] = entry.Name
223                         if entry.Size < nncp.PktEncOverhead {
224                                 ctx.LogD("nncp-bundle", sds, "Too small packet")
225                                 continue
226                         }
227                         pktName = filepath.Base(entry.Name)
228                         if _, err = nncp.FromBase32(pktName); err != nil {
229                                 ctx.LogD("nncp-bundle", sds, "Bad packet name")
230                                 continue
231                         }
232                         if _, err = io.ReadFull(tarR, pktEncBuf); err != nil {
233                                 ctx.LogD("nncp-bundle", nncp.SdsAdd(sds, nncp.SDS{"err": err}), "read")
234                                 continue
235                         }
236                         if _, err = xdr.Unmarshal(bytes.NewReader(pktEncBuf), &pktEnc); err != nil {
237                                 ctx.LogD("nncp-bundle", sds, "Bad packet structure")
238                                 continue
239                         }
240                         if pktEnc.Magic != nncp.MagicNNCPEv4 {
241                                 ctx.LogD("nncp-bundle", sds, "Bad packet magic number")
242                                 continue
243                         }
244                         if pktEnc.Nice > nice {
245                                 ctx.LogD("nncp-bundle", sds, "too nice")
246                                 continue
247                         }
248                         if *pktEnc.Sender == *ctx.SelfId && *doDelete {
249                                 if len(nodeIds) > 0 {
250                                         if _, exists = nodeIds[*pktEnc.Recipient]; !exists {
251                                                 ctx.LogD("nncp-bundle", sds, "Recipient is not requested")
252                                                 continue
253                                         }
254                                 }
255                                 nodeId32 := nncp.ToBase32(pktEnc.Recipient[:])
256                                 sds["xx"] = string(nncp.TTx)
257                                 sds["node"] = nodeId32
258                                 sds["pkt"] = pktName
259                                 dstPath = filepath.Join(
260                                         ctx.Spool,
261                                         nodeId32,
262                                         string(nncp.TTx),
263                                         pktName,
264                                 )
265                                 if _, err = os.Stat(dstPath); err != nil {
266                                         ctx.LogD("nncp-bundle", sds, "Packet is already missing")
267                                         continue
268                                 }
269                                 hsh, err := blake2b.New256(nil)
270                                 if err != nil {
271                                         log.Fatalln("Error during hasher creation:", err)
272                                 }
273                                 if _, err = hsh.Write(pktEncBuf); err != nil {
274                                         log.Fatalln("Error during writing:", err)
275                                 }
276                                 if _, err = io.Copy(hsh, tarR); err != nil {
277                                         log.Fatalln("Error during copying:", err)
278                                 }
279                                 if nncp.ToBase32(hsh.Sum(nil)) == pktName {
280                                         ctx.LogI("nncp-bundle", sds, "removed")
281                                         if !*dryRun {
282                                                 os.Remove(dstPath)
283                                         }
284                                 } else {
285                                         ctx.LogE("nncp-bundle", sds, "bad checksum")
286                                 }
287                                 continue
288                         }
289                         if *pktEnc.Recipient != *ctx.SelfId {
290                                 ctx.LogD("nncp-bundle", sds, "Unknown recipient")
291                                 continue
292                         }
293                         if len(nodeIds) > 0 {
294                                 if _, exists = nodeIds[*pktEnc.Sender]; !exists {
295                                         ctx.LogD("nncp-bundle", sds, "Sender is not requested")
296                                         continue
297                                 }
298                         }
299                         sds["node"] = nncp.ToBase32(pktEnc.Recipient[:])
300                         sds["pkt"] = pktName
301                         selfPath = filepath.Join(ctx.Spool, ctx.SelfId.String(), string(nncp.TRx))
302                         dstPath = filepath.Join(selfPath, pktName)
303                         if _, err = os.Stat(dstPath); err == nil || !os.IsNotExist(err) {
304                                 ctx.LogD("nncp-bundle", sds, "Packet already exists")
305                                 continue
306                         }
307                         if _, err = os.Stat(dstPath + nncp.SeenSuffix); err == nil || !os.IsNotExist(err) {
308                                 ctx.LogD("nncp-bundle", sds, "Packet already exists")
309                                 continue
310                         }
311                         if *doCheck {
312                                 if *dryRun {
313                                         hsh, err := blake2b.New256(nil)
314                                         if err != nil {
315                                                 log.Fatalln("Error during hasher creation:", err)
316                                         }
317                                         if _, err = hsh.Write(pktEncBuf); err != nil {
318                                                 log.Fatalln("Error during writing:", err)
319                                         }
320                                         if _, err = io.Copy(hsh, tarR); err != nil {
321                                                 log.Fatalln("Error during copying:", err)
322                                         }
323                                         if nncp.ToBase32(hsh.Sum(nil)) != pktName {
324                                                 ctx.LogE("nncp-bundle", sds, "bad checksum")
325                                                 continue
326                                         }
327                                 } else {
328                                         tmp, err := ctx.NewTmpFileWHash()
329                                         if err != nil {
330                                                 log.Fatalln("Error during temporary file creation:", err)
331                                         }
332                                         if _, err = tmp.W.Write(pktEncBuf); err != nil {
333                                                 log.Fatalln("Error during writing:", err)
334                                         }
335                                         if _, err = io.Copy(tmp.W, tarR); err != nil {
336                                                 log.Fatalln("Error during copying:", err)
337                                         }
338                                         if err = tmp.W.Flush(); err != nil {
339                                                 log.Fatalln("Error during flusing:", err)
340                                         }
341                                         if nncp.ToBase32(tmp.Hsh.Sum(nil)) == pktName {
342                                                 if err = tmp.Commit(selfPath); err != nil {
343                                                         log.Fatalln("Error during commiting:", err)
344                                                 }
345                                         } else {
346                                                 ctx.LogE("nncp-bundle", sds, "bad checksum")
347                                                 tmp.Cancel()
348                                                 continue
349                                         }
350                                 }
351                         } else {
352                                 if *dryRun {
353                                         if _, err = io.Copy(ioutil.Discard, tarR); err != nil {
354                                                 log.Fatalln("Error during copying:", err)
355                                         }
356                                 } else {
357                                         tmp, err := ctx.NewTmpFile()
358                                         if err != nil {
359                                                 log.Fatalln("Error during temporary file creation:", err)
360                                         }
361                                         bufTmp := bufio.NewWriterSize(tmp, CopyBufSize)
362                                         if _, err = bufTmp.Write(pktEncBuf); err != nil {
363                                                 log.Fatalln("Error during writing:", err)
364                                         }
365                                         if _, err = io.Copy(bufTmp, tarR); err != nil {
366                                                 log.Fatalln("Error during copying:", err)
367                                         }
368                                         if err = bufTmp.Flush(); err != nil {
369                                                 log.Fatalln("Error during flushing:", err)
370                                         }
371                                         if err = tmp.Sync(); err != nil {
372                                                 log.Fatalln("Error during syncing:", err)
373                                         }
374                                         tmp.Close()
375                                         if err = os.MkdirAll(selfPath, os.FileMode(0777)); err != nil {
376                                                 log.Fatalln("Error during mkdir:", err)
377                                         }
378                                         if err = os.Rename(tmp.Name(), dstPath); err != nil {
379                                                 log.Fatalln("Error during renaming:", err)
380                                         }
381                                         if err = nncp.DirSync(selfPath); err != nil {
382                                                 log.Fatalln("Error during syncing:", err)
383                                         }
384                                 }
385                         }
386                         ctx.LogI("nncp-bundle", nncp.SdsAdd(sds, nncp.SDS{
387                                 "size": strconv.FormatInt(entry.Size, 10),
388                         }), "")
389                 }
390         }
391 }