/******************************************************************************
*   TinTin++                                                                  *
*   Copyright (C) 2004 (See CREDITS file)                                     *
*                                                                             *
*   This program is protected under the GNU GPL (See COPYING)                 *
*                                                                             *
*   This program is free software; you can redistribute it and/or modify      *
*   it under the terms of the GNU General Public License as published by      *
*   the Free Software Foundation; either version 2 of the License, or         *
*   (at your option) any later version.                                       *
*                                                                             *
*   This program is distributed in the hope that it will be useful,           *
*   but WITHOUT ANY WARRANTY; without even the implied warranty of            *
*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             *
*   GNU General Public License for more details.                              *
*                                                                             *
*   You should have received a copy of the GNU General Public License         *
*   along with this program; if not, write to the Free Software               *
*   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA *
******************************************************************************/

/******************************************************************************
*                (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                 *
*                                                                             *
*                      coded by Igor van den Hoven 2004                       *
******************************************************************************/

#include "tintin.h"
#include "telnet.h"

/* what we see from the remote end */

char iac_do_naws[]            = { IAC, DO,    TELOPT_NAWS            };
char iac_will_naws[]          = { IAC, WILL,  TELOPT_NAWS            };

char iac_do_sga[]             = { IAC, DO,    TELOPT_SGA             };
char iac_will_sga[]           = { IAC, WILL,  TELOPT_SGA             };

char iac_sb_tspeed[]          = { IAC, SB,    TELOPT_TSPEED, ENV_SEND, IAC, SE };

char iac_dont_ttype[]         = { IAC, DONT,  TELOPT_TTYPE           };
char iac_sb_ttype[]           = { IAC, SB,    TELOPT_TTYPE,  ENV_SEND, IAC, SE  };



char iac_do_echo[]            = { IAC, DO,    TELOPT_ECHO            };
char iac_wont_echo[]          = { IAC, WONT,  TELOPT_ECHO            };
char iac_will_echo[]          = { IAC, WILL,  TELOPT_ECHO            };

char iac_will_mssp[]          = { IAC, WILL,  TELOPT_MSSP            };
char iac_sb_mssp[]            = { IAC, SB,    TELOPT_MSSP            };

char iac_sb_msdp[]            = { IAC, SB,    TELOPT_MSDP            };

char iac_sb_gmcp[]            = { IAC, SB,    TELOPT_GMCP            };

char iac_sb_new_environ[]     = { IAC, SB,    TELOPT_NEW_ENVIRON     };

char iac_sb_zmp[]             = { IAC, SB,    TELOPT_ZMP             };

char iac_sb_mccp1[]           = { IAC, SB,    TELOPT_MCCP1, WILL, SE };
char iac_will_mccp2[]         = { IAC, WILL,  TELOPT_MCCP2           };
char iac_sb_mccp2[]           = { IAC, SB,    TELOPT_MCCP2, IAC, SE  };

char iac_eor[]                = { IAC, EOR                           };
char iac_ga[]                 = { IAC, GA                            };


struct iac_type
{
	int      size;
	char   * code;
	int  (* func) (struct session *ses, int cplen, unsigned char *cpsrc);
};

struct iac_type iac_table [] =
{
	{   3,  iac_do_sga,           &send_will_sga           },
	{   3,  iac_will_sga,         &send_do_sga             },
	{   3,  iac_do_naws,          &send_sb_naws            },
	{   3,  iac_will_naws,        &send_sb_naws            },
	{   3,  iac_do_echo,          &send_echo_will          },
	{   3,  iac_will_echo,        &send_echo_off           },
	{   3,  iac_wont_echo,        &send_echo_on            },
	{   3,  iac_will_mccp2,       &send_do_mccp2           },
	{   3,  iac_will_mssp,        &send_do_mssp            },
	{   3,  iac_sb_mssp,          &recv_sb_mssp            },
	{   3,  iac_sb_msdp,          &recv_sb_msdp            },
	{   3,  iac_sb_gmcp,          &recv_sb_gmcp            },
	{   3,  iac_sb_new_environ,   &recv_sb_new_environ     },
	{   6,  iac_sb_tspeed,        &recv_sb_tspeed          },
	{   3,  iac_dont_ttype,       &recv_dont_ttype         },
	{   6,  iac_sb_ttype,         &recv_sb_ttype           },
	{   3,  iac_sb_zmp,           &recv_sb_zmp             },
	{   5,  iac_sb_mccp1,         &init_mccp               },
	{   5,  iac_sb_mccp2,         &init_mccp               },
	{   2,  iac_eor,              &mark_prompt             },
	{   2,  iac_ga,               &mark_prompt             },
	{   0,  NULL,                 NULL                     }
};


void telopt_debug(struct session *ses, char *format, ...)
{
	char buf[BUFFER_SIZE];
	va_list args;

	if (HAS_BIT(ses->telopts, TELOPT_FLAG_DEBUG))
	{
		va_start(args, format);
		vsprintf(buf, format, args);
		va_end(args);

		tintin_puts(ses, buf);
	}
}


