2 NNCP -- Node to Node copy, utilities for store-and-forward data exchange
3 Copyright (C) 2016-2022 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/>.
30 func cfgDirMkdir(dst ...string) error {
31 return os.MkdirAll(filepath.Join(dst...), os.FileMode(0777))
34 func cfgDirSave(v interface{}, dst ...string) error {
36 switch v := v.(type) {
53 r = strconv.Itoa(int(*v))
58 r = strconv.FormatUint(*v, 10)
62 panic("unsupported value type")
64 mode := os.FileMode(0666)
65 if strings.HasSuffix(dst[len(dst)-1], "prv") {
66 mode = os.FileMode(0600)
68 return ioutil.WriteFile(filepath.Join(dst...), []byte(r+"\n"), mode)
71 func cfgDirTouch(dst ...string) error {
72 if fd, err := os.Create(filepath.Join(dst...)); err == nil {
80 func CfgToDir(dst string, cfg *CfgJSON) (err error) {
81 if err = cfgDirMkdir(dst); err != nil {
84 if err = cfgDirSave(cfg.Spool, dst, "spool"); err != nil {
87 if err = cfgDirSave(cfg.Log, dst, "log"); err != nil {
90 if err = cfgDirSave(cfg.Umask, dst, "umask"); err != nil {
94 if err = cfgDirTouch(dst, "noprogress"); err != nil {
99 if err = cfgDirTouch(dst, "nohdr"); err != nil {
104 if len(cfg.MCDRxIfis) > 0 {
106 strings.Join(cfg.MCDRxIfis, "\n"),
112 if len(cfg.MCDTxIfis) > 0 {
113 if err = cfgDirMkdir(dst, "mcd-send"); err != nil {
116 for ifi, t := range cfg.MCDTxIfis {
117 if err = cfgDirSave(t, dst, "mcd-send", ifi); err != nil {
123 if cfg.Notify != nil {
124 if cfg.Notify.File != nil {
125 if err = cfgDirMkdir(dst, "notify", "file"); err != nil {
129 cfg.Notify.File.From,
130 dst, "notify", "file", "from",
136 dst, "notify", "file", "to",
141 if cfg.Notify.Freq != nil {
142 if err = cfgDirMkdir(dst, "notify", "freq"); err != nil {
146 cfg.Notify.Freq.From,
147 dst, "notify", "freq", "from",
153 dst, "notify", "freq", "to",
158 for k, v := range cfg.Notify.Exec {
159 if err = cfgDirMkdir(dst, "notify", "exec", k); err != nil {
162 if err = cfgDirSave(v.From, dst, "notify", "exec", k, "from"); err != nil {
165 if err = cfgDirSave(v.To, dst, "notify", "exec", k, "to"); err != nil {
172 if err = cfgDirMkdir(dst, "self"); err != nil {
175 if err = cfgDirSave(cfg.Self.Id, dst, "self", "id"); err != nil {
178 if err = cfgDirSave(cfg.Self.ExchPub, dst, "self", "exchpub"); err != nil {
181 if err = cfgDirSave(cfg.Self.ExchPrv, dst, "self", "exchprv"); err != nil {
184 if err = cfgDirSave(cfg.Self.SignPub, dst, "self", "signpub"); err != nil {
187 if err = cfgDirSave(cfg.Self.SignPrv, dst, "self", "signprv"); err != nil {
190 if err = cfgDirSave(cfg.Self.NoisePub, dst, "self", "noisepub"); err != nil {
193 if err = cfgDirSave(cfg.Self.NoisePrv, dst, "self", "noiseprv"); err != nil {
198 for name, n := range cfg.Neigh {
199 if err = cfgDirMkdir(dst, "neigh", name); err != nil {
202 if err = cfgDirSave(n.Id, dst, "neigh", name, "id"); err != nil {
205 if err = cfgDirSave(n.ExchPub, dst, "neigh", name, "exchpub"); err != nil {
208 if err = cfgDirSave(n.SignPub, dst, "neigh", name, "signpub"); err != nil {
211 if err = cfgDirSave(n.NoisePub, dst, "neigh", name, "noisepub"); err != nil {
214 if err = cfgDirSave(n.Incoming, dst, "neigh", name, "incoming"); err != nil {
219 if err = cfgDirMkdir(dst, "neigh", name, "exec"); err != nil {
222 for k, v := range n.Exec {
224 strings.Join(v, "\n"),
225 dst, "neigh", name, "exec", k,
233 if err = cfgDirMkdir(dst, "neigh", name, "freq"); err != nil {
238 dst, "neigh", name, "freq", "path",
244 dst, "neigh", name, "freq", "chunked",
250 dst, "neigh", name, "freq", "minsize",
256 dst, "neigh", name, "freq", "maxsize",
264 strings.Join(n.Via, "\n"),
265 dst, "neigh", name, "via",
271 if len(n.Addrs) > 0 {
272 if err = cfgDirMkdir(dst, "neigh", name, "addrs"); err != nil {
275 for k, v := range n.Addrs {
276 if err = cfgDirSave(v, dst, "neigh", name, "addrs", k); err != nil {
282 if err = cfgDirSave(n.RxRate, dst, "neigh", name, "rxrate"); err != nil {
285 if err = cfgDirSave(n.TxRate, dst, "neigh", name, "txrate"); err != nil {
288 if err = cfgDirSave(n.OnlineDeadline, dst, "neigh", name, "onlinedeadline"); err != nil {
291 if err = cfgDirSave(n.MaxOnlineTime, dst, "neigh", name, "maxonlinetime"); err != nil {
295 for i, call := range n.Calls {
296 is := strconv.Itoa(i)
297 if err = cfgDirMkdir(dst, "neigh", name, "calls", is); err != nil {
300 if err = cfgDirSave(call.Cron, dst, "neigh", name, "calls", is, "cron"); err != nil {
303 if err = cfgDirSave(call.Nice, dst, "neigh", name, "calls", is, "nice"); err != nil {
306 if err = cfgDirSave(call.Xx, dst, "neigh", name, "calls", is, "xx"); err != nil {
309 if err = cfgDirSave(call.RxRate, dst, "neigh", name, "calls", is, "rxrate"); err != nil {
312 if err = cfgDirSave(call.TxRate, dst, "neigh", name, "calls", is, "txrate"); err != nil {
315 if err = cfgDirSave(call.Addr, dst, "neigh", name, "calls", is, "addr"); err != nil {
318 if err = cfgDirSave(call.OnlineDeadline, dst, "neigh", name, "calls", is, "onlinedeadline"); err != nil {
321 if err = cfgDirSave(call.MaxOnlineTime, dst, "neigh", name, "calls", is, "maxonlinetime"); err != nil {
324 if call.WhenTxExists {
325 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "when-tx-exists"); err != nil {
330 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "nock"); err != nil {
335 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "mcd-ignore"); err != nil {
340 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss"); err != nil {
344 if call.AutoTossDoSeen {
345 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-doseen"); err != nil {
349 if call.AutoTossNoFile {
350 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-nofile"); err != nil {
354 if call.AutoTossNoFreq {
355 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-nofreq"); err != nil {
359 if call.AutoTossNoExec {
360 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-noexec"); err != nil {
364 if call.AutoTossNoTrns {
365 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-notrns"); err != nil {
369 if call.AutoTossNoArea {
370 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-noarea"); err != nil {
377 for name, a := range cfg.Areas {
378 if err = cfgDirMkdir(dst, "areas", name); err != nil {
381 if err = cfgDirSave(a.Id, dst, "areas", name, "id"); err != nil {
384 if err = cfgDirSave(a.Pub, dst, "areas", name, "pub"); err != nil {
387 if err = cfgDirSave(a.Prv, dst, "areas", name, "prv"); err != nil {
390 if err = cfgDirSave(a.Incoming, dst, "areas", name, "incoming"); err != nil {
394 if err = cfgDirTouch(dst, "areas", name, "allow-unknown"); err != nil {
399 if err = cfgDirMkdir(dst, "areas", name, "exec"); err != nil {
402 for k, v := range a.Exec {
404 strings.Join(v, "\n"),
405 dst, "areas", name, "exec", k,
413 strings.Join(a.Subs, "\n"),
414 dst, "areas", name, "subs",
424 func cfgDirLoad(src ...string) (v string, exists bool, err error) {
425 b, err := ioutil.ReadFile(filepath.Join(src...))
427 if os.IsNotExist(err) {
428 return "", false, nil
430 return "", false, err
432 return strings.TrimSuffix(string(b), "\n"), true, nil
435 func cfgDirLoadMust(src ...string) (v string, err error) {
436 s, exists, err := cfgDirLoad(src...)
441 return "", fmt.Errorf("required \"%s\" does not exist", src[len(src)-1])
446 func cfgDirLoadOpt(src ...string) (v *string, err error) {
447 s, exists, err := cfgDirLoad(src...)
457 func cfgDirLoadIntOpt(src ...string) (i64 *int64, err error) {
458 s, err := cfgDirLoadOpt(src...)
465 i, err := strconv.ParseInt(*s, 10, 64)
472 func cfgDirExists(src ...string) bool {
473 if _, err := os.Stat(filepath.Join(src...)); err == nil {
479 func cfgDirReadFromTo(src ...string) (*FromToJSON, error) {
480 fromTo := FromToJSON{}
483 fromTo.From, err = cfgDirLoadMust(append(src, "from")...)
488 fromTo.To, err = cfgDirLoadMust(append(src, "to")...)
496 func DirToCfg(src string) (*CfgJSON, error) {
500 cfg.Spool, err = cfgDirLoadMust(src, "spool")
504 cfg.Log, err = cfgDirLoadMust(src, "log")
509 if cfg.Umask, err = cfgDirLoadOpt(src, "umask"); err != nil {
512 cfg.OmitPrgrs = cfgDirExists(src, "noprogress")
513 cfg.NoHdr = cfgDirExists(src, "nohdr")
515 sp, err := cfgDirLoadOpt(src, "mcd-listen")
520 cfg.MCDRxIfis = strings.Split(*sp, "\n")
523 fis, err := ioutil.ReadDir(filepath.Join(src, "mcd-send"))
524 if err != nil && !os.IsNotExist(err) {
528 cfg.MCDTxIfis = make(map[string]int, len(fis))
530 for _, fi := range fis {
535 b, err := ioutil.ReadFile(filepath.Join(src, "mcd-send", fi.Name()))
539 i, err := strconv.Atoi(strings.TrimSuffix(string(b), "\n"))
546 notify := NotifyJSON{Exec: make(map[string]*FromToJSON)}
547 if cfgDirExists(src, "notify", "file") {
548 if notify.File, err = cfgDirReadFromTo(src, "notify", "file"); err != nil {
552 if cfgDirExists(src, "notify", "freq") {
553 if notify.Freq, err = cfgDirReadFromTo(src, "notify", "freq"); err != nil {
557 fis, err = ioutil.ReadDir(filepath.Join(src, "notify", "exec"))
558 if err != nil && !os.IsNotExist(err) {
561 for _, fi := range fis {
563 if n[0] == '.' || !fi.IsDir() {
566 if notify.Exec[fi.Name()], err = cfgDirReadFromTo(src, "notify", "exec", n); err != nil {
570 if notify.File != nil || notify.Freq != nil || len(notify.Exec) > 0 {
574 if _, err = ioutil.ReadDir(filepath.Join(src, "self")); err == nil {
575 self := NodeOurJSON{}
576 if self.Id, err = cfgDirLoadMust(src, "self", "id"); err != nil {
579 if self.ExchPub, err = cfgDirLoadMust(src, "self", "exchpub"); err != nil {
582 if self.ExchPrv, err = cfgDirLoadMust(src, "self", "exchprv"); err != nil {
585 if self.SignPub, err = cfgDirLoadMust(src, "self", "signpub"); err != nil {
588 if self.SignPrv, err = cfgDirLoadMust(src, "self", "signprv"); err != nil {
591 if self.NoisePub, err = cfgDirLoadMust(src, "self", "noisepub"); err != nil {
594 if self.NoisePrv, err = cfgDirLoadMust(src, "self", "noiseprv"); err != nil {
598 } else if !os.IsNotExist(err) {
602 cfg.Neigh = make(map[string]NodeJSON)
603 fis, err = ioutil.ReadDir(filepath.Join(src, "neigh"))
604 if err != nil && !os.IsNotExist(err) {
607 for _, fi := range fis {
613 if node.Id, err = cfgDirLoadMust(src, "neigh", n, "id"); err != nil {
616 if node.ExchPub, err = cfgDirLoadMust(src, "neigh", n, "exchpub"); err != nil {
619 if node.SignPub, err = cfgDirLoadMust(src, "neigh", n, "signpub"); err != nil {
622 if node.NoisePub, err = cfgDirLoadOpt(src, "neigh", n, "noisepub"); err != nil {
625 if node.Incoming, err = cfgDirLoadOpt(src, "neigh", n, "incoming"); err != nil {
629 node.Exec = make(map[string][]string)
630 fis2, err := ioutil.ReadDir(filepath.Join(src, "neigh", n, "exec"))
631 if err != nil && !os.IsNotExist(err) {
634 for _, fi2 := range fis2 {
639 s, err := cfgDirLoadMust(src, "neigh", n, "exec", n2)
643 node.Exec[n2] = strings.Split(s, "\n")
646 if cfgDirExists(src, "neigh", n, "freq") {
647 node.Freq = &NodeFreqJSON{}
648 if node.Freq.Path, err = cfgDirLoadOpt(src, "neigh", n, "freq", "path"); err != nil {
652 i64, err := cfgDirLoadIntOpt(src, "neigh", n, "freq", "chunked")
658 node.Freq.Chunked = &i
661 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "freq", "minsize")
667 node.Freq.MinSize = &i
670 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "freq", "maxsize")
676 node.Freq.MaxSize = &i
680 via, err := cfgDirLoadOpt(src, "neigh", n, "via")
685 node.Via = strings.Split(*via, "\n")
688 node.Addrs = make(map[string]string)
689 fis2, err = ioutil.ReadDir(filepath.Join(src, "neigh", n, "addrs"))
690 if err != nil && !os.IsNotExist(err) {
693 for _, fi2 := range fis2 {
698 if node.Addrs[n2], err = cfgDirLoadMust(src, "neigh", n, "addrs", n2); err != nil {
703 i64, err := cfgDirLoadIntOpt(src, "neigh", n, "rxrate")
712 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "txrate")
721 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "onlinedeadline")
727 node.OnlineDeadline = &i
730 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "maxonlinetime")
736 node.MaxOnlineTime = &i
739 fis2, err = ioutil.ReadDir(filepath.Join(src, "neigh", n, "calls"))
740 if err != nil && !os.IsNotExist(err) {
743 callsIdx := make([]int, 0, len(fis2))
744 for _, fi2 := range fis2 {
749 i, err := strconv.Atoi(n2)
753 callsIdx = append(callsIdx, i)
756 for _, i := range callsIdx {
758 is := strconv.Itoa(i)
759 if call.Cron, err = cfgDirLoadMust(
760 src, "neigh", n, "calls", is, "cron",
764 if call.Nice, err = cfgDirLoadOpt(
765 src, "neigh", n, "calls", is, "nice",
769 if call.Xx, err = cfgDirLoadOpt(
770 src, "neigh", n, "calls", is, "xx",
775 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "calls", is, "rxrate")
784 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "calls", is, "txrate")
793 if call.Addr, err = cfgDirLoadOpt(
794 src, "neigh", n, "calls", is, "addr",
799 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "calls", is, "onlinedeadline")
805 call.OnlineDeadline = &i
808 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "calls", is, "maxonlinetime")
814 call.MaxOnlineTime = &i
817 if cfgDirExists(src, "neigh", n, "calls", is, "when-tx-exists") {
818 call.WhenTxExists = true
820 if cfgDirExists(src, "neigh", n, "calls", is, "nock") {
823 if cfgDirExists(src, "neigh", n, "calls", is, "mcd-ignore") {
824 call.MCDIgnore = true
826 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss") {
829 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-doseen") {
830 call.AutoTossDoSeen = true
832 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-nofile") {
833 call.AutoTossNoFile = true
835 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-nofreq") {
836 call.AutoTossNoFreq = true
838 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-noexec") {
839 call.AutoTossNoExec = true
841 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-notrns") {
842 call.AutoTossNoTrns = true
844 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-noarea") {
845 call.AutoTossNoArea = true
847 node.Calls = append(node.Calls, call)
852 cfg.Areas = make(map[string]AreaJSON)
853 fis, err = ioutil.ReadDir(filepath.Join(src, "areas"))
854 if err != nil && !os.IsNotExist(err) {
857 for _, fi := range fis {
863 if area.Id, err = cfgDirLoadMust(src, "areas", n, "id"); err != nil {
866 if area.Pub, err = cfgDirLoadOpt(src, "areas", n, "pub"); err != nil {
869 if area.Prv, err = cfgDirLoadOpt(src, "areas", n, "prv"); err != nil {
873 subs, err := cfgDirLoadOpt(src, "areas", n, "subs")
878 area.Subs = strings.Split(*subs, "\n")
881 area.Exec = make(map[string][]string)
882 fis2, err := ioutil.ReadDir(filepath.Join(src, "areas", n, "exec"))
883 if err != nil && !os.IsNotExist(err) {
886 for _, fi2 := range fis2 {
891 s, err := cfgDirLoadMust(src, "areas", n, "exec", n2)
895 area.Exec[n2] = strings.Split(s, "\n")
898 if area.Incoming, err = cfgDirLoadOpt(src, "areas", n, "incoming"); err != nil {
902 if cfgDirExists(src, "areas", n, "allow-unknown") {
903 area.AllowUnknown = true