fix(chat): load complete verified owner snapshots and validate editor input
This commit is contained in:
1 parent
16d7fc4fb5
commit
a9618169ab
1 file changed
+62
-20
+62
-20
@@ -642,6 +642,10 @@ struct chat_member_tags_key {
|
||||
struct chat_member_tags {
|
||||
char ch_id[64];
|
||||
uint64_t node_id;
|
||||
sqlite3* db; /* Заимствована до остановки сервиса. */
|
||||
char baseline[MS_ADM_TAGS_MAX + 1]; /* Проверенный owner-блок на момент загрузки/commit. */
|
||||
uint8_t signature[64], x25519[32], ed25519[32];
|
||||
int version, parsing_edit;
|
||||
int key_count;
|
||||
struct chat_member_tags_key keys[CHAT_MEMBER_TAGS_MAX_KEYS];
|
||||
};
|
||||
@@ -667,9 +671,12 @@ static int tags_is_append_key(const char* key) {
|
||||
|
||||
static int tags_json_cb(const char* key, const char* json_val, void* arg) {
|
||||
struct chat_member_tags* t = (struct chat_member_tags*)arg;
|
||||
if (t->key_count >= CHAT_MEMBER_TAGS_MAX_KEYS || strlen(key) >= CHAT_MEMBER_TAGS_KEY_SZ) {
|
||||
if (!key[0] || t->key_count >= CHAT_MEMBER_TAGS_MAX_KEYS || strlen(key) >= CHAT_MEMBER_TAGS_KEY_SZ) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: tags JSON key limit exceeded", CC_ID); return -1;
|
||||
}
|
||||
if (t->parsing_edit && tags_is_append_key(key) && strcmp(json_val, "yes") && strcmp(json_val, "no")) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "tags: edited role must be yes/no key=%s", key); return -1;
|
||||
}
|
||||
struct chat_member_tags_key* k = &t->keys[t->key_count];
|
||||
snprintf(k->key, CHAT_MEMBER_TAGS_KEY_SZ, "%s", key);
|
||||
k->val = u_strdup(tags_is_append_key(key) ? tags_append_effective(json_val, NULL) : json_val);
|
||||
@@ -683,33 +690,64 @@ static int tags_json_cb(const char* key, const char* json_val, void* arg) {
|
||||
}
|
||||
|
||||
struct chat_member_tags* chat_member_tags_load(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t node_id) {
|
||||
struct chat_core_ctx* cc = CC(inst);
|
||||
if (!cc || !cc->initialized || !ch_id || !cc->db) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: tags_load — not initialized", CC_ID); return NULL; }
|
||||
struct chat_core_ctx* cc = inst ? CC(inst) : NULL;
|
||||
if (!cc || !cc->initialized || !chat_channel_id(ch_id) || !node_id || !cc->db) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "tags: invalid load context/channel/member"); return NULL;
|
||||
}
|
||||
struct chat_member_tags* t = u_calloc(1, sizeof(*t));
|
||||
if (!t) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: tags_load — OOM", CC_ID); return NULL; }
|
||||
snprintf(t->ch_id, sizeof(t->ch_id), "%s", ch_id);
|
||||
t->node_id = node_id;
|
||||
t->node_id = node_id; t->db = cc->db;
|
||||
|
||||
char peers_tbl[80]; peers_table_name(ch_id, peers_tbl, sizeof(peers_tbl));
|
||||
sqlite3_stmt* st = NULL;
|
||||
char sql[256]; snprintf(sql, sizeof(sql), "SELECT adm_tags FROM \"%s\" WHERE node_id=?", peers_tbl);
|
||||
if (sqlite3_prepare_v2(cc->db, sql, -1, &st, NULL) == SQLITE_OK) {
|
||||
sqlite3_bind_int64(st, 1, (sqlite3_int64)node_id);
|
||||
if (sqlite3_step(st) == SQLITE_ROW) {
|
||||
const char* tags = (const char*)sqlite3_column_text(st, 0);
|
||||
if (tags && tags[0] && chat_member_tags_replace(t, tags) < 0) {
|
||||
sqlite3_finalize(st); chat_member_tags_free(t); return NULL;
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(st);
|
||||
char sql[256]; snprintf(sql, sizeof(sql), "SELECT adm_tags,adm_tags_sig,x25519_pubkey,ed25519_pubkey,source FROM \"%s\" WHERE node_id=?", peers_tbl);
|
||||
const char* stage = "prepare"; int rc = sqlite3_prepare_v2(cc->db, sql, -1, &st, NULL);
|
||||
if (rc == SQLITE_OK) { stage = "bind"; rc = sqlite3_bind_int64(st, 1, (sqlite3_int64)node_id); }
|
||||
if (rc == SQLITE_OK) { stage = "step"; rc = sqlite3_step(st); }
|
||||
if (rc != SQLITE_ROW) goto error;
|
||||
stage = "fields"; rc = SQLITE_CORRUPT;
|
||||
int type = sqlite3_column_type(st, 0), bytes = sqlite3_column_bytes(st, 0), sig_type = sqlite3_column_type(st, 1);
|
||||
const char* text = (const char*)sqlite3_column_text(st, 0);
|
||||
const void *signature = sqlite3_column_blob(st, 1), *x = sqlite3_column_blob(st, 2), *ed = sqlite3_column_blob(st, 3);
|
||||
if ((type != SQLITE_NULL && type != SQLITE_TEXT) || (type == SQLITE_TEXT && (!text || strlen(text) != (size_t)bytes)) ||
|
||||
bytes > MS_ADM_TAGS_MAX || sqlite3_column_type(st, 2) != SQLITE_BLOB || sqlite3_column_bytes(st, 2) != 32 || !x ||
|
||||
sqlite3_column_type(st, 3) != SQLITE_BLOB || sqlite3_column_bytes(st, 3) != 32 || !ed || sqlite3_column_int(st, 4)) goto error;
|
||||
if (bytes) memcpy(t->baseline, text, (size_t)bytes + 1);
|
||||
if (sig_type != SQLITE_NULL) {
|
||||
if (sig_type != SQLITE_BLOB || sqlite3_column_bytes(st, 1) != 64 || !signature) goto error;
|
||||
memcpy(t->signature, signature, 64);
|
||||
}
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CHAT_SYNC, "%s: tags_load ch=%s nid=0x%016llx keys=%d",
|
||||
CC_ID, ch_id, (unsigned long long)node_id, t->key_count);
|
||||
memcpy(t->x25519, x, 32); memcpy(t->ed25519, ed, 32);
|
||||
stage = "finalize"; rc = sqlite3_finalize(st); st = NULL; if (rc != SQLITE_OK) goto error;
|
||||
t->version = member_sync_owner_version(cc->db, t->baseline);
|
||||
if (t->version < 0) goto invalid;
|
||||
if (t->version) {
|
||||
uint8_t key[32], body[MS_ADM_TAGS_MAX + 8];
|
||||
int length = member_sync_build_owner_msg(node_id, t->baseline, body, sizeof(body));
|
||||
if (length < 0 || topo_node_sqlite_channel_get(cc->db, ch_id, NULL, 0, NULL, NULL, key, NULL) ||
|
||||
sc_ed25519_verify(key, body, (size_t)length, t->signature) != SC_OK) goto invalid;
|
||||
if (chat_json_strings(cc->db, t->baseline, MS_ADM_TAGS_MAX, tags_json_cb, t)) goto invalid;
|
||||
} else if (memcmp(t->signature, (uint8_t[64]){0}, 64)) goto invalid;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CHAT_SYNC, "tags: loaded ch=%s node=%016llx version=%d keys=%d",
|
||||
ch_id, (unsigned long long)node_id, t->version, t->key_count);
|
||||
return t;
|
||||
invalid:
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "tags: invalid signed owner block ch=%s node=%016llx", ch_id, (unsigned long long)node_id);
|
||||
chat_member_tags_free(t); return NULL;
|
||||
error:
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "tags: load ch=%s node=%016llx stage=%s rc=%d: %s",
|
||||
ch_id, (unsigned long long)node_id, stage, rc, sqlite3_errmsg(cc->db));
|
||||
rc = sqlite3_finalize(st);
|
||||
if (rc != SQLITE_OK) DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "tags: load cleanup finalize=%d", rc);
|
||||
chat_member_tags_free(t); return NULL;
|
||||
}
|
||||
|
||||
int chat_member_tags_set(struct chat_member_tags* t, const char* key, const char* val) {
|
||||
if (!t || !key) return -1;
|
||||
if (!t || !key || !key[0] || !strcmp(key, "ver") ||
|
||||
(val && tags_is_append_key(key) && strcmp(val, "yes") && strcmp(val, "no"))) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "tags: invalid edit key/role or reserved version"); return -1;
|
||||
}
|
||||
if (strlen(key) >= CHAT_MEMBER_TAGS_KEY_SZ) { DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "%s: tags_set — key too long", CC_ID); return -1; }
|
||||
if (val && strlen(val) > MS_ADM_TAGS_MAX) { DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "%s: tags_set — val too long", CC_ID); return -1; }
|
||||
|
||||
@@ -722,7 +760,7 @@ int chat_member_tags_set(struct chat_member_tags* t, const char* key, const char
|
||||
if (!copy) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: tags_set allocation failed", CC_ID); return -1; }
|
||||
if (idx >= 0) {
|
||||
u_free(t->keys[idx].val); t->keys[idx].val = copy;
|
||||
} else if (t->key_count < CHAT_MEMBER_TAGS_MAX_KEYS) {
|
||||
} else if (t->key_count < CHAT_MEMBER_TAGS_MAX_KEYS - (tags_find(t, "ver") < 0)) {
|
||||
struct chat_member_tags_key* k = &t->keys[t->key_count++];
|
||||
snprintf(k->key, CHAT_MEMBER_TAGS_KEY_SZ, "%s", key);
|
||||
k->val = copy;
|
||||
@@ -743,8 +781,12 @@ int chat_member_tags_replace(struct chat_member_tags* t, const char* json) {
|
||||
if (!t || !json || strlen(json) > MS_ADM_TAGS_MAX) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: tags_replace invalid input or size", CC_ID); return -1;
|
||||
}
|
||||
struct chat_member_tags parsed = {0};
|
||||
int rc = json_flat_parse(json, tags_json_cb, &parsed);
|
||||
struct chat_member_tags parsed = {.db = t->db, .parsing_edit = 1};
|
||||
int rc = chat_json_strings(t->db, json, MS_ADM_TAGS_MAX, tags_json_cb, &parsed);
|
||||
char version[16]; snprintf(version, sizeof(version), "%d", t->version);
|
||||
const char* requested = chat_member_tags_get(&parsed, "ver");
|
||||
if (requested && strcmp(requested, version)) rc = -1;
|
||||
if (!requested && parsed.key_count == CHAT_MEMBER_TAGS_MAX_KEYS) rc = -1;
|
||||
if (rc != 0) {
|
||||
for (int i = 0; i < parsed.key_count; i++) { u_free(parsed.keys[i].raw); u_free(parsed.keys[i].val); }
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "%s: tags_replace — invalid JSON", CC_ID); return -1;
|
||||
|
||||
Reference in new issue
Block a user