]> Cypherpunks.ru repositories - nncp.git/blob - src/cfg.go
nncp-cfgdir
[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         Incoming *string             `json:"incoming,omitempty"`
55         Exec     map[string][]string `json:"exec,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  `json:"cron"`
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,omitempty"`
86         MCDIgnore      bool    `json:"mcd-ignore,omitempty"`
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         NoisePub string `json:"noisepub"`
104         NoisePrv string `json:"noiseprv"`
105 }
106
107 type FromToJSON struct {
108         From string `json:"from"`
109         To   string `json:"to"`
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,omitempty"`
121         Prv *string `json:"prv,omitempty"`
122
123         Subs []string `json:"subs"`
124
125         Incoming *string             `json:"incoming,omitempty"`
126         Exec     map[string][]string `json:"exec,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         MCDRxIfis []string       `json:"mcd-listen,omitempty"`
140         MCDTxIfis map[string]int `json:"mcd-send,omitempty"`
141
142         Notify *NotifyJSON `json:"notify,omitempty"`
143
144         Self  *NodeOurJSON        `json:"self"`
145         Neigh map[string]NodeJSON `json:"neigh"`
146
147         Areas map[string]AreaJSON `json:"areas,omitempty"`
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                 call.WhenTxExists = callCfg.WhenTxExists
307                 call.NoCK = callCfg.NoCK
308                 call.MCDIgnore = callCfg.MCDIgnore
309                 call.AutoToss = callCfg.AutoToss
310                 call.AutoTossDoSeen = callCfg.AutoTossDoSeen
311                 call.AutoTossNoFile = callCfg.AutoTossNoFile
312                 call.AutoTossNoFreq = callCfg.AutoTossNoFreq
313                 call.AutoTossNoExec = callCfg.AutoTossNoExec
314                 call.AutoTossNoTrns = callCfg.AutoTossNoTrns
315                 call.AutoTossNoArea = callCfg.AutoTossNoArea
316
317                 calls = append(calls, &call)
318         }
319
320         node := Node{
321                 Name:           name,
322                 Id:             nodeId,
323                 ExchPub:        new([32]byte),
324                 SignPub:        ed25519.PublicKey(signPub),
325                 Exec:           cfg.Exec,
326                 Incoming:       incoming,
327                 FreqPath:       freqPath,
328                 FreqChunked:    freqChunked,
329                 FreqMinSize:    freqMinSize,
330                 FreqMaxSize:    freqMaxSize,
331                 Calls:          calls,
332                 Addrs:          cfg.Addrs,
333                 RxRate:         defRxRate,
334                 TxRate:         defTxRate,
335                 OnlineDeadline: defOnlineDeadline,
336                 MaxOnlineTime:  defMaxOnlineTime,
337         }
338         copy(node.ExchPub[:], exchPub)
339         if len(noisePub) > 0 {
340                 node.NoisePub = new([32]byte)
341                 copy(node.NoisePub[:], noisePub)
342         }
343         return &node, nil
344 }
345
346 func NewNodeOur(cfg *NodeOurJSON) (*NodeOur, error) {
347         id, err := NodeIdFromString(cfg.Id)
348         if err != nil {
349                 return nil, err
350         }
351
352         exchPub, err := Base32Codec.DecodeString(cfg.ExchPub)
353         if err != nil {
354                 return nil, err
355         }
356         if len(exchPub) != 32 {
357                 return nil, errors.New("Invalid exchPub size")
358         }
359
360         exchPrv, err := Base32Codec.DecodeString(cfg.ExchPrv)
361         if err != nil {
362                 return nil, err
363         }
364         if len(exchPrv) != 32 {
365                 return nil, errors.New("Invalid exchPrv size")
366         }
367
368         signPub, err := Base32Codec.DecodeString(cfg.SignPub)
369         if err != nil {
370                 return nil, err
371         }
372         if len(signPub) != ed25519.PublicKeySize {
373                 return nil, errors.New("Invalid signPub size")
374         }
375
376         signPrv, err := Base32Codec.DecodeString(cfg.SignPrv)
377         if err != nil {
378                 return nil, err
379         }
380         if len(signPrv) != ed25519.PrivateKeySize {
381                 return nil, errors.New("Invalid signPrv size")
382         }
383
384         noisePub, err := Base32Codec.DecodeString(cfg.NoisePub)
385         if err != nil {
386                 return nil, err
387         }
388         if len(noisePub) != 32 {
389                 return nil, errors.New("Invalid noisePub size")
390         }
391
392         noisePrv, err := Base32Codec.DecodeString(cfg.NoisePrv)
393         if err != nil {
394                 return nil, err
395         }
396         if len(noisePrv) != 32 {
397                 return nil, errors.New("Invalid noisePrv size")
398         }
399
400         node := NodeOur{
401                 Id:       id,
402                 ExchPub:  new([32]byte),
403                 ExchPrv:  new([32]byte),
404                 SignPub:  ed25519.PublicKey(signPub),
405                 SignPrv:  ed25519.PrivateKey(signPrv),
406                 NoisePub: new([32]byte),
407                 NoisePrv: new([32]byte),
408         }
409         copy(node.ExchPub[:], exchPub)
410         copy(node.ExchPrv[:], exchPrv)
411         copy(node.NoisePub[:], noisePub)
412         copy(node.NoisePrv[:], noisePrv)
413         return &node, nil
414 }
415
416 func NewArea(ctx *Ctx, name string, cfg *AreaJSON) (*Area, error) {
417         areaId, err := AreaIdFromString(cfg.Id)
418         if err != nil {
419                 return nil, err
420         }
421         subs := make([]*NodeId, 0, len(cfg.Subs))
422         for _, s := range cfg.Subs {
423                 node, err := ctx.FindNode(s)
424                 if err != nil {
425                         return nil, err
426                 }
427                 subs = append(subs, node.Id)
428         }
429         area := Area{
430                 Name:     name,
431                 Id:       areaId,
432                 Subs:     subs,
433                 Exec:     cfg.Exec,
434                 Incoming: cfg.Incoming,
435         }
436         if cfg.Pub != nil {
437                 pub, err := Base32Codec.DecodeString(*cfg.Pub)
438                 if err != nil {
439                         return nil, err
440                 }
441                 if len(pub) != 32 {
442                         return nil, errors.New("Invalid pub size")
443                 }
444                 area.Pub = new([32]byte)
445                 copy(area.Pub[:], pub)
446         }
447         if cfg.Prv != nil {
448                 prv, err := Base32Codec.DecodeString(*cfg.Prv)
449                 if err != nil {
450                         return nil, err
451                 }
452                 if len(prv) != 32 {
453                         return nil, errors.New("Invalid prv size")
454                 }
455                 area.Prv = new([32]byte)
456                 copy(area.Prv[:], prv)
457         }
458         area.AllowUnknown = cfg.AllowUnknown
459         return &area, nil
460 }
461
462 func CfgParse(data []byte) (*CfgJSON, error) {
463         var err error
464         if bytes.Compare(data[:8], MagicNNCPBv3.B[:]) == 0 {
465                 os.Stderr.WriteString("Passphrase:") // #nosec G104
466                 password, err := term.ReadPassword(0)
467                 if err != nil {
468                         log.Fatalln(err)
469                 }
470                 os.Stderr.WriteString("\n") // #nosec G104
471                 data, err = DeEBlob(data, password)
472                 if err != nil {
473                         return nil, err
474                 }
475         } else if bytes.Compare(data[:8], MagicNNCPBv2.B[:]) == 0 {
476                 log.Fatalln(MagicNNCPBv2.TooOld())
477         } else if bytes.Compare(data[:8], MagicNNCPBv1.B[:]) == 0 {
478                 log.Fatalln(MagicNNCPBv1.TooOld())
479         }
480         var cfgGeneral map[string]interface{}
481         if err = hjson.Unmarshal(data, &cfgGeneral); err != nil {
482                 return nil, err
483         }
484         marshaled, err := json.Marshal(cfgGeneral)
485         if err != nil {
486                 return nil, err
487         }
488         var cfgJSON CfgJSON
489         err = json.Unmarshal(marshaled, &cfgJSON)
490         return &cfgJSON, err
491 }
492
493 func Cfg2Ctx(cfgJSON *CfgJSON) (*Ctx, error) {
494         if _, exists := cfgJSON.Neigh["self"]; !exists {
495                 return nil, errors.New("self neighbour missing")
496         }
497         var self *NodeOur
498         if cfgJSON.Self != nil {
499                 var err error
500                 self, err = NewNodeOur(cfgJSON.Self)
501                 if err != nil {
502                         return nil, err
503                 }
504         }
505         spoolPath := path.Clean(cfgJSON.Spool)
506         if !path.IsAbs(spoolPath) {
507                 return nil, errors.New("Spool path must be absolute")
508         }
509         logPath := path.Clean(cfgJSON.Log)
510         if !path.IsAbs(logPath) {
511                 return nil, errors.New("Log path must be absolute")
512         }
513         var umaskForce *int
514         if cfgJSON.Umask != nil {
515                 r, err := strconv.ParseUint(*cfgJSON.Umask, 8, 16)
516                 if err != nil {
517                         return nil, err
518                 }
519                 rInt := int(r)
520                 umaskForce = &rInt
521         }
522         showPrgrs := true
523         if cfgJSON.OmitPrgrs {
524                 showPrgrs = false
525         }
526         hdrUsage := true
527         if cfgJSON.NoHdr {
528                 hdrUsage = false
529         }
530         ctx := Ctx{
531                 Spool:      spoolPath,
532                 LogPath:    logPath,
533                 UmaskForce: umaskForce,
534                 ShowPrgrs:  showPrgrs,
535                 HdrUsage:   hdrUsage,
536                 Self:       self,
537                 Neigh:      make(map[NodeId]*Node, len(cfgJSON.Neigh)),
538                 Alias:      make(map[string]*NodeId),
539                 MCDRxIfis:  cfgJSON.MCDRxIfis,
540                 MCDTxIfis:  cfgJSON.MCDTxIfis,
541         }
542         if cfgJSON.Notify != nil {
543                 if cfgJSON.Notify.File != nil {
544                         ctx.NotifyFile = cfgJSON.Notify.File
545                 }
546                 if cfgJSON.Notify.Freq != nil {
547                         ctx.NotifyFreq = cfgJSON.Notify.Freq
548                 }
549                 if cfgJSON.Notify.Exec != nil {
550                         ctx.NotifyExec = cfgJSON.Notify.Exec
551                 }
552         }
553         vias := make(map[NodeId][]string)
554         for name, neighJSON := range cfgJSON.Neigh {
555                 neigh, err := NewNode(name, neighJSON)
556                 if err != nil {
557                         return nil, err
558                 }
559                 ctx.Neigh[*neigh.Id] = neigh
560                 if _, already := ctx.Alias[name]; already {
561                         return nil, errors.New("Node names conflict")
562                 }
563                 ctx.Alias[name] = neigh.Id
564                 vias[*neigh.Id] = neighJSON.Via
565         }
566         ctx.SelfId = ctx.Alias["self"]
567         for neighId, viasRaw := range vias {
568                 for _, viaRaw := range viasRaw {
569                         foundNodeId, err := ctx.FindNode(viaRaw)
570                         if err != nil {
571                                 return nil, err
572                         }
573                         ctx.Neigh[neighId].Via = append(
574                                 ctx.Neigh[neighId].Via,
575                                 foundNodeId.Id,
576                         )
577                 }
578         }
579         ctx.AreaId2Area = make(map[AreaId]*Area, len(cfgJSON.Areas))
580         ctx.AreaName2Id = make(map[string]*AreaId, len(cfgJSON.Areas))
581         for name, areaJSON := range cfgJSON.Areas {
582                 area, err := NewArea(&ctx, name, &areaJSON)
583                 if err != nil {
584                         return nil, err
585                 }
586                 ctx.AreaId2Area[*area.Id] = area
587                 ctx.AreaName2Id[name] = area.Id
588         }
589         return &ctx, nil
590 }