From: Sergey Matveev Date: Sat, 23 Nov 2019 12:07:57 +0000 (+0300) Subject: exec notification X-Git-Tag: v5.1.0^2~1 X-Git-Url: http://www.git.cypherpunks.ru/?p=nncp.git;a=commitdiff_plain;h=2c00ac2b448c4c267e25d3bac91c2d7dc1c01aef exec notification --- diff --git a/doc/cfg.texi b/doc/cfg.texi index c16c2b1..46fa5f3 100644 --- a/doc/cfg.texi +++ b/doc/cfg.texi @@ -18,6 +18,16 @@ Example @url{https://hjson.org/, Hjson} configuration file: from: nncp@localhost to: user+freq@example.com } + exec: { + "*.warcer": { + from: nncp@localhost + to: user+warcer@example.com + } + "eve.warcer": { + from: nncp@localhost + to: user+warcer-overriden@example.com + } + } } self: { @@ -90,12 +100,18 @@ override their umask to specified octal mask. Useful for using with @anchor{CfgNotify} @strong{notify} section contains notification settings for successfully -tossed file and freq packets. Corresponding @strong{from} and +tossed file, freq and exec packets. Corresponding @strong{from} and @strong{to} fields will be substituted in notification email message. -@emph{neigh/self/exec/sendmail} will be used as a local mailer. You can -omit either of those two @emph{from}/@emph{to} sections to omit +@code{neigh.self.exec.sendmail} will be used as a local mailer. You can +omit either of those two @code{from}/@code{to} sections to omit corresponding notifications, or the whole section at once. +@code{notify.exec} section is a mapping of exec handles and +corresponding @code{from}/@code{to} sections. Each handle has either +@code{NODE.HANDLE} or @code{*.HANDLE} syntax. You can override +notification options for some node with the first type of name. +Handle command's output will be included in notification messages. + @strong{self} section contains our node's private keypairs. @strong{exch*} and @strong{sign*} are used during @ref{Encrypted, encrypted} packet creation. @strong{noise*} are used during @ref{Sync, @@ -106,7 +122,7 @@ always has @strong{self} neighbour that is copy of our node's public data (public keys). It is useful for copy-paste sharing with your friends. Each section's key is a human-readable name of the neighbour. -Except for @emph{id}, @emph{exchpub} and @emph{signpub} each neighbour +Except for @code{id}, @code{exchpub} and @code{signpub} each neighbour node has the following fields: @table @strong @@ -158,9 +174,9 @@ transmission. @anchor{CfgVia} @item via An array of node identifiers that will be used as a relay to that node. -For example @verb{|[foo,bar]|} means that packet can reach current node -by transitioning through @emph{foo} and then @emph{bar} nodes. May be -omitted if direct connection exists and no relaying is required. +For example @verb{|["foo","bar"]|} means that packet can reach current +node by transitioning through @code{foo} and then @code{bar} nodes. May +be omitted if direct connection exists and no relaying is required. @anchor{CfgAddrs} @item addrs diff --git a/doc/cmds.texi b/doc/cmds.texi index f6adca7..1910651 100644 --- a/doc/cmds.texi +++ b/doc/cmds.texi @@ -287,6 +287,9 @@ echo My message | /usr/sbin/sendmail -t root@localhost @end verbatim +If @ref{CfgNotify, notification} is enabled on the remote side for exec +handles, then it will sent simple letter after successful command +execution with its output in message body. @node nncp-file @section nncp-file diff --git a/doc/news.ru.texi b/doc/news.ru.texi index 3290355..3226116 100644 --- a/doc/news.ru.texi +++ b/doc/news.ru.texi @@ -22,6 +22,10 @@ Добавлена @option{freq.maxsize} опция конфигурационного файл, запрещающая ответ на файловый запрос больше заданного размера. +@item +Возможность оповещения об успешно выполненных командах (exec) через +@option{notify.exec} опцию конфигурационного файла. + @end itemize @node Релиз 5.0.0 diff --git a/doc/news.texi b/doc/news.texi index ec52ba2..bc7a6fc 100644 --- a/doc/news.texi +++ b/doc/news.texi @@ -23,6 +23,10 @@ file options replaced with the structure: Added @option{freq.maxsize} configuration file option, forbidding of freq sending larger than specified size. +@item +Ability to notify about successfully executed commands (exec) with +@option{notify.exec} configuration file option. + @end itemize @node Release 5.0.0 diff --git a/src/cfg.go b/src/cfg.go index 46e6bba..edc132a 100644 --- a/src/cfg.go +++ b/src/cfg.go @@ -98,8 +98,9 @@ type FromToJSON struct { } type NotifyJSON struct { - File *FromToJSON `json:"file,omitempty"` - Freq *FromToJSON `json:"freq,omitempty"` + File *FromToJSON `json:"file,omitempty"` + Freq *FromToJSON `json:"freq,omitempty"` + Exec map[string]*FromToJSON `json:"exec,omitempty"` } type CfgJSON struct { @@ -437,6 +438,9 @@ func CfgParse(data []byte) (*Ctx, error) { 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, neighJSON := range cfgJSON.Neigh { diff --git a/src/cmd/nncp-cfgnew/main.go b/src/cmd/nncp-cfgnew/main.go index c7bb14e..e2eac99 100644 --- a/src/cmd/nncp-cfgnew/main.go +++ b/src/cmd/nncp-cfgnew/main.go @@ -113,6 +113,19 @@ func main() { # from: nncp@localhost # to: user+freq@example.com # } + # # Send some exec commands execution notifications + # exec: { + # # bob neighbour's "somehandle" notification + # bob.somehandle: { + # from: nncp+bob@localhost + # to: user+somehandle@example.com + # } + # # Any neighboor's "anotherhandle" + # *.anotherhandle: { + # from: nncp@localhost + # to: user+anotherhandle@example.com + # } + # } # } self: { @@ -148,50 +161,55 @@ func main() { # noisepub: UBM5K...VI42A # # # He is allowed to send email - # exec: {sendmail: ["/usr/sbin/sendmail"]} + # # exec: {sendmail: ["%s"]} # # # Allow incoming files saving in that directory - # incoming: "/home/alice/incoming" + # # incoming: "/home/alice/incoming" # # # Transitional nodes path - # via: ["bob", "eve"] + # # via: ["bob", "eve"] # # # Inactivity timeout when session with remote peer should be terminated - # onlinedeadline: 1800 + # # onlinedeadline: 1800 # # # Maximal online session lifetime - # maxonlinetime: 3600 + # # maxonlinetime: 3600 # - # # Allow freqing from that directory - # freq: "/home/bob/pub" - # # Send freqed files with chunks - # freqchunked: 1024 - # # Send freqed files with minumal chunk size - # freqminsize: 2048 + # # If neither freq section, nor freq.path exist, then no freqing allowed + # # freq: { + # # # Allow freqing from that directory + # # path: "/home/bob/pub" + # # # Send freqed files with chunks + # # # chunked: 1024 + # # # Send freqed files with minumal chunk size + # # # minsize: 2048 + # # # Maximal allowable freqing file size + # # # maxsize: 4096 + # # } # # # Set maximal packets per second receive and transmit rates - # rxrate: 10 - # txrate: 20 + # # rxrate: 10 + # # txrate: 20 # # # Address aliases - # addrs: { - # lan: "[fe80::1234%%igb0]:5400" - # internet: alice.com:3389 - # } + # # addrs: { + # # lan: "[fe80::1234%%igb0]:5400" + # # internet: alice.com:3389 + # # } # # # Calls configuration - # calls: [ - # { - # cron: "*/2 * * * *" - # onlinedeadline: 1800 - # maxonlinetime: 1750 - # nice: PRIORITY+10 - # rxrate: 10 - # txrate: 20 - # xx: rx - # addr: lan - # }, - # ] + # # calls: [ + # # { + # # cron: "*/2 * * * *" + # # onlinedeadline: 1800 + # # maxonlinetime: 1750 + # # nice: PRIORITY+10 + # # rxrate: 10 + # # txrate: 20 + # # xx: rx + # # addr: lan + # # }, + # # ] # } } }`, @@ -209,6 +227,7 @@ func main() { nncp.ToBase32(nodeOur.SignPub[:]), nncp.ToBase32(nodeOur.NoisePub[:]), nncp.DefaultSendmailPath, + nncp.DefaultSendmailPath, ) } if _, err = nncp.CfgParse([]byte(cfgRaw)); err != nil { diff --git a/src/ctx.go b/src/ctx.go index 1cee1a2..8a1146e 100644 --- a/src/ctx.go +++ b/src/ctx.go @@ -42,6 +42,7 @@ type Ctx struct { Debug bool NotifyFile *FromToJSON NotifyFreq *FromToJSON + NotifyExec map[string]*FromToJSON } func (ctx *Ctx) FindNode(id string) (*Node, error) { diff --git a/src/toss.go b/src/toss.go index 19e97e5..586f378 100644 --- a/src/toss.go +++ b/src/toss.go @@ -20,6 +20,7 @@ package nncp import ( "bufio" "bytes" + "encoding/base64" "fmt" "io" "io/ioutil" @@ -43,13 +44,22 @@ const ( SeenSuffix = ".seen" ) -func newNotification(fromTo *FromToJSON, subject string) io.Reader { - return strings.NewReader(fmt.Sprintf( - "From: %s\nTo: %s\nSubject: %s\n", - fromTo.From, - fromTo.To, - mime.BEncoding.Encode("UTF-8", subject), - )) +func newNotification(fromTo *FromToJSON, subject string, body []byte) io.Reader { + lines := []string{ + "From: " + fromTo.From, + "To: " + fromTo.To, + "Subject: " + mime.BEncoding.Encode("UTF-8", subject), + } + if len(body) > 0 { + lines = append(lines, []string{ + "MIME-Version: 1.0", + "Content-Type: text/plain; charset=utf-8", + "Content-Transfer-Encoding: base64", + "", + base64.StdEncoding.EncodeToString(body), + }...) + } + return strings.NewReader(strings.Join(lines, "\n")) } func (ctx *Ctx) Toss( @@ -58,6 +68,7 @@ func (ctx *Ctx) Toss( dryRun, doSeen, noFile, noFreq, noExec, noTrns bool, ) bool { isBad := false + sendmail := ctx.Neigh[*ctx.SelfId].Exec["sendmail"] decompressor, err := zstd.NewReader(nil) if err != nil { panic(err) @@ -118,9 +129,10 @@ func (ctx *Ctx) Toss( for _, p := range path[1:] { args = append(args, string(p)) } + argsStr := strings.Join(append([]string{handle}, args...), " ") sds := SdsAdd(sds, SDS{ "type": "exec", - "dst": strings.Join(append([]string{handle}, args...), " "), + "dst": argsStr, }) sender := ctx.Neigh[*job.PktEnc.Sender] cmdline, exists := sender.Exec[handle] @@ -144,11 +156,28 @@ func (ctx *Ctx) Toss( "NNCP_NICE="+strconv.Itoa(int(pkt.Nice)), ) cmd.Stdin = decompressor - if err = cmd.Run(); err != nil { + output, err := cmd.Output() + if err != nil { ctx.LogE("rx", SdsAdd(sds, SDS{"err": err}), "handle") isBad = true goto Closing } + if len(sendmail) > 0 && ctx.NotifyExec != nil { + notify, exists := ctx.NotifyExec[sender.Name+"."+handle] + if !exists { + notify, exists = ctx.NotifyExec["*."+handle] + } + if exists { + cmd := exec.Command( + sendmail[0], + append(sendmail[1:len(sendmail)], notify.To)..., + ) + cmd.Stdin = newNotification(notify, fmt.Sprintf( + "Exec from %s: %s", sender.Name, argsStr, + ), output) + cmd.Run() + } + } } ctx.LogI("rx", sds, "") if !dryRun { @@ -245,8 +274,7 @@ func (ctx *Ctx) Toss( ctx.LogE("rx", SdsAdd(sds, SDS{"err": err}), "remove") isBad = true } - sendmail, exists := ctx.Neigh[*ctx.SelfId].Exec["sendmail"] - if exists && len(sendmail) > 0 && ctx.NotifyFile != nil { + if len(sendmail) > 0 && ctx.NotifyFile != nil { cmd := exec.Command( sendmail[0], append(sendmail[1:len(sendmail)], ctx.NotifyFile.To)..., @@ -256,7 +284,7 @@ func (ctx *Ctx) Toss( ctx.Neigh[*job.PktEnc.Sender].Name, dst, humanize.IBytes(uint64(pktSize)), - )) + ), nil) cmd.Run() } } @@ -313,17 +341,14 @@ func (ctx *Ctx) Toss( ctx.LogE("rx", SdsAdd(sds, SDS{"err": err}), "remove") isBad = true } - sendmail, exists := ctx.Neigh[*ctx.SelfId].Exec["sendmail"] - if exists && len(sendmail) > 0 && ctx.NotifyFreq != nil { + if len(sendmail) > 0 && ctx.NotifyFreq != nil { cmd := exec.Command( sendmail[0], append(sendmail[1:len(sendmail)], ctx.NotifyFreq.To)..., ) cmd.Stdin = newNotification(ctx.NotifyFreq, fmt.Sprintf( - "Freq from %s: %s", - ctx.Neigh[*job.PktEnc.Sender].Name, - src, - )) + "Freq from %s: %s", sender.Name, src, + ), nil) cmd.Run() } }