diff --git a/lib/modules/Protocols.pmod/TELNET.pmod b/lib/modules/Protocols.pmod/TELNET.pmod
index e1d4c1f548b1c077e2d3145f8d264c5c8a69d7d2..15b1d494d12b0fc92e56f181c9ee58e3df08b6b9 100644
--- a/lib/modules/Protocols.pmod/TELNET.pmod
+++ b/lib/modules/Protocols.pmod/TELNET.pmod
@@ -1,11 +1,13 @@
 //
-// $Id: TELNET.pmod,v 1.5 1998/04/30 13:53:41 grubba Exp $
+// $Id: TELNET.pmod,v 1.6 1999/04/30 06:56:24 hubbe Exp $
 //
 // The TELNET protocol as described by RFC 764 and others.
 //
 // Henrik Grubbstr�m <grubba@idonex.se> 1998-04-04
 //
 
+// #define TELNET_DEBUG
+
 #ifdef TELNET_DEBUG
 #define DWRITE(X)	werror(X)
 #else
@@ -17,7 +19,212 @@
 //. Also implements the Q method of TELNET option negotiation
 //. as specified by RFC 1143.
 
-
+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;
+
+
+/* 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)
 
 //. o protocol
 //.   Implementation of the TELNET protocol.
@@ -29,76 +236,50 @@ class protocol
 
   //. + cb
   //.   Mapping containing extra callbacks.
-  static private mapping cb;
+  static mapping cb;
 
   //. + id
   //.   Value to send to the callbacks.
-  static private mixed id;
+  static mixed id;
 
   //. + write_cb
   //.   Write callback.
-  static private function(mixed|void:string) write_cb;
+  static function(mixed|void:string) write_cb;
 
   //. + read_cb
   //.   Read callback.
-  static private function(mixed, string:void) read_cb;
+  static function(mixed,string:void) read_cb;
 
   //. + close_cb
   //.   Close callback.
-  static private function(mixed|void:void) close_cb;
-
-  //. + TelnetCodes
-  //.   Mapping of telnet codes to their abreviations.
-  static private constant TelnetCodes = ([
-    236:"EOF",		// End Of File
-    237:"SUSP",		// Suspend Process
-    238:"ABORT",	// Abort Process
-    239:"EOR",		// End Of Record
-
-    // The following ones are specified in RFC 959:
-    240:"SE",		// Subnegotiation End
-    241:"NOP",		// No Operation
-    242:"DM",		// Data Mark
-    243:"BRK",		// Break
-    244:"IP",		// Interrupt Process
-    245:"AO",		// Abort Output
-    246:"AYT",		// Are You There
-    247:"EC",		// Erase Character
-    248:"EL",		// Erase Line
-    249:"GA",		// Go Ahead
-    250:"SB",		// Subnegotiation
-    251:"WILL",		// Desire to begin/confirmation of option
-    252:"WON'T",	// Refusal to begin/continue option
-    253:"DO",		// Request to begin/confirmation of option
-    254:"DON'T",	// Demand/confirmation of stop of option
-    255:"IAC",		// Interpret As Command
-  ]);
+  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;
 
   //. + option_states
   //.   Negotiation states of all WILL/WON'T options.
   //.   See RFC 1143 for a description of the states.
-  static private array(int) option_states = allocate(256);
+  static array(int) remote_options = allocate(256,NO);
+  static array(int) local_options = allocate(256,NO);
 
-  // See RFC 1143 for the use and meaning of these.
-  static private constant option_us_yes		= 0x0001;
-  static private constant option_us_want	= 0x0002;
-  static private constant option_us_opposite	= 0x0010;
-
-  static private constant option_him_yes	= 0x0100;
-  static private constant option_him_want	= 0x0200;
-  static private constant option_him_opposite	= 0x1000;
 
   //. + to_send
   //.   Data queued to be sent.
-  static private string to_send = "";
+  static string to_send = "";
 
   //. + nonblocking_write
   //.   Tells if we have set the nonblocking write callback or not.
-  static private int nonblocking_write;
+  static int nonblocking_write;
 
   //. - enable_write
   //.   Turns on the write callback if apropriate.
-  static private void enable_write()
+  static void enable_write()
   {
     if (!nonblocking_write && (write_cb || sizeof(to_send))) {
       fd->set_nonblocking(got_data, send_data, close_cb, got_oob);
@@ -108,7 +289,7 @@ class protocol
 
   //. - disable_write
   //.   Turns off the write callback if apropriate.
-  static private void disable_write()
+  static void disable_write()
   {
     if (!write_cb && !sizeof(to_send) && nonblocking_write) {
       fd->set_nonblocking(got_data, 0, close_cb, got_oob);
@@ -121,7 +302,8 @@ class protocol
   //. > s - String to send.
   void write(string s)
   {
-    to_send += replace(s, "\377", "\377\377");
+    DWRITE(sprintf("TELNET, writing :%O\n",s));
+    to_send += replace(s, C(IAC), C2(IAC,IAC));
     enable_write();
   }
 
@@ -137,7 +319,7 @@ class protocol
   //. - send_data
   //.   Callback called when it is clear to send data over the connection.
   //.   This function does the actual sending.
-  static private void send_data()
+  static void send_data()
   {
     if (!sizeof(to_send)) {
       if (write_cb) {
@@ -151,7 +333,7 @@ class protocol
     } else if (sizeof(to_send)) {
       if (to_send[0] == 242) {
 	// DataMark needs extra quoting... Stupid.
-	to_send = "\377\361" + to_send;
+	to_send = C2(IAC,NOP) + to_send;
       }
 
       int n = fd->write(to_send);
@@ -161,27 +343,6 @@ class protocol
     disable_write();
   }
 
-  //. + default_cb
-  //.   Mapping with the default handlers for TELNET commands.
-  static private mapping(string:function) default_cb = ([
-    "BRK":lambda() {
-	    destruct();
-	    throw(0);
-	  },
-    "AYT":lambda() {
-	    to_send += "\377\361";	// NOP
-	    enable_write();
-	  },
-    "WILL":lambda(int code) {
-	     to_send += sprintf("\377\376%c", code);	// DON'T xxx
-	     enable_write();
-	   },
-    "DO":lambda(int code) {
-	   to_send += sprintf("\377\374%c", code);	// WON'T xxx
-	   enable_write();
-	 },
-  ]);
-
   //. - send_synch
   //.   Sends a TELNET synch command.
   void send_synch()
@@ -190,165 +351,236 @@ class protocol
     to_send = "";
 
     if (fd->write_oob) {
-      fd->write_oob("\377");
+      fd->write_oob(C(IAC));
 
-      fd->write("\362");
+      fd->write(C(DM));
     } else {
       // Fallback...
-      fd->write("\377\362");
+      fd->write(C2(IAC,DM));
     }
   }
 
-  //. - enable_option
+  //. - send_DO
   //.   Starts negotiation to enable a TELNET option.
   //. > option - The option to enable.
-  void enable_option(int option)
+
+  //. - send_DONT
+  //.   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)
   {
-    if ((option < 0) || (option > 255)) {
-      throw(({ sprintf("Bad TELNET option #%d\n", option), backtrace() }));
+    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;
     }
-    switch(option_states[option] & 0xff00) {
-    case 0x0000: /* NO */
-    case 0x1000: /* NO OPPOSITE */
-      option_states[option] &= ~option_him_opposite;
-      option_states[option] |= option_him_want | option_him_yes;
-      to_send += sprintf("\377\375%c", option); // DO option
-      break;
-    case 0x0100: /* YES */
-    case 0x1100: /* YES OPPOSITE */
-      /* Error: Already enabled */
-      break;
-    case 0x0200: /* WANTNO EMPTY */
-      /* Error: Cannot initiate new request in the middle of negotiation. */
-      /* FIXME: **********************/
-      break;
-    case 0x1200: /* WANTNO OPPOSITE */
-      /* Error: Already queued an enable request */
-      break;
-    case 0x1300: /* WANTYES OPPOSITE */
-      /* Error: Already negotiating for enable */
-      break;
-    case 0x0300: /* WANTYES EMPTY */
-      option_states[option] &= ~option_him_opposite;
-      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();
   }
