From 05fce66b164baf9d50f929c6c5203a1242cd3a4d Mon Sep 17 00:00:00 2001 From: rlwilliamson-dev <123014229+rlwilliamson-dev@users.noreply.github.com> Date: Tue, 2 Jun 2026 15:43:29 -0500 Subject: [PATCH] room-server: drop dead anon-request handlers inherited from the clone MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The room server's onAnonDataRecv handles login inline (the room login protocol carries a sync_since cursor the repeater's doesn't), so the cloned repeater helpers handleLoginReq + handleAnonRegionsReq / handleAnonOwnerReq / handleAnonClockReq — and the anon_limiter they used — were dead after the port. Remove them (~160 lines). No behavior change. The inherited neighbor-tracking / node-discovery machinery is also unused on a room server; left in place for now (can be trimmed in a follow-up). --- zephcore/app/RoomServerMesh.cpp | 163 +------------------------------- zephcore/app/RoomServerMesh.h | 6 +- 2 files changed, 3 insertions(+), 166 deletions(-) diff --git a/zephcore/app/RoomServerMesh.cpp b/zephcore/app/RoomServerMesh.cpp index 3cc7301..aa8ce67 100644 --- a/zephcore/app/RoomServerMesh.cpp +++ b/zephcore/app/RoomServerMesh.cpp @@ -156,162 +156,6 @@ void RoomServerMesh::putNeighbour(const mesh::Identity& id, uint32_t timestamp, #endif } -uint8_t RoomServerMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood) { - ClientInfo* client = nullptr; - - if (data[0] == 0) { - client = acl.getClient(sender.pub_key, PUB_KEY_SIZE); - } - - if (client == nullptr) { - uint8_t perms; - - /* Constant-time comparison: pad the received password to the full - * 16-byte storage size with zeros, then compare against both - * stored passwords (which are already zero-padded by initNodePrefs). - * Compare both unconditionally so timing is identical for any - * wrong password regardless of which (admin/guest) it most - * resembles. */ - uint8_t received[sizeof(_prefs.password)] = {0}; - size_t r_len = strnlen((const char *)data, sizeof(received) - 1); - memcpy(received, data, r_len); - - bool admin_match = mesh::Utils::constantTimeEqual(received, - _prefs.password, - sizeof(received)); - bool guest_match = mesh::Utils::constantTimeEqual(received, - _prefs.guest_password, - sizeof(received)); - - if (admin_match) { - perms = PERM_ACL_ADMIN; - } else if (guest_match) { - perms = PERM_ACL_GUEST; - } else { - /* Apply global failed-login rate limit. The check itself is - * unconditional regardless of admin/guest path so its timing - * doesn't leak which credential the attempt was closer to. */ - if (!login_fail_limiter.allow(getRTCClock()->getCurrentTime())) { - LOG_WRN("Login rate-limited (failed attempts exceeded)"); - } else { - LOG_WRN("Invalid password"); - } - return 0; - } - - client = acl.putClient(sender, 0); - if (sender_timestamp <= client->last_timestamp) { - LOG_WRN("Possible login replay attack!"); - return 0; - } - - LOG_INF("Login success"); - client->last_timestamp = sender_timestamp; - client->last_activity = getRTCClock()->getCurrentTime(); - client->permissions &= ~0x03; - client->permissions |= perms; - memcpy(client->shared_secret, secret, PUB_KEY_SIZE); - - if (perms != PERM_ACL_GUEST) { - if (!dirty_contacts_expiry) dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); - } - } - - if (is_flood) { - client->out_path_len = OUT_PATH_UNKNOWN; - } - - uint32_t now = getRTCClock()->getCurrentTimeUnique(); - memcpy(reply_data, &now, 4); - reply_data[4] = RESP_SERVER_LOGIN_OK; - reply_data[5] = 0; - reply_data[6] = client->isAdmin() ? 1 : 0; - reply_data[7] = client->permissions; - getRNG()->random(&reply_data[8], 4); - reply_data[12] = FIRMWARE_VER_LEVEL; - - return 13; -} - -uint8_t RoomServerMesh::handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data, size_t data_len) { - if (anon_limiter.allow(getRTCClock()->getCurrentTime())) { - if (data_len < 1) return 0; - reply_path_len = *data++; - data_len--; - /* data is anon-req-supplied; bound copy with remaining data_len. - * If the claimed path is longer than the bytes provided, copyPath - * rejects (returns 0) and leaves reply_path stale — fall back to a - * flood reply instead of emitting a corrupt direct path (F2). - * path_len == 0 is a valid zero-hop direct reply, so don't treat - * its (also-0) return as a rejection. */ - if (reply_path_len != 0 && - mesh::Packet::copyPath(reply_path, data, data_len, reply_path_len) == 0) { - reply_path_len = OUT_PATH_UNKNOWN; - } - - memcpy(reply_data, &sender_timestamp, 4); - uint32_t now = getRTCClock()->getCurrentTime(); - memcpy(&reply_data[4], &now, 4); - - return 8 + region_map.exportNamesTo((char*)&reply_data[8], sizeof(reply_data) - 12, REGION_DENY_FLOOD); - } - return 0; -} - -uint8_t RoomServerMesh::handleAnonOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data, size_t data_len) { - if (anon_limiter.allow(getRTCClock()->getCurrentTime())) { - if (data_len < 1) return 0; - reply_path_len = *data++; - data_len--; - /* data is anon-req-supplied; bound copy with remaining data_len. - * If the claimed path is longer than the bytes provided, copyPath - * rejects (returns 0) and leaves reply_path stale — fall back to a - * flood reply instead of emitting a corrupt direct path (F2). - * path_len == 0 is a valid zero-hop direct reply, so don't treat - * its (also-0) return as a rejection. */ - if (reply_path_len != 0 && - mesh::Packet::copyPath(reply_path, data, data_len, reply_path_len) == 0) { - reply_path_len = OUT_PATH_UNKNOWN; - } - - memcpy(reply_data, &sender_timestamp, 4); - uint32_t now = getRTCClock()->getCurrentTime(); - memcpy(&reply_data[4], &now, 4); - sprintf((char*)&reply_data[8], "%s\n%s", _prefs.node_name, _prefs.owner_info); - - return 8 + strlen((char*)&reply_data[8]); - } - return 0; -} - -uint8_t RoomServerMesh::handleAnonClockReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data, size_t data_len) { - if (anon_limiter.allow(getRTCClock()->getCurrentTime())) { - if (data_len < 1) return 0; - reply_path_len = *data++; - data_len--; - /* data is anon-req-supplied; bound copy with remaining data_len. - * If the claimed path is longer than the bytes provided, copyPath - * rejects (returns 0) and leaves reply_path stale — fall back to a - * flood reply instead of emitting a corrupt direct path (F2). - * path_len == 0 is a valid zero-hop direct reply, so don't treat - * its (also-0) return as a rejection. */ - if (reply_path_len != 0 && - mesh::Packet::copyPath(reply_path, data, data_len, reply_path_len) == 0) { - reply_path_len = OUT_PATH_UNKNOWN; - } - - memcpy(reply_data, &sender_timestamp, 4); - uint32_t now = getRTCClock()->getCurrentTime(); - memcpy(&reply_data[4], &now, 4); - reply_data[8] = 0; // features - if (_prefs.disable_fwd) { - reply_data[8] |= 0x80; - } - return 9; - } - return 0; -} - int RoomServerMesh::handleRequest(ClientInfo* sender, uint32_t sender_timestamp, uint8_t* payload, size_t payload_len) { memcpy(reply_data, &sender_timestamp, 4); @@ -1134,11 +978,8 @@ RoomServerMesh::RoomServerMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh: _cli(board, rtc, acl, &_prefs, this), region_map(key_store), temp_map(key_store), discover_limiter(4, 120), - anon_limiter(4, 180), - /* Failed-login rate limit: 4 wrong-password attempts per 180s. Matches - * anon_limiter's shape so legitimate operators don't notice; brute-force - * attempts hit the cap quickly and trip the LOG_WRN below. Global rate - * (not per-sender) — trade-off documented in CRYPTO_AUDIT_INDEX.md + /* Failed-login rate limit: 4 wrong-password attempts per 180s. Global + * rate (not per-sender) — trade-off documented in CRYPTO_AUDIT_INDEX.md * Phase 4 (mitigation for upstream MeshCore#2556). */ login_fail_limiter(4, 180) { diff --git a/zephcore/app/RoomServerMesh.h b/zephcore/app/RoomServerMesh.h index f9ac65f..12da3d7 100644 --- a/zephcore/app/RoomServerMesh.h +++ b/zephcore/app/RoomServerMesh.h @@ -120,7 +120,7 @@ class RoomServerMesh : public mesh::Mesh, public CommonCLICallbacks { RegionEntry* load_stack[8]; RegionEntry* recv_pkt_region; TransportKey default_scope; - RateLimiter discover_limiter, anon_limiter, login_fail_limiter; + RateLimiter discover_limiter, login_fail_limiter; uint32_t pending_discover_tag; unsigned long pending_discover_until; bool region_load_active; @@ -154,10 +154,6 @@ class RoomServerMesh : public mesh::Mesh, public CommonCLICallbacks { #endif void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr); - uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood); - uint8_t handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data, size_t data_len); - uint8_t handleAnonOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data, size_t data_len); - uint8_t handleAnonClockReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data, size_t data_len); int handleRequest(ClientInfo* sender, uint32_t sender_timestamp, uint8_t* payload, size_t payload_len); mesh::Packet* createSelfAdvert();