scrap the multiple callbacks thing, just use a single event handler callback
diff --git a/README b/README
index 62a1893..07fa52c 100644
--- a/README
+++ b/README
@@ -49,8 +49,8 @@
 part is a single function for pushing received data into the
 libtelnet processor.  The third part is the libtelnet_send_*()
 functions, which generate TELNET commands and ensure data is properly
-formatted before sending over the wire.  The final part is the
-callback structure libtelnet_cb_t.
+formatted before sending over the wire.  The final part is the event
+handler interface.
 
 IIa. Initialization
 
@@ -60,21 +60,16 @@
    its own libtelnet_t structure, which is passed to all libtelnet
    API calls.
 
- struct libtelnet_cb_t;
-   An instance of this structure must be initialized and have all
-   mandatory and desired optional callbacks set.  See section IId
-   for more information.
-
  void libtelnet_init(struct libtelnet_t *telnet,
-     struct libtelnet_cb_t *cb, enum libtelnet_mode_t mode);
+     libtelnet_event_handler_t handler, enum libtelnet_mode_t mode);
    The libtelnet_init() function is responsible for initializing
    the data in a libtelnet_t structure.  It must be called
    immediately after establishing a connection and before any other
    libtelnet API calls are made.
 
-   The cb parameter must be a pointer to a fully initialized
-   instance of libtelnet_cb_t.  A single instance of the structure
-   can be shared between any number of libtelnet_t instances.
+   The handler parameter must be a function matching the
+   libtelnet_event_handler_t definition.  More information about
+   events can be found in section IId.
 
    The mode parameter must be one of LIBTELNET_MODE_SERVER,
    LIBTELNET_MODE_CLIENT, or LIBTELNET_MODE_PROXY.  These slightly
@@ -95,15 +90,24 @@
      unsigned char *buffer, unsigned int size, void *user_data);
    When your application receives data over the socket from the
    remote end, it must pass the received bytes into this function.
-   Callback functions will be invoked as the buffer is processed,
-   and the user_data parameter will be passed to each callback.
+
+   As the TELNET stream is parsed, events will be generated and
+   passed to the event handler given to libtelnet_init().  Of
+   particular interest for data receiving is the LIBTELNET_EV_DATA
+   event, which is triggered for any regular data such as user
+   input or server process output.
 
 IIc. Sending Data
 
- Note that all of the libtelnet_send_*() functions will invoke
- the send callback function attached to the libtelnet_t instance.
- The user_data parameter to each of these functions is passed
- through to the callback.
+ All of the libtelnet_send_*() functions will invoke the
+ LIBTELNET_EV_SEND event.  The user_data parameter to each of these
+ functions is passed through to the callback.
+
+ Note: it is very important that ALL data sent to the remote end of
+ the connection be passed through libtelnet.  All user input or
+ process output that you wish to send over the wire should be given
+ to libtelnet_send_data().  Do NOT send or buffer unprocessed output
+ data directly!
 
  void libtelnet_send_command(struct libtelnet_t *telnet,
      unsigned char cmd, void *user_data);
@@ -128,109 +132,164 @@
    Sends a TELNET sub-negotiation command.  The opt parameter
    is the sub-negotiation option.
 
-IId. Callbacks
+IId. Event Handling
 
- The libtelnet_cb_t structure containers a number of callback
- entry points.  Of these, only the send and data callbacks are
- absolutely required.  All others are optional.  The declarations
- below show the signature of the callback functions.
+ libtelnet relies on an event-handling mechanism for processing
+ the parsed TELNET protocol stream as well as for buffering and
+ sending output data.
 
- An example of initializing a libtelnet_cb_t structure:
+ When you initialize a libtelnet_t structure with libtelnet_init()
+ you had to pass in an event handler function.  This function must
+ meet the following prototype:
 
