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