276 lines
6.8 KiB
C
276 lines
6.8 KiB
C
/*
|
|
* Copyright (c) 2018 Lars Thrane A/S
|
|
* SPDX-License-Identifier: GPL-2.0
|
|
*
|
|
* Telnet protocol handling - server
|
|
*/
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "lib/nvt_log.h"
|
|
#include "telnet.h"
|
|
#include "telnet_param.h"
|
|
|
|
|
|
/* Telnet commands */
|
|
static const char *const tn_cmd[] = {
|
|
"SE", "NOP", "MARK", "BRK", "IP", "AO", "AYT", "EC",
|
|
"EL", "GA", "SB", "WILL", "WONT", "DO", "DONT", "IAC"
|
|
};
|
|
|
|
#define OPT_NACK 0 /* Reject (DONT/WONT) */
|
|
#define OPT_IGNORE 1 /* Ignore the command (?) */
|
|
#define OPT_ACK 2 /* Acknowledge (DO /WILL) */
|
|
|
|
/* Telnet option support */
|
|
static const unsigned char tn_opt[256] = {
|
|
/* *INDENT-OFF* */
|
|
[TNO_BINARY] = OPT_ACK,
|
|
[TNO_SUPP_GA] = OPT_ACK,
|
|
// [TNO_TTYP] = OPT_NACK,
|
|
// [TNO_NAWS] = OPT_NACK,
|
|
// [TNO_NEO] = OPT_NACK,
|
|
[TNO_CPCO] = OPT_ACK,
|
|
// [TNO_KERMIT] = OPT_NACK,
|
|
/* *INDENT-ON* */
|
|
};
|
|
|
|
|
|
static void _telnet_handle_req(tn_ctx_t *tcc, int cmd, int opt)
|
|
{
|
|
char buf[3];
|
|
|
|
switch (tn_opt[opt])
|
|
{
|
|
default:
|
|
case OPT_IGNORE:
|
|
return;
|
|
case OPT_ACK:
|
|
cmd = (cmd == TNC_FB_WILL) ? TNC_FD_DO : TNC_FB_WILL;
|
|
break;
|
|
case OPT_NACK:
|
|
cmd = (cmd == TNC_FB_WILL) ? TNC_FE_DONT : TNC_FC_WONT;
|
|
break;
|
|
}
|
|
|
|
buf[0] = TNC_FF_IAC;
|
|
buf[1] = cmd;
|
|
buf[2] = opt;
|
|
DBG("TNo: Op2 %02x %02x: %-4s %d", cmd, opt, tn_cmd[cmd & 0xf], opt);
|
|
tcc->cli_tx(tcc->cctx, buf, 3);
|
|
|
|
if (opt == TNO_CPCO && (cmd == TNC_FB_WILL || cmd == TNC_FD_DO))
|
|
telnet_rfc2217_init(tcc, TNI_ON);
|
|
}
|
|
|
|
void telnet_reply_opt(tn_ctx_t *tcc, int opt, int prm,
|
|
const void *val, int len)
|
|
{
|
|
unsigned char buf[128], *pu = buf;
|
|
const unsigned char *pv;
|
|
int i;
|
|
|
|
*pu++ = TNC_FF_IAC;
|
|
*pu++ = TNC_FA_SB;
|
|
|
|
*pu++ = opt;
|
|
*pu++ = prm;
|
|
if (len > 0 && len <= 32)
|
|
{
|
|
pv = val;
|
|
for (i = 0; i < len; i++)
|
|
{
|
|
*pu++ = *pv;
|
|
if (*pv++ == TNC_FF_IAC)
|
|
*pu++ = TNC_FF_IAC;
|
|
}
|
|
}
|
|
|
|
*pu++ = TNC_FF_IAC;
|
|
*pu++ = TNC_F0_SE;
|
|
|
|
len = pu - buf;
|
|
DBG2_BUF("opto", buf, len);
|
|
tcc->cli_tx(tcc->cctx, buf, len);
|
|
}
|
|
|
|
static void _telnet_handle_opt(tn_ctx_t *tcc, int opt,
|
|
unsigned char *pu, int nd)
|
|
{
|
|
DBG2_BUF("opti", pu, nd);
|
|
|
|
switch (opt)
|
|
{
|
|
default:
|
|
nvt_log(LOG_WARNING, "Unexpected SB: Opt = %d", opt);
|
|
break;
|
|
case TNO_CPCO: /* RFC 2217 */
|
|
tcc->mode_rfc2217 = 1;
|
|
telnet_rfc2217_handle_opt(tcc, opt, pu, nd);
|
|
break;
|
|
}
|
|
}
|
|
|
|
int telnet_rx(tn_ctx_t *tcc, char *buf, int *plen)
|
|
{
|
|
char *s, *p;
|
|
unsigned char *pu;
|
|
int len, rem;
|
|
int np, nd;
|
|
int b1, b2;
|
|
|
|
/* Drop telnet processing entirely if we haven't seen any telnet
|
|
* negotiation in the beginning */
|
|
if (tcc->bytes_rx >= 32 && !tcc->mode_telnet)
|
|
return 0;
|
|
|
|
len = *plen;
|
|
tcc->bytes_rx += len;
|
|
|
|
if (len < (int)tcc->off_rx)
|
|
tcc->off_rx = 0;
|
|
|
|
for (s = buf + tcc->off_rx, rem = len - tcc->off_rx; rem > 0; s = p)
|
|
{
|
|
DBG2_BUF("****", s, rem);
|
|
p = memchr(s, TNC_FF_IAC, rem);
|
|
if (!p)
|
|
break;
|
|
|
|
/* We have IAC (= 0xff) */
|
|
np = rem - (p - s); /* N. chars available incl. IAC */
|
|
if (np < 2)
|
|
goto tn_more;
|
|
|
|
pu = (unsigned char *)p;
|
|
b1 = pu[1];
|
|
switch (b1)
|
|
{
|
|
default:
|
|
case TNC_F0_SE:
|
|
case TNC_F1_NOP:
|
|
case TNC_F2_MARK:
|
|
case TNC_F3_BRK:
|
|
case TNC_F4_IP:
|
|
case TNC_F5_AO:
|
|
case TNC_F6_AYT:
|
|
case TNC_F7_EC:
|
|
case TNC_F8_EL:
|
|
case TNC_F9_GA:
|
|
DBG("TNi: Op1 %02x : %s", b1, tn_cmd[b1 & 0xf]);
|
|
nd = 2; /* Drop 2 bytes */
|
|
goto tn_drop;
|
|
|
|
case TNC_FB_WILL:
|
|
case TNC_FC_WONT:
|
|
case TNC_FD_DO:
|
|
case TNC_FE_DONT:
|
|
if (np < 3)
|
|
goto tn_more; /* Need more */
|
|
tcc->mode_telnet = 1; /* Any such sequence activates TN */
|
|
b2 = pu[2];
|
|
DBG("TNi: Op2 %02x %02x: %-4s %d", b1, b2, tn_cmd[b1 & 0xf], b2);
|
|
if (b1 == TNC_FB_WILL || b1 == TNC_FD_DO)
|
|
_telnet_handle_req(tcc, b1, b2);
|
|
nd = 3; /* Drop 3 bytes */
|
|
goto tn_drop;
|
|
|
|
case TNC_FA_SB:
|
|
for (nd = 2; nd < np - 1; nd++)
|
|
{
|
|
if (pu[nd] == TNC_FF_IAC && pu[nd + 1] == TNC_F0_SE)
|
|
{
|
|
nd -= 2;
|
|
if (nd <= 0)
|
|
break;
|
|
b2 = pu[2];
|
|
tcc->mode_telnet = 1; /* Any such sequence activates TN */
|
|
DBG("TNi: Op2 %02x %02x: %-4s %d",
|
|
b1, b2, tn_cmd[b1 & 0xf], b2);
|
|
_telnet_handle_opt(tcc, b2, pu + 3, nd - 1);
|
|
nd += 4;
|
|
goto tn_drop;
|
|
}
|
|
}
|
|
if (np < 16)
|
|
goto tn_more; /* Not done */
|
|
/* This does not look right */
|
|
nvt_log_buf(LOG_WARNING, "tnrx", p, np);
|
|
rem = 0; /* Quit loop */
|
|
break;
|
|
|
|
case TNC_FF_IAC:
|
|
p++;
|
|
nd = 1; /* Drop 1 bytes */
|
|
goto tn_drop;
|
|
|
|
tn_drop:
|
|
if (!tcc->mode_telnet)
|
|
goto tn_break;
|
|
DBG2_BUF("drop", p, nd);
|
|
rem -= nd + (p - s);
|
|
len -= nd;
|
|
memmove(p, p + nd, rem);
|
|
break;
|
|
|
|
tn_break:
|
|
rem = 0; /* Quit loop */
|
|
break;
|
|
|
|
tn_more: /* Request more data */
|
|
tcc->off_rx = len - np;
|
|
*plen = len;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
tcc->off_rx = 0;
|
|
*plen = len;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int telnet_tx(tn_ctx_t *tcc, const char *buf, int len)
|
|
{
|
|
const char *s, *p;
|
|
int rem, nd;
|
|
|
|
for (s = buf, rem = len; rem > 0; s = p)
|
|
{
|
|
if (tcc->mode_telnet)
|
|
p = memchr(s, TNC_FF_IAC, rem);
|
|
else
|
|
p = NULL;
|
|
if (!p)
|
|
{
|
|
/* Send the rest */
|
|
nd = rem;
|
|
tcc->cli_tx(tcc->cctx, s, nd);
|
|
}
|
|
else
|
|
{
|
|
nd = p - s + 1;
|
|
tcc->cli_tx(tcc->cctx, s, nd);
|
|
tcc->cli_tx(tcc->cctx, p, 1); /* One TNC_FF_IAC */
|
|
p++;
|
|
}
|
|
rem -= nd;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
tn_ctx_t *telnet_ctx_init(void *cctx, cli_tx_f *ftx, cli_ss_f *fss)
|
|
{
|
|
tn_ctx_t *tcc;
|
|
|
|
tcc = calloc(1, sizeof(tn_ctx_t));
|
|
tcc->cctx = cctx;
|
|
tcc->cli_tx = ftx; /* Write to client */
|
|
tcc->cli_ss = fss; /* Set state (line/modem) */
|
|
|
|
telnet_rfc2217_init(tcc, TNI_INIT);
|
|
|
|
return tcc;
|
|
}
|