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

Telnet server: alternative to ESPAsyncTCP #1799

Merged
merged 15 commits into from
Jul 12, 2019
4 changes: 4 additions & 0 deletions code/espurna/config/general.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@
#define TELNET_PORT 23 // Port to listen to telnet clients
#define TELNET_MAX_CLIENTS 1 // Max number of concurrent telnet clients

#ifndef TELNET_SERVER
#define TELNET_SERVER TELNET_SERVER_ASYNC // Can be either TELNET_SERVER_ASYNC (using ESPAsyncTCP) or TELNET_SERVER_WIFICLIENT (using WiFiClient)
#endif

//------------------------------------------------------------------------------
// TERMINAL
//------------------------------------------------------------------------------
Expand Down
167 changes: 132 additions & 35 deletions code/espurna/telnet.ino
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,21 @@ Parts of the code have been borrowed from Thomas Sarlandie's NetServer

*/

#define TELNET_SERVER_ASYNC 0
#define TELNET_SERVER_WIFICLIENT 1
Niek marked this conversation as resolved.
Show resolved Hide resolved

#if TELNET_SUPPORT

#include <ESPAsyncTCP.h>
#if TELNET_SERVER == TELNET_SERVER_WIFICLIENT
Niek marked this conversation as resolved.
Show resolved Hide resolved
#include <ESP8266WiFi.h>
WiFiServer _telnetServer = WiFiServer(TELNET_PORT);
WiFiClient _telnetClients[TELNET_MAX_CLIENTS];
#else
#include <ESPAsyncTCP.h>
AsyncServer _telnetServer = AsyncServer(TELNET_PORT);
AsyncClient _telnetClients[TELNET_MAX_CLIENTS];
#endif

AsyncServer * _telnetServer;
AsyncClient * _telnetClients[TELNET_MAX_CLIENTS];
bool _telnetFirst = true;