-  /* illustrative data callback */
-  void my_data_cb(libtelnet_t *telnet, unsigned char *buffer,
-      unsigned int size, void *user_data) {
-    /* print number of bytes received and then show the
-     * whole buffer */
-    printf("RECV(%d): %.*s\n", size, size, buffer);
+  void (libtelnet_t *telnet, libtelnet_event_t *event,
+      void *user_data); 
+
+ The libtelnet_event_t structure has the following definition:
+
+  struct libtelnet_event_t {
+    enum libtelnet_event_type_t type;
+    unsigned char command;
+    unsigned char telopt;
+    unsigned char *buffer;
+    unsigned int size;
+  };
+ 
+ The enumeration values of libtelnet_event_type_t are described in
+ detail below.  Whenever the the event handler is invoked, the
+ application must look at the event->type value and do any
+ necessary processing.
+
+ The only event that MUST be implemented is LIBTELNET_EV_SEND.
+ Most applications will also always want to implement the event
+ LIBTELNET_EV_DATA.
+
+ Here is an example event handler implementation which includes
+ handlers for several important events.
+
+  void my_event_handler(struct libtelnet_t *telnet,
+      libtelnet_event_t *ev, void *user_data) {
+    struct user_info *user = (struct user_info *)user_data;
+
+    switch (ev->type) {
+    case LIBTELNET_EV_DATA:
+      process_user_input(user, event->buffer, event->size);
+      break;
+    case LIBTELNET_EV_SEND:
+      write_to_descriptor(user, event->buffer, event->size);
+      break;
+    case LIBTELNET_EV_ERROR:
+      fatal_error("TELNET error: %s", event->buffer);
+      break;
+    }
   }
 
-  /* illustrative variable definitions */
-  libtelnet_t conn;
-  libtelnet_cb_t callbacks;
+ LIBTELNET_EV_DATA:
+   The DATA event is triggered whenever regular data (not part of
+   any special TELNET command) is received.  For a client, this
+   will be process output from the server.  For a server, this will
+   be input typed by the user.
 
-  /* clear all callbacks and set just the ones we want */
-  memset(&callbacks, 0, sizeof(callbacks));
-  callbacks->send = my_send_cb;
-  callbacks->data = my_data_cb;
+   The event->buffer value will contain the bytes received and the
+   event->size value will contain the number of bytes received.
+   Note that event->buffer is not NUL terminated!
 
-  /* initialize the connection with our callbacks */
-  libtelnet_init(&conn, &callbacks, LIBTELNET_MODE_SERVER);
+   NOTE: there is no guarantee that user input or server output
+   will be received in whole lines.  If you wish to process data
+   a line at a time, you are responsible for buffering the data and
+   checking for line terminators yourself!
+ 
+ LIBTELNET_EV_SEND:
+   This event is sent whenever libtelnet has generated data that
+   must be sent over the wire to the remove end.  Generally that
+   means calling send() or adding the data to your application's
+   output buffer.
 
- Remember that a single libtelnet_cb_t structure can be shared
- between any number of libtelnet_t instances.  There is no reason
- to make multiple copies of the data if all of your connections
- use the same callback functions.
+   The event->buffer value will contain the bytes to send and the
+   event->size value will contain the number of bytes to send.
+   Note that event->buffer is not NUL terminated, and may include
+   NUL characters in its data, so always use event->size!
 
- void libtelnet_cb_t->data(struct libtelnet_t *telnet,
-     unsigned char *buffer, unsigned int size, void *user_data);
-   Regular data has been received by the remote end.  For a server,
-   this would be input typed by the client; for a client, this is
-   process output generated by the server.
+   NOTE: Your SEND event handler must send or buffer the data in
+   its raw form as provided by libtelnet.  If you wish to perform
+   any kind of preprocessing on data you want to send to the other
+ 
+ LIBTELNET_EV_IAC:
+   The IAC event is triggered whenever a simple IAC command is
+   received, such as the IAC EOR (end of record, also called
+   go ahead or GA) command.
 
-   Note that data is not line-buffered by libtelnet.  A single
-   line of input may be broken into pieces and given to
-   consecutive calls to libtelnet_data_cb().  If you are doing
-   line-based processing of data, it is your responsibility to
-   buffer data and find the line breaks.
+   The command received is in the event->command value.
 
- void libtelnet_cb_t->send(struct libtelnet_t *telnet,
-     unsigned char *buffer, unsigned int size, void *user_data);
-   This is called whenever libtelnet has generated output to be
-   send to the remote end of the connection.  In most cases this
-   will be a simple wrapper arround your applications network
-   output buffering/transmission code.
+   The necessary processing depends on the specific commands; see
+   the TELNET RFC for more information.
+ 
+ LIBTELNET_EV_NEGOTIATE:
+   The NEGOTIATE event is sent when a TELNET neogitiation command
+   is received.
 
-   You can pass socket information through the user_data
-   parameter to libtelnet calls so that it is available in this
-   callback.
-
- void libtelnet_cb_t->command(struct libtelnet_t *telnet,
-     unsigned char cmd, void *user_data);
-   Called whenever a "simpler" TELNET command has arrived, such
-   as GO-AHEAD commands (255 249).  The necessary processing
-   depends on the specific commands; see the TELNET RFC for
-   more information.
-
- void libtelnet_cb_t->negotiate(struct libtelnet_t *telnet,
-     unsigned char cmd, unsigned char opt, void *user_data);
-   This function is called whenever a TELNET negotiation command
-   has been received.  The cmd parameter will be one of
-   LIBTELNET_WILL, LIBTELNET_WONT, LIBTELNET_DO, or LIBTELNET_DONT.
-   The opt parameter is the option being negotiated.
+   The event->command value will be one of LIBTELNET_WILL,
+   LIBTELNET_WONT, LIBTELNET_DO, or LIBTELNET_DONT.  The
+   event->telopt value will contain the option value being
+   negotiated.
 
    libtelnet does not currently manage negotiation for you.  For
    best practice in implementing TELNET negotiation, see:
 
     http://www.faqs.org/rfcs/rfc1143.html
 
- void libtelnet_cb_t->subnegotiation(struct libtelnet_t *telnet,
-     unsigned char opt, unsigned char *data, unsigned int size,
-     void *user_data);
-   Called whenever a TELNET sub-negotiation has been received.
+ LIBTELNET_EV_SUBNEGOTIATION:
+   Triggered whenever a TELNET sub-negotiation has been received.
    Sub-negotiations include the NAWS option for communicating
    terminal size to a server, the NEW-ENVIRON and TTYPE options
    for negotiating terminal features, and MUD-centric protocols
    such as ZMP, MSSP, and MCCP2.
 
-   The opt parameter is the option under sub-negotiation.  The
-   remaining data (if any) is passed in the buffer.
+   The event->telopt value is the option under sub-negotiation.
+   The remaining data (if any) is passed in event->buffer and
+   event->size.  Note that most subnegotiation commands can
+   include embedded NUL bytes in the subnegotiation data, and
+   the data event->buffer is not NUL terminated, so always use
+   the event->size value!
 
- void libtelnet_cb_t->compress(struct libtelnet_t *telnet,
-     char enabled, void *user_data);
-   The callback is invoked whenever the COMPRESS2 (MCCP2)
-   feature is enabled or disabled.  For servers, this is called
-   immediately after beginning compression after a client accepts
-   the COMPRESS2 option.  For clients, this is called immediately
-   after a compress stream begin or ends.
+   The meaning and necessary processing for subnegotiations are
+   defined in various TELNET RFCs and other informal
+   specifications.  A subnegotiation should never be sent unless
+   the specific option has been enabled through the use of the
+   telnet negotiation feature.
 
-   The enabled parameter is 1 if compression has begun and 0 if
-   compression has ended.
+ LIBTELNET_EV_COMPRESS
+   The COMPRESS event notifies the app that COMPRESS2/MCCP2
+   compression has begun or ended.  Only servers can send compressed
+   data, and hence only clients will receive compressed data.
 
-III. INTEGRATING LIBTELNET
+   The event->command value will be 1 if compression has started and
+   will be 0 if compression has ended.
+
+ LIBTELNET_EV_ERROR
+   This event is called whenever an error occurs while trying to
+   process the TELNET protocol.  This includes both invalid protocol
+   sequences (which are rare) and out of memory conditions.
+
+   With few exceptions, an error is non-recoverable, and the only
+   solid course of action is to close the connection.  This is
+   especially true for any errors involving the COMPRESS2 option.
+
+   The event->buffer value will contain a NUL terminated string
+   explaining the error, and the event->size value containers the
+   length of the string.
+
+   FIXME: we should pass the error code in one of the fields, and
+   better document which errors are definitely non-recoverable and
+   which are maybe-recoverable (mostly those are just IAC-in-SB
+   errors... every other error is related to MCCP2 and usually
+   results in being unable to further read the stream).
+
+III. INTEGRATING LIBTELNET WITH COMMON MUDS
 =====================================================================
 
-FIXME: fill in notes about implementing the libtelnet_*_cb functions
+FIXME: fill in some notes about how to splice in libtelnet with
+common Diku/Merc/Circle/etc. MUD codebases.
 
 IV. SAFETY AND CORRECTNESS CONSIDERATIONS
 =====================================================================
@@ -246,6 +305,21 @@
 of libtelnet always make use of the libtelnet_send_*() family of
 functions for all data being sent over the TELNET connection.
 
+In particular, if you are writing a client, all user input must be
+passed through to libtelnet_send_data().  This also includes any
+input generated automatically by scripts, triggers, or macros.
+
+For a server, any and all output -- including ANSI/VT100 escape
+codes, regular text, newlines, and so on -- must be passed through
+to libtelnet_send_data().
+
+Any TELNET commands that are to be sent must be given to one of the
+following: libtelnet_send_command, libtelnet_send_negotiate, or
+libtelnet_send_subnegotiation().
+
+If you are attempting to enable COMPRESS2/MCCP2, you must use the
+libtelnet_begin_compress2() function.
+
 V. MCCP2 COMPRESSION
 =====================================================================
 
@@ -254,6 +328,11 @@
 
  http://www.mudbytes.net/index.php?a=articles&s=mccp
 
+In order for libtelnet to support MCCP2, zlib must be installed and
+enabled when compiling libtelnet.  Use -DHAVE_ZLIB to enable zlib
+when compiling libtelnet.c and pass -lz to the linker to link in the
+zlib shared library.
+
 libtelnet transparently supports MCCP2.  For a server to support
 MCCP2, the application must begin negotiation of the COMPRESS2
 option using libtelnet_send_negotiate(), for example:
@@ -261,18 +340,15 @@
  libtelnet_send_negotiate(&telnet, LIBTELNET_WILL,
      LIBTELNET_OPTION_COMPRESS2, user_data);
 
-libtelnet will automatically detect if the client responds favoribly
-and will begin compressing data.  For clients, no action must be
-taken, as libtelnet will automatically handle the requests.
+If a favorable DO COMPRESS2 is sent back from the client (processed
+in a LIBTELNET_EV_NEGOTIATE event, with event->command equal to
+LIBTELNET_DO and event->telopt equal to LIBTELNET_TELOPT_COMPRESS2),
+then the server application can begin compression at any time by
+calling libtelnet_begin_compress2().
 
-NOTE: libtelnet will still invoke the callback functions for
-negotiation and sub-negotiation commands relating to MCCP2.  Do not
-respond to these.
-
-In order for libtelnet to support MCCP2, zlib must be installed and
-enabled when compiling libtelnet.  Use -DHAVE_ZLIB to enable zlib
-when compiling libtelnet.c and pass -lz to the linker to link in the
-zlib shared library.
+If a connection is in PROXY mode and COMPRESS2 support is enabled
+then libtelnet will automatically detect the start of a COMPRESS2
+stream, in either the sending or receiving direction.
 
 VI. TELNET PROXY UTILITY
 =====================================================================
diff --git a/libtelnet.c b/libtelnet.c
index 0c3496b..755ebed 100644
--- a/libtelnet.c
+++ b/libtelnet.c
@@ -22,10 +22,10 @@
 
 #include "libtelnet.h"
 
+/* error handler helpers */
 #ifdef ERROR
 #  undef ERROR
 #endif
-
 #define ERROR(telnet, code, user_data, msg) \
 		_error(telnet, __FILE__, __LINE__, code, user_data, "%s", msg)
 #define ERROR_NOMEM(telnet, user_data, msg) \
@@ -46,6 +46,21 @@
 static const unsigned int _buffer_sizes_count =
 	sizeof(_buffer_sizes) / sizeof(_buffer_sizes[0]);
 
+/* event dispatch helper */
+static void _event(struct libtelnet_t *telnet,
+		enum libtelnet_event_type_t type, unsigned char command,
+		unsigned char telopt, unsigned char *buffer, unsigned int size,
+		void *user_data) {
+	struct libtelnet_event_t ev;
+	ev.type = type;
+	ev.command = command;
+	ev.telopt = telopt;
+	ev.buffer = buffer;
+	ev.size = size;
+
+	telnet->eh(telnet, &ev, user_data);
+}
+
 /* error generation function */
 static void _error(struct libtelnet_t *telnet, const char *file, unsigned line,
 		enum libtelnet_error_t err, void *user_data, const char *fmt, ...) {
@@ -61,13 +76,7 @@
 			fmt, va);
 	va_end(va);
 
-	/* invoke user's custom error handler if possible; otherwise,
-	 * print to stderr
-	 */
-	if (telnet->cb->error)
-		telnet->cb->error(telnet, err, buffer, user_data);
-	else
-		fprintf(stderr, "**ERROR**: libtelnet: %s\n", buffer);
+	_event(telnet, LIBTELNET_EV_ERROR, err, 0, 0, 0, user_data);
 }
 
 /* initialize the zlib box for a telnet box; if deflate is non-zero, it
@@ -105,10 +114,10 @@
 }
 
 /* initialize a telnet state tracker */
