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