wickedmud/doc/
wickedmud/help/
wickedmud/players/
/***************************************************************************
 * Mud Telopt Handler 1.3 by Igor van den Hoven.               06 Apr 2009 *
 ***************************************************************************/

#include "mud.h"
#include "telnet.h"

#define TELOPT_DEBUG 1

char mccp_buf[MAX_OUTPUT];

char iac_will_ttype[]       = { IAC, WILL, TELOPT_TTYPE, 0 };
char iac_sb_ttype_is[]      = { IAC, SB,   TELOPT_TTYPE, ENV_IS, 0 };

char iac_sb_naws[]          = { IAC, SB,   TELOPT_NAWS, 0 };

char iac_will_new_environ[] = { IAC, WILL, TELOPT_NEW_ENVIRON, 0 };
char iac_sb_new_environ[]   = { IAC, SB,   TELOPT_NEW_ENVIRON, ENV_IS,0 };

char iac_do_mssp[]          = { IAC, DO,   TELOPT_MSSP, 0 };

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

char iac_do_mccp[]          = { IAC, DO,   TELOPT_MCCP, 0 };
char iac_dont_mccp[]        = { IAC, DONT, TELOPT_MCCP, 0 };


struct telopt_type
{
	int      size;
	char   * code;
	int   (* func) (D_S *d, unsigned char *src, int srclen);
};

const struct telopt_type telopt_table [] =
{
	{ 3, iac_will_ttype,       &process_will_ttype},
	{ 4, iac_sb_ttype_is,      &process_sb_ttype_is},

	{ 3, iac_sb_naws,          &process_sb_naws},

	{ 3, iac_will_new_environ, &process_will_new_environ},
	{ 4, iac_sb_new_environ,   &process_sb_new_environ},

	{ 3, iac_do_mssp,          &process_do_mssp},

	{ 3, iac_do_msdp,          &process_do_msdp},
	{ 3, iac_sb_msdp,          &process_sb_msdp},

	{ 3, iac_do_mccp,          &process_do_mccp},
	{ 3, iac_dont_mccp,        &process_dont_mccp},

	{ 0, NULL,                 NULL}
};

/*
	Call this to announce support for telopts marked as such in tables.c
*/

void announce_support( D_S *d)
{
	int i;

	for (i = 0 ; i < 255 ; i++)
	{
		if (telnet_table[i].flags)
		{
			if (HAS_BIT(telnet_table[i].flags, ANNOUNCE_WILL))
			{
				descriptor_printf(d, "%c%c%c", IAC, WILL, i);
			}
			if (HAS_BIT(telnet_table[i].flags, ANNOUNCE_DO))
			{
				descriptor_printf(d, "%c%c%c", IAC, DO, i);
			}
		}
	}
}

/*
	This is the main routine that strips out and handles telopt negotiations.
	It also deals with \r and \0 so commands are separated by a single \n.
*/