-void libtelnet_init(struct libtelnet_t *telnet, struct libtelnet_cb_t *cb,
+void libtelnet_init(struct libtelnet_t *telnet, libtelnet_event_handler_t eh,
 		enum libtelnet_mode_t mode) {
 	memset(telnet, 0, sizeof(struct libtelnet_t));
-	telnet->cb = cb;
+	telnet->eh = eh;
 	telnet->mode = mode;
 }
 
@@ -188,8 +197,8 @@
 			 * switch states */
 			if (byte == LIBTELNET_IAC) {
 				if (i != start)
-					telnet->cb->data(telnet, &buffer[start], i - start,
-							user_data);
+					_event(telnet, LIBTELNET_EV_DATA, 0, 0, &buffer[start],
+							i - start, user_data);
 				telnet->state = LIBTELNET_STATE_IAC;
 			}
 			break;
@@ -216,13 +225,13 @@
 				break;
 			/* IAC escaping */
 			case LIBTELNET_IAC:
-				telnet->cb->data(telnet, &byte, 1, user_data);
+				_event(telnet, LIBTELNET_EV_DATA, 0, 0, &byte, 1, user_data);
 				start = i + 1;
 				telnet->state = LIBTELNET_STATE_DATA;
 				break;
 			/* some other command */
 			default:
-				telnet->cb->command(telnet, byte, user_data);
+				_event(telnet, LIBTELNET_EV_IAC, byte, 0, 0, 0, user_data);
 				start = i + 1;
 				telnet->state = LIBTELNET_STATE_DATA;
 			}
