partial RFC1143 implementation; only reacts to negotiation, cannot handle initiating it yet

This commit is contained in:
Sean Middleditch 2009-03-15 23:02:10 -04:00
parent e7c442681a
commit 5b5bc92bc0
5 changed files with 394 additions and 94 deletions

82
README
View File

@ -226,20 +226,76 @@ IId. Event Handling
The necessary processing depends on the specific commands; see
the TELNET RFC for more information.
LIBTELNET_EV_NEGOTIATE:
The NEGOTIATE event is sent when a TELNET neogitiation command
is received.
LIBTELNET_EV_WILL:
LIBTELNET_EV_DO:
The WILL and DO events are sent when a TELNET negotiation
command of the same name is received.
The event->command value will be one of LIBTELNET_WILL,
LIBTELNET_WONT, LIBTELNET_DO, or LIBTELNET_DONT. The
event->telopt value will contain the option value being
negotiated.
WILL events are sent by the remote end when they wish to be
allowed to turn an option on on their end, or in confirmation
after you have sent a DO command to them.
libtelnet does not currently manage negotiation for you. For
best practice in implementing TELNET negotiation, see:
DO events are sent by the remote end when they wish for you
to turn on an option on your end, or in confirmation after you
have sent a WILL command to them.
In either case, the TELNET option under negotiation will be in
event->telopt field.
If you support the option and wish for it to be enabled you
must set the event->accept field to 1, unless this event is
a confirmation for a previous WILL/DO command you sent to the
remote end. If you do not set event->field to 1 then
libtelnet will send a rejection command back to the other end.
libtelnet manages some of the pecularities of negotiation for
you. For information on libtelnet's negotiation method, see:
http://www.faqs.org/rfcs/rfc1143.html
Examples:
You want remote end to use TTYPE, so you send DO TTYPE.
Remote accepts and sends WILL TTYPE.
Remote end wants you to use SGA, so they send DO_SGA.
You do not support SGA and set event->accept = 0.
Remote end wants to use ZMP, so they send WILL ZMP.
You support ZMP, so you set event->accept = 1 and enable
local ZMP support.
You want to use MCCP2, so you send WILL COMPRESS2.
Remote end accepts and sends DO COMPRESS2.
Note that in PROXY mode libtelnet will do no processing of its
own for you.
LIBTELNET_EV_WONT:
LIBTELNET_EV_DONT:
The WONT and DONT events are sent when the remote end of the
connection wishes to disable an option, when they are
refusing to a support an option that you have asked for, or
in confirmation of an option you have asked to be disabled.
Most commonly WONT and DONT events are sent as rejections of
features you requested by sending DO or WILL events. Receiving
these events means the TELNET option is not or will not be
supported by the remote end, so give up.
Sometimes WONT or DONT will be sent for TELNET options that are
already enabled, but the remote end wishes to stop using. You
cannot decline. These events are demands that must be complied
with. libtelnet will always send the appropriate response back
without consulting your application. These events are sent to
allow your application to disable its own use of the features.
In either case, the TELNET option under negotiation will be in
event->telopt field.
Note that in PROXY mode libtelnet will do no processing of its
own for you.
LIBTELNET_EV_SUBNEGOTIATION:
Triggered whenever a TELNET sub-negotiation has been received.
Sub-negotiations include the NAWS option for communicating
@ -347,11 +403,9 @@ option using libtelnet_send_negotiate(), for example:
libtelnet_send_negotiate(&telnet, LIBTELNET_WILL,
LIBTELNET_OPTION_COMPRESS2, user_data);
If a favorable DO COMPRESS2 is sent back from the client (processed
in a LIBTELNET_EV_NEGOTIATE event, with event->command equal to
LIBTELNET_DO and event->telopt equal to LIBTELNET_TELOPT_COMPRESS2),
then the server application can begin compression at any time by
calling libtelnet_begin_compress2().
If a favorable DO COMPRESS2 is sent back from the client then the
server application can begin compression at any time by calling
libtelnet_begin_compress2().
If a connection is in PROXY mode and COMPRESS2 support is enabled
then libtelnet will automatically detect the start of a COMPRESS2

View File