int translate_telopts(D_S *d, char *src, int srclen, char *out)
{
	int cnt, skip;
	unsigned char *pti, *pto;

	pti = src;
	pto = out;

	if (d->top_telnet)
	{
		if (d->top_telnet + srclen + 1 < MAX_BUFFER)
		{
			memcpy(d->telbuf + d->top_telnet, src, srclen);

			srclen += d->top_telnet;

			pti = d->telbuf;
		}
		d->top_telnet = 0;
	}

	while (srclen > 0)
	{
		switch (*pti)
		{
			case IAC:
				skip = 2;

				debug_telopts(d, pti, srclen);

				for (cnt = 0 ; telopt_table[cnt].code ; cnt++)
				{
					if (srclen < telopt_table[cnt].size)
					{
						if (!memcmp(pti, telopt_table[cnt].code, srclen))
						{
							skip = telopt_table[cnt].size;

							break;
						}
					}
					else
					{
						if (!memcmp(pti, telopt_table[cnt].code, telopt_table[cnt].size))
						{
							skip = telopt_table[cnt].func(d, pti, srclen);

							break;
						}
					}
				}

				if (telopt_table[cnt].code == NULL && srclen > 1)
				{
					switch (pti[1])
					{
						case WILL:
						case DO:
						case WONT:
						case DONT:
							skip = 3;
							break;

						case SB:
							skip = skip_sb(d, pti, srclen);
							break;

						case IAC:
							*pto++ = *pti++;
							srclen--;
							skip = 1;
							break;

						default:
							if (TELCMD_OK(pti[1]))
							{
								skip = 2;
							}
							else
							{
								skip = 1;
							}
							break;
					}
				}

				if (skip <= srclen)
				{
					pti += skip;
					srclen -= skip;
				}
				else
				{
					memcpy(d->telbuf, pti, srclen);
					d->top_telnet = srclen;

					*pto = 0;
					return strlen(out);
				}
				break;

			case '\r':
				if (srclen > 1 && pti[1] == '\0')
				{
					*pto++ = '\n';
				}
				pti++;
				srclen--;
				break;

			case '\0':
				pti++;
				srclen--;
				break;

			default:
				*pto++ = *pti++;
				srclen--;
				break;
		}
	}
	*pto = 0;

	return strlen(out);
}

void debug_telopts( D_S *d, unsigned char *src, int srclen )
{
	if (srclen > 1 && TELOPT_DEBUG)
	{
		switch(src[1])
		{
			case IAC:
				log_string("D%d@%s RCVD IAC IAC", d->control, d->hostname);
				break;

			case DO:
			case DONT:
			case WILL:
			case WONT:
			case SB:
				if (srclen > 2)
				{
					log_string("D%d@%s RCVD IAC %s %s", d->control, d->hostname, TELCMD(src[1]), TELOPT(src[2]));
				}
				else
				{
					log_string("D%d@%s RCVD IAC %s ?", d->control, d->hostname, TELCMD(src[1]));
				}
				break;

			default:
				if (TELCMD_OK(src[1]))
				{
					log_string("D%d@%s RCVD IAC %s", d->control, d->hostname, TELCMD(src[1]));
				}
				else
				{
					log_string("D%d@%s RCVD IAC %d", d->control, d->hostname, src[1]);
				}
				break;
		}
	}
	else
	{
		log_string("D%d@%s RCVD IAC ?", d->control, d->hostname);
	}
}

/*
	Send to client to have it disable local echo
*/

void send_echo_off( D_S *d )
{
	descriptor_printf(d, "%c%c%c", IAC, WILL, TELOPT_ECHO);
}

/*
	Send to client to have it enable local echo
*/

void send_echo_on( D_S *d )
{
	descriptor_printf(d, "%c%c%c", IAC, WONT, TELOPT_ECHO);
}


/*
	Terminal Type negotiation - make sure d->terminal_type is initialized.
*/

int process_will_ttype( D_S *d, unsigned char *src, int srclen )
{
	if (*d->terminal_type == 0)
	{
		descriptor_printf(d, "%c%c%c%c%c%c", IAC, SB, TELOPT_TTYPE, ENV_SEND, IAC, SE);
	}
	return 3;
}

int process_sb_ttype_is( D_S *d, unsigned char *src, int srclen )
{
	char val[MAX_BUFFER];
	char *pto;
	int i;

	if (skip_sb(d, src, srclen) > srclen)
	{
		return srclen + 1;
	}

	pto = val;

	for (i = 4 ; i < srclen && src[i] != SE ; i++)
	{
		switch (src[i])
		{
			default:			
				*pto++ = src[i];
				break;

			case IAC:
				*pto = 0;
				RESTRING(d->terminal_type, val);
				break;
		}
	}
	return i + 1;
}

/*
	NAWS: Negotiate About Window Size
*/

