Merge branch 'master' into rfc1143
diff --git a/README b/README
index bcf98fe..bcb55c8 100644
--- a/README
+++ b/README
@@ -18,7 +18,6 @@
 
 *** TODO ***
 
- - RFC 1143 option negotiation algorithm
  - automatic MCCP2 handling (controllable by host app)
  ? ZMP parsing
  ? MSSP parsing
@@ -29,14 +28,14 @@
 =====================================================================
 
 libtelnet provides safe and correct handling of the core TELNET
-protocol.  It does not include any "smarts," and all use of the
-protocol (such as deciding which options to support, enabling
-and disabling options, or processing subrequests) must be implemented
-by the application author.
+protocol.  In addition to the base TELNET protocol, libtelnet also
+implements the Q method of TELNET option negotiation.  libtelnet
+can be used for writing servers, clients, or proxies.
 
 For more information on the TELNET protocol, see:
 
  http://www.faqs.org/rfcs/rfc854.html
+ http://www.faqs.org/rfcs/rfc1143.html
 
 II. LIBTELNET API
 =====================================================================
@@ -222,20 +221,76 @@
    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.
+ LIBTELNET_EV_WILL:
+ LIBTELNET_EV_DO:
+   The WILL and DO events are sent when a TELNET negotiation
+   command of the same name is received.
 
-   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.
+   WILL events are sent by the remote end when they wish to be
+   allowed to turn an option on on their end, or in confirmation
+   after you have sent a DO command to them.
 
-   libtelnet does not currently manage negotiation for you.  For
-   best practice in implementing TELNET negotiation, see:
+   DO events are sent by the remote end when they wish for you
+   to turn on an option on your end, or in confirmation after you
+   have sent a WILL command to them.
+
+   In either case, the TELNET option under negotiation will be in
+   event->telopt field.
+
+   If you support the option and wish for it to be enabled you
+   must set the event->accept field to 1, unless this event is
+   a confirmation for a previous WILL/DO command you sent to the
+   remote end.  If you do not set event->field to 1 then
+   libtelnet will send a rejection command back to the other end.
+
+   libtelnet manages some of the pecularities of negotiation for
+   you.  For information on libtelnet's negotiation method, see:
 
     http://www.faqs.org/rfcs/rfc1143.html
 
+   Examples:
+
+    You want remote end to use TTYPE, so you send DO TTYPE.
+    Remote accepts and sends WILL TTYPE.
+
+    Remote end wants you to use SGA, so they send DO_SGA.
+    You do not support SGA and set event->accept = 0.
+
+    Remote end wants to use ZMP, so they send WILL ZMP.
+    You support ZMP, so you set event->accept = 1 and enable
+    local ZMP support.
+
+    You want to use MCCP2, so you send WILL COMPRESS2.
+    Remote end accepts and sends DO COMPRESS2.
+
+   Note that in PROXY mode libtelnet will do no processing of its
+   own for you.
+
+ LIBTELNET_EV_WONT:
+ LIBTELNET_EV_DONT:
+   The WONT and DONT events are sent when the remote end of the
+   connection wishes to disable an option, when they are
+   refusing to a support an option that you have asked for, or
+   in confirmation of an option you have asked to be disabled.
+
+   Most commonly WONT and DONT events are sent as rejections of
+   features you requested by sending DO or WILL events.  Receiving
+   these events means the TELNET option is not or will not be
+   supported by the remote end, so give up.
+
+   Sometimes WONT or DONT will be sent for TELNET options that are
+   already enabled, but the remote end wishes to stop using.  You
+   cannot decline.  These events are demands that must be complied
+   with.  libtelnet will always send the appropriate response back
+   without consulting your application.  These events are sent to
+   allow your application to disable its own use of the features.
+
+   In either case, the TELNET option under negotiation will be in
+   event->telopt field.
+
+   Note that in PROXY mode libtelnet will do no processing of its
+   own for you.
+
  LIBTELNET_EV_SUBNEGOTIATION:
    Triggered whenever a TELNET sub-negotiation has been received.
    Sub-negotiations include the NAWS option for communicating
@@ -343,11 +398,9 @@
  libtelnet_send_negotiate(&telnet, LIBTELNET_WILL,
      LIBTELNET_OPTION_COMPRESS2, user_data);
 
-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().
+If a favorable DO COMPRESS2 is sent back from the client then the
+server application can begin compression at any time by calling
+libtelnet_begin_compress2().
 
 If a connection is in PROXY mode and COMPRESS2 support is enabled
 then libtelnet will automatically detect the start of a COMPRESS2
