implement request side of RFC1143
diff --git a/README b/README
index f6b234d..1bb793d 100644
--- a/README
+++ b/README
@@ -29,14 +29,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
=====================================================================
diff --git a/libtelnet.c b/libtelnet.c
index 1860ece..ba44a4c 100644
--- a/libtelnet.c
+++ b/libtelnet.c
@@ -125,11 +125,98 @@
return LIBTELNET_EOK;
}
-/* negotiation handling magic */
-static void _negotiate(struct libtelnet_t *telnet, unsigned char cmd,
+/* 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) {
+ 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) {
- struct libtelnet_rfc1143_t *qtmp;
- int q;
+ libtelnet_rfc1143_t q;
/* in PROXY mode, just pass it thru and do nothing */
if (telnet->flags & LIBTELNET_FLAG_PROXY) {
@@ -151,150 +238,141 @@
}
/* 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;
- }
+ q = _get_rfc1143(telnet, telopt);
/* start processing... */
switch (cmd) {
/* request to enable option on remote end or confirm DO */
case LIBTELNET_WILL:
- switch (telnet->q[q].him) {
+ switch (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);
+ q.him = RFC1143_YES;
+ _set_rfc1143(telnet, q);
+ _send_negotiate(telnet, LIBTELNET_DO, telopt);
} else
- libtelnet_send_negotiate(telnet, LIBTELNET_DONT, telopt);
+ _send_negotiate(telnet, LIBTELNET_DONT, telopt);
break;
case RFC1143_YES:
break;
case RFC1143_WANTNO:
- telnet->q[q].him = RFC1143_NO;
+ q.him = RFC1143_NO;
+ _set_rfc1143(telnet, q);
_error(telnet, __LINE__, __func__, LIBTELNET_EPROTOCOL, 0,
"DONT answered by WILL");
break;
case RFC1143_WANTNO_OP:
- telnet->q[q].him = RFC1143_YES;
+ q.him = RFC1143_YES;
+ _set_rfc1143(telnet, q);
_error(telnet, __LINE__, __func__, LIBTELNET_EPROTOCOL, 0,
"DONT answered by WILL");
break;
case RFC1143_WANTYES:
- telnet->q[q].him = RFC1143_YES;
+ q.him = RFC1143_YES;
+ _set_rfc1143(telnet, q);
break;
case RFC1143_WANTYES_OP:
- telnet->q[q].him = RFC1143_WANTNO;
- libtelnet_send_negotiate(telnet, LIBTELNET_DONT, telopt);
+ 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 (telnet->q[q].him) {
+ switch (q.him) {
case RFC1143_NO:
break;
case RFC1143_YES:
- telnet->q[q].him = RFC1143_NO;
- libtelnet_send_negotiate(telnet, LIBTELNET_DONT, telopt);
+ 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:
- telnet->q[q].him = RFC1143_NO;
+ q.him = RFC1143_NO;
+ _set_rfc1143(telnet, q);
_event(telnet, LIBTELNET_EV_WONT, 0, telopt,
0, 0);
break;
case RFC1143_WANTNO_OP:
- telnet->q[q].him = RFC1143_WANTYES;
+ 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:
- telnet->q[q].him = RFC1143_NO;
+ q.him = RFC1143_NO;
+ _set_rfc1143(telnet, q);
break;
}
break;
/* request to enable option on local end or confirm WILL */
case LIBTELNET_DO:
- switch (telnet->q[q].us) {
+ switch (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);
+ q.us = RFC1143_YES;
+ _set_rfc1143(telnet, q);
+ _send_negotiate(telnet, LIBTELNET_WILL, telopt);
} else
- libtelnet_send_negotiate(telnet, LIBTELNET_WONT, telopt);
+ _send_negotiate(telnet, LIBTELNET_WONT, telopt);
break;
case RFC1143_YES:
break;
case RFC1143_WANTNO:
- telnet->q[q].us = RFC1143_NO;
+ q.us = RFC1143_NO;
+ _set_rfc1143(telnet, q);
_error(telnet, __LINE__, __func__, LIBTELNET_EPROTOCOL, 0,
"WONT answered by DO");
break;
case RFC1143_WANTNO_OP:
- telnet->q[q].us = RFC1143_YES;
+ q.us = RFC1143_YES;
+ _set_rfc1143(telnet, q);
_error(telnet, __LINE__, __func__, LIBTELNET_EPROTOCOL, 0,
"WONT answered by DO");
break;
case RFC1143_WANTYES:
- telnet->q[q].us = RFC1143_YES;
+ q.us = RFC1143_YES;
+ _set_rfc1143(telnet, q);
break;
case RFC1143_WANTYES_OP:
- telnet->q[q].us = RFC1143_WANTNO;
- libtelnet_send_negotiate(telnet, LIBTELNET_WONT, telopt);
+ 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 (telnet->q[q].us) {
+ switch (q.us) {
case RFC1143_NO:
break;
case RFC1143_YES:
- telnet->q[q].us = RFC1143_NO;
- libtelnet_send_negotiate(telnet, LIBTELNET_WONT, telopt);
+ 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:
- telnet->q[q].us = RFC1143_NO;
+ q.us = RFC1143_NO;
+ _set_rfc1143(telnet, q);
_event(telnet, LIBTELNET_EV_WONT, 0, telopt, 0, 0);
break;
case RFC1143_WANTNO_OP:
- telnet->q[q].us = RFC1143_WANTYES;
+ 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:
- telnet->q[q].us = RFC1143_NO;
+ q.us = RFC1143_NO;
+ _set_rfc1143(telnet, q);
break;
}
break;
@@ -593,46 +671,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 };
@@ -641,9 +679,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) */
@@ -716,29 +865,3 @@
_event(telnet, LIBTELNET_EV_SEND, 0, 0, compress2, sizeof(compress2));
#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;
-}
diff --git a/libtelnet.h b/libtelnet.h
index 2ac577b..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;
@@ -214,12 +215,4 @@
/* begin sending compressed data (server only) */
extern void libtelnet_begin_compress2(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) */