int translate_telopts(struct session *ses, unsigned char *src, int cplen)
{
	int skip, cnt;
	unsigned char *cpdst;
	unsigned char *cpsrc;

	if (cplen == 0)
	{
		return 0;
	}

	push_call("translate_telopts(%p,%p,%d)",ses,src,cplen);

	if (ses->mccp)
	{
		ses->mccp->next_in   = src;
		ses->mccp->avail_in  = cplen;

		ses->mccp->next_out  = gtd->mccp_buf;
		ses->mccp->avail_out = gtd->mccp_len;

		inflate:

		switch (inflate(ses->mccp, Z_SYNC_FLUSH))
		{
			case Z_OK:
				if (ses->mccp->avail_out == 0)
				{
					gtd->mccp_len *= 2;
					gtd->mccp_buf  = (unsigned char *) realloc(gtd->mccp_buf, gtd->mccp_len);

					ses->mccp->avail_out = gtd->mccp_len / 2;
					ses->mccp->next_out  = gtd->mccp_buf + gtd->mccp_len / 2;

					goto inflate;
				}
				cplen = ses->mccp->next_out - gtd->mccp_buf;
				cpsrc = gtd->mccp_buf;
				break;

			case Z_STREAM_END:
				tintin_printf2(ses, "");
				tintin_printf2(ses, "#COMPRESSION END, DISABLING MCCP.");

				cpsrc = src + (cplen - ses->mccp->avail_in);
				cplen = ses->mccp->avail_in;

				inflateEnd(ses->mccp);
				free(ses->mccp);
				ses->mccp = NULL;
				break;

			default:
				tintin_puts2(ses, "");
				tintin_puts2(ses, "#COMPRESSION ERROR, DISABLING MCCP.");
				send_dont_mccp2(ses, 0, NULL);
				inflateEnd(ses->mccp);
				free(ses->mccp);
				ses->mccp = NULL;
				cpsrc = src;
				cplen = 0;
				break;
		}
	}
	else
	{
		cpsrc = src;
	}

	if (HAS_BIT(ses->flags, SES_FLAG_LOGLEVEL) && ses->logfile)
	{
		fwrite(cpsrc, 1, cplen, ses->logfile);
		fflush(ses->logfile);
	}

 	if (ses->read_len + cplen >= ses->read_max)
	{
		ses->read_max = ses->read_len + cplen + 1000;
		ses->read_buf = (unsigned char *) realloc(ses->read_buf, ses->read_max);
	}

	memcpy(ses->read_buf + ses->read_len, cpsrc, cplen);

	cpsrc = ses->read_buf;
	cplen = ses->read_len + cplen;

	if (gtd->mud_output_len + cplen >= gtd->mud_output_max)
	{
		gtd->mud_output_max = gtd->mud_output_len + cplen + 1000;
		gtd->mud_output_buf = (char *) realloc(gtd->mud_output_buf, gtd->mud_output_max);
	}

	cpdst = (unsigned char *) gtd->mud_output_buf + gtd->mud_output_len;

	while (cplen > 0)
	{
		if (*cpsrc == IAC)
		{
			skip = 2;

			if (HAS_BIT(ses->telopts, TELOPT_FLAG_DEBUG))
			{
				switch(cpsrc[1])
				{
					case NOP:   
					case DM:
					case BREAK: 
					case IP:    
					case AO:    
					case AYT:   
					case EC:    
					case EL:
					case IAC:
					case GA:
					case EOR:
						tintin_printf2(ses, "RCVD IAC %s", TELCMD(cpsrc[1]));
						break;

					case DO:
					case DONT:
					case WILL:
					case WONT:
						if (cplen > 2)
						{
							tintin_printf2(ses, "RCVD IAC %s %s", TELCMD(cpsrc[1]), telopt_table[cpsrc[2]].name);
						}
						else
						{
							tintin_printf2(ses, "RCVD IAC %s %d (BAD TELOPT)", TELCMD(cpsrc[1]), cpsrc[2]);
						}
						break;

					case SB:
						if (cplen > 2)
						{
							tintin_printf2(ses, "RCVD IAC SB %s", telopt_table[cpsrc[2]].name);
						}
						break;

					default:
						if (TELCMD_OK(cpsrc[1]))
						{
							tintin_printf2(ses, "RCVD IAC %s %d", TELCMD(cpsrc[1]), cpsrc[2]);
						}
						else
						{
							tintin_printf2(ses, "RCVD IAC %d %d", cpsrc[1], cpsrc[2]);
						}
						break;
				}
			}

			for (cnt = 0 ; iac_table[cnt].code ; cnt++)
			{
				if (cplen < iac_table[cnt].size && !memcmp(cpsrc, iac_table[cnt].code, cplen))
				{
					skip = iac_table[cnt].size;

					break;
				}

				if (cplen >= iac_table[cnt].size && !memcmp(cpsrc, iac_table[cnt].code, iac_table[cnt].size))
				{
					skip = iac_table[cnt].func(ses, cplen, cpsrc);

					if (iac_table[cnt].func == init_mccp)
					{
						pop_call();
						return translate_telopts(ses, cpsrc + skip, cplen - skip);
					}
					break;
				}
			}

			if (iac_table[cnt].code == NULL && cplen > 1)
			{
				switch (cpsrc[1])
				{
					case SE:
					case NOP:
					case DM:
					case BREAK:
					case IP:
					case AO:
					case AYT:
					case EC:
					case EL:
					case GA:
					case EOR:
					     skip = 2;
					     break;

					case WILL:
						if (cplen > 2)
						{
							if (!check_all_events(ses, 1, 0, "IAC WILL %s", telopt_table[cpsrc[2]].name))
							{
								if (!HAS_BIT(ses->telopt_flag[cpsrc[2] / 32], 1 << cpsrc[2] % 32))
								{
									if (telopt_table[cpsrc[2]].want)
									{
										skip = send_do_telopt(ses, cplen, cpsrc);
									}
									else
									{
										skip = send_dont_telopt(ses, cplen, cpsrc);
									}
									SET_BIT(ses->telopt_flag[cpsrc[2] / 32], 1 << cpsrc[2] % 32);
								}
							}
						}
						skip = 3;
						break;

					case DO:
						if (cplen > 2)
						{
							if (!check_all_events(ses, 1, 0, "IAC DO %s", telopt_table[cpsrc[2]].name))
							{
								if (!HAS_BIT(ses->telopt_flag[cpsrc[2] / 32], 1 << cpsrc[2] % 32))
								{
									if (telopt_table[cpsrc[2]].want)
									{
										skip = send_will_telopt(ses, cplen, cpsrc);
									}
									else
									{
										skip = send_wont_telopt(ses, cplen, cpsrc);
									}
									SET_BIT(ses->telopt_flag[cpsrc[2] / 32], 1 << cpsrc[2] % 32);
								}
							}
						}
						skip = 3;
						break;

					case WONT:
					case DONT:
						if (cplen > 2)
						{
							check_all_events(ses, 2, 0, "IAC %s %s", TELCMD(cpsrc[1]), telopt_table[cpsrc[2]].name);

							DEL_BIT(ses->telopt_flag[cpsrc[2] / 32], 1 << cpsrc[2] % 32);
						}
						skip = 3;
						break;

					case SB:
						skip = recv_sb(ses, cplen, cpsrc);
						break;

					case IAC:
						gtd->mud_output_len++;
						*cpdst++ = 0xFF;
						skip = 2;
						break;

					default:
						tintin_printf(NULL, "#IAC BAD TELOPT %d", cpsrc[1]);
						skip = 1;
						break;
				}
			}
			if (skip <= cplen)
			{
				cplen -= skip;
				cpsrc += skip;
			}
			else
			{
				memcpy(ses->read_buf, cpsrc, cplen);

				gtd->mud_output_buf[gtd->mud_output_len] = 0;

				pop_call();
				return cplen;
			}
		}
#ifdef BIG5
		else if (*cpsrc & 0x80)
		{
			*cpdst++ = *cpsrc++;
			gtd->mud_output_len++;
			cplen--;

			if (cplen)
			{
				*cpdst++ = *cpsrc++;
				gtd->mud_output_len++;
				cplen--;
			}
		}
#endif
		else
		{
			/*
				skip '\0' and '\r' in text input
			*/

			switch (*cpsrc)
			{
				case '\0':
					cpsrc++;
					cplen--;
					continue;

				case '\r':
					if (cplen > 1 && cpsrc[1] == '\n')
					{
						cpsrc++;
						cplen--;
						continue;
					}
					break;

				case '\n':
					if (HAS_BIT(ses->telopts, TELOPT_FLAG_PROMPT))
					{
						DEL_BIT(ses->telopts, TELOPT_FLAG_PROMPT);
					}

					*cpdst++ = *cpsrc++;
					gtd->mud_output_len++;
					cplen--;

					while (*cpsrc == '\r')
					{
						cpsrc++;
						cplen--;
					}
					continue;

				default:
					if (HAS_BIT(ses->telopts, TELOPT_FLAG_PROMPT))
					{
						DEL_BIT(ses->telopts, TELOPT_FLAG_PROMPT);

						/*
							Fix up non vt muds
						*/

						if (HAS_BIT(ses->flags, SES_FLAG_SPLIT) || !IS_SPLIT(ses))
						{
							*cpdst++ = '\n';
							gtd->mud_output_len++;
						}
					}
					break;
			}
			*cpdst++ = *cpsrc++;
			gtd->mud_output_len++;
			cplen--;
		}
	}

	gtd->mud_output_buf[gtd->mud_output_len] = 0;

	pop_call();
	return 0;
}