@ -22,6 +22,18 @@
#include "libtelnet.h"
/* RFC1143 state names */
#define RFC1143_NO 0x00
#define RFC1143_YES 0x01
#define RFC1143_WANT 0x02
#define RFC1143_OP 0x04
#define RFC1143_WANTNO (RFC1143_WANT|RFC1143_YES)
#define RFC1143_WANTYES (RFC1143_WANT|RFC1143_NO)
#define RFC1143_WANTNO_OP (RFC1143_WANTNO|RFC1143_OP)
#define RFC1143_WANTYES_OP (RFC1143_WANTYES|RFC1143_OP)
/* buffer sizes */
static const unsigned int _buffer_sizes[] = {
0,
@ -33,18 +45,22 @@ static const unsigned int _buffer_sizes[] = {
static const unsigned int _buffer_sizes_count =
sizeof(_buffer_sizes) / sizeof(_buffer_sizes[0]);
/* event dispatch helper */
static void _event(struct libtelnet_t *telnet,
/* event dispatch helper; return value is value of the accept field of the
* event struct after dispatch; used for the funky REQUEST event */
static int _event(struct libtelnet_t *telnet,
enum libtelnet_event_type_t type, unsigned char command,
unsigned char telopt, unsigned char *buffer, unsigned int size) {
struct libtelnet_event_t ev;
ev.buffer = buffer;
ev.size = size;
ev.type = type;
ev.command = command;
ev.telopt = telopt;
ev.buffer = buffer;
ev.size = size;
ev.accept = 0;
telnet->eh(telnet, &ev, telnet->ud);
return ev.accept;
}
/* error generation function */
@ -102,6 +118,182 @@ z_stream *_init_zlib(struct libtelnet_t *telnet, int deflate, int err_fatal) {
return zlib;
}
/* negotiation handling magic */
static void _negotiate(struct libtelnet_t *telnet, unsigned char cmd,
unsigned char telopt) {
struct libtelnet_rfc1143_t *qtmp;
int q;
/* in PROXY mode, just pass it thru and do nothing */
if (telnet->mode == LIBTELNET_MODE_PROXY) {
switch (cmd) {
case LIBTELNET_WILL:
_event(telnet, LIBTELNET_EV_WILL, cmd, telopt, 0, 0);
break;
case LIBTELNET_WONT:
_event(telnet, LIBTELNET_EV_WONT, cmd, telopt, 0, 0);
break;
case LIBTELNET_DO:
_event(telnet, LIBTELNET_EV_DO, cmd, telopt, 0, 0);
break;
case LIBTELNET_DONT:
_event(telnet, LIBTELNET_EV_DONT, cmd, telopt, 0, 0);
break;
}
return;
}
/* lookup the current state of the option */
for (q = 0; q != telnet->q_size; ++q) {
if (telnet->q[q].telopt == telopt)
break;
}
/* not found */
if (q == telnet->q_size) {
/* if the option is unfound then it is off on both ends... and there
* is no need thus to respond to a WONT/DONT */
if (cmd == LIBTELNET_WONT || cmd == LIBTELNET_DONT)
return;
/* we're going to need to track state for it, so grow the queue
* and put the telopt into it; bail on allocation error
*/
if ((qtmp = (struct libtelnet_rfc1143_t *)malloc(sizeof(
struct libtelnet_rfc1143_t) * (telnet->q_size + 1))) == 0) {
_error(telnet, __LINE__, __func__, LIBTELNET_ENOMEM, 0,
"malloc() failed: %s", strerror(errno));
return;
}
telnet->q = qtmp;
memset(&telnet->q[telnet->q_size], 0,
sizeof(struct libtelnet_rfc1143_t));
telnet->q[telnet->q_size].telopt = telopt;
q = telnet->q_size;
++telnet->q_size;
}
/* start processing... */
switch (cmd) {
/* request to enable option on remote end or confirm DO */
case LIBTELNET_WILL:
switch (telnet->q[q].him) {
case RFC1143_NO:
if (_event(telnet, LIBTELNET_EV_WILL, cmd, telopt, 0, 0) == 1) {
telnet->q[q].him = RFC1143_YES;
libtelnet_send_negotiate(telnet, LIBTELNET_DO, telopt);
} else
libtelnet_send_negotiate(telnet, LIBTELNET_DONT, telopt);
break;
case RFC1143_YES:
break;
case RFC1143_WANTNO:
telnet->q[q].him = RFC1143_NO;
_error(telnet, __LINE__, __func__, LIBTELNET_EPROTOCOL, 0,
"DONT answered by WILL");
break;
case RFC1143_WANTNO_OP:
telnet->q[q].him = RFC1143_YES;
_error(telnet, __LINE__, __func__, LIBTELNET_EPROTOCOL, 0,
"DONT answered by WILL");
break;
case RFC1143_WANTYES:
telnet->q[q].him = RFC1143_YES;
break;
case RFC1143_WANTYES_OP:
telnet->q[q].him = RFC1143_WANTNO;
libtelnet_send_negotiate(telnet, LIBTELNET_DONT, telopt);
break;
}
break;
/* request to disable option on remote end, confirm DONT, reject DO */
case LIBTELNET_WONT:
switch (telnet->q[q].him) {
case RFC1143_NO:
break;
case RFC1143_YES:
telnet->q[q].him = RFC1143_NO;
libtelnet_send_negotiate(telnet, LIBTELNET_DONT, telopt);
_event(telnet, LIBTELNET_EV_WONT, 0, telopt,
0, 0);
break;
case RFC1143_WANTNO:
telnet->q[q].him = RFC1143_NO;
_event(telnet, LIBTELNET_EV_WONT, 0, telopt,
0, 0);
break;
case RFC1143_WANTNO_OP:
telnet->q[q].him = RFC1143_WANTYES;
_event(telnet, LIBTELNET_EV_DO, 0, telopt,
0, 0);
break;
case RFC1143_WANTYES:
case RFC1143_WANTYES_OP:
telnet->q[q].him = RFC1143_NO;
break;
}
break;
/* request to enable option on local end or confirm WILL */
case LIBTELNET_DO:
switch (telnet->q[q].us) {
case RFC1143_NO:
if (_event(telnet, LIBTELNET_EV_DO, cmd, telopt, 0, 0) == 1) {
telnet->q[q].us = RFC1143_YES;
libtelnet_send_negotiate(telnet, LIBTELNET_WILL, telopt);
} else
libtelnet_send_negotiate(telnet, LIBTELNET_WONT, telopt);
break;
case RFC1143_YES:
break;
case RFC1143_WANTNO:
telnet->q[q].us = RFC1143_NO;
_error(telnet, __LINE__, __func__, LIBTELNET_EPROTOCOL, 0,
"WONT answered by DO");
break;
case RFC1143_WANTNO_OP:
telnet->q[q].us = RFC1143_YES;
_error(telnet, __LINE__, __func__, LIBTELNET_EPROTOCOL, 0,
"WONT answered by DO");
break;
case RFC1143_WANTYES:
telnet->q[q].us = RFC1143_YES;
break;
case RFC1143_WANTYES_OP:
telnet->q[q].us = RFC1143_WANTNO;
libtelnet_send_negotiate(telnet, LIBTELNET_WONT, telopt);
break;
}
break;
/* request to disable option on local end, confirm WONT, reject WILL */
case LIBTELNET_DONT:
switch (telnet->q[q].us) {
case RFC1143_NO:
break;
case RFC1143_YES:
telnet->q[q].us = RFC1143_NO;
libtelnet_send_negotiate(telnet, LIBTELNET_WONT, telopt);
_event(telnet, LIBTELNET_EV_DONT, 0, telopt, 0, 0);
break;
case RFC1143_WANTNO:
telnet->q[q].us = RFC1143_NO;
_event(telnet, LIBTELNET_EV_WONT, 0, telopt, 0, 0);
break;
case RFC1143_WANTNO_OP:
telnet->q[q].us = RFC1143_WANTYES;
_event(telnet, LIBTELNET_EV_WILL, 0, telopt, 0, 0);
break;
case RFC1143_WANTYES:
case RFC1143_WANTYES_OP:
telnet->q[q].us = RFC1143_NO;
break;
}
break;
}
}
/* initialize a telnet state tracker */
void libtelnet_init(struct libtelnet_t *telnet, libtelnet_event_handler_t eh,
enum libtelnet_mode_t mode, void *user_data) {
@ -117,8 +309,8 @@ void libtelnet_free(struct libtelnet_t *telnet) {
if (telnet->buffer != 0) {
free(telnet->buffer);
telnet->buffer = 0;
telnet->size = 0;
telnet->length = 0;
telnet->buffer_size = 0;
telnet->buffer_pos = 0;
}
/* free zlib box(es) */
@ -132,6 +324,13 @@ void libtelnet_free(struct libtelnet_t *telnet) {
free(telnet->z_deflate);
telnet->z_deflate = 0;
}
/* free RFC1143 queue */
if (telnet->q) {
free(telnet->q);
telnet->q = 0;
telnet->q_size = 0;
}
}
/* push a byte into the telnet buffer */
@ -141,10 +340,10 @@ static enum libtelnet_error_t _buffer_byte(struct libtelnet_t *telnet,
int i;
/* check if we're out of room */
if (telnet->length == telnet->size) {
if (telnet->buffer_pos == telnet->buffer_size) {
/* find the next buffer size */
for (i = 0; i != _buffer_sizes_count; ++i) {
if (_buffer_sizes[i] == telnet->size)
if (_buffer_sizes[i] == telnet->buffer_size)
break;
}
@ -167,11 +366,11 @@ static enum libtelnet_error_t _buffer_byte(struct libtelnet_t *telnet,
}
telnet->buffer = new_buffer;
telnet->size = _buffer_sizes[i + 1];
telnet->buffer_size = _buffer_sizes[i + 1];
}
/* push the byte, all set */
telnet->buffer[telnet->length++] = byte;
telnet->buffer[telnet->buffer_pos++] = byte;
return LIBTELNET_EOK;
}
@ -230,22 +429,22 @@ static void _process(struct libtelnet_t *telnet, unsigned char *buffer,
/* negotiation commands */
case LIBTELNET_STATE_DO:
_event(telnet, LIBTELNET_EV_NEGOTIATE, LIBTELNET_DO, byte, 0, 0);
_negotiate(telnet, LIBTELNET_DO, byte);
start = i + 1;
telnet->state = LIBTELNET_STATE_DATA;
break;
case LIBTELNET_STATE_DONT:
_event(telnet, LIBTELNET_EV_NEGOTIATE, LIBTELNET_DONT, byte, 0, 0);
_negotiate(telnet, LIBTELNET_DONT, byte);
start = i + 1;
telnet->state = LIBTELNET_STATE_DATA;
break;
case LIBTELNET_STATE_WILL:
_event(telnet, LIBTELNET_EV_NEGOTIATE, LIBTELNET_WILL, byte, 0, 0);
_negotiate(telnet, LIBTELNET_WILL, byte);
start = i + 1;
telnet->state = LIBTELNET_STATE_DATA;
break;
case LIBTELNET_STATE_WONT:
_event(telnet, LIBTELNET_EV_NEGOTIATE, LIBTELNET_WONT, byte, 0, 0);
_negotiate(telnet, LIBTELNET_WONT, byte);
start = i + 1;
telnet->state = LIBTELNET_STATE_DATA;
break;
@ -253,7 +452,7 @@ static void _process(struct libtelnet_t *telnet, unsigned char *buffer,
/* subnegotiation -- determine subnegotiation telopt */
case LIBTELNET_STATE_SB:
telnet->sb_telopt = byte;
telnet->length = 0;
telnet->buffer_pos = 0;
telnet->state = LIBTELNET_STATE_SB_DATA;
break;
@ -280,7 +479,7 @@ static void _process(struct libtelnet_t *telnet, unsigned char *buffer,
/* invoke callback */
_event(telnet, LIBTELNET_EV_SUBNEGOTIATION, 0,
telnet->sb_telopt, telnet->buffer, telnet->length);
telnet->sb_telopt, telnet->buffer, telnet->buffer_pos);
#ifdef HAVE_ZLIB
/* if we are a client or a proxy and just received the
@ -355,7 +554,8 @@ void libtelnet_push(struct libtelnet_t *telnet, unsigned char *buffer,
telnet->z_inflate->avail_out = sizeof(inflate_buffer);
/* inflate until buffer exhausted and all output is produced */
while (telnet->z_inflate->avail_in > 0 || telnet->z_inflate->avail_out == 0) {
while (telnet->z_inflate->avail_in > 0 ||
telnet->z_inflate->avail_out == 0) {
/* reset output buffer */
/* decompress */
@ -405,7 +605,8 @@ static void _send(struct libtelnet_t *telnet, unsigned char *buffer,
telnet->z_deflate->avail_out = sizeof(deflate_buffer);
/* deflate until buffer exhausted and all output is produced */
while (telnet->z_deflate->avail_in > 0 || telnet->z_deflate->avail_out == 0) {
while (telnet->z_deflate->avail_in > 0 ||
telnet->z_deflate->avail_out == 0) {
/* compress */
if ((rs = deflate(telnet->z_deflate, Z_SYNC_FLUSH)) != Z_OK) {
_error(telnet, __LINE__, __func__, LIBTELNET_ECOMPRESS, 1,
@ -519,3 +720,29 @@ void libtelnet_begin_compress2(struct libtelnet_t *telnet) {
telnet->z_deflate = zlib;
#endif /* HAVE_ZLIB */
}
/* get local state of specific telopt */
int libtelnet_get_telopt_local (struct libtelnet_t *telnet,
unsigned char telopt) {
int i;
/* search for entry, return state if found */
for (i = 0; i != telnet->q_size; ++i)
if (telnet->q[i].telopt == telopt)
return telnet->q[i].us & RFC1143_YES;
/* not found... so it's off */
return 0;
}
/* get remote state of specific telopt */
int libtelnet_get_telopt_remote (struct libtelnet_t *telnet,
unsigned char telopt) {
int i;
/* search for entry, return state if found */
for (i = 0; i != telnet->q_size; ++i)
if (telnet->q[i].telopt == telopt)
return telnet->q[i].him & RFC1143_YES;
/* not found... so it's off */
return 0;
}

View File

@ -120,7 +120,10 @@ enum libtelnet_event_type_t {
LIBTELNET_EV_DATA = 0,
LIBTELNET_EV_SEND,
LIBTELNET_EV_IAC,
LIBTELNET_EV_NEGOTIATE,
LIBTELNET_EV_WILL,
LIBTELNET_EV_WONT,
LIBTELNET_EV_DO,
LIBTELNET_EV_DONT,
LIBTELNET_EV_SUBNEGOTIATION,
LIBTELNET_EV_COMPRESS,
LIBTELNET_EV_WARNING,
@ -129,15 +132,23 @@ enum libtelnet_event_type_t {
/* event information */
struct libtelnet_event_t {
/* type of event */
enum libtelnet_event_type_t type;
/* command info: only for IAC event */
unsigned char command;
/* telopt info: for NEGOTIATE and SUBNEGOTIATION events */
unsigned char telopt;
/* data buffer: for DATA, SEND, SUBNEGOTIATION, and ERROR events */
unsigned char *buffer;
unsigned int size;
/* type of event */
enum libtelnet_event_type_t type;
/* IAC command */
unsigned char command;
/* telopt info: for negotiation events SUBNEGOTIATION */
unsigned char telopt;
/* accept status: for WILL and DO events */
unsigned char accept;
};
/* option negotiation state (RFC 1143) */
struct libtelnet_rfc1143_t {
unsigned char telopt;
char us:4, him:4;
};
/* event handler declaration */
@ -155,18 +166,22 @@ struct libtelnet_t {
z_stream *z_deflate;
z_stream *z_inflate;
#endif
/* RFC1143 option negotiation states */
struct libtelnet_rfc1143_t *q;
/* sub-request buffer */
unsigned char *buffer;
/* current size of the buffer */
unsigned int size;
/* length of data in the buffer */
unsigned int length;
unsigned int buffer_size;
/* current buffer write position (also length of buffer data) */
unsigned int buffer_pos;
/* current state */
enum libtelnet_state_t state;
/* processing mode */
enum libtelnet_mode_t mode;
/* current subnegotiation telopt */
unsigned char sb_telopt;
/* length of RFC1143 queue */
unsigned char q_size;
};
/* initialize a telnet state tracker */
@ -200,4 +215,12 @@ extern void libtelnet_send_subnegotiation(struct libtelnet_t *telnet,
/* begin sending compressed data (server only) */
extern void libtelnet_begin_compress2(struct libtelnet_t *telnet);
/* return the status of a specific TELNET option on our end (US) */
extern int libtelnet_get_telopt_local(struct libtelnet_t *telnet,
unsigned char telopt);
/* return the status of a specific TELNET option on remote end (HIM) */
extern int libtelnet_get_telopt_remote(struct libtelnet_t *telnet,
unsigned char telopt);
#endif /* !defined(LIBTELNET_INCLUDE) */

View File

@ -89,55 +89,32 @@ static void _event_handler(struct libtelnet_t *telnet,
case LIBTELNET_EV_SEND:
_send(sock, ev->buffer, ev->size);
break;
/* accept any options we want */
case LIBTELNET_EV_NEGOTIATE:
switch (ev->command) {
case LIBTELNET_WILL:
switch (ev->telopt) {
/* accept request to enable compression */
case LIBTELNET_TELOPT_COMPRESS2:
libtelnet_send_negotiate(telnet, LIBTELNET_DO, ev->telopt);
break;
/* server "promises" to echo, so turn off local echo */
case LIBTELNET_TELOPT_ECHO:
do_echo = 0;
libtelnet_send_negotiate(telnet, LIBTELNET_DO, ev->telopt);
break;
/* unknown -- reject */
default:
libtelnet_send_negotiate(telnet, LIBTELNET_DONT, ev->telopt);
break;
}
break;
/* request to enable remote feature (or receipt) */
case LIBTELNET_EV_WILL:
/* we accept COMPRESS2 (MCCP) */
if (ev->telopt == LIBTELNET_TELOPT_COMPRESS2)
ev->accept = 1;
case LIBTELNET_WONT:
switch (ev->telopt) {
/* server wants us to do echoing, by telling us it won't */
case LIBTELNET_TELOPT_ECHO:
do_echo = 1;
libtelnet_send_negotiate(telnet, LIBTELNET_DONT, ev->telopt);
break;
}
break;
case LIBTELNET_DO:
switch (ev->telopt) {
/* accept request to enable terminal-type requests */
case LIBTELNET_TELOPT_TTYPE:
libtelnet_send_negotiate(telnet, LIBTELNET_WILL, ev->telopt);
break;
/* unknown - reject */
default:
libtelnet_send_negotiate(telnet, LIBTELNET_WONT, ev->telopt);
break;
}
break;
case LIBTELNET_DONT:
/* ignore for now */
break;
/* we'll agree to turn off our echo if server wants us to stop */
else if (ev->telopt == LIBTELNET_TELOPT_ECHO) {
do_echo = 0;
ev->accept = 1;
}
break;
/* notification of disabling remote feature (or receipt) */
case LIBTELNET_EV_WONT:
if (ev->telopt == LIBTELNET_TELOPT_ECHO)
do_echo = 1;
break;
/* request to enable local feature (or receipt) */
case LIBTELNET_EV_DO:
/* we support the TTYPE option */
if (ev->telopt == LIBTELNET_TELOPT_TTYPE)
ev->accept = 1;
break;
/* demand to disable local feature (or receipt) */
case LIBTELNET_EV_DONT:
break;
/* respond to particular subnegotiations */
case LIBTELNET_EV_SUBNEGOTIATION:
/* respond with our terminal type */

View File

@ -191,12 +191,31 @@ static void _event_handler(struct libtelnet_t *telnet,
libtelnet_send_command(&conn->remote->telnet, ev->command);
break;
/* negotiation */
case LIBTELNET_EV_NEGOTIATE:
printf("%s IAC %s %d (%s)" COLOR_NORMAL "\n", conn->name,
get_cmd(ev->command), (int)ev->telopt, get_opt(ev->telopt));
libtelnet_send_negotiate(&conn->remote->telnet, ev->command,
/* negotiation, WILL */
case LIBTELNET_EV_WILL:
printf("%s IAC WILL %d (%s)" COLOR_NORMAL "\n", conn->name,
(int)ev->telopt, get_opt(ev->telopt));
libtelnet_send_negotiate(&conn->remote->telnet, LIBTELNET_WILL,
ev->telopt);
break;
/* negotiation, WONT */
case LIBTELNET_EV_WONT:
printf("%s IAC WONT %d (%s)" COLOR_NORMAL "\n", conn->name,
(int)ev->telopt, get_opt(ev->telopt));
libtelnet_send_negotiate(&conn->remote->telnet, LIBTELNET_WONT,
ev->telopt);
break;
/* negotiation, DO */
case LIBTELNET_EV_DO:
printf("%s IAC DO %d (%s)" COLOR_NORMAL "\n", conn->name,
(int)ev->telopt, get_opt(ev->telopt));
libtelnet_send_negotiate(&conn->remote->telnet, LIBTELNET_DO,
ev->telopt);
break;
case LIBTELNET_EV_DONT:
printf("%s IAC DONT %d (%s)" COLOR_NORMAL "\n", conn->name,
(int)ev->telopt, get_opt(ev->telopt));
libtelnet_send_negotiate(&conn->remote->telnet, LIBTELNET_DONT,
ev->telopt);
break;
/* subnegotiation */