/* * libwebsockets - small server side websockets and web server implementation * * Copyright (C) 2010 - 2019 Andy Green * * 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 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 */ };