Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for reverse telnet #1920

Merged
merged 4 commits into from
Oct 9, 2019
Merged
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
8 changes: 8 additions & 0 deletions code/espurna/config/general.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <port>" and use the MQTT reverse telnet command to connect
#ifndef TELNET_REVERSE_SUPPORT
#define TELNET_REVERSE_SUPPORT 0
#endif

//------------------------------------------------------------------------------
// TERMINAL
//------------------------------------------------------------------------------
Expand Down Expand Up @@ -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"
Expand Down
137 changes: 103 additions & 34 deletions code/espurna/telnet.ino
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ Parts of the code have been borrowed from Thomas Sarlandie's NetServer

#if TELNET_SUPPORT

#define TELNET_IAC 0xFF
#define TELNET_XEOF 0xEC

#if TELNET_SERVER == TELNET_SERVER_WIFISERVER
#include <ESP8266WiFi.h>
WiFiServer _telnetServer = WiFiServer(TELNET_PORT);
Expand All @@ -20,8 +23,6 @@ Parts of the code have been borrowed from Thomas Sarlandie's NetServer
std::unique_ptr<AsyncClient> _telnetClients[TELNET_MAX_CLIENTS];
#endif

bool _telnetFirst = true;

bool _telnetAuth = TELNET_AUTHENTICATION;
bool _telnetClientsAuth[TELNET_MAX_CLIENTS];

Expand All @@ -42,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<WiFiClient>();
#else // TELNET_SERVER_ASYNC
_telnetClients[i] = std::make_unique<AsyncClient>();
#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
Expand Down Expand Up @@ -81,21 +134,15 @@ bool _telnetWrite(unsigned char clientId, const char * message) {
}

void _telnetData(unsigned char clientId, void *data, size_t len) {
// Skip first message since it's always garbage
if (_telnetFirst) {
_telnetFirst = false;
return;
}

// Capture close connection
char * p = (char *) data;

// C-d is sent as two bytes (sometimes repeating)
if (len >= 2) {
if ((p[0] == 0xFF) && (p[1] == 0xEC)) {
if ((len >= 2) && (p[0] == TELNET_IAC)) {
// C-d is sent as two bytes (sometimes repeating)
if (p[1] == TELNET_XEOF) {
_telnetDisconnect(clientId);
return;
}
return; // Ignore telnet negotiation
}

if ((strncmp(p, "close", 5) == 0) || (strncmp(p, "quit", 4) == 0)) {
Expand Down Expand Up @@ -132,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();
Expand All @@ -153,7 +222,6 @@ void _telnetNotifyConnected(unsigned char i) {
}
#endif

_telnetFirst = true;
wifiReconnectCheck();

}
Expand Down Expand Up @@ -209,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);
}
}
Expand Down Expand Up @@ -244,26 +312,6 @@ void _telnetNewClient(AsyncClient* client) {

_telnetClients[i] = std::unique_ptr<AsyncClient>(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;
}
Expand Down Expand Up @@ -318,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 <host> <port>"));
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();

Expand Down