initial client-only MCCP2 support

This commit is contained in:
Sean Middleditch 2009-03-14 03:35:49 -04:00
parent ce753de868
commit 9de1598cd5
4 changed files with 147 additions and 27 deletions

View File

@ -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)

View File

@ -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 @@ static const unsigned int _buffer_sizes_count =
/* 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 @@ static enum libtelnet_error_t _buffer_byte(
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 @@ void libtelnet_push(struct libtelnet_t *telnet, unsigned char *buffer,
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 @@ void libtelnet_push(struct libtelnet_t *telnet, unsigned char *buffer,
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 @@ void libtelnet_push(struct libtelnet_t *telnet, unsigned char *buffer,
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 @@ void libtelnet_push(struct libtelnet_t *telnet, unsigned char *buffer,
} 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 @@ void libtelnet_push(struct libtelnet_t *telnet, unsigned char *buffer,
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;
}
/* 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;
}
/* return to default state */
start = i + 1;
telnet->state = LIBTELNET_STATE_TEXT;
break;
/* escaped IAC byte */
case LIBTELNET_IAC:
@ -195,7 +239,7 @@ void libtelnet_push(struct libtelnet_t *telnet, unsigned char *buffer,
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 @@ void libtelnet_push(struct libtelnet_t *telnet, unsigned char *buffer,
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 @@ void libtelnet_push(struct libtelnet_t *telnet, unsigned char *buffer,
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) {

View File

@ -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 @@ enum libtelnet_error_t {
/* 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 */

View File

@ -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 @@ void libtelnet_negotiate_cb(struct libtelnet_t *telnet, unsigned char cmd,
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 @@ void libtelnet_error_cb(struct libtelnet_t *telnet,
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 @@ int main(int argc, char **argv) {
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;