@@ -230,22 +239,26 @@
 
 		/* negotiation commands */
 		case LIBTELNET_STATE_DO:
-			telnet->cb->negotiate(telnet, LIBTELNET_DO, byte, user_data);
+			_event(telnet, LIBTELNET_EV_NEGOTIATE, LIBTELNET_DO, byte,
+					0, 0, user_data);
 			start = i + 1;
 			telnet->state = LIBTELNET_STATE_DATA;
 			break;
 		case LIBTELNET_STATE_DONT:
-			telnet->cb->negotiate(telnet, LIBTELNET_DONT, byte, user_data);
+			_event(telnet, LIBTELNET_EV_NEGOTIATE, LIBTELNET_DONT, byte,
+					0, 0, user_data);
 			start = i + 1;
 			telnet->state = LIBTELNET_STATE_DATA;
 			break;
 		case LIBTELNET_STATE_WILL:
-			telnet->cb->negotiate(telnet, LIBTELNET_WILL, byte, user_data);
+			_event(telnet, LIBTELNET_EV_NEGOTIATE, LIBTELNET_WILL, byte,
+					0, 0, user_data);
 			start = i + 1;
 			telnet->state = LIBTELNET_STATE_DATA;
 			break;
 		case LIBTELNET_STATE_WONT:
