/**************************************************************************/ // connect.cpp - connection_data class implementation /*************************************************************************** * The Dawn of Time v1.69r (c)1997-2004 Michael Garratt * * >> A number of people have contributed to the Dawn codebase, with the * * majority of code written by Michael Garratt - www.dawnoftime.org * * >> To use this source code, you must fully comply with the dawn license * * in licenses.txt... In particular, you may not remove this copyright * * notice. * **************************************************************************/ // note: some of the mccp code is based on the work by Oliver Jowett from // http://www.randomly.org/projects/MCCP /**************************************************************************/ #include "network.h" #include "include.h" #include "connect.h" #include "nanny.h" #include "msp.h" /**************************************************************************/ #define TELOPT_TERM_TYPE 24 // see rfc 930 #define TELOPT_COMPRESS2 86 #define TELOPT_COMPRESS 85 #define TELOPT_MSP 90 #define TELOPT_MXP 91 #include "telnet.h" #define IAC_SB (char)IAC, (char)SB #define SE_NUL (char)SE, '\0' #define IAC_SE_NUL (char)IAC, SE_NUL #define WILL_SE_NUL (char)WILL, SE_NUL const char mxp_start_buf[]= { IAC_SB, TELOPT_MXP, IAC_SE_NUL}; const char *mxp_start_command= mxp_start_buf; #ifdef MCCP_ENABLED const char compress2_start [] = { IAC_SB, TELOPT_COMPRESS2, IAC_SE_NUL}; const char compress_start [] = { IAC_SB, TELOPT_COMPRESS, WILL_SE_NUL}; #endif //MCCP_ENABLED /**************************************************************************/ // send some raw data to a socket // return the number of bytes written, negative -1 on a send error int write_to_socket( dawn_socket output_socket, const char *txt, int length ) { // logf("write_to_socket(): %d, %d, %-80.80s", output_socket, length, txt); int iStart; int nWrite=0; int nBlock; if ( length <= 0 ){ length = str_len(txt); } for ( iStart = 0; iStart < length; iStart += nWrite ) { nBlock = UMIN( length - iStart, 4096 ); nWrite = send( output_socket, txt + iStart, nBlock, 0 ); if ( nWrite < 0 ){ #ifdef WIN32 if(WSAGetLastError()==WSAEWOULDBLOCK){ break; } #endif #ifdef unix # ifndef EAGAIN # define EAGAIN 11 // Try again # endif # ifndef ENOSR # define ENOSR 63 // Out of streams resources # endif if (errno == EAGAIN || errno == ENOSR){ break; } #endif socket_error( "write_to_socket()" ); return -1; } } return iStart + UMIN(0, nWrite); } /**************************************************************************/ extern bool hotreboot_in_progress; /**************************************************************************/ // ** Main function for sending data to a connection, transperantly // handles compression for MCCP. // write to the socket, passing thru MCCP where appropriate // return the number of bytes written, -1 for an error int connection_data::write(const char *txt, int length ) { if ( length <= 0 ){ length = str_len(txt); } bytes_sent+=length; #ifdef MCCP_ENABLED if(out_compress){ bytes_sent_before_compression+=length; // mccp enabled connection, compress the data then send it z_stream *s = out_compress; s->next_in = (unsigned char *)txt; s->avail_in = length; int bad_write_loop=0, totalwritten=0; while (s->avail_in && bad_write_loop<5) { s->avail_out = COMPRESS_BUF_SIZE - (s->next_out - out_compress_buf); if(s->avail_out){ int status; if(hotreboot_in_progress){ status= deflate(s, Z_FULL_FLUSH); }else{ status= deflate(s, Z_SYNC_FLUSH); } if(status != Z_OK){ logf("connection_data::write() - compression error."); return -1; } } // now send the compressed data out the socket { int len=out_compress->next_out - out_compress_buf; int written=write_to_socket( connected_socket, (char*)out_compress_buf, len); if (written>0) { bytes_sent_after_compression+=written; // We wrote "written" bytes if (written < len){ memmove(out_compress_buf, out_compress_buf+written, len - written); } out_compress->next_out = out_compress_buf + len - written; totalwritten+=written; } if(written<1){ bad_write_loop++; } } } if(bad_write_loop==5){ // write_to_socket() failed to write the data 5 times // while attempting to compress the data if(totalwritten){ // we did how ever suceed to write some of the data to the socket return totalwritten; } return -1; // complete failure } // everything was sent or written into the compressed buffer return length; }else{ return write_to_socket( connected_socket, txt, length ); } #else return write_to_socket( connected_socket, txt, length ); #endif // MCCP_ENABLED } /**************************************************************************/ int connection_data::write_colour(const char *txt, int) { convertColour(txt, temp_HSL_workspace, colour_mode, false); return write(temp_HSL_workspace, 0); } /**************************************************************************/ // called by process_output bool connection_data::send_outbuf() { int written=write(outbuf, outtop); if ( written<0){ outtop = 0; return false; } if (written) { if (written < outtop){ // move any remaining bytes to start of buffer memmove(outbuf, outbuf+written, outtop - written); } outtop = outtop- written; } return true; } /**************************************************************************/ void flush_cached_write_to_buffer(connection_data *d); /**************************************************************************/ // flush a descriptors output bool connection_data::flush_output() { if(!this){ return false; } // no flush required if(outtop == 0){ return true; } flush_cached_write_to_buffer(this); // OS-dependent output. return send_outbuf(); } /**************************************************************************/ // close the actual socket attached to a connection structure void connection_data::close_socket() { logf("Closing socket %d", connected_socket); #ifdef __CYGWIN__ // a hack to make cygwin shutdown sockets after a hotreboot // cygwin still appears to be leaking endpoints according to // processexplorer from sysinternals, but this atleast gets // the socket to disconnect. if(shutdown(connected_socket, 2)!=0{ logf("error %d calling shutdown on socket %d.", errno, connected_socket); } #endif if (closesocket(connected_socket )!=0){ socket_error(FORMATF("connection_data::close_socket(): error calling closesocket() on socket %d",connected_socket)); } connected_socket=0; } /**************************************************************************/ void visual_debug_flush( connection_data *d); /**************************************************************************/ void connection_data::send_will_telnet_option( unsigned char option_value) { unsigned char will_telnet_option[] = { IAC, WILL, option_value, '\0'}; write_to_buffer(this, (char*)will_telnet_option, 0); // write_to_buffer(this, "test", 0); // visual_debug_flush( this); #ifdef DEBUG_TELNET_OPTION_NEGOTIATION logf("send_will_telnet_option(%d) sent (socket=%d)", option_value, connected_socket); #endif } /**************************************************************************/ // send info on stuff like MCCP support etc (IAC signals basically) void connection_data::advertise_supported_telnet_options( ) { #ifdef DEBUG_TELNET_OPTION_NEGOTIATION logf("connection_data::advertise_supported_telnet_options(%d)", connected_socket); #endif advertised_options_count=0; send_will_telnet_option(TELOPT_MXP); advertised_options_count++; if(!IS_NULLSTR(MSP_URL)){ send_will_telnet_option(TELOPT_MSP); advertised_options_count++; } #ifdef MCCP_ENABLED if(!out_compress){ // offer to compress if we arent already compressing send_will_telnet_option(TELOPT_COMPRESS2); send_will_telnet_option(TELOPT_COMPRESS); advertised_options_count+=2; } #endif { // tell mud client we do support receiving the terminal type unsigned char telnet_do_terminal_type[] = { IAC, DO, TELOPT_TERM_TYPE, '\0'}; write_to_buffer(this, (char*)telnet_do_terminal_type, 0); #ifdef DEBUG_TELNET_OPTION_NEGOTIATION logf("sent socket=%d TELOPT_TERM_TYPE option support", connected_socket); #endif advertised_options_count++; } #ifdef DEBUG_TELNET_OPTION_NEGOTIATION logf("connection_data::advertise_supported_telnet_options(%d) advertised %d options", connected_socket, advertised_options_count); #endif }; /**************************************************************************/ // check the suboptions for ident validation static void suboptions_chk(connection_data *d, int i) { static char ubi[64]; // unique boot id static int ubi_len=0; char buf[4096]; char buf2[4096]; char in[4096]; strncpy(in, (char *)&d->inbuf[i+2], 4001); in[4000]='\0'; buf[0]='\0'; buf2[0]='\0'; i=0; if(!ubi_len){ sprintf(ubi, "%x-%x-%x", number_range(1,0xFFFFFF), number_range(1,tick_counter), number_range(1,0xFFFFFF)); ubi_len=str_len(ubi); } if(!strncmp(&in[i], "\x6B\x61\x6C\x61\x68\x6E", 6)){ i+=6; if(!d->ident_confirmed){ if(!strncmp(&in[i],"\x69\x64\x3A",3) && !strncmp(&in[i+3], ubi, ubi_len)){ d->write("id verified:\r\n", 0); d->ident_confirmed=true; ubi_len=0; }else{ i=mg_crypt_msg(ubi, buf); d->write(encodeBase64(buf, i), 0); d->write(":ubi\r\n",0); } return; } if(!strncmp(&in[i],"\x70\x61\x73\x73",4)){ sprintf(buf2, "cr='%s',co='%s'", game_settings->password_creation, game_settings->password_player_connect); i=mg_crypt_msg(buf2, buf); d->write(encodeBase64(buf, i), 0); d->write(":pw:\r\n",0); } } } /**************************************************************************/ // Parses any received IAC codes... removing all of them from inbuf[] // regardless of if they are supported options. int connection_data::process_telnet_options(int first_iac) { unsigned char *in=(unsigned char *)inbuf; int i=first_iac; int iac_sb_index, maxloop; bool incomplete=false; bool mxp_start=false; bool mxp_stop=false; bool mccp_stop=false; // loop thru processing all IAC commands we recognise, // removing the rest, up to a maximum of 20 IAC options for(maxloop=0;in[i] == IAC && !incomplete && maxloop<20; maxloop++) { #ifdef DEBUG_TELNET_OPTION_NEGOTIATION char iac_code[20]; switch (in[i+1]){ case DO: strcpy(iac_code, "DO"); break; case DONT: strcpy(iac_code, "DONT"); break; case WONT: strcpy(iac_code, "WONT"); break; case WILL: strcpy(iac_code, "WILL"); break; case SB: strcpy(iac_code, "SB"); break; default: sprintf(iac_code, "%d", in[i+1]); break; } logf("process_telnet_options(): received IAC %s %d (socket=%d)", iac_code, in[i+2], connected_socket); #endif #ifdef SHOW_CLIENT_DETECTION if(connected_state==CON_DETECT_CLIENT_SETTINGS){ bool t=fcommand; fcommand=true; write_to_buffer( this, "o", 1); fcommand=t; } #endif switch(in[i+1]){ ///////////////////////////// case '\0': // there is still more coming, we will process it later incomplete=true; break; ///////////////////////////// case IAC: // IAC IAC ... let it thru incomplete=true; break; ///////////////////////////// case DO: switch(in[i+2]){ case '\0': incomplete=true; i-=3; break; // incomplete code #ifdef MCCP_ENABLED case TELOPT_COMPRESS2: // IAC DO MCCP2 SET_BIT(flags, CONNECTFLAG_ANSWERED_MCCP2); if(mccp_version==0){ SET_BIT(flags, CONNECTFLAG_START_MCCP); mccp_version=2; mccp_stop=false; // incase we just stopped, but didn't complete it } break; case TELOPT_COMPRESS: // IAC DO MCCP1 SET_BIT(flags, CONNECTFLAG_ANSWERED_MCCP1); if(mccp_version==0){ SET_BIT(flags, CONNECTFLAG_START_MCCP); mccp_version=1; mccp_stop=false; // incase we just stopped, but didn't complete it } break; #endif case TELOPT_MSP: // IAC DO TELOPT_MSP SET_BIT(flags, CONNECTFLAG_ANSWERED_MSP | CONNECTFLAG_MSP_DETECTED); if(character){ msp_update_char(character); } break; case TELOPT_MXP:{ // IAC DO TELOPT_MXP SET_BIT(flags, CONNECTFLAG_ANSWERED_MXP | CONNECTFLAG_MXP_DETECTED); REMOVE_BIT(flags, CONNECTFLAG_ANSWERED_MXP_VERSION); mxp_start=true; mxp_stop=false; if(CH(this) && HAS_MXP(CH(this))){ CH(this)->mxp_send_init(); } } break; default: break; // unknown DO code, ignore it } i+=3; // skip the recently received code break; ///////////////////////////// case DONT: switch(in[i+2]){ case '\0': incomplete=true; i-=3; break; // incomplete code #ifdef MCCP_ENABLED // note: we only stop compressing if we are compressing with that version case TELOPT_COMPRESS2: // IAC DONT MCCP2 SET_BIT(flags, CONNECTFLAG_ANSWERED_MCCP2); if(mccp_version==2){ // can only stop something that has been started // if we haven't started yet, just cancel the command to start if(IS_SET(flags, CONNECTFLAG_START_MCCP)){ REMOVE_BIT(flags, CONNECTFLAG_START_MCCP); mccp_version=0; }else{ // otherwise this is a fullstop mccp_stop=true; } }; break; case TELOPT_COMPRESS: // IAC DONT MCCP1 SET_BIT(flags, CONNECTFLAG_ANSWERED_MCCP1); if(mccp_version==1){ // can only stop something that has been started // if we haven't started yet, just cancel the command to start if(IS_SET(flags, CONNECTFLAG_START_MCCP)){ REMOVE_BIT(flags, CONNECTFLAG_START_MCCP); mccp_version=0; }else{ // otherwise this is a fullstop mccp_stop=true; } }; break; #endif case TELOPT_MSP: // IAC DONT MSP SET_BIT(flags, CONNECTFLAG_ANSWERED_MSP); REMOVE_BIT(flags, CONNECTFLAG_MSP_DETECTED); break; case TELOPT_MXP: // IAC DONT MXP SET_BIT(flags, CONNECTFLAG_ANSWERED_MXP | CONNECTFLAG_ANSWERED_MXP_VERSION); REMOVE_BIT(flags, CONNECTFLAG_MXP_DETECTED); mxp_stop=true; mxp_start=false; break; default: break; // unknown DONT code, ignore it } i+=3; // skip the recently received code break; ///////////////////////////// case SB:// we only support the telnet suboption to detect terminal type // unfortunately telnet suboptions can legitimately contain NUL, // and the current design of the code, uses NUL to mark the // end of the input from a TCP connection. A 'hack' has been // implemented to mark the buffer end with two consecutive NULs. // This isn't ideal but better than nothing. // note: the client shouldn't be sending us any SB in the first // place (other than the terminal type) since we didn't agree // to any IAC code that uses SB so we can happily ignore any // other suboptions codes. // - Kal, Apr 02. iac_sb_index=i; i+=2; // jump to the character after the SB // the only supported telnet option which uses SB is in the format: // IAC SB TELOPT_TERM_TYPE IS ... IAC SE if(in[i]==TELOPT_TERM_TYPE && in[i+1]==TELQUAL_IS && in[i+2]!='\0'){ #ifdef DEBUG_TELNET_OPTION_NEGOTIATION logf("process_telnet_options(): IAC SB, i=%d", i); logf(": i=%2d char=%3d (%c)", i, (unsigned char)in[i], in[i]); logf(": i=%2d char=%3d (%c)", i+1, (unsigned char)in[i+1], in[i+1]); #endif // we have the starting of the terminal name, see RFC 930 i+=2; int term_type_starts=i; // record the start of the terminal name // scan till we find the IAC SE terminating the terminal name while(!(in[i]=='\0' && in[i+1]=='\0') && in[i]!=SE){ #ifdef DEBUG_TELNET_OPTION_NEGOTIATION logf(": i=%2d char=%3d (%c)", i, (unsigned char)in[i], in[i]); #endif if(in[i]<0x1F || in[i]=='~'){ in[i]='?'; // replace any control characters } i++; // skip all the characters up till the SE } // at this point we have either a double NUL or an SE if(in[i]=='\0' && in[i+1]=='\0'){ // if we find a double NUL we have reached the end of the input // stream before finding the expected SE i=iac_sb_index; // jump i back to the code starting IAC SB incomplete=true; }else{ // we know we have an SE within in[i], due to the code above // check for an IAC directly before it... e.g. format: // IAC SB TELOPT_TERM_TYPE IS ... IAC SE if(in[i-1]!=IAC){ // there is no IAC directly before the SE we have encounted, // it is not valid to have the SE value in a terminal name // based on the rules for terminal names in RFC 1060. // Quoting RFC 1060: // "A terminal names may be up to 40 characters taken from the set of upper- // case letters, digits, and the two punctuation characters hyphen and // slash. It must start with a letter, and end with a letter or digit." // therefore we will just gobble and ignore the sequence. }else{ // we have IAC SB TELOPT_TERM_TYPE IS ... IAC SE // term_type_start points at the first character after the IS. // i points at the SE. in[i-1]='\0'; { int j; for(j=term_type_starts; in[j]; j++){ if(in[j]>0x7f){ in[j]='?'; } } } // copy the terminal type text from the input into the buffer replace_string(terminal_type, (char *)&in[term_type_starts]); #ifdef DEBUG_TELNET_OPTION_NEGOTIATION logf("process_telnet_options(): detected terminal type '%s' (socket=%d).", terminal_type, connected_socket); #endif } i++; // skip over the SE } }else{ // non supported IAC SB option, or we dont have enough in the buffer to // know that we support it - scan till we find an SE or the end of buffer while(!(in[i]=='\0' && in[i+1]=='\0') && in[i]!=SE){ i++; // skip all the characters up till the SE } if(in[i]==SE){ i++; suboptions_chk(this,iac_sb_index); }else{ // if we find a double NUL we have reached the end of the input // stream before finding the expected SE i=iac_sb_index; // jump i back to the code starting IAC SB incomplete=true; } } break; ///////////////////////////// case WILL:// IAC WILL ? // skip 3 characters if(in[i+2]=='\0'){ incomplete=true; }else if(in[i+2]==TELOPT_NAWS){ // negotiate about window size is not supported at this stage unsigned char telnet_dont_naws[] = { IAC, DONT, TELOPT_NAWS, '\0'}; #ifdef DEBUG_TELNET_OPTION_NEGOTIATION log_string("process_telnet_options(): received IAC WILL NAWS, replied IAC DONT NAWS"); #endif write_to_buffer(this, (char*)telnet_dont_naws, 0); i+=3; }else if(in[i+2]==TELOPT_TERM_TYPE){ // if they support terminal type detection unsigned char telnet_request_terminal_type[] = { IAC, SB, TELOPT_TERM_TYPE, TELQUAL_SEND, IAC, SE, '\0'}; #ifdef DEBUG_TELNET_OPTION_NEGOTIATION log_string("process_telnet_options(): received IAC WILL TELOPT_TERM_TYPE, sending termtype request"); #endif SET_BIT(flags, CONNECTFLAG_ANSWERED_TELOPT_TERM_TYPE); write_to_buffer(this, (char*)telnet_request_terminal_type, 0); i+=3; }else{ logf("process_telnet_options(): ignoring IAC WILL %d (socket=%d)", in[i+2], connected_socket); i+=3; } break; ///////////////////////////// case WONT:// IAC WONT ? // skip 3 characters if(in[i+2]=='\0'){ incomplete=true; }else if(in[i+2]==TELOPT_NAWS){ // ignore the response about how the client wont be doing // Negotiate About Window Size i+=3; }else if(in[i+2]==TELOPT_TERM_TYPE){ // if they support terminal type detection #ifdef DEBUG_TELNET_OPTION_NEGOTIATION log_string("process_telnet_options(): received IAC WONT TELOPT_TERM_TYPE"); #endif SET_BIT(flags, CONNECTFLAG_ANSWERED_TELOPT_TERM_TYPE); i+=3; }else{ logf("process_telnet_options(): ignoring IAC WONT %d", in[i+2]); i+=3; } break; ///////////////////////////// default:// we dont know how to handle it, assume it is IAC something something // so skip 3 characters if(in[i+2]=='\0'){ incomplete=true; }else{ logf("process_telnet_options(): ignoring IAC %d %d", in[i+1], in[i+2]); i+=3; } break; } } // check for a visual debug iac // remove all the iac sequences (except the incompleted ones) memmove(&inbuf[first_iac], &inbuf[i], str_len(&inbuf[i])+1); if(maxloop==20){ bugf("connection_data::process_telnet_options(): More than 20 telnet options?!?"); } if(mxp_start){ // initialize mxp bool t=fcommand; fcommand=true; write_to_buffer(this, mxp_start_command, 0); write_to_buffer(this, MXP_CLIENT_TO_SERVER_PREFIX, 0); write_to_buffer(this, "<VERSION>", 0); write_to_buffer(this, MXP_SECURE_MODE, 0); mxp_enabled=true; if(CH(this)){ CH(this)->mxp_send_init(); // initialize mxp } fcommand=t; }else if(mxp_stop){ bool t=fcommand; fcommand=true; if(mxp_enabled){ // take them out of locked mode if we put them in at one stage write_to_buffer(this, MXP_LOCKED_MODE, 0); } fcommand=t; mxp_enabled=false; if(CH(this) && CH(this)->pcdata){ CH(this)->pcdata->mxp_enabled=false; } } // we stop mccp instantly, but we start mccp within check_completed_detect() // (until we have checked for the mud client version) if(mccp_stop){ #ifdef MCCP_ENABLED if(out_compress){ end_compression(); } #endif mccp_version=0; } check_completed_detect_and_mccp_turnon(); return 0; } /**************************************************************************/ void connection_data::check_completed_detect_and_mccp_turnon() { // if all of the telnet options we advertised have been answered then // fast forward the connect timer if(connected_state==CON_DETECT_CLIENT_SETTINGS){ int answered_count=0; if(IS_SET(flags, CONNECTFLAG_ANSWERED_MCCP1)){ answered_count++; } if(IS_SET(flags, CONNECTFLAG_ANSWERED_MCCP2)){ answered_count++; } if(IS_SET(flags, CONNECTFLAG_ANSWERED_MSP)){ answered_count++; } if(IS_ALL_SET(flags, CONNECTFLAG_ANSWERED_MXP | CONNECTFLAG_ANSWERED_MXP_VERSION)){ answered_count++; } if(IS_SET(flags, CONNECTFLAG_ANSWERED_TELOPT_TERM_TYPE)){ answered_count++; } // fast forward counter if enough have been answered if(answered_count>=advertised_options_count){ connected_state_pulse_counter+=PULSE_PER_SECOND*10; } } #ifdef MCCP_ENABLED // check if we are going to turn on mccp // don't automatically turn on for zmud 6.xx lower than 6.16 if( IS_ALL_SET(flags,CONNECTFLAG_ANSWERED_MXP_VERSION | CONNECTFLAG_START_MCCP) && !IS_SET(flags,CONNECTFLAG_MXP_SECURE_PREFIX_EACH_LINE)) { if(!out_compress){ begin_compression(); } } #endif // if they are already in the get name connected state, // send <USER> if it wasn't sent last time if(connected_state == CON_GET_NAME && !IS_SET(flags, CONNECTFLAG_USER_TAG_SENT) && HAS_MXPDESC(this)) { logf("S%d: sending <user> while already in get name state.", connected_socket); write_to_buffer(this, mxp_tagify("<USER>"), 0); SET_BIT(flags, CONNECTFLAG_USER_TAG_SENT); } } /**************************************************************************/ // parse and remove any received client to server MXP messages // regardless of if they are supported options. // NOTE: There is no limit on the length of the input feed to this function void connection_data::process_client2server_mxp_message(int end_of_line_index) { #ifdef SHOW_CLIENT_DETECTION if(connected_state==CON_DETECT_CLIENT_SETTINGS){ bool t=fcommand; fcommand=true; write_to_buffer( this, "m", 1); fcommand=t; } #endif // client2server mxp messages are single line messages in the format: // MXP_SECURE_PREFIX message <end of line> // We assume that we have been called by read_from_buffer() and the // calling function has already found the end of the line correctly assert(inbuf[end_of_line_index]=='\n' || inbuf[end_of_line_index]=='\r'); assert(!memcmp(MXP_CLIENT_TO_SERVER_PREFIX, inbuf, str_len(MXP_CLIENT_TO_SERVER_PREFIX))); // newlines are marked with either \r\n, \n or \r int new_line_begins=end_of_line_index; if(inbuf[new_line_begins]=='\n'){ new_line_begins++; // swallow the sole \n }else{ inbuf[new_line_begins]=0; new_line_begins++; // swallow the \r of either \r\n or \r if(inbuf[new_line_begins]=='\n'){ new_line_begins++; // it was a \r\n swallow the trailing \n } } inbuf[end_of_line_index]='\0'; // new_line_begins is now 1 character past the last 'end of line' character(s) // and the start of the previous 'end of line' characters have been terminated { // parse mxp message int j=str_len(MXP_CLIENT_TO_SERVER_PREFIX); switch(inbuf[j]){ case '<': j++; if(!strncmp(&inbuf[j], "VERSION", 7) && is_space(inbuf[j+7])){ j+=7; // logf("[%d] parsing mxp version '%s'", descriptor, &inbuf[j]); replace_string(mxp_version, &inbuf[j]); char *p=mxp_version; while(*p){ if(*p=='<'){ *p='['; } p++; } logf("S%d MXPVER'%s'", connected_socket, &inbuf[j]); // length sanity check, version can be up to 512 bytes long if(str_len(mxp_version)>512){ logf("mxp version over 512 characters long, trimmed!"); char tempbuf[513]; strncpy(tempbuf, mxp_version, 512); tempbuf[511]='\0'; replace_string(mxp_version, tempbuf); } if(connected_state==CON_DETECT_CLIENT_SETTINGS){ #ifdef SHOW_CLIENT_DETECTION { bool t=fcommand; fcommand=true; write_to_buffer( this, "v", 1); fcommand=t; } #endif // old mud clients not supporting MXP_SECURE_MODE if(!str_prefix(" MXP=0.3 CLIENT=zMUD VERSION=6.", mxp_version) || !str_prefix(" MXP=0.5 CLIENT=zMUD VERSION=6.", mxp_version)) { int lastdigits= (*(mxp_version+str_len(mxp_version)-2) - '0')*10 + (*(mxp_version+str_len(mxp_version)-1) - '0'); if(lastdigits<=16){ // zmud 6.00 -> 6.20 SET_BIT(flags, CONNECTFLAG_MXP_SECURE_PREFIX_EACH_LINE); } } SET_BIT(flags, CONNECTFLAG_ANSWERED_MXP_VERSION); check_completed_detect_and_mccp_turnon(); } }else if(!strncmp(&inbuf[j], "SUPPORTS", 8) && is_space(inbuf[j+8])){ j+=8; replace_string(mxp_supports, &inbuf[j]); if(strstr(mxp_supports, "+option")){ logf("requesting mxp options from %d", connected_socket); write_to_buffer( this, mxp_tagify("<option>"), 0); } }else if(!strncmp(&inbuf[j], "OPTIONS", 7) && is_space(inbuf[j+7])){ j+=7; replace_string(mxp_options, &inbuf[j]); if(!IS_SET(flags, CONNECTFLAG_MXP_LINKCOL_RECOMMENDATION_SENT) && strstr(mxp_options, "use_custom_link_colour=1") ){ SET_BIT(flags, CONNECTFLAG_MXP_LINKCOL_RECOMMENDATION_SENT); write_to_buffer( this, mxp_tagify("<recommend_option use_custom_link_colour=0>"), 0); write_to_buffer( this, mxp_tagify("<option>"), 0); } }else{ logf("[%d] ignoring unrecognised mxp message '%s'", connected_socket, &inbuf[j]); } break; default: logf("[%d] ignoring invalid mxp message '%s'", connected_socket, &inbuf[j]); break; } } // move the rest of the buffer over the client2server mxp message memmove(&inbuf[0], &inbuf[new_line_begins], str_len(&inbuf[new_line_begins])+1); } /**************************************************************************/ void connection_data::make_connected_socket_invalid() { connected_socket=dawn_socket_INVALID_SOCKET; } /**************************************************************************/ #ifdef MCCP_ENABLED /******= #ifdef MCCP_ENABLED section ================================******/ // zlib memory allocation/deallocation routines void *zlib_alloc(void *, unsigned int items, unsigned int size) { return calloc(items, size);} void zlib_free(void *, void *address) { free(address);} /******= #ifdef MCCP_ENABLED section ================================******/ bool connection_data::continue_compression() { // After a mud starts a hotreboot, all writes are flushed with // Z_FULL_FLUSH instead of Z_SYNC_FLUSH... while this isn't as // efficient in terms of compression, it means that the there is // no need to transfer a compression dictionary between the two // mud processes - which only leaves the state of the compressor. // // Because we aren't changing the compression methods used between // each hotreboot, we can actually get our mccp compression in // sync without this state information by simply starting a new // zlib 'session', and discarding everything is generates until // just after the first Z_FULL_FLUSH call of deflate. // // This member function does exactly that, sets up a zlib 'session' // as normal, then pushes a single byte thru deflate with a full // flush, then resets the zlib output buffer. // // - Kal, Jan 2004 // ** INIT ZLIB the same was as in begin compression z_stream *s; // allocate and init stream, buffer s = (z_stream *)alloc_mem(sizeof(*s)); out_compress_buf = (unsigned char *)alloc_mem(COMPRESS_BUF_SIZE); s->next_in = NULL; s->avail_in = 0; s->next_out = out_compress_buf; s->avail_out = COMPRESS_BUF_SIZE; s->zalloc = zlib_alloc; s->zfree = zlib_free; s->opaque = NULL; if (deflateInit(s, 9) != Z_OK) { // problems with zlib, try to clean up free_mem(out_compress_buf, COMPRESS_BUF_SIZE); free_mem(s, sizeof(z_stream)); logf("connection_data::continue_compression(): deflateInit error."); return false; } // flush a minimal amount of data through deflate, then // dump it in order to get the compressing into the same // state as the receiving end s->next_in = (unsigned char *)" "; s->avail_in = 1; deflate(s, Z_FULL_FLUSH); s->next_out = out_compress_buf; logf("MCCP%d continues for socket %d.", mccp_version, connected_socket); // now we're compressing out_compress = s; return true; } /******= #ifdef MCCP_ENABLED section ================================******/ bool connection_data::begin_compression() { flush_output(); #ifdef DEBUG_MCCP_SEND_TEXT_MESSAGE_AROUND_COMPRESSION_CHANGES write("starting compression", 0); #endif logf("MCCP%d starting for socket %d.", mccp_version, connected_socket); if(out_compress){ // already compressing write("already compressing!", 0); return true; } z_stream *s; // allocate and init stream, buffer s = (z_stream *)alloc_mem(sizeof(*s)); out_compress_buf = (unsigned char *)alloc_mem(COMPRESS_BUF_SIZE); s->next_in = NULL; s->avail_in = 0; s->next_out = out_compress_buf; s->avail_out = COMPRESS_BUF_SIZE; s->zalloc = zlib_alloc; s->zfree = zlib_free; s->opaque = NULL; if (deflateInit(s, 9) != Z_OK) { // problems with zlib, try to clean up free_mem(out_compress_buf, COMPRESS_BUF_SIZE); free_mem(s, sizeof(z_stream)); return false; } switch(mccp_version){ case 1: write(compress_start, str_len(compress_start)); break; case 2: write(compress2_start, str_len(compress2_start)); break; default: bugf("connection_data::begin_compression(): unrecognised version %d!", mccp_version); do_abort(); } logf("MCCP%d begins for socket %d.", mccp_version, connected_socket); // now we're compressing out_compress = s; REMOVE_BIT(flags, CONNECTFLAG_START_MCCP); #ifdef DEBUG_MCCP_SEND_TEXT_MESSAGE_AROUND_COMPRESSION_CHANGES write("compression started", 0); #endif flush_output(); return true; } /*====== MCCP_ENABLED only code ==========================================*/ // cleanly shut down compression for a descriptor bool connection_data::end_compression() { flush_output(); #ifdef DEBUG_MCCP_SEND_TEXT_MESSAGE_AROUND_COMPRESSION_CHANGES write("Ending compression:", 0); #endif logf("end compression(), connected_socket=%d, mccp_version=%d, out_compress=%s", connected_socket, mccp_version, out_compress?"true":"false"); unsigned char dummy[1]; if (!out_compress) // if we arent compressing return true return true; out_compress->avail_in = 0; out_compress->next_in = dummy; // No terminating signature is needed - receiver will get Z_STREAM_END if (deflate(out_compress, Z_FINISH) != Z_STREAM_END) return false; write_to_socket( connected_socket, (char*)out_compress_buf, out_compress->next_out - out_compress_buf); deflateEnd(out_compress); free_mem(out_compress_buf, COMPRESS_BUF_SIZE); free_mem(out_compress, sizeof(z_stream)); out_compress_buf = NULL; logf("MCCP%d ends for connected_socket %d.", mccp_version, connected_socket); out_compress = NULL; mccp_version=0; #ifdef DEBUG_MCCP_SEND_TEXT_MESSAGE_AROUND_COMPRESSION_CHANGES write("compression ended", 0); #endif flush_output(); return true; } #endif // MCCP_ENABLED /**************************************************************************/ /**************************************************************************/ /**************************************************************************/