/*************************************************************************** * Mud Telopt Handler 1.3 by Igor van den Hoven. 06 Apr 2009 * ***************************************************************************/ #include "mud.h" #include "telnet.h" #define TELOPT_DEBUG 1 char iac_do_eor[] = { IAC, DO, TELOPT_EOR, 0 }; 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) (DESCRIPTOR_DATA *d, unsigned char *src, int srclen); }; const struct telopt_type telopt_table [] = { { 3, iac_do_eor, &process_do_eor}, { 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( DESCRIPTOR_DATA *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); } } } } /* Call this right before a copyover to reset the telnet state */ void unannounce_support( DESCRIPTOR_DATA *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, WONT, i); } if (HAS_BIT(telnet_table[i].flags, ANNOUNCE_DO)) { descriptor_printf(d, "%c%c%c", IAC, DONT, 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(DESCRIPTOR_DATA *d, char *src, int srclen, char *out) { int cnt, skip; unsigned char *pti, *pto; pti = (unsigned char *) src; pto = (unsigned char *) out; if (d->teltop) { if (d->teltop + srclen + 1 < MAX_INPUT_LENGTH) { memcpy(d->telbuf + d->teltop, src, srclen); srclen += d->teltop; pti = (unsigned char *) d->telbuf; } d->teltop = 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->teltop = 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( DESCRIPTOR_DATA *d, unsigned char *src, int srclen ) { if (srclen > 1 && TELOPT_DEBUG) { switch(src[1]) { case IAC: log_printf("D%d@%s RCVD IAC IAC", d->descriptor, d->host); break; case DO: case DONT: case WILL: case WONT: case SB: if (srclen > 2) { if (src[1] == SB) { if (skip_sb(d, src, srclen) == srclen + 1) { log_printf("D%d@%s RCVD IAC SB %s ?", d->descriptor, d->host, TELOPT(src[2])); } else { log_printf("D%d@%s RCVD IAC SB %s IAC SE", d->descriptor, d->host, TELOPT(src[2])); } } else { log_printf("D%d@%s RCVD IAC %s %s", d->descriptor, d->host, TELCMD(src[1]), TELOPT(src[2])); } } else { log_printf("D%d@%s RCVD IAC %s ?", d->descriptor, d->host, TELCMD(src[1])); } break; default: if (TELCMD_OK(src[1])) { log_printf("D%d@%s RCVD IAC %s", d->descriptor, d->host, TELCMD(src[1])); } else { log_printf("D%d@%s RCVD IAC %d", d->descriptor, d->host, src[1]); } break; } } else { log_printf("D%d@%s RCVD IAC ?", d->descriptor, d->host); } } /* Send to client to have it disable local echo */ void send_echo_off( DESCRIPTOR_DATA *d ) { SET_BIT(d->comm_flags, COMM_FLAG_PASSWORD); descriptor_printf(d, "%c%c%c", IAC, WILL, TELOPT_ECHO); } /* Send to client to have it enable local echo */ void send_echo_on( DESCRIPTOR_DATA *d ) { DEL_BIT(d->comm_flags, COMM_FLAG_PASSWORD); descriptor_printf(d, "%c%c%c", IAC, WONT, TELOPT_ECHO); } /* Send right after the prompt to mark it as such. */ void send_eor( DESCRIPTOR_DATA *d ) { if (HAS_BIT(d->comm_flags, COMM_FLAG_EOR)) { descriptor_printf(d, "%c%c", IAC, EOR); } } /* End Of Record negotiation - not enabled by default in tables.c */ int process_do_eor( DESCRIPTOR_DATA *d, unsigned char *src, int srclen ) { SET_BIT(d->comm_flags, COMM_FLAG_EOR); return 3; } /* Terminal Type negotiation - make sure d->terminal_type is initialized. */ int process_will_ttype( DESCRIPTOR_DATA *d, unsigned char *src, int srclen ) { if (*d->terminal_type == 0) { // Request the first three terminal types to see if MTTS is supported, next reset to default. descriptor_printf(d, "%c%c%c%c%c%c", IAC, SB, TELOPT_TTYPE, ENV_SEND, IAC, SE); descriptor_printf(d, "%c%c%c%c%c%c", IAC, SB, TELOPT_TTYPE, ENV_SEND, IAC, SE); descriptor_printf(d, "%c%c%c%c%c%c", IAC, SB, TELOPT_TTYPE, ENV_SEND, IAC, SE); descriptor_printf(d, "%c%c%c", IAC, DONT, TELOPT_TTYPE); } return 3; } int process_sb_ttype_is( DESCRIPTOR_DATA *d, unsigned char *src, int srclen ) { char val[MAX_INPUT_LENGTH]; 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; if (*d->terminal_type == 0) { RESTRING(d->terminal_type, val); } else { if (sscanf(val, "MTTS %lld", &d->mtts) == 1) { if (HAS_BIT(d->mtts, MTTS_FLAG_256COLORS)) { SET_BIT(d->comm_flags, COMM_FLAG_256COLORS); } } if (strcasestr(val, "-256color") || strcasecmp(val, "xterm")) { SET_BIT(d->comm_flags, COMM_FLAG_256COLORS); } } break; } } return i + 1; } /* NAWS: Negotiate About Window Size */ int process_sb_naws( DESCRIPTOR_DATA *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( DESCRIPTOR_DATA *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( DESCRIPTOR_DATA *d, unsigned char *src, int srclen ) { char var[MAX_INPUT_LENGTH], val[MAX_INPUT_LENGTH]; 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; // Detect Windows telnet and enable remote echo. if (!strcasecmp(var, "SYSTEMTYPE") && !strcasecmp(val, "WIN32")) { if (!strcasecmp(d->terminal_type, "ANSI")) { SET_BIT(d->comm_flags, COMM_FLAG_REMOTEECHO); RESTRING(d->terminal_type, "WINDOWS TELNET"); } } break; default: i++; break; } } return i + 1; } /* MSDP: Mud Server Status Protocol http://tintin.sourceforge.net/msdp */ int process_do_msdp( DESCRIPTOR_DATA *d, unsigned char *src, int srclen ) { int index; if (d->msdp_data) { return 3; } d->msdp_data = (struct msdp_data **) calloc(mud->msdp_table_size, sizeof(struct msdp_data *)); for (index = 0 ; index < mud->msdp_table_size ; index++) { d->msdp_data[index] = (struct msdp_data *) calloc(1, sizeof(struct msdp_data *)); d->msdp_data[index]->flags = msdp_table[index].flags; d->msdp_data[index]->value = strdup(""); } // Easiest to handle variable initialization here. msdp_update_var(d, "SPECIFICATION", "%s", "http://tintin.sourceforge.net/msdp"); return 3; } int process_sb_msdp( DESCRIPTOR_DATA *d, unsigned char *src, int srclen ) { char var[MAX_INPUT_LENGTH], val[MAX_INPUT_LENGTH]; char *pto; int i, nest; if (skip_sb(d, src, srclen) > srclen) { return srclen + 1; } var[0] = val[0] = 0; i = 3; nest = 0; 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] != IAC) { if (src[i] == MSDP_TABLE_OPEN || src[i] == MSDP_ARRAY_OPEN) { nest++; } else if (src[i] == MSDP_TABLE_CLOSE || src[i] == MSDP_ARRAY_CLOSE) { nest--; } else if (nest == 0 && (src[i] == MSDP_VAR || src[i] == MSDP_VAL)) { break; } *pto++ = src[i++]; } *pto = 0; if (nest == 0) { process_msdp_varval(d, var, val); } break; default: i++; break; } } return i + 1; } /* MSSP: Mud Server Status Protocol http://tintin.sourceforge.net/mssp Uncomment and update as needed */ int process_do_mssp( DESCRIPTOR_DATA *d, unsigned char *src, int srclen ) { char buffer[MAX_STRING_LENGTH] = { 0 }; cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "NAME", MSSP_VAL, "MTH 1.4"); // Change to your Mud's name cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "PLAYERS", MSSP_VAL, mud->total_plr); cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "UPTIME", MSSP_VAL, mud->boot_time); // cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "HOSTNAME", MSSP_VAL, "example.com"); // cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "PORT", MSSP_VAL, "4321"); // cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "CODEBASE", MSSP_VAL, "MTH 1.4"); // cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "CONTACT", MSSP_VAL, "mud@example.com"); // cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "CRAWL DELAY", MSSP_VAL, "-1"); // 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, "http://example.com/icon.gif"); // 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, "United States"); // cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "MINIMUM AGE", MSSP_VAL, "13"); // cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "WEBSITE", MSSP_VAL, "http://example.com"); // cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "FAMILY", MSSP_VAL, "DikuMUD"); // cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "GENRE", MSSP_VAL, "Fantasy"); // cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "GAMEPLAY", MSSP_VAL, "Hack and Slash"); // cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "GAMESYSTEM", MSSP_VAL, "Custom"); // cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "INTERMUD", MSSP_VAL, ""); // cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "STATUS", MSSP_VAL, "Live"); // cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "SUBGENRE", MSSP_VAL, "High Fantasy"); // cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "AREAS", MSSP_VAL, mud->top_area); // cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "HELPFILES", MSSP_VAL, mud->top_help); // cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "MOBILES", MSSP_VAL, mud->top_mob_index); // cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "OBJECTS", MSSP_VAL, mud->top_obj_index); // cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "ROOMS", MSSP_VAL, mud->top_room); // cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "CLASSES", MSSP_VAL, MAX_CLASS); // cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "LEVELS", MSSP_VAL, MAX_LEVEL); // cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "RACES", MSSP_VAL, MAX_RACE); // cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "SKILLS", MSSP_VAL, MAX_SKILL); // 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, "MSDP", MSSP_VAL, 1); // 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, "UTF-8", 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; } /* 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( DESCRIPTOR_DATA *d ) { char start_mccp[] = { IAC, SB, TELOPT_MCCP, IAC, SE, 0 }; z_stream *stream; if (d->mccp) { return TRUE; } stream = calloc(1, sizeof(z_stream)); stream->next_in = NULL; stream->avail_in = 0; stream->next_out = mud->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_printf("start_compress: failed deflateInit2 D%d@%s", d->descriptor, d->host); free(stream); return FALSE; } write_to_descriptor(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( DESCRIPTOR_DATA *d ) { if (d->mccp == NULL) { return; } d->mccp->next_in = NULL; d->mccp->avail_in = 0; d->mccp->next_out = mud->mccp_buf; d->mccp->avail_out = COMPRESS_BUF_SIZE; if (deflate(d->mccp, Z_FINISH) != Z_STREAM_END) { log_printf("end_compress: failed to deflate D%d@%s", d->descriptor, d->host); } if (!HAS_BIT(d->comm_flags, COMM_FLAG_DISCONNECT)) { process_compressed(d); } if (deflateEnd(d->mccp) != Z_OK) { log_printf("end_compress: failed to deflateEnd D%d@%s", d->descriptor, d->host); } free(d->mccp); d->mccp = NULL; return; } void write_compressed( DESCRIPTOR_DATA *d ) { d->mccp->next_in = (unsigned char *) d->outbuf; d->mccp->avail_in = d->outtop; d->mccp->next_out = (unsigned char *) mud->mccp_buf; d->mccp->avail_out = COMPRESS_BUF_SIZE; d->outtop = 0; if (deflate(d->mccp, Z_SYNC_FLUSH) != Z_OK) { return; } process_compressed(d); return; } void process_compressed( DESCRIPTOR_DATA *d ) { int length; length = COMPRESS_BUF_SIZE - d->mccp->avail_out; if (write(d->descriptor, mud->mccp_buf, length) < 1) { log_printf("process_compressed D%d@%s", d->descriptor, d->host); SET_BIT(d->comm_flags, COMM_FLAG_DISCONNECT); return; } return; } int process_do_mccp( DESCRIPTOR_DATA *d, unsigned char *src, int srclen ) { start_compress(d); return 3; } int process_dont_mccp( DESCRIPTOR_DATA *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( DESCRIPTOR_DATA *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( DESCRIPTOR_DATA *d, char *fmt, ... ) { char buf[MAX_STRING_LENGTH]; int size; va_list args; va_start(args, fmt); size = vsprintf(buf, fmt, args); va_end(args); write_to_descriptor(d, buf, size); }