/*
	SGA
*/

int send_do_sga(struct session *ses, int cplen, unsigned char *cpsrc)
{
	SET_BIT(ses->telopts, TELOPT_FLAG_SGA);

	if (!HAS_BIT(ses->telopt_flag[TELOPT_SGA / 32], 1 << TELOPT_SGA % 32))
	{
		SET_BIT(ses->telopt_flag[TELOPT_SGA / 32], 1 << TELOPT_SGA % 32);

		socket_printf(ses, 3, "%c%c%c", IAC, DO, TELOPT_SGA);

		telopt_debug(ses, "SENT IAC DO %s", telopt_table[TELOPT_SGA].name);
	}
	return 3;
}

int send_will_sga(struct session *ses, int cplen, unsigned char *cpsrc)
{
	if (!HAS_BIT(ses->telopt_flag[TELOPT_SGA / 32], 1 << TELOPT_SGA % 32))
	{
		SET_BIT(ses->telopt_flag[TELOPT_SGA / 32], 1 << TELOPT_SGA % 32);

		socket_printf(ses, 3, "%c%c%c", IAC, WILL, TELOPT_SGA);

		telopt_debug(ses, "SENT IAC WILL %s", telopt_table[TELOPT_SGA].name);
	}
	return 3;
}

int mark_prompt(struct session *ses, int cplen, unsigned char *cpsrc)
{
	SET_BIT(ses->telopts, TELOPT_FLAG_PROMPT);

	return 2;
}

