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/>.
31 func cfgDirMkdir(dst ...string) error {
32 return os.MkdirAll(filepath.Join(dst...), os.FileMode(0777))
35 func cfgDirSave(v interface{}, dst ...string) error {
37 switch v := v.(type) {
54 r = strconv.Itoa(int(*v))
59 r = strconv.FormatUint(*v, 10)
63 panic("unsupported value type")
65 mode := os.FileMode(0666)
66 if strings.HasSuffix(dst[len(dst)-1], "prv") {
67 mode = os.FileMode(0600)
69 return os.WriteFile(filepath.Join(dst...), []byte(r+"\n"), mode)
72 func cfgDirTouch(dst ...string) error {
73 if fd, err := os.Create(filepath.Join(dst...)); err == nil {
81 func CfgToDir(dst string, cfg *CfgJSON) (err error) {
82 if err = cfgDirMkdir(dst); err != nil {
85 if err = cfgDirSave(cfg.Spool, dst, "spool"); err != nil {
88 if err = cfgDirSave(cfg.Log, dst, "log"); err != nil {
91 if err = cfgDirSave(cfg.Umask, dst, "umask"); err != nil {
95 if err = cfgDirTouch(dst, "noprogress"); err != nil {
100 if err = cfgDirTouch(dst, "nohdr"); err != nil {
105 if len(cfg.MCDRxIfis) > 0 {
107 strings.Join(cfg.MCDRxIfis, "\n"),
113 if len(cfg.MCDTxIfis) > 0 {
114 if err = cfgDirMkdir(dst, "mcd-send"); err != nil {
117 for ifi, t := range cfg.MCDTxIfis {
118 if err = cfgDirSave(t, dst, "mcd-send", ifi); err != nil {
124 if cfg.Notify != nil {
125 if cfg.Notify.File != nil {
126 if err = cfgDirMkdir(dst, "notify", "file"); err != nil {
130 cfg.Notify.File.From,
131 dst, "notify", "file", "from",
137 dst, "notify", "file", "to",
142 if cfg.Notify.Freq != nil {
143 if err = cfgDirMkdir(dst, "notify", "freq"); err != nil {
147 cfg.Notify.Freq.From,
148 dst, "notify", "freq", "from",
154 dst, "notify", "freq", "to",
159 for k, v := range cfg.Notify.Exec {
160 if err = cfgDirMkdir(dst, "notify", "exec", k); err != nil {
163 if err = cfgDirSave(v.From, dst, "notify", "exec", k, "from"); err != nil {
166 if err = cfgDirSave(v.To, dst, "notify", "exec", k, "to"); err != nil {
173 if err = cfgDirMkdir(dst, "self"); err != nil {
176 if err = cfgDirSave(cfg.Self.Id, dst, "self", "id"); err != nil {
179 if err = cfgDirSave(cfg.Self.ExchPub, dst, "self", "exchpub"); err != nil {
182 if err = cfgDirSave(cfg.Self.ExchPrv, dst, "self", "exchprv"); err != nil {
185 if err = cfgDirSave(cfg.Self.SignPub, dst, "self", "signpub"); err != nil {
188 if err = cfgDirSave(cfg.Self.SignPrv, dst, "self", "signprv"); err != nil {
191 if err = cfgDirSave(cfg.Self.NoisePub, dst, "self", "noisepub"); err != nil {
194 if err = cfgDirSave(cfg.Self.NoisePrv, dst, "self", "noiseprv"); err != nil {
199 for name, n := range cfg.Neigh {
200 if err = cfgDirMkdir(dst, "neigh", name); err != nil {
203 if err = cfgDirSave(n.Id, dst, "neigh", name, "id"); err != nil {
206 if err = cfgDirSave(n.ExchPub, dst, "neigh", name, "exchpub"); err != nil {
209 if err = cfgDirSave(n.SignPub, dst, "neigh", name, "signpub"); err != nil {
212 if err = cfgDirSave(n.NoisePub, dst, "neigh", name, "noisepub"); err != nil {
215 if err = cfgDirSave(n.Incoming, dst, "neigh", name, "incoming"); err != nil {
220 if err = cfgDirMkdir(dst, "neigh", name, "exec"); err != nil {
223 for k, v := range n.Exec {
225 strings.Join(v, "\n"),
226 dst, "neigh", name, "exec", k,
234 if err = cfgDirMkdir(dst, "neigh", name, "freq"); err != nil {
239 dst, "neigh", name, "freq", "path",
245 dst, "neigh", name, "freq", "chunked",
251 dst, "neigh", name, "freq", "minsize",
257 dst, "neigh", name, "freq", "maxsize",
265 strings.Join(n.Via, "\n"),
266 dst, "neigh", name, "via",
272 if len(n.Addrs) > 0 {
273 if err = cfgDirMkdir(dst, "neigh", name, "addrs"); err != nil {
276 for k, v := range n.Addrs {
277 if err = cfgDirSave(v, dst, "neigh", name, "addrs", k); err != nil {
283 if err = cfgDirSave(n.RxRate, dst, "neigh", name, "rxrate"); err != nil {
286 if err = cfgDirSave(n.TxRate, dst, "neigh", name, "txrate"); err != nil {
289 if err = cfgDirSave(n.OnlineDeadline, dst, "neigh", name, "onlinedeadline"); err != nil {
292 if err = cfgDirSave(n.MaxOnlineTime, dst, "neigh", name, "maxonlinetime"); err != nil {
296 for i, call := range n.Calls {
297 is := strconv.Itoa(i)
298 if err = cfgDirMkdir(dst, "neigh", name, "calls", is); err != nil {
301 if err = cfgDirSave(call.Cron, dst, "neigh", name, "calls", is, "cron"); err != nil {
304 if err = cfgDirSave(call.Nice, dst, "neigh", name, "calls", is, "nice"); err != nil {
307 if err = cfgDirSave(call.Xx, dst, "neigh", name, "calls", is, "xx"); err != nil {
310 if err = cfgDirSave(call.RxRate, dst, "neigh", name, "calls", is, "rxrate"); err != nil {
313 if err = cfgDirSave(call.TxRate, dst, "neigh", name, "calls", is, "txrate"); err != nil {
316 if err = cfgDirSave(call.Addr, dst, "neigh", name, "calls", is, "addr"); err != nil {
319 if err = cfgDirSave(call.OnlineDeadline, dst, "neigh", name, "calls", is, "onlinedeadline"); err != nil {
322 if err = cfgDirSave(call.MaxOnlineTime, dst, "neigh", name, "calls", is, "maxonlinetime"); err != nil {
325 if call.WhenTxExists {
326 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "when-tx-exists"); err != nil {
331 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "nock"); err != nil {
336 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "mcd-ignore"); err != nil {
341 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss"); err != nil {
345 if call.AutoTossDoSeen {
346 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-doseen"); err != nil {
350 if call.AutoTossNoFile {
351 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-nofile"); err != nil {
355 if call.AutoTossNoFreq {
356 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-nofreq"); err != nil {
360 if call.AutoTossNoExec {
361 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-noexec"); err != nil {
365 if call.AutoTossNoTrns {
366 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-notrns"); err != nil {
370 if call.AutoTossNoArea {
371 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-noarea"); err != nil {
378 for name, a := range cfg.Areas {
379 if err = cfgDirMkdir(dst, "areas", name); err != nil {
382 if err = cfgDirSave(a.Id, dst, "areas", name, "id"); err != nil {
385 if err = cfgDirSave(a.Pub, dst, "areas", name, "pub"); err != nil {
388 if err = cfgDirSave(a.Prv, dst, "areas", name, "prv"); err != nil {
391 if err = cfgDirSave(a.Incoming, dst, "areas", name, "incoming"); err != nil {
395 if err = cfgDirTouch(dst, "areas", name, "allow-unknown"); err != nil {
400 if err = cfgDirMkdir(dst, "areas", name, "exec"); err != nil {
403 for k, v := range a.Exec {
405 strings.Join(v, "\n"),
406 dst, "areas", name, "exec", k,
414 strings.Join(a.Subs, "\n"),
415 dst, "areas", name, "subs",
422 if len(cfg.YggdrasilAliases) > 0 {
423 if err = cfgDirMkdir(dst, "yggdrasil-aliases"); err != nil {
426 for alias, v := range cfg.YggdrasilAliases {
427 if err = cfgDirSave(v, dst, "yggdrasil-aliases", alias); err != nil {
436 func cfgDirLoad(src ...string) (v string, exists bool, err error) {
437 b, err := os.ReadFile(filepath.Join(src...))
439 if errors.Is(err, fs.ErrNotExist) {
440 return "", false, nil
442 return "", false, err
444 return strings.TrimSuffix(string(b), "\n"), true, nil
447 func cfgDirLoadMust(src ...string) (v string, err error) {
448 s, exists, err := cfgDirLoad(src...)
453 return "", fmt.Errorf("required \"%s\" does not exist", src[len(src)-1])
458 func cfgDirLoadOpt(src ...string) (v *string, err error) {
459 s, exists, err := cfgDirLoad(src...)
469 func cfgDirLoadIntOpt(src ...string) (i64 *int64, err error) {
470 s, err := cfgDirLoadOpt(src...)
477 i, err := strconv.ParseInt(*s, 10, 64)
484 func cfgDirExists(src ...string) bool {
485 if _, err := os.Stat(filepath.Join(src...)); err == nil {
491 func cfgDirReadFromTo(src ...string) (*FromToJSON, error) {
492 fromTo := FromToJSON{}
495 fromTo.From, err = cfgDirLoadMust(append(src, "from")...)
500 fromTo.To, err = cfgDirLoadMust(append(src, "to")...)
508 func DirToCfg(src string) (*CfgJSON, error) {
512 cfg.Spool, err = cfgDirLoadMust(src, "spool")
516 cfg.Log, err = cfgDirLoadMust(src, "log")
521 if cfg.Umask, err = cfgDirLoadOpt(src, "umask"); err != nil {
524 cfg.OmitPrgrs = cfgDirExists(src, "noprogress")
525 cfg.NoHdr = cfgDirExists(src, "nohdr")
527 sp, err := cfgDirLoadOpt(src, "mcd-listen")
532 cfg.MCDRxIfis = strings.Split(*sp, "\n")
535 fis, err := os.ReadDir(filepath.Join(src, "mcd-send"))
536 if err != nil && !errors.Is(err, fs.ErrNotExist) {
540 cfg.MCDTxIfis = make(map[string]int, len(fis))
542 for _, fi := range fis {
547 b, err := os.ReadFile(filepath.Join(src, "mcd-send", fi.Name()))
551 i, err := strconv.Atoi(strings.TrimSuffix(string(b), "\n"))
558 notify := NotifyJSON{Exec: make(map[string]*FromToJSON)}
559 if cfgDirExists(src, "notify", "file") {
560 if notify.File, err = cfgDirReadFromTo(src, "notify", "file"); err != nil {
564 if cfgDirExists(src, "notify", "freq") {
565 if notify.Freq, err = cfgDirReadFromTo(src, "notify", "freq"); err != nil {
569 fis, err = os.ReadDir(filepath.Join(src, "notify", "exec"))
570 if err != nil && !errors.Is(err, fs.ErrNotExist) {
573 for _, fi := range fis {
575 if n[0] == '.' || !fi.IsDir() {
578 if notify.Exec[fi.Name()], err = cfgDirReadFromTo(src, "notify", "exec", n); err != nil {
582 if notify.File != nil || notify.Freq != nil || len(notify.Exec) > 0 {
586 if _, err = os.ReadDir(filepath.Join(src, "self")); err == nil {
587 self := NodeOurJSON{}
588 if self.Id, err = cfgDirLoadMust(src, "self", "id"); err != nil {
591 if self.ExchPub, err = cfgDirLoadMust(src, "self", "exchpub"); err != nil {
594 if self.ExchPrv, err = cfgDirLoadMust(src, "self", "exchprv"); err != nil {
597 if self.SignPub, err = cfgDirLoadMust(src, "self", "signpub"); err != nil {
600 if self.SignPrv, err = cfgDirLoadMust(src, "self", "signprv"); err != nil {
603 if self.NoisePub, err = cfgDirLoadMust(src, "self", "noisepub"); err != nil {
606 if self.NoisePrv, err = cfgDirLoadMust(src, "self", "noiseprv"); err != nil {
610 } else if !errors.Is(err, fs.ErrNotExist) {
614 cfg.Neigh = make(map[string]NodeJSON)
615 fis, err = os.ReadDir(filepath.Join(src, "neigh"))
616 if err != nil && !errors.Is(err, fs.ErrNotExist) {
619 for _, fi := range fis {
625 if node.Id, err = cfgDirLoadMust(src, "neigh", n, "id"); err != nil {
628 if node.ExchPub, err = cfgDirLoadMust(src, "neigh", n, "exchpub"); err != nil {
631 if node.SignPub, err = cfgDirLoadMust(src, "neigh", n, "signpub"); err != nil {
634 if node.NoisePub, err = cfgDirLoadOpt(src, "neigh", n, "noisepub"); err != nil {
637 if node.Incoming, err = cfgDirLoadOpt(src, "neigh", n, "incoming"); err != nil {
641 node.Exec = make(map[string][]string)
642 fis2, err := os.ReadDir(filepath.Join(src, "neigh", n, "exec"))
643 if err != nil && !errors.Is(err, fs.ErrNotExist) {
646 for _, fi2 := range fis2 {
651 s, err := cfgDirLoadMust(src, "neigh", n, "exec", n2)
655 node.Exec[n2] = strings.Split(s, "\n")
658 if cfgDirExists(src, "neigh", n, "freq") {
659 node.Freq = &NodeFreqJSON{}
660 if node.Freq.Path, err = cfgDirLoadOpt(src, "neigh", n, "freq", "path"); err != nil {
664 i64, err := cfgDirLoadIntOpt(src, "neigh", n, "freq", "chunked")
670 node.Freq.Chunked = &i
673 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "freq", "minsize")
679 node.Freq.MinSize = &i
682 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "freq", "maxsize")
688 node.Freq.MaxSize = &i
692 via, err := cfgDirLoadOpt(src, "neigh", n, "via")
697 node.Via = strings.Split(*via, "\n")
700 node.Addrs = make(map[string]string)
701 fis2, err = os.ReadDir(filepath.Join(src, "neigh", n, "addrs"))
702 if err != nil && !errors.Is(err, fs.ErrNotExist) {
705 for _, fi2 := range fis2 {
710 if node.Addrs[n2], err = cfgDirLoadMust(src, "neigh", n, "addrs", n2); err != nil {
715 i64, err := cfgDirLoadIntOpt(src, "neigh", n, "rxrate")
724 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "txrate")
733 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "onlinedeadline")
739 node.OnlineDeadline = &i
742 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "maxonlinetime")
748 node.MaxOnlineTime = &i
751 fis2, err = os.ReadDir(filepath.Join(src, "neigh", n, "calls"))
752 if err != nil && !errors.Is(err, fs.ErrNotExist) {
755 callsIdx := make([]int, 0, len(fis2))
756 for _, fi2 := range fis2 {
761 i, err := strconv.Atoi(n2)
765 callsIdx = append(callsIdx, i)
768 for _, i := range callsIdx {
770 is := strconv.Itoa(i)
771 if call.Cron, err = cfgDirLoadMust(
772 src, "neigh", n, "calls", is, "cron",
776 if call.Nice, err = cfgDirLoadOpt(
777 src, "neigh", n, "calls", is, "nice",
781 if call.Xx, err = cfgDirLoadOpt(
782 src, "neigh", n, "calls", is, "xx",
787 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "calls", is, "rxrate")
796 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "calls", is, "txrate")
805 if call.Addr, err = cfgDirLoadOpt(
806 src, "neigh", n, "calls", is, "addr",
811 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "calls", is, "onlinedeadline")
817 call.OnlineDeadline = &i
820 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "calls", is, "maxonlinetime")
826 call.MaxOnlineTime = &i
829 if cfgDirExists(src, "neigh", n, "calls", is, "when-tx-exists") {
830 call.WhenTxExists = true
832 if cfgDirExists(src, "neigh", n, "calls", is, "nock") {
835 if cfgDirExists(src, "neigh", n, "calls", is, "mcd-ignore") {
836 call.MCDIgnore = true
838 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss") {
841 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-doseen") {
842 call.AutoTossDoSeen = true
844 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-nofile") {
845 call.AutoTossNoFile = true
847 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-nofreq") {
848 call.AutoTossNoFreq = true
850 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-noexec") {
851 call.AutoTossNoExec = true
853 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-notrns") {
854 call.AutoTossNoTrns = true
856 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-noarea") {
857 call.AutoTossNoArea = true
859 node.Calls = append(node.Calls, call)
864 cfg.Areas = make(map[string]AreaJSON)
865 fis, err = os.ReadDir(filepath.Join(src, "areas"))
866 if err != nil && !errors.Is(err, fs.ErrNotExist) {
869 for _, fi := range fis {
875 if area.Id, err = cfgDirLoadMust(src, "areas", n, "id"); err != nil {
878 if area.Pub, err = cfgDirLoadOpt(src, "areas", n, "pub"); err != nil {
881 if area.Prv, err = cfgDirLoadOpt(src, "areas", n, "prv"); err != nil {
885 subs, err := cfgDirLoadOpt(src, "areas", n, "subs")
890 area.Subs = strings.Split(*subs, "\n")
893 area.Exec = make(map[string][]string)
894 fis2, err := os.ReadDir(filepath.Join(src, "areas", n, "exec"))
895 if err != nil && !errors.Is(err, fs.ErrNotExist) {
898 for _, fi2 := range fis2 {
903 s, err := cfgDirLoadMust(src, "areas", n, "exec", n2)
907 area.Exec[n2] = strings.Split(s, "\n")
910 if area.Incoming, err = cfgDirLoadOpt(src, "areas", n, "incoming"); err != nil {
914 if cfgDirExists(src, "areas", n, "allow-unknown") {
915 area.AllowUnknown = true
920 fis, err = os.ReadDir(filepath.Join(src, "yggdrasil-aliases"))
921 if err != nil && !errors.Is(err, fs.ErrNotExist) {
925 cfg.YggdrasilAliases = make(map[string]string, len(fis))
927 for _, fi := range fis {
932 b, err := os.ReadFile(filepath.Join(src, "yggdrasil-aliases", fi.Name()))
936 cfg.YggdrasilAliases[n] = strings.TrimSuffix(string(b), "\n")