-			telnet->cb->negotiate(telnet, LIBTELNET_WONT, byte, user_data);
+			_event(telnet, LIBTELNET_EV_NEGOTIATE, LIBTELNET_WONT, byte,
+					0, 0, user_data);
 			start = i + 1;
 			telnet->state = LIBTELNET_STATE_DATA;
 			break;
@@ -280,8 +293,9 @@
 				}
 
 				/* invoke callback */
-				telnet->cb->subnegotiation(telnet, telnet->buffer[0],
-					telnet->buffer + 1, telnet->length - 1, user_data);
+				_event(telnet, LIBTELNET_EV_SUBNEGOTIATION, 0,
+						telnet->buffer[0], telnet->buffer + 1,
+						telnet->length - 1, user_data);
 				telnet->length = 0;
 
 #ifdef HAVE_ZLIB
@@ -299,7 +313,8 @@
 						break;
 
 					/* notify app that compression was enabled */
-					telnet->cb->compress(telnet, 1, user_data);
+					_event(telnet, LIBTELNET_EV_COMPRESS, 1, 0, 0, 0,
+							user_data);
 
 					/* any remaining bytes in the buffer are compressed.
 					 * we have to re-invoke libtelnet_push to get those
@@ -340,7 +355,8 @@
 
 	/* pass through any remaining bytes */ 
 	if (telnet->state == LIBTELNET_STATE_DATA && i != start)