/*
	TTYPE
*/

int recv_dont_ttype(struct session *ses, int cplen, unsigned char *cpsrc)
{
	if (!check_all_events(ses, 0, 0, "IAC DONT TERMINAL TYPE"))
	{
		DEL_BIT(ses->telopts, TELOPT_FLAG_TTYPE);

		DEL_BIT(ses->telopt_flag[cpsrc[2] / 32], 1 << cpsrc[2] % 32);
	}
	return 3;
}

int recv_sb_ttype(struct session *ses, int cplen, unsigned char *cpsrc)
{
	if (!check_all_events(ses, 0, 1, "IAC SB TERMINAL TYPE", ntos(cpsrc[3])))
	{
		if (HAS_BIT(ses->telopts, TELOPT_FLAG_TTYPE) && getenv("TERM"))
		{
			socket_printf(ses, 6 + strlen(getenv("TERM")), "%c%c%c%c%s%c%c", IAC, SB, TELOPT_TTYPE, 0, getenv("TERM"), IAC, SE);

			telopt_debug(ses, "SENT IAC SB TTYPE %s", getenv("TERM"));
		}
		else
		{
			socket_printf(ses, 14, "%c%c%c%c%s%c%c", IAC, SB, TELOPT_TTYPE, 0, "TINTIN++", IAC, SE);

			telopt_debug(ses, "SENT IAC SB TTYPE %s", "TINTIN++");

			SET_BIT(ses->telopts, TELOPT_FLAG_TTYPE);
		}
	}
	return 6;
}


/*
	TSPEED
*/

int recv_sb_tspeed(struct session *ses, int cplen, unsigned char *cpsrc)
{
	SET_BIT(ses->telopts, TELOPT_FLAG_TSPEED);

	if (!check_all_events(ses, 0, 1, "IAC SB TSPEED", ntos(cpsrc[3])))
	{
		socket_printf(ses, 17, "%c%c%c%c%s%c%c", IAC, SB, TELOPT_TSPEED, 0, "38400,38400", IAC, SE);

		telopt_debug(ses, "SENT IAC SB 0 %s 38400,38400 IAC SB", telopt_table[TELOPT_TSPEED].name);
	}
	return 6;
}


/*
	NAWS
*/

int send_will_naws(struct session *ses, int cplen, unsigned char *cpsrc)
{
	SET_BIT(ses->telopt_flag[TELOPT_NAWS / 32], 1 << TELOPT_NAWS % 32);

	socket_printf(ses, 3, "%c%c%c", IAC, WILL, TELOPT_NAWS);

	telopt_debug(ses, "SENT IAC WILL NAWS");

	return 3;
}

int send_sb_naws(struct session *ses, int cplen, unsigned char *cpsrc)
{
	int rows;

	if (!HAS_BIT(ses->telopt_flag[TELOPT_NAWS / 32], 1 << TELOPT_NAWS % 32))
	{
		send_will_naws(ses, cplen, cpsrc);
	}

	rows = HAS_BIT(ses->flags, SES_FLAG_SPLIT) ? ses->bot_row - ses->top_row + 1 : ses->rows;

	socket_printf(ses, 9, "%c%c%c%c%c%c%c%c%c", IAC, SB, TELOPT_NAWS, ses->cols / 256, ses->cols % 256, rows / 256, rows % 256, IAC, SE);

	SET_BIT(ses->telopts, TELOPT_FLAG_NAWS);

	telopt_debug(ses, "SENT IAC SB NAWS %d %d %d %d", ses->cols / 256, ses->cols % 256, ses->rows / 256, ses->rows % 256);

	return 3;
}

/*
	ECHO
*/

