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