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",
264 if err = cfgDirMkdir(dst, "neigh", name, "ack"); err != nil {
269 dst, "neigh", name, "ack", "minsize",
275 dst, "neigh", name, "ack", "nice",
283 strings.Join(n.Via, "\n"),
284 dst, "neigh", name, "via",
290 if len(n.Addrs) > 0 {
291 if err = cfgDirMkdir(dst, "neigh", name, "addrs"); err != nil {
294 for k, v := range n.Addrs {
295 if err = cfgDirSave(v, dst, "neigh", name, "addrs", k); err != nil {
301 if err = cfgDirSave(n.RxRate, dst, "neigh", name, "rxrate"); err != nil {
304 if err = cfgDirSave(n.TxRate, dst, "neigh", name, "txrate"); err != nil {
307 if err = cfgDirSave(n.OnlineDeadline, dst, "neigh", name, "onlinedeadline"); err != nil {
310 if err = cfgDirSave(n.MaxOnlineTime, dst, "neigh", name, "maxonlinetime"); err != nil {
314 for i, call := range n.Calls {
315 is := strconv.Itoa(i)
316 if err = cfgDirMkdir(dst, "neigh", name, "calls", is); err != nil {
319 if err = cfgDirSave(call.Cron, dst, "neigh", name, "calls", is, "cron"); err != nil {
322 if err = cfgDirSave(call.Nice, dst, "neigh", name, "calls", is, "nice"); err != nil {
325 if err = cfgDirSave(call.Xx, dst, "neigh", name, "calls", is, "xx"); err != nil {
328 if err = cfgDirSave(call.RxRate, dst, "neigh", name, "calls", is, "rxrate"); err != nil {
331 if err = cfgDirSave(call.TxRate, dst, "neigh", name, "calls", is, "txrate"); err != nil {
334 if err = cfgDirSave(call.Addr, dst, "neigh", name, "calls", is, "addr"); err != nil {
337 if err = cfgDirSave(call.OnlineDeadline, dst, "neigh", name, "calls", is, "onlinedeadline"); err != nil {
340 if err = cfgDirSave(call.MaxOnlineTime, dst, "neigh", name, "calls", is, "maxonlinetime"); err != nil {
343 if call.WhenTxExists {
344 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "when-tx-exists"); err != nil {
349 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "nock"); err != nil {
354 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "mcd-ignore"); err != nil {
359 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss"); err != nil {
363 if call.AutoTossDoSeen {
364 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-doseen"); err != nil {
368 if call.AutoTossNoFile {
369 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-nofile"); err != nil {
373 if call.AutoTossNoFreq {
374 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-nofreq"); err != nil {
378 if call.AutoTossNoExec {
379 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-noexec"); err != nil {
383 if call.AutoTossNoTrns {
384 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-notrns"); err != nil {
388 if call.AutoTossNoArea {
389 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-noarea"); err != nil {
393 if call.AutoTossNoACK {
394 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-noack"); err != nil {
398 if call.AutoTossGenACK {
399 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-gen-ack"); err != nil {
406 for name, a := range cfg.Areas {
407 if err = cfgDirMkdir(dst, "areas", name); err != nil {
410 if err = cfgDirSave(a.Id, dst, "areas", name, "id"); err != nil {
413 if err = cfgDirSave(a.Pub, dst, "areas", name, "pub"); err != nil {
416 if err = cfgDirSave(a.Prv, dst, "areas", name, "prv"); err != nil {
419 if err = cfgDirSave(a.Incoming, dst, "areas", name, "incoming"); err != nil {
423 if err = cfgDirTouch(dst, "areas", name, "allow-unknown"); err != nil {
428 if err = cfgDirMkdir(dst, "areas", name, "exec"); err != nil {
431 for k, v := range a.Exec {
433 strings.Join(v, "\n"),
434 dst, "areas", name, "exec", k,
442 strings.Join(a.Subs, "\n"),
443 dst, "areas", name, "subs",
450 if len(cfg.YggdrasilAliases) > 0 {
451 if err = cfgDirMkdir(dst, "yggdrasil-aliases"); err != nil {
454 for alias, v := range cfg.YggdrasilAliases {
455 if err = cfgDirSave(v, dst, "yggdrasil-aliases", alias); err != nil {
464 func cfgDirLoad(src ...string) (v string, exists bool, err error) {
465 b, err := os.ReadFile(filepath.Join(src...))
467 if errors.Is(err, fs.ErrNotExist) {
468 return "", false, nil
470 return "", false, err
472 return strings.TrimSuffix(string(b), "\n"), true, nil
475 func cfgDirLoadMust(src ...string) (v string, err error) {
476 s, exists, err := cfgDirLoad(src...)
481 return "", fmt.Errorf("required \"%s\" does not exist", src[len(src)-1])
486 func cfgDirLoadOpt(src ...string) (v *string, err error) {
487 s, exists, err := cfgDirLoad(src...)
497 func cfgDirLoadIntOpt(src ...string) (i64 *int64, err error) {
498 s, err := cfgDirLoadOpt(src...)
505 i, err := strconv.ParseInt(*s, 10, 64)
512 func cfgDirExists(src ...string) bool {
513 if _, err := os.Stat(filepath.Join(src...)); err == nil {
519 func cfgDirReadFromTo(src ...string) (*FromToJSON, error) {
520 fromTo := FromToJSON{}
523 fromTo.From, err = cfgDirLoadMust(append(src, "from")...)
528 fromTo.To, err = cfgDirLoadMust(append(src, "to")...)
536 func DirToCfg(src string) (*CfgJSON, error) {
540 cfg.Spool, err = cfgDirLoadMust(src, "spool")
544 cfg.Log, err = cfgDirLoadMust(src, "log")
549 if cfg.Umask, err = cfgDirLoadOpt(src, "umask"); err != nil {
552 cfg.OmitPrgrs = cfgDirExists(src, "noprogress")
553 cfg.NoHdr = cfgDirExists(src, "nohdr")
555 sp, err := cfgDirLoadOpt(src, "mcd-listen")
560 cfg.MCDRxIfis = strings.Split(*sp, "\n")
563 fis, err := os.ReadDir(filepath.Join(src, "mcd-send"))
564 if err != nil && !errors.Is(err, fs.ErrNotExist) {
568 cfg.MCDTxIfis = make(map[string]int, len(fis))
570 for _, fi := range fis {
575 b, err := os.ReadFile(filepath.Join(src, "mcd-send", fi.Name()))
579 i, err := strconv.Atoi(strings.TrimSuffix(string(b), "\n"))
586 notify := NotifyJSON{Exec: make(map[string]*FromToJSON)}
587 if cfgDirExists(src, "notify", "file") {
588 if notify.File, err = cfgDirReadFromTo(src, "notify", "file"); err != nil {
592 if cfgDirExists(src, "notify", "freq") {
593 if notify.Freq, err = cfgDirReadFromTo(src, "notify", "freq"); err != nil {
597 fis, err = os.ReadDir(filepath.Join(src, "notify", "exec"))
598 if err != nil && !errors.Is(err, fs.ErrNotExist) {
601 for _, fi := range fis {
603 if n[0] == '.' || !fi.IsDir() {
606 if notify.Exec[fi.Name()], err = cfgDirReadFromTo(src, "notify", "exec", n); err != nil {
610 if notify.File != nil || notify.Freq != nil || len(notify.Exec) > 0 {
614 if _, err = os.ReadDir(filepath.Join(src, "self")); err == nil {
615 self := NodeOurJSON{}
616 if self.Id, err = cfgDirLoadMust(src, "self", "id"); err != nil {
619 if self.ExchPub, err = cfgDirLoadMust(src, "self", "exchpub"); err != nil {
622 if self.ExchPrv, err = cfgDirLoadMust(src, "self", "exchprv"); err != nil {
625 if self.SignPub, err = cfgDirLoadMust(src, "self", "signpub"); err != nil {
628 if self.SignPrv, err = cfgDirLoadMust(src, "self", "signprv"); err != nil {
631 if self.NoisePub, err = cfgDirLoadMust(src, "self", "noisepub"); err != nil {
634 if self.NoisePrv, err = cfgDirLoadMust(src, "self", "noiseprv"); err != nil {
638 } else if !errors.Is(err, fs.ErrNotExist) {
642 cfg.Neigh = make(map[string]NodeJSON)
643 fis, err = os.ReadDir(filepath.Join(src, "neigh"))
644 if err != nil && !errors.Is(err, fs.ErrNotExist) {
647 for _, fi := range fis {
653 if node.Id, err = cfgDirLoadMust(src, "neigh", n, "id"); err != nil {
656 if node.ExchPub, err = cfgDirLoadMust(src, "neigh", n, "exchpub"); err != nil {
659 if node.SignPub, err = cfgDirLoadMust(src, "neigh", n, "signpub"); err != nil {
662 if node.NoisePub, err = cfgDirLoadOpt(src, "neigh", n, "noisepub"); err != nil {
665 if node.Incoming, err = cfgDirLoadOpt(src, "neigh", n, "incoming"); err != nil {
669 node.Exec = make(map[string][]string)
670 fis2, err := os.ReadDir(filepath.Join(src, "neigh", n, "exec"))
671 if err != nil && !errors.Is(err, fs.ErrNotExist) {
674 for _, fi2 := range fis2 {
679 s, err := cfgDirLoadMust(src, "neigh", n, "exec", n2)
683 node.Exec[n2] = strings.Split(s, "\n")
686 if cfgDirExists(src, "neigh", n, "freq") {
687 node.Freq = &NodeFreqJSON{}
688 if node.Freq.Path, err = cfgDirLoadOpt(src, "neigh", n, "freq", "path"); err != nil {
692 i64, err := cfgDirLoadIntOpt(src, "neigh", n, "freq", "chunked")
698 node.Freq.Chunked = &i
701 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "freq", "minsize")
707 node.Freq.MinSize = &i
710 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "freq", "maxsize")
716 node.Freq.MaxSize = &i
720 if cfgDirExists(src, "neigh", n, "ack") {
721 node.ACK = &NodeACKJSON{}
722 i64, err := cfgDirLoadIntOpt(src, "neigh", n, "ack", "minsize")
728 node.ACK.MinSize = &i
730 if node.ACK.Nice, err = cfgDirLoadOpt(
731 src, "neigh", n, "ack", "nice",
737 via, err := cfgDirLoadOpt(src, "neigh", n, "via")
742 node.Via = strings.Split(*via, "\n")
745 node.Addrs = make(map[string]string)
746 fis2, err = os.ReadDir(filepath.Join(src, "neigh", n, "addrs"))
747 if err != nil && !errors.Is(err, fs.ErrNotExist) {
750 for _, fi2 := range fis2 {
755 if node.Addrs[n2], err = cfgDirLoadMust(src, "neigh", n, "addrs", n2); err != nil {
760 i64, err := cfgDirLoadIntOpt(src, "neigh", n, "rxrate")
769 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "txrate")
778 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "onlinedeadline")
784 node.OnlineDeadline = &i
787 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "maxonlinetime")
793 node.MaxOnlineTime = &i
796 fis2, err = os.ReadDir(filepath.Join(src, "neigh", n, "calls"))
797 if err != nil && !errors.Is(err, fs.ErrNotExist) {
800 callsIdx := make([]int, 0, len(fis2))
801 for _, fi2 := range fis2 {
806 i, err := strconv.Atoi(n2)
810 callsIdx = append(callsIdx, i)
813 for _, i := range callsIdx {
815 is := strconv.Itoa(i)
816 if call.Cron, err = cfgDirLoadMust(
817 src, "neigh", n, "calls", is, "cron",
821 if call.Nice, err = cfgDirLoadOpt(
822 src, "neigh", n, "calls", is, "nice",
826 if call.Xx, err = cfgDirLoadOpt(
827 src, "neigh", n, "calls", is, "xx",
832 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "calls", is, "rxrate")
841 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "calls", is, "txrate")
850 if call.Addr, err = cfgDirLoadOpt(
851 src, "neigh", n, "calls", is, "addr",
856 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "calls", is, "onlinedeadline")
862 call.OnlineDeadline = &i
865 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "calls", is, "maxonlinetime")
871 call.MaxOnlineTime = &i
874 if cfgDirExists(src, "neigh", n, "calls", is, "when-tx-exists") {
875 call.WhenTxExists = true
877 if cfgDirExists(src, "neigh", n, "calls", is, "nock") {
880 if cfgDirExists(src, "neigh", n, "calls", is, "mcd-ignore") {
881 call.MCDIgnore = true
883 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss") {
886 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-doseen") {
887 call.AutoTossDoSeen = true
889 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-nofile") {
890 call.AutoTossNoFile = true
892 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-nofreq") {
893 call.AutoTossNoFreq = true
895 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-noexec") {
896 call.AutoTossNoExec = true
898 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-notrns") {
899 call.AutoTossNoTrns = true
901 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-noarea") {
902 call.AutoTossNoArea = true
904 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-noack") {
905 call.AutoTossNoACK = true
907 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-gen-ack") {
908 call.AutoTossGenACK = true
910 node.Calls = append(node.Calls, call)
915 cfg.Areas = make(map[string]AreaJSON)
916 fis, err = os.ReadDir(filepath.Join(src, "areas"))
917 if err != nil && !errors.Is(err, fs.ErrNotExist) {
920 for _, fi := range fis {
926 if area.Id, err = cfgDirLoadMust(src, "areas", n, "id"); err != nil {
929 if area.Pub, err = cfgDirLoadOpt(src, "areas", n, "pub"); err != nil {
932 if area.Prv, err = cfgDirLoadOpt(src, "areas", n, "prv"); err != nil {
936 subs, err := cfgDirLoadOpt(src, "areas", n, "subs")
941 area.Subs = strings.Split(*subs, "\n")
944 area.Exec = make(map[string][]string)
945 fis2, err := os.ReadDir(filepath.Join(src, "areas", n, "exec"))
946 if err != nil && !errors.Is(err, fs.ErrNotExist) {
949 for _, fi2 := range fis2 {
954 s, err := cfgDirLoadMust(src, "areas", n, "exec", n2)
958 area.Exec[n2] = strings.Split(s, "\n")
961 if area.Incoming, err = cfgDirLoadOpt(src, "areas", n, "incoming"); err != nil {
965 if cfgDirExists(src, "areas", n, "allow-unknown") {
966 area.AllowUnknown = true
971 fis, err = os.ReadDir(filepath.Join(src, "yggdrasil-aliases"))
972 if err != nil && !errors.Is(err, fs.ErrNotExist) {
976 cfg.YggdrasilAliases = make(map[string]string, len(fis))
978 for _, fi := range fis {
983 b, err := os.ReadFile(filepath.Join(src, "yggdrasil-aliases", fi.Name()))
987 cfg.YggdrasilAliases[n] = strings.TrimSuffix(string(b), "\n")