]> git.ipfire.org Git - thirdparty/rspamd.git/commitdiff
[Fix] Backward-compatible version negotiation for multi-flag fuzzy 5894/head
authorVsevolod Stakhov <vsevolod@rspamd.com>
Thu, 19 Feb 2026 17:32:10 +0000 (17:32 +0000)
committerVsevolod Stakhov <vsevolod@rspamd.com>
Thu, 19 Feb 2026 17:32:10 +0000 (17:32 +0000)
Use an unused bit (bit 4) in the version byte as a v2 capability flag.
New clients send version 4|0x10 initially; old servers mask to 4 and
work normally, new servers see the cap bit and send v2 replies. Once
a v2 reply is received, subsequent requests use native version 5.

src/fuzzy_storage.c
src/libserver/fuzzy_wire.h
src/plugins/fuzzy_check.c

index c960ac31b5efba5277e130047b998ff77e933510..b202c09f1be4a1939b5c6d90d5bf41ff92343df0 100644 (file)
@@ -1517,12 +1517,12 @@ rspamd_fuzzy_command_valid(struct rspamd_fuzzy_cmd *cmd, int r)
        case 4:
                if (cmd->shingles_count > 0) {
                        if (r >= sizeof(struct rspamd_fuzzy_shingle_cmd)) {
-                               ret = RSPAMD_FUZZY_EPOCH11;
+                               ret = (cmd->version & RSPAMD_FUZZY_V2_CAP) ? RSPAMD_FUZZY_EPOCH12 : RSPAMD_FUZZY_EPOCH11;
                        }
                }
                else {
                        if (r >= sizeof(*cmd)) {
-                               ret = RSPAMD_FUZZY_EPOCH11;
+                               ret = (cmd->version & RSPAMD_FUZZY_V2_CAP) ? RSPAMD_FUZZY_EPOCH12 : RSPAMD_FUZZY_EPOCH11;
                        }
                }
                break;
