2 NNCP -- Node to Node copy, utilities for store-and-forward data exchange
3 Copyright (C) 2016-2021 Sergey Matveev <stargrave@stargrave.org>
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.
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.
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/>.
38 xdr "github.com/davecgh/go-xdr/xdr2"
39 "github.com/dustin/go-humanize"
40 "github.com/klauspost/compress/zstd"
41 "golang.org/x/crypto/blake2b"
42 "golang.org/x/crypto/poly1305"
49 func newNotification(fromTo *FromToJSON, subject string, body []byte) io.Reader {
51 "From: " + fromTo.From,
53 "Subject: " + mime.BEncoding.Encode("UTF-8", subject),
59 "Content-Type: text/plain; charset=utf-8",
60 "Content-Transfer-Encoding: base64",
62 base64.StdEncoding.EncodeToString(body),
65 return strings.NewReader(strings.Join(lines, "\n"))
68 func pktSizeWithoutEnc(pktSize int64) int64 {
69 pktSize = pktSize - PktEncOverhead - PktOverhead - PktSizeOverhead
70 pktSizeBlocks := pktSize / (EncBlkSize + poly1305.TagSize)
71 if pktSize%(EncBlkSize+poly1305.TagSize) != 0 {
72 pktSize -= poly1305.TagSize
74 pktSize -= pktSizeBlocks * poly1305.TagSize
78 var JobRepeatProcess = errors.New("needs processing repeat")
89 decompressor *zstd.Decoder,
90 dryRun, doSeen, noFile, noFreq, noExec, noTrns, noArea bool,
93 sendmail := ctx.Neigh[*ctx.SelfId].Exec["sendmail"]
95 _, err := xdr.Unmarshal(pipeR, &pkt)
97 ctx.LogE("rx-unmarshal", les, err, func(les LEs) string {
98 return fmt.Sprintf("Tossing %s/%s: unmarshal", sender.Name, pktName)
102 les = append(les, LE{"Size", int64(pktSize)})
103 ctx.LogD("rx", les, func(les LEs) string {
105 "Tossing %s/%s (%s)",
106 sender.Name, pktName,
107 humanize.IBytes(pktSize),
111 case PktTypeExec, PktTypeExecFat:
115 path := bytes.Split(pkt.Path[:int(pkt.PathLen)], []byte{0})
116 handle := string(path[0])
117 args := make([]string, 0, len(path)-1)
118 for _, p := range path[1:] {
119 args = append(args, string(p))
121 argsStr := strings.Join(append([]string{handle}, args...), " ")
122 les = append(les, LE{"Type", "exec"}, LE{"Dst", argsStr})
123 cmdline, exists := sender.Exec[handle]
124 if !exists || len(cmdline) == 0 {
125 err = errors.New("No handle found")
127 "rx-no-handle", les, err,
128 func(les LEs) string {
130 "Tossing exec %s/%s (%s): %s",
131 sender.Name, pktName,
132 humanize.IBytes(pktSize), argsStr,
138 if pkt.Type == PktTypeExec {
139 if err = decompressor.Reset(pipeR); err != nil {
144 cmd := exec.Command(cmdline[0], append(cmdline[1:], args...)...)
147 "NNCP_SELF="+ctx.Self.Id.String(),
148 "NNCP_SENDER="+sender.Id.String(),
149 "NNCP_NICE="+strconv.Itoa(int(pkt.Nice)),
151 if pkt.Type == PktTypeExec {
152 cmd.Stdin = decompressor
156 output, err := cmd.Output()
158 ctx.LogE("rx-hande", les, err, func(les LEs) string {
160 "Tossing exec %s/%s (%s): %s: handling",
161 sender.Name, pktName,
162 humanize.IBytes(uint64(pktSize)), argsStr,
167 if len(sendmail) > 0 && ctx.NotifyExec != nil {
168 notify, exists := ctx.NotifyExec[sender.Name+"."+handle]
170 notify, exists = ctx.NotifyExec["*."+handle]
175 append(sendmail[1:], notify.To)...,
177 cmd.Stdin = newNotification(notify, fmt.Sprintf(
178 "Exec from %s: %s", sender.Name, argsStr,
180 if err = cmd.Run(); err != nil {
181 ctx.LogE("rx-notify", les, err, func(les LEs) string {
183 "Tossing exec %s/%s (%s): %s: notifying",
184 sender.Name, pktName,
185 humanize.IBytes(pktSize), argsStr,
192 ctx.LogI("rx", les, func(les LEs) string {
194 "Got exec from %s to %s (%s)",
195 sender.Name, argsStr,
196 humanize.IBytes(pktSize),
199 if !dryRun && jobPath != "" {
201 if fd, err := os.Create(jobPath + SeenSuffix); err == nil {
203 if err = DirSync(filepath.Dir(jobPath)); err != nil {
204 ctx.LogE("rx-dirsync", les, err, func(les LEs) string {
206 "Tossing file %s/%s (%s): %s: dirsyncing",
207 sender.Name, pktName,
208 humanize.IBytes(pktSize),
209 filepath.Base(jobPath),
216 if err = os.Remove(jobPath); err != nil {
217 ctx.LogE("rx-notify", les, err, func(les LEs) string {
219 "Tossing exec %s/%s (%s): %s: notifying",
220 sender.Name, pktName,
221 humanize.IBytes(pktSize), argsStr,
225 } else if ctx.HdrUsage {
226 os.Remove(jobPath + HdrSuffix)
234 dst := string(pkt.Path[:int(pkt.PathLen)])
235 les = append(les, LE{"Type", "file"}, LE{"Dst", dst})
236 if filepath.IsAbs(dst) {
237 err = errors.New("non-relative destination path")
239 "rx-non-rel", les, err,
240 func(les LEs) string {
242 "Tossing file %s/%s (%s): %s",
243 sender.Name, pktName,
244 humanize.IBytes(pktSize), dst,
250 incoming := sender.Incoming
252 err = errors.New("incoming is not allowed")
254 "rx-no-incoming", les, err,
255 func(les LEs) string {
257 "Tossing file %s/%s (%s): %s",
258 sender.Name, pktName,
259 humanize.IBytes(pktSize), dst,
265 dir := filepath.Join(*incoming, path.Dir(dst))
266 if err = os.MkdirAll(dir, os.FileMode(0777)); err != nil {
267 ctx.LogE("rx-mkdir", les, err, func(les LEs) string {
269 "Tossing file %s/%s (%s): %s: mkdir",
270 sender.Name, pktName,
271 humanize.IBytes(pktSize), dst,
277 tmp, err := TempFile(dir, "file")
279 ctx.LogE("rx-mktemp", les, err, func(les LEs) string {
281 "Tossing file %s/%s (%s): %s: mktemp",
282 sender.Name, pktName,
283 humanize.IBytes(pktSize), dst,
288 les = append(les, LE{"Tmp", tmp.Name()})
289 ctx.LogD("rx-tmp-created", les, func(les LEs) string {
291 "Tossing file %s/%s (%s): %s: created: %s",
292 sender.Name, pktName,
293 humanize.IBytes(pktSize), dst, tmp.Name(),
296 bufW := bufio.NewWriter(tmp)
297 if _, err = CopyProgressed(
298 bufW, pipeR, "Rx file",
299 append(les, LE{"FullSize", int64(pktSize)}),
302 ctx.LogE("rx-copy", les, err, func(les LEs) string {
304 "Tossing file %s/%s (%s): %s: copying",
305 sender.Name, pktName,
306 humanize.IBytes(pktSize), dst,
311 if err = bufW.Flush(); err != nil {
312 tmp.Close() // #nosec G104
313 ctx.LogE("rx-flush", les, err, func(les LEs) string {
315 "Tossing file %s/%s (%s): %s: flushing",
316 sender.Name, pktName,
317 humanize.IBytes(pktSize), dst,
322 if err = tmp.Sync(); err != nil {
323 tmp.Close() // #nosec G104
324 ctx.LogE("rx-sync", les, err, func(les LEs) string {
326 "Tossing file %s/%s (%s): %s: syncing",
327 sender.Name, pktName,
328 humanize.IBytes(pktSize), dst,
333 if err = tmp.Close(); err != nil {
334 ctx.LogE("rx-close", les, err, func(les LEs) string {
336 "Tossing file %s/%s (%s): %s: closing",
337 sender.Name, pktName,
338 humanize.IBytes(pktSize), dst,
343 dstPathOrig := filepath.Join(*incoming, dst)
344 dstPath := dstPathOrig
347 if _, err = os.Stat(dstPath); err != nil {
348 if os.IsNotExist(err) {
351 ctx.LogE("rx-stat", les, err, func(les LEs) string {
353 "Tossing file %s/%s (%s): %s: stating: %s",
354 sender.Name, pktName,
355 humanize.IBytes(pktSize), dst, dstPath,
360 dstPath = dstPathOrig + "." + strconv.Itoa(dstPathCtr)
363 if err = os.Rename(tmp.Name(), dstPath); err != nil {
364 ctx.LogE("rx-rename", les, err, func(les LEs) string {
366 "Tossing file %s/%s (%s): %s: renaming",
367 sender.Name, pktName,
368 humanize.IBytes(pktSize), dst,
373 if err = DirSync(*incoming); err != nil {
374 ctx.LogE("rx-dirsync", les, err, func(les LEs) string {
376 "Tossing file %s/%s (%s): %s: dirsyncing",
377 sender.Name, pktName,
378 humanize.IBytes(pktSize), dst,
383 les = les[:len(les)-1] // delete Tmp
385 ctx.LogI("rx", les, func(les LEs) string {
387 "Got file %s (%s) from %s",
388 dst, humanize.IBytes(pktSize), sender.Name,
394 if fd, err := os.Create(jobPath + SeenSuffix); err == nil {
396 if err = DirSync(filepath.Dir(jobPath)); err != nil {
397 ctx.LogE("rx-dirsync", les, err, func(les LEs) string {
399 "Tossing file %s/%s (%s): %s: dirsyncing",
400 sender.Name, pktName,
401 humanize.IBytes(pktSize),
402 filepath.Base(jobPath),
409 if err = os.Remove(jobPath); err != nil {
410 ctx.LogE("rx-remove", les, err, func(les LEs) string {
412 "Tossing file %s/%s (%s): %s: removing",
413 sender.Name, pktName,
414 humanize.IBytes(pktSize), dst,
418 } else if ctx.HdrUsage {
419 os.Remove(jobPath + HdrSuffix)
422 if len(sendmail) > 0 && ctx.NotifyFile != nil {
425 append(sendmail[1:], ctx.NotifyFile.To)...,
427 cmd.Stdin = newNotification(ctx.NotifyFile, fmt.Sprintf(
428 "File from %s: %s (%s)",
429 sender.Name, dst, humanize.IBytes(pktSize),
431 if err = cmd.Run(); err != nil {
432 ctx.LogE("rx-notify", les, err, func(les LEs) string {
434 "Tossing file %s/%s (%s): %s: notifying",
435 sender.Name, pktName,
436 humanize.IBytes(pktSize), dst,
447 src := string(pkt.Path[:int(pkt.PathLen)])
448 les := append(les, LE{"Type", "freq"}, LE{"Src", src})
449 if filepath.IsAbs(src) {
450 err = errors.New("non-relative source path")
452 "rx-non-rel", les, err,
453 func(les LEs) string {
455 "Tossing freq %s/%s (%s): %s: notifying",
456 sender.Name, pktName,
457 humanize.IBytes(pktSize), src,
463 dstRaw, err := ioutil.ReadAll(pipeR)
465 ctx.LogE("rx-read", les, err, func(les LEs) string {
467 "Tossing freq %s/%s (%s): %s: reading",
468 sender.Name, pktName,
469 humanize.IBytes(pktSize), src,
474 dst := string(dstRaw)
475 les = append(les, LE{"Dst", dst})
476 freqPath := sender.FreqPath
478 err = errors.New("freqing is not allowed")
480 "rx-no-freq", les, err,
481 func(les LEs) string {
483 "Tossing freq %s/%s (%s): %s -> %s",
484 sender.Name, pktName,
485 humanize.IBytes(pktSize), src, dst,
495 filepath.Join(*freqPath, src),
503 ctx.LogE("rx-tx", les, err, func(les LEs) string {
505 "Tossing freq %s/%s (%s): %s -> %s: txing",
506 sender.Name, pktName,
507 humanize.IBytes(pktSize), src, dst,
513 ctx.LogI("rx", les, func(les LEs) string {
514 return fmt.Sprintf("Got file request %s to %s", src, sender.Name)
519 if fd, err := os.Create(jobPath + SeenSuffix); err == nil {
521 if err = DirSync(filepath.Dir(jobPath)); err != nil {
522 ctx.LogE("rx-dirsync", les, err, func(les LEs) string {
524 "Tossing file %s/%s (%s): %s: dirsyncing",
525 sender.Name, pktName,
526 humanize.IBytes(pktSize),
527 filepath.Base(jobPath),
534 if err = os.Remove(jobPath); err != nil {
535 ctx.LogE("rx-remove", les, err, func(les LEs) string {
537 "Tossing freq %s/%s (%s): %s -> %s: removing",
538 sender.Name, pktName,
539 humanize.IBytes(pktSize), src, dst,
543 } else if ctx.HdrUsage {
544 os.Remove(jobPath + HdrSuffix)
547 if len(sendmail) > 0 && ctx.NotifyFreq != nil {
550 append(sendmail[1:], ctx.NotifyFreq.To)...,
552 cmd.Stdin = newNotification(ctx.NotifyFreq, fmt.Sprintf(
553 "Freq from %s: %s", sender.Name, src,
555 if err = cmd.Run(); err != nil {
556 ctx.LogE("rx-notify", les, err, func(les LEs) string {
558 "Tossing freq %s/%s (%s): %s -> %s: notifying",
559 sender.Name, pktName,
560 humanize.IBytes(pktSize), src, dst,
571 dst := new([MTHSize]byte)
572 copy(dst[:], pkt.Path[:int(pkt.PathLen)])
573 nodeId := NodeId(*dst)
574 les := append(les, LE{"Type", "trns"}, LE{"Dst", nodeId})
575 logMsg := func(les LEs) string {
577 "Tossing trns %s/%s (%s): %s",
578 sender.Name, pktName,
579 humanize.IBytes(pktSize),
583 node := ctx.Neigh[nodeId]
585 err = errors.New("unknown node")
586 ctx.LogE("rx-unknown", les, err, logMsg)
589 ctx.LogD("rx-tx", les, logMsg)
591 if err = ctx.TxTrns(node, nice, int64(pktSize), pipeR); err != nil {
592 ctx.LogE("rx", les, err, func(les LEs) string {
593 return logMsg(les) + ": txing"
598 ctx.LogI("rx", les, func(les LEs) string {
600 "Got transitional packet from %s to %s (%s)",
602 ctx.NodeName(&nodeId),
603 humanize.IBytes(pktSize),
606 if !dryRun && jobPath != "" {
608 if fd, err := os.Create(jobPath + SeenSuffix); err == nil {
610 if err = DirSync(filepath.Dir(jobPath)); err != nil {
611 ctx.LogE("rx-dirsync", les, err, func(les LEs) string {
613 "Tossing file %s/%s (%s): %s: dirsyncing",
614 sender.Name, pktName,
615 humanize.IBytes(pktSize),
616 filepath.Base(jobPath),
623 if err = os.Remove(jobPath); err != nil {
624 ctx.LogE("rx", les, err, func(les LEs) string {
626 "Tossing trns %s/%s (%s): %s: removing",
627 sender.Name, pktName,
628 humanize.IBytes(pktSize),
629 ctx.NodeName(&nodeId),
633 } else if ctx.HdrUsage {
634 os.Remove(jobPath + HdrSuffix)
642 areaId := new(AreaId)
643 copy(areaId[:], pkt.Path[:int(pkt.PathLen)])
644 les := append(les, LE{"Type", "area"}, LE{"Area", areaId})
645 logMsg := func(les LEs) string {
647 "Tossing %s/%s (%s): area %s",
648 sender.Name, pktName,
649 humanize.IBytes(pktSize),
650 ctx.AreaName(areaId),
653 area := ctx.AreaId2Area[*areaId]
655 err = errors.New("unknown area")
656 ctx.LogE("rx-area-unknown", les, err, logMsg)
659 pktEnc, pktEncRaw, err := ctx.HdrRead(pipeR)
660 fullPipeR := io.MultiReader(bytes.NewReader(pktEncRaw), pipeR)
662 ctx.LogE("rx-area-pkt-enc-read", les, err, logMsg)
665 msgHashRaw := blake2b.Sum256(pktEncRaw)
666 msgHash := Base32Codec.EncodeToString(msgHashRaw[:])
667 les = append(les, LE{"AreaMsg", msgHash})
668 ctx.LogD("rx-area", les, logMsg)
671 for _, nodeId := range area.Subs {
672 node := ctx.Neigh[*nodeId]
673 lesEcho := append(les, LE{"Echo", nodeId})
674 seenDir := filepath.Join(
675 ctx.Spool, nodeId.String(), AreaDir, area.Id.String(),
677 seenPath := filepath.Join(seenDir, msgHash+SeenSuffix)
678 logMsgNode := func(les LEs) string {
680 "%s: echoing to: %s", logMsg(les), node.Name,
683 if _, err := os.Stat(seenPath); err == nil {
684 ctx.LogD("rx-area-echo-seen", lesEcho, func(les LEs) string {
685 return logMsgNode(les) + ": already sent"
689 ctx.LogI("rx-area-echo", lesEcho, logMsgNode)
692 for _, nodeId := range area.Subs {
693 node := ctx.Neigh[*nodeId]
694 lesEcho := append(les, LE{"Echo", nodeId})
695 seenDir := filepath.Join(
696 ctx.Spool, nodeId.String(), AreaDir, area.Id.String(),
698 seenPath := filepath.Join(seenDir, msgHash+SeenSuffix)
699 logMsgNode := func(les LEs) string {
700 return fmt.Sprintf("%s: echo to: %s", logMsg(les), node.Name)
702 if _, err := os.Stat(seenPath); err == nil {
703 ctx.LogD("rx-area-echo-seen", lesEcho, func(les LEs) string {
704 return logMsgNode(les) + ": already sent"
708 if nodeId != sender.Id {
709 ctx.LogI("rx-area-echo", lesEcho, logMsgNode)
711 node, &pkt, nice, int64(pktSize), 0, fullPipeR, pktName, nil,
713 ctx.LogE("rx-area", lesEcho, err, logMsgNode)
717 if err = os.MkdirAll(seenDir, os.FileMode(0777)); err != nil {
718 ctx.LogE("rx-area-mkdir", lesEcho, err, logMsgNode)
721 if fd, err := os.Create(seenPath); err == nil {
723 if err = DirSync(seenDir); err != nil {
724 ctx.LogE("rx-area-dirsync", les, err, logMsgNode)
728 ctx.LogE("rx-area-touch", lesEcho, err, logMsgNode)
731 return JobRepeatProcess
735 seenDir := filepath.Join(
736 ctx.Spool, ctx.SelfId.String(), AreaDir, area.Id.String(),
738 seenPath := filepath.Join(seenDir, msgHash+SeenSuffix)
739 if _, err := os.Stat(seenPath); err == nil {
740 ctx.LogD("rx-area-seen", les, func(les LEs) string {
741 return logMsg(les) + ": already seen"
743 if !dryRun && jobPath != "" {
744 if err = os.Remove(jobPath); err != nil {
745 ctx.LogE("rx-area-remove", les, err, func(les LEs) string {
747 "Tossing area %s/%s (%s): %s: removing",
748 sender.Name, pktName,
749 humanize.IBytes(pktSize),
754 } else if ctx.HdrUsage {
755 os.Remove(jobPath + HdrSuffix)
762 ctx.LogD("rx-area-no-prv", les, func(les LEs) string {
763 return logMsg(les) + ": no private key for decoding"
766 signatureVerify := true
767 if _, senderKnown := ctx.Neigh[*pktEnc.Sender]; !senderKnown {
768 if !area.AllowUnknown {
769 err = errors.New("unknown sender")
772 append(les, LE{"Sender", pktEnc.Sender}),
774 func(les LEs) string {
775 return logMsg(les) + ": sender: " + pktEnc.Sender.String()
780 signatureVerify = false
782 areaNodeOur := NodeOur{Id: new(NodeId), ExchPrv: new([32]byte)}
783 copy(areaNodeOur.Id[:], area.Id[:])
784 copy(areaNodeOur.ExchPrv[:], area.Prv[:])
788 Incoming: area.Incoming,
791 copy(areaNode.Id[:], area.Id[:])
792 pktName := fmt.Sprintf(
794 Base32Codec.EncodeToString(areaId[:]), msgHash,
797 pipeR, pipeW := io.Pipe()
798 errs := make(chan error, 1)
807 uint64(pktSizeWithoutEnc(int64(pktSize))),
810 dryRun, doSeen, noFile, noFreq, noExec, noTrns, noArea,
813 _, _, _, err = PktEncRead(
822 pipeW.CloseWithError(err)
827 if err = <-errs; err != nil {
832 if !dryRun && jobPath != "" {
833 if err = os.MkdirAll(seenDir, os.FileMode(0777)); err != nil {
834 ctx.LogE("rx-area-mkdir", les, err, logMsg)
837 if fd, err := os.Create(seenPath); err == nil {
839 if err = DirSync(seenDir); err != nil {
840 ctx.LogE("rx-area-dirsync", les, err, logMsg)
844 if err = os.Remove(jobPath); err != nil {
845 ctx.LogE("rx", les, err, func(les LEs) string {
847 "Tossing area %s/%s (%s): %s: removing",
848 sender.Name, pktName,
849 humanize.IBytes(pktSize),
854 } else if ctx.HdrUsage {
855 os.Remove(jobPath + HdrSuffix)
860 err = errors.New("unknown type")
862 "rx-type-unknown", les, err,
863 func(les LEs) string {
865 "Tossing %s/%s (%s)",
866 sender.Name, pktName, humanize.IBytes(pktSize),
875 func (ctx *Ctx) Toss(
879 dryRun, doSeen, noFile, noFreq, noExec, noTrns, noArea bool,
881 dirLock, err := ctx.LockDir(nodeId, "toss")
885 defer ctx.UnlockDir(dirLock)
887 decompressor, err := zstd.NewReader(nil)
891 defer decompressor.Close()
892 for job := range ctx.Jobs(nodeId, xx) {
893 pktName := filepath.Base(job.Path)
895 {"Node", job.PktEnc.Sender},
897 {"Nice", int(job.PktEnc.Nice)},
899 if job.PktEnc.Nice > nice {
900 ctx.LogD("rx-too-nice", les, func(les LEs) string {
902 "Tossing %s/%s: too nice: %s",
903 ctx.NodeName(job.PktEnc.Sender), pktName,
904 NicenessFmt(job.PktEnc.Nice),
909 fd, err := os.Open(job.Path)
911 ctx.LogE("rx-open", les, err, func(les LEs) string {
913 "Tossing %s/%s: opening %s",
914 ctx.NodeName(job.PktEnc.Sender), pktName, job.Path,
920 sender := ctx.Neigh[*job.PktEnc.Sender]
922 err := errors.New("unknown node")
923 ctx.LogE("rx-open", les, err, func(les LEs) string {
926 ctx.NodeName(job.PktEnc.Sender), pktName,
932 errs := make(chan error, 1)
935 pipeR, pipeW := io.Pipe()
944 uint64(pktSizeWithoutEnc(job.Size)),
947 dryRun, doSeen, noFile, noFreq, noExec, noTrns, noArea,
950 pipeWB := bufio.NewWriter(pipeW)
951 sharedKey, _, _, err = PktEncRead(
960 pipeW.CloseWithError(err)
962 if err := pipeWB.Flush(); err != nil {
963 pipeW.CloseWithError(err)
973 if err = <-errs; err == JobRepeatProcess {
974 if _, err = fd.Seek(0, io.SeekStart); err != nil {
975 ctx.LogE("rx-seek", les, err, func(les LEs) string {
977 "Tossing %s/%s: can not seek",
978 ctx.NodeName(job.PktEnc.Sender),
986 } else if err != nil {
994 func (ctx *Ctx) AutoToss(
997 doSeen, noFile, noFreq, noExec, noTrns, noArea bool,
998 ) (chan struct{}, chan bool) {
999 finish := make(chan struct{})
1000 badCode := make(chan bool)
1010 time.Sleep(time.Second)
1012 nodeId, TRx, nice, false,
1013 doSeen, noFile, noFreq, noExec, noTrns, noArea) || bad
1016 return finish, badCode