mirror of https://gerrit.osmocom.org/libtelnet
initial client-only MCCP2 support
This commit is contained in:
parent
ce753de868
commit
9de1598cd5
5
Makefile
5
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)
|
||||
|
|
144
libtelnet.c
144
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 @@ 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) {
|
||||
|
|
10
libtelnet.h
10
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 @@ 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 */
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue