You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
264 lines
6.3 KiB
264 lines
6.3 KiB
/*
|
|
* libwebsockets - small server side websockets and web server implementation
|
|
*
|
|
* Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to
|
|
* deal in the Software without restriction, including without limitation the
|
|
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
* sell copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
* IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include "libwebsockets.h"
|
|
#include "lws-ssh.h"
|
|
|
|
#include <string.h>
|
|
|
|
struct per_vhost_data__telnet {
|
|
struct lws_context *context;
|
|
struct lws_vhost *vhost;
|
|
const struct lws_protocols *protocol;
|
|
struct per_session_data__telnet *live_pss_list;
|
|
const struct lws_ssh_ops *ops;
|
|
};
|
|
|
|
struct per_session_data__telnet {
|
|
struct per_session_data__telnet *next;
|
|
struct per_vhost_data__telnet *vhd;
|
|
uint32_t rx_tail;
|
|
void *priv;
|
|
|
|
uint32_t initial:1;
|
|
|
|
char state;
|
|
uint8_t cmd;
|
|
};
|
|
|
|
enum {
|
|
LTS_BINARY_XMIT,
|
|
LTS_ECHO,
|
|
LTS_SUPPRESS_GA,
|
|
|
|
|
|
LTSC_SUBOPT_END = 240,
|
|
LTSC_BREAK = 243,
|
|
LTSC_SUBOPT_START = 250,
|
|
LTSC_WILL = 251,
|
|
LTSC_WONT,
|
|
LTSC_DO,
|
|
LTSC_DONT,
|
|
LTSC_IAC,
|
|
|
|
LTST_WAIT_IAC = 0,
|
|
LTST_GOT_IAC,
|
|
LTST_WAIT_OPT,
|
|
};
|
|
|
|
static int
|
|
telnet_ld(struct per_session_data__telnet *pss, uint8_t c)
|
|
{
|
|
switch (pss->state) {
|
|
case LTST_WAIT_IAC:
|
|
if (c == LTSC_IAC) {
|
|
pss->state = LTST_GOT_IAC;
|
|
return 0;
|
|
}
|
|
return 1;
|
|
|
|
case LTST_GOT_IAC:
|
|
pss->state = LTST_WAIT_IAC;
|
|
|
|
switch (c) {
|
|
case LTSC_BREAK:
|
|
return 0;
|
|
case LTSC_WILL:
|
|
case LTSC_WONT:
|
|
case LTSC_DO:
|
|
case LTSC_DONT:
|
|
pss->cmd = c;
|
|
pss->state = LTST_WAIT_OPT;
|
|
return 0;
|
|
case LTSC_IAC:
|
|
return 1; /* double IAC */
|
|
}
|
|
return 0; /* ignore unknown */
|
|
|
|
case LTST_WAIT_OPT:
|
|
lwsl_notice(" tld: cmd %d: opt %d\n", pss->cmd, c);
|
|
pss->state = LTST_WAIT_IAC;
|
|
return 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static uint8_t init[] = {
|
|
LTSC_IAC, LTSC_WILL, 3,
|
|
LTSC_IAC, LTSC_WILL, 1,
|
|
LTSC_IAC, LTSC_DONT, 1,
|
|
LTSC_IAC, LTSC_DO, 0
|
|
};
|
|
|
|
static int
|
|
lws_callback_raw_telnet(struct lws *wsi, enum lws_callback_reasons reason,
|
|
void *user, void *in, size_t len)
|
|
{
|
|
struct per_session_data__telnet *pss =
|
|
(struct per_session_data__telnet *)user, **p;
|
|
struct per_vhost_data__telnet *vhd =
|
|
(struct per_vhost_data__telnet *)
|
|
lws_protocol_vh_priv_get(lws_get_vhost(wsi),
|
|
lws_get_protocol(wsi));
|
|
const struct lws_protocol_vhost_options *pvo =
|
|
(const struct lws_protocol_vhost_options *)in;
|
|
int n, m;
|
|
uint8_t buf[LWS_PRE + 800], *pu = in;
|
|
|
|
switch ((int)reason) {
|
|
case LWS_CALLBACK_PROTOCOL_INIT:
|
|
vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
|
|
lws_get_protocol(wsi),
|
|
sizeof(struct per_vhost_data__telnet));
|
|
vhd->context = lws_get_context(wsi);
|
|
vhd->protocol = lws_get_protocol(wsi);
|
|
vhd->vhost = lws_get_vhost(wsi);
|
|
|
|
while (pvo) {
|
|
if (!strcmp(pvo->name, "ops"))
|
|
vhd->ops = (const struct lws_ssh_ops *)pvo->value;
|
|
|
|
pvo = pvo->next;
|
|
}
|
|
|
|
if (!vhd->ops) {
|
|
lwsl_err("telnet pvo \"ops\" is mandatory\n");
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case LWS_CALLBACK_RAW_ADOPT:
|
|
pss->next = vhd->live_pss_list;
|
|
vhd->live_pss_list = pss;
|
|
pss->vhd = vhd;
|
|
pss->state = LTST_WAIT_IAC;
|
|
pss->initial = 0;
|
|
if (vhd->ops->channel_create)
|
|
vhd->ops->channel_create(wsi, &pss->priv);
|
|
lws_callback_on_writable(wsi);
|
|
break;
|
|
|
|
case LWS_CALLBACK_RAW_CLOSE:
|
|
p = &vhd->live_pss_list;
|
|
|
|
while (*p) {
|
|
if ((*p) == pss) {
|
|
if (vhd->ops->channel_destroy)
|
|
vhd->ops->channel_destroy(pss->priv);
|
|
*p = pss->next;
|
|
continue;
|
|
}
|
|
p = &((*p)->next);
|
|
}
|
|
break;
|
|
|
|
case LWS_CALLBACK_RAW_RX:
|
|
n = 0;
|
|
|
|
/* this stuff is coming in telnet line discipline, we
|
|
* have to strip IACs and process IAC repeats */
|
|
|
|
while (len--) {
|
|
if (telnet_ld(pss, *pu))
|
|
buf[n++] = *pu++;
|
|
else
|
|
pu++;
|
|
|
|
if (n > 100 || !len)
|
|
pss->vhd->ops->rx(pss->priv, wsi, buf, n);
|
|
}
|
|
break;
|
|
|
|
case LWS_CALLBACK_RAW_WRITEABLE:
|
|
n = 0;
|
|
if (!pss->initial) {
|
|
memcpy(buf + LWS_PRE, init, sizeof(init));
|
|
|
|
n = sizeof(init);
|
|
pss->initial = 1;
|
|
} else {
|
|
/* bring any waiting tx into second half of buffer
|
|
* restrict how much we can send to 1/4 of the buffer,
|
|
* because we have to apply telnet line discipline...
|
|
* in the worst case of all 0xff, doubling the size
|
|
*/
|
|
pu = buf + LWS_PRE + 400;
|
|
m = (int)pss->vhd->ops->tx(pss->priv, LWS_STDOUT, pu,
|
|
((int)sizeof(buf) - LWS_PRE - n - 401) / 2);
|
|
|
|
/*
|
|
* apply telnet line discipline and copy into place
|
|
* in output buffer
|
|
*/
|
|
while (m--) {
|
|
if (*pu == 0xff)
|
|
buf[LWS_PRE + n++] = 0xff;
|
|
buf[LWS_PRE + n++] = *pu++;
|
|
}
|
|
}
|
|
if (n > 0) {
|
|
m = lws_write(wsi, (unsigned char *)buf + LWS_PRE, n,
|
|
LWS_WRITE_HTTP);
|
|
if (m < 0) {
|
|
lwsl_err("ERROR %d writing to di socket\n", m);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (vhd->ops->tx_waiting(&pss->priv))
|
|
lws_callback_on_writable(wsi);
|
|
break;
|
|
|
|
case LWS_CALLBACK_SSH_UART_SET_RXFLOW:
|
|
/*
|
|
* this is sent to set rxflow state on any connections that
|
|
* sink on a particular uart. The uart index affected is in len
|
|
*
|
|
* More than one protocol may sink to the same uart, and the
|
|
* protocol may select the uart itself, eg, in the URL used
|
|
* to set up the connection.
|
|
*/
|
|
lws_rx_flow_control(wsi, len & 1);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
const struct lws_protocols protocols_telnet[] = {
|
|
{
|
|
"lws-telnetd-base",
|
|
lws_callback_raw_telnet,
|
|
sizeof(struct per_session_data__telnet),
|
|
1024, 0, NULL, 900
|
|
},
|
|
{ NULL, NULL, 0, 0, 0, NULL, 0 } /* terminator */
|
|
};
|
|
|
|
|