Skip to content
Snippets Groups Projects
Select Git revision
  • nettle_3.1rc1
  • master default
  • wip-slh-dsa-sha2-128s
  • master-updates
  • release-3.10-fixes
  • getopt-prototype
  • fix-bcrypt-warning
  • refactor-hmac
  • wip-use-alignas
  • trim-sha3-context
  • fix-gitlab-ci
  • check-fat-emulate
  • delete-digest_func-size
  • slh-dsa-shake-128f-nettle
  • slh-dsa-shake-128s-nettle
  • slh-dsa-shake-128s
  • delete-openpgp
  • ppc64-sha512
  • delete-md5-compat
  • cleanup-hmac-tests
  • ppc64-sha256
  • nettle_3.10.2_release_20250626
  • nettle_3.10.1_release_20241230
  • nettle_3.10_release_20240616
  • nettle_3.10rc2
  • nettle_3.10rc1
  • nettle_3.9.1_release_20230601
  • nettle_3.9_release_20230514
  • nettle_3.8.1_release_20220727
  • nettle_3.8_release_20220602
  • nettle_3.7.3_release_20210606
  • nettle_3.7.2_release_20210321
  • nettle_3.7.1_release_20210217
  • nettle_3.7_release_20210104
  • nettle_3.7rc1
  • nettle_3.6_release_20200429
  • nettle_3.6rc3
  • nettle_3.6rc2
  • nettle_3.6rc1
  • nettle_3.5.1_release_20190627
  • nettle_3.5_release_20190626
41 results

desinfo.h

