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