diff --git a/src/chat/chat_core.c b/src/chat/chat_core.c index 8ed030ea..9339c968 100644 --- a/src/chat/chat_core.c +++ b/src/chat/chat_core.c @@ -668,7 +668,7 @@ int chat_member_tags_commit(struct UTUN_INSTANCE* inst, struct chat_member_tags* char peers_tbl[80]; peers_table_name(t->ch_id, peers_tbl, sizeof(peers_tbl)); sqlite3_stmt* st = NULL; char sql[384]; snprintf(sql, sizeof(sql), - "SELECT x25519_pubkey, ed25519_pubkey, join_sig, join_ts, update_sig, update_ts, userinfo, storage" + "SELECT x25519_pubkey, ed25519_pubkey, join_sig, join_ts, update_sig, update_ts, userinfo, storage, source" " FROM \"%s\" WHERE node_id=?", peers_tbl); if (sqlite3_prepare_v2(cc->db, sql, -1, &st, NULL) != SQLITE_OK) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: tags_commit — member query failed", CC_ID); return -1; @@ -685,9 +685,15 @@ int chat_member_tags_commit(struct UTUN_INSTANCE* inst, struct chat_member_tags* uint64_t uts = (uint64_t)sqlite3_column_int64(st, 5); const char* uinfo = (const char*)sqlite3_column_text(st, 6); int stg = sqlite3_column_int(st, 7); + int src = sqlite3_column_int(st, 8); if (!x25 || !ed) { sqlite3_finalize(st); DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: tags_commit — member has no keys ch=%s", CC_ID, t->ch_id); return -1; } + if (src != 0) { sqlite3_finalize(st); + DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: tags_commit — cannot set flags on placeholder member (source=%d) ch=%s nid=0x%016llx", + CC_ID, src, t->ch_id, (unsigned long long)t->node_id); + return -1; + } const char* stg_val = chat_member_tags_get(t, "storage"); if (stg_val && strcmp(stg_val, "yes") == 0) stg = 1; diff --git a/src/chat/chat_sync.c b/src/chat/chat_sync.c index d9b5b0d3..f9b9787c 100644 --- a/src/chat/chat_sync.c +++ b/src/chat/chat_sync.c @@ -31,6 +31,7 @@ #include #include #include +#include #ifdef __ANDROID__ #include #endif @@ -76,6 +77,7 @@ struct chat_sync { /* Pending incoming invites (для ask/deny) */ uint64_t pending_inv_in_ch_id[4]; uint64_t pending_inv_in_node_id[4]; + uint64_t pending_inv_in_key[4]; int pending_inv_in_count; }; @@ -1403,6 +1405,10 @@ static void cs_handle_channel_invite(struct chat_sync* cs, uint64_t peer, /* skip inviter_update_sig/ts/userinfo for now — verify channel sig only */ p += 64; uint64_t inviter_update_ts; memcpy(&inviter_update_ts, p, 8); p += 8; uint8_t userinfo_len = *p++; p += userinfo_len; + /* join-ключ (зарегистрирован инвайтером) — хвостовые 8 байт */ + uint64_t join_key = 0; + if (p + 8 > pl + len) { DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: CHANNEL_INVITE truncated (no join_key) from=%016llx", CS_ID, (unsigned long long)peer); return; } + memcpy(&join_key, p, 8); p += 8; /* Verify channel signature */ uint8_t vmsg[1024]; size_t vlen = 0; @@ -1440,13 +1446,26 @@ static void cs_handle_channel_invite(struct chat_sync* cs, uint64_t peer, DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: CHANNEL_INVITE channel_put rc=%d ch=%s ch_x25519=%016llx", CS_ID, cprc, ch_id, *(const uint64_t*)x25519); chat_core_ensure_channel_ready(cs->inst, ch_id); - /* Initiate join: connect back and send CHANNEL_INFO_REQ */ + /* Initiate join: connect back and send JOIN_INFO_REQ с зарегистрированным ключом */ + cs->pending_join_key = join_key; chat_sync_join_channel(cs->inst, ch_id, peer); return; } /* policy == ask — post event to GUI */ { + /* сохранить join-ключ, чтобы принять инвайт позже через chat_sync_accept_invite */ + uint64_t ch_num = strtoull(ch_id, NULL, 10); + int slot = -1; + for (int i = 0; i < cs->pending_inv_in_count; i++) + if (cs->pending_inv_in_ch_id[i] == ch_num && cs->pending_inv_in_node_id[i] == peer) { slot = i; break; } + if (slot < 0 && cs->pending_inv_in_count < 4) slot = cs->pending_inv_in_count++; + if (slot >= 0) { + cs->pending_inv_in_ch_id[slot] = ch_num; + cs->pending_inv_in_node_id[slot] = peer; + cs->pending_inv_in_key[slot] = join_key; + } + size_t inv_name_len = 0; char inv_name[128] = ""; { sqlite3* vdb = cs->inst->topo_sqlite_db; sqlite3_stmt* st = NULL; @@ -1489,6 +1508,15 @@ static int cs_send_channel_invite(struct chat_sync* cs, struct UTUN_INSTANCE* in int ulen = member_sync_build_update_msg(join_sig, upd_ts, juser, umsg, (int)sizeof(umsg)); if (ulen >= 0) cs_ed25519_sign(inst->my_ed25519_privkey, umsg, (size_t)ulen, upd_sig); } + /* join-ключ: генерируем и регистрируем локально (инвайтер == connection-узел), + чтобы джойнер смог пройти JOIN_INFO_REQ с валидным ключом. */ + uint64_t join_key = 0; + if (RAND_bytes((uint8_t*)&join_key, sizeof(join_key)) != 1 || join_key == 0) { + DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: join_key generation failed ch=%s", CS_ID, ch_id); + return -1; + } + chat_join_register_key(inst, strtoull(ch_id, NULL, 10), 0, join_key); + uint8_t buf[1024]; size_t boff = 0; buf[boff++] = CS_MSG_CHANNEL_INVITE; uint8_t cnl = (uint8_t)strlen(ch_name); @@ -1508,9 +1536,10 @@ static int cs_send_channel_invite(struct chat_sync* cs, struct UTUN_INSTANCE* in char juser[256]; snprintf(juser, sizeof(juser), "{\"name\":\"%s\"}", inst->name[0] ? inst->name : ""); uint8_t il = (uint8_t)strlen(juser); buf[boff++] = il; memcpy(buf + boff, juser, il); boff += il; + memcpy(buf + boff, &join_key, 8); boff += 8; int rc = cs_send(cs, ch_id, target_node_id, buf, boff); - DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: sent CHANNEL_INVITE to 0x%016llx ch=%s rc=%d", - CS_ID, (unsigned long long)target_node_id, ch_id, rc); + DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: sent CHANNEL_INVITE to 0x%016llx ch=%s key=%016llx rc=%d", + CS_ID, (unsigned long long)target_node_id, ch_id, (unsigned long long)join_key, rc); return rc; } @@ -1716,16 +1745,31 @@ void chat_sync_invite_to_channel_with_addrs( /* ─── accept/deny incoming invite ─── */ void chat_sync_accept_invite(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint64_t inviter_node_id) { - if (!cs_of(inst) || !inst) return; + struct chat_sync* cs = cs_of(inst); + if (!cs || !inst) return; char ch_id[64]; snprintf(ch_id, sizeof(ch_id), "%llu", (unsigned long long)channel_id); if (channel_id == 0 || inviter_node_id == 0) { DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: accept_invite invalid params ch=%llu node=%016llx", CS_ID, (unsigned long long)channel_id, (unsigned long long)inviter_node_id); return; } - DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: accept_invite ch=%llu inviter=0x%016llx", - CS_ID, (unsigned long long)channel_id, (unsigned long long)inviter_node_id); + /* достать join-ключ, сохранённый при приёме CHANNEL_INVITE (ask) */ + uint64_t join_key = 0; + for (int i = 0; i < cs->pending_inv_in_count; i++) { + if (cs->pending_inv_in_ch_id[i] == channel_id && cs->pending_inv_in_node_id[i] == inviter_node_id) { + join_key = cs->pending_inv_in_key[i]; + break; + } + } + if (join_key == 0) { + DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: accept_invite no join_key for ch=%llu inviter=0x%016llx", + CS_ID, (unsigned long long)channel_id, (unsigned long long)inviter_node_id); + return; + } + DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: accept_invite ch=%llu inviter=0x%016llx key=%016llx", + CS_ID, (unsigned long long)channel_id, (unsigned long long)inviter_node_id, (unsigned long long)join_key); chat_core_ensure_channel_ready(inst, ch_id); + cs->pending_join_key = join_key; chat_sync_join_channel(inst, ch_id, inviter_node_id); } diff --git a/src/chat/member_sync.c b/src/chat/member_sync.c index 223631bf..b05bc6dd 100644 --- a/src/chat/member_sync.c +++ b/src/chat/member_sync.c @@ -438,10 +438,11 @@ int member_sync_apply_record(struct UTUN_INSTANCE* inst, const char* ch_id, char local_tags[256] = ""; uint64_t local_signed_by = 0; uint8_t local_signature[64] = {0}; + int local_src = 0; { sqlite3_stmt* st = NULL; char sql[512]; snprintf(sql, sizeof(sql), - "SELECT join_sig, join_ts, update_ts, adm_tags, signed_by, signature FROM \"%s\" WHERE node_id=?", peers_tbl); + "SELECT join_sig, join_ts, update_ts, adm_tags, signed_by, signature, source FROM \"%s\" WHERE node_id=?", peers_tbl); if (sqlite3_prepare_v2(db, sql, -1, &st, NULL) == SQLITE_OK) { sqlite3_bind_int64(st, 1, (sqlite3_int64)m->node_id); if (sqlite3_step(st) == SQLITE_ROW) { @@ -455,6 +456,7 @@ int member_sync_apply_record(struct UTUN_INSTANCE* inst, const char* ch_id, local_signed_by = (uint64_t)sqlite3_column_int64(st, 4); const uint8_t* ls = (const uint8_t*)sqlite3_column_blob(st, 5); if (ls && sqlite3_column_bytes(st, 5) >= 64) memcpy(local_signature, ls, 64); + local_src = sqlite3_column_int(st, 6); } sqlite3_finalize(st); } @@ -572,6 +574,9 @@ int member_sync_apply_record(struct UTUN_INSTANCE* inst, const char* ch_id, if (adm_ver > local_ver) block_b_changed = 1; else if (adm_ver < local_ver) block_b_stale = 1; } + if (block_b_changed && local_src != 0) + DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: adm_tags change on placeholder member (source=%d) — ignored by merkle, won't propagate nid=0x%016llx ns=%s", + MS_ID, local_src, (unsigned long long)m->node_id, ch_id); /* ── запись изменённых блоков ── */ if (block_a_changed) {