initial ZMP parsing support (experimental)
diff --git a/libtelnet.c b/libtelnet.c
index df8a882..a674c69 100644
--- a/libtelnet.c
+++ b/libtelnet.c
@@ -58,8 +58,10 @@
  * event struct after dispatch; used for the funky REQUEST event */
 static INLINE int _event(telnet_t *telnet, telnet_event_type_t type,
 		unsigned char command, unsigned char telopt,
-		const char *buffer, size_t size) {
+		const char *buffer, size_t size, const char **argv, size_t argc) {
 	telnet_event_t ev;
+	ev.argv = argv;
+	ev.argc = argc;
 	ev.buffer = buffer;
 	ev.size = size;
 	ev.type = type;
@@ -90,7 +92,7 @@
 
 	/* send error event to the user */
 	_event(telnet, fatal ? TELNET_EV_ERROR : TELNET_EV_WARNING, err,
-			0, buffer, strlen(buffer));
+			0, buffer, strlen(buffer), 0, 0);
 	
 	return err;
 }
@@ -166,7 +168,7 @@
 			}
 
 			_event(telnet, TELNET_EV_SEND, 0, 0, deflate_buffer,
-					sizeof(deflate_buffer) - telnet->z->avail_out);
+					sizeof(deflate_buffer) - telnet->z->avail_out, 0, 0);
 
 			/* prepare output buffer for next run */
 			telnet->z->next_out = (unsigned char *)deflate_buffer;
@@ -176,7 +178,7 @@
 	/* COMPRESS2 is not negotiated, just send */
 	} else
 #endif /* HAVE_ZLIB */
-		_event(telnet, TELNET_EV_SEND, 0, 0, buffer, size);
+		_event(telnet, TELNET_EV_SEND, 0, 0, buffer, size, 0, 0);
 }
 
 /* retrieve RFC1143 option state */
@@ -240,16 +242,16 @@
 	if (telnet->flags & TELNET_FLAG_PROXY) {
 		switch ((int)telnet->state) {
 		case TELNET_STATE_WILL:
-			_event(telnet, TELNET_EV_WILL, 0, telopt, 0, 0);
+			_event(telnet, TELNET_EV_WILL, 0, telopt, 0, 0, 0, 0);
 			break;
 		case TELNET_STATE_WONT:
-			_event(telnet, TELNET_EV_WONT, 0, telopt, 0, 0);
+			_event(telnet, TELNET_EV_WONT, 0, telopt, 0, 0, 0, 0);
 			break;
 		case TELNET_STATE_DO:
-			_event(telnet, TELNET_EV_DO, 0, telopt, 0, 0);
+			_event(telnet, TELNET_EV_DO, 0, telopt, 0, 0, 0, 0);
 			break;
 		case TELNET_STATE_DONT:
-			_event(telnet, TELNET_EV_DONT, 0, telopt, 0, 0);
+			_event(telnet, TELNET_EV_DONT, 0, telopt, 0, 0, 0, 0);
 			break;
 		}
 		return;
@@ -264,7 +266,7 @@
 	case TELNET_STATE_WILL:
 		switch (q.him) {
 		case Q_NO:
-			if (_event(telnet, TELNET_EV_WILL, 0, telopt, 0, 0) == 1) {
+			if (_event(telnet, TELNET_EV_WILL, 0, telopt, 0, 0, 0, 0) == 1) {
 				_set_rfc1143(telnet, telopt, q.us, Q_YES);
 				_send_negotiate(telnet, TELNET_DO, telopt);
 			} else
@@ -272,24 +274,24 @@
 			break;
 		case Q_WANTNO:
 			_set_rfc1143(telnet, telopt, q.us, Q_NO);
-			_event(telnet, TELNET_EV_WONT, 0, telopt, 0, 0);
+			_event(telnet, TELNET_EV_WONT, 0, telopt, 0, 0, 0, 0);
 			_error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0,
 					"DONT answered by WILL");
 			break;
 		case Q_WANTNO_OP:
 			_set_rfc1143(telnet, telopt, q.us, Q_YES);
-			_event(telnet, TELNET_EV_WILL, 0, telopt, 0, 0);
+			_event(telnet, TELNET_EV_WILL, 0, telopt, 0, 0, 0, 0);
 			_error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0,
 					"DONT answered by WILL");
 			break;
 		case Q_WANTYES:
 			_set_rfc1143(telnet, telopt, q.us, Q_YES);
-			_event(telnet, TELNET_EV_WILL, 0, telopt, 0, 0);
+			_event(telnet, TELNET_EV_WILL, 0, telopt, 0, 0, 0, 0);
 			break;
 		case Q_WANTYES_OP:
 			_set_rfc1143(telnet, telopt, q.us, Q_WANTNO);
 			_send_negotiate(telnet, TELNET_DONT, telopt);
-			_event(telnet, TELNET_EV_WILL, 0, telopt, 0, 0);
+			_event(telnet, TELNET_EV_WILL, 0, telopt, 0, 0, 0, 0);
 			break;
 		}
 		break;