Blame
  • TELNET.pmod 24.17 KiB
    //
    // $Id: TELNET.pmod,v 1.14 2000/12/28 15:03:30 grubba Exp $
    //
    // The TELNET protocol as described by RFC 764 and others.
    //
    // Henrik Grubbström <grubba@idonex.se> 1998-04-04
    //
    
    #pike __REAL_VERSION__
    
    // #define TELNET_DEBUG
    
    #ifdef TELNET_DEBUG
    #define DWRITE(X)	werror(X)
    #else
    #define DWRITE(X)
    #endif /* TELNET_DEBUG */
    
    //! Implements TELNET as described by RFC 764 and RFC 854
    //!
    //! Also implements the Q method of TELNET option negotiation
    //! as specified by RFC 1143.
    
    //! Table of IAC-codes.
    class TelnetCodes {
    
    constant IAC = 255;		/* interpret as command: */
    constant DONT = 254;		/* you are not to use option */
    constant DO = 253;		/* please, you use option */
    constant WONT = 252;		/* I won't use option */
    constant WILL = 251;		/* I will use option */
    constant SB = 250;		/* interpret as subnegotiation */
    constant GA = 249;		/* you may reverse the line */
    constant EL = 248;		/* erase the current line */
    constant EC = 247;		/* erase the current character */
    constant AYT = 246;		/* are you there */
    constant AO = 245;		/* abort output--but let prog finish */
    constant IP = 244;		/* interrupt process--permanently */
    constant BREAK = 243;		/* break */
    constant DM = 242;		/* data mark--for connect. cleaning */
    constant NOP = 241;		/* nop */
    constant SE = 240;		/* end sub negotiation */
    constant EOR = 239;             /* end of record (transparent mode) */
    constant ABORT = 238;		/* Abort process */
    constant SUSP = 237;		/* Suspend process */
    constant xEOF = 236;		/* End of file: EOF is already used... */
    
    constant SYNCH = 242;		/* for telfunc calls */
    };
    
    inherit TelnetCodes;
    
    
    //! Table of TELNET options.
    class Telopts {
    
    constant TELOPT_BINARY = 0;	/* 8-bit data path */
    constant TELOPT_ECHO = 1;	/* echo */
    constant TELOPT_RCP = 2;	/* prepare to reconnect */
    constant TELOPT_SGA = 3;        /* suppress go ahead */
    constant TELOPT_NAMS = 4;       /* approximate message size */
    constant TELOPT_STATUS = 5;     /* give status */
    constant TELOPT_TM = 6;         /* timing mark */
    constant TELOPT_RCTE = 7;	/* remote controlled transmission and echo */
    constant TELOPT_NAOL = 8;	/* negotiate about output line width */
    constant TELOPT_NAOP = 9;	/* negotiate about output page size */
    constant TELOPT_NAOCRD = 10;	/* negotiate about CR disposition */
    constant TELOPT_NAOHTS = 11;	/* negotiate about horizontal tabstops */
    constant TELOPT_NAOHTD = 12;	/* negotiate about horizontal tab disposition */
    constant TELOPT_NAOFFD = 13;	/* negotiate about formfeed disposition */
    constant TELOPT_NAOVTS = 14;	/* negotiate about vertical tab stops */
    constant TELOPT_NAOVTD = 15;	/* negotiate about vertical tab disposition */
    constant TELOPT_NAOLFD = 16;	/* negotiate about output LF disposition */
    constant TELOPT_XASCII = 17;	/* extended ascic character set */
    constant TELOPT_LOGOUT = 18;	/* force logout */
    constant TELOPT_BM = 19;	/* byte macro */
    constant TELOPT_DET = 20;	/* data entry terminal */
    constant TELOPT_SUPDUP = 21;	/* supdup protocol */
    constant TELOPT_SUPDUPOUTPUT = 22;	/* supdup output */
    constant TELOPT_SNDLOC = 23;	/* send location */
    constant TELOPT_TTYPE = 24;	/* terminal type */
    constant TELOPT_EOR = 25;	/* end or record */
    constant TELOPT_TUID = 26;	/* TACACS user identification */
    constant TELOPT_OUTMRK = 27;	/* output marking */
    constant TELOPT_TTYLOC = 28;	/* terminal location number */
    constant TELOPT_3270REGIME = 29;	/* 3270 regime */
    constant TELOPT_X3PAD = 30;	/* X.3 PAD */
    constant TELOPT_NAWS = 31;	/* window size */
    constant TELOPT_TSPEED = 32;	/* terminal speed */
    constant TELOPT_LFLOW = 33;	/* remote flow control */
    constant TELOPT_LINEMODE = 34;	/* Linemode option */
    constant TELOPT_XDISPLOC = 35;	/* X Display Location */
    constant TELOPT_OLD_ENVIRON = 36;	/* Old - Environment variables */
    constant TELOPT_AUTHENTICATION = 37; /* Authenticate */
    constant TELOPT_ENCRYPT = 38;	/* Encryption option */
    constant TELOPT_NEW_ENVIRON = 39;	/* New - Environment variables */
    constant TELOPT_EXOPL = 255;	/* extended-options-list */
    
    };
    
    inherit Telopts;
    
    mapping lookup_telopt=mkmapping(values(Telopts()),indices(Telopts()));
    mapping lookup_telnetcodes=mkmapping(values(TelnetCodes()),indices(TelnetCodes()));
    
    
    /* sub-option qualifiers */
    constant TELQUAL_IS=	0;	/* option is... */
    constant TELQUAL_SEND=	1;	/* send option */
    constant TELQUAL_INFO=	2;	/* ENVIRON: informational version of IS */
    constant TELQUAL_REPLY=	2;	/* AUTHENTICATION: client version of IS */
    constant TELQUAL_NAME=	3;	/* AUTHENTICATION: client version of IS */
    
    constant LFLOW_OFF=		0;	/* Disable remote flow control */
    constant LFLOW_ON=		1;	/* Enable remote flow control */
    constant LFLOW_RESTART_ANY=	2;	/* Restart output on any char */
    constant LFLOW_RESTART_XON=	3;	/* Restart output only on XON */
    
    
    constant	LM_MODE	= 1;
    constant	LM_FORWARDMASK = 2;
    constant	LM_SLC = 3;
    
    constant	MODE_EDIT =     0x01;
    constant	MODE_TRAPSIG =  0x02;
    constant	MODE_ACK =	0x04;
    constant	MODE_SOFT_TAB =	0x08;
    constant	MODE_LIT_ECHO =	0x10;
    
    constant	MODE_MASK =	0x1f;
    
    /* Not part of protocol, but needed to simplify things... */
    constant	MODE_FLOW =	0x0100;
    constant	MODE_ECHO =	0x0200;
    constant	MODE_INBIN =	0x0400;
    constant	MODE_OUTBIN =	0x0800;
    constant	MODE_FORCE =	0x1000;
    
    constant	SLC_SYNCH=	1;
    constant	SLC_BRK=	2;
    constant	SLC_IP=		3;
    constant	SLC_AO=		4;
    constant	SLC_AYT=	5;
    constant	SLC_EOR=	6;
    constant	SLC_ABORT=	7;
    constant	SLC_EOF=	8;
    constant	SLC_SUSP=	9;
    constant	SLC_EC=		10;
    constant	SLC_EL=		11;
    constant	SLC_EW=		12;
    constant	SLC_RP=		13;
    constant	SLC_LNEXT=	14;
    constant	SLC_XON=	15;
    constant	SLC_XOFF=	16;
    constant	SLC_FORW1=	17;
    constant	SLC_FORW2=	18;
    
    constant	NSLC=		18;
    
    constant	SLC_NOSUPPORT=	0;
    constant	SLC_CANTCHANGE=	1;
    constant	SLC_VARIABLE=	2;
    constant	SLC_DEFAULT=	3;
    constant	SLC_LEVELBITS=	0x03;
    
    constant	SLC_FUNC=	0;
    constant	SLC_FLAGS=	1;
    constant	SLC_VALUE=	2;
    
    constant	SLC_ACK=	0x80;
    constant	SLC_FLUSHIN=	0x40;
    constant	SLC_FLUSHOUT=	0x20;
    
    constant	OLD_ENV_VAR=	1;
    constant	OLD_ENV_VALUE=	0;
    constant	NEW_ENV_VAR=	0;
    constant	NEW_ENV_VALUE=	1;
    constant	ENV_ESC=	2;
    constant	ENV_USERVAR=	3;
    
    /*
     * AUTHENTICATION suboptions
     */
    
    /*
     * Who is authenticating who ...
     */
    constant	AUTH_WHO_CLIENT= 0;	/* Client authenticating server */
    constant	AUTH_WHO_SERVER= 1;	/* Server authenticating client */
    constant	AUTH_WHO_MASK=	 1;
    
    /*
     * amount of authentication done
     */
    constant	AUTH_HOW_ONE_WAY=	0;
    constant	AUTH_HOW_MUTUAL=	2;
    constant	AUTH_HOW_MASK=		2;
    
    constant	AUTHTYPE_NULL=		0;
    constant	AUTHTYPE_KERBEROS_V4=	1;
    constant	AUTHTYPE_KERBEROS_V5=	2;
    constant	AUTHTYPE_SPX=		3;
    constant	AUTHTYPE_MINK=		4;
    constant	AUTHTYPE_CNT=		5;
    
    constant	AUTHTYPE_TEST=		99;
    
    
    /*
     * ENCRYPTion suboptions
     */
    constant ENCRYPT_IS=		0;	/* I pick encryption type ... */
    constant ENCRYPT_SUPPORT=	1;	/* I support encryption types ... */
    constant ENCRYPT_REPLY=		2;	/* Initial setup response */
    constant ENCRYPT_START=		3;	/* Am starting to send encrypted */
    constant ENCRYPT_END=		4;	/* Am ending encrypted */
    constant ENCRYPT_REQSTART=	5;	/* Request you start encrypting */
    constant ENCRYPT_REQEND=	6;	/* Request you send encrypting */
    constant ENCRYPT_ENC_KEYID=	7;
    constant ENCRYPT_DEC_KEYID=	8;
    constant ENCRYPT_CNT=		9;
    
    constant ENCTYPE_ANY=		0;
    constant ENCTYPE_DES_CFB64=	1;
    constant ENCTYPE_DES_OFB64=	2;
    constant ENCTYPE_CNT=		3;
    
    #define C(X) sprintf("%c",X)
    #define C2(X,Y) sprintf("%c%c",X,Y)
    #define C3(X,Y,Z) sprintf("%c%c%c",X,Y,Z)
    
    //! Implementation of the TELNET protocol.
    class protocol
    {
      //! The connection.
      static object fd;
    
      //! Mapping containing extra callbacks.
      static mapping cb;
    
      //! Value to send to the callbacks.
      static mixed id;
    
      //! Write callback.
      static function(mixed|void:string) write_cb;
    
      //! Read callback.
      static function(mixed,string:void) read_cb;
    
      //! Close callback.
      static function(mixed|void:void) close_cb;
    
      // See RFC 1143 for the use and meaning of these.
    
      static constant UNKNOWN = 0;
      static constant YES = 1;
      static constant NO = 2;
      static constant WANT = 4;
      static constant OPPOSITE = 8;
    
      //! Negotiation states of all WILL/WON'T options.
      //! See RFC 1143 for a description of the states.
      static array(int) remote_options = allocate(256,NO);
      static array(int) local_options = allocate(256,NO);
    
    
      //! Data queued to be sent.
      static string to_send = "";
    
      //! Indicates that connection should be closed
      static int done;
    
      //! Tells if we have set the nonblocking write callback or not.
      static int nonblocking_write;
    
      //! Turns on the write callback if apropriate.
      static void enable_write()
      {
        if (!nonblocking_write && (write_cb || sizeof(to_send) || done)) {
          fd->set_nonblocking(got_data, send_data, close_cb, got_oob);
          nonblocking_write = 1;
        }
      }
    
      //! Turns off the write callback if apropriate.
      static void disable_write()
      {
        if (!write_cb && !sizeof(to_send) && !done && nonblocking_write) {
          fd->set_nonblocking(got_data, 0, close_cb, got_oob);
          nonblocking_write = 0;
        }
      }
    
      //! Queues data to be sent to the other end of the connection.
      //! @[s] - string to send.
      void write(string s)
      {
        DWRITE(sprintf("TELNET, writing :%O\n",s));
        to_send += replace(s, C(IAC), C2(IAC,IAC));
        enable_write();
      }
    
      //! Queues raw data to be sent to the other end of the connection.
      //! @[s] - string with raw telnet data to send.
      void write_raw(string s)
      {
        to_send += s;
        enable_write();
      }
    
      //! Closes the connetion neatly
      void close()
      {
        done=1;
        enable_write();
      }
    
      //! Callback called when it is clear to send data over the connection.
      //! This function does the actual sending.
      static void send_data()
      {
        if (!sizeof(to_send)) {
          if (write_cb) {
    	if(!(to_send = write_cb(id)))
    	{
    	  done=1;
    	  to_send="";
    	}
          }
        }
    
        if (sizeof(to_send))
        {
          if (to_send[0] == 242) {
    	// DataMark needs extra quoting... Stupid.
    	to_send = C2(IAC,NOP) + to_send;
          }
    
          int n = fd->write(to_send);
    
          to_send = to_send[n..];
        } else if(done) {
          fd->close();
          fd=0;
          nonblocking_write=0;
          return;
        }
        disable_write();
      }
    
      //! Sends a TELNET synch command.
      void send_synch()
      {
        // Clear send-queue.
        to_send = "";
    
        if (fd->write_oob) {
          fd->write_oob(C(IAC));
    
          fd->write(C(DM));
        } else {
          // Fallback...
          fd->write(C2(IAC,DM));
        }
      }
    
      //! @decl void send_DO(int option)
      //!
      //! Starts negotiation to enable a TELNET option.
      //!
      //! @[option] - the option to enable.
    
      //! @decl void send_DONT(int option)
      //!
      //! Starts negotiation to disable a TELNET option.
      //!
      //! @[option] - the option to disable.
    
    
    #define CONTROL(OPTIONS,DO,DONT,WILL,WONT,YES,NO)					\
      void send_##DO(int option)								\
      {											\
        if ((option < 0) || (option > 255)) {						\
          throw(({ sprintf("Bad TELNET option #%d\n", option), backtrace() }));		\
        }											\
         DWRITE(sprintf("TELNET: send_" #DO "(%s) state is %d\n",lookup_telopt[option] || (string)option,OPTIONS##_options[option])); \
        switch(OPTIONS##_options[option])								\
        {											\
          case NO:										\
          case UNKNOWN:									\
    	OPTIONS##_options[option]= WANT | YES;						\
            DWRITE(sprintf("TELNET: => " #DO " %s\n",lookup_telopt[option] || (string)option)); \
    	to_send += sprintf("%c%c%c",IAC,DO,option); 					\
    	break;										\
    											\
          case YES: /* Already enabled */							\
          case WANT | YES: /* Will be enabled soon */					\
    	break;										\
    											\
          case WANT | NO:									\
          case WANT | YES | OPPOSITE:							\
    	OPTIONS##_options[option]^=OPPOSITE;						\
    	break;										\
    											\
          default:										\
    	error("TELNET: Strange remote_options[%d]=%d\n",option,remote_options[option]);	\
    	/* ERROR: weird state! */							\
    	break;										\
        }											\
        enable_write();									\
      }
    
      CONTROL(remote,DO,DONT,WILL,WONT,YES,NO)
    
      CONTROL(remote,DONT,DO,WONT,WILL,NO,YES)
    
      CONTROL(local,WILL,WONT,DO,DONT,YES,NO)
    
      CONTROL(local,WONT,WILL,DONT,DO,NO,YES)
    
      void remote_option_callback(int opt, int onoff)
      {
        call_callback("Remote "+ lookup_telopt[opt], onoff);
        call_callback("Remote option", onoff);
      }
    
      void local_option_callback(int opt, int onoff)
      {
        call_callback("Local "+ lookup_telopt[opt], onoff);
        call_callback("Local option", onoff);
      }
    
      void set_remote_option(int opt, int state)
      {
        remote_options[opt]=state;
        switch(state)
        {
          case YES: remote_option_callback(opt,1); break;
          case NO:  remote_option_callback(opt,0); break;
        }
      }
    
      void set_local_option(int opt, int state)
      {
        local_options[opt]=state;
        switch(state)
        {
          case YES: local_option_callback(opt,1); break;
          case NO:  local_option_callback(opt,0); break;
        }
      }
    
      int WILL_callback(int opt)
      {
        return call_callback(WILL,opt);
      }
    
      int DO_callback(int opt)
      {
        return call_callback(DO,opt);
      }
    
      void send_SB(int|string ... args)
      {
        to_send+=
          C2(IAC,SB)+
          replace(Array.map(args,lambda(int|string s)
    			{
    			  return sprintf(intp(s)?"%c":"%s",s);
    			})*"",C(IAC),C2(IAC,IAC))+
    	  C2(IAC,SE);
        enable_write();
      }
      
    
      //! Indicates wether we are in synch-mode or not.
      static int synch = 0;
    
      static mixed call_callback(mixed what, mixed ... args)
      {
        if(mixed cb=cb[what])
        {
          mixed err=catch {
    	return cb(@args);
          };
          if(err)
          {
    	throw(err);
          }else{
    	if(!this_object()) return 0;
    	throw(err);
          }
        }
        switch(what)
        {
        case BREAK:
          destruct(this_object());
          throw(0);
          break;
    
        case AYT:
          to_send += C2(IAC,NOP);	// NOP
          enable_write();
          break;
        }
      }
    
      //! Callback called when Out-Of-Band data has been received.
      //!
      //! @[ignored] - the id from the connection.
      //!
      //! @[s] - the Out-Of-Band data received.
      //!
      static void got_oob(mixed ignored, string s)
      {
    #ifdef TELNET_DEBUG
      werror("TELNET: got_oob(\"%s\")\n",Array.map(values(s),lambda(int s) { 
        switch(s)
        {
          case ' '..'z':
    	return sprintf("%c",s);
    	
          default:
    	return sprintf("\\0x%02x",s);
        }
      })*"");
    #endif
        synch = synch || (s == C(IAC));
        call_callback("URG",id,s);
      }
    
      //! Calls @[read_cb()].
      //!
      //! Specifically provided for overloading
      //!
      static void call_read_cb(string data)
      {
        DWRITE("Fnurgel!\n");
        if(read_cb && strlen(data)) read_cb(id,data);
      }
    
      //! Callback called when normal data has been received.
      //! This function also does most of the TELNET protocol parsing.
      //!
      //! @[ignored] - the id from the connection.
      //! @[s] - the received data.
      //!
      static void got_data(mixed ignored, string line)
      {
    #ifdef TELNET_DEBUG
      werror("TELNET: got_data(\"%s\")\n",Array.map(values(line),lambda(int s) { 
        switch(s)
        {
          case ' '..'z':
    	return sprintf("%c",s);
    	
          default:
    	return sprintf("\\0x%02x",s);
        }
      })*"");
    #endif
    
        if (sizeof(line) && (line[0] == DM)) {
          DWRITE("TELNET: Data Mark\n");
          // Data Mark handing.
          line = line[1..];
          synch = 0;
        }
    
          if (search(line, C(IAC)) != -1) {
    	array a = line / C(IAC);
    
    	string parsed_line = a[0];
    	int i;
    	for (i=1; i < sizeof(a); i++) {
    	  string part = a[i];
    	  if (sizeof(part)) {
    
    	    DWRITE(sprintf("TELNET: Code %s\n", lookup_telnetcodes[part[0]] || (string)part[0]));
    
    	    int j;
    	    function fun;
    	    switch (part[0]) {
    	    default:
    	      call_callback(part[0]);
    	      a[i] = a[i][1..];
    	      break;
    
    	      // FIXME, find true end of subnegotiation!
    	    case SB:
    	      call_callback(SB,part[1..]);
    	      a[i] = "";
    	      break;
    
    	    case EC:	// Erase Character
    	      for (j=i; j--;) {
    		if (sizeof(a[j])) {
    		  a[j] = a[j][..sizeof(a[j])-2];
    		  break;
    		}
    	      }
    	      a[i] = a[i][1..];
    	      break;
    
    #if 0
    	    case EL:	// Erase Line
    	      for (j=0; j < i; j++) {
    		a[j] = "";
    	      }
    	      a[i] = a[i][1..];
    	      break;
    #endif
    
    #define HANDLE(OPTIONS,WILL,WONT,DO,DONT)						\
    	    case WILL:{									\
    	      int option = a[i][1];							\
    	      int state = OPTIONS##_options[option];				       	\
    	      a[i] = a[i][2..];								\
    											\
    	      DWRITE(sprintf(#WILL " %s, state 0x%04x\n", lookup_telopt[option], state));		\
    											\
    	      switch(state)								\
    	      {										\
    		case NO:								\
    		case UNKNOWN:								\
    		  if (WILL##_callback(option))						\
    		  {									\
    		    /* Agree about enabling */						\
    		  state=YES;								\
    		    send_##DO(option);							\
    		  } else {								\
    		    state=NO;								\
    		    send_##DONT(option);						\
    		  }									\
    		  break;								\
    											\
    		case YES:								\
    		  /* Ignore */								\
    		  break;								\
    											\
    		case WANT | NO:								\
    		  state=NO;								\
    		  break;								\
    											\
    		case WANT | YES:							\
    		  state=YES;								\
    		  break;								\
    											\
    		case WANT | YES | OPPOSITE:						\
    		  state=WANT | NO;							\
    		  send_##DONT(option);							\
    		  break;								\
    											\
    		default:								\
    		  error("TELNET: Strange remote_options[%d]=%d\n",			\
    			option,remote_options[option]);					\
    		  /* Weird state ! */							\
    	      }										\
    	      DWRITE(sprintf("=> " #WILL " %s, state 0x%04x\n", lookup_telopt[option], state));	\
    	      set_##OPTIONS##_option(option,state);					\
    	      break;}									\
    											\
    	    case WONT:{									\
    	      int option = a[i][1];							\
    	      int state = OPTIONS##_options[option];					\
    	      a[i] = a[i][2..];								\
    											\
    	      DWRITE(sprintf(#WONT " %s, state 0x%04x\n", lookup_telopt[option], state));		\
    											\
    	      switch(state)								\
    	      {										\
    		case UNKNOWN:								\
    		case NO:								\
    		  state=NO;								\
    		  break;								\
    											\
    		case YES:								\
    		  state=NO;								\
    		  send_##DONT(option);							\
    		  break;								\
    											\
    		case WANT | NO:								\
    		  state=NO;								\
    		  break;								\
    											\
    		case WANT | NO | OPPOSITE:						\
    		  state=WANT | YES;							\
    		  send_##DO(option);							\
    		  break;								\
    											\
    		case WANT | YES:							\
    		case WANT | YES | OPPOSITE:						\
    		  state=NO;								\
    		  break;								\
    											\
    		default:								\
    		  error("TELNET: Strange remote_options[%d]=%d\n",			\
    			option,remote_options[option]);					\
    		  /* Weird state */							\
    	      }										\
    											\
    	      DWRITE(sprintf("=> " #WONT " %s, state 0x%04x\n", lookup_telopt[option], state));	\
    	      set_##OPTIONS##_option(option,state);					\
    	      }break
    
    
    
    	      HANDLE(remote,WILL,WONT,DO,DONT);
    	      HANDLE(local,DO,DONT,WILL,WONT);
    
    	    case DM:	// Data Mark
    	      if (synch) {
    		for (j=0; j < i; j++) {
    		  a[j] = "";
    		}
    	      }
    	      a[i] = a[i][1..];
    	      synch = 0;
    	      break;
    	    }
    	  } else {
    	    // IAC IAC => IAC
    	    a[i] = C(IAC);
    	    i++;
    	  }
    	}
    //	werror("%O\n",a);
    	line = a * "";
          }
    
          if ((!synch)) {
    #ifdef TELNET_DEBUG
    	werror("TELNET: calling read_callback(X,\"%s\")\n",Array.map(values(line),lambda(int s) { 
    	  switch(s)
    	  {
    	    case ' '..'z':
    	      return sprintf("%c",s);
    	      
    	    default:
    	      return sprintf("\\0x%02x",s);
    	  }
    	})*"");
    #endif
    	call_read_cb(line);
          }
        enable_write();
      }
    
      //! Sets the callback to be called when it is clear to send.
      //!
      //! @[w_cb] - the new write callback.
      void set_write_callback(function(mixed|void:string) w_cb)
      {
        write_cb = w_cb;
        if (w_cb) {
          enable_write();
        } else {
          disable_write();
        }
      }
    
      //! Called when the initial setup is done.
      //!
      //! Created specifically for overloading
      //!
      void setup()
      {
      }
    
      //! Creates a TELNET protocol handler, and sets its callbacks.
      //!
      //! @[f] - file to use for the connection.
      //! @[r_cb] - function to call when data has arrived.
      //! @[w_cb] - function to call when data can be sent.
      //! @[c_cb] - function to call when the connection is closed.
      //! @[callbacks] - mapping with callbacks for the various TELNET commands.
      //! @[new_id] - value to send to the various callbacks.
      //!
      void create(object f,
    	      function(mixed,string:void) r_cb,
    	      function(mixed|void:string) w_cb,
    	      function(mixed|void:void) c_cb,
    	      mapping callbacks, mixed|void new_id)
      {
        fd = f;
        cb = callbacks;
    
        read_cb = r_cb;
        write_cb = w_cb;
        close_cb = c_cb;
        id = new_id;
    
        fd->set_nonblocking(got_data, w_cb && send_data, close_cb, got_oob);
        nonblocking_write = !!w_cb;
        setup();
      }
    
      void set_nonblocking(function r_cb, function w_cb, function c_cb)
      {
        read_cb = r_cb;
        write_cb = w_cb;
        close_cb = c_cb;
        fd->set_nonblocking(got_data, w_cb && send_data, close_cb, got_oob);
        nonblocking_write = !!w_cb;
      }
    
      void set_blocking()
      {
        read_cb = 0;
        write_cb = 0;
        close_cb = 0;
        fd->set_blocking();
        nonblocking_write = 0;
      }
    
      string query_address(int|void remote)
      {
        return fd->query_address(remote);
      }
    }
    
    //! Line-oriented TELNET protocol handler.
    class LineMode
    {
      inherit protocol;
    
      static string line_buffer="";
    
      static void call_read_cb(string data)
      {
        if(read_cb)
        {
          DWRITE(sprintf("Line callback... %O\n",data));
          data=replace(data,
    		   ({"\r\n", "\n", "\r", "\r\0"}),
    		   ({"\r",   "\r", "\r", "\r",}));
          line_buffer+=data;
          array(string) tmp=line_buffer/"\r";
          line_buffer=tmp[-1];
          for(int e=0;e<sizeof(tmp)-1;e++) read_cb(id,tmp[e]+"\n");
        }
      }
    
    
      void setup()
      {
        send_DO(TELOPT_BINARY);
        send_DO(TELOPT_SGA);
        send_DONT(TELOPT_LINEMODE);
        send_WILL(TELOPT_SGA);
      }
    }
    
    class Readline
    {
      inherit LineMode;
      object readline;
      
      string term;
      int width=80;
      int height=24;
      int icanon;
      
      mapping tcgetattr()
      {
        return ([
          "rows":height,
          "columns":width,
          "ECHO":local_options[TELOPT_ECHO],
          "ICANON": icanon,
          ]);
      }
      
      static void call_read_cb(string data)
      {
        if(read_cb)
        {
          if(!icanon)
          {
    	if(strlen(data)) read_cb(id,data);
          }else{
    	DWRITE(sprintf("Line callback... %O\n",data));
    	data=replace(data,
    		     ({"\r\n","\r\n","\r","\r\0"}),
    		     ({"\r",  "\r",  "\r","\r",}));
    	line_buffer+=data;
    	array(string) tmp=line_buffer/"\r";
    	line_buffer=tmp[-1];
    	for(int e=0;e<sizeof(tmp)-1;e++)
    	{
    	  read_cb(id,tmp[e]+"\n");
    	  write(prompt);
    	}
          }
        }
      }
      
      int tcsetattr(mapping options)
      {
    //    werror("%O\n",options);
        ( options->ECHO ? send_WONT : send_WILL )(TELOPT_ECHO);
        ( (icanon=options->ICANON) ? send_DONT : send_DO )(TELOPT_LINEMODE);
      }
    
      void set_secret(int onoff)
      {
        if(readline)
        {
          readline->set_echo(!onoff);
        }else{
          ( onoff ? send_WILL : send_WONT )(TELOPT_ECHO);
        }
      }
      
      static function(mixed,string:void) read_cb2;
      
      static void readline_callback(string data)
      {
        read_cb2(id,data+"\n");
      }
      
      static string prompt="";
      static mixed call_callback(mixed what, mixed ... args)
      {
        switch(what)
        {
          case SB:
    	string data=args[0];
    	DWRITE(sprintf("SB callback %O\n",data));
    	switch(data[0])
    	{
    	  case TELOPT_TTYPE:
    	    if(data[1]==TELQUAL_IS)
    	    {
    	      if(!read_cb2)
    	      {
    		read_cb2=read_cb;
    		term=data[2..];
    // 		werror("Enabeling READLINE, term=%s\n",term);
    		readline=Stdio.Readline(this_object(),lower_case(term));
    		readline->set_nonblocking(readline_callback);
    		readline->set_prompt(prompt);
    		readline->enable_history(200);
    		/* enable data processing */
    	      }
    	    }
    	    break;
    	    
    	  case TELOPT_NAWS:
    	    if(sscanf(data[1..],"%2c%2c",width,height)==2)
    	      if(readline)
    		readline->redisplay();
    	    break;
    	}
        }
      }
    
      void remote_option_callback(int opt, int onoff)
      {
        switch(opt)
        {
          case TELOPT_TTYPE:
    	if(onoff)
    	{
    	  send_SB(TELOPT_TTYPE,TELQUAL_SEND);
    	}else{
    	  werror("Revert to line mode not yet operational.\n");
    	  /* revert to stupid line mode */
    	}
        }
        ::remote_option_callback(opt,onoff);
      }
      
      void setup()
      {
        send_DO(TELOPT_SGA);
        send_DO(TELOPT_BINARY);
        send_DO(TELOPT_NAWS);
        send_DO(TELOPT_TTYPE);
        /* disable data processing */
      }
      
      void message(string s,void|int word_wrap)
      {
        if(readline)
        {
          readline->write(s,word_wrap);
        }else{
          write(replace(s,"\n","\r\n"));
        }
      }
      
      void set_prompt(string s)
      {
    //    werror("TELNET: prompt=%O\n",s);
        if(readline)
        {
          prompt=s;
          readline->set_prompt(prompt);
        }else{
          if(prompt!=s)
          {
    	if(s[..strlen(prompt)-1]==prompt)
    	  write(s);
    	prompt=s;
          }
        }
      }
    
      void close()
      {
        readline->set_blocking();
        readline=0;
        ::close();
      }
    }