bool _telnetAuth = TELNET_AUTHENTICATION;
Expand All @@ -38,16 +47,18 @@ void _telnetWebSocketOnSend(JsonObject& root) {
#endif

void _telnetDisconnect(unsigned char clientId) {
_telnetClients[clientId]->free();
delete _telnetClients[clientId];
_telnetClients[clientId] = NULL;
#if TELNET_SERVER == TELNET_SERVER_WIFICLIENT
_telnetClients[clientId].stop();
#else
_telnetClients[clientId].free();
Niek marked this conversation as resolved.
Show resolved Hide resolved
#endif
wifiReconnectCheck();
DEBUG_MSG_P(PSTR("[TELNET] Client #%d disconnected\n"), clientId);
}

bool _telnetWrite(unsigned char clientId, const char *data, size_t len) {
if (_telnetClients[clientId] && _telnetClients[clientId]->connected()) {
return (_telnetClients[clientId]->write(data, len) > 0);
if (_telnetClients[clientId].connected()) {
return (_telnetClients[clientId].write(data, len) > 0);
}
return false;
}
Expand All @@ -74,7 +85,6 @@ 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;
Expand All @@ -87,13 +97,21 @@ void _telnetData(unsigned char clientId, void *data, size_t len) {
// C-d is sent as two bytes (sometimes repeating)
if (len >= 2) {
if ((p[0] == 0xFF) && (p[1] == 0xEC)) {
_telnetClients[clientId]->close(true);
#if TELNET_SERVER == TELNET_SERVER_WIFICLIENT
_telnetClients[clientId].stop();
#else
_telnetClients[clientId].close(true);
#endif
return;
}
}

if ((strncmp(p, "close", 5) == 0) || (strncmp(p, "quit", 4) == 0)) {
_telnetClients[clientId]->close();
#if TELNET_SERVER == TELNET_SERVER_WIFICLIENT
_telnetClients[clientId].stop();
#else
_telnetClients[clientId].close(true);
#endif
return;
}

Expand All @@ -108,10 +126,10 @@ void _telnetData(unsigned char clientId, void *data, size_t len) {
String password = getAdminPass();
if (strncmp(p, password.c_str(), password.length()) == 0) {
DEBUG_MSG_P(PSTR("[TELNET] Client #%d authenticated\n"), clientId);
_telnetWrite(clientId, "Welcome!\n");
_telnetWrite(clientId, "Password correct, welcome!\n");
_telnetClientsAuth[clientId] = true;
} else {
_telnetWrite(clientId, "Password: ");
_telnetWrite(clientId, "Password (try again): ");
}
return;
}
Expand All @@ -120,13 +138,84 @@ void _telnetData(unsigned char clientId, void *data, size_t len) {
#if TERMINAL_SUPPORT
terminalInject(data, len);
#endif

}

void _telnetNewClient(AsyncClient *client) {
#if TELNET_SERVER == TELNET_SERVER_WIFICLIENT
void _telnetLoop() {
if (_telnetServer.hasClient()) {
int i;

for (i = 0; i < TELNET_MAX_CLIENTS; i++) {
if (!_telnetClients[i].connected()) {
_telnetClients[i] = _telnetServer.available();

if (_telnetClients[i].localIP() != WiFi.softAPIP()) {
// Telnet is always available for the ESPurna Core image
#ifdef ESPURNA_CORE
bool telnetSTA = true;
#else
bool telnetSTA = getSetting("telnetSTA", TELNET_STA).toInt() == 1;
#endif

if (!telnetSTA) {
DEBUG_MSG_P(PSTR("[TELNET] Rejecting - Only local connections\n"));
_telnetServer.available().println("Only local connections allowed");
_telnetDisconnect(i);
return;
}
}

DEBUG_MSG_P(PSTR("[TELNET] Client #%u connected\n"), i);

// If there is no terminal support automatically dump info and crash data
#if TERMINAL_SUPPORT == 0
info();
wifiDebug();
crashDump();
crashClear();
#endif

#ifdef ESPURNA_CORE
_telnetClientsAuth[i] = true;
#else
_telnetClientsAuth[i] = !_telnetAuth;
if (_telnetAuth) {
if (getAdminPass().length() != 0) {
_telnetWrite(i, "Password: ");
} else {
_telnetClientsAuth[i] = true;
}
}
#endif

_telnetFirst = true;
wifiReconnectCheck();
break;
}
}

if (client->localIP() != WiFi.softAPIP()) {
//no free/disconnected spot so reject
if (i == TELNET_MAX_CLIENTS) {
DEBUG_MSG_P(PSTR("[TELNET] Rejecting - Too many connections\n"));
}
}

// Read data from clients
for (int i = 0; i < TELNET_MAX_CLIENTS; i++) {
while (_telnetClients[i].available()) {
char data[512];
size_t len = _telnetClients[i].available();
unsigned int r = _telnetClients[i].readBytes(data, min(sizeof(data), len));

_telnetData(i, data, r);
}
}
}
#else
void _telnetNewClient(void *cl) {
AsyncClient *client = (AsyncClient *)cl; // in order to avoid using AsyncClient in function signature

if (client->localIP() != WiFi.softAPIP()) {
// Telnet is always available for the ESPurna Core image
#ifdef ESPURNA_CORE
bool telnetSTA = true;
Expand All @@ -143,31 +232,30 @@ void _telnetNewClient(AsyncClient *client) {
client->close(true);
return;
}

}

for (unsigned char i = 0; i < TELNET_MAX_CLIENTS; i++) {

if (!_telnetClients[i] || !_telnetClients[i]->connected()) {
if (_telnetClients[i].disconnected()) {

_telnetClients[i] = client;
_telnetClients[i] = *client;
mcspr marked this conversation as resolved.
Show resolved Hide resolved

client->onAck([i](void *s, AsyncClient *c, size_t len, uint32_t time) {
_telnetClients[i].onAck([i](void *s, AsyncClient *c, size_t len, uint32_t time) {
}, 0);

client->onData([i](void *s, AsyncClient *c, void *data, size_t len) {
_telnetClients[i].onData([i](void *s, AsyncClient *c, void *data, size_t len) {
_telnetData(i, data, len);
}, 0);

client->onDisconnect([i](void *s, AsyncClient *c) {
_telnetClients[i].onDisconnect([i](void *s, AsyncClient *c) {
_telnetDisconnect(i);
}, 0);

client->onError([i](void *s, AsyncClient *c, int8_t error) {
_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);

client->onTimeout([i](void *s, AsyncClient *c, uint32_t time) {
_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);
Expand All @@ -186,14 +274,19 @@ void _telnetNewClient(AsyncClient *client) {
_telnetClientsAuth[i] = true;
#else
_telnetClientsAuth[i] = !_telnetAuth;
if (_telnetAuth) _telnetWrite(i, "Password: ");
if (_telnetAuth) {
if (getAdminPass().length() != 0) {
Niek marked this conversation as resolved.
Show resolved Hide resolved
_telnetWrite(i, "Password: ");
} else {
_telnetClientsAuth[i] = true;
}
}
#endif

_telnetFirst = true;
wifiReconnectCheck();

return;

}

}
Expand All @@ -204,16 +297,16 @@ void _telnetNewClient(AsyncClient *client) {
delete c;
});
client->close(true);

}
#endif

// -----------------------------------------------------------------------------
// Public API
// -----------------------------------------------------------------------------

bool telnetConnected() {
for (unsigned char i = 0; i < TELNET_MAX_CLIENTS; i++) {
if (_telnetClients[i] && _telnetClients[i]->connected()) return true;
if (_telnetClients[i].connected()) return true;
}
return false;
}
Expand All @@ -228,12 +321,16 @@ void _telnetConfigure() {
}

void telnetSetup() {

_telnetServer = new AsyncServer(TELNET_PORT);
_telnetServer->onClient([](void *s, AsyncClient* c) {
_telnetNewClient(c);
}, 0);
_telnetServer->begin();
#if TELNET_SERVER == TELNET_SERVER_WIFICLIENT
espurnaRegisterLoop(_telnetLoop);
_telnetServer.setNoDelay(true);
_telnetServer.begin();
#else
_telnetServer.onClient([](void *s, AsyncClient* c) {
_telnetNewClient(c);
}, 0);
_telnetServer.begin();
#endif

#if WEB_SUPPORT
wsOnSendRegister(_telnetWebSocketOnSend);
Expand All @@ -247,4 +344,4 @@ void telnetSetup() {

}

#endif // TELNET_SUPPORT
#endif // TELNET_SUPPORT