]> Cypherpunks.ru repositories - nncp.git/blob - src/cypherpunks.ru/nncp/cfg.go
Merge branch 'develop'
[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
48         Incoming *string    `incoming,omitempty`
49         Freq     *string    `freq,omitempty`
50         Via      []string   `via,omitempty`
51         Calls    []CallYAML `calls,omitempty`
52
53         Addrs map[string]string `addrs,omitempty`
54
55         OnlineDeadline *uint `onlinedeadline,omitempty`
56         MaxOnlineTime  *uint `maxonlinetime,omitempty`
57 }
58
59 type CallYAML struct {
60         Cron           string
61         Nice           *int    `nice,omitempty`
62         Xx             *string `xx,omitempty`
63         Addr           *string `addr,omitempty`
64         OnlineDeadline *uint   `onlinedeadline,omitempty`
65         MaxOnlineTime  *uint   `maxonlinetime,omitempty`
66 }
67
68 type NodeOurYAML struct {
69         Id       string
70         ExchPub  string
71         ExchPrv  string
72         SignPub  string
73         SignPrv  string
74         NoisePrv string
75         NoisePub string
76 }
77
78 type FromToYAML struct {
79         From string
80         To   string
81 }
82
83 type NotifyYAML struct {
84         File *FromToYAML `file,omitempty`
85         Freq *FromToYAML `freq,omitempty`
86 }
87
88 type CfgYAML struct {
89         Self  NodeOurYAML
90         Neigh map[string]NodeYAML
91
92         Spool  string
93         Log    string
94         Notify *NotifyYAML `notify,omitempty`
95 }
96
97 func NewNode(name string, yml NodeYAML) (*Node, error) {
98         nodeId, err := NodeIdFromString(yml.Id)
99         if err != nil {
100                 return nil, err
101         }
102
103         exchPub, err := FromBase32(yml.ExchPub)
104         if err != nil {
105                 return nil, err
106         }
107         if len(exchPub) != 32 {
108                 return nil, errors.New("Invalid exchPub size")
109         }
110
111         signPub, err := FromBase32(yml.SignPub)
112         if err != nil {
113                 return nil, err
114         }
115         if len(signPub) != ed25519.PublicKeySize {
116                 return nil, errors.New("Invalid signPub size")
117         }
118
119         var noisePub []byte
120         if yml.NoisePub != nil {
121                 noisePub, err = FromBase32(*yml.NoisePub)
122                 if err != nil {
123                         return nil, err
124                 }
125                 if len(noisePub) != 32 {
126                         return nil, errors.New("Invalid noisePub size")
127                 }
128         }
129
130         var incoming *string
131         if yml.Incoming != nil {
132                 inc := path.Clean(*yml.Incoming)
133                 if !path.IsAbs(inc) {
134                         return nil, errors.New("Incoming path must be absolute")
135                 }
136                 incoming = &inc
137         }
138
139         var freq *string
140         if yml.Freq != nil {
141                 fr := path.Clean(*yml.Freq)
142                 if !path.IsAbs(fr) {
143                         return nil, errors.New("Freq path must be absolute")
144                 }
145                 freq = &fr
146         }
147
148         defOnlineDeadline := uint(DefaultDeadline)
149         if yml.OnlineDeadline != nil {
150                 if *yml.OnlineDeadline <= 0 {
151                         return nil, errors.New("OnlineDeadline must be at least 1 second")
152                 }
153                 defOnlineDeadline = *yml.OnlineDeadline
154         }
155         var defMaxOnlineTime uint
156         if yml.MaxOnlineTime != nil {
157                 defMaxOnlineTime = *yml.MaxOnlineTime
158         }
159
160         var calls []*Call
161         for _, callYml := range yml.Calls {
162                 expr, err := cronexpr.Parse(callYml.Cron)
163                 if err != nil {
164                         return nil, err
165                 }
166                 nice := uint8(255)
167                 if callYml.Nice != nil {
168                         if *callYml.Nice < 1 || *callYml.Nice > 255 {
169                                 return nil, errors.New("Nice must be between 1 and 255")
170                         }
171                         nice = uint8(*callYml.Nice)
172                 }
173                 var xx TRxTx
174                 if callYml.Xx != nil {
175                         switch *callYml.Xx {
176                         case "rx":
177                                 xx = TRx
178                         case "tx":
179                                 xx = TTx
180                         default:
181                                 return nil, errors.New("xx field must be either \"rx\" or \"tx\"")
182                         }
183                 }
184                 var addr *string
185                 if callYml.Addr != nil {
186                         if a, exists := yml.Addrs[*callYml.Addr]; exists {
187                                 addr = &a
188                         } else {
189                                 addr = callYml.Addr
190                         }
191                 }
192                 onlineDeadline := defOnlineDeadline
193                 if callYml.OnlineDeadline != nil {
194                         if *callYml.OnlineDeadline == 0 {
195                                 return nil, errors.New("OnlineDeadline must be at least 1 second")
196                         }
197                         onlineDeadline = *callYml.OnlineDeadline
198                 }
199                 var maxOnlineTime uint
200                 if callYml.MaxOnlineTime != nil {
201                         maxOnlineTime = *callYml.MaxOnlineTime
202                 }
203                 calls = append(calls, &Call{
204                         Cron:           expr,
205                         Nice:           nice,
206                         Xx:             &xx,
207                         Addr:           addr,
208                         OnlineDeadline: onlineDeadline,
209                         MaxOnlineTime:  maxOnlineTime,
210                 })
211         }
212
213         node := Node{
214                 Name:           name,
215                 Id:             nodeId,
216                 ExchPub:        new([32]byte),
217                 SignPub:        ed25519.PublicKey(signPub),
218                 Sendmail:       yml.Sendmail,
219                 Incoming:       incoming,
220                 Freq:           freq,
221                 Calls:          calls,
222                 Addrs:          yml.Addrs,
223                 OnlineDeadline: defOnlineDeadline,
224                 MaxOnlineTime:  defMaxOnlineTime,
225         }
226         copy(node.ExchPub[:], exchPub)
227         if len(noisePub) > 0 {
228                 node.NoisePub = new([32]byte)
229                 copy(node.NoisePub[:], noisePub)
230         }
231         return &node, nil
232 }
233
234 func NewNodeOur(yml NodeOurYAML) (*NodeOur, error) {
235         id, err := NodeIdFromString(yml.Id)
236         if err != nil {
237                 return nil, err
238         }
239
240         exchPub, err := FromBase32(yml.ExchPub)
241         if err != nil {
242                 return nil, err
243         }
244         if len(exchPub) != 32 {
245                 return nil, errors.New("Invalid exchPub size")
246         }
247
248         exchPrv, err := FromBase32(yml.ExchPrv)
249         if err != nil {
250                 return nil, err
251         }
252         if len(exchPrv) != 32 {
253                 return nil, errors.New("Invalid exchPrv size")
254         }
255
256         signPub, err := FromBase32(yml.SignPub)
257         if err != nil {
258                 return nil, err
259         }
260         if len(signPub) != ed25519.PublicKeySize {
261                 return nil, errors.New("Invalid signPub size")
262         }
263
264         signPrv, err := FromBase32(yml.SignPrv)
265         if err != nil {
266                 return nil, err
267         }
268         if len(signPrv) != ed25519.PrivateKeySize {
269                 return nil, errors.New("Invalid signPrv size")
270         }
271
272         noisePub, err := FromBase32(yml.NoisePub)
273         if err != nil {
274                 return nil, err
275         }
276         if len(noisePub) != 32 {
277                 return nil, errors.New("Invalid noisePub size")
278         }
279
280         noisePrv, err := FromBase32(yml.NoisePrv)
281         if err != nil {
282                 return nil, err
283         }
284         if len(noisePrv) != 32 {
285                 return nil, errors.New("Invalid noisePrv size")
286         }
287
288         node := NodeOur{
289                 Id:       id,
290                 ExchPub:  new([32]byte),
291                 ExchPrv:  new([32]byte),
292                 SignPub:  ed25519.PublicKey(signPub),
293                 SignPrv:  ed25519.PrivateKey(signPrv),
294                 NoisePub: new([32]byte),
295                 NoisePrv: new([32]byte),
296         }
297         copy(node.ExchPub[:], exchPub)
298         copy(node.ExchPrv[:], exchPrv)
299         copy(node.NoisePub[:], noisePub)
300         copy(node.NoisePrv[:], noisePrv)
301         return &node, nil
302 }
303
304 func (nodeOur *NodeOur) ToYAML() string {
305         yml := NodeOurYAML{
306                 Id:       nodeOur.Id.String(),
307                 ExchPub:  ToBase32(nodeOur.ExchPub[:]),
308                 ExchPrv:  ToBase32(nodeOur.ExchPrv[:]),
309                 SignPub:  ToBase32(nodeOur.SignPub[:]),
310                 SignPrv:  ToBase32(nodeOur.SignPrv[:]),
311                 NoisePub: ToBase32(nodeOur.NoisePub[:]),
312                 NoisePrv: ToBase32(nodeOur.NoisePrv[:]),
313         }
314         raw, err := yaml.Marshal(&yml)
315         if err != nil {
316                 panic(err)
317         }
318         return string(raw)
319 }
320
321 func CfgParse(data []byte) (*Ctx, error) {
322         var cfgYAML CfgYAML
323         err := yaml.Unmarshal(data, &cfgYAML)
324         if err != nil {
325                 return nil, err
326         }
327         self, err := NewNodeOur(cfgYAML.Self)
328         if err != nil {
329                 return nil, err
330         }
331         spoolPath := path.Clean(cfgYAML.Spool)
332         if !path.IsAbs(spoolPath) {
333                 return nil, errors.New("Spool path must be absolute")
334         }
335         logPath := path.Clean(cfgYAML.Log)
336         if !path.IsAbs(logPath) {
337                 return nil, errors.New("Log path must be absolute")
338         }
339         ctx := Ctx{
340                 Spool:   spoolPath,
341                 LogPath: logPath,
342                 Self:    self,
343                 Neigh:   make(map[NodeId]*Node, len(cfgYAML.Neigh)),
344                 Alias:   make(map[string]*NodeId),
345         }
346         if cfgYAML.Notify != nil {
347                 if cfgYAML.Notify.File != nil {
348                         ctx.NotifyFile = cfgYAML.Notify.File
349                 }
350                 if cfgYAML.Notify.Freq != nil {
351                         ctx.NotifyFreq = cfgYAML.Notify.Freq
352                 }
353         }
354         vias := make(map[NodeId][]string)
355         for name, neighYAML := range cfgYAML.Neigh {
356                 neigh, err := NewNode(name, neighYAML)
357                 if err != nil {
358                         return nil, err
359                 }
360                 ctx.Neigh[*neigh.Id] = neigh
361                 if _, already := ctx.Alias[name]; already {
362                         return nil, errors.New("Node names conflict")
363                 }
364                 ctx.Alias[name] = neigh.Id
365                 vias[*neigh.Id] = neighYAML.Via
366         }
367         for neighId, viasRaw := range vias {
368                 for _, viaRaw := range viasRaw {
369                         foundNodeId, err := ctx.FindNode(viaRaw)
370                         if err != nil {
371                                 return nil, err
372                         }
373                         ctx.Neigh[neighId].Via = append(
374                                 ctx.Neigh[neighId].Via,
375                                 foundNodeId.Id,
376                         )
377                 }
378         }
379         return &ctx, nil
380 }
381
382 func CfgPathFromEnv(cmdlineFlag *string) (p string) {
383         p = os.Getenv(CfgPathEnv)
384         if p == "" {
385                 p = *cmdlineFlag
386         }
387         return
388 }