// 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 }