]> Cypherpunks.ru repositories - nncp.git/blob - src/cypherpunks.ru/nncp/cfg.go
9419886071a5ac657729825ef3f428cfb1f1a9aa
[nncp.git] / src / cypherpunks.ru / nncp / cfg.go
1 /*
2 NNCP -- Node to Node copy, utilities for store-and-forward data exchange
3 Copyright (C) 2016-2017 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, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 package nncp
20
21 import (
22         "errors"
23         "os"
24         "path"
25
26         "github.com/gorhill/cronexpr"
27         "golang.org/x/crypto/ed25519"
28         "gopkg.in/yaml.v2"
29 )
30
31 const (
32         CfgPathEnv = "NNCPCFG"
33 )
34
35 var (
36         DefaultCfgPath      string = "/usr/local/etc/nncp.yaml"
37         DefaultSendmailPath string = "/usr/sbin/sendmail"
38         DefaultSpoolPath    string = "/var/spool/nncp"
39         DefaultLogPath      string = "/var/spool/nncp/log"
40 )
41
42 type NodeYAML struct {
43         Id          string
44         ExchPub     string
45         SignPub     string
46         NoisePub    *string    `noisepub,omitempty`
47         Sendmail    []string   `sendmail,omitempty`
48         Incoming    *string    `incoming,omitempty`
49         Freq        *string    `freq,omitempty`
50         FreqChunked *uint64    `freqchunked,omitempty`
51         FreqMinSize *uint64    `freqminsize,omitempty`
52         Via         []string   `via,omitempty`
53         Calls       []CallYAML `calls,omitempty`
54
55         Addrs map[string]string `addrs,omitempty`
56
57         OnlineDeadline *uint `onlinedeadline,omitempty`
58         MaxOnlineTime  *uint `maxonlinetime,omitempty`
59 }
60
61 type CallYAML struct {
62         Cron           string
63         Nice           *int    `nice,omitempty`
64         Xx             *string `xx,omitempty`
65         Addr           *string `addr,omitempty`
66         OnlineDeadline *uint   `onlinedeadline,omitempty`
67         MaxOnlineTime  *uint   `maxonlinetime,omitempty`
68 }
69
70 type NodeOurYAML struct {
71         Id       string
72         ExchPub  string
73         ExchPrv  string
74         SignPub  string
75         SignPrv  string
76         NoisePrv string
77         NoisePub string
78 }
79
80 type FromToYAML struct {
81         From string
82         To   string
83 }
84
85 type NotifyYAML struct {
86         File *FromToYAML `file,omitempty`
87         Freq *FromToYAML `freq,omitempty`
88 }
89
90 type CfgYAML struct {
91         Self  *NodeOurYAML `self,omitempty`
92         Neigh map[string]NodeYAML
93
94         Spool  string
95         Log    string
96         Notify *NotifyYAML `notify,omitempty`
97 }
98
99 func NewNode(name string, yml NodeYAML) (*Node, error) {
100         nodeId, err := NodeIdFromString(yml.Id)
101         if err != nil {
102                 return nil, err
103         }
104
105         exchPub, err := FromBase32(yml.ExchPub)
106         if err != nil {
107                 return nil, err
108         }
109         if len(exchPub) != 32 {
110                 return nil, errors.New("Invalid exchPub size")
111         }
112
113         signPub, err := FromBase32(yml.SignPub)
114         if err != nil {
115                 return nil, err
116         }
117         if len(signPub) != ed25519.PublicKeySize {
118                 return nil, errors.New("Invalid signPub size")
119         }
120
121         var noisePub []byte
122         if yml.NoisePub != nil {
123                 noisePub, err = FromBase32(*yml.NoisePub)
124                 if err != nil {
125                         return nil, err
126                 }
127                 if len(noisePub) != 32 {
128                         return nil, errors.New("Invalid noisePub size")
129                 }
130         }
131
132         var incoming *string
133         if yml.Incoming != nil {
134                 inc := path.Clean(*yml.Incoming)
135                 if !path.IsAbs(inc) {
136                         return nil, errors.New("Incoming path must be absolute")
137                 }
138                 incoming = &inc
139         }
140
141         var freq *string
142         if yml.Freq != nil {
143                 fr := path.Clean(*yml.Freq)
144                 if !path.IsAbs(fr) {
145                         return nil, errors.New("Freq path must be absolute")
146                 }
147                 freq = &fr
148         }
149         var freqChunked int64
150         if yml.FreqChunked != nil {
151                 if *yml.FreqChunked == 0 {
152                         return nil, errors.New("freqchunked value must be greater than zero")
153                 }
154                 freqChunked = int64(*yml.FreqChunked) * 1024
155         }
156         var freqMinSize int64
157         if yml.FreqMinSize != nil {
158                 freqMinSize = int64(*yml.FreqMinSize) * 1024
159         }
160
161         defOnlineDeadline := uint(DefaultDeadline)
162         if yml.OnlineDeadline != nil {
163                 if *yml.OnlineDeadline <= 0 {
164                         return nil, errors.New("OnlineDeadline must be at least 1 second")
165                 }
166                 defOnlineDeadline = *yml.OnlineDeadline
167         }
168         var defMaxOnlineTime uint
169         if yml.MaxOnlineTime != nil {
170                 defMaxOnlineTime = *yml.MaxOnlineTime
171         }
172
173         var calls []*Call
174         for _, callYml := range yml.Calls {
175                 expr, err := cronexpr.Parse(callYml.Cron)
176                 if err != nil {
177                         return nil, err
178                 }
179                 nice := uint8(255)
180                 if callYml.Nice != nil {
181                         if *callYml.Nice < 1 || *callYml.Nice > 255 {
182                                 return nil, errors.New("Nice must be between 1 and 255")
183                         }
184                         nice = uint8(*callYml.Nice)
185                 }
186                 var xx TRxTx
187                 if callYml.Xx != nil {
188                         switch *callYml.Xx {
189                         case "rx":
190                                 xx = TRx
191                         case "tx":
192                                 xx = TTx
193                         default:
194                                 return nil, errors.New("xx field must be either \"rx\" or \"tx\"")
195                         }
196                 }
197                 var addr *string
198                 if callYml.Addr != nil {
199                         if a, exists := yml.Addrs[*callYml.Addr]; exists {
200                                 addr = &a
201                         } else {
202                                 addr = callYml.Addr
203                         }
204                 }
205                 onlineDeadline := defOnlineDeadline
206                 if callYml.OnlineDeadline != nil {
207                         if *callYml.OnlineDeadline == 0 {
208                                 return nil, errors.New("OnlineDeadline must be at least 1 second")
209                         }
210                         onlineDeadline = *callYml.OnlineDeadline
211                 }
212                 var maxOnlineTime uint
213                 if callYml.MaxOnlineTime != nil {
214                         maxOnlineTime = *callYml.MaxOnlineTime
215                 }
216                 calls = append(calls, &Call{
217                         Cron:           expr,
218                         Nice:           nice,
219                         Xx:             &xx,
220                         Addr:           addr,
221                         OnlineDeadline: onlineDeadline,
222                         MaxOnlineTime:  maxOnlineTime,
223                 })
224         }
225
226         node := Node{
227                 Name:           name,
228                 Id:             nodeId,
229                 ExchPub:        new([32]byte),
230                 SignPub:        ed25519.PublicKey(signPub),
231                 Sendmail:       yml.Sendmail,
232                 Incoming:       incoming,
233                 Freq:           freq,
234                 FreqChunked:    freqChunked,
235                 FreqMinSize:    freqMinSize,
236                 Calls:          calls,
237                 Addrs:          yml.Addrs,
238                 OnlineDeadline: defOnlineDeadline,
239                 MaxOnlineTime:  defMaxOnlineTime,
240         }
241         copy(node.ExchPub[:], exchPub)
242         if len(noisePub) > 0 {
243                 node.NoisePub = new([32]byte)
244                 copy(node.NoisePub[:], noisePub)
245         }
246         return &node, nil
247 }
248
249 func NewNodeOur(yml *NodeOurYAML) (*NodeOur, error) {
250         id, err := NodeIdFromString(yml.Id)
251         if err != nil {
252                 return nil, err
253         }
254
255         exchPub, err := FromBase32(yml.ExchPub)
256         if err != nil {
257                 return nil, err
258         }
259         if len(exchPub) != 32 {
260                 return nil, errors.New("Invalid exchPub size")
261         }
262
263         exchPrv, err := FromBase32(yml.ExchPrv)
264         if err != nil {
265                 return nil, err
266         }
267         if len(exchPrv) != 32 {
268                 return nil, errors.New("Invalid exchPrv size")
269         }
270
271         signPub, err := FromBase32(yml.SignPub)
272         if err != nil {
273                 return nil, err
274         }
275         if len(signPub) != ed25519.PublicKeySize {
276                 return nil, errors.New("Invalid signPub size")
277         }
278
279         signPrv, err := FromBase32(yml.SignPrv)
280         if err != nil {
281                 return nil, err
282         }
283         if len(signPrv) != ed25519.PrivateKeySize {
284                 return nil, errors.New("Invalid signPrv size")
285         }
286
287         noisePub, err := FromBase32(yml.NoisePub)
288         if err != nil {
289                 return nil, err
290         }
291         if len(noisePub) != 32 {
292                 return nil, errors.New("Invalid noisePub size")
293         }
294
295         noisePrv, err := FromBase32(yml.NoisePrv)
296         if err != nil {
297                 return nil, err
298         }
299         if len(noisePrv) != 32 {
300                 return nil, errors.New("Invalid noisePrv size")
301         }
302
303         node := NodeOur{
304                 Id:       id,
305                 ExchPub:  new([32]byte),
306                 ExchPrv:  new([32]byte),
307                 SignPub:  ed25519.PublicKey(signPub),
308                 SignPrv:  ed25519.PrivateKey(signPrv),
309                 NoisePub: new([32]byte),
310                 NoisePrv: new([32]byte),
311         }
312         copy(node.ExchPub[:], exchPub)
313         copy(node.ExchPrv[:], exchPrv)
314         copy(node.NoisePub[:], noisePub)
315         copy(node.NoisePrv[:], noisePrv)
316         return &node, nil
317 }
318
319 func (nodeOur *NodeOur) ToYAML() string {
320         yml := NodeOurYAML{
321                 Id:       nodeOur.Id.String(),
322                 ExchPub:  ToBase32(nodeOur.ExchPub[:]),
323                 ExchPrv:  ToBase32(nodeOur.ExchPrv[:]),
324                 SignPub:  ToBase32(nodeOur.SignPub[:]),
325                 SignPrv:  ToBase32(nodeOur.SignPrv[:]),
326                 NoisePub: ToBase32(nodeOur.NoisePub[:]),
327                 NoisePrv: ToBase32(nodeOur.NoisePrv[:]),
328         }
329         raw, err := yaml.Marshal(&yml)
330         if err != nil {
331                 panic(err)
332         }
333         return string(raw)
334 }
335
336 func CfgParse(data []byte) (*Ctx, error) {
337         var cfgYAML CfgYAML
338         err := yaml.Unmarshal(data, &cfgYAML)
339         if err != nil {
340                 return nil, err
341         }
342         if _, exists := cfgYAML.Neigh["self"]; !exists {
343                 return nil, errors.New("self neighbour missing")
344         }
345         var self *NodeOur
346         if cfgYAML.Self != nil {
347                 self, err = NewNodeOur(cfgYAML.Self)
348                 if err != nil {
349                         return nil, err
350                 }
351         }
352         spoolPath := path.Clean(cfgYAML.Spool)
353         if !path.IsAbs(spoolPath) {
354                 return nil, errors.New("Spool path must be absolute")
355         }
356         logPath := path.Clean(cfgYAML.Log)
357         if !path.IsAbs(logPath) {
358                 return nil, errors.New("Log path must be absolute")
359         }
360         ctx := Ctx{
361                 Spool:   spoolPath,
362                 LogPath: logPath,
363                 Self:    self,
364                 Neigh:   make(map[NodeId]*Node, len(cfgYAML.Neigh)),
365                 Alias:   make(map[string]*NodeId),
366         }
367         if cfgYAML.Notify != nil {
368                 if cfgYAML.Notify.File != nil {
369                         ctx.NotifyFile = cfgYAML.Notify.File
370                 }
371                 if cfgYAML.Notify.Freq != nil {
372                         ctx.NotifyFreq = cfgYAML.Notify.Freq
373                 }
374         }
375         vias := make(map[NodeId][]string)
376         for name, neighYAML := range cfgYAML.Neigh {
377                 neigh, err := NewNode(name, neighYAML)
378                 if err != nil {
379                         return nil, err
380                 }
381                 ctx.Neigh[*neigh.Id] = neigh
382                 if _, already := ctx.Alias[name]; already {
383                         return nil, errors.New("Node names conflict")
384                 }
385                 ctx.Alias[name] = neigh.Id
386                 vias[*neigh.Id] = neighYAML.Via
387         }
388         ctx.SelfId = ctx.Alias["self"]
389         for neighId, viasRaw := range vias {
390                 for _, viaRaw := range viasRaw {
391                         foundNodeId, err := ctx.FindNode(viaRaw)
392                         if err != nil {
393                                 return nil, err
394                         }
395                         ctx.Neigh[neighId].Via = append(
396                                 ctx.Neigh[neighId].Via,
397                                 foundNodeId.Id,
398                         )
399                 }
400         }
401         return &ctx, nil
402 }
403
404 func CfgPathFromEnv(cmdlineFlag *string) (p string) {
405         p = os.Getenv(CfgPathEnv)
406         if p == "" {
407                 p = *cmdlineFlag
408         }
409         return
410 }