]> Cypherpunks.ru repositories - nncp.git/blob - src/cypherpunks.ru/nncp/toss.go
1f717faddedc1c01f4528f8f6ab1e2188a710ec5
[nncp.git] / src / cypherpunks.ru / nncp / toss.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, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 package nncp
20
21 import (
22         "bufio"
23         "bytes"
24         "compress/zlib"
25         "fmt"
26         "io"
27         "io/ioutil"
28         "log"
29         "mime"
30         "os"
31         "os/exec"
32         "path"
33         "path/filepath"
34         "strconv"
35         "strings"
36
37         "github.com/davecgh/go-xdr/xdr2"
38         "github.com/dustin/go-humanize"
39         "golang.org/x/crypto/blake2b"
40         "golang.org/x/crypto/poly1305"
41 )
42
43 const (
44         SeenSuffix = ".seen"
45 )
46
47 func newNotification(fromTo *FromToYAML, subject string) io.Reader {
48         return strings.NewReader(fmt.Sprintf(
49                 "From: %s\nTo: %s\nSubject: %s\n",
50                 fromTo.From,
51                 fromTo.To,
52                 mime.BEncoding.Encode("UTF-8", subject),
53         ))
54 }
55
56 func (ctx *Ctx) Toss(
57         nodeId *NodeId,
58         nice uint8,
59         dryRun, doSeen, noFile, noFreq, noExec, noTrns bool,
60 ) bool {
61         isBad := false
62         for job := range ctx.Jobs(nodeId, TRx) {
63                 pktName := filepath.Base(job.Fd.Name())
64                 sds := SDS{"node": job.PktEnc.Sender, "pkt": pktName}
65                 if job.PktEnc.Nice > nice {
66                         ctx.LogD("rx", SdsAdd(sds, SDS{
67                                 "nice": strconv.Itoa(int(job.PktEnc.Nice)),
68                         }), "too nice")
69                         continue
70                 }
71                 pipeR, pipeW := io.Pipe()
72                 errs := make(chan error, 1)
73                 go func(job Job) {
74                         pipeWB := bufio.NewWriter(pipeW)
75                         _, _, err := PktEncRead(
76                                 ctx.Self,
77                                 ctx.Neigh,
78                                 bufio.NewReader(job.Fd),
79                                 pipeWB,
80                         )
81                         errs <- err
82                         pipeWB.Flush()
83                         pipeW.Close()
84                         job.Fd.Close()
85                         if err != nil {
86                                 ctx.LogE("rx", SdsAdd(sds, SDS{"err": err}), "decryption")
87                         }
88                 }(job)
89                 var pkt Pkt
90                 var err error
91                 var pktSize int64
92                 var pktSizeBlocks int64
93                 if _, err = xdr.Unmarshal(pipeR, &pkt); err != nil {
94                         ctx.LogE("rx", SdsAdd(sds, SDS{"err": err}), "unmarshal")
95                         isBad = true
96                         goto Closing
97                 }
98                 pktSize = job.Size - PktEncOverhead - PktOverhead - PktSizeOverhead
99                 pktSizeBlocks = pktSize / (EncBlkSize + poly1305.TagSize)
100                 if pktSize%(EncBlkSize+poly1305.TagSize) != 0 {
101                         pktSize -= poly1305.TagSize
102                 }
103                 pktSize -= pktSizeBlocks * poly1305.TagSize
104                 sds["size"] = strconv.FormatInt(pktSize, 10)
105                 ctx.LogD("rx", sds, "taken")
106                 switch pkt.Type {
107                 case PktTypeExec:
108                         if noExec {
109                                 goto Closing
110                         }
111                         path := bytes.Split(pkt.Path[:int(pkt.PathLen)], []byte{0})
112                         handle := string(path[0])
113                         args := make([]string, 0, len(path)-1)
114                         for _, p := range path[1:] {
115                                 args = append(args, string(p))
116                         }
117                         sds := SdsAdd(sds, SDS{
118                                 "type": "exec",
119                                 "dst":  strings.Join(append([]string{handle}, args...), " "),
120                         })
121                         decompressor, err := zlib.NewReader(pipeR)
122                         if err != nil {
123                                 log.Fatalln(err)
124                         }
125                         sender := ctx.Neigh[*job.PktEnc.Sender]
126                         cmdline, exists := sender.Exec[handle]
127                         if !exists || len(cmdline) == 0 {
128                                 ctx.LogE("rx", SdsAdd(sds, SDS{"err": "No handle found"}), "")
129                                 isBad = true
130                                 goto Closing
131                         }
132                         if !dryRun {
133                                 cmd := exec.Command(
134                                         cmdline[0],
135                                         append(cmdline[1:len(cmdline)], args...)...,
136                                 )
137                                 cmd.Env = append(
138                                         cmd.Env,
139                                         "NNCP_SELF="+ctx.Self.Id.String(),
140                                         "NNCP_SENDER="+sender.Id.String(),
141                                         "NNCP_NICE="+strconv.Itoa(int(pkt.Nice)),
142                                 )
143                                 cmd.Stdin = decompressor
144                                 if err = cmd.Run(); err != nil {
145                                         ctx.LogE("rx", SdsAdd(sds, SDS{"err": err}), "handle")
146                                         isBad = true
147                                         goto Closing
148                                 }
149                         }
150                         ctx.LogI("rx", sds, "")
151                         if !dryRun {
152                                 if doSeen {
153                                         if fd, err := os.Create(job.Fd.Name() + SeenSuffix); err == nil {
154                                                 fd.Close()
155                                         }
156                                 }
157                                 if err = os.Remove(job.Fd.Name()); err != nil {
158                                         ctx.LogE("rx", SdsAdd(sds, SDS{"err": err}), "remove")
159                                         isBad = true
160                                 }
161                         }
162                 case PktTypeFile:
163                         if noFile {
164                                 goto Closing
165                         }
166                         dst := string(pkt.Path[:int(pkt.PathLen)])
167                         sds := SdsAdd(sds, SDS{"type": "file", "dst": dst})
168                         if filepath.IsAbs(dst) {
169                                 ctx.LogE("rx", sds, "non-relative destination path")
170                                 isBad = true
171                                 goto Closing
172                         }
173                         incoming := ctx.Neigh[*job.PktEnc.Sender].Incoming
174                         if incoming == nil {
175                                 ctx.LogE("rx", sds, "incoming is not allowed")
176                                 isBad = true
177                                 goto Closing
178                         }
179                         dir := filepath.Join(*incoming, path.Dir(dst))
180                         if err = os.MkdirAll(dir, os.FileMode(0700)); err != nil {
181                                 ctx.LogE("rx", SdsAdd(sds, SDS{"err": err}), "mkdir")
182                                 isBad = true
183                                 goto Closing
184                         }
185                         if !dryRun {
186                                 tmp, err := ioutil.TempFile(dir, "nncp-file")
187                                 if err != nil {
188                                         ctx.LogE("rx", SdsAdd(sds, SDS{"err": err}), "mktemp")
189                                         isBad = true
190                                         goto Closing
191                                 }
192                                 sds["tmp"] = tmp.Name()
193                                 ctx.LogD("rx", sds, "created")
194                                 bufW := bufio.NewWriter(tmp)
195                                 if _, err = io.Copy(bufW, pipeR); err != nil {
196                                         ctx.LogE("rx", SdsAdd(sds, SDS{"err": err}), "copy")
197                                         isBad = true
198                                         goto Closing
199                                 }
200                                 if err = bufW.Flush(); err != nil {
201                                         tmp.Close()
202                                         ctx.LogE("rx", SdsAdd(sds, SDS{"err": err}), "copy")
203                                         isBad = true
204                                         goto Closing
205                                 }
206                                 if err = tmp.Sync(); err != nil {
207                                         tmp.Close()
208                                         ctx.LogE("rx", SdsAdd(sds, SDS{"err": err}), "copy")
209                                         isBad = true
210                                         goto Closing
211                                 }
212                                 tmp.Close()
213                                 dstPathOrig := filepath.Join(*incoming, dst)
214                                 dstPath := dstPathOrig
215                                 dstPathCtr := 0
216                                 for {
217                                         if _, err = os.Stat(dstPath); err != nil {
218                                                 if os.IsNotExist(err) {
219                                                         break
220                                                 }
221                                                 ctx.LogE("rx", SdsAdd(sds, SDS{"err": err}), "stat")
222                                                 isBad = true
223                                                 goto Closing
224                                         }
225                                         dstPath = dstPathOrig + strconv.Itoa(dstPathCtr)
226                                         dstPathCtr++
227                                 }
228                                 if err = os.Rename(tmp.Name(), dstPath); err != nil {
229                                         ctx.LogE("rx", SdsAdd(sds, SDS{"err": err}), "rename")
230                                         isBad = true
231                                 }
232                                 delete(sds, "tmp")
233                         }
234                         ctx.LogI("rx", sds, "")
235                         if !dryRun {
236                                 if doSeen {
237                                         if fd, err := os.Create(job.Fd.Name() + SeenSuffix); err == nil {
238                                                 fd.Close()
239                                         }
240                                 }
241                                 if err = os.Remove(job.Fd.Name()); err != nil {
242                                         ctx.LogE("rx", SdsAdd(sds, SDS{"err": err}), "remove")
243                                         isBad = true
244                                 }
245                                 sendmail, exists := ctx.Neigh[*ctx.SelfId].Exec["sendmail"]
246                                 if exists && len(sendmail) > 0 && ctx.NotifyFile != nil {
247                                         cmd := exec.Command(
248                                                 sendmail[0],
249                                                 append(sendmail[1:len(sendmail)], ctx.NotifyFile.To)...,
250                                         )
251                                         cmd.Stdin = newNotification(ctx.NotifyFile, fmt.Sprintf(
252                                                 "File from %s: %s (%s)",
253                                                 ctx.Neigh[*job.PktEnc.Sender].Name,
254                                                 dst,
255                                                 humanize.IBytes(uint64(pktSize)),
256                                         ))
257                                         cmd.Run()
258                                 }
259                         }
260                 case PktTypeFreq:
261                         if noFreq {
262                                 goto Closing
263                         }
264                         src := string(pkt.Path[:int(pkt.PathLen)])
265                         if filepath.IsAbs(src) {
266                                 ctx.LogE("rx", sds, "non-relative source path")
267                                 isBad = true
268                                 goto Closing
269                         }
270                         sds := SdsAdd(sds, SDS{"type": "freq", "src": src})
271                         dstRaw, err := ioutil.ReadAll(pipeR)
272                         if err != nil {
273                                 ctx.LogE("rx", SdsAdd(sds, SDS{"err": err}), "read")
274                                 isBad = true
275                                 goto Closing
276                         }
277                         dst := string(dstRaw)
278                         sds["dst"] = dst
279                         sender := ctx.Neigh[*job.PktEnc.Sender]
280                         freq := sender.Freq
281                         if freq == nil {
282                                 ctx.LogE("rx", sds, "freqing is not allowed")
283                                 isBad = true
284                                 goto Closing
285                         }
286                         if !dryRun {
287                                 if sender.FreqChunked == 0 {
288                                         err = ctx.TxFile(
289                                                 sender,
290                                                 pkt.Nice,
291                                                 filepath.Join(*freq, src),
292                                                 dst,
293                                                 sender.FreqMinSize,
294                                         )
295                                 } else {
296                                         err = ctx.TxFileChunked(
297                                                 sender,
298                                                 pkt.Nice,
299                                                 filepath.Join(*freq, src),
300                                                 dst,
301                                                 sender.FreqMinSize,
302                                                 sender.FreqChunked,
303                                         )
304                                 }
305                                 if err != nil {
306                                         ctx.LogE("rx", SdsAdd(sds, SDS{"err": err}), "tx file")
307                                         isBad = true
308                                         goto Closing
309                                 }
310                         }
311                         ctx.LogI("rx", sds, "")
312                         if !dryRun {
313                                 if doSeen {
314                                         if fd, err := os.Create(job.Fd.Name() + SeenSuffix); err == nil {
315                                                 fd.Close()
316                                         }
317                                 }
318                                 if err = os.Remove(job.Fd.Name()); err != nil {
319                                         ctx.LogE("rx", SdsAdd(sds, SDS{"err": err}), "remove")
320                                         isBad = true
321                                 }
322                                 sendmail, exists := ctx.Neigh[*ctx.SelfId].Exec["sendmail"]
323                                 if exists && len(sendmail) > 0 && ctx.NotifyFreq != nil {
324                                         cmd := exec.Command(
325                                                 sendmail[0],
326                                                 append(sendmail[1:len(sendmail)], ctx.NotifyFreq.To)...,
327                                         )
328                                         cmd.Stdin = newNotification(ctx.NotifyFreq, fmt.Sprintf(
329                                                 "Freq from %s: %s",
330                                                 ctx.Neigh[*job.PktEnc.Sender].Name,
331                                                 src,
332                                         ))
333                                         cmd.Run()
334                                 }
335                         }
336                 case PktTypeTrns:
337                         if noTrns {
338                                 goto Closing
339                         }
340                         dst := new([blake2b.Size256]byte)
341                         copy(dst[:], pkt.Path[:int(pkt.PathLen)])
342                         nodeId := NodeId(*dst)
343                         node, known := ctx.Neigh[nodeId]
344                         sds := SdsAdd(sds, SDS{"type": "trns", "dst": nodeId})
345                         if !known {
346                                 ctx.LogE("rx", sds, "unknown node")
347                                 isBad = true
348                                 goto Closing
349                         }
350                         ctx.LogD("rx", sds, "taken")
351                         if !dryRun {
352                                 if err = ctx.TxTrns(node, job.PktEnc.Nice, pktSize, pipeR); err != nil {
353                                         ctx.LogE("rx", SdsAdd(sds, SDS{"err": err}), "tx trns")
354                                         isBad = true
355                                         goto Closing
356                                 }
357                         }
358                         ctx.LogI("rx", sds, "")
359                         if !dryRun {
360                                 if doSeen {
361                                         if fd, err := os.Create(job.Fd.Name() + SeenSuffix); err == nil {
362                                                 fd.Close()
363                                         }
364                                 }
365                                 if err = os.Remove(job.Fd.Name()); err != nil {
366                                         ctx.LogE("rx", SdsAdd(sds, SDS{"err": err}), "remove")
367                                         isBad = true
368                                 }
369                         }
370                 default:
371                         ctx.LogE("rx", sds, "unknown type")
372                         isBad = true
373                 }
374         Closing:
375                 pipeR.Close()
376         }
377         return isBad
378 }