1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-09 17:38:00 +00:00
putty-source/telnet.c
Simon Tatham 3214563d8e Convert a lot of 'int' variables to 'bool'.
My normal habit these days, in new code, is to treat int and bool as
_almost_ completely separate types. I'm still willing to use C's
implicit test for zero on an integer (e.g. 'if (!blob.len)' is fine,
no need to spell it out as blob.len != 0), but generally, if a
variable is going to be conceptually a boolean, I like to declare it
bool and assign to it using 'true' or 'false' rather than 0 or 1.

PuTTY is an exception, because it predates the C99 bool, and I've
stuck to its existing coding style even when adding new code to it.
But it's been annoying me more and more, so now that I've decided C99
bool is an acceptable thing to require from our toolchain in the first
place, here's a quite thorough trawl through the source doing
'boolification'. Many variables and function parameters are now typed
as bool rather than int; many assignments of 0 or 1 to those variables
are now spelled 'true' or 'false'.

I managed this thorough conversion with the help of a custom clang
plugin that I wrote to trawl the AST and apply heuristics to point out
where things might want changing. So I've even managed to do a decent
job on parts of the code I haven't looked at in years!

To make the plugin's work easier, I pushed platform front ends
generally in the direction of using standard 'bool' in preference to
platform-specific boolean types like Windows BOOL or GTK's gboolean;
I've left the platform booleans in places they _have_ to be for the
platform APIs to work right, but variables only used by my own code
have been converted wherever I found them.