diff --git a/libtelnet.c b/libtelnet.c
index baf74f7..5bf5295 100644
--- a/libtelnet.c
+++ b/libtelnet.c
@@ -22,6 +22,18 @@
 
 #include "libtelnet.h"
 
+/* RFC1143 state names */
+#define RFC1143_NO 0x00
+#define RFC1143_YES 0x01
+
+#define RFC1143_WANT 0x02
+#define RFC1143_OP 0x04
+
+#define RFC1143_WANTNO (RFC1143_WANT|RFC1143_YES)
+#define RFC1143_WANTYES (RFC1143_WANT|RFC1143_NO)
+#define RFC1143_WANTNO_OP (RFC1143_WANTNO|RFC1143_OP)
+#define RFC1143_WANTYES_OP (RFC1143_WANTYES|RFC1143_OP)
+
 /* buffer sizes */
 static const unsigned int _buffer_sizes[] = {
 	0,
@@ -33,18 +45,22 @@
 static const unsigned int _buffer_sizes_count = sizeof(_buffer_sizes) /
 		sizeof(_buffer_sizes[0]);
 
-/* event dispatch helper */
-static void _event(libtelnet_t *telnet, libtelnet_event_type_t type,
+/* event dispatch helper; return value is value of the accept field of the
+ * event struct after dispatch; used for the funky REQUEST event */
+static int _event(libtelnet_t *telnet, libtelnet_event_type_t type,
 		unsigned char command, unsigned char telopt, unsigned char *buffer,
 		unsigned int size) {
 	libtelnet_event_t ev;
+	ev.buffer = buffer;
+	ev.size = size;
 	ev.type = type;
 	ev.command = command;
 	ev.telopt = telopt;
-	ev.buffer = buffer;
-	ev.size = size;
+	ev.accept = 0;
 
 	telnet->eh(telnet, &ev, telnet->ud);
+
+	return ev.accept;
 }
 
 /* error generation function */
@@ -109,6 +125,260 @@
 	return LIBTELNET_EOK;
 }
 
+/* push bytes out, compressing them first if need be */
+static void _send(libtelnet_t *telnet, unsigned char *buffer,
+		unsigned int size) {
+#ifdef HAVE_ZLIB
+	/* if we have a deflate (compression) zlib box, use it */
+	if (telnet->z != 0 && telnet->flags & LIBTELNET_PFLAG_DEFLATE) {
+		unsigned char deflate_buffer[1024];
+		int rs;
+
+		/* initialize z state */
+		telnet->z->next_in = buffer;
+		telnet->z->avail_in = size;
+		telnet->z->next_out = deflate_buffer;
+		telnet->z->avail_out = sizeof(deflate_buffer);
+
+		/* deflate until buffer exhausted and all output is produced */
+		while (telnet->z->avail_in > 0 || telnet->z->avail_out == 0) {
+			/* compress */
+			if ((rs = deflate(telnet->z, Z_SYNC_FLUSH)) != Z_OK) {
+				_error(telnet, __LINE__, __func__, LIBTELNET_ECOMPRESS, 1,
+						"deflate() failed: %s", zError(rs));
+				deflateEnd(telnet->z);
+				free(telnet->z);
+				telnet->z = 0;
+				break;
+			}
+
+			_event(telnet, LIBTELNET_EV_SEND, 0, 0, deflate_buffer,
+					sizeof(deflate_buffer) - telnet->z->avail_out);
+
+			/* prepare output buffer for next run */
+			telnet->z->next_out = deflate_buffer;
+			telnet->z->avail_out = sizeof(deflate_buffer);
+		}
+
+	/* COMPRESS2 is not negotiated, just send */
+	} else
+#endif /* HAVE_ZLIB */
+		_event(telnet, LIBTELNET_EV_SEND, 0, 0, buffer, size);
+}
+
+/* retrieve RFC1143 option state */
+libtelnet_rfc1143_t _get_rfc1143(libtelnet_t *telnet, unsigned char telopt) {
+	static const libtelnet_rfc1143_t empty = { 0, 0, 0};
+	int i;
+
+	/* search for entry */
+	for (i = 0; i != telnet->q_size; ++i)
+		if (telnet->q[i].telopt == telopt)
+			return telnet->q[i];
+
+	/* not found, return empty value */
+	return empty;
+}
+
+/* save RFC1143 option state */
+void _set_rfc1143(libtelnet_t *telnet, libtelnet_rfc1143_t q) {
+	libtelnet_rfc1143_t *qtmp;
+	int i;
+
+	/* search for entry */
+	for (i = 0; i != telnet->q_size; ++i) {
+		if (telnet->q[i].telopt == q.telopt) {
+			telnet->q[i] = q;
+			return;
+		}
+	}
+
+	/* we're going to need to track state for it, so grow the queue
+	 * and put the telopt into it; bail on allocation error
+	 */
+	if ((qtmp = (libtelnet_rfc1143_t *)malloc(sizeof(
+			libtelnet_rfc1143_t) * (telnet->q_size + 1))) == 0) {
+		_error(telnet, __LINE__, __func__, LIBTELNET_ENOMEM, 0,
+				"malloc() failed: %s", strerror(errno));
+		return;
+	}
+	telnet->q = qtmp;
+	telnet->q[telnet->q_size++] = q;
+}
+
+/* send a negotiation without going through the RFC1143 checks */
+static void _send_negotiate(libtelnet_t *telnet, unsigned char cmd,
+		unsigned char opt) {
+	unsigned char bytes[3] = { LIBTELNET_IAC, cmd, opt };
+	_send(telnet, bytes, 3);
+}
+
+/* negotiation handling magic for RFC1143 */
+static void _negotiate(libtelnet_t *telnet, unsigned char cmd,
+		unsigned char telopt) {
+	libtelnet_rfc1143_t q;
+
+	/* in PROXY mode, just pass it thru and do nothing */
+	if (telnet->flags & LIBTELNET_FLAG_PROXY) {
+		switch (cmd) {
+		case LIBTELNET_WILL:
+			_event(telnet, LIBTELNET_EV_WILL, 0, telopt, 0, 0);
+			break;
+		case LIBTELNET_WONT:
+			_event(telnet, LIBTELNET_EV_WONT, 0, telopt, 0, 0);
+			break;
+		case LIBTELNET_DO:
+			_event(telnet, LIBTELNET_EV_DO, 0, telopt, 0, 0);
+			break;
+		case LIBTELNET_DONT:
+			_event(telnet, LIBTELNET_EV_DONT, 0, telopt, 0, 0);
+			break;
+		}
+		return;
+	}
+
+	/* lookup the current state of the option */
+	q = _get_rfc1143(telnet, telopt);
+
+	/* start processing... */
+	switch (cmd) {
+	/* request to enable option on remote end or confirm DO */
+	case LIBTELNET_WILL:
+		switch (q.him) {
+		case RFC1143_NO:
+			if (_event(telnet, LIBTELNET_EV_WILL, cmd, telopt, 0, 0) == 1) {
+				q.him = RFC1143_YES;
+				_set_rfc1143(telnet, q);
+				_send_negotiate(telnet, LIBTELNET_DO, telopt);
+			} else
+				_send_negotiate(telnet, LIBTELNET_DONT, telopt);
+			break;
+		case RFC1143_YES:
+			break;
+		case RFC1143_WANTNO:
+			q.him = RFC1143_NO;
+			_set_rfc1143(telnet, q);
+			_error(telnet, __LINE__, __func__, LIBTELNET_EPROTOCOL, 0,
+					"DONT answered by WILL");
+			break;
+		case RFC1143_WANTNO_OP:
+			q.him = RFC1143_YES;
+			_set_rfc1143(telnet, q);
+			_error(telnet, __LINE__, __func__, LIBTELNET_EPROTOCOL, 0,
+					"DONT answered by WILL");
+			break;
+		case RFC1143_WANTYES:
+			q.him = RFC1143_YES;
+			_set_rfc1143(telnet, q);
+			break;
+		case RFC1143_WANTYES_OP:
+			q.him = RFC1143_WANTNO;
+			_set_rfc1143(telnet, q);
+			_send_negotiate(telnet, LIBTELNET_DONT, telopt);
+			break;
+		}
+		break;
+
+	/* request to disable option on remote end, confirm DONT, reject DO */
+	case LIBTELNET_WONT:
+		switch (q.him) {
+		case RFC1143_NO:
+			break;
+		case RFC1143_YES:
+			q.him = RFC1143_NO;
+			_set_rfc1143(telnet, q);
+			_send_negotiate(telnet, LIBTELNET_DONT, telopt);
+			_event(telnet, LIBTELNET_EV_WONT, 0, telopt,
+					0, 0);
+			break;
+		case RFC1143_WANTNO:
+			q.him = RFC1143_NO;
+			_set_rfc1143(telnet, q);
+			_event(telnet, LIBTELNET_EV_WONT, 0, telopt,
+					0, 0);
+			break;
+		case RFC1143_WANTNO_OP:
+			q.him = RFC1143_WANTYES;
+			_set_rfc1143(telnet, q);
+			_event(telnet, LIBTELNET_EV_DO, 0, telopt,
+					0, 0);
+			break;
+		case RFC1143_WANTYES:
+		case RFC1143_WANTYES_OP:
+			q.him = RFC1143_NO;
+			_set_rfc1143(telnet, q);
+			break;
+		}
+		break;
+
+	/* request to enable option on local end or confirm WILL */
+	case LIBTELNET_DO:
+		switch (q.us) {
+		case RFC1143_NO:
+			if (_event(telnet, LIBTELNET_EV_DO, cmd, telopt, 0, 0) == 1) {
+				q.us = RFC1143_YES;
+				_set_rfc1143(telnet, q);
+				_send_negotiate(telnet, LIBTELNET_WILL, telopt);
+			} else
+				_send_negotiate(telnet, LIBTELNET_WONT, telopt);
+			break;
+		case RFC1143_YES:
+			break;
+		case RFC1143_WANTNO:
+			q.us = RFC1143_NO;
+			_set_rfc1143(telnet, q);
+			_error(telnet, __LINE__, __func__, LIBTELNET_EPROTOCOL, 0,
+					"WONT answered by DO");
+			break;
+		case RFC1143_WANTNO_OP:
+			q.us = RFC1143_YES;
+			_set_rfc1143(telnet, q);
+			_error(telnet, __LINE__, __func__, LIBTELNET_EPROTOCOL, 0,
+					"WONT answered by DO");
+			break;
+		case RFC1143_WANTYES:
+			q.us = RFC1143_YES;
+			_set_rfc1143(telnet, q);
+			break;
+		case RFC1143_WANTYES_OP:
+			q.us = RFC1143_WANTNO;
+			_set_rfc1143(telnet, q);
+			_send_negotiate(telnet, LIBTELNET_WONT, telopt);
+			break;
+		}
+		break;
+
+	/* request to disable option on local end, confirm WONT, reject WILL */
+	case LIBTELNET_DONT:
+		switch (q.us) {
+		case RFC1143_NO:
+			break;
+		case RFC1143_YES:
+			q.us = RFC1143_NO;
+			_set_rfc1143(telnet, q);
+			_send_negotiate(telnet, LIBTELNET_WONT, telopt);
+			_event(telnet, LIBTELNET_EV_DONT, 0, telopt, 0, 0);
+			break;
+		case RFC1143_WANTNO:
+			q.us = RFC1143_NO;
+			_set_rfc1143(telnet, q);
+			_event(telnet, LIBTELNET_EV_WONT, 0, telopt, 0, 0);
+			break;
+		case RFC1143_WANTNO_OP:
+			q.us = RFC1143_WANTYES;
+			_set_rfc1143(telnet, q);
+			_event(telnet, LIBTELNET_EV_WILL, 0, telopt, 0, 0);
+			break;
+		case RFC1143_WANTYES:
+		case RFC1143_WANTYES_OP:
+			q.us = RFC1143_NO;
+			_set_rfc1143(telnet, q);
+			break;
+		}
+		break;
+	}
+}
+
 /* initialize a telnet state tracker */
 void libtelnet_init(libtelnet_t *telnet, libtelnet_event_handler_t eh,
 		unsigned char flags, void *user_data) {
@@ -124,8 +394,8 @@
 	if (telnet->buffer != 0) {
 		free(telnet->buffer);
 		telnet->buffer = 0;
-		telnet->size = 0;
-		telnet->length = 0;
+		telnet->buffer_size = 0;
+		telnet->buffer_pos = 0;
 	}
 
 	/* free zlib box */
@@ -137,6 +407,13 @@
 		free(telnet->z);
 		telnet->z = 0;
 	}
+
+	/* free RFC1143 queue */
+	if (telnet->q) {
+		free(telnet->q);
+		telnet->q = 0;
+		telnet->q_size = 0;
+	}
 }
 
 /* push a byte into the telnet buffer */
