package validate import ( "strings" "jarl/internal/model" ) // checkBlocking checks blocklists and block/allow rules (docs/blocking.md). func (c *checker) checkBlocking() { viewNames := func(ids []int64) string { var names []string for _, id := range ids { if v := c.s.Views[id]; v != nil { names = append(names, v.Name) } } return strings.Join(names, ", ") } used := map[int64]bool{} // views (0: the server) with blocking // what describes where something applies, and warns when it can't // have any effect. effective := func(objType string, id int64, label string, ids []int64) []int64 { for _, vid := range ids { if c.s.Views[vid] == nil { c.errf("blocking_view_missing", objType, id, "%s names view %d, which doesn't exist", label, vid) } } views := c.s.BlockingViews(ids) if len(views) == 0 { c.warnf("blocking_no_view", objType, id, "%s has no effect: no view offers recursion, and blocking only changes answers to recursive queries", label) } var idle []int64 for _, vid := range views { if !c.s.RecursionOn(vid) { idle = append(idle, vid) } } if len(idle) > 0 { c.warnf("blocking_no_recursion", objType, id, "%s has no effect in view %s: it doesn't offer recursion, and blocking only changes answers to recursive queries", label, viewNames(idle)) } for _, vid := range views { used[vid] = true } return views } for _, b := range c.s.SortedBlocklists() { if !b.Disabled { effective("blocklist", b.ID, "blocklist "+b.Name, b.ViewIDs) } } // One rule per name and view: two would compete for the same entries // of the policy zone. seen := map[string]*model.BlockRule{} // view|name -> first rule for _, r := range c.s.SortedBlockRules() { label := "the rule for " + r.Name for _, vid := range effective("blockrule", r.ID, label, r.ViewIDs) { key := c.blockingScope(vid) + "|" + r.Name if first := seen[key]; first != nil && first.ID != r.ID { where := "" if v := c.s.Views[vid]; v != nil { where = " in view " + v.Name } c.errf("blocking_rule_duplicate", "blockrule", r.ID, "there are two rules for %s%s (%s and %s); keep one", r.Name, where, first.Action, r.Action) continue } seen[key] = r } } // Jarl writes the response-policy statement where blocking is used; // named allows one per view (or for the server). for vid := range used { if o := c.s.FindOption(model.ScopeView, vid, "response-policy"); vid != 0 && o != nil { p := c.errf("blocking_response_policy", "option", o.ID, "view %s has a response-policy option, but Jarl's blocking writes that statement there; remove the option (or the blocking that applies to this view)", c.s.Views[vid].Name) p.View = c.s.Views[vid].Name } } if o := c.s.FindOption(model.ScopeOptions, 0, "response-policy"); o != nil && len(used) > 0 { c.errf("blocking_response_policy", "option", o.ID, "the server has a response-policy option, which Jarl's blocking replaces wherever it is used; remove the option, or set up your policy zones with Jarl's blocking instead") } for _, z := range c.s.Zones { if n := strings.TrimSuffix(z.Name, "."); n == "jarl.invalid" || strings.HasSuffix(n, ".jarl.invalid") { c.zoneProblem(z, Error, "zone_name_reserved", "zone names under jarl.invalid are reserved for Jarl's blocking") } } } func (c *checker) blockingScope(viewID int64) string { if v := c.s.Views[viewID]; v != nil { return v.Name } return "" }