diff --git a/code/espurna/config/general.h b/code/espurna/config/general.h index ef8742f8..bbe5c2c8 100644 --- a/code/espurna/config/general.h +++ b/code/espurna/config/general.h @@ -130,6 +130,13 @@ #define TELNET_SERVER TELNET_SERVER_ASYNC // Can be either TELNET_SERVER_ASYNC (using ESPAsyncTCP) or TELNET_SERVER_WIFISERVER (using WiFiServer) #endif +// Enable this flag to add support for reverse telnet (+800 bytes) +// This is useful to telnet to a device behind a NAT or firewall +// To use this feature, start a listen server on a publicly reachable host with e.g. "ncat -vlp " and use the MQTT reverse telnet command to connect +#ifndef TELNET_REVERSE_SUPPORT +#define TELNET_REVERSE_SUPPORT 0 +#endif + //------------------------------------------------------------------------------ // TERMINAL //------------------------------------------------------------------------------ @@ -1068,6 +1075,7 @@ #define MQTT_TOPIC_IRIN "irin" #define MQTT_TOPIC_IROUT "irout" #define MQTT_TOPIC_OTA "ota" +#define MQTT_TOPIC_TELNET_REVERSE "telnet_reverse" // Light module #define MQTT_TOPIC_CHANNEL "channel" diff --git a/code/espurna/telnet.ino b/code/espurna/telnet.ino index 3ab088d0..3ab6a09d 100644 --- a/code/espurna/telnet.ino +++ b/code/espurna/telnet.ino @@ -43,6 +43,58 @@ void _telnetWebSocketOnConnected(JsonObject& root) { #endif +#if TELNET_REVERSE_SUPPORT + void _telnetReverse(const char * host, uint16_t port) { + DEBUG_MSG_P(PSTR("[TELNET] Connecting to reverse telnet on %s:%d\n"), host, port); + + unsigned char i; + for (i = 0; i < TELNET_MAX_CLIENTS; i++) { + if (!_telnetClients[i] || !_telnetClients[i]->connected()) { + #if TELNET_SERVER == TELNET_SERVER_WIFISERVER + _telnetClients[i] = std::make_unique(); + #else // TELNET_SERVER_ASYNC + _telnetClients[i] = std::make_unique(); + #endif + + if (_telnetClients[i]->connect(host, port)) { + return _telnetNotifyConnected(i); + } else { + DEBUG_MSG_P(PSTR("[TELNET] Error connecting reverse telnet\n")); + return _telnetDisconnect(i); + } + } + } + + //no free/disconnected spot so reject + if (i == TELNET_MAX_CLIENTS) { + DEBUG_MSG_P(PSTR("[TELNET] Failed too connect - too many clients connected\n")); + } + } + + #if MQTT_SUPPORT + void _telnetReverseMQTTCallback(unsigned int type, const char * topic, const char * payload) { + if (type == MQTT_CONNECT_EVENT) { + mqttSubscribe(MQTT_TOPIC_TELNET_REVERSE); + } else if (type == MQTT_MESSAGE_EVENT) { + String t = mqttMagnitude((char *) topic); + + if (t.equals(MQTT_TOPIC_TELNET_REVERSE)) { + String pl = String(payload); + int col = pl.indexOf(':'); + if (col != -1) { + String host = pl.substring(0, col); + uint16_t port = pl.substring(col + 1).toInt(); + + _telnetReverse(host.c_str(), port); + } else { + DEBUG_MSG_P(PSTR("[TELNET] Incorrect reverse telnet value given, use the form \"host:ip\"")); + } + } + } + } + #endif +#endif + void _telnetDisconnect(unsigned char clientId) { // ref: we are called from onDisconnect, async is already stopped #if TELNET_SERVER == TELNET_SERVER_WIFISERVER @@ -127,6 +179,28 @@ void _telnetNotifyConnected(unsigned char i) { DEBUG_MSG_P(PSTR("[TELNET] Client #%u connected\n"), i); + #if TELNET_SERVER == TELNET_SERVER_ASYNC + _telnetClients[i]->onAck([i](void *s, AsyncClient *c, size_t len, uint32_t time) { + }, 0); + + _telnetClients[i]->onData([i](void *s, AsyncClient *c, void *data, size_t len) { + _telnetData(i, data, len); + }, 0); + + _telnetClients[i]->onDisconnect([i](void *s, AsyncClient *c) { + _telnetDisconnect(i); + }, 0); + + _telnetClients[i]->onError([i](void *s, AsyncClient *c, int8_t error) { + DEBUG_MSG_P(PSTR("[TELNET] Error %s (%d) on client #%u\n"), c->errorToString(error), error, i); + }, 0); + + _telnetClients[i]->onTimeout([i](void *s, AsyncClient *c, uint32_t time) { + DEBUG_MSG_P(PSTR("[TELNET] Timeout on client #%u at %lu\n"), i, time); + c->close(); + }, 0); + #endif + // If there is no terminal support automatically dump info and crash data #if TERMINAL_SUPPORT == 0 info(); @@ -203,7 +277,7 @@ void _telnetLoop() { char data[TERMINAL_BUFFER_SIZE]; size_t len = _telnetClients[i]->available(); unsigned int r = _telnetClients[i]->readBytes(data, min(sizeof(data), len)); - + _telnetData(i, data, r); } } @@ -238,26 +312,6 @@ void _telnetNewClient(AsyncClient* client) { _telnetClients[i] = std::unique_ptr(client); - _telnetClients[i]->onAck([i](void *s, AsyncClient *c, size_t len, uint32_t time) { - }, 0); - - _telnetClients[i]->onData([i](void *s, AsyncClient *c, void *data, size_t len) { - _telnetData(i, data, len); - }, 0); - - _telnetClients[i]->onDisconnect([i](void *s, AsyncClient *c) { - _telnetDisconnect(i); - }, 0); - - _telnetClients[i]->onError([i](void *s, AsyncClient *c, int8_t error) { - DEBUG_MSG_P(PSTR("[TELNET] Error %s (%d) on client #%u\n"), c->errorToString(error), error, i); - }, 0); - - _telnetClients[i]->onTimeout([i](void *s, AsyncClient *c, uint32_t time) { - DEBUG_MSG_P(PSTR("[TELNET] Timeout on client #%u at %lu\n"), i, time); - c->close(); - }, 0); - _telnetNotifyConnected(i); return; } @@ -312,6 +366,27 @@ void telnetSetup() { .onKeyCheck(_telnetWebSocketOnKeyCheck); #endif + #if TELNET_REVERSE_SUPPORT + #if MQTT_SUPPORT + mqttRegister(_telnetReverseMQTTCallback); + #endif + + #if TERMINAL_SUPPORT + terminalRegisterCommand(F("TELNET.REVERSE"), [](Embedis* e) { + if (e->argc < 3) { + terminalError(F("Wrong arguments. Usage: TELNET.REVERSE ")); + return; + } + + String host = String(e->argv[1]); + uint16_t port = String(e->argv[2]).toInt(); + + terminalOK(); + _telnetReverse(host.c_str(), port); + }); + #endif + #endif + espurnaRegisterReload(_telnetConfigure); _telnetConfigure();