@@ -146,10 +423,10 @@
 	int i;
 
 	/* check if we're out of room */
-	if (telnet->length == telnet->size) {
+	if (telnet->buffer_pos == telnet->buffer_size) {
 		/* find the next buffer size */
 		for (i = 0; i != _buffer_sizes_count; ++i) {
-			if (_buffer_sizes[i] == telnet->size)
+			if (_buffer_sizes[i] == telnet->buffer_size)
 				break;
 		}
 
@@ -172,11 +449,11 @@
 		}
 
 		telnet->buffer = new_buffer;
-		telnet->size = _buffer_sizes[i + 1];
+		telnet->buffer_size = _buffer_sizes[i + 1];
 	}
 
 	/* push the byte, all set */
-	telnet->buffer[telnet->length++] = byte;
+	telnet->buffer[telnet->buffer_pos++] = byte;
 	return LIBTELNET_EOK;
 }
 
@@ -235,22 +512,22 @@
 
 		/* negotiation commands */
 		case LIBTELNET_STATE_DO:
-			_event(telnet, LIBTELNET_EV_NEGOTIATE, LIBTELNET_DO, byte, 0, 0);
+			_negotiate(telnet, LIBTELNET_DO, byte);
 			start = i + 1;
 			telnet->state = LIBTELNET_STATE_DATA;
 			break;
 		case LIBTELNET_STATE_DONT:
-			_event(telnet, LIBTELNET_EV_NEGOTIATE, LIBTELNET_DONT, byte, 0, 0);
+			_negotiate(telnet, LIBTELNET_DONT, byte);
 			start = i + 1;
 			telnet->state = LIBTELNET_STATE_DATA;
 			break;
 		case LIBTELNET_STATE_WILL:
-			_event(telnet, LIBTELNET_EV_NEGOTIATE, LIBTELNET_WILL, byte, 0, 0);
+			_negotiate(telnet, LIBTELNET_WILL, byte);
 			start = i + 1;
 			telnet->state = LIBTELNET_STATE_DATA;
 			break;
 		case LIBTELNET_STATE_WONT:
-			_event(telnet, LIBTELNET_EV_NEGOTIATE, LIBTELNET_WONT, byte, 0, 0);
+			_negotiate(telnet, LIBTELNET_WONT, byte);
 			start = i + 1;
 			telnet->state = LIBTELNET_STATE_DATA;
 			break;
@@ -258,7 +535,7 @@
 		/* subnegotiation -- determine subnegotiation telopt */
 		case LIBTELNET_STATE_SB:
 			telnet->sb_telopt = byte;
-			telnet->length = 0;
+			telnet->buffer_pos = 0;
 			telnet->state = LIBTELNET_STATE_SB_DATA;
 			break;
 
