2 NNCP -- Node to Node copy, utilities for store-and-forward data exchange
3 Copyright (C) 2016-2021 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 {
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 {
196 for name, n := range cfg.Neigh {
197 if err = cfgDirMkdir(dst, "neigh", name); err != nil {
200 if err = cfgDirSave(n.Id, dst, "neigh", name, "id"); err != nil {
203 if err = cfgDirSave(n.ExchPub, dst, "neigh", name, "exchpub"); err != nil {
206 if err = cfgDirSave(n.SignPub, dst, "neigh", name, "signpub"); err != nil {
209 if err = cfgDirSave(n.NoisePub, dst, "neigh", name, "noisepub"); err != nil {
212 if err = cfgDirSave(n.Incoming, dst, "neigh", name, "incoming"); err != nil {
217 if err = cfgDirMkdir(dst, "neigh", name, "exec"); err != nil {
220 for k, v := range n.Exec {
222 strings.Join(v, "\n"),
223 dst, "neigh", name, "exec", k,
231 if err = cfgDirMkdir(dst, "neigh", name, "freq"); err != nil {
236 dst, "neigh", name, "freq", "path",
242 dst, "neigh", name, "freq", "chunked",
248 dst, "neigh", name, "freq", "minsize",
254 dst, "neigh", name, "freq", "maxsize",
262 strings.Join(n.Via, "\n"),
263 dst, "neigh", name, "via",
269 if len(n.Addrs) > 0 {
270 if err = cfgDirMkdir(dst, "neigh", name, "addrs"); err != nil {
273 for k, v := range n.Addrs {
274 if err = cfgDirSave(v, dst, "neigh", name, "addrs", k); err != nil {
280 if err = cfgDirSave(n.RxRate, dst, "neigh", name, "rxrate"); err != nil {
283 if err = cfgDirSave(n.TxRate, dst, "neigh", name, "txrate"); err != nil {
286 if err = cfgDirSave(n.OnlineDeadline, dst, "neigh", name, "onlinedeadline"); err != nil {
289 if err = cfgDirSave(n.MaxOnlineTime, dst, "neigh", name, "maxonlinetime"); err != nil {
293 for i, call := range n.Calls {
294 is := strconv.Itoa(i)
295 if err = cfgDirMkdir(dst, "neigh", name, "calls", is); err != nil {
298 if err = cfgDirSave(call.Cron, dst, "neigh", name, "calls", is, "cron"); err != nil {
301 if err = cfgDirSave(call.Nice, dst, "neigh", name, "calls", is, "nice"); err != nil {
304 if err = cfgDirSave(call.Xx, dst, "neigh", name, "calls", is, "xx"); err != nil {
307 if err = cfgDirSave(call.RxRate, dst, "neigh", name, "calls", is, "rxrate"); err != nil {
310 if err = cfgDirSave(call.TxRate, dst, "neigh", name, "calls", is, "txrate"); err != nil {
313 if err = cfgDirSave(call.Addr, dst, "neigh", name, "calls", is, "addr"); err != nil {
316 if err = cfgDirSave(call.OnlineDeadline, dst, "neigh", name, "calls", is, "onlinedeadline"); err != nil {
319 if err = cfgDirSave(call.MaxOnlineTime, dst, "neigh", name, "calls", is, "maxonlinetime"); err != nil {
322 if call.WhenTxExists {
323 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "when-tx-exists"); err != nil {
328 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "nock"); err != nil {
333 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "mcd-ignore"); err != nil {
338 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss"); err != nil {
342 if call.AutoTossDoSeen {
343 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-doseen"); err != nil {
347 if call.AutoTossNoFile {
348 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-nofile"); err != nil {
352 if call.AutoTossNoFreq {
353 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-nofreq"); err != nil {
357 if call.AutoTossNoExec {
358 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-noexec"); err != nil {
362 if call.AutoTossNoTrns {
363 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-notrns"); err != nil {
367 if call.AutoTossNoArea {
368 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-noarea"); err != nil {
375 for name, a := range cfg.Areas {
376 if err = cfgDirMkdir(dst, "areas", name); err != nil {
379 if err = cfgDirSave(a.Id, dst, "areas", name, "id"); err != nil {
382 if err = cfgDirSave(a.Pub, dst, "areas", name, "pub"); err != nil {
385 if err = cfgDirSave(a.Prv, dst, "areas", name, "prv"); err != nil {
388 if err = cfgDirSave(a.Incoming, dst, "areas", name, "incoming"); err != nil {
392 if err = cfgDirTouch(dst, "areas", name, "allow-unknown"); err != nil {
397 if err = cfgDirMkdir(dst, "areas", name, "exec"); err != nil {
400 for k, v := range a.Exec {
402 strings.Join(v, "\n"),
403 dst, "areas", name, "exec", k,
411 strings.Join(a.Subs, "\n"),
412 dst, "areas", name, "subs",
422 func cfgDirLoad(src ...string) (v string, exists bool, err error) {
423 b, err := ioutil.ReadFile(filepath.Join(src...))
425 if os.IsNotExist(err) {
426 return "", false, nil
428 return "", false, err
430 return strings.TrimSuffix(string(b), "\n"), true, nil
433 func cfgDirLoadMust(src ...string) (v string, err error) {
434 s, exists, err := cfgDirLoad(src...)
439 return "", fmt.Errorf("required \"%s\" does not exist", src[len(src)-1])
444 func cfgDirLoadOpt(src ...string) (v *string, err error) {
445 s, exists, err := cfgDirLoad(src...)
455 func cfgDirLoadIntOpt(src ...string) (i64 *int64, err error) {
456 s, err := cfgDirLoadOpt(src...)
463 i, err := strconv.ParseInt(*s, 10, 64)
470 func cfgDirExists(src ...string) bool {
471 if _, err := os.Stat(filepath.Join(src...)); err == nil {
477 func cfgDirReadFromTo(src ...string) (*FromToJSON, error) {
478 fromTo := FromToJSON{}
481 fromTo.From, err = cfgDirLoadMust(append(src, "from")...)
486 fromTo.To, err = cfgDirLoadMust(append(src, "to")...)
494 func DirToCfg(src string) (*CfgJSON, error) {
498 cfg.Spool, err = cfgDirLoadMust(src, "spool")
502 cfg.Log, err = cfgDirLoadMust(src, "log")
507 if cfg.Umask, err = cfgDirLoadOpt(src, "umask"); err != nil {
510 cfg.OmitPrgrs = cfgDirExists(src, "noprogress")
511 cfg.NoHdr = cfgDirExists(src, "nohdr")
513 sp, err := cfgDirLoadOpt(src, "mcd-listen")
518 cfg.MCDRxIfis = strings.Split(*sp, "\n")
521 fis, err := ioutil.ReadDir(filepath.Join(src, "mcd-send"))
522 if err != nil && !os.IsNotExist(err) {
526 cfg.MCDTxIfis = make(map[string]int, len(fis))
528 for _, fi := range fis {
533 b, err := ioutil.ReadFile(filepath.Join(src, "mcd-send", fi.Name()))
537 i, err := strconv.Atoi(strings.TrimSuffix(string(b), "\n"))
544 notify := NotifyJSON{Exec: make(map[string]*FromToJSON)}
545 if cfgDirExists(src, "notify", "file") {
546 if notify.File, err = cfgDirReadFromTo(src, "notify", "file"); err != nil {
550 if cfgDirExists(src, "notify", "freq") {
551 if notify.Freq, err = cfgDirReadFromTo(src, "notify", "freq"); err != nil {
555 fis, err = ioutil.ReadDir(filepath.Join(src, "notify", "exec"))
556 if err != nil && !os.IsNotExist(err) {
559 for _, fi := range fis {
561 if n[0] == '.' || !fi.IsDir() {
564 if notify.Exec[fi.Name()], err = cfgDirReadFromTo(src, "notify", "exec", n); err != nil {
568 if notify.File != nil || notify.Freq != nil || len(notify.Exec) > 0 {
572 self := NodeOurJSON{}
573 if self.Id, err = cfgDirLoadMust(src, "self", "id"); err != nil {
576 if self.ExchPub, err = cfgDirLoadMust(src, "self", "exchpub"); err != nil {
579 if self.ExchPrv, err = cfgDirLoadMust(src, "self", "exchprv"); err != nil {
582 if self.SignPub, err = cfgDirLoadMust(src, "self", "signpub"); err != nil {
585 if self.SignPrv, err = cfgDirLoadMust(src, "self", "signprv"); err != nil {
588 if self.NoisePub, err = cfgDirLoadMust(src, "self", "noisepub"); err != nil {
591 if self.NoisePrv, err = cfgDirLoadMust(src, "self", "noiseprv"); err != nil {
596 cfg.Neigh = make(map[string]NodeJSON)
597 fis, err = ioutil.ReadDir(filepath.Join(src, "neigh"))
598 if err != nil && !os.IsNotExist(err) {
601 for _, fi := range fis {
607 if node.Id, err = cfgDirLoadMust(src, "neigh", n, "id"); err != nil {
610 if node.ExchPub, err = cfgDirLoadMust(src, "neigh", n, "exchpub"); err != nil {
613 if node.SignPub, err = cfgDirLoadMust(src, "neigh", n, "signpub"); err != nil {
616 if node.NoisePub, err = cfgDirLoadOpt(src, "neigh", n, "noisepub"); err != nil {
619 if node.Incoming, err = cfgDirLoadOpt(src, "neigh", n, "incoming"); err != nil {
623 node.Exec = make(map[string][]string)
624 fis2, err := ioutil.ReadDir(filepath.Join(src, "neigh", n, "exec"))
625 if err != nil && !os.IsNotExist(err) {
628 for _, fi2 := range fis2 {
633 s, err := cfgDirLoadMust(src, "neigh", n, "exec", n2)
637 node.Exec[n2] = strings.Split(s, "\n")
640 if cfgDirExists(src, "neigh", n, "freq") {
641 node.Freq = &NodeFreqJSON{}
642 if node.Freq.Path, err = cfgDirLoadOpt(src, "neigh", n, "freq", "path"); err != nil {
646 i64, err := cfgDirLoadIntOpt(src, "neigh", n, "freq", "chunked")
652 node.Freq.Chunked = &i
655 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "freq", "minsize")
661 node.Freq.MinSize = &i
664 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "freq", "maxsize")
670 node.Freq.MaxSize = &i
674 via, err := cfgDirLoadOpt(src, "neigh", n, "via")
679 node.Via = strings.Split(*via, "\n")
682 node.Addrs = make(map[string]string)
683 fis2, err = ioutil.ReadDir(filepath.Join(src, "neigh", n, "addrs"))
684 if err != nil && !os.IsNotExist(err) {
687 for _, fi2 := range fis2 {
692 if node.Addrs[n2], err = cfgDirLoadMust(src, "neigh", n, "addrs", n2); err != nil {
697 i64, err := cfgDirLoadIntOpt(src, "neigh", n, "rxrate")
706 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "txrate")
715 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "onlinedeadline")
721 node.OnlineDeadline = &i
724 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "maxonlinetime")
730 node.MaxOnlineTime = &i
733 fis2, err = ioutil.ReadDir(filepath.Join(src, "neigh", n, "calls"))
734 if err != nil && !os.IsNotExist(err) {
737 callsIdx := make([]int, 0, len(fis2))
738 for _, fi2 := range fis2 {
743 i, err := strconv.Atoi(n2)
747 callsIdx = append(callsIdx, i)
750 for _, i := range callsIdx {
752 is := strconv.Itoa(i)
753 if call.Cron, err = cfgDirLoadMust(
754 src, "neigh", n, "calls", is, "cron",
758 if call.Nice, err = cfgDirLoadOpt(
759 src, "neigh", n, "calls", is, "nice",
763 if call.Xx, err = cfgDirLoadOpt(
764 src, "neigh", n, "calls", is, "xx",
769 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "calls", is, "rxrate")
778 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "calls", is, "txrate")
787 if call.Addr, err = cfgDirLoadOpt(
788 src, "neigh", n, "calls", is, "addr",
793 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "calls", is, "onlinedeadline")
799 call.OnlineDeadline = &i
802 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "calls", is, "maxonlinetime")
808 call.MaxOnlineTime = &i
811 if cfgDirExists(src, "neigh", n, "calls", is, "when-tx-exists") {
812 call.WhenTxExists = true
814 if cfgDirExists(src, "neigh", n, "calls", is, "nock") {
817 if cfgDirExists(src, "neigh", n, "calls", is, "mcd-ignore") {
818 call.MCDIgnore = true
820 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss") {
823 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-doseen") {
824 call.AutoTossDoSeen = true
826 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-nofile") {
827 call.AutoTossNoFile = true
829 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-nofreq") {
830 call.AutoTossNoFreq = true
832 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-noexec") {
833 call.AutoTossNoExec = true
835 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-notrns") {
836 call.AutoTossNoTrns = true
838 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-noarea") {
839 call.AutoTossNoArea = true
841 node.Calls = append(node.Calls, call)
846 cfg.Areas = make(map[string]AreaJSON)
847 fis, err = ioutil.ReadDir(filepath.Join(src, "areas"))
848 if err != nil && !os.IsNotExist(err) {
851 for _, fi := range fis {
857 if area.Id, err = cfgDirLoadMust(src, "areas", n, "id"); err != nil {
860 if area.Pub, err = cfgDirLoadOpt(src, "areas", n, "pub"); err != nil {
863 if area.Prv, err = cfgDirLoadOpt(src, "areas", n, "prv"); err != nil {
867 subs, err := cfgDirLoadOpt(src, "areas", n, "subs")
872 area.Subs = strings.Split(*subs, "\n")
875 area.Exec = make(map[string][]string)
876 fis2, err := ioutil.ReadDir(filepath.Join(src, "areas", n, "exec"))
877 if err != nil && !os.IsNotExist(err) {
880 for _, fi2 := range fis2 {
885 s, err := cfgDirLoadMust(src, "areas", n, "exec", n2)
889 area.Exec[n2] = strings.Split(s, "\n")
892 if area.Incoming, err = cfgDirLoadOpt(src, "areas", n, "incoming"); err != nil {
896 if cfgDirExists(src, "areas", n, "allow-unknown") {
897 area.AllowUnknown = true