|
46 | 46 |
|
47 | 47 | /* ------------------------------ Code -------------------------------- */ |
48 | 48 |
|
| 49 | +#define REQ_TYPE_LOGIN 0x00 |
49 | 50 | #define REQ_TYPE_GET_STATUS 0x01 |
50 | 51 | #define REQ_TYPE_KEEP_ALIVE 0x02 |
51 | 52 | #define REQ_TYPE_GET_TELEMETRY_DATA 0x03 |
@@ -139,12 +140,10 @@ void SensorMesh::saveContacts() { |
139 | 140 | } |
140 | 141 | } |
141 | 142 |
|
142 | | -int SensorMesh::handleRequest(ContactInfo& sender, uint32_t sender_timestamp, uint8_t* payload, size_t payload_len) { |
143 | | - // uint32_t now = getRTCClock()->getCurrentTimeUnique(); |
144 | | - // memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp |
| 143 | +uint8_t SensorMesh::handleRequest(bool is_admin, uint32_t sender_timestamp, uint8_t req_type, uint8_t* payload, size_t payload_len) { |
145 | 144 | memcpy(reply_data, &sender_timestamp, 4); // reflect sender_timestamp back in response packet (kind of like a 'tag') |
146 | 145 |
|
147 | | - switch (payload[0]) { |
| 146 | + switch (req_type) { |
148 | 147 | case REQ_TYPE_GET_TELEMETRY_DATA: { |
149 | 148 | telemetry.reset(); |
150 | 149 | telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f); |
@@ -253,63 +252,79 @@ int SensorMesh::getAGCResetInterval() const { |
253 | 252 | return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds |
254 | 253 | } |
255 | 254 |
|
256 | | -void SensorMesh::onAnonDataRecv(mesh::Packet* packet, uint8_t type, const mesh::Identity& sender, uint8_t* data, size_t len) { |
257 | | - if (type == PAYLOAD_TYPE_ANON_REQ) { // received an initial request by a possible admin client (unknown at this stage) |
| 255 | +uint8_t SensorMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data) { |
| 256 | + bool is_admin; |
| 257 | + if (strcmp((char *) data, _prefs.password) == 0) { // check for valid password |
| 258 | + is_admin = true; |
| 259 | + } else if (strcmp((char *) data, _prefs.guest_password) == 0) { // check guest password |
| 260 | + is_admin = false; |
| 261 | + } else { |
| 262 | + #if MESH_DEBUG |
| 263 | + MESH_DEBUG_PRINTLN("Invalid password: %s", &data[4]); |
| 264 | + #endif |
| 265 | + return 0; |
| 266 | + } |
| 267 | + |
| 268 | + auto client = putContact(sender); // add to contacts (if not already known) |
| 269 | + if (sender_timestamp <= client->last_timestamp) { |
| 270 | + MESH_DEBUG_PRINTLN("Possible login replay attack!"); |
| 271 | + return 0; // FATAL: client table is full -OR- replay attack |
| 272 | + } |
| 273 | + |
| 274 | + MESH_DEBUG_PRINTLN("Login success!"); |
| 275 | + client->last_timestamp = sender_timestamp; |
| 276 | + client->last_activity = getRTCClock()->getCurrentTime(); |
| 277 | + client->type = is_admin ? 1 : 0; |
| 278 | + memcpy(client->shared_secret, secret, PUB_KEY_SIZE); |
| 279 | + |
| 280 | + if (is_admin) { |
| 281 | + // only need to saveContacts() if this is an admin |
| 282 | + dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); |
| 283 | + } |
| 284 | + |
| 285 | + uint32_t now = getRTCClock()->getCurrentTimeUnique(); |
| 286 | + memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp |
| 287 | + reply_data[4] = RESP_SERVER_LOGIN_OK; |
| 288 | + reply_data[5] = 0; // NEW: recommended keep-alive interval (secs / 16) |
| 289 | + reply_data[6] = client->type; |
| 290 | + reply_data[7] = 0; // FUTURE: reserved |
| 291 | + getRNG()->random(&reply_data[8], 4); // random blob to help packet-hash uniqueness |
| 292 | + |
| 293 | + return 12; // reply length |
| 294 | +} |
| 295 | + |
| 296 | +void SensorMesh::onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) { |
| 297 | + if (packet->getPayloadType() == PAYLOAD_TYPE_ANON_REQ) { // received an initial request by a possible admin client (unknown at this stage) |
258 | 298 | uint32_t timestamp; |
259 | 299 | memcpy(×tamp, data, 4); |
260 | 300 |
|
261 | | - bool is_admin; |
262 | 301 | data[len] = 0; // ensure null terminator |
263 | | - if (strcmp((char *) &data[4], _prefs.password) == 0) { // check for valid password |
264 | | - is_admin = true; |
265 | | - } else if (strcmp((char *) &data[4], _prefs.guest_password) == 0) { // check guest password |
266 | | - is_admin = false; |
267 | | - } else { |
268 | | - #if MESH_DEBUG |
269 | | - MESH_DEBUG_PRINTLN("Invalid password: %s", &data[4]); |
270 | | - #endif |
271 | | - return; |
272 | | - } |
273 | 302 |
|
274 | | - auto client = putContact(sender); // add to contacts (if not already known) |
275 | | - if (timestamp <= client->last_timestamp) { |
276 | | - MESH_DEBUG_PRINTLN("Possible login replay attack!"); |
277 | | - return; // FATAL: client table is full -OR- replay attack |
| 303 | + uint8_t req_code; |
| 304 | + uint8_t i = 4; |
| 305 | + if (data[4] < 32) { // non-print char, is a request code |
| 306 | + req_code = data[i++]; |
| 307 | + } else { |
| 308 | + req_code = REQ_TYPE_LOGIN; |
278 | 309 | } |
279 | 310 |
|
280 | | - MESH_DEBUG_PRINTLN("Login success!"); |
281 | | - client->last_timestamp = timestamp; |
282 | | - client->last_activity = getRTCClock()->getCurrentTime(); |
283 | | - client->type = is_admin ? 1 : 0; |
284 | | - self_id.calcSharedSecret(client->shared_secret, client->id); // calc ECDH shared secret |
285 | | - |
286 | | - if (is_admin) { |
287 | | - // only need to saveContacts() if this is an admin |
288 | | - dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); |
| 311 | + uint8_t reply_len; |
| 312 | + if (req_code == REQ_TYPE_LOGIN) { |
| 313 | + reply_len = handleLoginReq(sender, secret, timestamp, &data[i]); |
| 314 | + } else { |
| 315 | + reply_len = handleRequest(false, timestamp, req_code, &data[i], len - i); |
289 | 316 | } |
290 | 317 |
|
291 | | - uint32_t now = getRTCClock()->getCurrentTimeUnique(); |
292 | | - memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp |
293 | | - reply_data[4] = RESP_SERVER_LOGIN_OK; |
294 | | - reply_data[5] = 0; // NEW: recommended keep-alive interval (secs / 16) |
295 | | - reply_data[6] = client->type; |
296 | | - reply_data[7] = 0; // FUTURE: reserved |
297 | | - getRNG()->random(&reply_data[8], 4); // random blob to help packet-hash uniqueness |
| 318 | + if (reply_len == 0) return; // invalid request |
298 | 319 |
|
299 | 320 | if (packet->isRouteFlood()) { |
300 | 321 | // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response |
301 | | - mesh::Packet* path = createPathReturn(sender, client->shared_secret, packet->path, packet->path_len, |
302 | | - PAYLOAD_TYPE_RESPONSE, reply_data, 12); |
| 322 | + mesh::Packet* path = createPathReturn(sender, secret, packet->path, packet->path_len, |
| 323 | + PAYLOAD_TYPE_RESPONSE, reply_data, reply_len); |
303 | 324 | if (path) sendFlood(path, SERVER_RESPONSE_DELAY); |
304 | 325 | } else { |
305 | | - mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, client->shared_secret, reply_data, 12); |
306 | | - if (reply) { |
307 | | - if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT |
308 | | - sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY); |
309 | | - } else { |
310 | | - sendFlood(reply, SERVER_RESPONSE_DELAY); |
311 | | - } |
312 | | - } |
| 326 | + mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len); |
| 327 | + if (reply) sendFlood(reply, SERVER_RESPONSE_DELAY); |
313 | 328 | } |
314 | 329 | } |
315 | 330 | } |
@@ -361,7 +376,7 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i |
361 | 376 | memcpy(×tamp, data, 4); |
362 | 377 |
|
363 | 378 | if (timestamp > from.last_timestamp) { // prevent replay attacks |
364 | | - int reply_len = handleRequest(from, timestamp, &data[4], len - 4); |
| 379 | + uint8_t reply_len = handleRequest(from.isAdmin(), timestamp, data[4], &data[5], len - 5); |
365 | 380 | if (reply_len == 0) return; // invalid command |
366 | 381 |
|
367 | 382 | from.last_timestamp = timestamp; |
|
0 commit comments