@@ -285,7 +562,7 @@
 
 				/* invoke callback */
 				_event(telnet, LIBTELNET_EV_SUBNEGOTIATION, 0,
-						telnet->sb_telopt, telnet->buffer, telnet->length);
+						telnet->sb_telopt, telnet->buffer, telnet->buffer_pos);
 
 #ifdef HAVE_ZLIB
 				/* received COMPRESS2 begin marker, setup our zlib box and
@@ -390,46 +667,6 @@
 		_process(telnet, buffer, size);
 }
 
-static void _send(libtelnet_t *telnet, unsigned char *buffer,
-		unsigned int size) {
-#ifdef HAVE_ZLIB
-	/* if we have a deflate (compression) zlib box, use it */
-	if (telnet->z != 0 && telnet->flags & LIBTELNET_PFLAG_DEFLATE) {
-		unsigned char deflate_buffer[1024];
-		int rs;
-
-		/* initialize z state */
-		telnet->z->next_in = buffer;
-		telnet->z->avail_in = size;
-		telnet->z->next_out = deflate_buffer;
-		telnet->z->avail_out = sizeof(deflate_buffer);
-
-		/* deflate until buffer exhausted and all output is produced */
-		while (telnet->z->avail_in > 0 || telnet->z->avail_out == 0) {
-			/* compress */
-			if ((rs = deflate(telnet->z, Z_SYNC_FLUSH)) != Z_OK) {
-				_error(telnet, __LINE__, __func__, LIBTELNET_ECOMPRESS, 1,
-						"deflate() failed: %s", zError(rs));
-				deflateEnd(telnet->z);
-				free(telnet->z);
-				telnet->z = 0;
-				break;
-			}
-
-			_event(telnet, LIBTELNET_EV_SEND, 0, 0, deflate_buffer,
-					sizeof(deflate_buffer) - telnet->z->avail_out);
-
-			/* prepare output buffer for next run */
-			telnet->z->next_out = deflate_buffer;
-			telnet->z->avail_out = sizeof(deflate_buffer);
-		}
-
-	/* COMPRESS2 is not negotiated, just send */
-	} else
-#endif /* HAVE_ZLIB */
-		_event(telnet, LIBTELNET_EV_SEND, 0, 0, buffer, size);
-}
-
 /* send an iac command */
 void libtelnet_send_command(libtelnet_t *telnet, unsigned char cmd) {
 	unsigned char bytes[2] = { LIBTELNET_IAC, cmd };
@@ -438,9 +675,120 @@
 
 /* send negotiation */
 void libtelnet_send_negotiate(libtelnet_t *telnet, unsigned char cmd,
-		unsigned char opt) {
-	unsigned char bytes[3] = { LIBTELNET_IAC, cmd, opt };
-	_send(telnet, bytes, 3);
+		unsigned char telopt) {
+	libtelnet_rfc1143_t q;
+
+	/* if we're in proxy mode, just send it now */
+	if (telnet->flags & LIBTELNET_FLAG_PROXY) {
+		unsigned char bytes[3] = { LIBTELNET_IAC, cmd, telopt };
+		_send(telnet, bytes, 3);
+		return;
+	}
+	
+	/* get current option states */
+	q = _get_rfc1143(telnet, telopt);
+
+	switch (cmd) {
+	/* advertise willingess to support an option */
+	case LIBTELNET_WILL:
+		switch (q.us) {
+		case RFC1143_NO:
+			q.us = RFC1143_WANTYES;
+			_set_rfc1143(telnet, q);
+			_negotiate(telnet, LIBTELNET_WILL, telopt);
+			break;
+		case RFC1143_YES:
+			break;
+		case RFC1143_WANTNO:
+			q.us = RFC1143_WANTNO_OP;
+			_set_rfc1143(telnet, q);
+			break;
+		case RFC1143_WANTYES:
+			break;
+		case RFC1143_WANTNO_OP:
+			break;
+		case RFC1143_WANTYES_OP:
+			q.us = RFC1143_WANTYES;
+			_set_rfc1143(telnet, q);
+			break;
+		}
+		break;
+
+	/* force turn-off of locally enabled option */
+	case LIBTELNET_WONT:
+		switch (q.us) {
+		case RFC1143_NO:
+			break;
+		case RFC1143_YES:
+			q.us = RFC1143_WANTNO;
+			_set_rfc1143(telnet, q);
+			_negotiate(telnet, LIBTELNET_WONT, telopt);
+			break;
+		case RFC1143_WANTNO:
+			break;
+		case RFC1143_WANTYES:
+			q.us = RFC1143_WANTYES_OP;
+			_set_rfc1143(telnet, q);
+			break;
+		case RFC1143_WANTNO_OP:
+			q.us = RFC1143_WANTNO;
+			_set_rfc1143(telnet, q);
+			break;
+		case RFC1143_WANTYES_OP:
+			break;
+		}
+		break;
+
+	/* ask remote end to enable an option */
+	case LIBTELNET_DO:
+		switch (q.him) {
+		case RFC1143_NO:
+			q.him = RFC1143_WANTYES;
+			_set_rfc1143(telnet, q);
+			_negotiate(telnet, LIBTELNET_DO, telopt);
+			break;
+		case RFC1143_YES:
+			break;
+		case RFC1143_WANTNO:
+			q.him = RFC1143_WANTNO_OP;
+			_set_rfc1143(telnet, q);
+			break;
+		case RFC1143_WANTYES:
+			break;
+		case RFC1143_WANTNO_OP:
+			break;
+		case RFC1143_WANTYES_OP:
+			q.him = RFC1143_WANTYES;
+			_set_rfc1143(telnet, q);
+			break;
+		}
+		break;
+
+	/* demand remote end disable an option */
+	case LIBTELNET_DONT:
+		switch (q.him) {
+		case RFC1143_NO:
+			break;
+		case RFC1143_YES:
+			q.him = RFC1143_WANTNO;
+			_set_rfc1143(telnet, q);
+			_negotiate(telnet, LIBTELNET_DONT, telopt);
+			break;
+		case RFC1143_WANTNO:
+			break;
+		case RFC1143_WANTYES:
+			q.him = RFC1143_WANTYES_OP;
+			_set_rfc1143(telnet, q);
+			break;
+		case RFC1143_WANTNO_OP:
+			q.him = RFC1143_WANTNO;
+			_set_rfc1143(telnet, q);
+			break;
+		case RFC1143_WANTYES_OP:
+			break;
+		}
+		break;
+	}
 }
 
 /* send non-command data (escapes IAC bytes) */
@@ -495,13 +843,6 @@
 	unsigned char compress2[] = { LIBTELNET_IAC, LIBTELNET_SB,
 			LIBTELNET_TELOPT_COMPRESS2, LIBTELNET_IAC, LIBTELNET_SE };
 
-	/* don't do this if we've already got a compression stream */
-	if (telnet->z != 0) {
-		_error(telnet, __LINE__, __func__, LIBTELNET_EBADVAL, 0,
-				"compression already enabled");
-		return;
-	}
-
 	/* attempt to create output stream first, bail if we can't */
 	if (_init_zlib(telnet, 1, 0) != LIBTELNET_EOK)
 		return;
diff --git a/libtelnet.h b/libtelnet.h
index f926320..8db4d6c 100644
--- a/libtelnet.h
+++ b/libtelnet.h
@@ -15,6 +15,7 @@
 /* forward declarations */
 typedef struct libtelnet_t libtelnet_t;
 typedef struct libtelnet_event_t libtelnet_event_t;
+typedef struct libtelnet_rfc1143_t libtelnet_rfc1143_t;
 typedef enum libtelnet_mode_t libtelnet_mode_t;
 typedef enum libtelnet_state_t libtelnet_state_t;
 typedef enum libtelnet_error_t libtelnet_error_t;
@@ -122,7 +123,10 @@
 	LIBTELNET_EV_DATA = 0,
 	LIBTELNET_EV_SEND,
 	LIBTELNET_EV_IAC,
-	LIBTELNET_EV_NEGOTIATE,
+	LIBTELNET_EV_WILL,
+	LIBTELNET_EV_WONT,
+	LIBTELNET_EV_DO,
+	LIBTELNET_EV_DONT,
 	LIBTELNET_EV_SUBNEGOTIATION,
 	LIBTELNET_EV_COMPRESS,
 	LIBTELNET_EV_WARNING,
@@ -131,15 +135,23 @@
 
 /* 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;
+	/* type of event */ 
+	enum libtelnet_event_type_t type;
+	/* IAC command */
+	unsigned char command;
+	/* telopt info: for negotiation events SUBNEGOTIATION */
+	unsigned char telopt;
+	/* accept status: for WILL and DO events */
+	unsigned char accept;
+};
+
+/* option negotiation state (RFC 1143) */
+struct libtelnet_rfc1143_t {
+	unsigned char telopt;
+	char us:4, him:4;
 };
 
 /* event handler declaration */
