Merge branch 'master' into rfc1143
diff --git a/README b/README
index bcf98fe..bcb55c8 100644
--- a/README
+++ b/README
@@ -18,7 +18,6 @@
*** TODO ***
- - RFC 1143 option negotiation algorithm
- automatic MCCP2 handling (controllable by host app)
? ZMP parsing
? MSSP parsing
@@ -29,14 +28,14 @@
=====================================================================
libtelnet provides safe and correct handling of the core TELNET
-protocol. It does not include any "smarts," and all use of the
-protocol (such as deciding which options to support, enabling
-and disabling options, or processing subrequests) must be implemented
-by the application author.
+protocol. In addition to the base TELNET protocol, libtelnet also
+implements the Q method of TELNET option negotiation. libtelnet
+can be used for writing servers, clients, or proxies.
For more information on the TELNET protocol, see:
http://www.faqs.org/rfcs/rfc854.html
+ http://www.faqs.org/rfcs/rfc1143.html
II. LIBTELNET API
=====================================================================
@@ -222,20 +221,76 @@
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
@@ -343,11 +398,9 @@
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
diff --git a/libtelnet.c b/libtelnet.c
index baf74f7..5bf5295 100644
--- a/libtelnet.c
+++ b/libtelnet.c
@@ -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_count = sizeof(_buffer_sizes) /
sizeof(_buffer_sizes[0]);
-/* event dispatch helper */
-static void _event(libtelnet_t *telnet, libtelnet_event_type_t type,
+/* 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(libtelnet_t *telnet, libtelnet_event_type_t type,
unsigned char command, unsigned char telopt, unsigned char *buffer,
unsigned int size) {
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 */
@@ -109,6 +125,260 @@
return LIBTELNET_EOK;
}
+/* push bytes out, compressing them first if need be */
+static void _send(libtelnet_t *telnet, unsigned char *buffer,
+ unsigned int size) {
+#ifdef HAVE_ZLIB
+ /* if we have a deflate (compression) zlib box, use it */
+ if (telnet->z != 0 && telnet->flags & LIBTELNET_PFLAG_DEFLATE) {
+ unsigned char deflate_buffer[1024];
+ int rs;
+
+ /* initialize z state */
+ telnet->z->next_in = buffer;
+ telnet->z->avail_in = size;
+ telnet->z->next_out = deflate_buffer;
+ telnet->z->avail_out = sizeof(deflate_buffer);
+
+ /* deflate until buffer exhausted and all output is produced */
+ while (telnet->z->avail_in > 0 || telnet->z->avail_out == 0) {
+ /* compress */
+ if ((rs = deflate(telnet->z, Z_SYNC_FLUSH)) != Z_OK) {
+ _error(telnet, __LINE__, __func__, LIBTELNET_ECOMPRESS, 1,
+ "deflate() failed: %s", zError(rs));
+ deflateEnd(telnet->z);
+ free(telnet->z);
+ telnet->z = 0;
+ break;
+ }
+
+ _event(telnet, LIBTELNET_EV_SEND, 0, 0, deflate_buffer,
+ sizeof(deflate_buffer) - telnet->z->avail_out);
+
+ /* prepare output buffer for next run */
+ telnet->z->next_out = deflate_buffer;
+ telnet->z->avail_out = sizeof(deflate_buffer);
+ }
+
+ /* COMPRESS2 is not negotiated, just send */
+ } else
+#endif /* HAVE_ZLIB */
+ _event(telnet, LIBTELNET_EV_SEND, 0, 0, buffer, size);
+}
+
+/* retrieve RFC1143 option state */
+libtelnet_rfc1143_t _get_rfc1143(libtelnet_t *telnet, unsigned char telopt) {
+ static const libtelnet_rfc1143_t empty = { 0, 0, 0};
+ int i;
+
+ /* search for entry */
+ for (i = 0; i != telnet->q_size; ++i)
+ if (telnet->q[i].telopt == telopt)
+ return telnet->q[i];
+
+ /* not found, return empty value */
+ return empty;
+}
+
+/* save RFC1143 option state */
+void _set_rfc1143(libtelnet_t *telnet, libtelnet_rfc1143_t q) {
+ libtelnet_rfc1143_t *qtmp;
+ int i;
+
+ /* search for entry */
+ for (i = 0; i != telnet->q_size; ++i) {
+ if (telnet->q[i].telopt == q.telopt) {
+ telnet->q[i] = q;
+ 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 = (libtelnet_rfc1143_t *)malloc(sizeof(
+ libtelnet_rfc1143_t) * (telnet->q_size + 1))) == 0) {
+ _error(telnet, __LINE__, __func__, LIBTELNET_ENOMEM, 0,
+ "malloc() failed: %s", strerror(errno));
+ return;
+ }
+ telnet->q = qtmp;
+ telnet->q[telnet->q_size++] = q;
+}
+
+/* send a negotiation without going through the RFC1143 checks */
+static void _send_negotiate(libtelnet_t *telnet, unsigned char cmd,
+ unsigned char opt) {
+ unsigned char bytes[3] = { LIBTELNET_IAC, cmd, opt };
+ _send(telnet, bytes, 3);
+}
+
+/* negotiation handling magic for RFC1143 */
+static void _negotiate(libtelnet_t *telnet, unsigned char cmd,
+ unsigned char telopt) {
+ libtelnet_rfc1143_t q;
+
+ /* in PROXY mode, just pass it thru and do nothing */
+ if (telnet->flags & LIBTELNET_FLAG_PROXY) {
+ switch (cmd) {
+ case LIBTELNET_WILL:
+ _event(telnet, LIBTELNET_EV_WILL, 0, telopt, 0, 0);
+ break;
+ case LIBTELNET_WONT:
+ _event(telnet, LIBTELNET_EV_WONT, 0, telopt, 0, 0);
+ break;
+ case LIBTELNET_DO:
+ _event(telnet, LIBTELNET_EV_DO, 0, telopt, 0, 0);
+ break;
+ case LIBTELNET_DONT:
+ _event(telnet, LIBTELNET_EV_DONT, 0, telopt, 0, 0);
+ break;
+ }
+ return;
+ }
+
+ /* lookup the current state of the option */
+ q = _get_rfc1143(telnet, telopt);
+
+ /* start processing... */
+ switch (cmd) {
+ /* request to enable option on remote end or confirm DO */
+ case LIBTELNET_WILL:
+ switch (q.him) {
+ case RFC1143_NO:
+ if (_event(telnet, LIBTELNET_EV_WILL, cmd, telopt, 0, 0) == 1) {
+ q.him = RFC1143_YES;
+ _set_rfc1143(telnet, q);
+ _send_negotiate(telnet, LIBTELNET_DO, telopt);
+ } else
+ _send_negotiate(telnet, LIBTELNET_DONT, telopt);
+ break;
+ case RFC1143_YES:
+ break;
+ case RFC1143_WANTNO:
+ q.him = RFC1143_NO;
+ _set_rfc1143(telnet, q);
+ _error(telnet, __LINE__, __func__, LIBTELNET_EPROTOCOL, 0,
+ "DONT answered by WILL");
+ break;
+ case RFC1143_WANTNO_OP:
+ q.him = RFC1143_YES;
+ _set_rfc1143(telnet, q);
+ _error(telnet, __LINE__, __func__, LIBTELNET_EPROTOCOL, 0,
+ "DONT answered by WILL");
+ break;
+ case RFC1143_WANTYES:
+ q.him = RFC1143_YES;
+ _set_rfc1143(telnet, q);
+ break;
+ case RFC1143_WANTYES_OP:
+ q.him = RFC1143_WANTNO;
+ _set_rfc1143(telnet, q);
+ _send_negotiate(telnet, LIBTELNET_DONT, telopt);
+ break;
+ }
+ break;
+
+ /* request to disable option on remote end, confirm DONT, reject DO */
+ case LIBTELNET_WONT:
+ switch (q.him) {
+ case RFC1143_NO:
+ break;
+ case RFC1143_YES:
+ q.him = RFC1143_NO;
+ _set_rfc1143(telnet, q);
+ _send_negotiate(telnet, LIBTELNET_DONT, telopt);
+ _event(telnet, LIBTELNET_EV_WONT, 0, telopt,
+ 0, 0);
+ break;
+ case RFC1143_WANTNO:
+ q.him = RFC1143_NO;
+ _set_rfc1143(telnet, q);
+ _event(telnet, LIBTELNET_EV_WONT, 0, telopt,
+ 0, 0);
+ break;
+ case RFC1143_WANTNO_OP:
+ q.him = RFC1143_WANTYES;
+ _set_rfc1143(telnet, q);
+ _event(telnet, LIBTELNET_EV_DO, 0, telopt,
+ 0, 0);
+ break;
+ case RFC1143_WANTYES:
+ case RFC1143_WANTYES_OP:
+ q.him = RFC1143_NO;
+ _set_rfc1143(telnet, q);
+ break;
+ }
+ break;
+
+ /* request to enable option on local end or confirm WILL */
+ case LIBTELNET_DO:
+ switch (q.us) {
+ case RFC1143_NO:
+ if (_event(telnet, LIBTELNET_EV_DO, cmd, telopt, 0, 0) == 1) {
+ q.us = RFC1143_YES;
+ _set_rfc1143(telnet, q);
+ _send_negotiate(telnet, LIBTELNET_WILL, telopt);
+ } else
+ _send_negotiate(telnet, LIBTELNET_WONT, telopt);
+ break;
+ case RFC1143_YES:
+ break;
+ case RFC1143_WANTNO:
+ q.us = RFC1143_NO;
+ _set_rfc1143(telnet, q);
+ _error(telnet, __LINE__, __func__, LIBTELNET_EPROTOCOL, 0,
+ "WONT answered by DO");
+ break;
+ case RFC1143_WANTNO_OP:
+ q.us = RFC1143_YES;
+ _set_rfc1143(telnet, q);
+ _error(telnet, __LINE__, __func__, LIBTELNET_EPROTOCOL, 0,
+ "WONT answered by DO");
+ break;
+ case RFC1143_WANTYES:
+ q.us = RFC1143_YES;
+ _set_rfc1143(telnet, q);
+ break;
+ case RFC1143_WANTYES_OP:
+ q.us = RFC1143_WANTNO;
+ _set_rfc1143(telnet, q);
+ _send_negotiate(telnet, LIBTELNET_WONT, telopt);
+ break;
+ }
+ break;
+
+ /* request to disable option on local end, confirm WONT, reject WILL */
+ case LIBTELNET_DONT:
+ switch (q.us) {
+ case RFC1143_NO:
+ break;
+ case RFC1143_YES:
+ q.us = RFC1143_NO;
+ _set_rfc1143(telnet, q);
+ _send_negotiate(telnet, LIBTELNET_WONT, telopt);
+ _event(telnet, LIBTELNET_EV_DONT, 0, telopt, 0, 0);
+ break;
+ case RFC1143_WANTNO:
+ q.us = RFC1143_NO;
+ _set_rfc1143(telnet, q);
+ _event(telnet, LIBTELNET_EV_WONT, 0, telopt, 0, 0);
+ break;
+ case RFC1143_WANTNO_OP:
+ q.us = RFC1143_WANTYES;
+ _set_rfc1143(telnet, q);
+ _event(telnet, LIBTELNET_EV_WILL, 0, telopt, 0, 0);
+ break;
+ case RFC1143_WANTYES:
+ case RFC1143_WANTYES_OP:
+ q.us = RFC1143_NO;
+ _set_rfc1143(telnet, q);
+ break;
+ }
+ break;
+ }
+}
+
/* initialize a telnet state tracker */
void libtelnet_init(libtelnet_t *telnet, libtelnet_event_handler_t eh,
unsigned char flags, void *user_data) {
@@ -124,8 +394,8 @@
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 */
@@ -137,6 +407,13 @@
free(telnet->z);
telnet->z = 0;
}
+
+ /* free RFC1143 queue */
+ if (telnet->q) {
+ free(telnet->q);
+ telnet->q = 0;
+ telnet->q_size = 0;
+ }
}
/* push a byte into the telnet buffer */
@@ -146,10 +423,10 @@
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;
}
@@ -172,11 +449,11 @@
}
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;
}
@@ -235,22 +512,22 @@
/* 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;
@@ -258,7 +535,7 @@
/* 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;
@@ -285,7 +562,7 @@
/* 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
/* received COMPRESS2 begin marker, setup our zlib box and
@@ -390,46 +667,6 @@
_process(telnet, buffer, size);
}
-static void _send(libtelnet_t *telnet, unsigned char *buffer,
- unsigned int size) {
-#ifdef HAVE_ZLIB
- /* if we have a deflate (compression) zlib box, use it */
- if (telnet->z != 0 && telnet->flags & LIBTELNET_PFLAG_DEFLATE) {
- unsigned char deflate_buffer[1024];
- int rs;
-
- /* initialize z state */
- telnet->z->next_in = buffer;
- telnet->z->avail_in = size;
- telnet->z->next_out = deflate_buffer;
- telnet->z->avail_out = sizeof(deflate_buffer);
-
- /* deflate until buffer exhausted and all output is produced */
- while (telnet->z->avail_in > 0 || telnet->z->avail_out == 0) {
- /* compress */
- if ((rs = deflate(telnet->z, Z_SYNC_FLUSH)) != Z_OK) {
- _error(telnet, __LINE__, __func__, LIBTELNET_ECOMPRESS, 1,
- "deflate() failed: %s", zError(rs));
- deflateEnd(telnet->z);
- free(telnet->z);
- telnet->z = 0;
- break;
- }
-
- _event(telnet, LIBTELNET_EV_SEND, 0, 0, deflate_buffer,
- sizeof(deflate_buffer) - telnet->z->avail_out);
-
- /* prepare output buffer for next run */
- telnet->z->next_out = deflate_buffer;
- telnet->z->avail_out = sizeof(deflate_buffer);
- }
-
- /* COMPRESS2 is not negotiated, just send */
- } else
-#endif /* HAVE_ZLIB */
- _event(telnet, LIBTELNET_EV_SEND, 0, 0, buffer, size);
-}
-
/* send an iac command */
void libtelnet_send_command(libtelnet_t *telnet, unsigned char cmd) {
unsigned char bytes[2] = { LIBTELNET_IAC, cmd };
@@ -438,9 +675,120 @@
/* send negotiation */
void libtelnet_send_negotiate(libtelnet_t *telnet, unsigned char cmd,
- unsigned char opt) {
- unsigned char bytes[3] = { LIBTELNET_IAC, cmd, opt };
- _send(telnet, bytes, 3);
+ unsigned char telopt) {
+ libtelnet_rfc1143_t q;
+
+ /* if we're in proxy mode, just send it now */
+ if (telnet->flags & LIBTELNET_FLAG_PROXY) {
+ unsigned char bytes[3] = { LIBTELNET_IAC, cmd, telopt };
+ _send(telnet, bytes, 3);
+ return;
+ }
+
+ /* get current option states */
+ q = _get_rfc1143(telnet, telopt);
+
+ switch (cmd) {
+ /* advertise willingess to support an option */
+ case LIBTELNET_WILL:
+ switch (q.us) {
+ case RFC1143_NO:
+ q.us = RFC1143_WANTYES;
+ _set_rfc1143(telnet, q);
+ _negotiate(telnet, LIBTELNET_WILL, telopt);
+ break;
+ case RFC1143_YES:
+ break;
+ case RFC1143_WANTNO:
+ q.us = RFC1143_WANTNO_OP;
+ _set_rfc1143(telnet, q);
+ break;
+ case RFC1143_WANTYES:
+ break;
+ case RFC1143_WANTNO_OP:
+ break;
+ case RFC1143_WANTYES_OP:
+ q.us = RFC1143_WANTYES;
+ _set_rfc1143(telnet, q);
+ break;
+ }
+ break;
+
+ /* force turn-off of locally enabled option */
+ case LIBTELNET_WONT:
+ switch (q.us) {
+ case RFC1143_NO:
+ break;
+ case RFC1143_YES:
+ q.us = RFC1143_WANTNO;
+ _set_rfc1143(telnet, q);
+ _negotiate(telnet, LIBTELNET_WONT, telopt);
+ break;
+ case RFC1143_WANTNO:
+ break;
+ case RFC1143_WANTYES:
+ q.us = RFC1143_WANTYES_OP;
+ _set_rfc1143(telnet, q);
+ break;
+ case RFC1143_WANTNO_OP:
+ q.us = RFC1143_WANTNO;
+ _set_rfc1143(telnet, q);
+ break;
+ case RFC1143_WANTYES_OP:
+ break;
+ }
+ break;
+
+ /* ask remote end to enable an option */
+ case LIBTELNET_DO:
+ switch (q.him) {
+ case RFC1143_NO:
+ q.him = RFC1143_WANTYES;
+ _set_rfc1143(telnet, q);
+ _negotiate(telnet, LIBTELNET_DO, telopt);
+ break;
+ case RFC1143_YES:
+ break;
+ case RFC1143_WANTNO:
+ q.him = RFC1143_WANTNO_OP;
+ _set_rfc1143(telnet, q);
+ break;
+ case RFC1143_WANTYES:
+ break;
+ case RFC1143_WANTNO_OP:
+ break;
+ case RFC1143_WANTYES_OP:
+ q.him = RFC1143_WANTYES;
+ _set_rfc1143(telnet, q);
+ break;
+ }
+ break;
+
+ /* demand remote end disable an option */
+ case LIBTELNET_DONT:
+ switch (q.him) {
+ case RFC1143_NO:
+ break;
+ case RFC1143_YES:
+ q.him = RFC1143_WANTNO;
+ _set_rfc1143(telnet, q);
+ _negotiate(telnet, LIBTELNET_DONT, telopt);
+ break;
+ case RFC1143_WANTNO:
+ break;
+ case RFC1143_WANTYES:
+ q.him = RFC1143_WANTYES_OP;
+ _set_rfc1143(telnet, q);
+ break;
+ case RFC1143_WANTNO_OP:
+ q.him = RFC1143_WANTNO;
+ _set_rfc1143(telnet, q);
+ break;
+ case RFC1143_WANTYES_OP:
+ break;
+ }
+ break;
+ }
}
/* send non-command data (escapes IAC bytes) */
@@ -495,13 +843,6 @@
unsigned char compress2[] = { LIBTELNET_IAC, LIBTELNET_SB,
LIBTELNET_TELOPT_COMPRESS2, LIBTELNET_IAC, LIBTELNET_SE };
- /* don't do this if we've already got a compression stream */
- if (telnet->z != 0) {
- _error(telnet, __LINE__, __func__, LIBTELNET_EBADVAL, 0,
- "compression already enabled");
- return;
- }
-
/* attempt to create output stream first, bail if we can't */
if (_init_zlib(telnet, 1, 0) != LIBTELNET_EOK)
return;
diff --git a/libtelnet.h b/libtelnet.h
index f926320..8db4d6c 100644
--- a/libtelnet.h
+++ b/libtelnet.h
@@ -15,6 +15,7 @@
/* forward declarations */
typedef struct libtelnet_t libtelnet_t;
typedef struct libtelnet_event_t libtelnet_event_t;
+typedef struct libtelnet_rfc1143_t libtelnet_rfc1143_t;
typedef enum libtelnet_mode_t libtelnet_mode_t;
typedef enum libtelnet_state_t libtelnet_state_t;
typedef enum libtelnet_error_t libtelnet_error_t;
@@ -122,7 +123,10 @@
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,
@@ -131,15 +135,23 @@
/* 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 */
@@ -156,18 +168,22 @@
/* zlib (mccp2) compression */
z_stream *z;
#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;
/* option flags */
unsigned char flags;
/* current subnegotiation telopt */
unsigned char sb_telopt;
+ /* length of RFC1143 queue */
+ unsigned char q_size;
};
/* initialize a telnet state tracker */
diff --git a/telnet-client.c b/telnet-client.c
index c79977b..ef1b8e6 100644
--- a/telnet-client.c
+++ b/telnet-client.c
@@ -89,55 +89,32 @@
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 */
diff --git a/telnet-proxy.c b/telnet-proxy.c
index 078806d..45480d8 100644
--- a/telnet-proxy.c
+++ b/telnet-proxy.c
@@ -191,12 +191,31 @@
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 */