index 450df6f32e10ce6ccfd8413cf81fa9f6d0cea76e..f0b7becc424cfed188d8d295844ea47cb4b97be4 100644 (file)
 extern "C" {
 #endif
 
-#define RSPAMD_FUZZY_VERSION 5
+#define RSPAMD_FUZZY_VERSION 4
 #define RSPAMD_FUZZY_KEYLEN 8
 
 #define RSPAMD_FUZZY_FLAG_WEAK (1u << 7u)
 /* Use lower 4 bits for the version */
 #define RSPAMD_FUZZY_VERSION_MASK 0x0fu
+/* Capability bit: client supports v2 (multi-flag) replies */
+#define RSPAMD_FUZZY_V2_CAP (1u << 4u)
 /* Commands for fuzzy storage */
 #define FUZZY_CHECK 0
 #define FUZZY_WRITE 1
index 66daabb6ac4b3d1ce7b015b865f7ff89add8a35b..8602d3397c8383e038efd345323ecf20982ab428 100644 (file)
@@ -124,6 +124,7 @@ struct fuzzy_rule {
        struct rspamd_hash_map_helper *skip_map;
        struct fuzzy_ctx *ctx;
        int lua_id;
+       gboolean server_supports_v2;
 
        /* TCP configuration */
        gboolean tcp_enabled;   /* Explicitly enable TCP */
@@ -163,6 +164,15 @@ struct fuzzy_ctx {
        gboolean enabled;
 };
 
+static inline uint8_t
+fuzzy_cmd_version(const struct fuzzy_rule *rule)
+{
+       if (rule->server_supports_v2) {
+               return 5; /* Native v5 after server confirmed v2 support */
+       }
+       return RSPAMD_FUZZY_VERSION | RSPAMD_FUZZY_V2_CAP; /* 4 | 0x10 = 0x14 */
+}
+
 enum fuzzy_result_type {
        FUZZY_RESULT_TXT,
        FUZZY_RESULT_IMG,
@@ -1468,6 +1478,10 @@ fuzzy_tcp_process_reply(struct fuzzy_tcp_connection *conn,
                }
        }
 
+       if (is_v2) {
+               rule->server_supports_v2 = TRUE;
+       }
+
        /* Extract tag and lookup pending command */
        tag = rep->v1.tag;
        khiter_t k = kh_get(fuzzy_pending_hash, rule->pending_requests, tag);
@@ -3158,7 +3172,7 @@ fuzzy_cmd_stat(struct fuzzy_rule *rule,
        }
 
        cmd->cmd = c;
-       cmd->version = RSPAMD_FUZZY_PLUGIN_VERSION;
+       cmd->version = fuzzy_cmd_version(rule);
        cmd->shingles_count = 0;
        cmd->tag = ottery_rand_uint32();
 
@@ -3215,7 +3229,7 @@ fuzzy_cmd_ping(struct fuzzy_rule *rule,
 
 
        cmd->cmd = FUZZY_PING;
-       cmd->version = RSPAMD_FUZZY_PLUGIN_VERSION;
+       cmd->version = fuzzy_cmd_version(rule);
        cmd->shingles_count = 0;
        cmd->value = fuzzy_milliseconds_since_midnight(); /* Record timestamp */
        cmd->tag = ottery_rand_uint32();
@@ -3277,7 +3291,7 @@ fuzzy_cmd_hash(struct fuzzy_rule *rule,
        }
 
        cmd->cmd = c;
-       cmd->version = RSPAMD_FUZZY_PLUGIN_VERSION;
+       cmd->version = fuzzy_cmd_version(rule);
        cmd->shingles_count = 0;
        cmd->tag = ottery_rand_uint32();
 
@@ -3706,7 +3720,7 @@ fuzzy_cmd_from_text_part(struct rspamd_task *task,
        if (!short_text) {
                shcmd->basic.tag = ottery_rand_uint32();
                shcmd->basic.cmd = c;
-               shcmd->basic.version = RSPAMD_FUZZY_PLUGIN_VERSION;
+               shcmd->basic.version = fuzzy_cmd_version(rule);
 
                if (c != FUZZY_CHECK) {
                        shcmd->basic.flag = flag;
@@ -3718,7 +3732,7 @@ fuzzy_cmd_from_text_part(struct rspamd_task *task,
        else {
                cmd->tag = ottery_rand_uint32();
                cmd->cmd = c;
-               cmd->version = RSPAMD_FUZZY_PLUGIN_VERSION;
+               cmd->version = fuzzy_cmd_version(rule);
 
                if (c != FUZZY_CHECK) {
                        cmd->flag = flag;
@@ -3915,7 +3929,7 @@ fuzzy_cmd_from_html_part(struct rspamd_task *task,
 
        shcmd->basic.tag = ottery_rand_uint32();
        shcmd->basic.cmd = c;
-       shcmd->basic.version = RSPAMD_FUZZY_PLUGIN_VERSION;
+       shcmd->basic.version = fuzzy_cmd_version(rule);
 
        if (c != FUZZY_CHECK) {
                shcmd->basic.flag = flag;
@@ -4017,7 +4031,7 @@ fuzzy_cmd_from_image_part (struct fuzzy_rule *rule,
 
        shcmd->basic.tag = ottery_rand_uint32 ();
        shcmd->basic.cmd = c;
-       shcmd->basic.version = RSPAMD_FUZZY_PLUGIN_VERSION;
+       shcmd->basic.version = fuzzy_cmd_version(rule);
 
        if (c != FUZZY_CHECK) {
                shcmd->basic.flag = flag;
@@ -4075,7 +4089,7 @@ fuzzy_cmd_from_data_part(struct fuzzy_rule *rule,
        }
 
        cmd->cmd = c;
-       cmd->version = RSPAMD_FUZZY_PLUGIN_VERSION;
+       cmd->version = fuzzy_cmd_version(rule);
        if (c != FUZZY_CHECK) {
                cmd->flag = flag;
                cmd->value = weight;
@@ -4622,6 +4636,9 @@ fuzzy_check_try_read(struct fuzzy_client_session *session)
 
                while ((rep = fuzzy_process_reply(&p, &r,
                                                                                  session->commands, session->rule, &cmd, &io, &rep_v2)) != NULL) {
+                       if (rep_v2) {
+                               session->rule->server_supports_v2 = TRUE;
+                       }
                        if (rep->v1.prob > 0.5) {
                                if (cmd->cmd == FUZZY_CHECK) {
                                        fuzzy_insert_result(session, rep, cmd, io, rep->v1.flag);