-		telnet->cb->data(telnet, &buffer[start], i - start, user_data);
+		_event(telnet, LIBTELNET_EV_DATA, 0, 0, buffer + start, i - start,
+				user_data);
 }
 
 /* push a bytes into the state tracker */
@@ -378,7 +394,7 @@
 
 			/* on error (or on end of stream) disable further inflation */
 			if (rs != Z_OK) {
-				telnet->cb->compress(telnet, 0, user_data);
+				_event(telnet, LIBTELNET_EV_COMPRESS, 0, 0, 0, 0, user_data);
 
 				inflateEnd(telnet->z_inflate);
 				free(telnet->z_inflate);
@@ -418,8 +434,9 @@
 				break;
 			}
 
-			telnet->cb->send(telnet, deflate_buffer, sizeof(deflate_buffer) -
-					telnet->z_deflate->avail_out, user_data);
+			_event(telnet, LIBTELNET_EV_SEND, 0, 0, deflate_buffer,
+					sizeof(deflate_buffer) - telnet->z_deflate->avail_out,
+					user_data);
 
 			/* prepare output buffer for next run */
 			telnet->z_deflate->next_out = deflate_buffer;
@@ -429,7 +446,7 @@
 	/* COMPRESS2 is not negotiated, just send */
 	} else
 #endif /* HAVE_ZLIB */
-		telnet->cb->send(telnet, buffer, size, user_data);
+		_event(telnet, LIBTELNET_EV_SEND, 0, 0, buffer, size, user_data);
 }
 
 /* send an iac command */
@@ -489,7 +506,7 @@
 			return;
 
 		/* notify app that compression was enabled */
-		telnet->cb->compress(telnet, 1, user_data);
+		_event(telnet, LIBTELNET_EV_COMPRESS, 1, 0, 0, 0, user_data);
 	}
 #endif /* HAVE_ZLIB */
 }
diff --git a/libtelnet.h b/libtelnet.h
index 4efc87b..843b76b 100644
--- a/libtelnet.h
+++ b/libtelnet.h
@@ -113,37 +113,38 @@
 	LIBTELNET_EUNKNOWN /* some crazy unexplainable unknown error */
 };
 