@@ -300,15 +302,15 @@
 		case Q_YES:
 			_set_rfc1143(telnet, telopt, q.us, Q_NO);
 			_send_negotiate(telnet, TELNET_DONT, telopt);
-			_event(telnet, TELNET_EV_WONT, 0, telopt, 0, 0);
+			_event(telnet, TELNET_EV_WONT, 0, telopt, 0, 0, 0, 0);
 			break;
 		case Q_WANTNO:
 			_set_rfc1143(telnet, telopt, q.us, Q_NO);
-			_event(telnet, TELNET_EV_WONT, 0, telopt, 0, 0);
+			_event(telnet, TELNET_EV_WONT, 0, telopt, 0, 0, 0, 0);
 			break;
 		case Q_WANTNO_OP:
 			_set_rfc1143(telnet, telopt, q.us, Q_WANTYES);
-			_event(telnet, TELNET_EV_DO, 0, telopt, 0, 0);
+			_event(telnet, TELNET_EV_DO, 0, telopt, 0, 0, 0, 0);
 			break;
 		case Q_WANTYES:
 		case Q_WANTYES_OP:
@@ -321,7 +323,7 @@
 	case TELNET_STATE_DO:
 		switch (q.us) {
 		case Q_NO:
-			if (_event(telnet, TELNET_EV_DO, 0, telopt, 0, 0) == 1) {
+			if (_event(telnet, TELNET_EV_DO, 0, telopt, 0, 0, 0, 0) == 1) {
 				_set_rfc1143(telnet, telopt, Q_YES, q.him);
 				_send_negotiate(telnet, TELNET_WILL, telopt);
 			} else
@@ -329,24 +331,24 @@
 			break;
 		case Q_WANTNO:
 			_set_rfc1143(telnet, telopt, Q_NO, q.him);
-			_event(telnet, TELNET_EV_DONT, 0, telopt, 0, 0);
+			_event(telnet, TELNET_EV_DONT, 0, telopt, 0, 0, 0, 0);
 			_error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0,
 					"WONT answered by DO");
 			break;
 		case Q_WANTNO_OP:
 			_set_rfc1143(telnet, telopt, Q_YES, q.him);
-			_event(telnet, TELNET_EV_DO, 0, telopt, 0, 0);
+			_event(telnet, TELNET_EV_DO, 0, telopt, 0, 0, 0, 0);
 			_error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0,
 					"WONT answered by DO");
 			break;
 		case Q_WANTYES:
 			_set_rfc1143(telnet, telopt, Q_YES, q.him);
-			_event(telnet, TELNET_EV_DO, 0, telopt, 0, 0);
+			_event(telnet, TELNET_EV_DO, 0, telopt, 0, 0, 0, 0);
 			break;
 		case Q_WANTYES_OP:
 			_set_rfc1143(telnet, telopt, Q_WANTNO, q.him);
 			_send_negotiate(telnet, TELNET_WONT, telopt);
-			_event(telnet, TELNET_EV_DO, 0, telopt, 0, 0);
+			_event(telnet, TELNET_EV_DO, 0, telopt, 0, 0, 0, 0);
 			break;
 		}
 		break;
@@ -357,15 +359,15 @@
 		case Q_YES:
 			_set_rfc1143(telnet, telopt, Q_NO, q.him);
 			_send_negotiate(telnet, TELNET_WONT, telopt);
-			_event(telnet, TELNET_EV_DONT, 0, telopt, 0, 0);
+			_event(telnet, TELNET_EV_DONT, 0, telopt, 0, 0, 0, 0);
 			break;
 		case Q_WANTNO:
 			_set_rfc1143(telnet, telopt, Q_NO, q.him);