@@ -156,18 +168,22 @@
 	/* zlib (mccp2) compression */
 	z_stream *z;
 #endif
+	/* RFC1143 option negotiation states */
+	struct libtelnet_rfc1143_t *q;
 	/* sub-request buffer */
 	unsigned char *buffer;
 	/* current size of the buffer */
-	unsigned int size;
-	/* length of data in the buffer */
-	unsigned int length;
+	unsigned int buffer_size;
+	/* current buffer write position (also length of buffer data) */
+	unsigned int buffer_pos;
 	/* current state */
 	enum libtelnet_state_t state;
 	/* option flags */
 	unsigned char flags;
 	/* current subnegotiation telopt */
 	unsigned char sb_telopt;
+	/* length of RFC1143 queue */
+	unsigned char q_size;
 };
 
 /* initialize a telnet state tracker */
diff --git a/telnet-client.c b/telnet-client.c
index c79977b..ef1b8e6 100644
--- a/telnet-client.c
+++ b/telnet-client.c
@@ -89,55 +89,32 @@
 	case LIBTELNET_EV_SEND:
 		_send(sock, ev->buffer, ev->size);
 		break;
-	/* accept any options we want */
-	case LIBTELNET_EV_NEGOTIATE:
-		switch (ev->command) {
-		case LIBTELNET_WILL:
-			switch (ev->telopt) {
-			/* accept request to enable compression */
-			case LIBTELNET_TELOPT_COMPRESS2:
-				libtelnet_send_negotiate(telnet, LIBTELNET_DO, ev->telopt);
-				break;
-			/* server "promises" to echo, so turn off local echo */
-			case LIBTELNET_TELOPT_ECHO:
-				do_echo = 0;
-				libtelnet_send_negotiate(telnet, LIBTELNET_DO, ev->telopt);
-				break;
-			/* unknown -- reject */
-			default:
-				libtelnet_send_negotiate(telnet, LIBTELNET_DONT, ev->telopt);
-				break;
-			}
-			break;
+	/* request to enable remote feature (or receipt) */
+	case LIBTELNET_EV_WILL:
+		/* we accept COMPRESS2 (MCCP) */
+		if (ev->telopt == LIBTELNET_TELOPT_COMPRESS2)
+			ev->accept = 1;
 
-		case LIBTELNET_WONT:
-			switch (ev->telopt) {
-			/* server wants us to do echoing, by telling us it won't */
-			case LIBTELNET_TELOPT_ECHO:
-				do_echo = 1;
-				libtelnet_send_negotiate(telnet, LIBTELNET_DONT, ev->telopt);
-				break;
-			}
-			break;
-
-		case LIBTELNET_DO:
-			switch (ev->telopt) {
-			/* accept request to enable terminal-type requests */
-			case LIBTELNET_TELOPT_TTYPE:
-				libtelnet_send_negotiate(telnet, LIBTELNET_WILL, ev->telopt);
-				break;
-			/* unknown - reject */
-			default:
-				libtelnet_send_negotiate(telnet, LIBTELNET_WONT, ev->telopt);
-				break;
-			}
-			break;
-
-		case LIBTELNET_DONT:
-			/* ignore for now */
-			break;
+		/* we'll agree to turn off our echo if server wants us to stop */
+		else if (ev->telopt == LIBTELNET_TELOPT_ECHO) {
+			do_echo = 0;
+			ev->accept = 1;
 		}
 		break;
+	/* notification of disabling remote feature (or receipt) */
+	case LIBTELNET_EV_WONT:
+		if (ev->telopt == LIBTELNET_TELOPT_ECHO)
+			do_echo = 1;
+		break;
+	/* request to enable local feature (or receipt) */
+	case LIBTELNET_EV_DO:
+		/* we support the TTYPE option */
+		if (ev->telopt == LIBTELNET_TELOPT_TTYPE)
+			ev->accept = 1;
+		break;
+	/* demand to disable local feature (or receipt) */
+	case LIBTELNET_EV_DONT:
+		break;
 	/* respond to particular subnegotiations */
 	case LIBTELNET_EV_SUBNEGOTIATION:
 		/* respond with our terminal type */
diff --git a/telnet-proxy.c b/telnet-proxy.c
index 078806d..45480d8 100644
--- a/telnet-proxy.c
+++ b/telnet-proxy.c
@@ -191,12 +191,31 @@
 
 		libtelnet_send_command(&conn->remote->telnet, ev->command);
 		break;
-	/* negotiation */
-	case LIBTELNET_EV_NEGOTIATE:
-		printf("%s IAC %s %d (%s)" COLOR_NORMAL "\n", conn->name,
-				get_cmd(ev->command), (int)ev->telopt, get_opt(ev->telopt));
-
-		libtelnet_send_negotiate(&conn->remote->telnet, ev->command,
+	/* negotiation, WILL */
+	case LIBTELNET_EV_WILL:
+		printf("%s IAC WILL %d (%s)" COLOR_NORMAL "\n", conn->name,
+				(int)ev->telopt, get_opt(ev->telopt));
+		libtelnet_send_negotiate(&conn->remote->telnet, LIBTELNET_WILL,
+				ev->telopt);
+		break;
+	/* negotiation, WONT */
+	case LIBTELNET_EV_WONT:
+		printf("%s IAC WONT %d (%s)" COLOR_NORMAL "\n", conn->name,
+				(int)ev->telopt, get_opt(ev->telopt));
+		libtelnet_send_negotiate(&conn->remote->telnet, LIBTELNET_WONT,
+				ev->telopt);
+		break;
+	/* negotiation, DO */
+	case LIBTELNET_EV_DO:
+		printf("%s IAC DO %d (%s)" COLOR_NORMAL "\n", conn->name,
+				(int)ev->telopt, get_opt(ev->telopt));
+		libtelnet_send_negotiate(&conn->remote->telnet, LIBTELNET_DO,
+				ev->telopt);
+		break;
+	case LIBTELNET_EV_DONT:
+		printf("%s IAC DONT %d (%s)" COLOR_NORMAL "\n", conn->name,
+				(int)ev->telopt, get_opt(ev->telopt));
+		libtelnet_send_negotiate(&conn->remote->telnet, LIBTELNET_DONT,
 				ev->telopt);
 		break;
 	/* subnegotiation */