int process_sb_naws( D_S *d, unsigned char *src, int srclen )
{
	int i, j;

	d->cols = d->rows = 0;

	if (skip_sb(d, src, srclen) > srclen)
	{
		return srclen + 1;
	}

	for (i = 3, j = 0 ; i < srclen && j < 4 ; i++, j++)
	{
		switch (j)
		{
			case 0:
				d->cols += (src[i] == IAC) ? src[i++] * 256 : src[i] * 256;
				break;
			case 1:
				d->cols += (src[i] == IAC) ? src[i++] : src[i];
				break;
			case 2:
				d->rows += (src[i] == IAC) ? src[i++] * 256 : src[i] * 256;
				break;
			case 3:
				d->rows += (src[i] == IAC) ? src[i++] : src[i];
				break;
		}
	}

	return skip_sb(d, src, srclen);
}

/*
	NEW ENVIRON, used here to discover Windows telnet.
*/

int process_will_new_environ( D_S *d, unsigned char *src, int srclen )
{
	descriptor_printf(d, "%c%c%c%c%c%s%c%c", IAC, SB, TELOPT_NEW_ENVIRON, ENV_SEND, ENV_VAR, "SYSTEMTYPE", IAC, SE);

	return 3;
}

int process_sb_new_environ( D_S *d, unsigned char *src, int srclen )
{
	char var[MAX_BUFFER], val[MAX_BUFFER];
	char *pto;
	int i;

	if (skip_sb(d, src, srclen) > srclen)
	{
		return srclen + 1;
	}

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

	i = 4;

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

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

			case ENV_VAL:
				i++;
				pto = val;

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

				if (!strcasecmp(var, "SYSTEMTYPE") && !strcasecmp(val, "WIN32"))
				{
					RESTRING(d->terminal_type, "WIN32");
				}
				break;

			default:
				i++;
				break;
		}
	}
	return i + 1;
}

/*
	MSSP: Mud Server Status Protocol
*/

int process_do_mssp( D_S *d, unsigned char *src, int srclen )
{
	char buffer[MAX_OUTPUT] = { 0 };

	cat_sprintf(buffer, "%c%s%c%s",   MSSP_VAR, "NAME",              MSSP_VAL, "WickedMUD");
	cat_sprintf(buffer, "%c%s%c%d",   MSSP_VAR, "PLAYERS",           MSSP_VAL, total_players());
	cat_sprintf(buffer, "%c%s%c%lld", MSSP_VAR, "UPTIME",            MSSP_VAL, (long long) boot_time);

/*	cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "HOSTNAME",          MSSP_VAL, ""); */
/*	cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "PORT",              MSSP_VAL, ""); */

	cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "CRAWL DELAY",       MSSP_VAL, "-1");
	cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "CODEBASE",          MSSP_VAL, "WickedMud 1.1");
	cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "CONTACT",           MSSP_VAL, "");
	cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "CREATED",           MSSP_VAL, "2009");
	cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "ICON",              MSSP_VAL, "");
/*	cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "IP",                MSSP_VAL, ""); */
	cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "LANGUAGE",          MSSP_VAL, "English");
	cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "LOCATION",          MSSP_VAL, "");
	cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "MINIMUM AGE",       MSSP_VAL, "0");
	cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "WEBSITE",           MSSP_VAL, "http://tintin.sourceforge.net/mssp");

	cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "FAMILY",            MSSP_VAL, "SocketMUD");
	cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "GENRE",             MSSP_VAL, "None");
	cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "GAMEPLAY",          MSSP_VAL, "None");
	cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "GAMESYSTEM",        MSSP_VAL, "None");
	cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "INTERMUD",          MSSP_VAL, "");
	cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "STATUS",            MSSP_VAL, "Alpha");
	cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "SUBGENRE",          MSSP_VAL, "None");

	cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "ANSI",              MSSP_VAL, 1);
	cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "MCCP",              MSSP_VAL, 1);
	cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "MCP",               MSSP_VAL, 0);
	cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "MSP",               MSSP_VAL, 0);
	cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "MXP",               MSSP_VAL, 0);
	cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "PUEBLO",            MSSP_VAL, 0);
	cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "VT100",             MSSP_VAL, 0);
	cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "XTERM 256 COLORS",  MSSP_VAL, 0);

	cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "PAY TO PLAY",       MSSP_VAL, 0);
	cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "PAY FOR PERKS",     MSSP_VAL, 0);
	cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "HIRING BUILDERS",   MSSP_VAL, 0);
	cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "HIRING CODERS",     MSSP_VAL, 0);

	descriptor_printf(d, "%c%c%c%s%c%c", IAC, SB, TELOPT_MSSP, buffer, IAC, SE);

	return 3;
}