-			_event(telnet, TELNET_EV_WONT, 0, telopt, 0, 0);
+			_event(telnet, TELNET_EV_WONT, 0, telopt, 0, 0, 0, 0);
 			break;
 		case Q_WANTNO_OP:
 			_set_rfc1143(telnet, telopt, Q_WANTYES, q.him);
-			_event(telnet, TELNET_EV_WILL, 0, telopt, 0, 0);
+			_event(telnet, TELNET_EV_WILL, 0, telopt, 0, 0, 0, 0);
 			break;
 		case Q_WANTYES:
 		case Q_WANTYES_OP:
@@ -376,6 +378,68 @@
 	}
 }
 
+/* process a subnegotiation buffer; return non-zero if the current buffer
+ * must be aborted and reprocessed due to COMPRESS2 being activated
+ */
+static int _subnegotiate(telnet_t *telnet) {
+	/* invoke callback */
+	_event(telnet, TELNET_EV_SUBNEGOTIATION, 0, telnet->sb_telopt,
+			telnet->buffer, telnet->buffer_pos, 0, 0);
+
+	/* received COMPRESS2 begin marker, setup our zlib box and
+	 * start handling the compressed stream if it's not already.
+	 */
+#ifdef HAVE_ZLIB
+	if (telnet->sb_telopt == TELNET_TELOPT_COMPRESS2) {
+		if (_init_zlib(telnet, 0, 1) != TELNET_EOK)
+			return 0;
+
+		/* notify app that compression was enabled */
+		_event(telnet, TELNET_EV_COMPRESS, 1, 0, 0, 0, 0, 0);
+		return 1;
+	}
+#endif /* HAVE_ZLIB */
+
+	/* parse ZMP args */
+	if (telnet->sb_telopt == TELNET_TELOPT_ZMP) {
+		const char **argv;
+		const char *c = telnet->buffer;
+		size_t i, argc = 0;
+
+		/* make sure this is a valid ZMP buffer */
+		if (telnet->buffer_pos == 0 || telnet->buffer[telnet->buffer_pos - 1]
+				!= 0)
+			return 0;
+
+		/* count arguments */
+		while (c != telnet->buffer + telnet->buffer_pos + 1) {
+			++argc;
+			c += strlen(c) + 1;
+		}
+
+		/* allocate argument array, bail on error */
+		if ((argv = (const char **)malloc(sizeof(char *) * argc)) == 0) {
+			_error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0,
+					"malloc() failed: %s", strerror(errno));
+			return 0;
+		}
+
+		/* populate argument array */
+		for (i = 0, c = telnet->buffer; i != argc; ++i) {
+			argv[i] = c;
+			c += strlen(c) + 1;
+		}
+
+		/* invoke ZMP event */
+		_event(telnet, TELNET_EV_ZMP, 0, 0, 0, 0, argv, argc);
+
+		/* free argument array */
+		free(argv);
+	}
+
+	return 0;
+}
+
 /* initialize a telnet state tracker */
 void telnet_init(telnet_t *telnet, telnet_event_handler_t eh,
 		unsigned char flags, void *user_data) {
@@ -468,7 +532,7 @@
 			if (byte == TELNET_IAC) {
 				if (i != start)
 					_event(telnet, TELNET_EV_DATA, 0, 0, &buffer[start],
-							i - start);
+							i - start, 0, 0);
 				telnet->state = TELNET_STATE_IAC;
 			}
 			break;
@@ -495,13 +559,13 @@
 				break;
 			/* IAC escaping */
 			case TELNET_IAC:
-				_event(telnet, TELNET_EV_DATA, 0, 0, (char*)&byte, 1);
+				_event(telnet, TELNET_EV_DATA, 0, 0, (char*)&byte, 1, 0, 0);
 				start = i + 1;
 				telnet->state = TELNET_STATE_DATA;
 				break;
 			/* some other command */
 			default:
-				_event(telnet, TELNET_EV_IAC, byte, 0, 0, 0);
+				_event(telnet, TELNET_EV_IAC, byte, 0, 0, 0, 0, 0);
 				start = i + 1;
 				telnet->state = TELNET_STATE_DATA;
 			}
