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