From: Sergey Matveev Date: Fri, 9 Jul 2021 19:52:51 +0000 (+0300) Subject: nncp-cfgdir X-Git-Tag: v7.3.0^2~1 X-Git-Url: http://www.git.cypherpunks.ru/?p=nncp.git;a=commitdiff_plain;h=65ac1674ff0f9bd99bb29b5b8b1dc596b06216ce nncp-cfgdir --- diff --git a/bin/cmd.list b/bin/cmd.list index 693027a..de2a35f 100644 --- a/bin/cmd.list +++ b/bin/cmd.list @@ -1,6 +1,7 @@ nncp-bundle nncp-call nncp-caller +nncp-cfgdir nncp-cfgenc nncp-cfgmin nncp-cfgnew diff --git a/doc/cfg/dir.texi b/doc/cfg/dir.texi new file mode 100644 index 0000000..8297925 --- /dev/null +++ b/doc/cfg/dir.texi @@ -0,0 +1,107 @@ +@node Configuration directory +@section Configuration directory + +Optionally you can convert configuration file to the directory layout +with @ref{nncp-cfgdir} command. And vice versa too, of course loosing +the comment lines. Directory layout can looks like that: + +@example +nncp-cfg-dir +├── areas +│ ├── home +│ │ ├── id +│ │ ├── incoming +│ │ ├── prv +│ │ ├── pub +│ │ └── subs +│ └── homero +│ ├── id +│ ├── exec +│ │ └── sendmail +│ ├── prv +│ ├── pub +│ └── allow-unknown +├── log +├── mcd-listen +├── neigh +│ ├── beta +│ │ ├── exchpub +│ │ ├── exec +│ │ │ └── sendmail +│ │ ├── id +│ │ ├── incoming +│ │ ├── noisepub +│ │ ├── signpub +│ │ └── via +│ ├── gw +│ │ ├── addrs +│ │ │ ├── lan +│ │ │ └── main +│ │ ├── calls +│ │ │ ├── 0 +│ │ │ │ ├── autotoss +│ │ │ │ ├── cron +│ │ │ │ ├── nice +│ │ │ │ └── rxrate +│ │ │ ├── 1 +│ │ │ │ ├── addr +│ │ │ │ ├── autotoss +│ │ │ │ ├── cron +│ │ │ │ └── onlinedeadline +│ │ │ └── 2 +│ │ │ ├── addr +│ │ │ ├── autotoss +│ │ │ ├── cron +│ │ │ └── onlinedeadline +│ │ ├── exchpub +│ │ ├── exec +│ │ │ └── sendmail +│ │ ├── freq +│ │ │ └── chunked +│ │ ├── id +│ │ ├── incoming +│ │ ├── noisepub +│ │ └── signpub +│ └── self +│ ├── exchpub +│ ├── exec +│ │ ├── appender +│ │ ├── sendmail +│ │ └── slow +│ ├── freq +│ │ └── path +│ ├── id +│ ├── incoming +│ ├── noisepub +│ └── signpub +├── notify +│ ├── file +│ │ ├── from +│ │ └── to +│ └── freq +│ ├── from +│ └── to +├── self +│ ├── exchprv +│ ├── exchpub +│ ├── id +│ ├── noiseprv +│ ├── noisepub +│ ├── signprv +│ └── signpub +└── spool +@end example + +Your @option{-cfg} and @env{$NNCPCFG} could point to that directory, +instead of @file{.hjson} file. It will be transparently converted to +internal JSON representation. However it can not be encrypted with the +@ref{nncp-cfgenc}. + +That layout should be much more machine friendly and scriptable. Each +string parameters is stored as a single line plain text file. String +arrays are newline-separated plain text files. Dictionaries are +transformed to the subdirectories. Its structure should be +self-describing. True booleans are stored as an empty flag-file +existence (their absence equals to false). All names starting with "." +are skipped. All files ending with @file{prv} are created with 600 +permissions, instead of the default 666. diff --git a/doc/cfg/index.texi b/doc/cfg/index.texi index 1944eaa..b44f243 100644 --- a/doc/cfg/index.texi +++ b/doc/cfg/index.texi @@ -2,8 +2,9 @@ @unnumbered Configuration file NNCP uses single file configuration file in @url{https://hjson.org/, -Hjson} format. Initially it is created with @ref{nncp-cfgnew} command -and at minimum it can look like this: +Hjson} format (see also section about @ref{Configuration directory, +directory layout}) . Initially it is created with @ref{nncp-cfgnew} +command and at minimum it can look like this: @verbatim spool: /var/spool/nncp @@ -32,7 +33,7 @@ neigh: { And for being able to communicate with at least one other node, you just need to add single key to the @code{neigh} section similar to the "self". -All configuration file can be separated on five sections: +Whole configuration file can be separated on five sections: @menu * General options: CfgGeneral. @@ -40,6 +41,9 @@ All configuration file can be separated on five sections: * Notifications: CfgNotify. * Neighbours: CfgNeigh. * Areas: CfgAreas. + +You can optionally convert it to directory layout +* Configuration directory:: @end menu @include cfg/general.texi @@ -47,3 +51,4 @@ All configuration file can be separated on five sections: @include cfg/notify.texi @include cfg/neigh.texi @include cfg/areas.texi +@include cfg/dir.texi diff --git a/doc/cmd/index.texi b/doc/cmd/index.texi index a360e28..b4cdce8 100644 --- a/doc/cmd/index.texi +++ b/doc/cmd/index.texi @@ -48,6 +48,7 @@ Configuration file commands * nncp-cfgnew:: * nncp-cfgmin:: * nncp-cfgenc:: +* nncp-cfgdir:: Packets creation commands @@ -86,6 +87,7 @@ Maintenance, monitoring and debugging commands: @include cmd/nncp-cfgnew.texi @include cmd/nncp-cfgmin.texi @include cmd/nncp-cfgenc.texi +@include cmd/nncp-cfgdir.texi @include cmd/nncp-file.texi @include cmd/nncp-exec.texi @include cmd/nncp-freq.texi diff --git a/doc/cmd/nncp-cfgdir.texi b/doc/cmd/nncp-cfgdir.texi new file mode 100644 index 0000000..ec45ddd --- /dev/null +++ b/doc/cmd/nncp-cfgdir.texi @@ -0,0 +1,12 @@ +@node nncp-cfgdir +@section nncp-cfgdir + +@example +$ nncp-cfgdir [options] [-cfg ...] -dump /path/to/dir +$ nncp-cfgdir [options] -load /path/to/dir > cfg.hjson +@end example + +@option{-dump} option dumps current configuration file to the +@ref{Configuration directory, directory layout} at @file{/path/to/dir}. +@option{-load} loads it and parses, outputing the resulting Hjson to +stdout. diff --git a/doc/news.ru.texi b/doc/news.ru.texi index 5671040..a3f6c40 100644 --- a/doc/news.ru.texi +++ b/doc/news.ru.texi @@ -2,6 +2,7 @@ @section Новости @menu +* Релиз 7.3.0:: * Релиз 7.2.1:: * Релиз 7.2.0:: * Релиз 7.1.1:: @@ -52,6 +53,16 @@ * Релиз 0.2:: @end menu +@node Релиз 7.3.0 +@subsection Релиз 7.3.0 +@itemize + +@item +Возможность использовать конфигурацию в виде директории с набором +файлов. Появилась команда @command{nncp-cfgdir}. + +@end itemize + @node Релиз 7.2.1 @subsection Релиз 7.2.1 @itemize diff --git a/doc/news.texi b/doc/news.texi index dcb8087..3375cd4 100644 --- a/doc/news.texi +++ b/doc/news.texi @@ -4,6 +4,7 @@ See also this page @ref{Новости, on russian}. @menu +* Release 7.3.0: Release 7_3_0. * Release 7.2.1: Release 7_2_1. * Release 7.2.0: Release 7_2_0. * Release 7.1.1: Release 7_1_1. @@ -54,6 +55,16 @@ See also this page @ref{Новости, on russian}. * Release 0.2: Release 0_2. @end menu +@node Release 7_3_0 +@section Release 7.3.0 +@itemize + +@item +Ability to use directory with a bunch of files as a configuration. +@command{nncp-cfgdir} command appeared. + +@end itemize + @node Release 7_2_1 @section Release 7.2.1 @itemize diff --git a/ports/nncp/Makefile b/ports/nncp/Makefile index 7aed8a6..c0022f0 100644 --- a/ports/nncp/Makefile +++ b/ports/nncp/Makefile @@ -1,5 +1,5 @@ PORTNAME= nncp -DISTVERSION= 7.2.0 +DISTVERSION= 7.3.0 CATEGORIES= net MASTER_SITES= http://www.nncpgo.org/download/ diff --git a/ports/nncp/pkg-plist b/ports/nncp/pkg-plist index 268e547..e360a4a 100644 --- a/ports/nncp/pkg-plist +++ b/ports/nncp/pkg-plist @@ -1,6 +1,7 @@ bin/nncp-bundle bin/nncp-call bin/nncp-caller +bin/nncp-cfgdir bin/nncp-cfgenc bin/nncp-cfgmin bin/nncp-cfgnew diff --git a/src/cfg.go b/src/cfg.go index bacfd7e..5a404ed 100644 --- a/src/cfg.go +++ b/src/cfg.go @@ -51,8 +51,8 @@ type NodeJSON struct { ExchPub string `json:"exchpub"` SignPub string `json:"signpub"` NoisePub *string `json:"noisepub,omitempty"` - Exec map[string][]string `json:"exec,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"` @@ -73,7 +73,7 @@ type NodeFreqJSON struct { } type CallJSON struct { - Cron string + Cron string `json:"cron"` Nice *string `json:"nice,omitempty"` Xx *string `json:"xx,omitempty"` RxRate *int `json:"rxrate,omitempty"` @@ -81,17 +81,17 @@ type CallJSON struct { 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"` - MCDIgnore *bool `json:"mcd-ignore"` - - 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"` + 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 NodeOurJSON struct { @@ -100,13 +100,13 @@ type NodeOurJSON struct { ExchPrv string `json:"exchprv"` SignPub string `json:"signpub"` SignPrv string `json:"signprv"` - NoisePrv string `json:"noiseprv"` NoisePub string `json:"noisepub"` + NoisePrv string `json:"noiseprv"` } type FromToJSON struct { - From string - To string + From string `json:"from"` + To string `json:"to"` } type NotifyJSON struct { @@ -117,34 +117,34 @@ type NotifyJSON struct { type AreaJSON struct { Id string `json:"id"` - Pub string `json:"pub"` + Pub *string `json:"pub,omitempty"` Prv *string `json:"prv,omitempty"` Subs []string `json:"subs"` - Exec map[string][]string `json:"exec,omitempty"` Incoming *string `json:"incoming,omitempty"` + Exec map[string][]string `json:"exec,omitempty"` - AllowUnknown *bool `json:"allow-unknown,omitempty"` + AllowUnknown bool `json:"allow-unknown,omitempty"` } type CfgJSON struct { - Spool string `json:"spool"` - Log string `json:"log"` - Umask string `json:"umask,omitempty"` + 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"` - MCDRxIfis []string `json:"mcd-listen"` - MCDTxIfis map[string]int `json:"mcd-send"` - - Areas map[string]AreaJSON `json:"areas"` + Areas map[string]AreaJSON `json:"areas,omitempty"` } func NewNode(name string, cfg NodeJSON) (*Node, error) { @@ -303,36 +303,16 @@ func NewNode(name string, cfg NodeJSON) (*Node, error) { if callCfg.MaxOnlineTime != nil { call.MaxOnlineTime = time.Duration(*callCfg.MaxOnlineTime) * time.Second } - if callCfg.WhenTxExists != nil { - call.WhenTxExists = *callCfg.WhenTxExists - } - if callCfg.NoCK != nil { - call.NoCK = *callCfg.NoCK - } - if callCfg.MCDIgnore != nil { - call.MCDIgnore = *callCfg.MCDIgnore - } - if callCfg.AutoToss != nil { - call.AutoToss = *callCfg.AutoToss - } - if callCfg.AutoTossDoSeen != nil { - call.AutoTossDoSeen = *callCfg.AutoTossDoSeen - } - if callCfg.AutoTossNoFile != nil { - call.AutoTossNoFile = *callCfg.AutoTossNoFile - } - if callCfg.AutoTossNoFreq != nil { - call.AutoTossNoFreq = *callCfg.AutoTossNoFreq - } - if callCfg.AutoTossNoExec != nil { - call.AutoTossNoExec = *callCfg.AutoTossNoExec - } - if callCfg.AutoTossNoTrns != nil { - call.AutoTossNoTrns = *callCfg.AutoTossNoTrns - } - if callCfg.AutoTossNoArea != nil { - call.AutoTossNoArea = *callCfg.AutoTossNoArea - } + 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) } @@ -449,19 +429,21 @@ func NewArea(ctx *Ctx, name string, cfg *AreaJSON) (*Area, error) { area := Area{ Name: name, Id: areaId, - Pub: new([32]byte), Subs: subs, Exec: cfg.Exec, Incoming: cfg.Incoming, } - pub, err := Base32Codec.DecodeString(cfg.Pub) - if err != nil { - return nil, err - } - if len(pub) != 32 { - return nil, errors.New("Invalid pub size") + 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) } - copy(area.Pub[:], pub) if cfg.Prv != nil { prv, err := Base32Codec.DecodeString(*cfg.Prv) if err != nil { @@ -473,13 +455,11 @@ func NewArea(ctx *Ctx, name string, cfg *AreaJSON) (*Area, error) { area.Prv = new([32]byte) copy(area.Prv[:], prv) } - if cfg.AllowUnknown != nil { - area.AllowUnknown = *cfg.AllowUnknown - } + 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.B[:]) == 0 { os.Stderr.WriteString("Passphrase:") // #nosec G104 @@ -506,14 +486,17 @@ func CfgParse(data []byte) (*Ctx, error) { return nil, err } var cfgJSON CfgJSON - if err = json.Unmarshal(marshaled, &cfgJSON); err != nil { - return nil, err - } + 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 cfgJSON.Self != nil { + var err error self, err = NewNodeOur(cfgJSON.Self) if err != nil { return nil, err @@ -528,8 +511,8 @@ func CfgParse(data []byte) (*Ctx, error) { return nil, errors.New("Log path must be absolute") } var umaskForce *int - if cfgJSON.Umask != "" { - r, err := strconv.ParseUint(cfgJSON.Umask, 8, 16) + if cfgJSON.Umask != nil { + r, err := strconv.ParseUint(*cfgJSON.Umask, 8, 16) if err != nil { return nil, err } diff --git a/src/cfgdir.go b/src/cfgdir.go new file mode 100644 index 0000000..ec1e58c --- /dev/null +++ b/src/cfgdir.go @@ -0,0 +1,903 @@ +/* +NNCP -- Node to Node copy, utilities for store-and-forward data exchange +Copyright (C) 2016-2021 Sergey Matveev + +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 +the Free Software Foundation, version 3 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +package nncp + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "sort" + "strconv" + "strings" +) + +func cfgDirMkdir(dst ...string) error { + return os.MkdirAll(filepath.Join(dst...), os.FileMode(0777)) +} + +func cfgDirSave(v interface{}, dst ...string) error { + var r string + switch v := v.(type) { + case *string: + if v == nil { + return nil + } + r = *v + case string: + r = v + case *int: + if v == nil { + return nil + } + r = strconv.Itoa(*v) + case *uint: + if v == nil { + return nil + } + r = strconv.Itoa(int(*v)) + case *uint64: + if v == nil { + return nil + } + r = strconv.FormatUint(*v, 10) + case int: + r = strconv.Itoa(v) + default: + panic("unsupported value type") + } + mode := os.FileMode(0666) + if strings.HasSuffix(dst[len(dst)-1], "prv") { + mode = os.FileMode(0600) + } + return ioutil.WriteFile(filepath.Join(dst...), []byte(r+"\n"), mode) +} + +func cfgDirTouch(dst ...string) error { + if fd, err := os.Create(filepath.Join(dst...)); err == nil { + fd.Close() + } else { + return err + } + return nil +} + +func CfgToDir(dst string, cfg *CfgJSON) (err error) { + if err = cfgDirMkdir(dst); err != nil { + return + } + if err = cfgDirSave(cfg.Spool, dst, "spool"); err != nil { + return + } + if err = cfgDirSave(cfg.Log, dst, "log"); err != nil { + return + } + if err = cfgDirSave(cfg.Umask, dst, "umask"); err != nil { + return + } + if cfg.OmitPrgrs { + if err = cfgDirTouch(dst, "noprogress"); err != nil { + return + } + } + if cfg.NoHdr { + if err = cfgDirTouch(dst, "nohdr"); err != nil { + return + } + } + + if len(cfg.MCDRxIfis) > 0 { + if err = cfgDirSave( + strings.Join(cfg.MCDRxIfis, "\n"), + dst, "mcd-listen", + ); err != nil { + return + } + } + if len(cfg.MCDTxIfis) > 0 { + if err = cfgDirMkdir(dst, "mcd-send"); err != nil { + return + } + for ifi, t := range cfg.MCDTxIfis { + if err = cfgDirSave(t, dst, "mcd-send", ifi); err != nil { + return + } + } + } + + if cfg.Notify != nil { + if cfg.Notify.File != nil { + if err = cfgDirMkdir(dst, "notify", "file"); err != nil { + return + } + if err = cfgDirSave( + cfg.Notify.File.From, + dst, "notify", "file", "from", + ); err != nil { + return + } + if err = cfgDirSave( + cfg.Notify.File.To, + dst, "notify", "file", "to", + ); err != nil { + return + } + } + if cfg.Notify.Freq != nil { + if err = cfgDirMkdir(dst, "notify", "freq"); err != nil { + return + } + if err = cfgDirSave( + cfg.Notify.Freq.From, + dst, "notify", "freq", "from", + ); err != nil { + return + } + if err = cfgDirSave( + cfg.Notify.Freq.To, + dst, "notify", "freq", "to", + ); err != nil { + return + } + } + for k, v := range cfg.Notify.Exec { + if err = cfgDirMkdir(dst, "notify", "exec", k); err != nil { + return + } + if err = cfgDirSave(v.From, dst, "notify", "exec", k, "from"); err != nil { + return + } + if err = cfgDirSave(v.To, dst, "notify", "exec", k, "to"); err != nil { + return + } + } + } + + if err = cfgDirMkdir(dst, "self"); err != nil { + return + } + if err = cfgDirSave(cfg.Self.Id, dst, "self", "id"); err != nil { + return + } + if err = cfgDirSave(cfg.Self.ExchPub, dst, "self", "exchpub"); err != nil { + return + } + if err = cfgDirSave(cfg.Self.ExchPrv, dst, "self", "exchprv"); err != nil { + return + } + if err = cfgDirSave(cfg.Self.SignPub, dst, "self", "signpub"); err != nil { + return + } + if err = cfgDirSave(cfg.Self.SignPrv, dst, "self", "signprv"); err != nil { + return + } + if err = cfgDirSave(cfg.Self.NoisePub, dst, "self", "noisepub"); err != nil { + return + } + if err = cfgDirSave(cfg.Self.NoisePrv, dst, "self", "noiseprv"); err != nil { + return + } + + for name, n := range cfg.Neigh { + if err = cfgDirMkdir(dst, "neigh", name); err != nil { + return + } + if err = cfgDirSave(n.Id, dst, "neigh", name, "id"); err != nil { + return + } + if err = cfgDirSave(n.ExchPub, dst, "neigh", name, "exchpub"); err != nil { + return + } + if err = cfgDirSave(n.SignPub, dst, "neigh", name, "signpub"); err != nil { + return + } + if err = cfgDirSave(n.NoisePub, dst, "neigh", name, "noisepub"); err != nil { + return + } + if err = cfgDirSave(n.Incoming, dst, "neigh", name, "incoming"); err != nil { + return + } + + if len(n.Exec) > 0 { + if err = cfgDirMkdir(dst, "neigh", name, "exec"); err != nil { + return + } + for k, v := range n.Exec { + if err = cfgDirSave( + strings.Join(v, "\n"), + dst, "neigh", name, "exec", k, + ); err != nil { + return + } + } + } + + if n.Freq != nil { + if err = cfgDirMkdir(dst, "neigh", name, "freq"); err != nil { + return + } + if err = cfgDirSave( + n.Freq.Path, + dst, "neigh", name, "freq", "path", + ); err != nil { + return + } + if err = cfgDirSave( + n.Freq.Chunked, + dst, "neigh", name, "freq", "chunked", + ); err != nil { + return + } + if err = cfgDirSave( + n.Freq.MinSize, + dst, "neigh", name, "freq", "minsize", + ); err != nil { + return + } + if err = cfgDirSave( + n.Freq.MaxSize, + dst, "neigh", name, "freq", "maxsize", + ); err != nil { + return + } + } + + if len(n.Via) > 0 { + if err = cfgDirSave( + strings.Join(n.Via, "\n"), + dst, "neigh", name, "via", + ); err != nil { + return + } + } + + if len(n.Addrs) > 0 { + if err = cfgDirMkdir(dst, "neigh", name, "addrs"); err != nil { + return + } + for k, v := range n.Addrs { + if err = cfgDirSave(v, dst, "neigh", name, "addrs", k); err != nil { + return + } + } + } + + if err = cfgDirSave(n.RxRate, dst, "neigh", name, "rxrate"); err != nil { + return + } + if err = cfgDirSave(n.TxRate, dst, "neigh", name, "txrate"); err != nil { + return + } + if err = cfgDirSave(n.OnlineDeadline, dst, "neigh", name, "onlinedeadline"); err != nil { + return + } + if err = cfgDirSave(n.MaxOnlineTime, dst, "neigh", name, "maxonlinetime"); err != nil { + return + } + + for i, call := range n.Calls { + is := strconv.Itoa(i) + if err = cfgDirMkdir(dst, "neigh", name, "calls", is); err != nil { + return + } + if err = cfgDirSave(call.Cron, dst, "neigh", name, "calls", is, "cron"); err != nil { + return + } + if err = cfgDirSave(call.Nice, dst, "neigh", name, "calls", is, "nice"); err != nil { + return + } + if err = cfgDirSave(call.Xx, dst, "neigh", name, "calls", is, "xx"); err != nil { + return + } + if err = cfgDirSave(call.RxRate, dst, "neigh", name, "calls", is, "rxrate"); err != nil { + return + } + if err = cfgDirSave(call.TxRate, dst, "neigh", name, "calls", is, "txrate"); err != nil { + return + } + if err = cfgDirSave(call.Addr, dst, "neigh", name, "calls", is, "addr"); err != nil { + return + } + if err = cfgDirSave(call.OnlineDeadline, dst, "neigh", name, "calls", is, "onlinedeadline"); err != nil { + return + } + if err = cfgDirSave(call.MaxOnlineTime, dst, "neigh", name, "calls", is, "maxonlinetime"); err != nil { + return + } + if call.WhenTxExists { + if err = cfgDirTouch(dst, "neigh", name, "calls", is, "when-tx-exists"); err != nil { + return + } + } + if call.NoCK { + if err = cfgDirTouch(dst, "neigh", name, "calls", is, "nock"); err != nil { + return + } + } + if call.MCDIgnore { + if err = cfgDirTouch(dst, "neigh", name, "calls", is, "mcd-ignore"); err != nil { + return + } + } + if call.AutoToss { + if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss"); err != nil { + return + } + } + if call.AutoTossDoSeen { + if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-doseen"); err != nil { + return + } + } + if call.AutoTossNoFile { + if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-nofile"); err != nil { + return + } + } + if call.AutoTossNoFreq { + if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-nofreq"); err != nil { + return + } + } + if call.AutoTossNoExec { + if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-noexec"); err != nil { + return + } + } + if call.AutoTossNoTrns { + if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-notrns"); err != nil { + return + } + } + if call.AutoTossNoArea { + if err = cfgDirTouch(dst, "neigh", name, "calls", is, "autotoss-noarea"); err != nil { + return + } + } + } + } + + for name, a := range cfg.Areas { + if err = cfgDirMkdir(dst, "areas", name); err != nil { + return + } + if err = cfgDirSave(a.Id, dst, "areas", name, "id"); err != nil { + return + } + if err = cfgDirSave(a.Pub, dst, "areas", name, "pub"); err != nil { + return + } + if err = cfgDirSave(a.Prv, dst, "areas", name, "prv"); err != nil { + return + } + if err = cfgDirSave(a.Incoming, dst, "areas", name, "incoming"); err != nil { + return + } + if a.AllowUnknown { + if err = cfgDirTouch(dst, "areas", name, "allow-unknown"); err != nil { + return + } + } + if len(a.Exec) > 0 { + if err = cfgDirMkdir(dst, "areas", name, "exec"); err != nil { + return + } + for k, v := range a.Exec { + if err = cfgDirSave( + strings.Join(v, "\n"), + dst, "areas", name, "exec", k, + ); err != nil { + return + } + } + } + if len(a.Subs) > 0 { + if err = cfgDirSave( + strings.Join(a.Subs, "\n"), + dst, "areas", name, "subs", + ); err != nil { + return + } + } + } + + return +} + +func cfgDirLoad(src ...string) (v string, exists bool, err error) { + b, err := ioutil.ReadFile(filepath.Join(src...)) + if err != nil { + if os.IsNotExist(err) { + return "", false, nil + } + return "", false, err + } + return strings.TrimSuffix(string(b), "\n"), true, nil +} + +func cfgDirLoadMust(src ...string) (v string, err error) { + s, exists, err := cfgDirLoad(src...) + if err != nil { + return "", err + } + if !exists { + return "", fmt.Errorf("required \"%s\" does not exist", src[len(src)-1]) + } + return s, nil +} + +func cfgDirLoadOpt(src ...string) (v *string, err error) { + s, exists, err := cfgDirLoad(src...) + if err != nil { + return nil, err + } + if !exists { + return nil, nil + } + return &s, nil +} + +func cfgDirLoadIntOpt(src ...string) (i64 *int64, err error) { + s, err := cfgDirLoadOpt(src...) + if err != nil { + return nil, err + } + if s == nil { + return nil, nil + } + i, err := strconv.ParseInt(*s, 10, 64) + if err != nil { + return nil, err + } + return &i, nil +} + +func cfgDirExists(src ...string) bool { + if _, err := os.Stat(filepath.Join(src...)); err == nil { + return true + } + return false +} + +func cfgDirReadFromTo(src ...string) (*FromToJSON, error) { + fromTo := FromToJSON{} + + var err error + fromTo.From, err = cfgDirLoadMust(append(src, "from")...) + if err != nil { + return nil, err + } + + fromTo.To, err = cfgDirLoadMust(append(src, "to")...) + if err != nil { + return nil, err + } + + return &fromTo, nil +} + +func DirToCfg(src string) (*CfgJSON, error) { + cfg := CfgJSON{} + var err error + + cfg.Spool, err = cfgDirLoadMust(src, "spool") + if err != nil { + return nil, err + } + cfg.Log, err = cfgDirLoadMust(src, "log") + if err != nil { + return nil, err + } + + if cfg.Umask, err = cfgDirLoadOpt(src, "umask"); err != nil { + return nil, err + } + cfg.OmitPrgrs = cfgDirExists(src, "noprogress") + cfg.NoHdr = cfgDirExists(src, "nohdr") + + sp, err := cfgDirLoadOpt(src, "mcd-listen") + if err != nil { + return nil, err + } + if sp != nil { + cfg.MCDRxIfis = strings.Split(*sp, "\n") + } + + fis, err := ioutil.ReadDir(filepath.Join(src, "mcd-send")) + if err != nil && !os.IsNotExist(err) { + return nil, err + } + if len(fis) > 0 { + cfg.MCDTxIfis = make(map[string]int, len(fis)) + } + for _, fi := range fis { + n := fi.Name() + if n[0] == '.' { + continue + } + b, err := ioutil.ReadFile(filepath.Join(src, "mcd-send", fi.Name())) + if err != nil { + return nil, err + } + i, err := strconv.Atoi(strings.TrimSuffix(string(b), "\n")) + if err != nil { + return nil, err + } + cfg.MCDTxIfis[n] = i + } + + notify := NotifyJSON{Exec: make(map[string]*FromToJSON)} + if cfgDirExists(src, "notify", "file") { + if notify.File, err = cfgDirReadFromTo(src, "notify", "file"); err != nil { + return nil, err + } + } + if cfgDirExists(src, "notify", "freq") { + if notify.Freq, err = cfgDirReadFromTo(src, "notify", "freq"); err != nil { + return nil, err + } + } + fis, err = ioutil.ReadDir(filepath.Join(src, "notify", "exec")) + if err != nil && !os.IsNotExist(err) { + return nil, err + } + for _, fi := range fis { + n := fi.Name() + if n[0] == '.' || !fi.IsDir() { + continue + } + if notify.Exec[fi.Name()], err = cfgDirReadFromTo(src, "notify", "exec", n); err != nil { + return nil, err + } + } + if notify.File != nil || notify.Freq != nil || len(notify.Exec) > 0 { + cfg.Notify = ¬ify + } + + self := NodeOurJSON{} + if self.Id, err = cfgDirLoadMust(src, "self", "id"); err != nil { + return nil, err + } + if self.ExchPub, err = cfgDirLoadMust(src, "self", "exchpub"); err != nil { + return nil, err + } + if self.ExchPrv, err = cfgDirLoadMust(src, "self", "exchprv"); err != nil { + return nil, err + } + if self.SignPub, err = cfgDirLoadMust(src, "self", "signpub"); err != nil { + return nil, err + } + if self.SignPrv, err = cfgDirLoadMust(src, "self", "signprv"); err != nil { + return nil, err + } + if self.NoisePub, err = cfgDirLoadMust(src, "self", "noisepub"); err != nil { + return nil, err + } + if self.NoisePrv, err = cfgDirLoadMust(src, "self", "noiseprv"); err != nil { + return nil, err + } + cfg.Self = &self + + cfg.Neigh = make(map[string]NodeJSON) + fis, err = ioutil.ReadDir(filepath.Join(src, "neigh")) + if err != nil && !os.IsNotExist(err) { + return nil, err + } + for _, fi := range fis { + n := fi.Name() + if n[0] == '.' { + continue + } + node := NodeJSON{} + if node.Id, err = cfgDirLoadMust(src, "neigh", n, "id"); err != nil { + return nil, err + } + if node.ExchPub, err = cfgDirLoadMust(src, "neigh", n, "exchpub"); err != nil { + return nil, err + } + if node.SignPub, err = cfgDirLoadMust(src, "neigh", n, "signpub"); err != nil { + return nil, err + } + if node.NoisePub, err = cfgDirLoadOpt(src, "neigh", n, "noisepub"); err != nil { + return nil, err + } + if node.Incoming, err = cfgDirLoadOpt(src, "neigh", n, "incoming"); err != nil { + return nil, err + } + + node.Exec = make(map[string][]string) + fis2, err := ioutil.ReadDir(filepath.Join(src, "neigh", n, "exec")) + if err != nil && !os.IsNotExist(err) { + return nil, err + } + for _, fi2 := range fis2 { + n2 := fi2.Name() + if n2[0] == '.' { + continue + } + s, err := cfgDirLoadMust(src, "neigh", n, "exec", n2) + if err != nil { + return nil, err + } + node.Exec[n2] = strings.Split(s, "\n") + } + + if cfgDirExists(src, "neigh", n, "freq") { + node.Freq = &NodeFreqJSON{} + if node.Freq.Path, err = cfgDirLoadOpt(src, "neigh", n, "freq", "path"); err != nil { + return nil, err + } + + i64, err := cfgDirLoadIntOpt(src, "neigh", n, "freq", "chunked") + if err != nil { + return nil, err + } + if i64 != nil { + i := uint64(*i64) + node.Freq.Chunked = &i + } + + i64, err = cfgDirLoadIntOpt(src, "neigh", n, "freq", "minsize") + if err != nil { + return nil, err + } + if i64 != nil { + i := uint64(*i64) + node.Freq.MinSize = &i + } + + i64, err = cfgDirLoadIntOpt(src, "neigh", n, "freq", "maxsize") + if err != nil { + return nil, err + } + if i64 != nil { + i := uint64(*i64) + node.Freq.MaxSize = &i + } + } + + via, err := cfgDirLoadOpt(src, "neigh", n, "via") + if err != nil { + return nil, err + } + if via != nil { + node.Via = strings.Split(*via, "\n") + } + + node.Addrs = make(map[string]string) + fis2, err = ioutil.ReadDir(filepath.Join(src, "neigh", n, "addrs")) + if err != nil && !os.IsNotExist(err) { + return nil, err + } + for _, fi2 := range fis2 { + n2 := fi2.Name() + if n2[0] == '.' { + continue + } + if node.Addrs[n2], err = cfgDirLoadMust(src, "neigh", n, "addrs", n2); err != nil { + return nil, err + } + } + + i64, err := cfgDirLoadIntOpt(src, "neigh", n, "rxrate") + if err != nil { + return nil, err + } + if i64 != nil { + i := int(*i64) + node.RxRate = &i + } + + i64, err = cfgDirLoadIntOpt(src, "neigh", n, "txrate") + if err != nil { + return nil, err + } + if i64 != nil { + i := int(*i64) + node.TxRate = &i + } + + i64, err = cfgDirLoadIntOpt(src, "neigh", n, "onlinedeadline") + if err != nil { + return nil, err + } + if i64 != nil { + i := uint(*i64) + node.OnlineDeadline = &i + } + + i64, err = cfgDirLoadIntOpt(src, "neigh", n, "maxonlinetime") + if err != nil { + return nil, err + } + if i64 != nil { + i := uint(*i64) + node.MaxOnlineTime = &i + } + + fis2, err = ioutil.ReadDir(filepath.Join(src, "neigh", n, "calls")) + if err != nil && !os.IsNotExist(err) { + return nil, err + } + callsIdx := make([]int, 0, len(fis2)) + for _, fi2 := range fis2 { + n2 := fi2.Name() + if !fi2.IsDir() { + continue + } + i, err := strconv.Atoi(n2) + if err != nil { + continue + } + callsIdx = append(callsIdx, i) + } + sort.Ints(callsIdx) + for _, i := range callsIdx { + call := CallJSON{} + is := strconv.Itoa(i) + if call.Cron, err = cfgDirLoadMust( + src, "neigh", n, "calls", is, "cron", + ); err != nil { + return nil, err + } + if call.Nice, err = cfgDirLoadOpt( + src, "neigh", n, "calls", is, "nice", + ); err != nil { + return nil, err + } + if call.Xx, err = cfgDirLoadOpt( + src, "neigh", n, "calls", is, "xx", + ); err != nil { + return nil, err + } + + i64, err = cfgDirLoadIntOpt(src, "neigh", n, "calls", is, "rxrate") + if err != nil { + return nil, err + } + if i64 != nil { + i := int(*i64) + call.RxRate = &i + } + + i64, err = cfgDirLoadIntOpt(src, "neigh", n, "calls", is, "txrate") + if err != nil { + return nil, err + } + if i64 != nil { + i := int(*i64) + call.TxRate = &i + } + + if call.Addr, err = cfgDirLoadOpt( + src, "neigh", n, "calls", is, "addr", + ); err != nil { + return nil, err + } + + i64, err = cfgDirLoadIntOpt(src, "neigh", n, "calls", is, "onlinedeadline") + if err != nil { + return nil, err + } + if i64 != nil { + i := uint(*i64) + call.OnlineDeadline = &i + } + + i64, err = cfgDirLoadIntOpt(src, "neigh", n, "calls", is, "maxonlinetime") + if err != nil { + return nil, err + } + if i64 != nil { + i := uint(*i64) + call.MaxOnlineTime = &i + } + + if cfgDirExists(src, "neigh", n, "calls", is, "when-tx-exists") { + call.WhenTxExists = true + } + if cfgDirExists(src, "neigh", n, "calls", is, "nock") { + call.NoCK = true + } + if cfgDirExists(src, "neigh", n, "calls", is, "mcd-ignore") { + call.MCDIgnore = true + } + if cfgDirExists(src, "neigh", n, "calls", is, "autotoss") { + call.AutoToss = true + } + if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-doseen") { + call.AutoTossDoSeen = true + } + if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-nofile") { + call.AutoTossNoFile = true + } + if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-nofreq") { + call.AutoTossNoFreq = true + } + if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-noexec") { + call.AutoTossNoExec = true + } + if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-notrns") { + call.AutoTossNoTrns = true + } + if cfgDirExists(src, "neigh", n, "calls", is, "autotoss-noarea") { + call.AutoTossNoArea = true + } + node.Calls = append(node.Calls, call) + } + cfg.Neigh[n] = node + } + + cfg.Areas = make(map[string]AreaJSON) + fis, err = ioutil.ReadDir(filepath.Join(src, "areas")) + if err != nil && !os.IsNotExist(err) { + return nil, err + } + for _, fi := range fis { + n := fi.Name() + if n[0] == '.' { + continue + } + area := AreaJSON{} + if area.Id, err = cfgDirLoadMust(src, "areas", n, "id"); err != nil { + return nil, err + } + if area.Pub, err = cfgDirLoadOpt(src, "areas", n, "pub"); err != nil { + return nil, err + } + if area.Prv, err = cfgDirLoadOpt(src, "areas", n, "prv"); err != nil { + return nil, err + } + + subs, err := cfgDirLoadOpt(src, "areas", n, "subs") + if err != nil { + return nil, err + } + if subs != nil { + area.Subs = strings.Split(*subs, "\n") + } + + area.Exec = make(map[string][]string) + fis2, err := ioutil.ReadDir(filepath.Join(src, "areas", n, "exec")) + if err != nil && !os.IsNotExist(err) { + return nil, err + } + for _, fi2 := range fis2 { + n2 := fi2.Name() + if n2[0] == '.' { + continue + } + s, err := cfgDirLoadMust(src, "areas", n, "exec", n2) + if err != nil { + return nil, err + } + area.Exec[n2] = strings.Split(s, "\n") + } + + if area.Incoming, err = cfgDirLoadOpt(src, "areas", n, "incoming"); err != nil { + return nil, err + } + + if cfgDirExists(src, "areas", n, "allow-unknown") { + area.AllowUnknown = true + } + cfg.Areas[n] = area + } + + return &cfg, nil +} diff --git a/src/cmd/nncp-cfgdir/main.go b/src/cmd/nncp-cfgdir/main.go new file mode 100644 index 0000000..cbc8f8f --- /dev/null +++ b/src/cmd/nncp-cfgdir/main.go @@ -0,0 +1,99 @@ +/* +NNCP -- Node to Node copy, utilities for store-and-forward data exchange +Copyright (C) 2016-2021 Sergey Matveev + +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 +the Free Software Foundation, version 3 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +// Convert NNCP Hjson configuration file to the directory layout. +package main + +import ( + "flag" + "fmt" + "io/ioutil" + "log" + "os" + + "github.com/hjson/hjson-go" + "go.cypherpunks.ru/nncp/v7" +) + +func usage() { + fmt.Fprintf(os.Stderr, nncp.UsageHeader()) + fmt.Fprintf(os.Stderr, "nncp-cfgdir -- Convert configuration file to the directory layout.\n\n") + fmt.Fprintf(os.Stderr, "Usage: %s [options] [-cfg ...] -dump /path/to/dir\n", os.Args[0]) + fmt.Fprintf(os.Stderr, " %s [options] -load /path/to/dir > cfg.hjson\nOptions:\n", os.Args[0]) + flag.PrintDefaults() +} + +func main() { + var ( + doDump = flag.Bool("dump", false, "Dump configuration file to the directory") + doLoad = flag.Bool("load", false, "Load directory to create configuration file") + cfgPath = flag.String("cfg", nncp.DefaultCfgPath, "Path to configuration file") + version = flag.Bool("version", false, "Print version information") + warranty = flag.Bool("warranty", false, "Print warranty information") + ) + log.SetFlags(log.Lshortfile) + flag.Usage = usage + flag.Parse() + if *warranty { + fmt.Println(nncp.Warranty) + return + } + if *version { + fmt.Println(nncp.VersionGet()) + return + } + + if (!*doDump && !*doLoad) || flag.NArg() != 1 { + usage() + os.Exit(1) + } + + if *doDump { + cfgRaw, err := ioutil.ReadFile(*cfgPath) + if err != nil { + log.Fatalln(err) + } + cfg, err := nncp.CfgParse(cfgRaw) + if err != nil { + log.Fatalln(err) + } + if err = nncp.CfgToDir(flag.Arg(0), cfg); err != nil { + log.Fatalln(err) + } + } + if *doLoad { + cfg, err := nncp.DirToCfg(flag.Arg(0)) + if err != nil { + log.Fatalln(err) + } + if _, err = nncp.Cfg2Ctx(cfg); err != nil { + log.Fatalln(err) + } + marshaled, err := hjson.MarshalWithOptions(cfg, hjson.EncoderOptions{ + Eol: "\n", + BracesSameLine: true, + QuoteAlways: false, + IndentBy: " ", + AllowMinusZero: false, + }) + if err != nil { + log.Fatalln(err) + } + os.Stdout.Write(marshaled) + os.Stdout.WriteString("\n") + } +} diff --git a/src/ctx.go b/src/ctx.go index cb5c428..4d762d2 100644 --- a/src/ctx.go +++ b/src/ctx.go @@ -88,9 +88,7 @@ func (ctx *Ctx) ensureRxDir(nodeId *NodeId) error { } func CtxFromCmdline( - cfgPath, - spoolPath, - logPath string, + cfgPath, spoolPath, logPath string, quiet, showPrgrs, omitPrgrs, debug bool, ) (*Ctx, error) { env := os.Getenv(CfgPathEnv) @@ -100,11 +98,27 @@ func CtxFromCmdline( if showPrgrs && omitPrgrs { return nil, errors.New("simultaneous -progress and -noprogress") } - cfgRaw, err := ioutil.ReadFile(cfgPath) + fi, err := os.Stat(cfgPath) if err != nil { return nil, err } - ctx, err := CfgParse(cfgRaw) + var cfg *CfgJSON + if fi.IsDir() { + cfg, err = DirToCfg(cfgPath) + if err != nil { + return nil, err + } + } else { + cfgRaw, err := ioutil.ReadFile(cfgPath) + if err != nil { + return nil, err + } + cfg, err = CfgParse(cfgRaw) + if err != nil { + return nil, err + } + } + ctx, err := Cfg2Ctx(cfg) if err != nil { return nil, err } diff --git a/src/nncp.go b/src/nncp.go index 2c087b3..3bfcb3a 100644 --- a/src/nncp.go +++ b/src/nncp.go @@ -40,7 +40,7 @@ along with this program. If not, see .` const Base32Encoded32Len = 52 var ( - Version string = "7.2.1" + Version string = "7.3.0" Base32Codec *base32.Encoding = base32.StdEncoding.WithPadding(base32.NoPadding) )