int send_echo_on(struct session *ses, int cplen, unsigned char *cpsrc)
{
	socket_printf(ses, 3, "%c%c%c", IAC, DONT, TELOPT_ECHO);

	SET_BIT(ses->telopts, TELOPT_FLAG_ECHO);

	telopt_debug(ses, "SENT IAC DONT ECHO");

	return 3;
}

int send_echo_off(struct session *ses, int cplen, unsigned char *cpsrc)
{
	socket_printf(ses, 3, "%c%c%c", IAC, DO, TELOPT_ECHO);

	DEL_BIT(ses->telopts, TELOPT_FLAG_ECHO);

	telopt_debug(ses, "SENT IAC DO ECHO");

	return 3;
}

int send_echo_will(struct session *ses, int cplen, unsigned char *cpsrc)
{
	socket_printf(ses, 3, "%c%c%c", IAC, WILL, TELOPT_ECHO);

	DEL_BIT(ses->telopts, TELOPT_FLAG_ECHO);

	telopt_debug(ses, "SENT IAC WILL ECHO");

	return 3;
}

/*
	IP
*/

int send_ip(struct session *ses, int cplen, unsigned char *cpsrc)
{
	socket_printf(ses, 5, "%c%c%c%c%c", IAC, IP, IAC, DO, TELOPT_TIMINGMARK);

	telopt_debug(ses, "SENT IAC IP");
	telopt_debug(ses, "SENT IAC DO TIMING MARK");

	return 3;
}

/*
	Automatic telopt handling
*/

int send_wont_telopt(struct session *ses, int cplen, unsigned char *cpsrc)
{
	socket_printf(ses, 3, "%c%c%c", IAC, WONT, cpsrc[2]);

	telopt_debug(ses, "SENT IAC WONT %s", telopt_table[cpsrc[2]].name);

	return 3;
}

int send_dont_telopt(struct session *ses, int cplen, unsigned char *cpsrc)
{
	socket_printf(ses, 3, "%c%c%c", IAC, DONT, cpsrc[2]);

	telopt_debug(ses, "SENT IAC DONT %s", telopt_table[cpsrc[2]].name);

	return 3;
}

int send_will_telopt(struct session *ses, int cplen, unsigned char *cpsrc)
{
	socket_printf(ses, 3, "%c%c%c", IAC, WILL, cpsrc[2]);

	telopt_debug(ses, "SENT IAC WILL %s", telopt_table[cpsrc[2]].name);

	return 3;
}

int send_do_telopt(struct session *ses, int cplen, unsigned char *cpsrc)
{
	socket_printf(ses, 3, "%c%c%c", IAC, DO, cpsrc[2]);

	telopt_debug(ses, "SENT IAC DO %s", telopt_table[cpsrc[2]].name);

	return 3;
}

/*
	MSSP (Mud Server Status Protocol)
*/

int send_do_mssp(struct session *ses, int cplen, unsigned char *cpsrc)
{
	if (!check_all_events(ses, 0, 0, "IAC WILL MSSP"))
	{
		if (HAS_BIT(ses->telopts, TELOPT_FLAG_DEBUG))
		{
			socket_printf(ses, 3, "%c%c%c", IAC, DO, TELOPT_MSSP);

			telopt_debug(ses, "SENT IAC DO MSSP");
		}
	}
	return 3;
}

int recv_sb_mssp(struct session *ses, int cplen, unsigned char *src)
{
	char var[BUFFER_SIZE], val[BUFFER_SIZE];
	char *pto;
	int i;

	var[0] = val[0] = i = 0;

	if (skip_sb(ses, cplen, src) > cplen)
	{
		return cplen + 1;
	}

	while (i < cplen && src[i] != SE)
	{
		switch (src[i])
		{
			case MSSP_VAR:
				i++;
				pto = var;

				while (i < cplen && src[i] >= 3 && src[i] != IAC)
				{
					*pto++ = src[i++];
				}
				*pto = 0;
				break;

			case MSSP_VAL:
				i++;
				pto = val;

				while (i < cplen && src[i] >= 3 && src[i] != IAC)
				{
					*pto++ = src[i++];
				}
				*pto = 0;

				telopt_debug(ses, "MSSP VAR %-20s VAL %s", var, val);

				check_all_events(ses, 0, 2, "IAC SB MSSP", var, val);
				break;

			default:
				i++;
				break;
		}
	}

	telopt_debug(ses, "IAC SB MSSP IAC SE");

	check_all_events(ses, 0, 0, "IAC SB MSSP IAC SE");

	return UMIN(i + 1, cplen);
}

/*
	MSDP (Mud Server Data Protocol)
*/

#define MSDP_STATE_NEW 0
#define MSDP_STATE_VAR 1
#define MSDP_STATE_VAL 2

