src/go/plugin/go.d/modules/fail2ban/collect.go
// SPDX-License-Identifier: GPL-3.0-or-later
package fail2ban
import (
"bufio"
"bytes"
"errors"
"fmt"
"strconv"
"strings"
"time"
)
func (f *Fail2Ban) collect() (map[string]int64, error) {
now := time.Now()
if now.Sub(f.lastDiscoverTime) > f.discoverEvery || f.forceDiscover {
jails, err := f.discoverJails()
if err != nil {
return nil, err
}
f.jails = jails
f.lastDiscoverTime = now
f.forceDiscover = false
}
mx := make(map[string]int64)
if err := f.collectJails(mx); err != nil {
return nil, err
}
return mx, nil
}
func (f *Fail2Ban) discoverJails() ([]string, error) {
bs, err := f.exec.status()
if err != nil {
return nil, err
}
jails, err := parseFail2banStatus(bs)
if err != nil {
return nil, err
}
if len(jails) == 0 {
return nil, errors.New("no jails found")
}
f.Debugf("discovered %d jails: %v", len(jails), jails)
return jails, nil
}
func (f *Fail2Ban) collectJails(mx map[string]int64) error {
seen := make(map[string]bool)
for _, jail := range f.jails {
f.Debugf("querying status for jail '%s'", jail)
bs, err := f.exec.jailStatus(jail)
if err != nil {
if errors.Is(err, errJailNotExist) {
f.forceDiscover = true
continue
}
return err
}
failed, banned, err := parseFail2banJailStatus(bs)
if err != nil {
return err
}
if !f.seenJails[jail] {
f.seenJails[jail] = true
f.addJailCharts(jail)
}
seen[jail] = true
px := fmt.Sprintf("jail_%s_", jail)
mx[px+"currently_failed"] = failed
mx[px+"currently_banned"] = banned
}
for jail := range f.seenJails {
if !seen[jail] {
delete(f.seenJails, jail)
f.removeJailCharts(jail)
}
}
return nil
}
func parseFail2banJailStatus(jailStatus []byte) (failed, banned int64, err error) {
const (
failedSub = "Currently failed:"
bannedSub = "Currently banned:"
)
var failedFound, bannedFound bool
sc := bufio.NewScanner(bytes.NewReader(jailStatus))
for sc.Scan() && !(failedFound && bannedFound) {
text := strings.TrimSpace(sc.Text())
if text == "" {
continue
}
if !failedFound {
if i := strings.Index(text, failedSub); i != -1 {
failedFound = true
s := strings.TrimSpace(text[i+len(failedSub):])
if failed, err = strconv.ParseInt(s, 10, 64); err != nil {
return 0, 0, fmt.Errorf("failed to parse currently failed value (%s): %v", s, err)
}
}
}
if !bannedFound {
if i := strings.Index(text, bannedSub); i != -1 {
bannedFound = true
s := strings.TrimSpace(text[i+len(bannedSub):])
if banned, err = strconv.ParseInt(s, 10, 64); err != nil {
return 0, 0, fmt.Errorf("failed to parse currently banned value (%s): %v", s, err)
}
}
}
}
if !failedFound || !bannedFound {
return 0, 0, errors.New("failed to find failed and banned values")
}
return failed, banned, nil
}
func parseFail2banStatus(status []byte) ([]string, error) {
const sub = "Jail list:"
var jails []string
sc := bufio.NewScanner(bytes.NewReader(status))
for sc.Scan() {
text := strings.TrimSpace(sc.Text())
if i := strings.Index(text, sub); i != -1 {
s := strings.ReplaceAll(text[i+len(sub):], ",", "")
jails = strings.Fields(s)
break
}
}
if len(jails) == 0 {
return nil, errors.New("failed to find jails")
}
return jails, nil
}