initial client-only MCCP2 support
diff --git a/Makefile b/Makefile
index 83978da..5b59f9a 100644
--- a/Makefile
+++ b/Makefile
@@ -1,2 +1,5 @@
+CFLAGS = -Wall -g -O0 -DHAVE_ZLIB
+LFLAGS = -lz
+
telnet-proxy: telnet-proxy.c libtelnet.c libtelnet.h
- $(CC) -o telnet-proxy -Wall telnet-proxy.c libtelnet.c
+ $(CC) -o telnet-proxy $(CFLAGS) telnet-proxy.c libtelnet.c $(LFLAGS)
diff --git a/libtelnet.c b/libtelnet.c
index 3921b16..37b7b9b 100644
--- a/libtelnet.c
+++ b/libtelnet.c
@@ -1,4 +1,7 @@
/*
+ * Sean Middleditch
+ * sean@sourcemud.org
+ *
* The author or authors of this code dedicate any and all copyright interest
* in this code to the public domain. We make this dedication for the benefit
* of the public at large and to the detriment of our heirs and successors. We
@@ -7,6 +10,12 @@
*/
#include <malloc.h>
+#include <string.h>
+
+#ifdef HAVE_ZLIB
+#include "zlib.h"
+#endif
+
#include "libtelnet.h"
/* buffer sizes */
@@ -22,20 +31,25 @@
/* initialize a telnet state tracker */
void libtelnet_init(struct libtelnet_t *telnet) {
- telnet->state = LIBTELNET_STATE_TEXT;
- telnet->buffer = 0;
- telnet->size = 0;
- telnet->length = 0;
+ memset(telnet, 0, sizeof(struct libtelnet_t));
}
/* free up any memory allocated by a state tracker */
void libtelnet_free(struct libtelnet_t *telnet) {
+ /* free sub-request buffer */
if (telnet->buffer != 0) {
free(telnet->buffer);
telnet->buffer = 0;
telnet->size = 0;
telnet->length = 0;
}
+
+ /* free zlib box */
+ if (telnet->zlib != 0) {
+ inflateEnd(telnet->zlib);
+ free(telnet->zlib);
+ telnet->zlib = 0;
+ }
}
/* push a byte into the telnet buffer */
@@ -78,8 +92,7 @@
return LIBTELNET_ERROR_OK;
}
-/* push a single byte into the state tracker */
-void libtelnet_push(struct libtelnet_t *telnet, unsigned char *buffer,
+static void _process(struct libtelnet_t *telnet, unsigned char *buffer,
unsigned int size, void *user_data) {
unsigned char byte;
unsigned int i, start;
@@ -87,7 +100,7 @@
byte = buffer[i];
switch (telnet->state) {
/* regular data */
- case LIBTELNET_STATE_TEXT:
+ case LIBTELNET_STATE_DATA:
/* on an IAC byte, pass through all pending bytes and
* switch states */
if (byte == LIBTELNET_IAC) {
@@ -122,13 +135,13 @@
case LIBTELNET_IAC:
libtelnet_data_cb(telnet, &byte, 1, user_data);
start = i + 1;
- telnet->state = LIBTELNET_STATE_TEXT;
+ telnet->state = LIBTELNET_STATE_DATA;
break;
/* some other command */
default:
libtelnet_command_cb(telnet, byte, user_data);
start = i + 1;
- telnet->state = LIBTELNET_STATE_TEXT;
+ telnet->state = LIBTELNET_STATE_DATA;
}
break;
@@ -136,22 +149,22 @@
case LIBTELNET_STATE_DO:
libtelnet_negotiate_cb(telnet, LIBTELNET_DO, byte, user_data);
start = i + 1;
- telnet->state = LIBTELNET_STATE_TEXT;
+ telnet->state = LIBTELNET_STATE_DATA;
break;
case LIBTELNET_STATE_DONT:
libtelnet_negotiate_cb(telnet, LIBTELNET_DONT, byte, user_data);
start = i + 1;
- telnet->state = LIBTELNET_STATE_TEXT;
+ telnet->state = LIBTELNET_STATE_DATA;
break;
case LIBTELNET_STATE_WILL:
libtelnet_negotiate_cb(telnet, LIBTELNET_WILL, byte, user_data);
start = i + 1;
- telnet->state = LIBTELNET_STATE_TEXT;
+ telnet->state = LIBTELNET_STATE_DATA;
break;
case LIBTELNET_STATE_WONT:
libtelnet_negotiate_cb(telnet, LIBTELNET_WONT, byte, user_data);
start = i + 1;
- telnet->state = LIBTELNET_STATE_TEXT;
+ telnet->state = LIBTELNET_STATE_DATA;
break;
/* subrequest -- buffer bytes until end request */
@@ -163,7 +176,7 @@
} else if (_buffer_byte(telnet, byte, user_data) !=
LIBTELNET_ERROR_OK) {
start = i + 1;
- telnet->state = LIBTELNET_STATE_TEXT;
+ telnet->state = LIBTELNET_STATE_DATA;
} else {
telnet->state = LIBTELNET_STATE_SB;
}
@@ -174,20 +187,51 @@
switch (byte) {
/* end subrequest */
case LIBTELNET_SE:
+ /* return to default state */
+ start = i + 1;
+ telnet->state = LIBTELNET_STATE_DATA;
+
/* zero-size buffer is a protocol error */
if (telnet->length == 0) {
libtelnet_error_cb(telnet, LIBTELNET_ERROR_PROTOCOL,
user_data);
- /* process */
- } else {
- libtelnet_subrequest_cb(telnet, telnet->buffer[0],
- telnet->buffer + 1, telnet->length - 1, user_data);
- telnet->length = 0;
+ break;
}
-
- /* return to default state */
- start = i + 1;
- telnet->state = LIBTELNET_STATE_TEXT;
+
+ /* invoke callback */
+ libtelnet_subrequest_cb(telnet, telnet->buffer[0],
+ telnet->buffer + 1, telnet->length - 1, user_data);
+ telnet->length = 0;
+
+ /* if this was MCCP2, enable compression stream */
+ if (telnet->buffer[0] == LIBTELNET_OPTION_COMPRESS2) {
+ /* allocate zstream box */
+ if ((telnet->zlib = (z_stream *)malloc(sizeof(z_stream)))
+ == 0) {
+ libtelnet_error_cb(telnet,
+ LIBTELNET_ERROR_NOMEM, user_data);
+ }
+
+ /* initialize */
+ memset(telnet->zlib, 0, sizeof(z_stream));
+ if (inflateInit(telnet->zlib) != Z_OK) {
+ free(telnet->zlib);
+ telnet->zlib = 0;
+ libtelnet_error_cb(telnet,
+ LIBTELNET_ERROR_UNKNOWN, user_data);
+ break;
+ }
+
+ /* any remaining bytes in the buffer are compressed.
+ * we have to re-invoke libtelnet_push to get those
+ * bytes inflated and abort trying to process the
+ * remaining compressed bytes in the current _process
+ * buffer argument
+ */
+ libtelnet_push(telnet, &buffer[i + 1], size - i - 1,
+ user_data);
+ return;
+ }
break;
/* escaped IAC byte */
case LIBTELNET_IAC:
@@ -195,7 +239,7 @@
if (_buffer_byte(telnet, LIBTELNET_IAC, user_data) !=
LIBTELNET_ERROR_OK) {
start = i + 1;
- telnet->state = LIBTELNET_STATE_TEXT;
+ telnet->state = LIBTELNET_STATE_DATA;
} else {
telnet->state = LIBTELNET_STATE_SB;
}
@@ -205,7 +249,7 @@
libtelnet_error_cb(telnet, LIBTELNET_ERROR_PROTOCOL,
user_data);
start = i + 1;
- telnet->state = LIBTELNET_STATE_TEXT;
+ telnet->state = LIBTELNET_STATE_DATA;
break;
}
break;
@@ -217,6 +261,56 @@
libtelnet_data_cb(telnet, &buffer[start], i - start, user_data);
}
+/* push a bytes into the state tracker */
+void libtelnet_push(struct libtelnet_t *telnet, unsigned char *buffer,
+ unsigned int size, void *user_data) {
+ /* if COMPRESS2 has been negotiated and we have a zlib box, we
+ * need to inflate the buffer before processing
+ */
+ if (telnet->zlib) {
+ unsigned char inflate_buffer[4096];
+ int rs;
+
+ /* initialize zlib state */
+ telnet->zlib->next_in = buffer;
+ telnet->zlib->avail_in = size;
+ telnet->zlib->next_out = inflate_buffer;
+ telnet->zlib->avail_out = sizeof(inflate_buffer);
+
+ /* inflate until buffer exhausted and all output is produced */
+ while (telnet->zlib->avail_in > 0 || telnet->zlib->avail_out == 0) {
+ /* reset output buffer */
+
+ /* decompress */
+ rs = inflate(telnet->zlib, Z_SYNC_FLUSH);
+
+ /* process the decompressed bytes on success */
+ if (rs == Z_OK || rs == Z_STREAM_END)
+ _process(telnet, inflate_buffer, sizeof(inflate_buffer) -
+ telnet->zlib->avail_out, user_data);
+ else
+ libtelnet_error_cb(telnet, LIBTELNET_ERROR_UNKNOWN,
+ user_data);
+
+ /* prepare output buffer for next run */
+ telnet->zlib->next_out = inflate_buffer;
+ telnet->zlib->avail_out = sizeof(inflate_buffer);
+
+ /* on error (or on end of stream) disable further inflation */
+ if (rs != Z_OK) {
+ inflateEnd(telnet->zlib);
+ free(telnet->zlib);
+ telnet->zlib = 0;
+ break;
+ }
+ }
+
+ /* COMPRESS2 is not negotiated, just ignore */
+ } else {
+ _process(telnet, buffer, size, user_data);
+ }
+}
+
/* send an iac command */
void libtelnet_send_command(struct libtelnet_t *telnet, unsigned char cmd,
void *user_data) {
diff --git a/libtelnet.h b/libtelnet.h
index 16e16f0..1ad8e3d 100644
--- a/libtelnet.h
+++ b/libtelnet.h
@@ -1,4 +1,7 @@
/*
+ * Sean Middleditch
+ * sean@sourcemud.org
+ *
* The author or authors of this code dedicate any and all copyright interest
* in this code to the public domain. We make this dedication for the benefit
* of the public at large and to the detriment of our heirs and successors. We
@@ -22,11 +25,12 @@
#define LIBTELNET_OPTION_BINARY 0
#define LIBTELNET_OPTION_ECHO 1
#define LIBTELNET_OPTION_NAWS 31
+#define LIBTELNET_OPTION_COMPRESS2 86
#define LIBTELNET_OPTION_ZMP 93
/* telnet states */
enum libtelnet_state_t {
- LIBTELNET_STATE_TEXT = 0,
+ LIBTELNET_STATE_DATA = 0,
LIBTELNET_STATE_IAC,
LIBTELNET_STATE_DO,
LIBTELNET_STATE_DONT,
@@ -47,6 +51,10 @@
/* state tracker */
struct libtelnet_t {
+ /* zlib (mccp2) compression */
+#ifdef HAVE_ZLIB
+ z_stream *zlib;
+#endif
/* sub-request buffer */
unsigned char *buffer;
/* current size of the buffer */
diff --git a/telnet-proxy.c b/telnet-proxy.c
index 2f1904f..9a38af6 100644
--- a/telnet-proxy.c
+++ b/telnet-proxy.c
@@ -20,6 +20,10 @@
#include <ctype.h>
#include <unistd.h>
+#ifdef HAVE_ZLIB
+#include "zlib.h"
+#endif
+
#include "libtelnet.h"
struct conn_t {
@@ -177,6 +181,15 @@
printf("%s IAC %s %d (%s)\e[0m\n", conn->name, get_cmd(cmd),
(int)opt, get_opt(opt));
+ /* FIXME: HACK: allow MCCP2 from server without passing it
+ * through to client. this is temporary until libtelnet supports
+ * the server-end of MCCP2.
+ */
+ if (cmd == LIBTELNET_WILL && opt == LIBTELNET_OPTION_COMPRESS2) {
+ libtelnet_send_negotiate(&conn->telnet, LIBTELNET_DO, opt, conn);
+ return;
+ }
+
libtelnet_send_negotiate(&conn->remote->telnet, cmd, opt,
conn->remote);
}
@@ -201,6 +214,7 @@
struct conn_t *conn = (struct conn_t*)user_data;
printf("%s ERROR: %d\e[0m\n", conn->name, (int)error);
+ exit(1);
}
int main(int argc, char **argv) {
@@ -245,6 +259,7 @@
fprintf(stderr, "listen() failed: %s\n", strerror(errno));
return 1;
}
+ addrlen = sizeof(addr);
if ((client.sock = accept(listen_sock, (struct sockaddr *)&addr, &addrlen)) == -1) {
fprintf(stderr, "accept() failed: %s\n", strerror(errno));
return 1;