int recv_sb_msdp(struct session *ses, int cplen, unsigned char *src)
{
	char buf[BUFFER_SIZE], var[BUFFER_SIZE], val[BUFFER_SIZE];
	char *pto, *ptarr[1000];
	int i, nest = 0, state[1000];

	var[0] = val[0] = i = state[nest] = 0;

	if (skip_sb(ses, cplen, src) > cplen)
	{
		return cplen + 1;
	}

	while (i < cplen && src[i] != SE)
	{
		switch (src[i])
		{
			case MSDP_VAR:
				i++;
				pto = buf;

				while (i < cplen && src[i] > 4 && src[i] != IAC)
				{
					*pto++ = src[i++];
				}
				*pto = 0;

				substitute(ses, buf, var, SUB_SEC);

				state[nest] = 0;
				break;

			case MSDP_VAL:
				pto = val;

				while (i < cplen)
				{
					switch (src[i])
					{
						case MSDP_VAR:
							if (state[nest] >= 2)
							{
								*pto++ = '}';
							}

							if (nest == 0)
							{
								goto end_val;
							}

							if (state[nest] >= 1)
							{
								*pto++ = '}';
							}
							*pto++ = '{';

							state[nest] = 0;
							break;

						case MSDP_VAL:
							if (state[nest] == 0)
							{
								if (nest)
								{
									*pto++ = '}';
									*pto++ = '{';
								}
								ptarr[nest] = pto;
								state[nest]++;
							}
							else if (state[nest] == 1)
							{
								sprintf(buf, "{1}{%.*s}{2}{", (int) (pto - ptarr[nest]), ptarr[nest]);
								sprintf(ptarr[nest], "%s", buf);
								pto += 9;
								state[nest]++;
							}
							else
							{
								pto += sprintf(pto, "}{%d}{", ++state[nest]);
							}
							break;

						case MSDP_OPEN:
							if (++nest > 1)
							{
								*pto++ = '{';
							}
							state[nest] = 0;
							break;

						case MSDP_CLOSE:
							if (state[nest] >= 2)
							{
								*pto++ = '}';
							}

							if (state[nest] >= 1)
							{
								*pto++ = '}';
							}

							if (--nest > 0)
							{
								*pto++ = '}';
							}

							break;

						case IAC:
							if (state[nest] >= 2)
							{
								*pto++ = '}';
							}
							goto end_val;

						case '\\':
							*pto++ = '\\';
							*pto++ = '\\';
							break;

						case '{':
							*pto++ = '\\';
							*pto++ = 'x';
							*pto++ = '7';
							*pto++ = 'B';
							break;

						case '}':
							*pto++ = '\\';
							*pto++ = 'x';
							*pto++ = '7';
							*pto++ = 'D';
							break;

						case COMMAND_SEPARATOR:
							*pto++ = '\\';
							*pto++ = COMMAND_SEPARATOR;
							break;

						default:
							*pto++ = src[i];
							break;
					}
					i++;
				}

				end_val:

				*pto = 0;

				telopt_debug(ses, "IAC SB MSDP VAR %-20s VAL %s", var, val);

				check_all_events(ses, 1, 2, "IAC SB MSDP %s", var, var, val);

				check_all_events(ses, 0, 2, "IAC SB MSDP", var, val);

				state[nest] = 0;
				break;

			default:
				i++;
				break;
		}
	}

	telopt_debug(ses, "IAC SB MSDP IAC SE");

	check_all_events(ses, 0, 0, "IAC SB MSDP IAC SE");

	return UMIN(i + 1, cplen);
}

/*
	NEW-ENVIRON
*/

int recv_sb_new_environ(struct session *ses, int cplen, unsigned char *src)
{
	char buf[BUFFER_SIZE], var[BUFFER_SIZE], val[BUFFER_SIZE];
	char *pto;
	int i, x;

	var[0] = val[0] = 0;

	if (skip_sb(ses, cplen, src) > cplen)
	{
		return cplen + 1;
	}

	telopt_debug(ses, "IAC SB NEW-ENVIRON %d %d", src[3], src[4]);

	i = 4;
	x = -1;

	while (i < cplen && src[i] != SE)
	{
		switch (src[i])
		{
			case ENV_VAR:
			case ENV_USR:
				x = src[i];
				i++;
				pto = buf;

				while (i < cplen && src[i] >= 4 && src[i] != IAC)
				{
					*pto++ = src[i++];
				}
				*pto = 0;

				substitute(ses, buf, var, SUB_SEC);

				if (src[3] == ENV_SEND)
				{
					telopt_debug(ses, "IAC SB NEW-ENVIRON SEND %s", x ? "VAR" : "USR");

					check_all_events(ses, 1, 2, "IAC SB NEW-ENVIRON SEND %s", x ? "VAR" : "USR", var, "");
				}
				break;

			case ENV_VAL:
				i++;
				pto = buf;

				while (i < cplen && src[i] >= 3 && src[i] != IAC)
				{
					*pto++ = src[i++];
				}
				*pto = 0;

				substitute(ses, buf, val, SUB_SEC);

				telopt_debug(ses, "IAC SB NEW-ENVIRON %s %s", src[3] ? "IS" : "INFO", x ? "VAR" : "USR");

				check_all_events(ses, 2, 2, "IAC SB NEW-ENVIRON %s %s", src[i] ? "IS" : "INFO", x ? "VAR" : "USR", var, val);
				break;

			case IAC:
				if (x == -1)
				{
					telopt_debug(ses, "IAC SB NEW-ENVIRON %s", src[3] == 0 ? "IS" : src[3] == 1 ? "SEND" : "INFO");

					check_all_events(ses, 1, 2, "IAC SB NEW-ENVIRON %s", src[3] == 0 ? "IS" : src[3] == 1 ? "SEND" : "INFO", var, val);
				}
				i++;
				break;
			default:
				i++;
				break;
		}
	}

	telopt_debug(ses, "IAC SB NEW-ENVIRON IAC SE");

	check_all_events(ses, 0, 0, "IAC SB NEW-ENVIRON IAC SE");

	return i + 1;
}