+  
 
-  //. - disable_option
-  //.   Starts negotiation to disable a TELNET option.
-  //. > option - The option to disable.
-  void disable_option(int option)
+  //. + synch
+  //.   Indicates wether we are in synch-mode or not.
+  static int synch = 0;
+
+  static mixed call_callback(mixed what, mixed ... args)
   {
-    if ((option < 0) || (option > 255)) {
-      throw(({ sprintf("Bad TELNET option #%d\n", option), backtrace() }));
+    if(mixed cb=cb[what])
+    {
+      mixed err=catch {
+	return cb(@args);
+      };
+      if(err)
+      {
+	throw(err);
+      }else{
+	if(!this_object()) return;
+	throw(err);
+      }
     }
-    switch(option_states[option] & 0xff00) {
-    case 0x0000: /* NO */
-    case 0x1000: /* NO OPPOSITE */
-      /* Error: Already disabled */
-      break;
-    case 0x0100: /* YES */
-    case 0x1100: /* YES OPPOSITE */
-      option_states[option] &= ~(option_him_opposite | option_him_yes);
-      option_states[option] |= option_him_want;
-      to_send += sprintf("\377\376%c", option); // DON'T option
+    switch(what)
+    {
+    case BREAK:
+      destruct(this_object());
+      throw(0);
       break;
-    case 0x0200: /* WANTNO EMPTY */
-      /* Error: Already negotiating for disable */
-      break;
-    case 0x1200: /* WANTNO OPPOSITE */
-      option_states[option] &= ~option_him_opposite;
-      break;
-    case 0x1300: /* WANTYES OPPOSITE */
-      /* Error: Cannot initiate new request in the middle of negotiation. */
-      /* FIXME: **********************/
-      break;
-    case 0x0300: /* WANTYES EMPTY */
-      /* Error: Already queued an disable request */
+
+    case AYT:
+      to_send += C2(IAC,NOP);	// NOP
+      enable_write();
       break;
     }
-    enable_write();
   }
 
-  //. + synch
-  //.   Indicates wether we are in synch-mode or not.
-  static private int synch = 0;
-
   //. - got_oob
   //.   Callback called when Out-Of-Band data has been received.
   //. > ignored - The id from the connection.
   //. > s - The Out-Of-Band data received.
-  static private void got_oob(mixed ignored, string s)
+  static void got_oob(mixed ignored, string s)
   {
-    DWRITE(sprintf("TELNET: got_oob(\"%s\")\n", s));
-    synch = synch || (s == "\377");
-    if (cb["URG"]) {
-      cb["URG"](id, 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);
   }
 
-  //. + rest
-  //.   Contains data left over from the line-buffering.
-  static private string rest = "";
+  //. - call_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);
+  }
 
   //. - got_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 private void got_data(mixed ignored, string s)
+  static void got_data(mixed ignored, string line)
   {
-    DWRITE(sprintf("TELNET: got_data(\"%s\")\n", s));
+#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(s) && (s[0] == 242)) {
+    if (sizeof(line) && (line[0] == DM)) {
       DWRITE("TELNET: Data Mark\n");
       // Data Mark handing.
-      s = s[1..];
+      line = line[1..];
       synch = 0;
     }
 
-    // A single read() can contain multiple or partial commands
-    // RFC 1123 4.1.2.10
-
-    array lines = s/"\r\n";
-
-    int lineno;
-    for(lineno = 0; lineno < sizeof(lines); lineno++) {
-      string line = lines[lineno];
-      if (search(line, "\377") != -1) {
-	array a = line / "\377";
+      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)) {
-	    string name = TelnetCodes[part[0]];
 
-	    DWRITE(sprintf("TELNET: Code %s\n", name || "Unknown"));
+	    DWRITE(sprintf("TELNET: Code %s\n", lookup_telnetcodes[part[0]] || (string)part[0]));
 
 	    int j;
 	    function fun;
-	    switch (name) {
-	    case 0:
-	      // FIXME: Should probably have a warning here.
-	      break;
+	    switch (part[0]) {
 	    default:
-	      if (fun = (cb[name] || default_cb[name])) {
-		mixed err = catch {
-		  fun();
-		};
-		if (err) {
-		  throw(err);
-		} else if (!zero_type(err)) {
-		  // We were just destructed.
-		  return;
-		}
-	      }
+	      call_callback(part[0]);
 	      a[i] = a[i][1..];
 	      break;
-	    case "EC":	// Erase Character
+
+	      // 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];
@@ -357,188 +589,114 @@ class protocol
 	      }
 	      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;
-	    case "WILL":
-	      int option = a[i][1];
-	      int state = option_states[option];
-	      a[i] = a[i][2..];
-
-	      DWRITE(sprintf("WILL %d, state 0x%04x\n", option, state));
-
-	      switch(state & 0xff00) {
-	      case 0x0000: /* NO */
-	      case 0x1000: /* NO OPPOSITE */
-		if ((fun = (cb["WILL"] || default_cb["WILL"])) &&
-		    fun(option)) {
-		  /* Agree about enabling */
-		  option_states[option] |= option_him_yes;
-		  to_send += sprintf("\377\375%c", option); // DO option
-		} else {
-		  to_send += sprintf("\377\376%c", option); // DON'T option
-		}
-		break;
-	      case 0x0100: /* YES */
-	      case 0x1100: /* YES OPPOSITE */
-		/* Ignore */
-		break;
-	      case 0x0200: /* WANTNO EMPTY */
-		state &= ~option_him_want;
-		if ((fun = (cb["Enable_Option"] ||
-			    default_cb["Enable_Option"]))) {
-		  fun(option);
-		}
-		break;
-	      case 0x1200: /* WANTNO OPPOSITE */
-		state &= ~(option_him_yes|option_him_opposite);
-		to_send += sprintf("\377\376%c", option); // DON'T option
-		break;
-	      case 0x0300: /* WANTYES EMPTY */
-	      case 0x1300: /* WANTYES OPPOSITE */
-		  // Error: DON'T answered by WILL
-		  option_states[option] &= ~0x1200;
-		  option_states[option] |= option_him_yes;
-		  if ((fun = (cb["Enable_Option"] ||
-			      default_cb["Enable_Option"]))) {
-		    fun(option);
-		  }
-		break;
-	      }
-
-	      state = option_states[option];
-	      DWRITE(sprintf("=> WILL %d, state 0x%04x\n", option, state));
-
-	      break;
-	    case "WON'T":
-	      int option = a[i][1];
-	      int state = option_states[option];
-	      a[i] = a[i][2..];
-
-	      DWRITE(sprintf("WON'T %d, state 0x%04x\n", option, state));
-
-	      switch(state & 0xff00) {
-	      case 0x0000: /* NO */
-	      case 0x1000: /* NO OPPOSITE */
-		/* Ignore */
-		break;
-	      case 0x0100: /* YES */
-	      case 0x1100: /* YES OPPOSITE */
-		option_states[option] &= ~0xff00;
-		to_send += sprintf("\377\376%c", option); // DON'T option
-		if ((fun = (cb["Disable_Option"] ||
-			    default_cb["Disable_Option"]))) {
-		  fun(option);
-		}
-		break;
-	      case 0x0200: /* WANTNO EMPTY */
-	      case 0x0300: /* WANTYES EMPTY */
-	      case 0x1300: /* WANTYES OPPOSITE */
-		option_states[option] &= ~0xff00;
-		break;
-	      case 0x1200: /* WANTNO OPPOSITE */
-		option_states[option] &= ~option_him_opposite;
-		option_states[option] |= option_him_yes;
-		to_send += sprintf("\377\375%c", option); // DO option
-		break;
-	      }
-
-	      state = option_states[option];
-	      DWRITE(sprintf("=> WON'T %d, state 0x%04x\n", option, state));
-
-	      break;
-	    case "DO":
-	      int option = a[i][1];
-	      int state = option_states[option];
-	      a[i] = a[i][2..];
-
-	      DWRITE(sprintf("DO %d, state 0x%04x\n", option, state));
-
-	      switch(state & 0xff) {
-	      case 0x00: /* NO */
-	      case 0x10: /* NO OPPOSITE */
-		if ((fun = (cb["DO"] || default_cb["DO"])) &&
-		    fun(option)) {
-		  /* Agree about enabling */
-		  DWRITE("AGREE\n");
-		  option_states[option] |= option_us_yes;
-		  to_send += sprintf("\377\373%c", option); // WILL option
-		} else {
-		  DWRITE("DISAGREE\n");
-		  to_send += sprintf("\377\374%c", option); // WON'T option
-		}
-		break;
-	      case 0x01: /* YES */
-	      case 0x11: /* YES OPPOSITE */
-		/* Ignore */
-		break;
-	      case 0x02: /* WANTNO EMPTY */
-		state &= ~option_us_want;
-		if ((fun = (cb["Enable_Option"] ||
-			    default_cb["Enable_Option"]))) {
-		  fun(option);
-		}
-		break;
-	      case 0x12: /* WANTNO OPPOSITE */
-		state &= ~(option_us_yes|option_us_opposite);
-		to_send += sprintf("\377\374%c", option); // WON'T option
-		break;
-	      case 0x03: /* WANTYES EMPTY */
-	      case 0x13: /* WANTYES OPPOSITE */
-		  option_states[option] &= ~0x12;
-		  option_states[option] |= option_us_yes;
-		  if ((fun = (cb["Enable_Option"] ||
-			      default_cb["Enable_Option"]))) {
-		    fun(option);
-		  }
-		break;
-	      }
-
-	      state = option_states[option];
-	      DWRITE(sprintf("=> DO %d, state 0x%04x\n", option, state));
-
-	      break;
-	    case "DON'T":
-	      int option = a[i][1];
-	      int state = option_states[option];
-	      a[i] = a[i][2..];
-
-	      DWRITE(sprintf("DON'T %d, state 0x%04x\n", option, state));
-
-	      switch(state & 0xff) {
-	      case 0x00: /* NO */
-	      case 0x10: /* NO OPPOSITE */
-		/* Ignore */
-		break;
-	      case 0x01: /* YES */
-	      case 0x11: /* YES OPPOSITE */
-		option_states[option] &= ~0xff;
-		to_send += sprintf("\377\374%c", option); // WON'T option
-		if ((fun = (cb["Disable_Option"] ||
-			    default_cb["Disable_Option"]))) {
-		  fun(option);
-		}
-		break;
-	      case 0x02: /* WANTNO EMPTY */
-	      case 0x03: /* WANTYES EMPTY */
-	      case 0x13: /* WANTYES OPPOSITE */
-		option_states[option] &= ~0xff;
-		break;
-	      case 0x12: /* WANTNO OPPOSITE */
-		option_states[option] &= ~option_us_opposite;
-		option_states[option] |= option_us_yes;
-		to_send += sprintf("\377\373%c", option); // WILL option
-		break;
-	      }
-
-	      state = option_states[option];
-	      DWRITE(sprintf("=> DON'T %d, state 0x%04x\n", option, state));
-
-	      break;
-	    case "DM":	// Data Mark
+#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] = "";
@@ -553,22 +711,25 @@ class protocol
 	    i++;
 	  }
 	}
+//	werror("%O\n",a);
 	line = a * "";
       }
-      if (!lineno) {
-	line = rest + line;
-      }
-      if (lineno < (sizeof(lines)-1)) {
-	if ((!synch) && read_cb) {
-	  DWRITE(sprintf("TELNET: Calling read_callback(X, \"%s\")\n",
-			       line));
-	  read_cb(id, line);
-	}
-      } else {
-	DWRITE(sprintf("TELNET: Partial line is \"%s\"\n", line));
-	rest = line;
+
+      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();
   }
 
@@ -585,6 +746,12 @@ class protocol
     }
   }
 
+  //. - create
+  //. created specifically for overloading
+  void setup()
+  {
+  }
+
   //. - create
   //.   Creates a TELNET protocol handler, and sets its callbacks.
   //. > f - File to use for the connection.
@@ -609,5 +776,222 @@ class protocol
 
     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);
   }
 }
+
+
+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","\r\n","\r","\r\0"}),
+		   ({"\r",  "\r",  "\r","\r",}));
+      line_buffer+=data;
+      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;
+	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 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->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"));
+    }
+  }
+  
+  static string prompt="";
+  void set_prompt(string s)
+  {
+    if(readline)
+    {
+      prompt=s;
+      readline->set_prompt(prompt);
+    }else{
+      if(prompt!=s)
+      {
+	if(s[..strlen(prompt)-1]==prompt)
+	  write(s);
+	prompt=s;
+      }
+    }
+  }
+
+  void destroy()
+  {
+    if(readline) destruct(readline);
+  }
+}
+