]> Cypherpunks.ru repositories - nncp.git/blob - src/cfg.go
bacfd7e29243631c2ecb543d47217bb97b73c171
[nncp.git] / src / cfg.go
1 /*
2 NNCP -- Node to Node copy, utilities for store-and-forward data exchange
3 Copyright (C) 2016-2021 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         "bytes"
22         "encoding/json"
23         "errors"
24         "log"
25         "os"
26         "path"
27         "strconv"
28         "time"
29
30         "github.com/gorhill/cronexpr"
31         "github.com/hjson/hjson-go"
32         "golang.org/x/crypto/ed25519"
33         "golang.org/x/term"
34 )
35
36 const (
37         CfgPathEnv  = "NNCPCFG"
38         CfgSpoolEnv = "NNCPSPOOL"
39         CfgLogEnv   = "NNCPLOG"
40 )
41
42 var (
43         DefaultCfgPath      string = "/usr/local/etc/nncp.hjson"
44         DefaultSendmailPath string = "/usr/sbin/sendmail"
45         DefaultSpoolPath    string = "/var/spool/nncp"
46         DefaultLogPath      string = "/var/spool/nncp/log"
47 )
48
49 type NodeJSON struct {
50         Id       string              `json:"id"`
51         ExchPub  string              `json:"exchpub"`
52         SignPub  string              `json:"signpub"`
53         NoisePub *string             `json:"noisepub,omitempty"`
54         Exec     map[string][]string `json:"exec,omitempty"`
55         Incoming *string             `json:"incoming,omitempty"`
56         Freq     *NodeFreqJSON       `json:"freq,omitempty"`
57         Via      []string            `json:"via,omitempty"`
58         Calls    []CallJSON          `json:"calls,omitempty"`
59
60         Addrs map[string]string `json:"addrs,omitempty"`
61
62         RxRate         *int  `json:"rxrate,omitempty"`
63         TxRate         *int  `json:"txrate,omitempty"`
64         OnlineDeadline *uint `json:"onlinedeadline,omitempty"`
65         MaxOnlineTime  *uint `json:"maxonlinetime,omitempty"`
66 }
67
68 type NodeFreqJSON struct {
69         Path    *string `json:"path,omitempty"`
70         Chunked *uint64 `json:"chunked,omitempty"`
71         MinSize *uint64 `json:"minsize,omitempty"`
72         MaxSize *uint64 `json:"maxsize,omitempty"`
73 }
74
75 type CallJSON struct {
76         Cron           string
77         Nice           *string `json:"nice,omitempty"`
78         Xx             *string `json:"xx,omitempty"`
79         RxRate         *int    `json:"rxrate,omitempty"`
80         TxRate         *int    `json:"txrate,omitempty"`
81         Addr           *string `json:"addr,omitempty"`
82         OnlineDeadline *uint   `json:"onlinedeadline,omitempty"`
83         MaxOnlineTime  *uint   `json:"maxonlinetime,omitempty"`
84         WhenTxExists   *bool   `json:"when-tx-exists,omitempty"`
85         NoCK           *bool   `json:"nock"`
86         MCDIgnore      *bool   `json:"mcd-ignore"`
87
88         AutoToss       *bool `json:"autotoss,omitempty"`
89         AutoTossDoSeen *bool `json:"autotoss-doseen,omitempty"`
90         AutoTossNoFile *bool `json:"autotoss-nofile,omitempty"`
91         AutoTossNoFreq *bool `json:"autotoss-nofreq,omitempty"`
92         AutoTossNoExec *bool `json:"autotoss-noexec,omitempty"`
93         AutoTossNoTrns *bool `json:"autotoss-notrns,omitempty"`
94         AutoTossNoArea *bool `json:"autotoss-noarea,omitempty"`
95 }
96
97 type NodeOurJSON struct {
98         Id       string `json:"id"`
99         ExchPub  string `json:"exchpub"`
100         ExchPrv  string `json:"exchprv"`
101         SignPub  string `json:"signpub"`
102         SignPrv  string `json:"signprv"`
103         NoisePrv string `json:"noiseprv"`
104         NoisePub string `json:"noisepub"`
105 }
106
107 type FromToJSON struct {
108         From string
109         To   string
110 }
111
112 type NotifyJSON struct {
113         File *FromToJSON            `json:"file,omitempty"`
114         Freq *FromToJSON            `json:"freq,omitempty"`
115         Exec map[string]*FromToJSON `json:"exec,omitempty"`
116 }
117
118 type AreaJSON struct {
119         Id  string  `json:"id"`
120         Pub string  `json:"pub"`
121         Prv *string `json:"prv,omitempty"`
122
123         Subs []string `json:"subs"`
124
125         Exec     map[string][]string `json:"exec,omitempty"`
126         Incoming *string             `json:"incoming,omitempty"`
127
128         AllowUnknown *bool `json:"allow-unknown,omitempty"`
129 }
130
131 type CfgJSON struct {
132         Spool string `json:"spool"`
133         Log   string `json:"log"`
134         Umask string `json:"umask,omitempty"`
135
136         OmitPrgrs bool `json:"noprogress,omitempty"`
137         NoHdr     bool `json:"nohdr,omitempty"`
138
139         Notify *NotifyJSON `json:"notify,omitempty"`
140
141         Self  *NodeOurJSON        `json:"self"`
142         Neigh map[string]NodeJSON `json:"neigh"`
143
144         MCDRxIfis []string       `json:"mcd-listen"`
145         MCDTxIfis map[string]int `json:"mcd-send"`
146
147         Areas map[string]AreaJSON `json:"areas"`
148 }
149
150 func NewNode(name string, cfg NodeJSON) (*Node, error) {
151         nodeId, err := NodeIdFromString(cfg.Id)
152         if err != nil {
153                 return nil, err
154         }
155
156         exchPub, err := Base32Codec.DecodeString(cfg.ExchPub)
157         if err != nil {
158                 return nil, err
159         }
160         if len(exchPub) != 32 {
161                 return nil, errors.New("Invalid exchPub size")
162         }
163
164         signPub, err := Base32Codec.DecodeString(cfg.SignPub)
165         if err != nil {
166                 return nil, err
167         }
168         if len(signPub) != ed25519.PublicKeySize {
169                 return nil, errors.New("Invalid signPub size")
170         }
171
172         var noisePub []byte
173         if cfg.NoisePub != nil {
174                 noisePub, err = Base32Codec.DecodeString(*cfg.NoisePub)
175                 if err != nil {
176                         return nil, err
177                 }
178                 if len(noisePub) != 32 {
179                         return nil, errors.New("Invalid noisePub size")
180                 }
181         }
182
183         var incoming *string
184         if cfg.Incoming != nil {
185                 inc := path.Clean(*cfg.Incoming)
186                 if !path.IsAbs(inc) {
187                         return nil, errors.New("Incoming path must be absolute")
188                 }
189                 incoming = &inc
190         }
191
192         var freqPath *string
193         freqChunked := int64(MaxFileSize)
194         var freqMinSize int64
195         freqMaxSize := int64(MaxFileSize)
196         if cfg.Freq != nil {
197                 f := cfg.Freq
198                 if f.Path != nil {
199                         fPath := path.Clean(*f.Path)
200                         if !path.IsAbs(fPath) {
201                                 return nil, errors.New("freq.path path must be absolute")
202                         }
203                         freqPath = &fPath
204                 }
205                 if f.Chunked != nil {
206                         if *f.Chunked == 0 {
207                                 return nil, errors.New("freq.chunked value must be greater than zero")
208                         }
209                         freqChunked = int64(*f.Chunked) * 1024
210                 }
211                 if f.MinSize != nil {
212                         freqMinSize = int64(*f.MinSize) * 1024
213                 }
214                 if f.MaxSize != nil {
215                         freqMaxSize = int64(*f.MaxSize) * 1024
216                 }
217         }
218
219         defRxRate := 0
220         if cfg.RxRate != nil && *cfg.RxRate > 0 {
221                 defRxRate = *cfg.RxRate
222         }
223         defTxRate := 0
224         if cfg.TxRate != nil && *cfg.TxRate > 0 {
225                 defTxRate = *cfg.TxRate
226         }
227
228         defOnlineDeadline := DefaultDeadline
229         if cfg.OnlineDeadline != nil {
230                 if *cfg.OnlineDeadline <= 0 {
231                         return nil, errors.New("OnlineDeadline must be at least 1 second")
232                 }
233                 defOnlineDeadline = time.Duration(*cfg.OnlineDeadline) * time.Second
234         }
235         var defMaxOnlineTime time.Duration
236         if cfg.MaxOnlineTime != nil {
237                 defMaxOnlineTime = time.Duration(*cfg.MaxOnlineTime) * time.Second
238         }
239
240         var calls []*Call
241         for _, callCfg := range cfg.Calls {
242                 expr, err := cronexpr.Parse(callCfg.Cron)
243                 if err != nil {
244                         return nil, err
245                 }
246
247                 nice := uint8(255)
248                 if callCfg.Nice != nil {
249                         nice, err = NicenessParse(*callCfg.Nice)
250                         if err != nil {
251                                 return nil, err
252                         }
253                 }
254
255                 var xx TRxTx
256                 if callCfg.Xx != nil {
257                         switch *callCfg.Xx {
258                         case "rx":
259                                 xx = TRx
260                         case "tx":
261                                 xx = TTx
262                         default:
263                                 return nil, errors.New("xx field must be either \"rx\" or \"tx\"")
264                         }
265                 }
266
267                 rxRate := defRxRate
268                 if callCfg.RxRate != nil {
269                         rxRate = *callCfg.RxRate
270                 }
271                 txRate := defTxRate
272                 if callCfg.TxRate != nil {
273                         txRate = *callCfg.TxRate
274                 }
275
276                 var addr *string
277                 if callCfg.Addr != nil {
278                         if a, exists := cfg.Addrs[*callCfg.Addr]; exists {
279                                 addr = &a
280                         } else {
281                                 addr = callCfg.Addr
282                         }
283                 }
284
285                 onlineDeadline := defOnlineDeadline
286                 if callCfg.OnlineDeadline != nil {
287                         if *callCfg.OnlineDeadline == 0 {
288                                 return nil, errors.New("OnlineDeadline must be at least 1 second")
289                         }
290                         onlineDeadline = time.Duration(*callCfg.OnlineDeadline) * time.Second
291                 }
292
293                 call := Call{
294                         Cron:           expr,
295                         Nice:           nice,
296                         Xx:             xx,
297                         RxRate:         rxRate,
298                         TxRate:         txRate,
299                         Addr:           addr,
300                         OnlineDeadline: onlineDeadline,
301                 }
302
303                 if callCfg.MaxOnlineTime != nil {
304                         call.MaxOnlineTime = time.Duration(*callCfg.MaxOnlineTime) * time.Second
305                 }
306                 if callCfg.WhenTxExists != nil {
307                         call.WhenTxExists = *callCfg.WhenTxExists
308                 }
309                 if callCfg.NoCK != nil {
310                         call.NoCK = *callCfg.NoCK
311                 }
312                 if callCfg.MCDIgnore != nil {
313                         call.MCDIgnore = *callCfg.MCDIgnore
314                 }
315                 if callCfg.AutoToss != nil {
316                         call.AutoToss = *callCfg.AutoToss
317                 }
318                 if callCfg.AutoTossDoSeen != nil {
319                         call.AutoTossDoSeen = *callCfg.AutoTossDoSeen
320                 }
321                 if callCfg.AutoTossNoFile != nil {
322                         call.AutoTossNoFile = *callCfg.AutoTossNoFile
323                 }
324                 if callCfg.AutoTossNoFreq != nil {
325                         call.AutoTossNoFreq = *callCfg.AutoTossNoFreq
326                 }
327                 if callCfg.AutoTossNoExec != nil {
328                         call.AutoTossNoExec = *callCfg.AutoTossNoExec
329                 }
330                 if callCfg.AutoTossNoTrns != nil {
331                         call.AutoTossNoTrns = *callCfg.AutoTossNoTrns
332                 }
333                 if callCfg.AutoTossNoArea != nil {
334                         call.AutoTossNoArea = *callCfg.AutoTossNoArea
335                 }
336
337                 calls = append(calls, &call)
338         }
339
340         node := Node{
341                 Name:           name,
342                 Id:             nodeId,
343                 ExchPub:        new([32]byte),
344                 SignPub:        ed25519.PublicKey(signPub),
345                 Exec:           cfg.Exec,
346                 Incoming:       incoming,
347                 FreqPath:       freqPath,
348                 FreqChunked:    freqChunked,
349                 FreqMinSize:    freqMinSize,
350                 FreqMaxSize:    freqMaxSize,
351                 Calls:          calls,
352                 Addrs:          cfg.Addrs,
353                 RxRate:         defRxRate,
354                 TxRate:         defTxRate,
355                 OnlineDeadline: defOnlineDeadline,
356                 MaxOnlineTime:  defMaxOnlineTime,
357         }
358         copy(node.ExchPub[:], exchPub)
359         if len(noisePub) > 0 {
360                 node.NoisePub = new([32]byte)
361                 copy(node.NoisePub[:], noisePub)
362         }
363         return &node, nil
364 }
365
366 func NewNodeOur(cfg *NodeOurJSON) (*NodeOur, error) {
367         id, err := NodeIdFromString(cfg.Id)
368         if err != nil {
369                 return nil, err
370         }
371
372         exchPub, err := Base32Codec.DecodeString(cfg.ExchPub)
373         if err != nil {
374                 return nil, err
375         }
376         if len(exchPub) != 32 {
377                 return nil, errors.New("Invalid exchPub size")
378         }
379
380         exchPrv, err := Base32Codec.DecodeString(cfg.ExchPrv)
381         if err != nil {
382                 return nil, err
383         }
384         if len(exchPrv) != 32 {
385                 return nil, errors.New("Invalid exchPrv size")
386         }
387
388         signPub, err := Base32Codec.DecodeString(cfg.SignPub)
389         if err != nil {
390                 return nil, err
391         }
392         if len(signPub) != ed25519.PublicKeySize {
393                 return nil, errors.New("Invalid signPub size")
394         }
395
396         signPrv, err := Base32Codec.DecodeString(cfg.SignPrv)
397         if err != nil {
398                 return nil, err
399         }
400         if len(signPrv) != ed25519.PrivateKeySize {
401                 return nil, errors.New("Invalid signPrv size")
402         }
403
404         noisePub, err := Base32Codec.DecodeString(cfg.NoisePub)
405         if err != nil {
406                 return nil, err
407         }
408         if len(noisePub) != 32 {
409                 return nil, errors.New("Invalid noisePub size")
410         }
411
412         noisePrv, err := Base32Codec.DecodeString(cfg.NoisePrv)
413         if err != nil {
414                 return nil, err
415         }
416         if len(noisePrv) != 32 {
417                 return nil, errors.New("Invalid noisePrv size")
418         }
419
420         node := NodeOur{
421                 Id:       id,
422                 ExchPub:  new([32]byte),
423                 ExchPrv:  new([32]byte),
424                 SignPub:  ed25519.PublicKey(signPub),
425                 SignPrv:  ed25519.PrivateKey(signPrv),
426                 NoisePub: new([32]byte),
427                 NoisePrv: new([32]byte),
428         }
429         copy(node.ExchPub[:], exchPub)
430         copy(node.ExchPrv[:], exchPrv)
431         copy(node.NoisePub[:], noisePub)
432         copy(node.NoisePrv[:], noisePrv)
433         return &node, nil
434 }
435
436 func NewArea(ctx *Ctx, name string, cfg *AreaJSON) (*Area, error) {
437         areaId, err := AreaIdFromString(cfg.Id)
438         if err != nil {
439                 return nil, err
440         }
441         subs := make([]*NodeId, 0, len(cfg.Subs))
442         for _, s := range cfg.Subs {
443                 node, err := ctx.FindNode(s)
444                 if err != nil {
445                         return nil, err
446                 }
447                 subs = append(subs, node.Id)
448         }
449         area := Area{
450                 Name:     name,
451                 Id:       areaId,
452                 Pub:      new([32]byte),
453                 Subs:     subs,
454                 Exec:     cfg.Exec,
455                 Incoming: cfg.Incoming,
456         }
457         pub, err := Base32Codec.DecodeString(cfg.Pub)
458         if err != nil {
459                 return nil, err
460         }
461         if len(pub) != 32 {
462                 return nil, errors.New("Invalid pub size")
463         }
464         copy(area.Pub[:], pub)
465         if cfg.Prv != nil {
466                 prv, err := Base32Codec.DecodeString(*cfg.Prv)
467                 if err != nil {
468                         return nil, err
469                 }
470                 if len(prv) != 32 {
471                         return nil, errors.New("Invalid prv size")
472                 }
473                 area.Prv = new([32]byte)
474                 copy(area.Prv[:], prv)
475         }
476         if cfg.AllowUnknown != nil {
477                 area.AllowUnknown = *cfg.AllowUnknown
478         }
479         return &area, nil
480 }
481
482 func CfgParse(data []byte) (*Ctx, error) {
483         var err error
484         if bytes.Compare(data[:8], MagicNNCPBv3.B[:]) == 0 {
485                 os.Stderr.WriteString("Passphrase:") // #nosec G104
486                 password, err := term.ReadPassword(0)
487                 if err != nil {
488                         log.Fatalln(err)
489                 }
490                 os.Stderr.WriteString("\n") // #nosec G104
491                 data, err = DeEBlob(data, password)
492                 if err != nil {
493                         return nil, err
494                 }
495         } else if bytes.Compare(data[:8], MagicNNCPBv2.B[:]) == 0 {
496                 log.Fatalln(MagicNNCPBv2.TooOld())
497         } else if bytes.Compare(data[:8], MagicNNCPBv1.B[:]) == 0 {
498                 log.Fatalln(MagicNNCPBv1.TooOld())
499         }
500         var cfgGeneral map[string]interface{}
501         if err = hjson.Unmarshal(data, &cfgGeneral); err != nil {
502                 return nil, err
503         }
504         marshaled, err := json.Marshal(cfgGeneral)
505         if err != nil {
506                 return nil, err
507         }
508         var cfgJSON CfgJSON
509         if err = json.Unmarshal(marshaled, &cfgJSON); err != nil {
510                 return nil, err
511         }
512         if _, exists := cfgJSON.Neigh["self"]; !exists {
513                 return nil, errors.New("self neighbour missing")
514         }
515         var self *NodeOur
516         if cfgJSON.Self != nil {
517                 self, err = NewNodeOur(cfgJSON.Self)
518                 if err != nil {
519                         return nil, err
520                 }
521         }
522         spoolPath := path.Clean(cfgJSON.Spool)
523         if !path.IsAbs(spoolPath) {
524                 return nil, errors.New("Spool path must be absolute")
525         }
526         logPath := path.Clean(cfgJSON.Log)
527         if !path.IsAbs(logPath) {
528                 return nil, errors.New("Log path must be absolute")
529         }
530         var umaskForce *int
531         if cfgJSON.Umask != "" {
532                 r, err := strconv.ParseUint(cfgJSON.Umask, 8, 16)
533                 if err != nil {
534                         return nil, err
535                 }
536                 rInt := int(r)
537                 umaskForce = &rInt
538         }
539         showPrgrs := true
540         if cfgJSON.OmitPrgrs {
541                 showPrgrs = false
542         }
543         hdrUsage := true
544         if cfgJSON.NoHdr {
545                 hdrUsage = false
546         }
547         ctx := Ctx{
548                 Spool:      spoolPath,
549                 LogPath:    logPath,
550                 UmaskForce: umaskForce,
551                 ShowPrgrs:  showPrgrs,
552                 HdrUsage:   hdrUsage,
553                 Self:       self,
554                 Neigh:      make(map[NodeId]*Node, len(cfgJSON.Neigh)),
555                 Alias:      make(map[string]*NodeId),
556                 MCDRxIfis:  cfgJSON.MCDRxIfis,
557                 MCDTxIfis:  cfgJSON.MCDTxIfis,
558         }
559         if cfgJSON.Notify != nil {
560                 if cfgJSON.Notify.File != nil {
561                         ctx.NotifyFile = cfgJSON.Notify.File
562                 }
563                 if cfgJSON.Notify.Freq != nil {
564                         ctx.NotifyFreq = cfgJSON.Notify.Freq
565                 }
566                 if cfgJSON.Notify.Exec != nil {
567                         ctx.NotifyExec = cfgJSON.Notify.Exec
568                 }
569         }
570         vias := make(map[NodeId][]string)
571         for name, neighJSON := range cfgJSON.Neigh {
572                 neigh, err := NewNode(name, neighJSON)
573                 if err != nil {
574                         return nil, err
575                 }
576                 ctx.Neigh[*neigh.Id] = neigh
577                 if _, already := ctx.Alias[name]; already {
578                         return nil, errors.New("Node names conflict")
579                 }
580                 ctx.Alias[name] = neigh.Id
581                 vias[*neigh.Id] = neighJSON.Via
582         }
583         ctx.SelfId = ctx.Alias["self"]
584         for neighId, viasRaw := range vias {
585                 for _, viaRaw := range viasRaw {
586                         foundNodeId, err := ctx.FindNode(viaRaw)
587                         if err != nil {
588                                 return nil, err
589                         }
590                         ctx.Neigh[neighId].Via = append(
591                                 ctx.Neigh[neighId].Via,
592                                 foundNodeId.Id,
593                         )
594                 }
595         }
596         ctx.AreaId2Area = make(map[AreaId]*Area, len(cfgJSON.Areas))
597         ctx.AreaName2Id = make(map[string]*AreaId, len(cfgJSON.Areas))
598         for name, areaJSON := range cfgJSON.Areas {
599                 area, err := NewArea(&ctx, name, &areaJSON)
600                 if err != nil {
601                         return nil, err
602                 }
603                 ctx.AreaId2Area[*area.Id] = area
604                 ctx.AreaName2Id[name] = area.Id
605         }
606         return &ctx, nil
607 }