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