From 1aa528d352e95261d374475ee7a6afdf38bf32f8 Mon Sep 17 00:00:00 2001 From: evgeny Date: Wed, 4 Nov 2026 00:45:06 +0100 Subject: [PATCH] Authorize member disclosure separately from recovery requests and clean abandoned versions --- src/chat/member_sync.c | 34 ++++++++++++++++++++++++++++++---- tests/media_members_fixture.h | 2 +- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/chat/member_sync.c b/src/chat/member_sync.c index da4c41bb7..23a3db38a 100644 --- a/src/chat/member_sync.c +++ b/src/chat/member_sync.c @@ -1611,10 +1611,27 @@ static int _member_apply_items(void* ctx, const char* ns, uint64_t from_peer, co } static int _member_validate_peer(void* ctx, const char* ns, uint64_t peer) { - struct UTUN_INSTANCE* inst = (struct UTUN_INSTANCE*)ctx; - sqlite3* db = _db(inst); - if (!db) return 0; - return topo_node_sqlite_member_in_channel(db, ns, peer); + struct UTUN_INSTANCE* inst = ctx; + struct ETCP_CONN* conn = inst ? instance_find_conn(inst, peer) : NULL; + return conn && conn->initialized && conn->links_up && conn->state != 2 && !conn->close_requested && + topo_group_chat_allowed(inst, chat_channel_id(ns), peer) && + topo_node_sqlite_member_key_allowed(_db(inst), ns, conn->peer_pubkey); +} + +/* Разрешение только спросить: канал выбран локально, ключ источника уже связан с его JOIN. + * Никаких страниц/хешей в ответ этот режим не выдаёт; источник сам проверяет наш допуск. */ +static int member_validate_request(void* ctx, const char* ns, uint64_t peer) { + struct UTUN_INSTANCE* inst = ctx; + struct TOPO_GROUP* group = inst && inst->topo_groups ? topo_groups_find(inst->topo_groups, chat_channel_id(ns)) : NULL; + struct ETCP_CONN* conn = inst ? instance_find_conn(inst, peer) : NULL; + if (!group || group->group_type != TOPO_GROUP_TYPE_CHAT || group->stopping || strcmp(group->channel_id, ns) || + !conn || !conn->initialized || !conn->links_up || conn->state == 2 || conn->close_requested) return 0; + struct member_saved record; + int found = member_read_saved(_db(inst), ns, peer, 0, 0, &record); + if (found != 1 || record.source || record.malformed || memcmp(record.x, conn->peer_pubkey, 32) || + sc_derive_node_id_from_pubkey(record.x) != peer || _verify_join_sig(_db(inst), ns, &record.rec) != 1) return 0; + uint8_t key[32]; enum ms_author_status status = member_sync_author_key(inst, ns, peer, key); + return status == MS_AUTHOR_ACTIVE || status == MS_AUTHOR_PENDING; } static int member_finish(void* ctx, const char* ns) { @@ -1690,6 +1707,7 @@ static const struct merkle_sync_data_ops g_member_ops = { .get_page = _member_get_page, .apply_items = _member_apply_items, .validate_peer = _member_validate_peer, + .validate_request = member_validate_request, .finish = member_finish, .on_result = member_policy_result, .begin_pull = member_begin_pull, @@ -1716,6 +1734,10 @@ static int _rebuild_all_trees(struct UTUN_INSTANCE* inst) { } uint64_t mask = merkle_sync_level_prefix(UINT64_MAX, MT_MAX_LEVEL); char sql[256]; + snprintf(sql, sizeof(sql), "DELETE FROM \"%s\" WHERE record_state<>0", table); + if (sqlite3_exec(db, sql, NULL, NULL, NULL) != SQLITE_OK) { rc = SQLITE_ERROR; break; } + if (sqlite3_changes(db)) DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "member receive abandoned versions removed ch=%s rows=%d", + ns, sqlite3_changes(db)); snprintf(sql, sizeof(sql), "SELECT DISTINCT (node_id & %lld) FROM \"%s\" WHERE record_state=0 AND source=0", (long long)mask, table); sqlite3_stmt* leaves = NULL; int lr = sqlite3_prepare_v2(db, sql, -1, &leaves, NULL); @@ -1829,6 +1851,10 @@ void member_sync_destroy(struct UTUN_INSTANCE* inst) { int member_sync_start(struct UTUN_INSTANCE* inst, uint64_t peer, const char* ch_id, merkle_sync_done_cb done_cb, void* arg) { + if (!_member_validate_peer(inst, ch_id, peer) && !member_validate_request(inst, ch_id, peer)) { + DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "member start denied ch=%s peer=%016llx", ch_id ? ch_id : "-", (unsigned long long)peer); + return -1; + } if (member_sync_verify_and_purge(inst, ch_id) < 0) { DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: start rejected after purge failure ns=%s peer=%016llx", MS_ID, ch_id ? ch_id : "-", (unsigned long long)peer); return -1; diff --git a/tests/media_members_fixture.h b/tests/media_members_fixture.h index 89bb9612d..2164398a4 100644 --- a/tests/media_members_fixture.h +++ b/tests/media_members_fixture.h @@ -25,7 +25,7 @@ static void media_test_group(struct UTUN_INSTANCE* inst, uint64_t id, const uint if (queue_data_put_with_index(inst->topo_groups->group_list, &g->ll)) abort(); } char sql[512]; - snprintf(sql, sizeof(sql), "CREATE TABLE IF NOT EXISTS peers_%llu(node_id INTEGER PRIMARY KEY,source INTEGER DEFAULT 0," + snprintf(sql, sizeof(sql), "CREATE TABLE IF NOT EXISTS peers_%llu(node_id INTEGER PRIMARY KEY,record_state INTEGER DEFAULT 0,source INTEGER DEFAULT 0," "deleted INTEGER DEFAULT 0,admitted INTEGER DEFAULT 1,node_type INTEGER DEFAULT 0,adm_tags TEXT DEFAULT '{}')", (unsigned long long)id); if (sqlite3_exec(inst->topo_sqlite_db, sql, NULL, NULL, NULL) != SQLITE_OK) abort();