diff --git a/libtelnet.c b/libtelnet.c index 37b7b9b..b7a3aed 100644 --- a/libtelnet.c +++ b/libtelnet.c @@ -30,8 +30,9 @@ static const unsigned int _buffer_sizes_count = sizeof(_buffer_sizes) / sizeof(_buffer_sizes[0]); /* initialize a telnet state tracker */ -void libtelnet_init(struct libtelnet_t *telnet) { +void libtelnet_init(struct libtelnet_t *telnet, enum libtelnet_mode_t mode) { memset(telnet, 0, sizeof(struct libtelnet_t)); + telnet->mode = mode; } /* free up any memory allocated by a state tracker */ @@ -203,8 +204,13 @@ static void _process(struct libtelnet_t *telnet, unsigned char *buffer, 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) { +#ifdef HAVE_ZLIB + /* if we are a client and just received the COMPRESS2 + * begin marker, setup our zlib box and start handling + * the compressed stream + */ + if (telnet->mode == LIBTELNET_MODE_CLIENT && + telnet->buffer[0] == LIBTELNET_OPTION_COMPRESS2) { /* allocate zstream box */ if ((telnet->zlib = (z_stream *)malloc(sizeof(z_stream))) == 0) { @@ -222,6 +228,9 @@ static void _process(struct libtelnet_t *telnet, unsigned char *buffer, break; } + /* notify app that compression was enabled */ + libtelnet_compress_cb(telnet, 1, user_data); + /* 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 @@ -232,6 +241,8 @@ static void _process(struct libtelnet_t *telnet, unsigned char *buffer, user_data); return; } +#endif /* HAVE_ZLIB */ + break; /* escaped IAC byte */ case LIBTELNET_IAC: @@ -264,10 +275,11 @@ static void _process(struct libtelnet_t *telnet, unsigned char *buffer, /* 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 +#ifdef HAVE_ZLIB + /* if we are a client and we have a zlib box, then COMPRESS2 has been + * negotiated and we need to inflate the buffer before processing */ - if (telnet->zlib) { + if (telnet->mode == LIBTELNET_MODE_CLIENT && telnet->zlib != 0) { unsigned char inflate_buffer[4096]; int rs; @@ -298,6 +310,8 @@ void libtelnet_push(struct libtelnet_t *telnet, unsigned char *buffer, /* on error (or on end of stream) disable further inflation */ if (rs != Z_OK) { + libtelnet_compress_cb(telnet, 0, user_data); + inflateEnd(telnet->zlib); free(telnet->zlib); telnet->zlib = 0; @@ -305,24 +319,67 @@ void libtelnet_push(struct libtelnet_t *telnet, unsigned char *buffer, } } - /* COMPRESS2 is not negotiated, just ignore */ - } else { + /* COMPRESS2 is not negotiated, just process */ + } else +#endif /* HAVE_ZLIB */ _process(telnet, buffer, size, user_data); - } +} + +static void _send(struct libtelnet_t *telnet, unsigned char *buffer, + unsigned int size, void *user_data) { +#ifdef HAVE_ZLIB + /* if we are a server and we have a zlib box, then COMPRESS2 has been + * negotiated and we need to deflate the buffer before sending it out + */ + if (telnet->mode == LIBTELNET_MODE_SERVER && telnet->zlib != 0) { + unsigned char deflate_buffer[1024]; + + /* initialize zlib state */ + telnet->zlib->next_in = buffer; + telnet->zlib->avail_in = size; + telnet->zlib->next_out = deflate_buffer; + telnet->zlib->avail_out = sizeof(deflate_buffer); + + /* deflate until buffer exhausted and all output is produced */ + while (telnet->zlib->avail_in > 0 || telnet->zlib->avail_out == 0) { + /* reset output buffer */ + + /* compress */ + if (deflate(telnet->zlib, Z_SYNC_FLUSH) != Z_OK) { + libtelnet_error_cb(telnet, LIBTELNET_ERROR_UNKNOWN, + user_data); + deflateEnd(telnet->zlib); + free(telnet->zlib); + telnet->zlib = 0; + break; + } + + libtelnet_send_cb(telnet, deflate_buffer, sizeof(deflate_buffer) - + telnet->zlib->avail_out, user_data); + + /* prepare output buffer for next run */ + telnet->zlib->next_out = deflate_buffer; + telnet->zlib->avail_out = sizeof(deflate_buffer); + } + + /* COMPRESS2 is not negotiated, just send */ + } else +#endif /* HAVE_ZLIB */ + libtelnet_send_cb(telnet, buffer, size, user_data); } /* send an iac command */ void libtelnet_send_command(struct libtelnet_t *telnet, unsigned char cmd, void *user_data) { unsigned char bytes[2] = { LIBTELNET_IAC, cmd }; - libtelnet_send_cb(telnet, bytes, 2, user_data); + _send(telnet, bytes, 2, user_data); } /* send negotiation */ void libtelnet_send_negotiate(struct libtelnet_t *telnet, unsigned char cmd, unsigned char opt, void *user_data) { unsigned char bytes[3] = { LIBTELNET_IAC, cmd, opt }; - libtelnet_send_cb(telnet, bytes, 3, user_data); + _send(telnet, bytes, 3, user_data); } /* send non-command data (escapes IAC bytes) */ @@ -334,7 +391,7 @@ void libtelnet_send_data(struct libtelnet_t *telnet, unsigned char *buffer, if (buffer[i] == LIBTELNET_IAC) { /* dump prior text if any */ if (i != l) - libtelnet_send_cb(telnet, buffer + l, i - l, user_data); + _send(telnet, buffer + l, i - l, user_data); l = i + 1; /* send escape */ @@ -344,7 +401,7 @@ void libtelnet_send_data(struct libtelnet_t *telnet, unsigned char *buffer, /* send whatever portion of buffer is left */ if (i != l) - libtelnet_send_cb(telnet, buffer + l, i - l, user_data); + _send(telnet, buffer + l, i - l, user_data); } /* send sub-request */ @@ -354,4 +411,31 @@ void libtelnet_send_subrequest(struct libtelnet_t *telnet, unsigned char type, libtelnet_send_data(telnet, &type, 1, user_data); libtelnet_send_data(telnet, buffer, size, user_data); libtelnet_send_command(telnet, LIBTELNET_SE, user_data); + +#ifdef HAVE_ZLIB + /* if we're a server and we just sent the COMPRESS2 marker, we must + * make sure all further data is compressed + */ + if (telnet->mode == LIBTELNET_MODE_SERVER && type == + 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 (deflateInit(telnet->zlib, Z_DEFAULT_COMPRESSION) != Z_OK) { + free(telnet->zlib); + telnet->zlib = 0; + libtelnet_error_cb(telnet, + LIBTELNET_ERROR_UNKNOWN, user_data); + } + + /* notify app that compression was enabled */ + libtelnet_compress_cb(telnet, 1, user_data); + } +#endif /* HAVE_ZLIB */ } diff --git a/libtelnet.h b/libtelnet.h index 1ad8e3d..575d7ba 100644 --- a/libtelnet.h +++ b/libtelnet.h @@ -28,6 +28,12 @@ #define LIBTELNET_OPTION_COMPRESS2 86 #define LIBTELNET_OPTION_ZMP 93 +/* libtelnet modes */ +enum libtelnet_mode_t { + LIBTELNET_MODE_SERVER = 0, + LIBTELNET_MODE_CLIENT +}; + /* telnet states */ enum libtelnet_state_t { LIBTELNET_STATE_DATA = 0, @@ -37,7 +43,7 @@ enum libtelnet_state_t { LIBTELNET_STATE_WILL, LIBTELNET_STATE_WONT, LIBTELNET_STATE_SB, - LIBTELNET_STATE_SB_IAC, + LIBTELNET_STATE_SB_IAC }; /* error codes */ @@ -46,7 +52,7 @@ enum libtelnet_error_t { LIBTELNET_ERROR_NOMEM, /* memory allocation failure */ LIBTELNET_ERROR_OVERFLOW, /* data exceeds buffer size */ LIBTELNET_ERROR_PROTOCOL, /* invalid sequence of special bytes */ - LIBTELNET_ERROR_UNKNOWN, /* some crazy unexplainable unknown error */ + LIBTELNET_ERROR_UNKNOWN /* some crazy unexplainable unknown error */ }; /* state tracker */ @@ -63,50 +69,57 @@ struct libtelnet_t { unsigned int length; /* current state */ enum libtelnet_state_t state; + /* processing mode */ + enum libtelnet_mode_t mode; }; /* libtelnet callback declarations * APPLICATION MUST IMPLEMENT THESE FUNCTIONS!! */ extern void libtelnet_data_cb(struct libtelnet_t *telnet, - unsigned char *buffer, unsigned int size, void *user_data); + unsigned char *buffer, unsigned int size, void *user_data); extern void libtelnet_send_cb(struct libtelnet_t *telnet, - unsigned char *buffer, unsigned int size, void *user_data); + unsigned char *buffer, unsigned int size, void *user_data); extern void libtelnet_command_cb(struct libtelnet_t *telnet, - unsigned char cmd, void *user_data); + unsigned char cmd, void *user_data); extern void libtelnet_negotiate_cb(struct libtelnet_t *telnet, - unsigned char cmd, unsigned char opt, void *user_data); + unsigned char cmd, unsigned char opt, void *user_data); extern void libtelnet_subrequest_cb(struct libtelnet_t *telnet, - unsigned char type, unsigned char *data, unsigned int size, - void *user_data); + unsigned char type, unsigned char *data, unsigned int size, + void *user_data); +#ifdef HAVE_ZLIB +extern void libtelnet_compress_cb(struct libtelnet_t *telnet, + char enabled, void *user_data); +#endif extern void libtelnet_error_cb(struct libtelnet_t *telnet, - enum libtelnet_error_t error, void *user_data); + enum libtelnet_error_t error, void *user_data); /* initialize a telnet state tracker */ -extern void libtelnet_init(struct libtelnet_t *telnet); +extern void libtelnet_init(struct libtelnet_t *telnet, + enum libtelnet_mode_t mode); /* free up any memory allocated by a state tracker */ extern void libtelnet_free(struct libtelnet_t *telnet); /* push a byte buffer into the state tracker */ extern void libtelnet_push(struct libtelnet_t *telnet, - unsigned char *buffer, unsigned int size, void *user_data); + unsigned char *buffer, unsigned int size, void *user_data); /* send an iac command */ extern void libtelnet_send_command(struct libtelnet_t *telnet, - unsigned char cmd, void *user_data); + unsigned char cmd, void *user_data); /* send negotiation */ extern void libtelnet_send_negotiate(struct libtelnet_t *telnet, - unsigned char cmd, unsigned char opt, void *user_data); + unsigned char cmd, unsigned char opt, void *user_data); /* send non-command data (escapes IAC bytes) */ extern void libtelnet_send_data(struct libtelnet_t *telnet, - unsigned char *buffer, unsigned int size, void *user_data); + unsigned char *buffer, unsigned int size, void *user_data); /* send sub-request */ extern void libtelnet_send_subrequest(struct libtelnet_t *telnet, - unsigned char type, unsigned char *buffer, unsigned int size, - void *user_data); + unsigned char type, unsigned char *buffer, unsigned int size, + void *user_data); #endif /* !defined(LIBTELNET_INCLUDE) */ diff --git a/telnet-proxy.c b/telnet-proxy.c index 9a38af6..218d860 100644 --- a/telnet-proxy.c +++ b/telnet-proxy.c @@ -181,15 +181,6 @@ 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); } @@ -209,6 +200,13 @@ void libtelnet_subrequest_cb(struct libtelnet_t *telnet, unsigned char type, conn->remote); } +void libtelnet_compress_cb(struct libtelnet_t *telnet, char enabled, + void *user_data) { + struct conn_t *conn = (struct conn_t*)user_data; + + printf("%s COMPRESSION %s\e[0m\n", conn->name, enabled ? "ON" : "OFF"); +} + void libtelnet_error_cb(struct libtelnet_t *telnet, enum libtelnet_error_t error, void *user_data) { struct conn_t *conn = (struct conn_t*)user_data; @@ -296,9 +294,13 @@ int main(int argc, char **argv) { client.name = "\e[34mCLIENT"; client.remote = &server; - /* initialize telnet boxes */ - libtelnet_init(&server.telnet); - libtelnet_init(&client.telnet); + /* initialize telnet boxes + * NOTE: we set the server connect to the CLIENT mode because we + * are acting as a client of the server; likewise, we set the + * client connection to SERVER mode becauser we are acting as a + * server to the client. */ + libtelnet_init(&server.telnet, LIBTELNET_MODE_CLIENT); + libtelnet_init(&client.telnet, LIBTELNET_MODE_SERVER); /* initialize poll descriptors */ memset(pfd, 0, sizeof(pfd));