int recv_sb_zmp(struct session *ses, int cplen, unsigned char *src)
{
	char buf[BUFFER_SIZE], var[BUFFER_SIZE], val[BUFFER_SIZE];
	char *pto;
	int i, x;

	var[0] = val[0] = x = 0;

	if (skip_sb(ses, cplen, src) > cplen)
	{
		return cplen + 1;
	}

	i = 3;

	while (i < cplen && src[i] != SE)
	{
		switch (src[i])
		{
			case IAC:
				i++;
				break;

			default:
				pto = buf;

				while (i < cplen && src[i])
				{
					*pto++ = src[i++];
				}
				*pto = src[i++];

				substitute(ses, buf, x ? val : var, SUB_SEC);

				if (x++)
				{
					telopt_debug(ses, "IAC SB ZMP %s", var);

					check_all_events(ses, 1, 1, "IAC SB ZMP %s", var, val);
				}
				break;
		}
	}

	telopt_debug(ses, "IAC SB ZMP %s IAC SE", var);

	check_all_events(ses, 1, 0, "IAC SB ZMP %s IAC SE", var);

	return UMIN(i + 1, cplen);
}

int recv_sb_gmcp(struct session *ses, int cplen, unsigned char *src)
{
	char mod[BUFFER_SIZE], val[BUFFER_SIZE], json[BUFFER_SIZE], *pto;
	int i, state[1000], nest, type;

	push_call("recv_sb_gmcp(%p,%d,%p)",ses,cplen,src);

	if (skip_sb(ses, cplen, src) > cplen)
	{
		pop_call();
		return cplen + 1;
	}

	mod[0] = val[0] = state[0] = nest = type = 0;

	i = 3;

	pto = mod;

	// space out

	while (i < cplen && src[i] == ' ')
	{
		i++;
	}

	// grab module

	while (i < cplen && src[i] != IAC)
	{
		if (src[i] == ' ' || src[i] == '{' || src[i] == '[')
		{
			break;
		}
		*pto++ = src[i++];
	}

	*pto = 0;

	// parse JSON content

	pto = val;

	while (i < cplen && src[i] != IAC)
	{
		switch (src[i])
		{
			case ' ':
				i++;
				break;

			case '{':
				if (nest != 0)
				{
					*pto++ = '{';
				}
				i++;
				state[++nest] = 0;
				break;

			case '}':
				nest--;
				i++;
				if (nest != 0)
				{
					*pto++ = '}';
				}
				break;

			case '[':
				if (nest != 0)
				{
					*pto++ = '{';
				}
				i++;
				state[++nest] = 1;
				pto += sprintf(pto, "{%d}", state[nest]);
				break;

			case ']':
				nest--;
				i++;
				if (nest != 0)
				{
					*pto++ = '}';
				}
				break;

			case ':':
				i++;
				break;

			case ',':
				i++;
				if (state[nest])
				{
					pto += sprintf(pto, "{%d}", ++state[nest]);
				}
				break;

			case '"':
				i++;
				if (nest)
				{
					*pto++ = '{';
				}
				type = 1;

				while (i < cplen && src[i] != IAC && type == 1)
				{
					switch (src[i])
					{
						case '\\':
							i++;

							if (i < cplen && src[i] == '"')
							{
								*pto++ = src[i++];
							}
							else
							{
								*pto++ = '\\';
								*pto++ = '\\';
							}
							break;

						case '"':
							i++;
							type = 0;
							break;

						case '{':
							i++;
							*pto++ = '\\';
							*pto++ = 'x';
							*pto++ = '7';
							*pto++ = 'B';
							break;

						case '}':
							i++;
							*pto++ = '\\';
							*pto++ = 'x';
							*pto++ = '7';
							*pto++ = 'D';
							break;

						case COMMAND_SEPARATOR:
							i++;
							*pto++ = '\\';
							*pto++ = COMMAND_SEPARATOR;
							break;

						default:
							*pto++ = src[i++];
							break;
					}
				}

				if (nest)
				{
					*pto++ = '}';
				}
				break;

			default:
				if (nest)
				{
					*pto++ = '{';
				}

				type = 1;

				while (i < cplen && src[i] != IAC && type == 1)
				{
					switch (src[i])
					{
						case '}':
						case ']':
						case ',':
						case ':':
							type = 0;
							break;

						case ' ':
							i++;
							break;

						default:
							*pto++ = src[i++];
							break;
					}
				}

				if (nest)
				{
					*pto++ = '}';
				}
				break;
		}
	}
	*pto = 0;

	// Raw json data for debugging purposes.

	pto = json;
	i = 3;

	while (i < cplen && src[i] != IAC)
	{
		switch (src[i])
		{
			case '\\':
				i++;
				*pto++ = '\\';
				*pto++ = '\\';
				break;

			case '{':
				i++;
				*pto++ = '\\';
				*pto++ = 'x';
				*pto++ = '7';
				*pto++ = 'B';
				break;

			case '}':
				i++;
				*pto++ = '\\';
				*pto++ = 'x';
				*pto++ = '7';
				*pto++ = 'D';
				break;

			case COMMAND_SEPARATOR:
				i++;
				*pto++ = '\\';
				*pto++ = COMMAND_SEPARATOR;
				break;

			default:
				*pto++ = src[i++];
				break;
		}
	}
	*pto = 0;

	while (i < cplen && src[i] != SE)
	{
		i++;
	}

	telopt_debug(ses, "IAC SB GMCP %s IAC SE", mod);

	check_all_events(ses, 1, 2, "IAC SB GMCP %s IAC SE", mod, val, json);

	pop_call();
	return UMIN(i + 1, cplen);
}