/*
	MSDP: Mud Server Data Protocol
*/

int process_do_msdp( D_S *d, unsigned char *src, int srclen )
{
	char buffer[MAX_OUTPUT] = { 0 };

	cat_sprintf(buffer, "%c%s%c%s", MSDP_VAR, "VARIABLE", MSDP_VAL, "A SIMPLE VARIABLE");

	cat_sprintf(buffer, "%c%s%c%s%c%s%c%s", MSDP_VAR, "ARRAY", MSDP_VAL, "A", MSDP_VAL, "SIMPLE", MSDP_VAL, "ARRAY");

	cat_sprintf(buffer, "%c%s%c%c%c%s%c%s%c%s%c%s%c%s%c%s%c", MSDP_VAR, "NEST", MSDP_VAL, MSDP_OPEN, MSDP_VAR, "1", MSDP_VAL, "A", MSDP_VAR, "2", MSDP_VAL, "SIMPLE", MSDP_VAR, "3", MSDP_VAL, "NEST", MSDP_CLOSE);

	cat_sprintf(buffer, "%c%s%c%c%c%s%c%s%c%s%c%s%c", MSDP_VAR, "NESTED_ARRAY", MSDP_VAL, MSDP_OPEN, MSDP_VAR, "ARRAY", MSDP_VAL, "A", MSDP_VAL, "SIMPLE", MSDP_VAL, "ARRAY", MSDP_CLOSE);

	cat_sprintf(buffer, "%c%s%c%c%c%s%c%s%c%s%c%s%c%s%c%s%c%s%c%s%c%s%c", MSDP_VAR, "NESTED_ARRAYS", MSDP_VAL, MSDP_OPEN, MSDP_VAR, "1", MSDP_VAL, "A", MSDP_VAL, "B", MSDP_VAR, "2", MSDP_VAL, "C", MSDP_VAL, "D", MSDP_VAR, "3", MSDP_VAL, "E", MSDP_VAL, "F", MSDP_CLOSE);

	descriptor_printf(d, "%c%c%c%s%c%c", IAC, SB, TELOPT_MSDP, buffer, IAC, SE);

	return 3;
}

int process_sb_msdp( D_S *d, unsigned char *src, int srclen )
{
	char var[MAX_BUFFER], val[MAX_BUFFER];
	char *pto;
	int i;

	if (skip_sb(d, src, srclen) > srclen)
	{
		return srclen + 1;
	}

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

	i = 3;

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

				while (i < srclen && src[i] != MSDP_VAL && src[i] != IAC)
				{
					*pto++ = src[i++];
				}
				*pto = 0;

				break;

			case MSDP_VAL:
				i++;
				pto = val;

				while (i < srclen && src[i] != MSDP_VAR && src[i] != IAC)
				{
					*pto++ = src[i++];
				}
				*pto = 0;

				descriptor_printf(d, "IAC SB MSDP VAR (%s) %*s VAL (%s)\r\n", var, 20 - strlen(val), "", val);

				break;

			default:
				i++;
				break;
		}
	}
	descriptor_printf(d, "IAC SB MSDP IAC SE\r\n");

	return i + 1;
}

