diff --git a/conf.go b/conf.go index b6bc164..7b1be58 100644 --- a/conf.go +++ b/conf.go @@ -6,39 +6,63 @@ import ( "log" "os" "regexp" + "strings" "time" "gopkg.in/yaml.v3" ) type Conf struct { - Streams map[string]Stream + Patterns map[string]string + Streams map[string]*Stream } type Stream struct { + name string + Cmd []string Filters map[string]*Filter } type Filter struct { + stream *Stream + name string + Regex []string - compiledRegex []regexp.Regexp // Computed after YAML parsing + compiledRegex []regexp.Regexp + patternName string + Retry uint RetryPeriod string `yaml:"retry-period"` - retryDuration time.Duration // Computed after YAML parsing - Actions map[string]*Action + retryDuration time.Duration + + Actions map[string]*Action } type Action struct { - name, filterName, streamName string // Computed after YAML parsing - Cmd []string - After string `yaml:",omitempty"` - afterDuration time.Duration // Computed after YAML parsing + filter *Filter + name string + + Cmd []string + + After string `yaml:",omitempty"` + afterDuration time.Duration } func (c *Conf) setup() { - for streamName, stream := range c.Streams { - for filterName, filter := range stream.Filters { + for patternName, pattern := range c.Patterns { + c.Patterns[patternName] = fmt.Sprintf("(?P<%s>%s)", patternName, pattern) + } + for streamName := range c.Streams { + + stream := c.Streams[streamName] + stream.name = streamName + + for filterName := range stream.Filters { + + filter := stream.Filters[filterName] + filter.stream = stream + filter.name = filterName // Parse Duration retryDuration, err := time.ParseDuration(filter.RetryPeriod) @@ -48,16 +72,35 @@ func (c *Conf) setup() { filter.retryDuration = retryDuration // Compute Regexes + // Look for Patterns inside Regexes for _, regex := range filter.Regex { + for patternName, pattern := range c.Patterns { + if strings.Contains(regex, patternName) { + + switch filter.patternName { + case "": + filter.patternName = patternName + case patternName: + // no op + filter.patternName = patternName + default: + log.Fatalf( + "ERROR Can't mix different patterns (%s, %s) in same filter (%s.%s)\n", + filter.patternName, patternName, streamName, filterName, + ) + } + + regex = strings.Replace(regex, fmt.Sprintf("<%s>", patternName), pattern, 1) + } + } filter.compiledRegex = append(filter.compiledRegex, *regexp.MustCompile(regex)) } - for actionName, action := range filter.Actions { + for actionName := range filter.Actions { - // Give all relevant infos to Actions + action := filter.Actions[actionName] + action.filter = filter action.name = actionName - action.filterName = filterName - action.streamName = streamName // Parse Duration if action.After != "" { @@ -87,7 +130,6 @@ func parseConf(filename string) *Conf { } conf.setup() - fmt.Printf("conf.Streams[0].Filters[0].Actions: %s\n", conf.Streams["tailDown"].Filters["lookForProuts"].Actions) return &conf } diff --git a/main.go b/main.go index b258442..1758f29 100644 --- a/main.go +++ b/main.go @@ -2,11 +2,14 @@ package main import ( "bufio" + "fmt" "log" - "time" "os/exec" + "strings" + "time" ) +// Executes a command and channel-send its stdout func cmdStdout(commandline []string) chan string { lines := make(chan string) @@ -31,45 +34,55 @@ func cmdStdout(commandline []string) chan string { return lines } -func (f *Filter) match(line string) bool { - log.Printf("trying to match line {%s}...\n", line) +// Whether one of the filter's regexes is matched on a line +func (f *Filter) match(line string) string { for _, regex := range f.compiledRegex { - log.Printf("...on %s\n", regex.String()) - if match := regex.FindString(line); match != "" { - log.Printf("match `%v` in line: `%v`\n", regex.String(), line) - return true + + if matches := regex.FindStringSubmatch(line); matches != nil { + + match := matches[regex.SubexpIndex(f.patternName)] + + log.Printf("INFO %s.%s: match `%v`\n", f.stream.name, f.name, match) + return match } } - return false + return "" } -func (f *Filter) launch(line *string) { +func (f *Filter) execActions(match string) { + pattern := fmt.Sprintf("<%s>", f.patternName) for _, a := range f.Actions { - go a.launch(line) + go a.exec(match, pattern) } } -func (a *Action) launch(line *string) { +func (a *Action) exec(match, pattern string) { if a.afterDuration != 0 { time.Sleep(a.afterDuration) } - log.Printf("INFO %s.%s.%s: line {%s} → run %s\n", a.streamName, a.filterName, a.name, *line, a.Cmd) - cmd := exec.Command(a.Cmd[0], a.Cmd[1:]...) + computedCommand := make([]string, 0, len(a.Cmd)) + for _, item := range a.Cmd { + computedCommand = append(computedCommand, strings.ReplaceAll(item, pattern, match)) + } + + log.Printf("INFO %s.%s.%s: run %s\n", a.filter.stream.name, a.filter.name, a.name, computedCommand) + + cmd := exec.Command(computedCommand[0], computedCommand[1:]...) if ret := cmd.Run(); ret != nil { - log.Printf("ERR %s.%s.%s: line {%s} → run %s, code {%s}\n", a.streamName, a.filterName, a.name, *line, a.Cmd, ret) + log.Printf("ERR %s.%s.%s: run %s, code %s\n", a.filter.stream.name, a.filter.name, a.name, computedCommand, ret) } } func (s *Stream) handle() { - log.Printf("streamHandle{%v}: start\n", s.Cmd) + log.Printf("INFO %s: start %s\n", s.name, s.Cmd) lines := cmdStdout(s.Cmd) for line := range lines { for _, filter := range s.Filters { - if filter.match(line) { - filter.launch(&line) + if match := filter.match(line); match != "" { + filter.execActions(match) } } } diff --git a/reaction.yml b/reaction.yml index 8f72d21..742d3b1 100644 --- a/reaction.yml +++ b/reaction.yml @@ -3,21 +3,21 @@ definitions: - &iptablesban iptables -I reaction 1 -s -j block - &iptablesunban iptables -D reaction 1 -s -j block -# regexes: -# ip: '(([0-9]{1,3}\.){3}[0-9]{1,3})|([0-9a-fA-F:]{2,90})' +patterns: + ip: '(([0-9]{1,3}\.){3}[0-9]{1,3})|([0-9a-fA-F:]{2,90})' streams: tailDown: cmd: [ "tail", "-f", "/home/ao/DOWN" ] filters: - lookForProuts: + findIP: regex: - - prout - retry: 1 + - found + # retry: 1 retry-period: 1s actions: damn: - cmd: [ "echo", "DAMN" ] + cmd: [ "echo", "" ] sleepdamn: - cmd: [ "echo", "sleepDAMN" ] - after: 2s + cmd: [ "echo", "sleep", "" ] + after: 1s