Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 34 additions & 5 deletions examples/simple_repeater/MyMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secr
MESH_DEBUG_PRINTLN("Login success!");
client->last_timestamp = sender_timestamp;
client->last_activity = getRTCClock()->getCurrentTime();
client->permissions &= ~0x03;
client->permissions &= ~PERM_ACL_ROLE_MASK;
client->permissions |= perms;
memcpy(client->shared_secret, secret, PUB_KEY_SIZE);

Expand All @@ -136,7 +136,7 @@ uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secr
memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp
reply_data[4] = RESP_SERVER_LOGIN_OK;
reply_data[5] = 0; // Legacy: was recommended keep-alive interval (secs / 16)
reply_data[6] = client->isAdmin() ? 1 : 0;
reply_data[6] = (client->isAdmin() || client->isRegionMgr()) ? 1 : 0;
reply_data[7] = client->permissions;
getRNG()->random(&reply_data[8], 4); // random blob to help packet-hash uniqueness
reply_data[12] = FIRMWARE_VER_LEVEL; // New field
Expand Down Expand Up @@ -682,7 +682,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx,
} else {
MESH_DEBUG_PRINTLN("onPeerDataRecv: possible replay attack detected");
}
} else if (type == PAYLOAD_TYPE_TXT_MSG && len > 5 && client->isAdmin()) { // a CLI command
} else if (type == PAYLOAD_TYPE_TXT_MSG && len > 5 && (client->isAdmin() || client->isRegionMgr())) { // a CLI command
uint32_t sender_timestamp;
memcpy(&sender_timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong)
uint8_t flags = (data[4] >> 2); // message attempt number, and other flags
Expand Down Expand Up @@ -719,7 +719,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx,
if (is_retry) {
*reply = 0;
} else {
handleCommand(sender_timestamp, command, reply);
handleCommand(client, command, reply);
}
int text_len = strlen(reply);
if (text_len > 0) {
Expand Down Expand Up @@ -1165,7 +1165,27 @@ void MyMesh::clearStats() {
((SimpleMeshTables *)getTables())->resetStats();
}

void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply) {
// Whitelist helper for region manager command perms
static bool isRegionMgrAllowed(const char* cmd) {
while(*cmd == ' ') cmd++; // skip leading spaces
// region commands (read + write region map)
if (memcmp(cmd, "region", 6) == 0) return true;
// read-only getters / status
if (memcmp(cmd, "get ", 4) == 0) return true;
if (memcmp(cmd, "ver", 3) == 0) return true;
if (memcmp(cmd, "board", 5) == 0) return true;
// "neighbors" (plural) is read-only; reject "neighbor.remove" by checking next char
if (memcmp(cmd, "neighbors", 9) == 0) return true;
// bare "clock" is read-only; "clock sync" must be denied
if (memcmp(cmd, "clock", 5) == 0 && memcmp(cmd, "clock sync", 10) != 0) return true;
// sensor reads only
if (memcmp(cmd, "sensor get ", 11) == 0) return true;
if (memcmp(cmd, "sensor list", 11) == 0) return true;
return false;
}

void MyMesh::handleCommand(ClientInfo* sender, char *command, char *reply) {
uint32_t sender_timestamp = sender ? sender->last_timestamp : 0; // Serial CLI passes NULL
if (region_load_active) {
if (StrHelper::isBlank(command)) { // empty/blank line, signal to terminate 'load' operation
region_map = temp_map; // copy over the temp instance as new current map
Expand Down Expand Up @@ -1208,6 +1228,15 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply
command += 3;
}

// Region managers are limited to read-only queries and region commands
// Admins are unrestricted
if (sender && !sender->isAdmin() && sender->isRegionMgr()) {
if (!isRegionMgrAllowed(command)) {
strcpy(reply, "Err - not permitted");
return;
}
}

// handle ACL related commands
if (memcmp(command, "setperm ", 8) == 0) { // format: setperm {pubkey-hex} {permissions-int8}
char* hex = &command[8];
Expand Down
2 changes: 1 addition & 1 deletion examples/simple_repeater/MyMesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
void saveIdentity(const mesh::LocalIdentity& new_id) override;
void clearStats() override;

void handleCommand(uint32_t sender_timestamp, char* command, char* reply);
void handleCommand(ClientInfo* sender, char* command, char* reply);
void loop();

#if defined(WITH_BRIDGE)
Expand Down
2 changes: 1 addition & 1 deletion examples/simple_repeater/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ void loop() {
Serial.print('\n');
command[len - 1] = 0; // replace newline with C string null terminator
char reply[160];
the_mesh.handleCommand(0, command, reply); // NOTE: there is no sender_timestamp via serial!
the_mesh.handleCommand(NULL, command, reply); // NOTE: sender is NULL via serial
if (reply[0]) {
Serial.print(" -> "); Serial.println(reply);
}
Expand Down
3 changes: 2 additions & 1 deletion src/helpers/ClientACL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ ClientInfo* ClientACL::putClient(const mesh::Identity& id, uint8_t init_perms) {
ClientInfo* oldest = &clients[MAX_CLIENTS - 1];
for (int i = 0; i < num_clients; i++) {
if (id.matches(clients[i].id)) return &clients[i]; // already known
if (!clients[i].isAdmin() && clients[i].last_activity < min_time) {
if ( (!clients[i].isAdmin() && !clients[i].isRegionMgr())
&& clients[i].last_activity < min_time) {
oldest = &clients[i];
min_time = oldest->last_activity;
}
Expand Down
4 changes: 3 additions & 1 deletion src/helpers/ClientACL.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
#include <Mesh.h>
#include <helpers/IdentityStore.h>

#define PERM_ACL_ROLE_MASK 3 // lower 2 bits
#define PERM_ACL_ROLE_MASK 7 // lower 3 bits
#define PERM_ACL_GUEST 0
#define PERM_ACL_READ_ONLY 1
#define PERM_ACL_READ_WRITE 2
#define PERM_ACL_ADMIN 3
#define PERM_ACL_REGION_MGR 4

#define OUT_PATH_UNKNOWN 0xFF

Expand All @@ -31,6 +32,7 @@ struct ClientInfo {
} extra;

bool isAdmin() const { return (permissions & PERM_ACL_ROLE_MASK) == PERM_ACL_ADMIN; }
bool isRegionMgr() const { return (permissions & PERM_ACL_ROLE_MASK) == PERM_ACL_REGION_MGR; }
};

#ifndef MAX_CLIENTS
Expand Down