-/* libtelnet callback declarations */
-struct libtelnet_cb_t {
-	/* received (processed) data */
-	void (*data)(struct libtelnet_t *telnet, unsigned char *buffer,
-			unsigned int size, void *user_data);
-	/* processed data to buffer for sending */
-	void (*send)(struct libtelnet_t *telnet, unsigned char *buffer,
-			unsigned int size, void *user_data);
-	/* unknown command notification */
-	void (*command)(struct libtelnet_t *telnet, unsigned char cmd,
-			void *user_data);
-	/* negotiation notification */
-	void (*negotiate)(struct libtelnet_t *telnet, unsigned char cmd,
-			unsigned char opt, void *user_data);
-	/* unknown subnegotiation notification */
-	void (*subnegotiation)(struct libtelnet_t *telnet, unsigned char opt,
-			unsigned char *data, unsigned int size, void *user_data);
-	/* error handler */
-	void (*error)(struct libtelnet_t *telnet, enum libtelnet_error_t error,
-			const char *msg, void *user_data);
-
-	#ifdef HAVE_ZLIB
-	void (*compress)(struct libtelnet_t *telnet,
-			char enabled, void *user_data);
-	#endif
+/* event codes */
+enum libtelnet_event_type_t {
+	LIBTELNET_EV_DATA = 0,
+	LIBTELNET_EV_SEND,
+	LIBTELNET_EV_IAC,
+	LIBTELNET_EV_NEGOTIATE,
+	LIBTELNET_EV_SUBNEGOTIATION,
+	LIBTELNET_EV_COMPRESS,
+	LIBTELNET_EV_ERROR
 };
 
+/* event information */
+struct libtelnet_event_t {
+	/* type of event */ 
+	enum libtelnet_event_type_t type;
+	/* command info: only for IAC event */
+	unsigned char command;
+	/* telopt info: for NEGOTIATE and SUBNEGOTIATION events */
+	unsigned char telopt;
+	/* data buffer: for DATA, SEND, SUBNEGOTIATION, and ERROR events */
+	unsigned char *buffer;
+	unsigned int size;
+};
+
+/* event handler declaration */
+typedef void (*libtelnet_event_handler_t)(struct libtelnet_t *telnet,
+		struct libtelnet_event_t *event, void *user_data);
+
 /* state tracker */
 struct libtelnet_t {
-	/* callback table */
-	struct libtelnet_cb_t *cb;
+	/* event handler */
+	libtelnet_event_handler_t eh;
 #ifdef HAVE_ZLIB
 	/* zlib (mccp2) compression */
 	z_stream *z_deflate;
@@ -163,7 +164,7 @@
 
 /* initialize a telnet state tracker */
 extern void libtelnet_init(struct libtelnet_t *telnet,
-		struct libtelnet_cb_t *cb, enum libtelnet_mode_t mode);
+		libtelnet_event_handler_t eh, enum libtelnet_mode_t mode);
 
 /* free up any memory allocated by a state tracker */
 extern void libtelnet_free(struct libtelnet_t *telnet);
diff --git a/telnet-proxy.c b/telnet-proxy.c
index ca808a7..935abe6 100644
--- a/telnet-proxy.c
+++ b/telnet-proxy.c
@@ -127,32 +127,12 @@
 	}
 }
 
