/***************************************************************************
* 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);
}