2 NNCP -- Node to Node copy, utilities for store-and-forward data exchange
3 Copyright (C) 2016-2023 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/>.
29 func cfgDirMkdir(dst ...string) error {
30 return os.MkdirAll(filepath.Join(dst...), os.FileMode(0777))
33 func cfgDirSave(v interface{}, dst ...string) error {
35 switch v := v.(type) {
52 r = strconv.Itoa(int(*v))
57 r = strconv.FormatUint(*v, 10)
61 panic("unsupported value type")
63 mode := os.FileMode(0666)
64 if strings.HasSuffix(dst[len(dst)-1], "prv") {
65 mode = os.FileMode(0600)
67 return os.WriteFile(filepath.Join(dst...), []byte(r+"\n"), mode)
70 func cfgDirTouch(dst ...string) error {
71 if fd, err := os.Create(filepath.Join(dst...)); err == nil {
79 func CfgToDir(dst string, cfg *CfgJSON) (err error) {
80 if err = cfgDirMkdir(dst); err != nil {
83 if err = cfgDirSave(cfg.Spool, dst, "spool"); err != nil {
86 if err = cfgDirSave(cfg.Log, dst, "log"); err != nil {
89 if err = cfgDirSave(cfg.Umask, dst, "umask"); err != nil {
93 if err = cfgDirTouch(dst, "noprogress"); err != nil {
98 if err = cfgDirTouch(dst, "nohdr"); err != nil {
103 if len(cfg.MCDRxIfis) > 0 {
105 strings.Join(cfg.MCDRxIfis, "\n"),
111 if len(cfg.MCDTxIfis) > 0 {
112 if err = cfgDirMkdir(dst, "mcd-send"); err != nil {
115 for ifi, t := range cfg.MCDTxIfis {
116 if err = cfgDirSave(t, dst, "mcd-send", ifi); err != nil {
122 if cfg.Notify != nil {
123 if cfg.Notify.File != nil {
124 if err = cfgDirMkdir(dst, "notify", "file"); err != nil {
128 cfg.Notify.File.From,
129 dst, "notify", "file", "from",
135 dst, "notify", "file", "to",
140 if cfg.Notify.Freq != nil {
141 if err = cfgDirMkdir(dst, "notify", "freq"); err != nil {
145 cfg.Notify.Freq.From,
146 dst, "notify", "freq", "from",
152 dst, "notify", "freq", "to",
157 for k, v := range cfg.Notify.Exec {
158 if err = cfgDirMkdir(dst, "notify", "exec", k); err != nil {
161 if err = cfgDirSave(v.From, dst, "notify", "exec", k, "from"); err != nil {
164 if err = cfgDirSave(v.To, dst, "notify", "exec", k, "to"); err != nil {
171 if err = cfgDirMkdir(dst, "self"); err != nil {
174 if err = cfgDirSave(cfg.Self.Id, dst, "self", "id"); err != nil {
177 if err = cfgDirSave(cfg.Self.ExchPub, dst, "self", "exchpub"); err != nil {
180 if err = cfgDirSave(cfg.Self.ExchPrv, dst, "self", "exchprv"); err != nil {
183 if err = cfgDirSave(cfg.Self.SignPub, dst, "self", "signpub"); err != nil {
186 if err = cfgDirSave(cfg.Self.SignPrv, dst, "self", "signprv"); err != nil {
189 if err = cfgDirSave(cfg.Self.NoisePub, dst, "self", "noisepub"); err != nil {
192 if err = cfgDirSave(cfg.Self.NoisePrv, dst, "self", "noiseprv"); err != nil {
197 for name, n := range cfg.Neigh {
198 if err = cfgDirMkdir(dst, "neigh", name); err != nil {
201 if err = cfgDirSave(n.Id, dst, "neigh", name, "id"); err != nil {
204 if err = cfgDirSave(n.ExchPub, dst, "neigh", name, "exchpub"); err != nil {
207 if err = cfgDirSave(n.SignPub, dst, "neigh", name, "signpub"); err != nil {
210 if err = cfgDirSave(n.NoisePub, dst, "neigh", name, "noisepub"); err != nil {
213 if err = cfgDirSave(n.Incoming, dst, "neigh", name, "incoming"); err != nil {
218 if err = cfgDirMkdir(dst, "neigh", name, "exec"); err != nil {
221 for k, v := range n.Exec {
223 strings.Join(v, "\n"),
224 dst, "neigh", name, "exec", k,
232 if err = cfgDirMkdir(dst, "neigh", name, "freq"); err != nil {
237 dst, "neigh", name, "freq", "path",
243 dst, "neigh", name, "freq", "chunked",
249 dst, "neigh", name, "freq", "minsize",
255 dst, "neigh", name, "freq", "maxsize",
263 strings.Join(n.Via, "\n"),
264 dst, "neigh", name, "via",
270 if len(n.Addrs) > 0 {
271 if err = cfgDirMkdir(dst, "neigh", name, "addrs"); err != nil {
274 for k, v := range n.Addrs {
275 if err = cfgDirSave(v, dst, "neigh", name, "addrs", k); err != nil {
281 if err = cfgDirSave(n.RxRate, dst, "neigh", name, "rxrate"); err != nil {
284 if err = cfgDirSave(n.TxRate, dst, "neigh", name, "txrate"); err != nil {
287 if err = cfgDirSave(n.OnlineDeadline, dst, "neigh", name, "onlinedeadline"); err != nil {
290 if err = cfgDirSave(n.MaxOnlineTime, dst, "neigh", name, "maxonlinetime"); err != nil {
294 for i, call := range n.Calls {
295 is := strconv.Itoa(i)
296 if err = cfgDirMkdir(dst, "neigh", name, "calls", is); err != nil {
299 if err = cfgDirSave(call.Cron, dst, "neigh", name, "calls", is, "cron"); err != nil {
302 if err = cfgDirSave(call.Nice, dst, "neigh", name, "calls", is, "nice"); err != nil {
305 if err = cfgDirSave(call.Xx, dst, "neigh", name, "calls", is, "xx"); err != nil {
308 if err = cfgDirSave(call.RxRate, dst, "neigh", name, "calls", is, "rxrate"); err != nil {
311 if err = cfgDirSave(call.TxRate, dst, "neigh", name, "calls", is, "txrate"); err != nil {
314 if err = cfgDirSave(call.Addr, dst, "neigh", name, "calls", is, "addr"); err != nil {
317 if err = cfgDirSave(call.OnlineDeadline, dst, "neigh", name, "calls", is, "onlinedeadline"); err != nil {
320 if err = cfgDirSave(call.MaxOnlineTime, dst, "neigh", name, "calls", is, "maxonlinetime"); err != nil {
323 if call.WhenTxExists {
324 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "when-tx-exists"); err != nil {
329 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "nock"); err != nil {
334 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "mcd-ignore"); err != nil {
339 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss"); err != nil {
343 if call.AutoTossDoSeen {
344 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-doseen"); err != nil {
348 if call.AutoTossNoFile {
349 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-nofile"); err != nil {
353 if call.AutoTossNoFreq {
354 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-nofreq"); err != nil {
358 if call.AutoTossNoExec {
359 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-noexec"); err != nil {
363 if call.AutoTossNoTrns {
364 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-notrns"); err != nil {
368 if call.AutoTossNoArea {
369 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-noarea"); err != nil {
376 for name, a := range cfg.Areas {
377 if err = cfgDirMkdir(dst, "areas", name); err != nil {
380 if err = cfgDirSave(a.Id, dst, "areas", name, "id"); err != nil {
383 if err = cfgDirSave(a.Pub, dst, "areas", name, "pub"); err != nil {
386 if err = cfgDirSave(a.Prv, dst, "areas", name, "prv"); err != nil {
389 if err = cfgDirSave(a.Incoming, dst, "areas", name, "incoming"); err != nil {
393 if err = cfgDirTouch(dst, "areas", name, "allow-unknown"); err != nil {
398 if err = cfgDirMkdir(dst, "areas", name, "exec"); err != nil {
401 for k, v := range a.Exec {
403 strings.Join(v, "\n"),
404 dst, "areas", name, "exec", k,
412 strings.Join(a.Subs, "\n"),
413 dst, "areas", name, "subs",
420 if len(cfg.YggdrasilAliases) > 0 {
421 if err = cfgDirMkdir(dst, "yggdrasil-aliases"); err != nil {
424 for alias, v := range cfg.YggdrasilAliases {
425 if err = cfgDirSave(v, dst, "yggdrasil-aliases", alias); err != nil {
434 func cfgDirLoad(src ...string) (v string, exists bool, err error) {
435 b, err := os.ReadFile(filepath.Join(src...))
437 if os.IsNotExist(err) {
438 return "", false, nil
440 return "", false, err
442 return strings.TrimSuffix(string(b), "\n"), true, nil
445 func cfgDirLoadMust(src ...string) (v string, err error) {
446 s, exists, err := cfgDirLoad(src...)
451 return "", fmt.Errorf("required \"%s\" does not exist", src[len(src)-1])
456 func cfgDirLoadOpt(src ...string) (v *string, err error) {
457 s, exists, err := cfgDirLoad(src...)
467 func cfgDirLoadIntOpt(src ...string) (i64 *int64, err error) {
468 s, err := cfgDirLoadOpt(src...)
475 i, err := strconv.ParseInt(*s, 10, 64)
482 func cfgDirExists(src ...string) bool {
483 if _, err := os.Stat(filepath.Join(src...)); err == nil {
489 func cfgDirReadFromTo(src ...string) (*FromToJSON, error) {
490 fromTo := FromToJSON{}
493 fromTo.From, err = cfgDirLoadMust(append(src, "from")...)
498 fromTo.To, err = cfgDirLoadMust(append(src, "to")...)
506 func DirToCfg(src string) (*CfgJSON, error) {
510 cfg.Spool, err = cfgDirLoadMust(src, "spool")
514 cfg.Log, err = cfgDirLoadMust(src, "log")
519 if cfg.Umask, err = cfgDirLoadOpt(src, "umask"); err != nil {
522 cfg.OmitPrgrs = cfgDirExists(src, "noprogress")
523 cfg.NoHdr = cfgDirExists(src, "nohdr")
525 sp, err := cfgDirLoadOpt(src, "mcd-listen")
530 cfg.MCDRxIfis = strings.Split(*sp, "\n")
533 fis, err := os.ReadDir(filepath.Join(src, "mcd-send"))
534 if err != nil && !os.IsNotExist(err) {
538 cfg.MCDTxIfis = make(map[string]int, len(fis))
540 for _, fi := range fis {
545 b, err := os.ReadFile(filepath.Join(src, "mcd-send", fi.Name()))
549 i, err := strconv.Atoi(strings.TrimSuffix(string(b), "\n"))
556 notify := NotifyJSON{Exec: make(map[string]*FromToJSON)}
557 if cfgDirExists(src, "notify", "file") {
558 if notify.File, err = cfgDirReadFromTo(src, "notify", "file"); err != nil {
562 if cfgDirExists(src, "notify", "freq") {
563 if notify.Freq, err = cfgDirReadFromTo(src, "notify", "freq"); err != nil {
567 fis, err = os.ReadDir(filepath.Join(src, "notify", "exec"))
568 if err != nil && !os.IsNotExist(err) {
571 for _, fi := range fis {
573 if n[0] == '.' || !fi.IsDir() {
576 if notify.Exec[fi.Name()], err = cfgDirReadFromTo(src, "notify", "exec", n); err != nil {
580 if notify.File != nil || notify.Freq != nil || len(notify.Exec) > 0 {
584 if _, err = os.ReadDir(filepath.Join(src, "self")); err == nil {
585 self := NodeOurJSON{}
586 if self.Id, err = cfgDirLoadMust(src, "self", "id"); err != nil {
589 if self.ExchPub, err = cfgDirLoadMust(src, "self", "exchpub"); err != nil {
592 if self.ExchPrv, err = cfgDirLoadMust(src, "self", "exchprv"); err != nil {
595 if self.SignPub, err = cfgDirLoadMust(src, "self", "signpub"); err != nil {
598 if self.SignPrv, err = cfgDirLoadMust(src, "self", "signprv"); err != nil {
601 if self.NoisePub, err = cfgDirLoadMust(src, "self", "noisepub"); err != nil {
604 if self.NoisePrv, err = cfgDirLoadMust(src, "self", "noiseprv"); err != nil {
608 } else if !os.IsNotExist(err) {
612 cfg.Neigh = make(map[string]NodeJSON)
613 fis, err = os.ReadDir(filepath.Join(src, "neigh"))
614 if err != nil && !os.IsNotExist(err) {
617 for _, fi := range fis {
623 if node.Id, err = cfgDirLoadMust(src, "neigh", n, "id"); err != nil {
626 if node.ExchPub, err = cfgDirLoadMust(src, "neigh", n, "exchpub"); err != nil {
629 if node.SignPub, err = cfgDirLoadMust(src, "neigh", n, "signpub"); err != nil {
632 if node.NoisePub, err = cfgDirLoadOpt(src, "neigh", n, "noisepub"); err != nil {
635 if node.Incoming, err = cfgDirLoadOpt(src, "neigh", n, "incoming"); err != nil {
639 node.Exec = make(map[string][]string)
640 fis2, err := os.ReadDir(filepath.Join(src, "neigh", n, "exec"))
641 if err != nil && !os.IsNotExist(err) {
644 for _, fi2 := range fis2 {
649 s, err := cfgDirLoadMust(src, "neigh", n, "exec", n2)
653 node.Exec[n2] = strings.Split(s, "\n")
656 if cfgDirExists(src, "neigh", n, "freq") {
657 node.Freq = &NodeFreqJSON{}
658 if node.Freq.Path, err = cfgDirLoadOpt(src, "neigh", n, "freq", "path"); err != nil {
662 i64, err := cfgDirLoadIntOpt(src, "neigh", n, "freq", "chunked")
668 node.Freq.Chunked = &i
671 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "freq", "minsize")
677 node.Freq.MinSize = &i
680 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "freq", "maxsize")
686 node.Freq.MaxSize = &i
690 via, err := cfgDirLoadOpt(src, "neigh", n, "via")
695 node.Via = strings.Split(*via, "\n")
698 node.Addrs = make(map[string]string)
699 fis2, err = os.ReadDir(filepath.Join(src, "neigh", n, "addrs"))
700 if err != nil && !os.IsNotExist(err) {
703 for _, fi2 := range fis2 {
708 if node.Addrs[n2], err = cfgDirLoadMust(src, "neigh", n, "addrs", n2); err != nil {
713 i64, err := cfgDirLoadIntOpt(src, "neigh", n, "rxrate")
722 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "txrate")
731 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "onlinedeadline")
737 node.OnlineDeadline = &i
740 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "maxonlinetime")
746 node.MaxOnlineTime = &i
749 fis2, err = os.ReadDir(filepath.Join(src, "neigh", n, "calls"))
750 if err != nil && !os.IsNotExist(err) {
753 callsIdx := make([]int, 0, len(fis2))
754 for _, fi2 := range fis2 {
759 i, err := strconv.Atoi(n2)
763 callsIdx = append(callsIdx, i)
766 for _, i := range callsIdx {
768 is := strconv.Itoa(i)
769 if call.Cron, err = cfgDirLoadMust(
770 src, "neigh", n, "calls", is, "cron",
774 if call.Nice, err = cfgDirLoadOpt(
775 src, "neigh", n, "calls", is, "nice",
779 if call.Xx, err = cfgDirLoadOpt(
780 src, "neigh", n, "calls", is, "xx",
785 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "calls", is, "rxrate")
794 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "calls", is, "txrate")
803 if call.Addr, err = cfgDirLoadOpt(
804 src, "neigh", n, "calls", is, "addr",
809 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "calls", is, "onlinedeadline")
815 call.OnlineDeadline = &i
818 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "calls", is, "maxonlinetime")
824 call.MaxOnlineTime = &i
827 if cfgDirExists(src, "neigh", n, "calls", is, "when-tx-exists") {
828 call.WhenTxExists = true
830 if cfgDirExists(src, "neigh", n, "calls", is, "nock") {
833 if cfgDirExists(src, "neigh", n, "calls", is, "mcd-ignore") {
834 call.MCDIgnore = true
836 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss") {
839 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-doseen") {
840 call.AutoTossDoSeen = true
842 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-nofile") {
843 call.AutoTossNoFile = true
845 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-nofreq") {
846 call.AutoTossNoFreq = true
848 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-noexec") {
849 call.AutoTossNoExec = true
851 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-notrns") {
852 call.AutoTossNoTrns = true
854 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-noarea") {
855 call.AutoTossNoArea = true
857 node.Calls = append(node.Calls, call)
862 cfg.Areas = make(map[string]AreaJSON)
863 fis, err = os.ReadDir(filepath.Join(src, "areas"))
864 if err != nil && !os.IsNotExist(err) {
867 for _, fi := range fis {
873 if area.Id, err = cfgDirLoadMust(src, "areas", n, "id"); err != nil {
876 if area.Pub, err = cfgDirLoadOpt(src, "areas", n, "pub"); err != nil {
879 if area.Prv, err = cfgDirLoadOpt(src, "areas", n, "prv"); err != nil {
883 subs, err := cfgDirLoadOpt(src, "areas", n, "subs")
888 area.Subs = strings.Split(*subs, "\n")
891 area.Exec = make(map[string][]string)
892 fis2, err := os.ReadDir(filepath.Join(src, "areas", n, "exec"))
893 if err != nil && !os.IsNotExist(err) {
896 for _, fi2 := range fis2 {
901 s, err := cfgDirLoadMust(src, "areas", n, "exec", n2)
905 area.Exec[n2] = strings.Split(s, "\n")
908 if area.Incoming, err = cfgDirLoadOpt(src, "areas", n, "incoming"); err != nil {
912 if cfgDirExists(src, "areas", n, "allow-unknown") {
913 area.AllowUnknown = true
918 fis, err = os.ReadDir(filepath.Join(src, "yggdrasil-aliases"))
919 if err != nil && !os.IsNotExist(err) {
923 cfg.YggdrasilAliases = make(map[string]string, len(fis))
925 for _, fi := range fis {
930 b, err := os.ReadFile(filepath.Join(src, "yggdrasil-aliases", fi.Name()))
934 cfg.YggdrasilAliases[n] = strings.TrimSuffix(string(b), "\n")