]> Cypherpunks.ru repositories - nncp.git/blob - src/cfgdir.go
f9378c8b34c6abd94294a44fc4842f73c209e052
[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 len(n.Via) > 0 {
264                         if err = cfgDirSave(
265                                 strings.Join(n.Via, "\n"),
266                                 dst, "neigh", name, "via",
267                         ); err != nil {
268                                 return
269                         }
270                 }
271
272                 if len(n.Addrs) > 0 {
273                         if err = cfgDirMkdir(dst, "neigh", name, "addrs"); err != nil {
274                                 return
275                         }
276                         for k, v := range n.Addrs {
277                                 if err = cfgDirSave(v, dst, "neigh", name, "addrs", k); err != nil {
278                                         return
279                                 }
280                         }
281                 }
282
283                 if err = cfgDirSave(n.RxRate, dst, "neigh", name, "rxrate"); err != nil {
284                         return
285                 }
286                 if err = cfgDirSave(n.TxRate, dst, "neigh", name, "txrate"); err != nil {
287                         return
288                 }
289                 if err = cfgDirSave(n.OnlineDeadline, dst, "neigh", name, "onlinedeadline"); err != nil {
290                         return
291                 }
292                 if err = cfgDirSave(n.MaxOnlineTime, dst, "neigh", name, "maxonlinetime"); err != nil {
293                         return
294                 }
295
296                 for i, call := range n.Calls {
297                         is := strconv.Itoa(i)
298                         if err = cfgDirMkdir(dst, "neigh", name, "calls", is); err != nil {
299                                 return
300                         }
301                         if err = cfgDirSave(call.Cron, dst, "neigh", name, "calls", is, "cron"); err != nil {
302                                 return
303                         }
304                         if err = cfgDirSave(call.Nice, dst, "neigh", name, "calls", is, "nice"); err != nil {
305                                 return
306                         }
307                         if err = cfgDirSave(call.Xx, dst, "neigh", name, "calls", is, "xx"); err != nil {
308                                 return
309                         }
310                         if err = cfgDirSave(call.RxRate, dst, "neigh", name, "calls", is, "rxrate"); err != nil {
311                                 return
312                         }
313                         if err = cfgDirSave(call.TxRate, dst, "neigh", name, "calls", is, "txrate"); err != nil {
314                                 return
315                         }
316                         if err = cfgDirSave(call.Addr, dst, "neigh", name, "calls", is, "addr"); err != nil {
317                                 return
318                         }
319                         if err = cfgDirSave(call.OnlineDeadline, dst, "neigh", name, "calls", is, "onlinedeadline"); err != nil {
320                                 return
321                         }
322                         if err = cfgDirSave(call.MaxOnlineTime, dst, "neigh", name, "calls", is, "maxonlinetime"); err != nil {
323                                 return
324                         }
325                         if call.WhenTxExists {
326                                 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "when-tx-exists"); err != nil {
327                                         return
328                                 }
329                         }
330                         if call.NoCK {
331                                 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "nock"); err != nil {
332                                         return
333                                 }
334                         }
335                         if call.MCDIgnore {
336                                 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "mcd-ignore"); err != nil {
337                                         return
338                                 }
339                         }
340                         if call.AutoToss {
341                                 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss"); err != nil {
342                                         return
343                                 }
344                         }
345                         if call.AutoTossDoSeen {
346                                 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-doseen"); err != nil {
347                                         return
348                                 }
349                         }
350                         if call.AutoTossNoFile {
351                                 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-nofile"); err != nil {
352                                         return
353                                 }
354                         }
355                         if call.AutoTossNoFreq {
356                                 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-nofreq"); err != nil {
357                                         return
358                                 }
359                         }
360                         if call.AutoTossNoExec {
361                                 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-noexec"); err != nil {
362                                         return
363                                 }
364                         }
365                         if call.AutoTossNoTrns {
366                                 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-notrns"); err != nil {
367                                         return
368                                 }
369                         }
370                         if call.AutoTossNoArea {
371                                 if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-noarea"); err != nil {
372                                         return
373                                 }
374                         }
375                 }
376         }
377
378         for name, a := range cfg.Areas {
379                 if err = cfgDirMkdir(dst, "areas", name); err != nil {
380                         return
381                 }
382                 if err = cfgDirSave(a.Id, dst, "areas", name, "id"); err != nil {
383                         return
384                 }
385                 if err = cfgDirSave(a.Pub, dst, "areas", name, "pub"); err != nil {
386                         return
387                 }
388                 if err = cfgDirSave(a.Prv, dst, "areas", name, "prv"); err != nil {
389                         return
390                 }
391                 if err = cfgDirSave(a.Incoming, dst, "areas", name, "incoming"); err != nil {
392                         return
393                 }
394                 if a.AllowUnknown {
395                         if err = cfgDirTouch(dst, "areas", name, "allow-unknown"); err != nil {
396                                 return
397                         }
398                 }
399                 if len(a.Exec) > 0 {
400                         if err = cfgDirMkdir(dst, "areas", name, "exec"); err != nil {
401                                 return
402                         }
403                         for k, v := range a.Exec {
404                                 if err = cfgDirSave(
405                                         strings.Join(v, "\n"),
406                                         dst, "areas", name, "exec", k,
407                                 ); err != nil {
408                                         return
409                                 }
410                         }
411                 }
412                 if len(a.Subs) > 0 {
413                         if err = cfgDirSave(
414                                 strings.Join(a.Subs, "\n"),
415                                 dst, "areas", name, "subs",
416                         ); err != nil {
417                                 return
418                         }
419                 }
420         }
421
422         if len(cfg.YggdrasilAliases) > 0 {
423                 if err = cfgDirMkdir(dst, "yggdrasil-aliases"); err != nil {
424                         return
425                 }
426                 for alias, v := range cfg.YggdrasilAliases {
427                         if err = cfgDirSave(v, dst, "yggdrasil-aliases", alias); err != nil {
428                                 return
429                         }
430                 }
431         }
432
433         return
434 }
435
436 func cfgDirLoad(src ...string) (v string, exists bool, err error) {
437         b, err := os.ReadFile(filepath.Join(src...))
438         if err != nil {
439                 if errors.Is(err, fs.ErrNotExist) {
440                         return "", false, nil
441                 }
442                 return "", false, err
443         }
444         return strings.TrimSuffix(string(b), "\n"), true, nil
445 }
446
447 func cfgDirLoadMust(src ...string) (v string, err error) {
448         s, exists, err := cfgDirLoad(src...)
449         if err != nil {
450                 return "", err
451         }
452         if !exists {
453                 return "", fmt.Errorf("required \"%s\" does not exist", src[len(src)-1])
454         }
455         return s, nil
456 }
457
458 func cfgDirLoadOpt(src ...string) (v *string, err error) {
459         s, exists, err := cfgDirLoad(src...)
460         if err != nil {
461                 return nil, err
462         }
463         if !exists {
464                 return nil, nil
465         }
466         return &s, nil
467 }
468
469 func cfgDirLoadIntOpt(src ...string) (i64 *int64, err error) {
470         s, err := cfgDirLoadOpt(src...)
471         if err != nil {
472                 return nil, err
473         }
474         if s == nil {
475                 return nil, nil
476         }
477         i, err := strconv.ParseInt(*s, 10, 64)
478         if err != nil {
479                 return nil, err
480         }
481         return &i, nil
482 }
483
484 func cfgDirExists(src ...string) bool {
485         if _, err := os.Stat(filepath.Join(src...)); err == nil {
486                 return true
487         }
488         return false
489 }
490
491 func cfgDirReadFromTo(src ...string) (*FromToJSON, error) {
492         fromTo := FromToJSON{}
493
494         var err error
495         fromTo.From, err = cfgDirLoadMust(append(src, "from")...)
496         if err != nil {
497                 return nil, err
498         }
499
500         fromTo.To, err = cfgDirLoadMust(append(src, "to")...)
501         if err != nil {
502                 return nil, err
503         }
504
505         return &fromTo, nil
506 }
507
508 func DirToCfg(src string) (*CfgJSON, error) {
509         cfg := CfgJSON{}
510         var err error
511
512         cfg.Spool, err = cfgDirLoadMust(src, "spool")
513         if err != nil {
514                 return nil, err
515         }
516         cfg.Log, err = cfgDirLoadMust(src, "log")
517         if err != nil {
518                 return nil, err
519         }
520
521         if cfg.Umask, err = cfgDirLoadOpt(src, "umask"); err != nil {
522                 return nil, err
523         }
524         cfg.OmitPrgrs = cfgDirExists(src, "noprogress")
525         cfg.NoHdr = cfgDirExists(src, "nohdr")
526
527         sp, err := cfgDirLoadOpt(src, "mcd-listen")
528         if err != nil {
529                 return nil, err
530         }
531         if sp != nil {
532                 cfg.MCDRxIfis = strings.Split(*sp, "\n")
533         }
534
535         fis, err := os.ReadDir(filepath.Join(src, "mcd-send"))
536         if err != nil && !errors.Is(err, fs.ErrNotExist) {
537                 return nil, err
538         }
539         if len(fis) > 0 {
540                 cfg.MCDTxIfis = make(map[string]int, len(fis))
541         }
542         for _, fi := range fis {
543                 n := fi.Name()
544                 if n[0] == '.' {
545                         continue
546                 }
547                 b, err := os.ReadFile(filepath.Join(src, "mcd-send", fi.Name()))
548                 if err != nil {
549                         return nil, err
550                 }
551                 i, err := strconv.Atoi(strings.TrimSuffix(string(b), "\n"))
552                 if err != nil {
553                         return nil, err
554                 }
555                 cfg.MCDTxIfis[n] = i
556         }
557
558         notify := NotifyJSON{Exec: make(map[string]*FromToJSON)}
559         if cfgDirExists(src, "notify", "file") {
560                 if notify.File, err = cfgDirReadFromTo(src, "notify", "file"); err != nil {
561                         return nil, err
562                 }
563         }
564         if cfgDirExists(src, "notify", "freq") {
565                 if notify.Freq, err = cfgDirReadFromTo(src, "notify", "freq"); err != nil {
566                         return nil, err
567                 }
568         }
569         fis, err = os.ReadDir(filepath.Join(src, "notify", "exec"))
570         if err != nil && !errors.Is(err, fs.ErrNotExist) {
571                 return nil, err
572         }
573         for _, fi := range fis {
574                 n := fi.Name()
575                 if n[0] == '.' || !fi.IsDir() {
576                         continue
577                 }
578                 if notify.Exec[fi.Name()], err = cfgDirReadFromTo(src, "notify", "exec", n); err != nil {
579                         return nil, err
580                 }
581         }
582         if notify.File != nil || notify.Freq != nil || len(notify.Exec) > 0 {
583                 cfg.Notify = &notify
584         }
585
586         if _, err = os.ReadDir(filepath.Join(src, "self")); err == nil {
587                 self := NodeOurJSON{}
588                 if self.Id, err = cfgDirLoadMust(src, "self", "id"); err != nil {
589                         return nil, err
590                 }
591                 if self.ExchPub, err = cfgDirLoadMust(src, "self", "exchpub"); err != nil {
592                         return nil, err
593                 }
594                 if self.ExchPrv, err = cfgDirLoadMust(src, "self", "exchprv"); err != nil {
595                         return nil, err
596                 }
597                 if self.SignPub, err = cfgDirLoadMust(src, "self", "signpub"); err != nil {
598                         return nil, err
599                 }
600                 if self.SignPrv, err = cfgDirLoadMust(src, "self", "signprv"); err != nil {
601                         return nil, err
602                 }
603                 if self.NoisePub, err = cfgDirLoadMust(src, "self", "noisepub"); err != nil {
604                         return nil, err
605                 }
606                 if self.NoisePrv, err = cfgDirLoadMust(src, "self", "noiseprv"); err != nil {
607                         return nil, err
608                 }
609                 cfg.Self = &self
610         } else if !errors.Is(err, fs.ErrNotExist) {
611                 return nil, err
612         }
613
614         cfg.Neigh = make(map[string]NodeJSON)
615         fis, err = os.ReadDir(filepath.Join(src, "neigh"))
616         if err != nil && !errors.Is(err, fs.ErrNotExist) {
617                 return nil, err
618         }
619         for _, fi := range fis {
620                 n := fi.Name()
621                 if n[0] == '.' {
622                         continue
623                 }
624                 node := NodeJSON{}
625                 if node.Id, err = cfgDirLoadMust(src, "neigh", n, "id"); err != nil {
626                         return nil, err
627                 }
628                 if node.ExchPub, err = cfgDirLoadMust(src, "neigh", n, "exchpub"); err != nil {
629                         return nil, err
630                 }
631                 if node.SignPub, err = cfgDirLoadMust(src, "neigh", n, "signpub"); err != nil {
632                         return nil, err
633                 }
634                 if node.NoisePub, err = cfgDirLoadOpt(src, "neigh", n, "noisepub"); err != nil {
635                         return nil, err
636                 }
637                 if node.Incoming, err = cfgDirLoadOpt(src, "neigh", n, "incoming"); err != nil {
638                         return nil, err
639                 }
640
641                 node.Exec = make(map[string][]string)
642                 fis2, err := os.ReadDir(filepath.Join(src, "neigh", n, "exec"))
643                 if err != nil && !errors.Is(err, fs.ErrNotExist) {
644                         return nil, err
645                 }
646                 for _, fi2 := range fis2 {
647                         n2 := fi2.Name()
648                         if n2[0] == '.' {
649                                 continue
650                         }
651                         s, err := cfgDirLoadMust(src, "neigh", n, "exec", n2)
652                         if err != nil {
653                                 return nil, err
654                         }
655                         node.Exec[n2] = strings.Split(s, "\n")
656                 }
657
658                 if cfgDirExists(src, "neigh", n, "freq") {
659                         node.Freq = &NodeFreqJSON{}
660                         if node.Freq.Path, err = cfgDirLoadOpt(src, "neigh", n, "freq", "path"); err != nil {
661                                 return nil, err
662                         }
663
664                         i64, err := cfgDirLoadIntOpt(src, "neigh", n, "freq", "chunked")
665                         if err != nil {
666                                 return nil, err
667                         }
668                         if i64 != nil {
669                                 i := uint64(*i64)
670                                 node.Freq.Chunked = &i
671                         }
672
673                         i64, err = cfgDirLoadIntOpt(src, "neigh", n, "freq", "minsize")
674                         if err != nil {
675                                 return nil, err
676                         }
677                         if i64 != nil {
678                                 i := uint64(*i64)
679                                 node.Freq.MinSize = &i
680                         }
681
682                         i64, err = cfgDirLoadIntOpt(src, "neigh", n, "freq", "maxsize")
683                         if err != nil {
684                                 return nil, err
685                         }
686                         if i64 != nil {
687                                 i := uint64(*i64)
688                                 node.Freq.MaxSize = &i
689                         }
690                 }
691
692                 via, err := cfgDirLoadOpt(src, "neigh", n, "via")
693                 if err != nil {
694                         return nil, err
695                 }
696                 if via != nil {
697                         node.Via = strings.Split(*via, "\n")
698                 }
699
700                 node.Addrs = make(map[string]string)
701                 fis2, err = os.ReadDir(filepath.Join(src, "neigh", n, "addrs"))
702                 if err != nil && !errors.Is(err, fs.ErrNotExist) {
703                         return nil, err
704                 }
705                 for _, fi2 := range fis2 {
706                         n2 := fi2.Name()
707                         if n2[0] == '.' {
708                                 continue
709                         }
710                         if node.Addrs[n2], err = cfgDirLoadMust(src, "neigh", n, "addrs", n2); err != nil {
711                                 return nil, err
712                         }
713                 }
714
715                 i64, err := cfgDirLoadIntOpt(src, "neigh", n, "rxrate")
716                 if err != nil {
717                         return nil, err
718                 }
719                 if i64 != nil {
720                         i := int(*i64)
721                         node.RxRate = &i
722                 }
723
724                 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "txrate")
725                 if err != nil {
726                         return nil, err
727                 }
728                 if i64 != nil {
729                         i := int(*i64)
730                         node.TxRate = &i
731                 }
732
733                 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "onlinedeadline")
734                 if err != nil {
735                         return nil, err
736                 }
737                 if i64 != nil {
738                         i := uint(*i64)
739                         node.OnlineDeadline = &i
740                 }
741
742                 i64, err = cfgDirLoadIntOpt(src, "neigh", n, "maxonlinetime")
743                 if err != nil {
744                         return nil, err
745                 }
746                 if i64 != nil {
747                         i := uint(*i64)
748                         node.MaxOnlineTime = &i
749                 }
750
751                 fis2, err = os.ReadDir(filepath.Join(src, "neigh", n, "calls"))
752                 if err != nil && !errors.Is(err, fs.ErrNotExist) {
753                         return nil, err
754                 }
755                 callsIdx := make([]int, 0, len(fis2))
756                 for _, fi2 := range fis2 {
757                         n2 := fi2.Name()
758                         if !fi2.IsDir() {
759                                 continue
760                         }
761                         i, err := strconv.Atoi(n2)
762                         if err != nil {
763                                 continue
764                         }
765                         callsIdx = append(callsIdx, i)
766                 }
767                 sort.Ints(callsIdx)
768                 for _, i := range callsIdx {
769                         call := CallJSON{}
770                         is := strconv.Itoa(i)
771                         if call.Cron, err = cfgDirLoadMust(
772                                 src, "neigh", n, "calls", is, "cron",
773                         ); err != nil {
774                                 return nil, err
775                         }
776                         if call.Nice, err = cfgDirLoadOpt(
777                                 src, "neigh", n, "calls", is, "nice",
778                         ); err != nil {
779                                 return nil, err
780                         }
781                         if call.Xx, err = cfgDirLoadOpt(
782                                 src, "neigh", n, "calls", is, "xx",
783                         ); err != nil {
784                                 return nil, err
785                         }
786
787                         i64, err = cfgDirLoadIntOpt(src, "neigh", n, "calls", is, "rxrate")
788                         if err != nil {
789                                 return nil, err
790                         }
791                         if i64 != nil {
792                                 i := int(*i64)
793                                 call.RxRate = &i
794                         }
795
796                         i64, err = cfgDirLoadIntOpt(src, "neigh", n, "calls", is, "txrate")
797                         if err != nil {
798                                 return nil, err
799                         }
800                         if i64 != nil {
801                                 i := int(*i64)
802                                 call.TxRate = &i
803                         }
804
805                         if call.Addr, err = cfgDirLoadOpt(
806                                 src, "neigh", n, "calls", is, "addr",
807                         ); err != nil {
808                                 return nil, err
809                         }
810
811                         i64, err = cfgDirLoadIntOpt(src, "neigh", n, "calls", is, "onlinedeadline")
812                         if err != nil {
813                                 return nil, err
814                         }
815                         if i64 != nil {
816                                 i := uint(*i64)
817                                 call.OnlineDeadline = &i
818                         }
819
820                         i64, err = cfgDirLoadIntOpt(src, "neigh", n, "calls", is, "maxonlinetime")
821                         if err != nil {
822                                 return nil, err
823                         }
824                         if i64 != nil {
825                                 i := uint(*i64)
826                                 call.MaxOnlineTime = &i
827                         }
828
829                         if cfgDirExists(src, "neigh", n, "calls", is, "when-tx-exists") {
830                                 call.WhenTxExists = true
831                         }
832                         if cfgDirExists(src, "neigh", n, "calls", is, "nock") {
833                                 call.NoCK = true
834                         }
835                         if cfgDirExists(src, "neigh", n, "calls", is, "mcd-ignore") {
836                                 call.MCDIgnore = true
837                         }
838                         if cfgDirExists(src, "neigh", n, "calls", is, "autotoss") {
839                                 call.AutoToss = true
840                         }
841                         if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-doseen") {
842                                 call.AutoTossDoSeen = true
843                         }
844                         if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-nofile") {
845                                 call.AutoTossNoFile = true
846                         }
847                         if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-nofreq") {
848                                 call.AutoTossNoFreq = true
849                         }
850                         if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-noexec") {
851                                 call.AutoTossNoExec = true
852                         }
853                         if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-notrns") {
854                                 call.AutoTossNoTrns = true
855                         }
856                         if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-noarea") {
857                                 call.AutoTossNoArea = true
858                         }
859                         node.Calls = append(node.Calls, call)
860                 }
861                 cfg.Neigh[n] = node
862         }
863
864         cfg.Areas = make(map[string]AreaJSON)
865         fis, err = os.ReadDir(filepath.Join(src, "areas"))
866         if err != nil && !errors.Is(err, fs.ErrNotExist) {
867                 return nil, err
868         }
869         for _, fi := range fis {
870                 n := fi.Name()
871                 if n[0] == '.' {
872                         continue
873                 }
874                 area := AreaJSON{}
875                 if area.Id, err = cfgDirLoadMust(src, "areas", n, "id"); err != nil {
876                         return nil, err
877                 }
878                 if area.Pub, err = cfgDirLoadOpt(src, "areas", n, "pub"); err != nil {
879                         return nil, err
880                 }
881                 if area.Prv, err = cfgDirLoadOpt(src, "areas", n, "prv"); err != nil {
882                         return nil, err
883                 }
884
885                 subs, err := cfgDirLoadOpt(src, "areas", n, "subs")
886                 if err != nil {
887                         return nil, err
888                 }
889                 if subs != nil {
890                         area.Subs = strings.Split(*subs, "\n")
891                 }
892
893                 area.Exec = make(map[string][]string)
894                 fis2, err := os.ReadDir(filepath.Join(src, "areas", n, "exec"))
895                 if err != nil && !errors.Is(err, fs.ErrNotExist) {
896                         return nil, err
897                 }
898                 for _, fi2 := range fis2 {
899                         n2 := fi2.Name()
900                         if n2[0] == '.' {
901                                 continue
902                         }
903                         s, err := cfgDirLoadMust(src, "areas", n, "exec", n2)
904                         if err != nil {
905                                 return nil, err
906                         }
907                         area.Exec[n2] = strings.Split(s, "\n")
908                 }
909
910                 if area.Incoming, err = cfgDirLoadOpt(src, "areas", n, "incoming"); err != nil {
911                         return nil, err
912                 }
913
914                 if cfgDirExists(src, "areas", n, "allow-unknown") {
915                         area.AllowUnknown = true
916                 }
917                 cfg.Areas[n] = area
918         }
919
920         fis, err = os.ReadDir(filepath.Join(src, "yggdrasil-aliases"))
921         if err != nil && !errors.Is(err, fs.ErrNotExist) {
922                 return nil, err
923         }
924         if len(fis) > 0 {
925                 cfg.YggdrasilAliases = make(map[string]string, len(fis))
926         }
927         for _, fi := range fis {
928                 n := fi.Name()
929                 if n[0] == '.' {
930                         continue
931                 }
932                 b, err := os.ReadFile(filepath.Join(src, "yggdrasil-aliases", fi.Name()))
933                 if err != nil {
934                         return nil, err
935                 }
936                 cfg.YggdrasilAliases[n] = strings.TrimSuffix(string(b), "\n")
937         }
938
939         return &cfg, nil
940 }