@@ -541,36 +605,21 @@
 			switch (byte) {
 			/* end subnegotiation */
 			case TELNET_SE:
-				/* return to default state */
-				start = i + 1;
-				telnet->state = TELNET_STATE_DATA;
-
-				/* invoke callback */
-				_event(telnet, TELNET_EV_SUBNEGOTIATION, 0,
-						telnet->sb_telopt, telnet->buffer, telnet->buffer_pos);
-
-#ifdef HAVE_ZLIB
-				/* received COMPRESS2 begin marker, setup our zlib box and
-				 * start handling the compressed stream if it's not already.
-				 */
-				if (telnet->sb_telopt == TELNET_TELOPT_COMPRESS2) {
-					if (_init_zlib(telnet, 0, 1) != TELNET_EOK)
-						break;
-
-					/* notify app that compression was enabled */
-					_event(telnet, TELNET_EV_COMPRESS, 1, 0, 0, 0);
-
+				/* process subnegotiation */
+				if (_subnegotiate(telnet) != 0) {
 					/* any remaining bytes in the buffer are compressed.
 					 * we have to re-invoke telnet_recv to get those
 					 * bytes inflated and abort trying to process the
 					 * remaining compressed bytes in the current _process
 					 * buffer argument
 					 */
-					telnet_recv(telnet, &buffer[start], size - start);
+					telnet_recv(telnet, &buffer[i + 1], size - start);
 					return;
 				}
-#endif /* HAVE_ZLIB */
 
+				/* return to default state */
+				start = i + 1;
+				telnet->state = TELNET_STATE_DATA;
 				break;
 			/* escaped IAC byte */
 			case TELNET_IAC:
@@ -594,10 +643,19 @@
 
 				/* process what we've got */
 				_event(telnet, TELNET_EV_SUBNEGOTIATION, 0, telnet->sb_telopt,
-						telnet->buffer, telnet->buffer_pos);
+						telnet->buffer, telnet->buffer_pos, 0, 0);
+
+				/* process subnegotiation; see comment in
+				 * TELNET_STATE_SB_DATA_IAC about invoking telnet_recv()
+				 */
+				if (_subnegotiate(telnet) != 0) {
+					telnet_recv(telnet, &buffer[i + 1], size - start);
+					return;
+				}
 
 				/* recursive call to get the current input byte processed
-				 * as a regular IAC command
+				 * as a regular IAC command.  we could use a goto, but
+				 * that would be gross.
 				 */
 				telnet->state = TELNET_STATE_IAC;
 				_process(telnet, (char *)&byte, 1);
@@ -609,7 +667,7 @@
 
 	/* pass through any remaining bytes */ 
 	if (telnet->state == TELNET_STATE_DATA && i != start)
-		_event(telnet, TELNET_EV_DATA, 0, 0, buffer + start, i - start);
+		_event(telnet, TELNET_EV_DATA, 0, 0, buffer + start, i - start, 0, 0);
 }
 
 /* push a bytes into the state tracker */
@@ -648,7 +706,7 @@
 
 			/* on error (or on end of stream) disable further inflation */
 			if (rs != Z_OK) {
-				_event(telnet, TELNET_EV_COMPRESS, 0, 0, 0, 0);
+				_event(telnet, TELNET_EV_COMPRESS, 0, 0, 0, 0, 0, 0);
 
 				inflateEnd(telnet->z);
 				free(telnet->z);
@@ -802,7 +860,7 @@
 			return;
 
 		/* notify app that compression was enabled */
-		_event(telnet, TELNET_EV_COMPRESS, 1, 0, 0, 0);
+		_event(telnet, TELNET_EV_COMPRESS, 1, 0, 0, 0, 0, 0);
 	}
 #endif /* HAVE_ZLIB */
 }
@@ -820,10 +878,10 @@
 	 * instead of passing through _send because _send would result in
 	 * the compress marker itself being compressed.
 	 */
-	_event(telnet, TELNET_EV_SEND, 0, 0, compress2, sizeof(compress2));
+	_event(telnet, TELNET_EV_SEND, 0, 0, compress2, sizeof(compress2), 0, 0);
 
 	/* notify app that compression was successfully enabled */
-	_event(telnet, TELNET_EV_COMPRESS, 1, 0, 0, 0);
+	_event(telnet, TELNET_EV_COMPRESS, 1, 0, 0, 0, 0, 0);
 #endif /* HAVE_ZLIB */
 }