]> Cypherpunks.ru repositories - nncp.git/blob - src/cfg.go
On demand calling
[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
86         AutoToss       *bool `json:"autotoss,omitempty"`
87         AutoTossDoSeen *bool `json:"autotoss-doseen,omitempty"`
88         AutoTossNoFile *bool `json:"autotoss-nofile,omitempty"`
89         AutoTossNoFreq *bool `json:"autotoss-nofreq,omitempty"`
90         AutoTossNoExec *bool `json:"autotoss-noexec,omitempty"`
91         AutoTossNoTrns *bool `json:"autotoss-notrns,omitempty"`
92 }
93
94 type NodeOurJSON struct {
95         Id       string `json:"id"`
96         ExchPub  string `json:"exchpub"`
97         ExchPrv  string `json:"exchprv"`
98         SignPub  string `json:"signpub"`
99         SignPrv  string `json:"signprv"`
100         NoisePrv string `json:"noiseprv"`
101         NoisePub string `json:"noisepub"`
102 }
103
104 type FromToJSON struct {
105         From string
106         To   string
107 }
108
109 type NotifyJSON struct {
110         File *FromToJSON            `json:"file,omitempty"`
111         Freq *FromToJSON            `json:"freq,omitempty"`
112         Exec map[string]*FromToJSON `json:"exec,omitempty"`
113 }
114
115 type CfgJSON struct {
116         Spool string `json:"spool"`
117         Log   string `json:"log"`
118         Umask string `json:"umask,omitempty"`
119
120         OmitPrgrs bool `json:"noprogress,omitempty"`
121
122         Notify *NotifyJSON `json:"notify,omitempty"`
123
124         Self  *NodeOurJSON        `json:"self"`
125         Neigh map[string]NodeJSON `json:"neigh"`
126 }
127
128 func NewNode(name string, cfg NodeJSON) (*Node, error) {
129         nodeId, err := NodeIdFromString(cfg.Id)
130         if err != nil {
131                 return nil, err
132         }
133
134         exchPub, err := Base32Codec.DecodeString(cfg.ExchPub)
135         if err != nil {
136                 return nil, err
137         }
138         if len(exchPub) != 32 {
139                 return nil, errors.New("Invalid exchPub size")
140         }
141
142         signPub, err := Base32Codec.DecodeString(cfg.SignPub)
143         if err != nil {
144                 return nil, err
145         }
146         if len(signPub) != ed25519.PublicKeySize {
147                 return nil, errors.New("Invalid signPub size")
148         }
149
150         var noisePub []byte
151         if cfg.NoisePub != nil {
152                 noisePub, err = Base32Codec.DecodeString(*cfg.NoisePub)
153                 if err != nil {
154                         return nil, err
155                 }
156                 if len(noisePub) != 32 {
157                         return nil, errors.New("Invalid noisePub size")
158                 }
159         }
160
161         var incoming *string
162         if cfg.Incoming != nil {
163                 inc := path.Clean(*cfg.Incoming)
164                 if !path.IsAbs(inc) {
165                         return nil, errors.New("Incoming path must be absolute")
166                 }
167                 incoming = &inc
168         }
169
170         var freqPath *string
171         freqChunked := int64(MaxFileSize)
172         var freqMinSize int64
173         freqMaxSize := int64(MaxFileSize)
174         if cfg.Freq != nil {
175                 f := cfg.Freq
176                 if f.Path != nil {
177                         fPath := path.Clean(*f.Path)
178                         if !path.IsAbs(fPath) {
179                                 return nil, errors.New("freq.path path must be absolute")
180                         }
181                         freqPath = &fPath
182                 }
183                 if f.Chunked != nil {
184                         if *f.Chunked == 0 {
185                                 return nil, errors.New("freq.chunked value must be greater than zero")
186                         }
187                         freqChunked = int64(*f.Chunked) * 1024
188                 }
189                 if f.MinSize != nil {
190                         freqMinSize = int64(*f.MinSize) * 1024
191                 }
192                 if f.MaxSize != nil {
193                         freqMaxSize = int64(*f.MaxSize) * 1024
194                 }
195         }
196
197         defRxRate := 0
198         if cfg.RxRate != nil && *cfg.RxRate > 0 {
199                 defRxRate = *cfg.RxRate
200         }
201         defTxRate := 0
202         if cfg.TxRate != nil && *cfg.TxRate > 0 {
203                 defTxRate = *cfg.TxRate
204         }
205
206         defOnlineDeadline := DefaultDeadline
207         if cfg.OnlineDeadline != nil {
208                 if *cfg.OnlineDeadline <= 0 {
209                         return nil, errors.New("OnlineDeadline must be at least 1 second")
210                 }
211                 defOnlineDeadline = time.Duration(*cfg.OnlineDeadline) * time.Second
212         }
213         var defMaxOnlineTime time.Duration
214         if cfg.MaxOnlineTime != nil {
215                 defMaxOnlineTime = time.Duration(*cfg.MaxOnlineTime) * time.Second
216         }
217
218         var calls []*Call
219         for _, callCfg := range cfg.Calls {
220                 expr, err := cronexpr.Parse(callCfg.Cron)
221                 if err != nil {
222                         return nil, err
223                 }
224
225                 nice := uint8(255)
226                 if callCfg.Nice != nil {
227                         nice, err = NicenessParse(*callCfg.Nice)
228                         if err != nil {
229                                 return nil, err
230                         }
231                 }
232
233                 var xx TRxTx
234                 if callCfg.Xx != nil {
235                         switch *callCfg.Xx {
236                         case "rx":
237                                 xx = TRx
238                         case "tx":
239                                 xx = TTx
240                         default:
241                                 return nil, errors.New("xx field must be either \"rx\" or \"tx\"")
242                         }
243                 }
244
245                 rxRate := defRxRate
246                 if callCfg.RxRate != nil {
247                         rxRate = *callCfg.RxRate
248                 }
249                 txRate := defTxRate
250                 if callCfg.TxRate != nil {
251                         txRate = *callCfg.TxRate
252                 }
253
254                 var addr *string
255                 if callCfg.Addr != nil {
256                         if a, exists := cfg.Addrs[*callCfg.Addr]; exists {
257                                 addr = &a
258                         } else {
259                                 addr = callCfg.Addr
260                         }
261                 }
262
263                 onlineDeadline := defOnlineDeadline
264                 if callCfg.OnlineDeadline != nil {
265                         if *callCfg.OnlineDeadline == 0 {
266                                 return nil, errors.New("OnlineDeadline must be at least 1 second")
267                         }
268                         onlineDeadline = time.Duration(*callCfg.OnlineDeadline) * time.Second
269                 }
270
271                 call := Call{
272                         Cron:           expr,
273                         Nice:           nice,
274                         Xx:             xx,
275                         RxRate:         rxRate,
276                         TxRate:         txRate,
277                         Addr:           addr,
278                         OnlineDeadline: onlineDeadline,
279                 }
280
281                 if callCfg.MaxOnlineTime != nil {
282                         call.MaxOnlineTime = time.Duration(*callCfg.MaxOnlineTime) * time.Second
283                 }
284                 if callCfg.WhenTxExists != nil {
285                         call.WhenTxExists = *callCfg.WhenTxExists
286                 }
287                 if callCfg.AutoToss != nil {
288                         call.AutoToss = *callCfg.AutoToss
289                 }
290                 if callCfg.AutoTossDoSeen != nil {
291                         call.AutoTossDoSeen = *callCfg.AutoTossDoSeen
292                 }
293                 if callCfg.AutoTossNoFile != nil {
294                         call.AutoTossNoFile = *callCfg.AutoTossNoFile
295                 }
296                 if callCfg.AutoTossNoFreq != nil {
297                         call.AutoTossNoFreq = *callCfg.AutoTossNoFreq
298                 }
299                 if callCfg.AutoTossNoExec != nil {
300                         call.AutoTossNoExec = *callCfg.AutoTossNoExec
301                 }
302                 if callCfg.AutoTossNoTrns != nil {
303                         call.AutoTossNoTrns = *callCfg.AutoTossNoTrns
304                 }
305
306                 calls = append(calls, &call)
307         }
308
309         node := Node{
310                 Name:           name,
311                 Id:             nodeId,
312                 ExchPub:        new([32]byte),
313                 SignPub:        ed25519.PublicKey(signPub),
314                 Exec:           cfg.Exec,
315                 Incoming:       incoming,
316                 FreqPath:       freqPath,
317                 FreqChunked:    freqChunked,
318                 FreqMinSize:    freqMinSize,
319                 FreqMaxSize:    freqMaxSize,
320                 Calls:          calls,
321                 Addrs:          cfg.Addrs,
322                 RxRate:         defRxRate,
323                 TxRate:         defTxRate,
324                 OnlineDeadline: defOnlineDeadline,
325                 MaxOnlineTime:  defMaxOnlineTime,
326         }
327         copy(node.ExchPub[:], exchPub)
328         if len(noisePub) > 0 {
329                 node.NoisePub = new([32]byte)
330                 copy(node.NoisePub[:], noisePub)
331         }
332         return &node, nil
333 }
334
335 func NewNodeOur(cfg *NodeOurJSON) (*NodeOur, error) {
336         id, err := NodeIdFromString(cfg.Id)
337         if err != nil {
338                 return nil, err
339         }
340
341         exchPub, err := Base32Codec.DecodeString(cfg.ExchPub)
342         if err != nil {
343                 return nil, err
344         }
345         if len(exchPub) != 32 {
346                 return nil, errors.New("Invalid exchPub size")
347         }
348
349         exchPrv, err := Base32Codec.DecodeString(cfg.ExchPrv)
350         if err != nil {
351                 return nil, err
352         }
353         if len(exchPrv) != 32 {
354                 return nil, errors.New("Invalid exchPrv size")
355         }
356
357         signPub, err := Base32Codec.DecodeString(cfg.SignPub)
358         if err != nil {
359                 return nil, err
360         }
361         if len(signPub) != ed25519.PublicKeySize {
362                 return nil, errors.New("Invalid signPub size")
363         }
364
365         signPrv, err := Base32Codec.DecodeString(cfg.SignPrv)
366         if err != nil {
367                 return nil, err
368         }
369         if len(signPrv) != ed25519.PrivateKeySize {
370                 return nil, errors.New("Invalid signPrv size")
371         }
372
373         noisePub, err := Base32Codec.DecodeString(cfg.NoisePub)
374         if err != nil {
375                 return nil, err
376         }
377         if len(noisePub) != 32 {
378                 return nil, errors.New("Invalid noisePub size")
379         }
380
381         noisePrv, err := Base32Codec.DecodeString(cfg.NoisePrv)
382         if err != nil {
383                 return nil, err
384         }
385         if len(noisePrv) != 32 {
386                 return nil, errors.New("Invalid noisePrv size")
387         }
388
389         node := NodeOur{
390                 Id:       id,
391                 ExchPub:  new([32]byte),
392                 ExchPrv:  new([32]byte),
393                 SignPub:  ed25519.PublicKey(signPub),
394                 SignPrv:  ed25519.PrivateKey(signPrv),
395                 NoisePub: new([32]byte),
396                 NoisePrv: new([32]byte),
397         }
398         copy(node.ExchPub[:], exchPub)
399         copy(node.ExchPrv[:], exchPrv)
400         copy(node.NoisePub[:], noisePub)
401         copy(node.NoisePrv[:], noisePrv)
402         return &node, nil
403 }
404
405 func CfgParse(data []byte) (*Ctx, error) {
406         var err error
407         if bytes.Compare(data[:8], MagicNNCPBv3[:]) == 0 {
408                 os.Stderr.WriteString("Passphrase:") // #nosec G104
409                 password, err := term.ReadPassword(0)
410                 if err != nil {
411                         log.Fatalln(err)
412                 }
413                 os.Stderr.WriteString("\n") // #nosec G104
414                 data, err = DeEBlob(data, password)
415                 if err != nil {
416                         return nil, err
417                 }
418         }
419         var cfgGeneral map[string]interface{}
420         if err = hjson.Unmarshal(data, &cfgGeneral); err != nil {
421                 return nil, err
422         }
423         marshaled, err := json.Marshal(cfgGeneral)
424         if err != nil {
425                 return nil, err
426         }
427         var cfgJSON CfgJSON
428         if err = json.Unmarshal(marshaled, &cfgJSON); err != nil {
429                 return nil, err
430         }
431         if _, exists := cfgJSON.Neigh["self"]; !exists {
432                 return nil, errors.New("self neighbour missing")
433         }
434         var self *NodeOur
435         if cfgJSON.Self != nil {
436                 self, err = NewNodeOur(cfgJSON.Self)
437                 if err != nil {
438                         return nil, err
439                 }
440         }
441         spoolPath := path.Clean(cfgJSON.Spool)
442         if !path.IsAbs(spoolPath) {
443                 return nil, errors.New("Spool path must be absolute")
444         }
445         logPath := path.Clean(cfgJSON.Log)
446         if !path.IsAbs(logPath) {
447                 return nil, errors.New("Log path must be absolute")
448         }
449         var umaskForce *int
450         if cfgJSON.Umask != "" {
451                 r, err := strconv.ParseUint(cfgJSON.Umask, 8, 16)
452                 if err != nil {
453                         return nil, err
454                 }
455                 rInt := int(r)
456                 umaskForce = &rInt
457         }
458         showPrgrs := true
459         if cfgJSON.OmitPrgrs {
460                 showPrgrs = false
461         }
462         ctx := Ctx{
463                 Spool:      spoolPath,
464                 LogPath:    logPath,
465                 UmaskForce: umaskForce,
466                 ShowPrgrs:  showPrgrs,
467                 Self:       self,
468                 Neigh:      make(map[NodeId]*Node, len(cfgJSON.Neigh)),
469                 Alias:      make(map[string]*NodeId),
470         }
471         if cfgJSON.Notify != nil {
472                 if cfgJSON.Notify.File != nil {
473                         ctx.NotifyFile = cfgJSON.Notify.File
474                 }
475                 if cfgJSON.Notify.Freq != nil {
476                         ctx.NotifyFreq = cfgJSON.Notify.Freq
477                 }
478                 if cfgJSON.Notify.Exec != nil {
479                         ctx.NotifyExec = cfgJSON.Notify.Exec
480                 }
481         }
482         vias := make(map[NodeId][]string)
483         for name, neighJSON := range cfgJSON.Neigh {
484                 neigh, err := NewNode(name, neighJSON)
485                 if err != nil {
486                         return nil, err
487                 }
488                 ctx.Neigh[*neigh.Id] = neigh
489                 if _, already := ctx.Alias[name]; already {
490                         return nil, errors.New("Node names conflict")
491                 }
492                 ctx.Alias[name] = neigh.Id
493                 vias[*neigh.Id] = neighJSON.Via
494         }
495         ctx.SelfId = ctx.Alias["self"]
496         for neighId, viasRaw := range vias {
497                 for _, viaRaw := range viasRaw {
498                         foundNodeId, err := ctx.FindNode(viaRaw)
499                         if err != nil {
500                                 return nil, err
501                         }
502                         ctx.Neigh[neighId].Via = append(
503                                 ctx.Neigh[neighId].Via,
504                                 foundNodeId.Id,
505                         )
506                 }
507         }
508         return &ctx, nil
509 }