room-server: drop dead anon-request handlers inherited from the clone

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).
This commit is contained in:
rlwilliamson-dev committed 2026-06-02 15:43:29 -05:00
1 parent fcd5fd8641
commit 05fce66b16
2 files changed
+3 -166

No files matched your search

+2 -161
View File
@@ -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) {