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