dawn/notes/
dawn/src/
dawn/src/docs/
/**************************************************************************/
// 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
/**************************************************************************/
/**************************************************************************/
/**************************************************************************/