/*
	MCCP: Mud Client Compression Protocol
*/

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


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


int start_compress( D_S *d )
{
	char start_mccp[] = { IAC, SB, TELOPT_MCCP, IAC, SE, 0 };
	z_stream *stream;

	if (d->mccp)
	{
		return TRUE;
	}

	stream = calloc(sizeof(z_stream), 1);

	stream->next_in	    = NULL;
	stream->avail_in    = 0;

	stream->next_out    = mccp_buf;
	stream->avail_out   = COMPRESS_BUF_SIZE;

	stream->data_type   = Z_ASCII;
	stream->zalloc      = zlib_alloc;
	stream->zfree       = zlib_free;
	stream->opaque      = Z_NULL;

	/*
		12, 5 = 32K of memory, more than enough
	*/

	if (deflateInit2(stream, Z_BEST_COMPRESSION, Z_DEFLATED, 12, 5, Z_DEFAULT_STRATEGY) != Z_OK)
	{
		log_string("start_compress: failed deflateInit2 D%d@%s", d->control, d->hostname);
		free(stream);

		return FALSE;
	}

	text_to_socket(d, start_mccp, 0);

	/*
		The above call must send all pending output to the descriptor, since from now on we'll be compressing.
	*/

	d->mccp = stream;

	return TRUE;
}


void end_compress( D_S *d )
{
	if (d->mccp == NULL)
	{
		return;
	}

	d->mccp->next_in	= NULL;
	d->mccp->avail_in	= 0;

	d->mccp->next_out	= mccp_buf;
	d->mccp->avail_out	= COMPRESS_BUF_SIZE;

	if (deflate(d->mccp, Z_FINISH) != Z_STREAM_END)
	{
		log_string("end_compress: failed to deflate D%d@%s", d->control, d->hostname);
	}

	process_compressed(d);

	if (deflateEnd(d->mccp) != Z_OK)
	{
		log_string("end_compress: failed to deflateEnd D%d@%s", d->control, d->hostname);
	}

	free(d->mccp);

	d->mccp = NULL;

	return;
}


int write_compressed( D_S *d )
{
	d->mccp->next_in    = d->outbuf;
	d->mccp->avail_in   = d->top_output;

	d->mccp->next_out   = mccp_buf;
	d->mccp->avail_out  = COMPRESS_BUF_SIZE;

	d->top_output       = 0;

	if (deflate(d->mccp, Z_SYNC_FLUSH) != Z_OK)
	{
		return;
	}

	return process_compressed(d);
}


int process_compressed( D_S *d )
{
	int length;

	length = COMPRESS_BUF_SIZE - d->mccp->avail_out;

	if (write(d->control, mccp_buf, length) < 1)
	{
		log_string("process_compressed D%d@%s", d->control, d->hostname);

		return FALSE;
	}

	return TRUE;
}


int process_do_mccp( D_S *d, unsigned char *src, int srclen )
{
	start_compress(d);

	return 3;
}


int process_dont_mccp( D_S *d, unsigned char *src, int srclen )
{
	end_compress(d);

	return 3;
}

/*
	Returns the length of a telnet subnegotiation, return srclen + 1 for incomplete state.
*/

int skip_sb( D_S *d, unsigned char *src, int srclen )
{
	int i;

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

	return srclen + 1;
}


		
/*
	Utility function
*/

void descriptor_printf( D_S *d, char *fmt, ... )
{
	char buf[MAX_OUTPUT];
	int size;
	va_list args;

	va_start(args, fmt);

	size = vsprintf(buf, fmt, args);

	va_end(args);

	text_to_socket(d, buf, size);
}

char *cat_sprintf(char *dest, char *fmt, ...)
{
	char buf[MAX_OUTPUT];

	va_list args;

	va_start(args, fmt);
	vsprintf(buf, fmt, args);
	va_end(args);

	return strcat(dest, buf);
}