]> Cypherpunks.ru repositories - nncp.git/blob - src/cfgdir.go
Generate ACKs during tossing
[nncp.git] / src / cfgdir.go
1 /*
2 NNCP -- Node to Node copy, utilities for store-and-forward data exchange
3 Copyright (C) 2016-2023 Sergey Matveev <stargrave@stargrave.org>
4
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.
8
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.
13
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/>.
16 */
17
18 package nncp
19
20 import (
21         "errors"
22         "fmt"
23         "io/fs"
24         "os"
25         "path/filepath"
26         "sort"
27         "strconv"
28         "strings"
29 )
30
31 func cfgDirMkdir(dst ...string) error {
32         return os.MkdirAll(filepath.Join(dst...), os.FileMode(0777))
33 }
34
35 func cfgDirSave(v interface{}, dst ...string) error {
36         var r string
37         switch v := v.(type) {
38         case *string:
39                 if v == nil {
40                         return nil
41                 }
42                 r = *v
43         case string:
44                 r = v
45         case *int:
46                 if v == nil {
47                         return nil
48                 }
49                 r = strconv.Itoa(*v)
50         case *uint:
51                 if v == nil {
52                         return nil
53                 }
54                 r = strconv.Itoa(int(*v))
55         case *uint64:
56                 if v == nil {
57                         return nil
58                 }
59                 r = strconv.FormatUint(*v, 10)
60         case int:
61                 r = strconv.Itoa(v)
62         default:
63                 panic("unsupported value type")
64         }
65         mode := os.FileMode(0666)
66         if strings.HasSuffix(dst[len(dst)-1], "prv") {
67                 mode = os.FileMode(0600)
68         }
69         return os.WriteFile(filepath.Join(dst...), []byte(r+"\n"), mode)
70 }
71
72 func cfgDirTouch(dst ...string) error {
73         if fd, err := os.Create(filepath.Join(dst...)); err == nil {
74                 fd.Close()
75         } else {
76                 return err
77         }
78         return nil
79 }
80
81 func CfgToDir(dst string, cfg *CfgJSON) (err error) {
82         if err = cfgDirMkdir(dst); err != nil {
83                 return
84         }
85         if err = cfgDirSave(cfg.Spool, dst, "spool"); err != nil {
86                 return
87         }
88         if err = cfgDirSave(cfg.Log, dst, "log"); err != nil {
89                 return
90         }
91         if err = cfgDirSave(cfg.Umask, dst, "umask"); err != nil {
92                 return
93         }
94         if cfg.OmitPrgrs {
95                 if err = cfgDirTouch(dst, "noprogress"); err != nil {
96                         return
97                 }
98         }
99         if cfg.NoHdr {
100                 if err = cfgDirTouch(dst, "nohdr"); err != nil {
101                         return
102                 }
103         }
104
105         if len(cfg.MCDRxIfis) > 0 {
106                 if err = cfgDirSave(
107                         strings.Join(cfg.MCDRxIfis, "\n"),
108                         dst, "mcd-listen",
109                 ); err != nil {
110                         return
111                 }
112         }
113         if len(cfg.MCDTxIfis) > 0 {
114                 if err = cfgDirMkdir(dst, "mcd-send"); err != nil {
115                         return
116                 }
117                 for ifi, t := range cfg.MCDTxIfis {
118                         if err = cfgDirSave(t, dst, "mcd-send", ifi); err != nil {
119                                 return
120                         }
121                 }
122         }
123
124         if cfg.Notify != nil {
125                 if cfg.Notify.File != nil {
126                         if err = cfgDirMkdir(dst, "notify", "file"); err != nil {
127                                 return
128                         }
129                         if err = cfgDirSave(
130                                 cfg.Notify.File.From,
131                                 dst, "notify", "file", "from",
132                         ); err != nil {
133                                 return
134                         }
135                         if err = cfgDirSave(
136                                 cfg.Notify.File.To,
137                                 dst, "notify", "file", "to",
138                         ); err != nil {
139                                 return
140                         }
141                 }
142                 if cfg.Notify.Freq != nil {
143                         if err = cfgDirMkdir(dst, "notify", "freq"); err != nil {
144                                 return
145                         }
146                         if err = cfgDirSave(
147                                 cfg.Notify.Freq.From,
148                                 dst, "notify", "freq", "from",
149                         ); err != nil {
150                                 return
151                         }
152                         if err = cfgDirSave(
153                                 cfg.Notify.Freq.To,
154                                 dst, "notify", "freq", "to",
155                         ); err != nil {
156                                 return
157                         }
158                 }
159                 for k, v := range cfg.Notify.Exec {
160                         if err = cfgDirMkdir(dst, "notify", "exec", k); err != nil {
161                                 return
162                         }
163                         if err = cfgDirSave(v.From, dst, "notify", "exec", k, "from"); err != nil {
164                                 return
165                         }
166                         if err = cfgDirSave(v.To, dst, "notify", "exec", k, "to"); err != nil {
167                                 return
168                         }
169                 }
170         }
171
172         if cfg.Self != nil {
173                 if err = cfgDirMkdir(dst, "self"); err != nil {
174                         return
175                 }
176                 if err = cfgDirSave(cfg.Self.Id, dst, "self", "id"); err != nil {
177                         return
178                 }
179                 if err = cfgDirSave(cfg.Self.ExchPub, dst, "self", "exchpub"); err != nil {
180                         return
181                 }
182                 if err = cfgDirSave(cfg.Self.ExchPrv, dst, "self", "exchprv"); err != nil {
183                         return
184                 }
185                 if err = cfgDirSave(cfg.Self.SignPub, dst, "self", "signpub"); err != nil {
186                         return
187                 }
188                 if err = cfgDirSave(cfg.Self.SignPrv, dst, "self", "signprv"); err != nil {
189                         return
190                 }
191                 if err = cfgDirSave(cfg.Self.NoisePub, dst, "self", "noisepub"); err != nil {
192                         return
193                 }
194                 if err = cfgDirSave(cfg.Self.NoisePrv, dst, "self", "noiseprv"); err != nil {
195                         return
196                 }
197         }
198
199         for name, n := range cfg.Neigh {
200                 if err = cfgDirMkdir(dst, "neigh", name); err != nil {
201                         return
202                 }
203                 if err = cfgDirSave(n.Id, dst, "neigh", name, "id"); err != nil {
204                         return
205                 }
206                 if err = cfgDirSave(n.ExchPub, dst, "neigh", name, "exchpub"); err != nil {
207                         return
208                 }
209                 if err = cfgDirSave(n.SignPub, dst, "neigh", name, "signpub"); err != nil {
210                         return
211                 }
212                 if err = cfgDirSave(n.NoisePub, dst, "neigh", name, "noisepub"); err != nil {
213                         return
214                 }
215                 if err = cfgDirSave(n.Incoming, dst, "neigh", name, "incoming"); err != nil {
216                         return
217                 }
218
219                 if len(n.Exec) > 0 {
220                         if err = cfgDirMkdir(dst, "neigh", name, "exec"); err != nil {
221                                 return
222                         }
223                         for k, v := range n.Exec {
224                                 if err = cfgDirSave(
225                                         strings.Join(v, "\n"),
226                                         dst, "neigh", name, "exec", k,
227                                 ); err != nil {
228                                         return
229                                 }
230                         }
231                 }
232
233                 if n.Freq != nil {
234                         if err = cfgDirMkdir(dst, "neigh", name, "freq"); err != nil {
235                                 return
236                         }
237                         if err = cfgDirSave(
238                                 n.Freq.Path,
239                                 dst, "neigh", name, "freq", "path",
240                         ); err != nil {
241                                 return
242                         }
243                         if err = cfgDirSave(
244                                 n.Freq.Chunked,
245                                 dst, "neigh", name, "freq", "chunked",
246                         ); err != nil {
247                                 return
248                         }
249                         if err = cfgDirSave(
250                                 n.Freq.MinSize,
251                                 dst, "neigh", name, "freq", "minsize",
252                         ); err != nil {
253                                 return
254                         }
255                         if err = cfgDirSave(
256                                 n.Freq.MaxSize,
257                                 dst, "neigh", name, "freq", "maxsize",
258                         ); err != nil {
259                                 return
260                         }
261                 }
262
263                 if n.ACK != nil {
264                         if err = cfgDirMkdir(dst, "neigh", name, "ack"); err != nil {
265                                 return
266                         }
267                         if err = cfgDirSave(
268                                 n.ACK.MinSize,
269                                 dst, "neigh", name, "ack", "minsize",
270                         ); err != nil {
271                                 return
272                         }
273                         if err = cfgDirSave(
274                                 n.ACK.Nice,
275                                 dst, "neigh", name, "ack", "nice",
276                         ); err != nil {
277                                 return
278                         }
279                 }
280
281                 if len(n.Via) > 0 {
282                         if err = cfgDirSave(
283                                 strings.Join(n.Via, "\n"),
284                                 dst, "neigh", name, "via",
285                         ); err != nil {
286                                 return
287                         }
288                 }
289
290                 if len(n.Addrs) > 0 {
291                         if err = cfgDirMkdir(dst, "neigh", name, "addrs"); err != nil {
292                                 return
293                         }
294                         for k, v := range n.Addrs {
295                                 if err = cfgDirSave(v, dst, "neigh", name, "addrs", k); err != nil {
296                                         return
297                                 }
298                         }
299                 }
300
301                 if err = cfgDirSave(n.RxRate, dst, "neigh", name, "rxrate"); err != nil {
302                         return
303                 }
304                 if err = cfgDirSave(n.TxRate, dst, "neigh", name, "txrate"); err != nil {
305                         return
306                 }
307                 if err = cfgDirSave(n.OnlineDeadline, dst, "neigh", name, "onlinedeadline"); err != nil {
308                         return
309                 }
310                 if err = cfgDirSave(n.MaxOnlineTime, dst, "neigh", name, "maxonlinetime"); err != nil {
311                         return
312                 }
313
314                 for i, call := range n.Calls {
315                         is := strconv.Itoa(i)
316                         if err = cfgDirMkdir(dst, "neigh", name, "calls", is); err != nil {
317                                 return
318                         }
319                         if err = cfgDirSave(call.Cron, dst, "neigh", name, "calls", is, "cron"); err != nil {
320                                 return
321                         }
322                         if err = cfgDirSave(call.Nice, dst, "neigh", name, "calls", is, "nice"); err != nil {
323                                 return
324                         }
325                         if err = cfgDirSave(call.Xx, dst, "neigh", name, "calls", is, "xx"); err != nil {
326                                 return
327                         }
328                         if err = cfgDirSave(call.RxRate, dst, "neigh", name, "calls", is, "rxrate"); err != nil {
329                                 return
330                         }
331                         if err = cfgDirSave(call.TxRate, dst, "neigh", name, "calls", is, "txrate"); err != nil {
332                                 return
333                         }
334                         if err = cfgDirSave(call.Addr, dst, "neigh", name, "calls", is, "addr"); err != nil {
335                                 return
336                         }
337                         if err = cfgDirSave(call.OnlineDeadline, dst, "neigh", name, "calls", is, "onlinedeadline"); err != nil {
338                                 return
339                         }
340                         if err = cfgDirSave(call.MaxOnlineTime, dst, "neigh", name, "calls", is, "maxonlinetime"); err != nil {
341                                 return
342                         }
343                         if call.WhenTxExists {
344                                 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "when-tx-exists"); err != nil {
345                                         return
346                                 }
347                         }
348                         if call.NoCK {
349                                 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "nock"); err != nil {
350                                         return
351                                 }
352                         }
353                         if call.MCDIgnore {
354                                 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "mcd-ignore"); err != nil {
355                                         return
356                                 }
357                         }
358                         if call.AutoToss {
359                                 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss"); err != nil {
360                                         return
361                                 }
362                         }
363                         if call.AutoTossDoSeen {
364                                 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-doseen"); err != nil {
365                                         return
366                                 }
367                         }
368                         if call.AutoTossNoFile {
369                                 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-nofile"); err != nil {
370                                         return
371                                 }
372                         }
373                         if call.AutoTossNoFreq {
374                                 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-nofreq"); err != nil {
375                                         return
376                                 }
377                         }
378                         if call.AutoTossNoExec {
379                                 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-noexec"); err != nil {
380                                         return
381                                 }
382                         }
383                         if call.AutoTossNoTrns {
384                                 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-notrns"); err != nil {
385                                         return
386                                 }
387                         }
388                         if call.AutoTossNoArea {
389                                 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-noarea"); err != nil {
390                                         return
391                                 }
392                         }
393                         if call.AutoTossNoACK {
394                                 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-noack"); err != nil {
395                                         return
396                                 }
397                         }
398                         if call.AutoTossGenACK {
399                                 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-gen-ack"); err != nil {
400                                         return
401                                 }
402                         }
403                 }
404         }
405
406         for name, a := range cfg.Areas {
407                 if err = cfgDirMkdir(dst, "areas", name); err != nil {
408                         return
409                 }
410                 if err = cfgDirSave(a.Id, dst, "areas", name, "id"); err != nil {
411                         return
412                 }
413                 if err = cfgDirSave(a.Pub, dst, "areas", name, "pub"); err != nil {
414                         return
415                 }
416                 if err = cfgDirSave(a.Prv, dst, "areas", name, "prv"); err != nil {
417                         return
418                 }
419                 if err = cfgDirSave(a.Incoming, dst, "areas", name, "incoming"); err != nil {
420                         return
421                 }
422                 if a.AllowUnknown {
423                         if err = cfgDirTouch(dst, "areas", name, "allow-unknown"); err != nil {
424                                 return
425                         }
426                 }
427                 if len(a.Exec) > 0 {
428                         if err = cfgDirMkdir(dst, "areas", name, "exec"); err != nil {
429                                 return
430                         }
431                         for k, v := range a.Exec {
432                                 if err = cfgDirSave(
433                                         strings.Join(v, "\n"),
434                                         dst, "areas", name, "exec", k,
435                                 ); err != nil {
436                                         return
437                                 }
438                         }
439                 }
440                 if len(a.Subs) > 0 {
441                         if err = cfgDirSave(
442                                 strings.Join(a.Subs, "\n"),
443                                 dst, "areas", name, "subs",
444                         ); err != nil {
445                                 return
446                         }
447                 }
448         }
449
450         if len(cfg.YggdrasilAliases) > 0 {
451                 if err = cfgDirMkdir(dst, "yggdrasil-aliases"); err != nil {
452                         return
453                 }
454                 for alias, v := range cfg.YggdrasilAliases {
455                         if err = cfgDirSave(v, dst, "yggdrasil-aliases", alias); err != nil {
456                                 return
457                         }
458                 }
459         }
460
461         return
462 }
463
464 func cfgDirLoad(src ...string) (v string, exists bool, err error) {
465         b, err := os.ReadFile(filepath.Join(src...))
466         if err != nil {
467                 if errors.Is(err, fs.ErrNotExist) {
468                         return "", false, nil
469                 }
470                 return "", false, err
471         }
472         return strings.TrimSuffix(string(b), "\n"), true, nil
473 }
474
475 func cfgDirLoadMust(src ...string) (v string, err error) {
476         s, exists, err := cfgDirLoad(src...)
477         if err != nil {
478                 return "", err
479         }
480         if !exists {
481                 return "", fmt.Errorf("required \"%s\" does not exist", src[len(src)-1])
482         }
483         return s, nil
484 }
485
486 func cfgDirLoadOpt(src ...string) (v *string, err error) {
487         s, exists, err := cfgDirLoad(src...)
488         if err != nil {
489                 return nil, err
490         }
491         if !exists {
492                 return nil, nil
493         }
494         return &s, nil
495 }
496
497 func cfgDirLoadIntOpt(src ...string) (i64 *int64, err error) {
498         s, err := cfgDirLoadOpt(src...)
499         if err != nil {
500                 return nil, err
501         }
502         if s == nil {
503                 return nil, nil
504         }
505         i, err := strconv.ParseInt(*s, 10, 64)
506         if err != nil {
507                 return nil, err
508         }
509         return &i, nil
510 }
511
512 func cfgDirExists(src ...string) bool {
513         if _, err := os.Stat(filepath.Join(src...)); err == nil {
514                 return true
515         }
516         return false
517 }
518
519 func cfgDirReadFromTo(src ...string) (*FromToJSON, error) {
520         fromTo := FromToJSON{}
521
522         var err error
523         fromTo.From, err = cfgDirLoadMust(append(src, "from")...)
524         if err != nil {
525                 return nil, err
526         }
527
528         fromTo.To, err = cfgDirLoadMust(append(src, "to")...)
529         if err != nil {
530                 return nil, err
531         }
532
533         return &fromTo, nil
534 }
535
536 func DirToCfg(src string) (*CfgJSON, error) {
537         cfg := CfgJSON{}
538         var err error
539
540         cfg.Spool, err = cfgDirLoadMust(src, "spool")
541         if err != nil {
542                 return nil, err
543         }
544         cfg.Log, err = cfgDirLoadMust(src, "log")
545         if err != nil {
546                 return nil, err
547         }
548
549         if cfg.Umask, err = cfgDirLoadOpt(src, "umask"); err != nil {
550                 return nil, err
551         }
552         cfg.OmitPrgrs = cfgDirExists(src, "noprogress")
553         cfg.NoHdr = cfgDirExists(src, "nohdr")
554
555         sp, err := cfgDirLoadOpt(src, "mcd-listen")
556         if err != nil {
557                 return nil, err
558         }
559         if sp != nil {
560                 cfg.MCDRxIfis = strings.Split(*sp, "\n")
561         }
562
563         fis, err := os.ReadDir(filepath.Join(src, "mcd-send"))
564         if err != nil && !errors.Is(err, fs.ErrNotExist) {
565                 return nil, err
566         }
567         if len(fis) > 0 {
568                 cfg.MCDTxIfis = make(map[string]int, len(fis))
569         }
570         for _, fi := range fis {
571                 n := fi.Name()
572                 if n[0] == '.' {
573                         continue
574                 }
575                 b, err := os.ReadFile(filepath.Join(src, "mcd-send", fi.Name()))
576                 if err != nil {
577                         return nil, err
578                 }
579                 i, err := strconv.Atoi(strings.TrimSuffix(string(b), "\n"))
580                 if err != nil {
581                         return nil, err
582                 }
583                 cfg.MCDTxIfis[n] = i
584         }
585
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 {
589                         return nil, err
590                 }
591         }
592         if cfgDirExists(src, "notify", "freq") {
593                 if notify.Freq, err = cfgDirReadFromTo(src, "notify", "freq"); err != nil {
594                         return nil, err
595                 }
596         }
597         fis, err = os.ReadDir(filepath.Join(src, "notify", "exec"))
598         if err != nil && !errors.Is(err, fs.ErrNotExist) {
599                 return nil, err
600         }
601         for _, fi := range fis {
602                 n := fi.Name()
603                 if n[0] == '.' || !fi.IsDir() {
604                         continue
605                 }
606                 if notify.Exec[fi.Name()], err = cfgDirReadFromTo(src, "notify", "exec", n); err != nil {
607                         return nil, err
608                 }
609         }
610         if notify.File != nil || notify.Freq != nil || len(notify.Exec) > 0 {
611                 cfg.Notify = &notify
612         }
613
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 {
617                         return nil, err
618                 }
619                 if self.ExchPub, err = cfgDirLoadMust(src, "self", "exchpub"); err != nil {
620                         return nil, err
621                 }
622                 if self.ExchPrv, err = cfgDirLoadMust(src, "self", "exchprv"); err != nil {
623                         return nil, err
624                 }
625                 if self.SignPub, err = cfgDirLoadMust(src, "self", "signpub"); err != nil {
626                         return nil, err
627                 }
628                 if self.SignPrv, err = cfgDirLoadMust(src, "self", "signprv"); err != nil {
629                         return nil, err
630                 }
631                 if self.NoisePub, err = cfgDirLoadMust(src, "self", "noisepub"); err != nil {
632                         return nil, err
633                 }
634                 if self.NoisePrv, err = cfgDirLoadMust(src, "self", "noiseprv"); err != nil {
635                         return nil, err
636                 }
637                 cfg.Self = &self
638         } else if !errors.Is(err, fs.ErrNotExist) {
639                 return nil, err
640         }
641
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) {
645                 return nil, err
646         }
647         for _, fi := range fis {
648                 n := fi.Name()
649                 if n[0] == '.' {
650                         continue
651                 }
652                 node := NodeJSON{}
653                 if node.Id, err = cfgDirLoadMust(src, "neigh", n, "id"); err != nil {
654                         return nil, err
655                 }
656                 if node.ExchPub, err = cfgDirLoadMust(src, "neigh", n, "exchpub"); err != nil {
657                         return nil, err
658                 }
659                 if node.SignPub, err = cfgDirLoadMust(src, "neigh", n, "signpub"); err != nil {
660                         return nil, err
661                 }
662                 if node.NoisePub, err = cfgDirLoadOpt(src, "neigh", n, "noisepub"); err != nil {
663                         return nil, err
664                 }
665                 if node.Incoming, err = cfgDirLoadOpt(src, "neigh", n, "incoming"); err != nil {
666                         return nil, err
667                 }
668
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) {
672                         return nil, err
673                 }
674                 for _, fi2 := range fis2 {
675                         n2 := fi2.Name()
676                         if n2[0] == '.' {
677                                 continue
678                         }
679                         s, err := cfgDirLoadMust(src, "neigh", n, "exec", n2)
680                         if err != nil {
681                                 return nil, err
682                         }
683                         node.Exec[n2] = strings.Split(s, "\n")
684                 }
685
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 {
689                                 return nil, err
690                         }
691
692                         i64, err := cfgDirLoadIntOpt(src, "neigh", n, "freq", "chunked")
693                         if err != nil {
694                                 return nil, err
695                         }
696                         if i64 != nil {
697                                 i := uint64(*i64)
698                                 node.Freq.Chunked = &i
699                         }
700
701                         i64, err = cfgDirLoadIntOpt(src, "neigh", n, "freq", "minsize")
702                         if err != nil {
703                                 return nil, err
704                         }
705                         if i64 != nil {
706                                 i := uint64(*i64)
707                                 node.Freq.MinSize = &i
708                         }
709
710                         i64, err = cfgDirLoadIntOpt(src, "neigh", n, "freq", "maxsize")
711                         if err != nil {
712                                 return nil, err
713                         }
714                         if i64 != nil {
715                                 i := uint64(*i64)
716                                 node.Freq.MaxSize = &i
717                         }
718                 }
719
720                 if cfgDirExists(src, "neigh", n, "ack") {
721                         node.ACK = &NodeACKJSON{}
722                         i64, err := cfgDirLoadIntOpt(src, "neigh", n, "ack", "minsize")
723                         if err != nil {
724                                 return nil, err
725                         }
726                         if i64 != nil {
727                                 i := uint64(*i64)
728                                 node.ACK.MinSize = &i
729                         }
730                         if node.ACK.Nice, err = cfgDirLoadOpt(
731                                 src, "neigh", n, "ack", "nice",
732                         ); err != nil {
733                                 return nil, err
734                         }
735                 }
736
737                 via, err := cfgDirLoadOpt(src, "neigh", n, "via")
738                 if err != nil {
739                         return nil, err
740                 }
741                 if via != nil {
742                         node.Via = strings.Split(*via, "\n")
743                 }
744
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) {
748                         return nil, err
749                 }
750                 for _, fi2 := range fis2 {
751                         n2 := fi2.Name()
752                         if n2[0] == '.' {
753                                 continue
754                         }
755                         if node.Addrs[n2], err = cfgDirLoadMust(src, "neigh", n, "addrs", n2); err != nil {
756                                 return nil, err
757                         }
758                 }
759
760                 i64, err := cfgDirLoadIntOpt(src, "neigh", n, "rxrate")
761                 if err != nil {
762                         return nil, err
763                 }
764                 if i64 != nil {
765                         i := int(*i64)
766                         node.RxRate = &i
767                 }
768
769                 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "txrate")
770                 if err != nil {
771                         return nil, err
772                 }
773                 if i64 != nil {
774                         i := int(*i64)
775                         node.TxRate = &i
776                 }
777
778                 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "onlinedeadline")
779                 if err != nil {
780                         return nil, err
781                 }
782                 if i64 != nil {
783                         i := uint(*i64)
784                         node.OnlineDeadline = &i
785                 }
786
787                 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "maxonlinetime")
788                 if err != nil {
789                         return nil, err
790                 }
791                 if i64 != nil {
792                         i := uint(*i64)
793                         node.MaxOnlineTime = &i
794                 }
795
796                 fis2, err = os.ReadDir(filepath.Join(src, "neigh", n, "calls"))
797                 if err != nil && !errors.Is(err, fs.ErrNotExist) {
798                         return nil, err
799                 }
800                 callsIdx := make([]int, 0, len(fis2))
801                 for _, fi2 := range fis2 {
802                         n2 := fi2.Name()
803                         if !fi2.IsDir() {
804                                 continue
805                         }
806                         i, err := strconv.Atoi(n2)
807                         if err != nil {
808                                 continue
809                         }
810                         callsIdx = append(callsIdx, i)
811                 }
812                 sort.Ints(callsIdx)
813                 for _, i := range callsIdx {
814                         call := CallJSON{}
815                         is := strconv.Itoa(i)
816                         if call.Cron, err = cfgDirLoadMust(
817                                 src, "neigh", n, "calls", is, "cron",
818                         ); err != nil {
819                                 return nil, err
820                         }
821                         if call.Nice, err = cfgDirLoadOpt(
822                                 src, "neigh", n, "calls", is, "nice",
823                         ); err != nil {
824                                 return nil, err
825                         }
826                         if call.Xx, err = cfgDirLoadOpt(
827                                 src, "neigh", n, "calls", is, "xx",
828                         ); err != nil {
829                                 return nil, err
830                         }
831
832                         i64, err = cfgDirLoadIntOpt(src, "neigh", n, "calls", is, "rxrate")
833                         if err != nil {
834                                 return nil, err
835                         }
836                         if i64 != nil {
837                                 i := int(*i64)
838                                 call.RxRate = &i
839                         }
840
841                         i64, err = cfgDirLoadIntOpt(src, "neigh", n, "calls", is, "txrate")
842                         if err != nil {
843                                 return nil, err
844                         }
845                         if i64 != nil {
846                                 i := int(*i64)
847                                 call.TxRate = &i
848                         }
849
850                         if call.Addr, err = cfgDirLoadOpt(
851                                 src, "neigh", n, "calls", is, "addr",
852                         ); err != nil {
853                                 return nil, err
854                         }
855
856                         i64, err = cfgDirLoadIntOpt(src, "neigh", n, "calls", is, "onlinedeadline")
857                         if err != nil {
858                                 return nil, err
859                         }
860                         if i64 != nil {
861                                 i := uint(*i64)
862                                 call.OnlineDeadline = &i
863                         }
864
865                         i64, err = cfgDirLoadIntOpt(src, "neigh", n, "calls", is, "maxonlinetime")
866                         if err != nil {
867                                 return nil, err
868                         }
869                         if i64 != nil {
870                                 i := uint(*i64)
871                                 call.MaxOnlineTime = &i
872                         }
873
874                         if cfgDirExists(src, "neigh", n, "calls", is, "when-tx-exists") {
875                                 call.WhenTxExists = true
876                         }
877                         if cfgDirExists(src, "neigh", n, "calls", is, "nock") {
878                                 call.NoCK = true
879                         }
880                         if cfgDirExists(src, "neigh", n, "calls", is, "mcd-ignore") {
881                                 call.MCDIgnore = true
882                         }
883                         if cfgDirExists(src, "neigh", n, "calls", is, "autotoss") {
884                                 call.AutoToss = true
885                         }
886                         if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-doseen") {
887                                 call.AutoTossDoSeen = true
888                         }
889                         if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-nofile") {
890                                 call.AutoTossNoFile = true
891                         }
892                         if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-nofreq") {
893                                 call.AutoTossNoFreq = true
894                         }
895                         if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-noexec") {
896                                 call.AutoTossNoExec = true
897                         }
898                         if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-notrns") {
899                                 call.AutoTossNoTrns = true
900                         }
901                         if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-noarea") {
902                                 call.AutoTossNoArea = true
903                         }
904                         if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-noack") {
905                                 call.AutoTossNoACK = true
906                         }
907                         if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-gen-ack") {
908                                 call.AutoTossGenACK = true
909                         }
910                         node.Calls = append(node.Calls, call)
911                 }
912                 cfg.Neigh[n] = node
913         }
914
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) {
918                 return nil, err
919         }
920         for _, fi := range fis {
921                 n := fi.Name()
922                 if n[0] == '.' {
923                         continue
924                 }
925                 area := AreaJSON{}
926                 if area.Id, err = cfgDirLoadMust(src, "areas", n, "id"); err != nil {
927                         return nil, err
928                 }
929                 if area.Pub, err = cfgDirLoadOpt(src, "areas", n, "pub"); err != nil {
930                         return nil, err
931                 }
932                 if area.Prv, err = cfgDirLoadOpt(src, "areas", n, "prv"); err != nil {
933                         return nil, err
934                 }
935
936                 subs, err := cfgDirLoadOpt(src, "areas", n, "subs")
937                 if err != nil {
938                         return nil, err
939                 }
940                 if subs != nil {
941                         area.Subs = strings.Split(*subs, "\n")
942                 }
943
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) {
947                         return nil, err
948                 }
949                 for _, fi2 := range fis2 {
950                         n2 := fi2.Name()
951                         if n2[0] == '.' {
952                                 continue
953                         }
954                         s, err := cfgDirLoadMust(src, "areas", n, "exec", n2)
955                         if err != nil {
956                                 return nil, err
957                         }
958                         area.Exec[n2] = strings.Split(s, "\n")
959                 }
960
961                 if area.Incoming, err = cfgDirLoadOpt(src, "areas", n, "incoming"); err != nil {
962                         return nil, err
963                 }
964
965                 if cfgDirExists(src, "areas", n, "allow-unknown") {
966                         area.AllowUnknown = true
967                 }
968                 cfg.Areas[n] = area
969         }
970
971         fis, err = os.ReadDir(filepath.Join(src, "yggdrasil-aliases"))
972         if err != nil && !errors.Is(err, fs.ErrNotExist) {
973                 return nil, err
974         }
975         if len(fis) > 0 {
976                 cfg.YggdrasilAliases = make(map[string]string, len(fis))
977         }
978         for _, fi := range fis {
979                 n := fi.Name()
980                 if n[0] == '.' {
981                         continue
982                 }
983                 b, err := os.ReadFile(filepath.Join(src, "yggdrasil-aliases", fi.Name()))
984                 if err != nil {
985                         return nil, err
986                 }
987                 cfg.YggdrasilAliases[n] = strings.TrimSuffix(string(b), "\n")
988         }
989
990         return &cfg, nil
991 }