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",
421 if len(cfg.YggdrasilAliases) > 0 {
422 if err = cfgDirMkdir(dst, "yggdrasil-aliases"); err != nil {
425 for alias, v := range cfg.YggdrasilAliases {
426 if err = cfgDirSave(v, dst, "yggdrasil-aliases", alias); err != nil {
435 func cfgDirLoad(src ...string) (v string, exists bool, err error) {
436 b, err := ioutil.ReadFile(filepath.Join(src...))
438 if os.IsNotExist(err) {
439 return "", false, nil
441 return "", false, err
443 return strings.TrimSuffix(string(b), "\n"), true, nil
446 func cfgDirLoadMust(src ...string) (v string, err error) {
447 s, exists, err := cfgDirLoad(src...)
452 return "", fmt.Errorf("required \"%s\" does not exist", src[len(src)-1])
457 func cfgDirLoadOpt(src ...string) (v *string, err error) {
458 s, exists, err := cfgDirLoad(src...)
468 func cfgDirLoadIntOpt(src ...string) (i64 *int64, err error) {
469 s, err := cfgDirLoadOpt(src...)
476 i, err := strconv.ParseInt(*s, 10, 64)
483 func cfgDirExists(src ...string) bool {
484 if _, err := os.Stat(filepath.Join(src...)); err == nil {
490 func cfgDirReadFromTo(src ...string) (*FromToJSON, error) {
491 fromTo := FromToJSON{}
494 fromTo.From, err = cfgDirLoadMust(append(src, "from")...)
499 fromTo.To, err = cfgDirLoadMust(append(src, "to")...)
507 func DirToCfg(src string) (*CfgJSON, error) {
511 cfg.Spool, err = cfgDirLoadMust(src, "spool")
515 cfg.Log, err = cfgDirLoadMust(src, "log")
520 if cfg.Umask, err = cfgDirLoadOpt(src, "umask"); err != nil {
523 cfg.OmitPrgrs = cfgDirExists(src, "noprogress")
524 cfg.NoHdr = cfgDirExists(src, "nohdr")
526 sp, err := cfgDirLoadOpt(src, "mcd-listen")
531 cfg.MCDRxIfis = strings.Split(*sp, "\n")
534 fis, err := ioutil.ReadDir(filepath.Join(src, "mcd-send"))
535 if err != nil && !os.IsNotExist(err) {
539 cfg.MCDTxIfis = make(map[string]int, len(fis))
541 for _, fi := range fis {
546 b, err := ioutil.ReadFile(filepath.Join(src, "mcd-send", fi.Name()))
550 i, err := strconv.Atoi(strings.TrimSuffix(string(b), "\n"))
557 notify := NotifyJSON{Exec: make(map[string]*FromToJSON)}
558 if cfgDirExists(src, "notify", "file") {
559 if notify.File, err = cfgDirReadFromTo(src, "notify", "file"); err != nil {
563 if cfgDirExists(src, "notify", "freq") {
564 if notify.Freq, err = cfgDirReadFromTo(src, "notify", "freq"); err != nil {
568 fis, err = ioutil.ReadDir(filepath.Join(src, "notify", "exec"))
569 if err != nil && !os.IsNotExist(err) {
572 for _, fi := range fis {
574 if n[0] == '.' || !fi.IsDir() {
577 if notify.Exec[fi.Name()], err = cfgDirReadFromTo(src, "notify", "exec", n); err != nil {
581 if notify.File != nil || notify.Freq != nil || len(notify.Exec) > 0 {
585 if _, err = ioutil.ReadDir(filepath.Join(src, "self")); err == nil {
586 self := NodeOurJSON{}
587 if self.Id, err = cfgDirLoadMust(src, "self", "id"); err != nil {
590 if self.ExchPub, err = cfgDirLoadMust(src, "self", "exchpub"); err != nil {
593 if self.ExchPrv, err = cfgDirLoadMust(src, "self", "exchprv"); err != nil {
596 if self.SignPub, err = cfgDirLoadMust(src, "self", "signpub"); err != nil {
599 if self.SignPrv, err = cfgDirLoadMust(src, "self", "signprv"); err != nil {
602 if self.NoisePub, err = cfgDirLoadMust(src, "self", "noisepub"); err != nil {
605 if self.NoisePrv, err = cfgDirLoadMust(src, "self", "noiseprv"); err != nil {
609 } else if !os.IsNotExist(err) {
613 cfg.Neigh = make(map[string]NodeJSON)
614 fis, err = ioutil.ReadDir(filepath.Join(src, "neigh"))
615 if err != nil && !os.IsNotExist(err) {
618 for _, fi := range fis {
624 if node.Id, err = cfgDirLoadMust(src, "neigh", n, "id"); err != nil {
627 if node.ExchPub, err = cfgDirLoadMust(src, "neigh", n, "exchpub"); err != nil {
630 if node.SignPub, err = cfgDirLoadMust(src, "neigh", n, "signpub"); err != nil {
633 if node.NoisePub, err = cfgDirLoadOpt(src, "neigh", n, "noisepub"); err != nil {
636 if node.Incoming, err = cfgDirLoadOpt(src, "neigh", n, "incoming"); err != nil {
640 node.Exec = make(map[string][]string)
641 fis2, err := ioutil.ReadDir(filepath.Join(src, "neigh", n, "exec"))
642 if err != nil && !os.IsNotExist(err) {
645 for _, fi2 := range fis2 {
650 s, err := cfgDirLoadMust(src, "neigh", n, "exec", n2)
654 node.Exec[n2] = strings.Split(s, "\n")
657 if cfgDirExists(src, "neigh", n, "freq") {
658 node.Freq = &NodeFreqJSON{}
659 if node.Freq.Path, err = cfgDirLoadOpt(src, "neigh", n, "freq", "path"); err != nil {
663 i64, err := cfgDirLoadIntOpt(src, "neigh", n, "freq", "chunked")
669 node.Freq.Chunked = &i
672 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "freq", "minsize")
678 node.Freq.MinSize = &i
681 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "freq", "maxsize")
687 node.Freq.MaxSize = &i
691 via, err := cfgDirLoadOpt(src, "neigh", n, "via")
696 node.Via = strings.Split(*via, "\n")
699 node.Addrs = make(map[string]string)
700 fis2, err = ioutil.ReadDir(filepath.Join(src, "neigh", n, "addrs"))
701 if err != nil && !os.IsNotExist(err) {
704 for _, fi2 := range fis2 {
709 if node.Addrs[n2], err = cfgDirLoadMust(src, "neigh", n, "addrs", n2); err != nil {
714 i64, err := cfgDirLoadIntOpt(src, "neigh", n, "rxrate")
723 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "txrate")
732 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "onlinedeadline")
738 node.OnlineDeadline = &i
741 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "maxonlinetime")
747 node.MaxOnlineTime = &i
750 fis2, err = ioutil.ReadDir(filepath.Join(src, "neigh", n, "calls"))
751 if err != nil && !os.IsNotExist(err) {
754 callsIdx := make([]int, 0, len(fis2))
755 for _, fi2 := range fis2 {
760 i, err := strconv.Atoi(n2)
764 callsIdx = append(callsIdx, i)
767 for _, i := range callsIdx {
769 is := strconv.Itoa(i)
770 if call.Cron, err = cfgDirLoadMust(
771 src, "neigh", n, "calls", is, "cron",
775 if call.Nice, err = cfgDirLoadOpt(
776 src, "neigh", n, "calls", is, "nice",
780 if call.Xx, err = cfgDirLoadOpt(
781 src, "neigh", n, "calls", is, "xx",
786 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "calls", is, "rxrate")
795 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "calls", is, "txrate")
804 if call.Addr, err = cfgDirLoadOpt(
805 src, "neigh", n, "calls", is, "addr",
810 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "calls", is, "onlinedeadline")
816 call.OnlineDeadline = &i
819 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "calls", is, "maxonlinetime")
825 call.MaxOnlineTime = &i
828 if cfgDirExists(src, "neigh", n, "calls", is, "when-tx-exists") {
829 call.WhenTxExists = true
831 if cfgDirExists(src, "neigh", n, "calls", is, "nock") {
834 if cfgDirExists(src, "neigh", n, "calls", is, "mcd-ignore") {
835 call.MCDIgnore = true
837 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss") {
840 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-doseen") {
841 call.AutoTossDoSeen = true
843 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-nofile") {
844 call.AutoTossNoFile = true
846 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-nofreq") {
847 call.AutoTossNoFreq = true
849 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-noexec") {
850 call.AutoTossNoExec = true
852 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-notrns") {
853 call.AutoTossNoTrns = true
855 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-noarea") {
856 call.AutoTossNoArea = true
858 node.Calls = append(node.Calls, call)
863 cfg.Areas = make(map[string]AreaJSON)
864 fis, err = ioutil.ReadDir(filepath.Join(src, "areas"))
865 if err != nil && !os.IsNotExist(err) {
868 for _, fi := range fis {
874 if area.Id, err = cfgDirLoadMust(src, "areas", n, "id"); err != nil {
877 if area.Pub, err = cfgDirLoadOpt(src, "areas", n, "pub"); err != nil {
880 if area.Prv, err = cfgDirLoadOpt(src, "areas", n, "prv"); err != nil {
884 subs, err := cfgDirLoadOpt(src, "areas", n, "subs")
889 area.Subs = strings.Split(*subs, "\n")
892 area.Exec = make(map[string][]string)
893 fis2, err := ioutil.ReadDir(filepath.Join(src, "areas", n, "exec"))
894 if err != nil && !os.IsNotExist(err) {
897 for _, fi2 := range fis2 {
902 s, err := cfgDirLoadMust(src, "areas", n, "exec", n2)
906 area.Exec[n2] = strings.Split(s, "\n")
909 if area.Incoming, err = cfgDirLoadOpt(src, "areas", n, "incoming"); err != nil {
913 if cfgDirExists(src, "areas", n, "allow-unknown") {
914 area.AllowUnknown = true
919 fis, err = ioutil.ReadDir(filepath.Join(src, "yggdrasil-aliases"))
920 if err != nil && !os.IsNotExist(err) {
924 cfg.YggdrasilAliases = make(map[string]string, len(fis))
926 for _, fi := range fis {
931 b, err := ioutil.ReadFile(filepath.Join(src, "yggdrasil-aliases", fi.Name()))
935 cfg.YggdrasilAliases[n] = strings.TrimSuffix(string(b), "\n")