/*
	MCCP
*/

int send_do_mccp2(struct session *ses, int cplen, unsigned char *cpsrc)
{
	if (HAS_BIT(ses->flags, SES_FLAG_MCCP))
	{
		socket_printf(ses, 3, "%c%c%c", IAC, DO, TELOPT_MCCP2);

		telopt_debug(ses, "SENT IAC DO MCCP2");
	}
	else
	{
		socket_printf(ses, 3, "%c%c%c", IAC, WONT, TELOPT_MCCP2);

		telopt_debug(ses, "SENT IAC WONT MCCP2 (MCCP HAS BEEN DISABLED)");
	}
	return 3;
}

int send_dont_mccp2(struct session *ses, int cplen, unsigned char *cpsrc)
{
	socket_printf(ses, 3, "%c%c%c", IAC, DONT, TELOPT_MCCP2);

	telopt_debug(ses, "SENT DONT MCCP2");

	return 3;
}


int init_mccp(struct session *ses, int cplen, unsigned char *cpsrc)
{
	if (ses->mccp)
	{
		return 5;
	}

	ses->mccp = (z_stream *) calloc(1, sizeof(z_stream));

	ses->mccp->data_type = Z_ASCII;
	ses->mccp->zalloc    = zlib_alloc;
	ses->mccp->zfree     = zlib_free;
	ses->mccp->opaque    = NULL;

	if (inflateInit(ses->mccp) != Z_OK)
	{
		tintin_puts2(ses, "#FAILED TO INITIALIZE MCCP.");
		send_dont_mccp2(ses, 0, NULL);
		free(ses->mccp);
		ses->mccp = NULL;
	}
	else
	{
		telopt_debug(ses, "MCCP INITIALIZED.");
	}
	return 5;
}


void *zlib_alloc(void *opaque, unsigned int items, unsigned int size)
{
	return calloc(items, size);
}


void zlib_free(void *opaque, void *address)
{
	free(address);
}

void init_telnet_session(struct session *ses)
{
//	send_do_sga(ses, 0, NULL);
//	send_will_naws(ses, 0, NULL);
}


/*
	Returns the length of a telnet subnegotiation
*/

int skip_sb(struct session *ses, int cplen, unsigned char *cpsrc)
{
	int i;

	for (i = 1 ; i < cplen ; i++)
	{
		if (cpsrc[i] == SE && cpsrc[i-1] == IAC)
		{
			return i + 1;
		}
	}

	telopt_debug(ses, "SKIP SB (%d)", cplen);

	return cplen + 1;
}

int recv_sb(struct session *ses, int cplen, unsigned char *cpsrc)
{
	char *pt1, *pt2, var1[BUFFER_SIZE], var2[BUFFER_SIZE];
	int i;

	if (skip_sb(ses, cplen, cpsrc) > cplen)
	{
		return cplen + 1;
	}

	pt1 = var1;
	pt2 = var2;

	for (i = 3 ; i < cplen ; i++)
	{
		if (cpsrc[i] == IAC && i + 1 < cplen && cpsrc[i+1] == SE)
		{
			break;
		}
		else
		{
			*pt1++ = cpsrc[i];

			sprintf(pt2, "%03d ", cpsrc[i]);

			pt2 += 4;
		}
	}

	*pt1 = 0;
	*pt2 = 0;

	check_all_events(ses, 1, 2, "IAC SB %s", telopt_table[cpsrc[2]].name, var1, var2);

	return i + 2;
}