/*
NNCP -- Node to Node copy, utilities for store-and-forward data exchange
-Copyright (C) 2016-2019 Sergey Matveev <stargrave@stargrave.org>
+Copyright (C) 2016-2021 Sergey Matveev <stargrave@stargrave.org>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
import (
"bytes"
+ "encoding/json"
"errors"
+ "fmt"
"log"
"os"
"path"
+ "strconv"
+ "time"
"github.com/gorhill/cronexpr"
+ "github.com/hjson/hjson-go"
"golang.org/x/crypto/ed25519"
- "golang.org/x/crypto/ssh/terminal"
- "gopkg.in/yaml.v2"
+ "golang.org/x/term"
)
const (
)
var (
- DefaultCfgPath string = "/usr/local/etc/nncp.yaml"
+ DefaultCfgPath string = "/usr/local/etc/nncp.hjson"
DefaultSendmailPath string = "/usr/sbin/sendmail"
DefaultSpoolPath string = "/var/spool/nncp"
DefaultLogPath string = "/var/spool/nncp/log"
)
-type NodeYAML struct {
- Id string
- ExchPub string
- SignPub string
- NoisePub *string `yaml:"noisepub,omitempty"`
- Exec map[string][]string `yaml:"exec,omitempty"`
- Incoming *string `yaml:"incoming,omitempty"`
- Freq *string `yaml:"freq,omitempty"`
- FreqChunked *uint64 `yaml:"freqchunked,omitempty"`
- FreqMinSize *uint64 `yaml:"freqminsize,omitempty"`
- Via []string `yaml:"via,omitempty"`
- Calls []CallYAML `yaml:"calls,omitempty"`
-
- Addrs map[string]string `yaml:"addrs,omitempty"`
-
- RxRate *int `yaml:"rxrate,omitempty"`
- TxRate *int `yaml:"txrate,omitempty"`
- OnlineDeadline *uint `yaml:"onlinedeadline,omitempty"`
- MaxOnlineTime *uint `yaml:"maxonlinetime,omitempty"`
+type NodeJSON struct {
+ Id string `json:"id"`
+ ExchPub string `json:"exchpub"`
+ SignPub string `json:"signpub"`
+ NoisePub *string `json:"noisepub,omitempty"`
+ Incoming *string `json:"incoming,omitempty"`
+ Exec map[string][]string `json:"exec,omitempty"`
+ Freq *NodeFreqJSON `json:"freq,omitempty"`
+ Via []string `json:"via,omitempty"`
+ Calls []CallJSON `json:"calls,omitempty"`
+
+ Addrs map[string]string `json:"addrs,omitempty"`
+
+ RxRate *int `json:"rxrate,omitempty"`
+ TxRate *int `json:"txrate,omitempty"`
+ OnlineDeadline *uint `json:"onlinedeadline,omitempty"`
+ MaxOnlineTime *uint `json:"maxonlinetime,omitempty"`
}
-type CallYAML struct {
- Cron string
- Nice *string `yaml:"nice,omitempty"`
- Xx string `yaml:"xx,omitempty"`
- RxRate *int `yaml:"rxrate,omitempty"`
- TxRate *int `yaml:"txrate,omitempty"`
- Addr *string `yaml:"addr,omitempty"`
- OnlineDeadline *uint `yaml:"onlinedeadline,omitempty"`
- MaxOnlineTime *uint `yaml:"maxonlinetime,omitempty"`
+type NodeFreqJSON struct {
+ Path *string `json:"path,omitempty"`
+ Chunked *uint64 `json:"chunked,omitempty"`
+ MinSize *uint64 `json:"minsize,omitempty"`
+ MaxSize *uint64 `json:"maxsize,omitempty"`
}
-type NodeOurYAML struct {
- Id string
- ExchPub string
- ExchPrv string
- SignPub string
- SignPrv string
- NoisePrv string
- NoisePub string
+type CallJSON struct {
+ Cron string `json:"cron"`
+ Nice *string `json:"nice,omitempty"`
+ Xx *string `json:"xx,omitempty"`
+ RxRate *int `json:"rxrate,omitempty"`
+ TxRate *int `json:"txrate,omitempty"`
+ Addr *string `json:"addr,omitempty"`
+ OnlineDeadline *uint `json:"onlinedeadline,omitempty"`
+ MaxOnlineTime *uint `json:"maxonlinetime,omitempty"`
+ WhenTxExists bool `json:"when-tx-exists,omitempty"`
+ NoCK bool `json:"nock,omitempty"`
+ MCDIgnore bool `json:"mcd-ignore,omitempty"`
+
+ AutoToss bool `json:"autotoss,omitempty"`
+ AutoTossDoSeen bool `json:"autotoss-doseen,omitempty"`
+ AutoTossNoFile bool `json:"autotoss-nofile,omitempty"`
+ AutoTossNoFreq bool `json:"autotoss-nofreq,omitempty"`
+ AutoTossNoExec bool `json:"autotoss-noexec,omitempty"`
+ AutoTossNoTrns bool `json:"autotoss-notrns,omitempty"`
+ AutoTossNoArea bool `json:"autotoss-noarea,omitempty"`
}
-type FromToYAML struct {
- From string
- To string
+type NodeOurJSON struct {
+ Id string `json:"id"`
+ ExchPub string `json:"exchpub"`
+ ExchPrv string `json:"exchprv"`
+ SignPub string `json:"signpub"`
+ SignPrv string `json:"signprv"`
+ NoisePub string `json:"noisepub"`
+ NoisePrv string `json:"noiseprv"`
}
-type NotifyYAML struct {
- File *FromToYAML `yaml:"file,omitempty"`
- Freq *FromToYAML `yaml:"freq,omitempty"`
+type FromToJSON struct {
+ From string `json:"from"`
+ To string `json:"to"`
}
-type CfgYAML struct {
- Self *NodeOurYAML `yaml:"self,omitempty"`
- Neigh map[string]NodeYAML
+type NotifyJSON struct {
+ File *FromToJSON `json:"file,omitempty"`
+ Freq *FromToJSON `json:"freq,omitempty"`
+ Exec map[string]*FromToJSON `json:"exec,omitempty"`
+}
+
+type AreaJSON struct {
+ Id string `json:"id"`
+ Pub *string `json:"pub,omitempty"`
+ Prv *string `json:"prv,omitempty"`
+
+ Subs []string `json:"subs"`
+
+ Incoming *string `json:"incoming,omitempty"`
+ Exec map[string][]string `json:"exec,omitempty"`
- Spool string
- Log string
- Notify *NotifyYAML `yaml:"notify,omitempty"`
+ AllowUnknown bool `json:"allow-unknown,omitempty"`
}
-func NewNode(name string, yml NodeYAML) (*Node, error) {
- nodeId, err := NodeIdFromString(yml.Id)
+type CfgJSON struct {
+ Spool string `json:"spool"`
+ Log string `json:"log"`
+ Umask *string `json:"umask,omitempty"`
+
+ OmitPrgrs bool `json:"noprogress,omitempty"`
+ NoHdr bool `json:"nohdr,omitempty"`
+
+ MCDRxIfis []string `json:"mcd-listen,omitempty"`
+ MCDTxIfis map[string]int `json:"mcd-send,omitempty"`
+
+ Notify *NotifyJSON `json:"notify,omitempty"`
+
+ Self *NodeOurJSON `json:"self"`
+ Neigh map[string]NodeJSON `json:"neigh"`
+
+ Areas map[string]AreaJSON `json:"areas,omitempty"`
+}
+
+func NewNode(name string, cfg NodeJSON) (*Node, error) {
+ nodeId, err := NodeIdFromString(cfg.Id)
if err != nil {
return nil, err
}
- exchPub, err := FromBase32(yml.ExchPub)
+ exchPub, err := Base32Codec.DecodeString(cfg.ExchPub)
if err != nil {
return nil, err
}
return nil, errors.New("Invalid exchPub size")
}
- signPub, err := FromBase32(yml.SignPub)
+ signPub, err := Base32Codec.DecodeString(cfg.SignPub)
if err != nil {
return nil, err
}
}
var noisePub []byte
- if yml.NoisePub != nil {
- noisePub, err = FromBase32(*yml.NoisePub)
+ if cfg.NoisePub != nil {
+ noisePub, err = Base32Codec.DecodeString(*cfg.NoisePub)
if err != nil {
return nil, err
}
}
var incoming *string
- if yml.Incoming != nil {
- inc := path.Clean(*yml.Incoming)
+ if cfg.Incoming != nil {
+ inc := path.Clean(*cfg.Incoming)
if !path.IsAbs(inc) {
return nil, errors.New("Incoming path must be absolute")
}
incoming = &inc
}
- var freq *string
- if yml.Freq != nil {
- fr := path.Clean(*yml.Freq)
- if !path.IsAbs(fr) {
- return nil, errors.New("Freq path must be absolute")
+ var freqPath *string
+ freqChunked := int64(MaxFileSize)
+ var freqMinSize int64
+ freqMaxSize := int64(MaxFileSize)
+ if cfg.Freq != nil {
+ f := cfg.Freq
+ if f.Path != nil {
+ fPath := path.Clean(*f.Path)
+ if !path.IsAbs(fPath) {
+ return nil, errors.New("freq.path path must be absolute")
+ }
+ freqPath = &fPath
}
- freq = &fr
- }
- var freqChunked int64
- if yml.FreqChunked != nil {
- if *yml.FreqChunked == 0 {
- return nil, errors.New("freqchunked value must be greater than zero")
+ if f.Chunked != nil {
+ if *f.Chunked == 0 {
+ return nil, errors.New("freq.chunked value must be greater than zero")
+ }
+ freqChunked = int64(*f.Chunked) * 1024
+ }
+ if f.MinSize != nil {
+ freqMinSize = int64(*f.MinSize) * 1024
+ }
+ if f.MaxSize != nil {
+ freqMaxSize = int64(*f.MaxSize) * 1024
}
- freqChunked = int64(*yml.FreqChunked) * 1024
- }
- var freqMinSize int64
- if yml.FreqMinSize != nil {
- freqMinSize = int64(*yml.FreqMinSize) * 1024
}
defRxRate := 0
- if yml.RxRate != nil && *yml.RxRate > 0 {
- defRxRate = *yml.RxRate
+ if cfg.RxRate != nil && *cfg.RxRate > 0 {
+ defRxRate = *cfg.RxRate
}
defTxRate := 0
- if yml.TxRate != nil && *yml.TxRate > 0 {
- defTxRate = *yml.TxRate
+ if cfg.TxRate != nil && *cfg.TxRate > 0 {
+ defTxRate = *cfg.TxRate
}
- defOnlineDeadline := uint(DefaultDeadline)
- if yml.OnlineDeadline != nil {
- if *yml.OnlineDeadline <= 0 {
+ defOnlineDeadline := DefaultDeadline
+ if cfg.OnlineDeadline != nil {
+ if *cfg.OnlineDeadline <= 0 {
return nil, errors.New("OnlineDeadline must be at least 1 second")
}
- defOnlineDeadline = *yml.OnlineDeadline
+ defOnlineDeadline = time.Duration(*cfg.OnlineDeadline) * time.Second
}
- var defMaxOnlineTime uint
- if yml.MaxOnlineTime != nil {
- defMaxOnlineTime = *yml.MaxOnlineTime
+ var defMaxOnlineTime time.Duration
+ if cfg.MaxOnlineTime != nil {
+ defMaxOnlineTime = time.Duration(*cfg.MaxOnlineTime) * time.Second
}
var calls []*Call
- for _, callYml := range yml.Calls {
- expr, err := cronexpr.Parse(callYml.Cron)
+ for _, callCfg := range cfg.Calls {
+ expr, err := cronexpr.Parse(callCfg.Cron)
if err != nil {
return nil, err
}
nice := uint8(255)
- if callYml.Nice != nil {
- nice, err = NicenessParse(*callYml.Nice)
+ if callCfg.Nice != nil {
+ nice, err = NicenessParse(*callCfg.Nice)
if err != nil {
return nil, err
}
}
var xx TRxTx
- switch callYml.Xx {
- case "rx":
- xx = TRx
- case "tx":
- xx = TTx
- case "":
- default:
- return nil, errors.New("xx field must be either \"rx\" or \"tx\"")
+ if callCfg.Xx != nil {
+ switch *callCfg.Xx {
+ case "rx":
+ xx = TRx
+ case "tx":
+ xx = TTx
+ default:
+ return nil, errors.New("xx field must be either \"rx\" or \"tx\"")
+ }
}
rxRate := defRxRate
- if callYml.RxRate != nil {
- rxRate = *callYml.RxRate
+ if callCfg.RxRate != nil {
+ rxRate = *callCfg.RxRate
}
txRate := defTxRate
- if callYml.TxRate != nil {
- txRate = *callYml.TxRate
+ if callCfg.TxRate != nil {
+ txRate = *callCfg.TxRate
}
var addr *string
- if callYml.Addr != nil {
- if a, exists := yml.Addrs[*callYml.Addr]; exists {
+ if callCfg.Addr != nil {
+ if a, exists := cfg.Addrs[*callCfg.Addr]; exists {
addr = &a
} else {
- addr = callYml.Addr
+ addr = callCfg.Addr
}
}
onlineDeadline := defOnlineDeadline
- if callYml.OnlineDeadline != nil {
- if *callYml.OnlineDeadline == 0 {
+ if callCfg.OnlineDeadline != nil {
+ if *callCfg.OnlineDeadline == 0 {
return nil, errors.New("OnlineDeadline must be at least 1 second")
}
- onlineDeadline = *callYml.OnlineDeadline
- }
-
- var maxOnlineTime uint
- if callYml.MaxOnlineTime != nil {
- maxOnlineTime = *callYml.MaxOnlineTime
+ onlineDeadline = time.Duration(*callCfg.OnlineDeadline) * time.Second
}
- calls = append(calls, &Call{
+ call := Call{
Cron: expr,
Nice: nice,
Xx: xx,
TxRate: txRate,
Addr: addr,
OnlineDeadline: onlineDeadline,
- MaxOnlineTime: maxOnlineTime,
- })
+ }
+
+ if callCfg.MaxOnlineTime != nil {
+ call.MaxOnlineTime = time.Duration(*callCfg.MaxOnlineTime) * time.Second
+ }
+ call.WhenTxExists = callCfg.WhenTxExists
+ call.NoCK = callCfg.NoCK
+ call.MCDIgnore = callCfg.MCDIgnore
+ call.AutoToss = callCfg.AutoToss
+ call.AutoTossDoSeen = callCfg.AutoTossDoSeen
+ call.AutoTossNoFile = callCfg.AutoTossNoFile
+ call.AutoTossNoFreq = callCfg.AutoTossNoFreq
+ call.AutoTossNoExec = callCfg.AutoTossNoExec
+ call.AutoTossNoTrns = callCfg.AutoTossNoTrns
+ call.AutoTossNoArea = callCfg.AutoTossNoArea
+
+ calls = append(calls, &call)
}
node := Node{
Id: nodeId,
ExchPub: new([32]byte),
SignPub: ed25519.PublicKey(signPub),
- Exec: yml.Exec,
+ Exec: cfg.Exec,
Incoming: incoming,
- Freq: freq,
+ FreqPath: freqPath,
FreqChunked: freqChunked,
FreqMinSize: freqMinSize,
+ FreqMaxSize: freqMaxSize,
Calls: calls,
- Addrs: yml.Addrs,
+ Addrs: cfg.Addrs,
RxRate: defRxRate,
TxRate: defTxRate,
OnlineDeadline: defOnlineDeadline,
return &node, nil
}
-func NewNodeOur(yml *NodeOurYAML) (*NodeOur, error) {
- id, err := NodeIdFromString(yml.Id)
+func NewNodeOur(cfg *NodeOurJSON) (*NodeOur, error) {
+ id, err := NodeIdFromString(cfg.Id)
if err != nil {
return nil, err
}
- exchPub, err := FromBase32(yml.ExchPub)
+ exchPub, err := Base32Codec.DecodeString(cfg.ExchPub)
if err != nil {
return nil, err
}
return nil, errors.New("Invalid exchPub size")
}
- exchPrv, err := FromBase32(yml.ExchPrv)
+ exchPrv, err := Base32Codec.DecodeString(cfg.ExchPrv)
if err != nil {
return nil, err
}
return nil, errors.New("Invalid exchPrv size")
}
- signPub, err := FromBase32(yml.SignPub)
+ signPub, err := Base32Codec.DecodeString(cfg.SignPub)
if err != nil {
return nil, err
}
return nil, errors.New("Invalid signPub size")
}
- signPrv, err := FromBase32(yml.SignPrv)
+ signPrv, err := Base32Codec.DecodeString(cfg.SignPrv)
if err != nil {
return nil, err
}
return nil, errors.New("Invalid signPrv size")
}
- noisePub, err := FromBase32(yml.NoisePub)
+ noisePub, err := Base32Codec.DecodeString(cfg.NoisePub)
if err != nil {
return nil, err
}
return nil, errors.New("Invalid noisePub size")
}
- noisePrv, err := FromBase32(yml.NoisePrv)
+ noisePrv, err := Base32Codec.DecodeString(cfg.NoisePrv)
if err != nil {
return nil, err
}
return &node, nil
}
-func (nodeOur *NodeOur) ToYAML() string {
- yml := NodeOurYAML{
- Id: nodeOur.Id.String(),
- ExchPub: ToBase32(nodeOur.ExchPub[:]),
- ExchPrv: ToBase32(nodeOur.ExchPrv[:]),
- SignPub: ToBase32(nodeOur.SignPub[:]),
- SignPrv: ToBase32(nodeOur.SignPrv[:]),
- NoisePub: ToBase32(nodeOur.NoisePub[:]),
- NoisePrv: ToBase32(nodeOur.NoisePrv[:]),
- }
- raw, err := yaml.Marshal(&yml)
+func NewArea(ctx *Ctx, name string, cfg *AreaJSON) (*Area, error) {
+ areaId, err := AreaIdFromString(cfg.Id)
if err != nil {
- panic(err)
+ return nil, err
+ }
+ subs := make([]*NodeId, 0, len(cfg.Subs))
+ for _, s := range cfg.Subs {
+ node, err := ctx.FindNode(s)
+ if err != nil {
+ return nil, err
+ }
+ subs = append(subs, node.Id)
+ }
+ area := Area{
+ Name: name,
+ Id: areaId,
+ Subs: subs,
+ Exec: cfg.Exec,
+ Incoming: cfg.Incoming,
+ }
+ if cfg.Pub != nil {
+ pub, err := Base32Codec.DecodeString(*cfg.Pub)
+ if err != nil {
+ return nil, err
+ }
+ if len(pub) != 32 {
+ return nil, errors.New("Invalid pub size")
+ }
+ area.Pub = new([32]byte)
+ copy(area.Pub[:], pub)
}
- return string(raw)
+ if cfg.Prv != nil {
+ if area.Pub == nil {
+ return nil, fmt.Errorf("area %s: prv requires pub presence", name)
+ }
+ prv, err := Base32Codec.DecodeString(*cfg.Prv)
+ if err != nil {
+ return nil, err
+ }
+ if len(prv) != 32 {
+ return nil, errors.New("Invalid prv size")
+ }
+ area.Prv = new([32]byte)
+ copy(area.Prv[:], prv)
+ }
+ area.AllowUnknown = cfg.AllowUnknown
+ return &area, nil
}
-func CfgParse(data []byte) (*Ctx, error) {
+func CfgParse(data []byte) (*CfgJSON, error) {
var err error
- if bytes.Compare(data[:8], MagicNNCPBv3[:]) == 0 {
+ if bytes.Compare(data[:8], MagicNNCPBv3.B[:]) == 0 {
os.Stderr.WriteString("Passphrase:")
- password, err := terminal.ReadPassword(0)
+ password, err := term.ReadPassword(0)
if err != nil {
log.Fatalln(err)
}
if err != nil {
return nil, err
}
+ } else if bytes.Compare(data[:8], MagicNNCPBv2.B[:]) == 0 {
+ log.Fatalln(MagicNNCPBv2.TooOld())
+ } else if bytes.Compare(data[:8], MagicNNCPBv1.B[:]) == 0 {
+ log.Fatalln(MagicNNCPBv1.TooOld())
+ }
+ var cfgGeneral map[string]interface{}
+ if err = hjson.Unmarshal(data, &cfgGeneral); err != nil {
+ return nil, err
}
- var cfgYAML CfgYAML
- if err = yaml.Unmarshal(data, &cfgYAML); err != nil {
+ marshaled, err := json.Marshal(cfgGeneral)
+ if err != nil {
return nil, err
}
- if _, exists := cfgYAML.Neigh["self"]; !exists {
+ var cfgJSON CfgJSON
+ err = json.Unmarshal(marshaled, &cfgJSON)
+ return &cfgJSON, err
+}
+
+func Cfg2Ctx(cfgJSON *CfgJSON) (*Ctx, error) {
+ if _, exists := cfgJSON.Neigh["self"]; !exists {
return nil, errors.New("self neighbour missing")
}
var self *NodeOur
- if cfgYAML.Self != nil {
- self, err = NewNodeOur(cfgYAML.Self)
+ if cfgJSON.Self != nil {
+ var err error
+ self, err = NewNodeOur(cfgJSON.Self)
if err != nil {
return nil, err
}
}
- spoolPath := path.Clean(cfgYAML.Spool)
+ spoolPath := path.Clean(cfgJSON.Spool)
if !path.IsAbs(spoolPath) {
return nil, errors.New("Spool path must be absolute")
}
- logPath := path.Clean(cfgYAML.Log)
+ logPath := path.Clean(cfgJSON.Log)
if !path.IsAbs(logPath) {
return nil, errors.New("Log path must be absolute")
}
- ctx := Ctx{
- Spool: spoolPath,
- LogPath: logPath,
- Self: self,
- Neigh: make(map[NodeId]*Node, len(cfgYAML.Neigh)),
- Alias: make(map[string]*NodeId),
+ var umaskForce *int
+ if cfgJSON.Umask != nil {
+ r, err := strconv.ParseUint(*cfgJSON.Umask, 8, 16)
+ if err != nil {
+ return nil, err
+ }
+ rInt := int(r)
+ umaskForce = &rInt
+ }
+ showPrgrs := true
+ if cfgJSON.OmitPrgrs {
+ showPrgrs = false
}
- if cfgYAML.Notify != nil {
- if cfgYAML.Notify.File != nil {
- ctx.NotifyFile = cfgYAML.Notify.File
+ hdrUsage := true
+ if cfgJSON.NoHdr {
+ hdrUsage = false
+ }
+ ctx := Ctx{
+ Spool: spoolPath,
+ LogPath: logPath,
+ UmaskForce: umaskForce,
+ ShowPrgrs: showPrgrs,
+ HdrUsage: hdrUsage,
+ Self: self,
+ Neigh: make(map[NodeId]*Node, len(cfgJSON.Neigh)),
+ Alias: make(map[string]*NodeId),
+ MCDRxIfis: cfgJSON.MCDRxIfis,
+ MCDTxIfis: cfgJSON.MCDTxIfis,
+ }
+ if cfgJSON.Notify != nil {
+ if cfgJSON.Notify.File != nil {
+ ctx.NotifyFile = cfgJSON.Notify.File
}
- if cfgYAML.Notify.Freq != nil {
- ctx.NotifyFreq = cfgYAML.Notify.Freq
+ if cfgJSON.Notify.Freq != nil {
+ ctx.NotifyFreq = cfgJSON.Notify.Freq
+ }
+ if cfgJSON.Notify.Exec != nil {
+ ctx.NotifyExec = cfgJSON.Notify.Exec
}
}
vias := make(map[NodeId][]string)
- for name, neighYAML := range cfgYAML.Neigh {
- neigh, err := NewNode(name, neighYAML)
+ for name, neighJSON := range cfgJSON.Neigh {
+ neigh, err := NewNode(name, neighJSON)
if err != nil {
return nil, err
}
return nil, errors.New("Node names conflict")
}
ctx.Alias[name] = neigh.Id
- vias[*neigh.Id] = neighYAML.Via
+ vias[*neigh.Id] = neighJSON.Via
}
ctx.SelfId = ctx.Alias["self"]
for neighId, viasRaw := range vias {
)
}
}
+ ctx.AreaId2Area = make(map[AreaId]*Area, len(cfgJSON.Areas))
+ ctx.AreaName2Id = make(map[string]*AreaId, len(cfgJSON.Areas))
+ for name, areaJSON := range cfgJSON.Areas {
+ area, err := NewArea(&ctx, name, &areaJSON)
+ if err != nil {
+ return nil, err
+ }
+ ctx.AreaId2Area[*area.Id] = area
+ ctx.AreaName2Id[name] = area.Id
+ }
return &ctx, nil
}