]> git.ipfire.org Git - thirdparty/rspamd.git/commitdiff
[Fix] fuzzy_check: accept SRV-only rules at config-load
authorVsevolod Stakhov <vsevolod@rspamd.com>
Sat, 23 May 2026 13:15:02 +0000 (14:15 +0100)
committerVsevolod Stakhov <vsevolod@rspamd.com>
Sat, 23 May 2026 13:15:02 +0000 (14:15 +0100)
After switching the default rspamd.com rule to service=fuzzy+rspamd.com,
'rspamadm configtest' logged 'no servers defined for fuzzy rule with
name: rspamd.com' and the rule was rejected. The check at
fuzzy_check.c:2183 uses rspamd_upstreams_count(), which deliberately
excludes SRV parent placeholders because callers like the upstream-
weight setter in dns.c and the lua_createtable size hints elsewhere
want the dispatchable cluster size, not the configured-entry count.

At config-load the SRV parent is the only thing in the list (members
are populated asynchronously after DNS resolution), so the existing
count returned 0 and the rule was rejected.

Add rspamd_upstreams_count_total() that includes SRV parents and use
it for the "is anything configured at all" gate. The four other
callers of rspamd_upstreams_count (dns weight, three Lua table size
hints) keep the existing dispatchable-only semantics, which is what
they want.

src/libutil/upstream.c
src/libutil/upstream.h
src/plugins/fuzzy_check.c

index 8edd1506e1d7e329c3037f035e5eefaf85f6e256..3f470bf8b7741086580addf100c761438d9baa05 100644 (file)
@@ -1911,6 +1911,21 @@ gsize rspamd_upstreams_count(struct upstream_list *ups)
        return n;
 }
 
+gsize rspamd_upstreams_count_total(struct upstream_list *ups)
+{
+       if (ups == NULL) {
+               return 0;
+       }
+
+       /*
+        * Includes SRV parent placeholders. Used by "is anything configured"
+        * gates that run at config-load time, before async SRV resolution has
+        * populated members; a parent-only list must read non-empty here or
+        * the rule would be rejected on every fresh start.
+        */
+       return ups->ups->len;
+}
+
 gsize rspamd_upstreams_alive(struct upstream_list *ups)
 {
        return ups != NULL ? ups->alive->len : 0;
index 7f61a2de8a8766c2c4efcd0b4145d7081b1b61a2..2e1cf36a16efbe3c8e722e82a81271f62b4ec886 100644 (file)
@@ -183,12 +183,23 @@ void rspamd_upstreams_set_rotation(struct upstream_list *ups,
 void rspamd_upstreams_destroy(struct upstream_list *ups);
 
 /**
- * Returns count of upstreams in a list
+ * Returns count of upstreams in a list. SRV parent placeholders are excluded
+ * (they are not selectable themselves); use this when sizing the dispatchable
+ * cluster (e.g. weight calculations, output table preallocation).
  * @param ups
  * @return
  */
 gsize rspamd_upstreams_count(struct upstream_list *ups);
 
+/**
+ * Returns the total count of configured upstreams, including SRV parents that
+ * have not yet been resolved into members. Use this for "is anything configured
+ * at all" checks at config-load time, before async SRV resolution has run.
+ * @param ups
+ * @return
+ */
+gsize rspamd_upstreams_count_total(struct upstream_list *ups);
+
 /**
  * Returns the number of upstreams in the list
  * @param ups
index 7dbf786b9a48de214d4bfd270c3bc668557f5ded..d17b2d935df1c28db91eeb3bcd1a4fd5e422a891 100644 (file)
@@ -2180,7 +2180,13 @@ fuzzy_parse_rule(struct rspamd_config *cfg, const ucl_object_t *obj,
                                                  strlen(shingles_key_str), NULL, 0);
        rule->shingles_key->len = 16;
 
-       if (rspamd_upstreams_count(rule->read_servers) == 0) {
+       /*
+        * Use the *total* count so a rule whose only entry is an SRV
+        * placeholder (service=...) loads cleanly; rspamd_upstreams_count
+        * would return 0 here because the SRV parent isn't a dispatchable
+        * upstream until async resolution materialises members.
+        */
+       if (rspamd_upstreams_count_total(rule->read_servers) == 0) {
                msg_err_config("no servers defined for fuzzy rule with name: %s",
                                           rule->name);
                return -1;