-static void _data_cb(struct libtelnet_t *telnet, unsigned char *buffer,
-		unsigned int size, void *user_data) {
-	struct conn_t *conn = (struct conn_t*)user_data;
-
-	printf("%s DATA: ", conn->name);
-	print_buffer(buffer, size);
-	printf("\e[0m\n");
-
-	libtelnet_send_data(&conn->remote->telnet, buffer, size,
-			conn->remote);
-}
-
-static void _send_cb(struct libtelnet_t *telnet, unsigned char *buffer,
-		unsigned int size, void *user_data) {
-	struct conn_t *conn = (struct conn_t*)user_data;
+static void _send(int sock, unsigned char *buffer, unsigned int size) {
 	int rs;
 
-	/* DONT SPAM
-	printf("%s SEND: ", conn->name);
-	print_buffer(buffer, size);
-	printf("\e[0m\n");
-	*/
-
 	/* send data */
 	while (size > 0) {
-		if ((rs = send(conn->sock, buffer, size, 0)) == -1) {
+		if ((rs = send(sock, buffer, size, 0)) == -1) {
 			fprintf(stderr, "send() failed: %s\n", strerror(errno));
 			exit(1);
 		} else if (rs == 0) {
@@ -166,55 +146,68 @@
 	}
 }
 
-static void _command_cb(struct libtelnet_t *telnet, unsigned char cmd,
-		void *user_data) {
+static void _event_handler(struct libtelnet_t *telnet,
+		struct libtelnet_event_t *ev, void *user_data) {
 	struct conn_t *conn = (struct conn_t*)user_data;
 
-	printf("%s IAC %s\e[0m\n", conn->name, get_cmd(cmd));
+	switch (ev->type) {
+	/* data received */
+	case LIBTELNET_EV_DATA:
+		printf("%s DATA: ", conn->name);
+		print_buffer(ev->buffer, ev->size);
+		printf("\e[0m\n");
 
-	libtelnet_send_command(&conn->remote->telnet, cmd, conn->remote);
-}
+		libtelnet_send_data(&conn->remote->telnet, ev->buffer, ev->size,
+				conn->remote);
+		break;
+	/* data must be sent */
+	case LIBTELNET_EV_SEND:
+		/* DONT SPAM
+		printf("%s SEND: ", conn->name);
+		print_buffer(ev->buffer, ev->size);
+		printf("\e[0m\n");
+		*/
 
-static void _negotiate_cb(struct libtelnet_t *telnet, unsigned char cmd,
-		unsigned char opt, void *user_data) {
-	struct conn_t *conn = (struct conn_t*)user_data;
+		_send(conn->sock, ev->buffer, ev->size);
+		break;
+	/* IAC command */
+	case LIBTELNET_EV_IAC:
+		printf("%s IAC %s\e[0m\n", conn->name, get_cmd(ev->command));
 
-	printf("%s IAC %s %d (%s)\e[0m\n", conn->name, get_cmd(cmd),
-			(int)opt, get_opt(opt));
+		libtelnet_send_command(&conn->remote->telnet, ev->command,
+				conn->remote);
+		break;
+	/* negotiation */
+	case LIBTELNET_EV_NEGOTIATE:
+		printf("%s IAC %s %d (%s)\e[0m\n", conn->name, get_cmd(ev->command),
+				(int)ev->telopt, get_opt(ev->telopt));
 
-	libtelnet_send_negotiate(&conn->remote->telnet, cmd, opt,
-			conn->remote);
-}
+		libtelnet_send_negotiate(&conn->remote->telnet, ev->command,
+				ev->telopt, conn->remote);
+		break;
+	/* subnegotiation */
+	case LIBTELNET_EV_SUBNEGOTIATION:
+		printf("%s SUB %d (%s)", conn->name, (int)ev->telopt,
+				get_opt(ev->telopt));
+		if (ev->size > 0) {
+			printf(" [%u]: ", ev->size);
+			print_buffer(ev->buffer, ev->size);
+		}
+		printf("\e[0m\n");
 
-static void _subnegotiation_cb(struct libtelnet_t *telnet,
-		unsigned char type, unsigned char *buffer, unsigned int size,
-		void *user_data) {
-	struct conn_t *conn = (struct conn_t*)user_data;
-
-	printf("%s SUB %d (%s)", conn->name, (int)type, get_opt(type));
-	if (size > 0) {
-		printf(" [%u]: ", size);
-		print_buffer(buffer, size);
+		libtelnet_send_subnegotiation(&conn->remote->telnet, ev->telopt,
+				ev->buffer, ev->size, conn->remote);
+		break;
+	/* compression notification */
+	case LIBTELNET_EV_COMPRESS:
+		printf("%s COMPRESSION %s\e[0m\n", conn->name,
+				ev->command ? "ON" : "OFF");
+		break;
+	/* error */
+	case LIBTELNET_EV_ERROR:
+		printf("%s ERROR: %.*s\e[0m\n", conn->name, ev->size, ev->buffer);
+		exit(1);
 	}
-	printf("\e[0m\n");
-
-	libtelnet_send_subnegotiation(&conn->remote->telnet, type, buffer, size,
-			conn->remote);
-}
-
-static void _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");
-}
-
-static void _error_cb(struct libtelnet_t *telnet, enum libtelnet_error_t error,
-		const char *msg, void *user_data) {
-	struct conn_t *conn = (struct conn_t*)user_data;
-
-	printf("%s ERROR: %s\e[0m\n", conn->name, msg);
-	exit(1);
 }
 
 int main(int argc, char **argv) {
@@ -226,7 +219,6 @@
 	struct pollfd pfd[2];
 	struct conn_t server;
 	struct conn_t client;
-	struct libtelnet_cb_t cb_table;
 	struct addrinfo *ai;
 	struct addrinfo hints;
 
@@ -310,19 +302,9 @@
 	client.name = "\e[34mCLIENT";
 	client.remote = &server;
 
-	/* initialize libtelnet callback table */
-	memset(&cb_table, 0, sizeof(cb_table));
-	cb_table.data = _data_cb;
-	cb_table.send = _send_cb;
-	cb_table.command = _command_cb;
-	cb_table.negotiate = _negotiate_cb;
-	cb_table.subnegotiation = _subnegotiation_cb;
-	cb_table.compress = _compress_cb;
-	cb_table.error = _error_cb;
-
 	/* initialize telnet boxes */
-	libtelnet_init(&server.telnet, &cb_table, LIBTELNET_MODE_PROXY);
-	libtelnet_init(&client.telnet, &cb_table, LIBTELNET_MODE_PROXY);
+	libtelnet_init(&server.telnet, _event_handler, LIBTELNET_MODE_PROXY);
+	libtelnet_init(&client.telnet, _event_handler, LIBTELNET_MODE_PROXY);
 
 	/* initialize poll descriptors */
 	memset(pfd, 0, sizeof(pfd));