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