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

/*
* 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 */
};