In a few places there are int values that look very like booleans in
_most_ of the places they're used, but have a rarely-used third value,
or a distinction between different nonzero values that most users
don't care about. In these cases, I've _removed_ uses of 'true' and
'false' for the return values, to emphasise that there's something
more subtle going on than a simple boolean answer:
 - the 'multisel' field in dialog.h's list box structure, for which
   the GTK front end in particular recognises a difference between 1
   and 2 but nearly everything else treats as boolean
 - the 'urgent' parameter to plug_receive, where 1 vs 2 tells you
   something about the specific location of the urgent pointer, but
   most clients only care about 0 vs 'something nonzero'
 - the return value of wc_match, where -1 indicates a syntax error in
   the wildcard.
 - the return values from SSH-1 RSA-key loading functions, which use
   -1 for 'wrong passphrase' and 0 for all other failures (so any
   caller which already knows it's not loading an _encrypted private_
   key can treat them as boolean)
 - term->esc_query, and the 'query' parameter in toggle_mode in
   terminal.c, which _usually_ hold 0 for ESC[123h or 1 for ESC[?123h,
   but can also hold -1 for some other intervening character that we
   don't support.

In a few places there's an integer that I haven't turned into a bool
even though it really _can_ only take values 0 or 1 (and, as above,
tried to make the call sites consistent in not calling those values
true and false), on the grounds that I thought it would make it more
confusing to imply that the 0 value was in some sense 'negative' or
bad and the 1 positive or good:
 - the return value of plug_accepting uses the POSIXish convention of
   0=success and nonzero=error; I think if I made it bool then I'd
   also want to reverse its sense, and that's a job for a separate
   piece of work.
 - the 'screen' parameter to lineptr() in terminal.c, where 0 and 1
   represent the default and alternate screens. There's no obvious
   reason why one of those should be considered 'true' or 'positive'
   or 'success' - they're just indices - so I've left it as int.

ssh_scp_recv had particularly confusing semantics for its previous int
return value: its call sites used '<= 0' to check for error, but it
never actually returned a negative number, just 0 or 1. Now the
function and its call sites agree that it's a bool.

In a couple of places I've renamed variables called 'ret', because I
don't like that name any more - it's unclear whether it means the
return value (in preparation) for the _containing_ function or the
return value received from a subroutine call, and occasionally I've
accidentally used the same variable for both and introduced a bug. So
where one of those got in my way, I've renamed it to 'toret' or 'retd'
(the latter short for 'returned') in line with my usual modern
practice, but I haven't done a thorough job of finding all of them.

Finally, one amusing side effect of doing this is that I've had to
separate quite a few chained assignments. It used to be perfectly fine
to write 'a = b = c = TRUE' when a,b,c were int and TRUE was just a
the 'true' defined by stdbool.h, that idiom provokes a warning from
gcc: 'suggest parentheses around assignment used as truth value'!
2018-11-03 13:45:00 +00:00

1079 lines
30 KiB
C

/*
* Telnet backend.
*/
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include "putty.h"
#define IAC 255 /* interpret as command: */
#define DONT 254 /* you are not to use option */
#define DO 253 /* please, you use option */
#define WONT 252 /* I won't use option */
#define WILL 251 /* I will use option */
#define SB 250 /* interpret as subnegotiation */
#define SE 240 /* end sub negotiation */
#define GA 249 /* you may reverse the line */
#define EL 248 /* erase the current line */
#define EC 247 /* erase the current character */
#define AYT 246 /* are you there */
#define AO 245 /* abort output--but let prog finish */
#define IP 244 /* interrupt process--permanently */
#define BREAK 243 /* break */
#define DM 242 /* data mark--for connect. cleaning */
#define NOP 241 /* nop */
#define EOR 239 /* end of record (transparent mode) */
#define ABORT 238 /* Abort process */
#define SUSP 237 /* Suspend process */
#define xEOF 236 /* End of file: EOF is already used... */
#define TELOPTS(X) \
X(BINARY, 0) /* 8-bit data path */ \
X(ECHO, 1) /* echo */ \
X(RCP, 2) /* prepare to reconnect */ \
X(SGA, 3) /* suppress go ahead */ \
X(NAMS, 4) /* approximate message size */ \
X(STATUS, 5) /* give status */ \
X(TM, 6) /* timing mark */ \
X(RCTE, 7) /* remote controlled transmission and echo */ \
X(NAOL, 8) /* negotiate about output line width */ \
X(NAOP, 9) /* negotiate about output page size */ \
X(NAOCRD, 10) /* negotiate about CR disposition */ \
X(NAOHTS, 11) /* negotiate about horizontal tabstops */ \
X(NAOHTD, 12) /* negotiate about horizontal tab disposition */ \
X(NAOFFD, 13) /* negotiate about formfeed disposition */ \
X(NAOVTS, 14) /* negotiate about vertical tab stops */ \
X(NAOVTD, 15) /* negotiate about vertical tab disposition */ \
X(NAOLFD, 16) /* negotiate about output LF disposition */ \
X(XASCII, 17) /* extended ascic character set */ \
X(LOGOUT, 18) /* force logout */ \
X(BM, 19) /* byte macro */ \
X(DET, 20) /* data entry terminal */ \
X(SUPDUP, 21) /* supdup protocol */ \
X(SUPDUPOUTPUT, 22) /* supdup output */ \
X(SNDLOC, 23) /* send location */ \
X(TTYPE, 24) /* terminal type */ \
X(EOR, 25) /* end or record */ \
X(TUID, 26) /* TACACS user identification */ \
X(OUTMRK, 27) /* output marking */ \
X(TTYLOC, 28) /* terminal location number */ \
X(3270REGIME, 29) /* 3270 regime */ \
X(X3PAD, 30) /* X.3 PAD */ \
X(NAWS, 31) /* window size */ \
X(TSPEED, 32) /* terminal speed */ \
X(LFLOW, 33) /* remote flow control */ \
X(LINEMODE, 34) /* Linemode option */ \
X(XDISPLOC, 35) /* X Display Location */ \
X(OLD_ENVIRON, 36) /* Old - Environment variables */ \
X(AUTHENTICATION, 37) /* Authenticate */ \
X(ENCRYPT, 38) /* Encryption option */ \
X(NEW_ENVIRON, 39) /* New - Environment variables */ \
X(TN3270E, 40) /* TN3270 enhancements */ \
X(XAUTH, 41) \
X(CHARSET, 42) /* Character set */ \
X(RSP, 43) /* Remote serial port */ \
X(COM_PORT_OPTION, 44) /* Com port control */ \
X(SLE, 45) /* Suppress local echo */ \
X(STARTTLS, 46) /* Start TLS */ \
X(KERMIT, 47) /* Automatic Kermit file transfer */ \
X(SEND_URL, 48) \
X(FORWARD_X, 49) \
X(PRAGMA_LOGON, 138) \
X(SSPI_LOGON, 139) \
X(PRAGMA_HEARTBEAT, 140) \
X(EXOPL, 255) /* extended-options-list */
#define telnet_enum(x,y) TELOPT_##x = y,
enum { TELOPTS(telnet_enum) dummy=0 };
#undef telnet_enum
#define TELQUAL_IS 0 /* option is... */
#define TELQUAL_SEND 1 /* send option */
#define TELQUAL_INFO 2 /* ENVIRON: informational version of IS */
#define BSD_VAR 1
#define BSD_VALUE 0
#define RFC_VAR 0
#define RFC_VALUE 1
#define CR 13
#define LF 10
#define NUL 0
#define iswritable(x) \
( (x) != IAC && \
(telnet->opt_states[o_we_bin.index] == ACTIVE || (x) != CR))
static const char *telopt(int opt)
{
#define telnet_str(x,y) case TELOPT_##x: return #x;
switch (opt) {
TELOPTS(telnet_str)
default:
return "<unknown>";
}
#undef telnet_str
}
struct Opt {
int send; /* what we initially send */
int nsend; /* -ve send if requested to stop it */
int ack, nak; /* +ve and -ve acknowledgements */
int option; /* the option code */
int index; /* index into telnet->opt_states[] */
enum {
REQUESTED, ACTIVE, INACTIVE, REALLY_INACTIVE
} initial_state;
};
enum {
OPTINDEX_NAWS,
OPTINDEX_TSPEED,
OPTINDEX_TTYPE,
OPTINDEX_OENV,
OPTINDEX_NENV,
OPTINDEX_ECHO,
OPTINDEX_WE_SGA,
OPTINDEX_THEY_SGA,
OPTINDEX_WE_BIN,
OPTINDEX_THEY_BIN,
NUM_OPTS
};
static const struct Opt o_naws =
{ WILL, WONT, DO, DONT, TELOPT_NAWS, OPTINDEX_NAWS, REQUESTED };
static const struct Opt o_tspeed =
{ WILL, WONT, DO, DONT, TELOPT_TSPEED, OPTINDEX_TSPEED, REQUESTED };
static const struct Opt o_ttype =
{ WILL, WONT, DO, DONT, TELOPT_TTYPE, OPTINDEX_TTYPE, REQUESTED };
static const struct Opt o_oenv =
{ WILL, WONT, DO, DONT, TELOPT_OLD_ENVIRON, OPTINDEX_OENV, INACTIVE };
static const struct Opt o_nenv =
{ WILL, WONT, DO, DONT, TELOPT_NEW_ENVIRON, OPTINDEX_NENV, REQUESTED };
static const struct Opt o_echo =
{ DO, DONT, WILL, WONT, TELOPT_ECHO, OPTINDEX_ECHO, REQUESTED };
static const struct Opt o_we_sga =
{ WILL, WONT, DO, DONT, TELOPT_SGA, OPTINDEX_WE_SGA, REQUESTED };
static const struct Opt o_they_sga =
{ DO, DONT, WILL, WONT, TELOPT_SGA, OPTINDEX_THEY_SGA, REQUESTED };
static const struct Opt o_we_bin =
{ WILL, WONT, DO, DONT, TELOPT_BINARY, OPTINDEX_WE_BIN, INACTIVE };
static const struct Opt o_they_bin =
{ DO, DONT, WILL, WONT, TELOPT_BINARY, OPTINDEX_THEY_BIN, INACTIVE };
static const struct Opt *const opts[] = {
&o_naws, &o_tspeed, &o_ttype, &o_oenv, &o_nenv, &o_echo,
&o_we_sga, &o_they_sga, &o_we_bin, &o_they_bin, NULL
};
typedef struct Telnet Telnet;
struct Telnet {
Socket *s;
bool closed_on_socket_error;
Seat *seat;
LogContext *logctx;
Ldisc *ldisc;
int term_width, term_height;
int opt_states[NUM_OPTS];
bool echoing, editing;
bool activated;
int bufsize;
bool in_synch;
int sb_opt, sb_len;
unsigned char *sb_buf;
int sb_size;
bool session_started;
enum {
TOP_LEVEL, SEENIAC, SEENWILL, SEENWONT, SEENDO, SEENDONT,
SEENSB, SUBNEGOT, SUBNEG_IAC, SEENCR
} state;
Conf *conf;
Pinger *pinger;
Plug plug;
Backend backend;
};
#define TELNET_MAX_BACKLOG 4096
#define SB_DELTA 1024
static void c_write(Telnet *telnet, const void *buf, int len)
{
int backlog;
backlog = seat_stdout(telnet->seat, buf, len);
sk_set_frozen(telnet->s, backlog > TELNET_MAX_BACKLOG);
}
static void log_option(Telnet *telnet, const char *sender, int cmd, int option)
{
/*
* The strange-looking "<?""?>" below is there to avoid a
* trigraph - a double question mark followed by > maps to a
* closing brace character!
*/
logeventf(telnet->logctx, "%s:\t%s %s", sender,
(cmd == WILL ? "WILL" : cmd == WONT ? "WONT" :
cmd == DO ? "DO" : cmd == DONT ? "DONT" : "<?""?>"),
telopt(option));
}
static void send_opt(Telnet *telnet, int cmd, int option)
{
unsigned char b[3];
b[0] = IAC;
b[1] = cmd;
b[2] = option;
telnet->bufsize = sk_write(telnet->s, b, 3);
log_option(telnet, "client", cmd, option);
}
static void deactivate_option(Telnet *telnet, const struct Opt *o)
{
if (telnet->opt_states[o->index] == REQUESTED ||
telnet->opt_states[o->index] == ACTIVE)
send_opt(telnet, o->nsend, o->option);
telnet->opt_states[o->index] = REALLY_INACTIVE;
}
/*
* Generate side effects of enabling or disabling an option.
*/
static void option_side_effects(
Telnet *telnet, const struct Opt *o, bool enabled)
{
if (o->option == TELOPT_ECHO && o->send == DO)
telnet->echoing = !enabled;
else if (o->option == TELOPT_SGA && o->send == DO)
telnet->editing = !enabled;
if (telnet->ldisc) /* cause ldisc to notice the change */
ldisc_echoedit_update(telnet->ldisc);
/* Ensure we get the minimum options */
if (!telnet->activated) {
if (telnet->opt_states[o_echo.index] == INACTIVE) {
telnet->opt_states[o_echo.index] = REQUESTED;
send_opt(telnet, o_echo.send, o_echo.option);
}
if (telnet->opt_states[o_we_sga.index] == INACTIVE) {
telnet->opt_states[o_we_sga.index] = REQUESTED;
send_opt(telnet, o_we_sga.send, o_we_sga.option);
}
if (telnet->opt_states[o_they_sga.index] == INACTIVE) {
telnet->opt_states[o_they_sga.index] = REQUESTED;
send_opt(telnet, o_they_sga.send, o_they_sga.option);
}
telnet->activated = true;
}
}
static void activate_option(Telnet *telnet, const struct Opt *o)
{
if (o->send == WILL && o->option == TELOPT_NAWS)
backend_size(&telnet->backend,
telnet->term_width, telnet->term_height);
if (o->send == WILL &&
(o->option == TELOPT_NEW_ENVIRON ||
o->option == TELOPT_OLD_ENVIRON)) {
/*
* We may only have one kind of ENVIRON going at a time.
* This is a hack, but who cares.
*/
deactivate_option(telnet, o->option ==
TELOPT_NEW_ENVIRON ? &o_oenv : &o_nenv);
}
option_side_effects(telnet, o, true);
}
static void refused_option(Telnet *telnet, const struct Opt *o)
{
if (o->send == WILL && o->option == TELOPT_NEW_ENVIRON &&
telnet->opt_states[o_oenv.index] == INACTIVE) {
send_opt(telnet, WILL, TELOPT_OLD_ENVIRON);
telnet->opt_states[o_oenv.index] = REQUESTED;
}
option_side_effects(telnet, o, false);
}
static void proc_rec_opt(Telnet *telnet, int cmd, int option)
{
const struct Opt *const *o;
log_option(telnet, "server", cmd, option);
for (o = opts; *o; o++) {
if ((*o)->option == option && (*o)->ack == cmd) {
switch (telnet->opt_states[(*o)->index]) {
case REQUESTED:
telnet->opt_states[(*o)->index] = ACTIVE;
activate_option(telnet, *o);
break;
case ACTIVE:
break;
case INACTIVE:
telnet->opt_states[(*o)->index] = ACTIVE;
send_opt(telnet, (*o)->send, option);
activate_option(telnet, *o);
break;
case REALLY_INACTIVE:
send_opt(telnet, (*o)->nsend, option);
break;
}
return;
} else if ((*o)->option == option && (*o)->nak == cmd) {
switch (telnet->opt_states[(*o)->index]) {
case REQUESTED:
telnet->opt_states[(*o)->index] = INACTIVE;
refused_option(telnet, *o);
break;
case ACTIVE:
telnet->opt_states[(*o)->index] = INACTIVE;
send_opt(telnet, (*o)->nsend, option);
option_side_effects(telnet, *o, false);
break;
case INACTIVE:
case REALLY_INACTIVE:
break;
}
return;
}
}
/*
* If we reach here, the option was one we weren't prepared to
* cope with. If the request was positive (WILL or DO), we send
* a negative ack to indicate refusal. If the request was
* negative (WONT / DONT), we must do nothing.
*/
if (cmd == WILL || cmd == DO)
send_opt(telnet, (cmd == WILL ? DONT : WONT), option);
}
static void process_subneg(Telnet *telnet)
{
unsigned char *b, *p, *q;
int var, value, n, bsize;
char *e, *eval, *ekey, *user;
switch (telnet->sb_opt) {
case TELOPT_TSPEED:
if (telnet->sb_len == 1 && telnet->sb_buf[0] == TELQUAL_SEND) {
char *termspeed = conf_get_str(telnet->conf, CONF_termspeed);
b = snewn(20 + strlen(termspeed), unsigned char);
b[0] = IAC;
b[1] = SB;
b[2] = TELOPT_TSPEED;
b[3] = TELQUAL_IS;
strcpy((char *)(b + 4), termspeed);
n = 4 + strlen(termspeed);
b[n] = IAC;
b[n + 1] = SE;
telnet->bufsize = sk_write(telnet->s, b, n + 2);
logevent(telnet->logctx, "server:\tSB TSPEED SEND");
logeventf(telnet->logctx, "client:\tSB TSPEED IS %s", termspeed);
sfree(b);
} else
logevent(telnet->logctx, "server:\tSB TSPEED <something weird>");
break;
case TELOPT_TTYPE:
if (telnet->sb_len == 1 && telnet->sb_buf[0] == TELQUAL_SEND) {
char *termtype = conf_get_str(telnet->conf, CONF_termtype);
b = snewn(20 + strlen(termtype), unsigned char);
b[0] = IAC;
b[1] = SB;
b[2] = TELOPT_TTYPE;
b[3] = TELQUAL_IS;
for (n = 0; termtype[n]; n++)
b[n + 4] = (termtype[n] >= 'a' && termtype[n] <= 'z' ?
termtype[n] + 'A' - 'a' :
termtype[n]);
b[n + 4] = IAC;
b[n + 5] = SE;
telnet->bufsize = sk_write(telnet->s, b, n + 6);
b[n + 4] = 0;
logevent(telnet->logctx, "server:\tSB TTYPE SEND");
logeventf(telnet->logctx, "client:\tSB TTYPE IS %s", b + 4);
sfree(b);
} else
logevent(telnet->logctx, "server:\tSB TTYPE <something weird>\r\n");
break;
case TELOPT_OLD_ENVIRON:
case TELOPT_NEW_ENVIRON:
p = telnet->sb_buf;
q = p + telnet->sb_len;
if (p < q && *p == TELQUAL_SEND) {
p++;
logeventf(telnet->logctx, "server:\tSB %s SEND",
telopt(telnet->sb_opt));
if (telnet->sb_opt == TELOPT_OLD_ENVIRON) {
if (conf_get_bool(telnet->conf, CONF_rfc_environ)) {
value = RFC_VALUE;
var = RFC_VAR;
} else {
value = BSD_VALUE;
var = BSD_VAR;
}
/*
* Try to guess the sense of VAR and VALUE.
*/
while (p < q) {
if (*p == RFC_VAR) {
value = RFC_VALUE;
var = RFC_VAR;
} else if (*p == BSD_VAR) {
value = BSD_VALUE;
var = BSD_VAR;
}
p++;
}
} else {
/*
* With NEW_ENVIRON, the sense of VAR and VALUE
* isn't in doubt.
*/
value = RFC_VALUE;
var = RFC_VAR;
}
bsize = 20;
for (eval = conf_get_str_strs(telnet->conf, CONF_environmt,
NULL, &ekey);
eval != NULL;
eval = conf_get_str_strs(telnet->conf, CONF_environmt,
ekey, &ekey))
bsize += strlen(ekey) + strlen(eval) + 2;
user = get_remote_username(telnet->conf);
if (user)
bsize += 6 + strlen(user);
b = snewn(bsize, unsigned char);
b[0] = IAC;
b[1] = SB;
b[2] = telnet->sb_opt;
b[3] = TELQUAL_IS;
n = 4;
for (eval = conf_get_str_strs(telnet->conf, CONF_environmt,
NULL, &ekey);
eval != NULL;
eval = conf_get_str_strs(telnet->conf, CONF_environmt,
ekey, &ekey)) {
b[n++] = var;
for (e = ekey; *e; e++)
b[n++] = *e;
b[n++] = value;
for (e = eval; *e; e++)
b[n++] = *e;
}
if (user) {
b[n++] = var;
b[n++] = 'U';
b[n++] = 'S';
b[n++] = 'E';
b[n++] = 'R';
b[n++] = value;
for (e = user; *e; e++)
b[n++] = *e;
}
b[n++] = IAC;
b[n++] = SE;
telnet->bufsize = sk_write(telnet->s, b, n);
if (n == 6) {
logeventf(telnet->logctx, "client:\tSB %s IS <nothing>",
telopt(telnet->sb_opt));
} else {
logeventf(telnet->logctx, "client:\tSB %s IS:",
telopt(telnet->sb_opt));
for (eval = conf_get_str_strs(telnet->conf, CONF_environmt,
NULL, &ekey);
eval != NULL;
eval = conf_get_str_strs(telnet->conf, CONF_environmt,
ekey, &ekey)) {
logeventf(telnet->logctx, "\t%s=%s", ekey, eval);
}
if (user)
logeventf(telnet->logctx, "\tUSER=%s", user);
}
sfree(b);
sfree(user);
}
break;
}
}
static void do_telnet_read(Telnet *telnet, char *buf, int len)
{
char *outbuf = NULL;
int outbuflen = 0, outbufsize = 0;
#define ADDTOBUF(c) do { \
if (outbuflen >= outbufsize) { \
outbufsize = outbuflen + 256; \
outbuf = sresize(outbuf, outbufsize, char); \
} \
outbuf[outbuflen++] = (c); \
} while (0)
while (len--) {
int c = (unsigned char) *buf++;
switch (telnet->state) {
case TOP_LEVEL:
case SEENCR:
if (c == NUL && telnet->state == SEENCR)
telnet->state = TOP_LEVEL;
else if (c == IAC)
telnet->state = SEENIAC;
else {
if (!telnet->in_synch)
ADDTOBUF(c);
#if 1
/* I can't get the F***ing winsock to insert the urgent IAC
* into the right position! Even with SO_OOBINLINE it gives
* it to recv too soon. And of course the DM byte (that
* arrives in the same packet!) appears several K later!!
*
* Oh well, we do get the DM in the right place so I'll
* just stop hiding on the next 0xf2 and hope for the best.
*/
else if (c == DM)
telnet->in_synch = false;
#endif
if (c == CR && telnet->opt_states[o_they_bin.index] != ACTIVE)
telnet->state = SEENCR;
else
telnet->state = TOP_LEVEL;
}
break;
case SEENIAC:
if (c == DO)
telnet->state = SEENDO;
else if (c == DONT)
telnet->state = SEENDONT;
else if (c == WILL)
telnet->state = SEENWILL;
else if (c == WONT)
telnet->state = SEENWONT;
else if (c == SB)
telnet->state = SEENSB;
else if (c == DM) {
telnet->in_synch = false;
telnet->state = TOP_LEVEL;
} else {
/* ignore everything else; print it if it's IAC */
if (c == IAC) {
ADDTOBUF(c);
}
telnet->state = TOP_LEVEL;
}
break;
case SEENWILL:
proc_rec_opt(telnet, WILL, c);
telnet->state = TOP_LEVEL;
break;
case SEENWONT:
proc_rec_opt(telnet, WONT, c);
telnet->state = TOP_LEVEL;
break;
case SEENDO:
proc_rec_opt(telnet, DO, c);
telnet->state = TOP_LEVEL;
break;
case SEENDONT:
proc_rec_opt(telnet, DONT, c);
telnet->state = TOP_LEVEL;
break;
case SEENSB:
telnet->sb_opt = c;
telnet->sb_len = 0;
telnet->state = SUBNEGOT;
break;
case SUBNEGOT:
if (c == IAC)
telnet->state = SUBNEG_IAC;
else {
subneg_addchar:
if (telnet->sb_len >= telnet->sb_size) {
telnet->sb_size += SB_DELTA;
telnet->sb_buf = sresize(telnet->sb_buf, telnet->sb_size,
unsigned char);
}
telnet->sb_buf[telnet->sb_len++] = c;
telnet->state = SUBNEGOT; /* in case we came here by goto */
}
break;
case SUBNEG_IAC:
if (c != SE)
goto subneg_addchar; /* yes, it's a hack, I know, but... */
else {
process_subneg(telnet);
telnet->state = TOP_LEVEL;
}
break;
}
}
if (outbuflen)
c_write(telnet, outbuf, outbuflen);
sfree(outbuf);
}
static void telnet_log(Plug *plug, int type, SockAddr *addr, int port,
const char *error_msg, int error_code)
{
Telnet *telnet = container_of(plug, Telnet, plug);
backend_socket_log(telnet->seat, telnet->logctx, type, addr, port,
error_msg, error_code, telnet->conf,
telnet->session_started);
}
static void telnet_closing(Plug *plug, const char *error_msg, int error_code,
bool calling_back)
{
Telnet *telnet = container_of(plug, Telnet, plug);
/*
* We don't implement independent EOF in each direction for Telnet
* connections; as soon as we get word that the remote side has
* sent us EOF, we wind up the whole connection.
*/
if (telnet->s) {
sk_close(telnet->s);
telnet->s = NULL;
if (error_msg)
telnet->closed_on_socket_error = true;
seat_notify_remote_exit(telnet->seat);
}
if (error_msg) {
logevent(telnet->logctx, error_msg);
seat_connection_fatal(telnet->seat, "%s", error_msg);
}
/* Otherwise, the remote side closed the connection normally. */
}
static void telnet_receive(Plug *plug, int urgent, char *data, int len)
{
Telnet *telnet = container_of(plug, Telnet, plug);
if (urgent)
telnet->in_synch = true;
telnet->session_started = true;
do_telnet_read(telnet, data, len);
}
static void telnet_sent(Plug *plug, int bufsize)
{
Telnet *telnet = container_of(plug, Telnet, plug);
telnet->bufsize = bufsize;
}
static const PlugVtable Telnet_plugvt = {
telnet_log,
telnet_closing,
telnet_receive,
telnet_sent
};
/*
* Called to set up the Telnet connection.
*
* Returns an error message, or NULL on success.
*
* Also places the canonical host name into `realhost'. It must be
* freed by the caller.
*/
static const char *telnet_init(Seat *seat, Backend **backend_handle,
LogContext *logctx, Conf *conf,
const char *host, int port,
char **realhost, bool nodelay, bool keepalive)
{
SockAddr *addr;
const char *err;
Telnet *telnet;
char *loghost;
int addressfamily;
telnet = snew(Telnet);
telnet->plug.vt = &Telnet_plugvt;
telnet->backend.vt = &telnet_backend;
telnet->conf = conf_copy(conf);
telnet->s = NULL;
telnet->closed_on_socket_error = false;
telnet->echoing = true;
telnet->editing = true;
telnet->activated = false;
telnet->sb_buf = NULL;
telnet->sb_size = 0;
telnet->seat = seat;
telnet->logctx = logctx;
telnet->term_width = conf_get_int(telnet->conf, CONF_width);
telnet->term_height = conf_get_int(telnet->conf, CONF_height);
telnet->state = TOP_LEVEL;
telnet->ldisc = NULL;
telnet->pinger = NULL;
telnet->session_started = true;
*backend_handle = &telnet->backend;
/*
* Try to find host.
*/
addressfamily = conf_get_int(telnet->conf, CONF_addressfamily);
addr = name_lookup(host, port, realhost, telnet->conf, addressfamily,
telnet->logctx, "Telnet connection");
if ((err = sk_addr_error(addr)) != NULL) {
sk_addr_free(addr);
return err;
}
if (port < 0)
port = 23; /* default telnet port */
/*
* Open socket.
*/
telnet->s = new_connection(addr, *realhost, port, false, true, nodelay,
keepalive, &telnet->plug, telnet->conf);
if ((err = sk_socket_error(telnet->s)) != NULL)
return err;
telnet->pinger = pinger_new(telnet->conf, &telnet->backend);
/*
* Initialise option states.
*/
if (conf_get_bool(telnet->conf, CONF_passive_telnet)) {
const struct Opt *const *o;
for (o = opts; *o; o++)
telnet->opt_states[(*o)->index] = INACTIVE;
} else {
const struct Opt *const *o;
for (o = opts; *o; o++) {
telnet->opt_states[(*o)->index] = (*o)->initial_state;
if (telnet->opt_states[(*o)->index] == REQUESTED)
send_opt(telnet, (*o)->send, (*o)->option);
}
telnet->activated = true;
}
/*
* Set up SYNCH state.
*/
telnet->in_synch = false;
/*
* We can send special commands from the start.
*/
seat_update_specials_menu(telnet->seat);
/*
* loghost overrides realhost, if specified.
*/
loghost = conf_get_str(telnet->conf, CONF_loghost);
if (*loghost) {
char *colon;
sfree(*realhost);
*realhost = dupstr(loghost);
colon = host_strrchr(*realhost, ':');
if (colon)
*colon++ = '\0';
}
return NULL;
}
static void telnet_free(Backend *be)
{
Telnet *telnet = container_of(be, Telnet, backend);
sfree(telnet->sb_buf);
if (telnet->s)
sk_close(telnet->s);
if (telnet->pinger)
pinger_free(telnet->pinger);
conf_free(telnet->conf);
sfree(telnet);
}
/*
* Reconfigure the Telnet backend. There's no immediate action
* necessary, in this backend: we just save the fresh config for
* any subsequent negotiations.
*/
static void telnet_reconfig(Backend *be, Conf *conf)
{
Telnet *telnet = container_of(be, Telnet, backend);
pinger_reconfig(telnet->pinger, telnet->conf, conf);
conf_free(telnet->conf);
telnet->conf = conf_copy(conf);
}
/*
* Called to send data down the Telnet connection.
*/
static int telnet_send(Backend *be, const char *buf, int len)
{
Telnet *telnet = container_of(be, Telnet, backend);
unsigned char *p, *end;
static const unsigned char iac[2] = { IAC, IAC };
static const unsigned char cr[2] = { CR, NUL };
#if 0
static const unsigned char nl[2] = { CR, LF };
#endif
if (telnet->s == NULL)
return 0;
p = (unsigned char *)buf;
end = (unsigned char *)(buf + len);
while (p < end) {
unsigned char *q = p;
while (p < end && iswritable(*p))
p++;
telnet->bufsize = sk_write(telnet->s, q, p - q);
while (p < end && !iswritable(*p)) {
telnet->bufsize =
sk_write(telnet->s, *p == IAC ? iac : cr, 2);
p++;
}
}
return telnet->bufsize;
}
/*
* Called to query the current socket sendability status.
*/
static int telnet_sendbuffer(Backend *be)
{
Telnet *telnet = container_of(be, Telnet, backend);
return telnet->bufsize;
}
/*
* Called to set the size of the window from Telnet's POV.
*/
static void telnet_size(Backend *be, int width, int height)
{
Telnet *telnet = container_of(be, Telnet, backend);
unsigned char b[24];
int n;
telnet->term_width = width;
telnet->term_height = height;
if (telnet->s == NULL || telnet->opt_states[o_naws.index] != ACTIVE)
return;
n = 0;
b[n++] = IAC;
b[n++] = SB;
b[n++] = TELOPT_NAWS;
b[n++] = telnet->term_width >> 8;
if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */
b[n++] = telnet->term_width & 0xFF;
if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */
b[n++] = telnet->term_height >> 8;
if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */
b[n++] = telnet->term_height & 0xFF;
if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */
b[n++] = IAC;
b[n++] = SE;
telnet->bufsize = sk_write(telnet->s, b, n);
logeventf(telnet->logctx, "client:\tSB NAWS %d,%d",
telnet->term_width, telnet->term_height);
}
/*
* Send Telnet special codes.
*/
static void telnet_special(Backend *be, SessionSpecialCode code, int arg)
{
Telnet *telnet = container_of(be, Telnet, backend);
unsigned char b[2];
if (telnet->s == NULL)
return;
b[0] = IAC;
switch (code) {
case SS_AYT:
b[1] = AYT;
telnet->bufsize = sk_write(telnet->s, b, 2);
break;
case SS_BRK:
b[1] = BREAK;
telnet->bufsize = sk_write(telnet->s, b, 2);
break;
case SS_EC:
b[1] = EC;
telnet->bufsize = sk_write(telnet->s, b, 2);
break;
case SS_EL:
b[1] = EL;
telnet->bufsize = sk_write(telnet->s, b, 2);
break;
case SS_GA:
b[1] = GA;
telnet->bufsize = sk_write(telnet->s, b, 2);
break;
case SS_NOP:
b[1] = NOP;
telnet->bufsize = sk_write(telnet->s, b, 2);
break;
case SS_ABORT:
b[1] = ABORT;
telnet->bufsize = sk_write(telnet->s, b, 2);
break;
case SS_AO:
b[1] = AO;
telnet->bufsize = sk_write(telnet->s, b, 2);
break;
case SS_IP:
b[1] = IP;
telnet->bufsize = sk_write(telnet->s, b, 2);
break;
case SS_SUSP:
b[1] = SUSP;
telnet->bufsize = sk_write(telnet->s, b, 2);
break;
case SS_EOR:
b[1] = EOR;
telnet->bufsize = sk_write(telnet->s, b, 2);
break;
case SS_EOF:
b[1] = xEOF;
telnet->bufsize = sk_write(telnet->s, b, 2);
break;
case SS_EOL:
/* In BINARY mode, CR-LF becomes just CR -
* and without the NUL suffix too. */
if (telnet->opt_states[o_we_bin.index] == ACTIVE)
telnet->bufsize = sk_write(telnet->s, "\r", 1);
else
telnet->bufsize = sk_write(telnet->s, "\r\n", 2);
break;
case SS_SYNCH:
b[1] = DM;
telnet->bufsize = sk_write(telnet->s, b, 1);
telnet->bufsize = sk_write_oob(telnet->s, b + 1, 1);
break;
case SS_PING:
if (telnet->opt_states[o_they_sga.index] == ACTIVE) {
b[1] = NOP;
telnet->bufsize = sk_write(telnet->s, b, 2);
}
break;
default:
break; /* never heard of it */
}
}
static const SessionSpecial *telnet_get_specials(Backend *be)
{
static const SessionSpecial specials[] = {
{"Are You There", SS_AYT},
{"Break", SS_BRK},
{"Synch", SS_SYNCH},
{"Erase Character", SS_EC},
{"Erase Line", SS_EL},
{"Go Ahead", SS_GA},
{"No Operation", SS_NOP},
{NULL, SS_SEP},
{"Abort Process", SS_ABORT},
{"Abort Output", SS_AO},
{"Interrupt Process", SS_IP},
{"Suspend Process", SS_SUSP},
{NULL, SS_SEP},
{"End Of Record", SS_EOR},
{"End Of File", SS_EOF},
{NULL, SS_EXITMENU}
};
return specials;
}
static bool telnet_connected(Backend *be)
{
Telnet *telnet = container_of(be, Telnet, backend);
return telnet->s != NULL;
}
static bool telnet_sendok(Backend *be)
{
/* Telnet *telnet = container_of(be, Telnet, backend); */
return true;
}
static void telnet_unthrottle(Backend *be, int backlog)
{
Telnet *telnet = container_of(be, Telnet, backend);
sk_set_frozen(telnet->s, backlog > TELNET_MAX_BACKLOG);
}
static bool telnet_ldisc(Backend *be, int option)
{
Telnet *telnet = container_of(be, Telnet, backend);
if (option == LD_ECHO)
return telnet->echoing;
if (option == LD_EDIT)
return telnet->editing;
return false;
}
static void telnet_provide_ldisc(Backend *be, Ldisc *ldisc)
{
Telnet *telnet = container_of(be, Telnet, backend);
telnet->ldisc = ldisc;
}
static int telnet_exitcode(Backend *be)
{
Telnet *telnet = container_of(be, Telnet, backend);
if (telnet->s != NULL)
return -1; /* still connected */
else if (telnet->closed_on_socket_error)
return INT_MAX; /* a socket error counts as an unclean exit */
else
/* Telnet doesn't transmit exit codes back to the client */
return 0;
}
/*
* cfg_info for Telnet does nothing at all.
*/
static int telnet_cfg_info(Backend *be)
{
return 0;
}
const struct BackendVtable telnet_backend = {
telnet_init,
telnet_free,
telnet_reconfig,
telnet_send,
telnet_sendbuffer,
telnet_size,
telnet_special,
telnet_get_specials,
telnet_connected,
telnet_exitcode,
telnet_sendok,
telnet_ldisc,
telnet_provide_ldisc,
telnet_unthrottle,
telnet_cfg_info,
NULL /* test_for_upstream */,
"telnet",
PROT_TELNET,
23
};