1 // NNCP -- Node to Node copy, utilities for store-and-forward data exchange
2 // Copyright (C) 2016-2024 Sergey Matveev <stargrave@stargrave.org>
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, version 3 of the License.
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
13 // You should have received a copy of the GNU General Public License
14 // 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",
262 if err = cfgDirMkdir(dst, "neigh", name, "ack"); err != nil {
267 dst, "neigh", name, "ack", "minsize",
273 dst, "neigh", name, "ack", "nice",
281 strings.Join(n.Via, "\n"),
282 dst, "neigh", name, "via",
288 if len(n.Addrs) > 0 {
289 if err = cfgDirMkdir(dst, "neigh", name, "addrs"); err != nil {
292 for k, v := range n.Addrs {
293 if err = cfgDirSave(v, dst, "neigh", name, "addrs", k); err != nil {
299 if err = cfgDirSave(n.RxRate, dst, "neigh", name, "rxrate"); err != nil {
302 if err = cfgDirSave(n.TxRate, dst, "neigh", name, "txrate"); err != nil {
305 if err = cfgDirSave(n.OnlineDeadline, dst, "neigh", name, "onlinedeadline"); err != nil {
308 if err = cfgDirSave(n.MaxOnlineTime, dst, "neigh", name, "maxonlinetime"); err != nil {
312 for i, call := range n.Calls {
313 is := strconv.Itoa(i)
314 if err = cfgDirMkdir(dst, "neigh", name, "calls", is); err != nil {
317 if err = cfgDirSave(call.Cron, dst, "neigh", name, "calls", is, "cron"); err != nil {
320 if err = cfgDirSave(call.Nice, dst, "neigh", name, "calls", is, "nice"); err != nil {
323 if err = cfgDirSave(call.Xx, dst, "neigh", name, "calls", is, "xx"); err != nil {
326 if err = cfgDirSave(call.RxRate, dst, "neigh", name, "calls", is, "rxrate"); err != nil {
329 if err = cfgDirSave(call.TxRate, dst, "neigh", name, "calls", is, "txrate"); err != nil {
332 if err = cfgDirSave(call.Addr, dst, "neigh", name, "calls", is, "addr"); err != nil {
335 if err = cfgDirSave(call.OnlineDeadline, dst, "neigh", name, "calls", is, "onlinedeadline"); err != nil {
338 if err = cfgDirSave(call.MaxOnlineTime, dst, "neigh", name, "calls", is, "maxonlinetime"); err != nil {
341 if call.WhenTxExists {
342 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "when-tx-exists"); err != nil {
347 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "nock"); err != nil {
352 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "mcd-ignore"); err != nil {
357 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss"); err != nil {
361 if call.AutoTossDoSeen {
362 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-doseen"); err != nil {
366 if call.AutoTossNoFile {
367 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-nofile"); err != nil {
371 if call.AutoTossNoFreq {
372 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-nofreq"); err != nil {
376 if call.AutoTossNoExec {
377 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-noexec"); err != nil {
381 if call.AutoTossNoTrns {
382 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-notrns"); err != nil {
386 if call.AutoTossNoArea {
387 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-noarea"); err != nil {
391 if call.AutoTossNoACK {
392 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-noack"); err != nil {
396 if call.AutoTossGenACK {
397 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-gen-ack"); err != nil {
404 for name, a := range cfg.Areas {
405 if err = cfgDirMkdir(dst, "areas", name); err != nil {
408 if err = cfgDirSave(a.Id, dst, "areas", name, "id"); err != nil {
411 if err = cfgDirSave(a.Pub, dst, "areas", name, "pub"); err != nil {
414 if err = cfgDirSave(a.Prv, dst, "areas", name, "prv"); err != nil {
417 if err = cfgDirSave(a.Incoming, dst, "areas", name, "incoming"); err != nil {
421 if err = cfgDirTouch(dst, "areas", name, "allow-unknown"); err != nil {
426 if err = cfgDirMkdir(dst, "areas", name, "exec"); err != nil {
429 for k, v := range a.Exec {
431 strings.Join(v, "\n"),
432 dst, "areas", name, "exec", k,
440 strings.Join(a.Subs, "\n"),
441 dst, "areas", name, "subs",
448 if len(cfg.YggdrasilAliases) > 0 {
449 if err = cfgDirMkdir(dst, "yggdrasil-aliases"); err != nil {
452 for alias, v := range cfg.YggdrasilAliases {
453 if err = cfgDirSave(v, dst, "yggdrasil-aliases", alias); err != nil {
462 func cfgDirLoad(src ...string) (v string, exists bool, err error) {
463 b, err := os.ReadFile(filepath.Join(src...))
465 if errors.Is(err, fs.ErrNotExist) {
466 return "", false, nil
468 return "", false, err
470 return strings.TrimSuffix(string(b), "\n"), true, nil
473 func cfgDirLoadMust(src ...string) (v string, err error) {
474 s, exists, err := cfgDirLoad(src...)
479 return "", fmt.Errorf("required \"%s\" does not exist", src[len(src)-1])
484 func cfgDirLoadOpt(src ...string) (v *string, err error) {
485 s, exists, err := cfgDirLoad(src...)
495 func cfgDirLoadIntOpt(src ...string) (i64 *int64, err error) {
496 s, err := cfgDirLoadOpt(src...)
503 i, err := strconv.ParseInt(*s, 10, 64)
510 func cfgDirExists(src ...string) bool {
511 if _, err := os.Stat(filepath.Join(src...)); err == nil {
517 func cfgDirReadFromTo(src ...string) (*FromToJSON, error) {
518 fromTo := FromToJSON{}
521 fromTo.From, err = cfgDirLoadMust(append(src, "from")...)
526 fromTo.To, err = cfgDirLoadMust(append(src, "to")...)
534 func DirToCfg(src string) (*CfgJSON, error) {
538 cfg.Spool, err = cfgDirLoadMust(src, "spool")
542 cfg.Log, err = cfgDirLoadMust(src, "log")
547 if cfg.Umask, err = cfgDirLoadOpt(src, "umask"); err != nil {
550 cfg.OmitPrgrs = cfgDirExists(src, "noprogress")
551 cfg.NoHdr = cfgDirExists(src, "nohdr")
553 sp, err := cfgDirLoadOpt(src, "mcd-listen")
558 cfg.MCDRxIfis = strings.Split(*sp, "\n")
561 fis, err := os.ReadDir(filepath.Join(src, "mcd-send"))
562 if err != nil && !errors.Is(err, fs.ErrNotExist) {
566 cfg.MCDTxIfis = make(map[string]int, len(fis))
568 for _, fi := range fis {
573 b, err := os.ReadFile(filepath.Join(src, "mcd-send", fi.Name()))
577 i, err := strconv.Atoi(strings.TrimSuffix(string(b), "\n"))
584 notify := NotifyJSON{Exec: make(map[string]*FromToJSON)}
585 if cfgDirExists(src, "notify", "file") {
586 if notify.File, err = cfgDirReadFromTo(src, "notify", "file"); err != nil {
590 if cfgDirExists(src, "notify", "freq") {
591 if notify.Freq, err = cfgDirReadFromTo(src, "notify", "freq"); err != nil {
595 fis, err = os.ReadDir(filepath.Join(src, "notify", "exec"))
596 if err != nil && !errors.Is(err, fs.ErrNotExist) {
599 for _, fi := range fis {
601 if n[0] == '.' || !fi.IsDir() {
604 if notify.Exec[fi.Name()], err = cfgDirReadFromTo(src, "notify", "exec", n); err != nil {
608 if notify.File != nil || notify.Freq != nil || len(notify.Exec) > 0 {
612 if _, err = os.ReadDir(filepath.Join(src, "self")); err == nil {
613 self := NodeOurJSON{}
614 if self.Id, err = cfgDirLoadMust(src, "self", "id"); err != nil {
617 if self.ExchPub, err = cfgDirLoadMust(src, "self", "exchpub"); err != nil {
620 if self.ExchPrv, err = cfgDirLoadMust(src, "self", "exchprv"); err != nil {
623 if self.SignPub, err = cfgDirLoadMust(src, "self", "signpub"); err != nil {
626 if self.SignPrv, err = cfgDirLoadMust(src, "self", "signprv"); err != nil {
629 if self.NoisePub, err = cfgDirLoadMust(src, "self", "noisepub"); err != nil {
632 if self.NoisePrv, err = cfgDirLoadMust(src, "self", "noiseprv"); err != nil {
636 } else if !errors.Is(err, fs.ErrNotExist) {
640 cfg.Neigh = make(map[string]NodeJSON)
641 fis, err = os.ReadDir(filepath.Join(src, "neigh"))
642 if err != nil && !errors.Is(err, fs.ErrNotExist) {
645 for _, fi := range fis {
651 if node.Id, err = cfgDirLoadMust(src, "neigh", n, "id"); err != nil {
654 if node.ExchPub, err = cfgDirLoadMust(src, "neigh", n, "exchpub"); err != nil {
657 if node.SignPub, err = cfgDirLoadMust(src, "neigh", n, "signpub"); err != nil {
660 if node.NoisePub, err = cfgDirLoadOpt(src, "neigh", n, "noisepub"); err != nil {
663 if node.Incoming, err = cfgDirLoadOpt(src, "neigh", n, "incoming"); err != nil {
667 node.Exec = make(map[string][]string)
668 fis2, err := os.ReadDir(filepath.Join(src, "neigh", n, "exec"))
669 if err != nil && !errors.Is(err, fs.ErrNotExist) {
672 for _, fi2 := range fis2 {
677 s, err := cfgDirLoadMust(src, "neigh", n, "exec", n2)
681 node.Exec[n2] = strings.Split(s, "\n")
684 if cfgDirExists(src, "neigh", n, "freq") {
685 node.Freq = &NodeFreqJSON{}
686 if node.Freq.Path, err = cfgDirLoadOpt(src, "neigh", n, "freq", "path"); err != nil {
690 i64, err := cfgDirLoadIntOpt(src, "neigh", n, "freq", "chunked")
696 node.Freq.Chunked = &i
699 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "freq", "minsize")
705 node.Freq.MinSize = &i
708 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "freq", "maxsize")
714 node.Freq.MaxSize = &i
718 if cfgDirExists(src, "neigh", n, "ack") {
719 node.ACK = &NodeACKJSON{}
720 i64, err := cfgDirLoadIntOpt(src, "neigh", n, "ack", "minsize")
726 node.ACK.MinSize = &i
728 if node.ACK.Nice, err = cfgDirLoadOpt(
729 src, "neigh", n, "ack", "nice",
735 via, err := cfgDirLoadOpt(src, "neigh", n, "via")
740 node.Via = strings.Split(*via, "\n")
743 node.Addrs = make(map[string]string)
744 fis2, err = os.ReadDir(filepath.Join(src, "neigh", n, "addrs"))
745 if err != nil && !errors.Is(err, fs.ErrNotExist) {
748 for _, fi2 := range fis2 {
753 if node.Addrs[n2], err = cfgDirLoadMust(src, "neigh", n, "addrs", n2); err != nil {
758 i64, err := cfgDirLoadIntOpt(src, "neigh", n, "rxrate")
767 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "txrate")
776 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "onlinedeadline")
782 node.OnlineDeadline = &i
785 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "maxonlinetime")
791 node.MaxOnlineTime = &i
794 fis2, err = os.ReadDir(filepath.Join(src, "neigh", n, "calls"))
795 if err != nil && !errors.Is(err, fs.ErrNotExist) {
798 callsIdx := make([]int, 0, len(fis2))
799 for _, fi2 := range fis2 {
804 i, err := strconv.Atoi(n2)
808 callsIdx = append(callsIdx, i)
811 for _, i := range callsIdx {
813 is := strconv.Itoa(i)
814 if call.Cron, err = cfgDirLoadMust(
815 src, "neigh", n, "calls", is, "cron",
819 if call.Nice, err = cfgDirLoadOpt(
820 src, "neigh", n, "calls", is, "nice",
824 if call.Xx, err = cfgDirLoadOpt(
825 src, "neigh", n, "calls", is, "xx",
830 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "calls", is, "rxrate")
839 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "calls", is, "txrate")
848 if call.Addr, err = cfgDirLoadOpt(
849 src, "neigh", n, "calls", is, "addr",
854 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "calls", is, "onlinedeadline")
860 call.OnlineDeadline = &i
863 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "calls", is, "maxonlinetime")
869 call.MaxOnlineTime = &i
872 if cfgDirExists(src, "neigh", n, "calls", is, "when-tx-exists") {
873 call.WhenTxExists = true
875 if cfgDirExists(src, "neigh", n, "calls", is, "nock") {
878 if cfgDirExists(src, "neigh", n, "calls", is, "mcd-ignore") {
879 call.MCDIgnore = true
881 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss") {
884 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-doseen") {
885 call.AutoTossDoSeen = true
887 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-nofile") {
888 call.AutoTossNoFile = true
890 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-nofreq") {
891 call.AutoTossNoFreq = true
893 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-noexec") {
894 call.AutoTossNoExec = true
896 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-notrns") {
897 call.AutoTossNoTrns = true
899 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-noarea") {
900 call.AutoTossNoArea = true
902 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-noack") {
903 call.AutoTossNoACK = true
905 if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-gen-ack") {
906 call.AutoTossGenACK = true
908 node.Calls = append(node.Calls, call)
913 cfg.Areas = make(map[string]AreaJSON)
914 fis, err = os.ReadDir(filepath.Join(src, "areas"))
915 if err != nil && !errors.Is(err, fs.ErrNotExist) {
918 for _, fi := range fis {
924 if area.Id, err = cfgDirLoadMust(src, "areas", n, "id"); err != nil {
927 if area.Pub, err = cfgDirLoadOpt(src, "areas", n, "pub"); err != nil {
930 if area.Prv, err = cfgDirLoadOpt(src, "areas", n, "prv"); err != nil {
934 subs, err := cfgDirLoadOpt(src, "areas", n, "subs")
939 area.Subs = strings.Split(*subs, "\n")
942 area.Exec = make(map[string][]string)
943 fis2, err := os.ReadDir(filepath.Join(src, "areas", n, "exec"))
944 if err != nil && !errors.Is(err, fs.ErrNotExist) {
947 for _, fi2 := range fis2 {
952 s, err := cfgDirLoadMust(src, "areas", n, "exec", n2)
956 area.Exec[n2] = strings.Split(s, "\n")
959 if area.Incoming, err = cfgDirLoadOpt(src, "areas", n, "incoming"); err != nil {
963 if cfgDirExists(src, "areas", n, "allow-unknown") {
964 area.AllowUnknown = true
969 fis, err = os.ReadDir(filepath.Join(src, "yggdrasil-aliases"))
970 if err != nil && !errors.Is(err, fs.ErrNotExist) {
974 cfg.YggdrasilAliases = make(map[string]string, len(fis))
976 for _, fi := range fis {
981 b, err := os.ReadFile(filepath.Join(src, "yggdrasil-aliases", fi.Name()))
985 cfg.YggdrasilAliases[n] = strings.TrimSuffix(string(b), "\n")