diff --git a/.gitattributes b/.gitattributes
index 11140e3671dd0fffcb8728874f4ffd9953ece972..53d1e27e149d0697b9f6c2996ca3564957bcc8cf 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -39,6 +39,7 @@ testfont binary
 /lib/modules/LR.pmod/scanner.pike foreign_ident
 /lib/modules/MIME.pmod foreign_ident
 /lib/modules/PDB.pmod foreign_ident
+/lib/modules/Protocols.pmod/TELNET.pmod foreign_ident
 /lib/modules/SSL.pmod/alert.pike foreign_ident
 /lib/modules/SSL.pmod/cipher.pike foreign_ident
 /lib/modules/SSL.pmod/connection.pike foreign_ident
diff --git a/lib/modules/Protocols.pmod/TELNET.pmod b/lib/modules/Protocols.pmod/TELNET.pmod
new file mode 100644
index 0000000000000000000000000000000000000000..59ce66804fbbcb083efee1737efc68e53c4dbee6
--- /dev/null
+++ b/lib/modules/Protocols.pmod/TELNET.pmod
@@ -0,0 +1,286 @@
+//
+// $Id: TELNET.pmod,v 1.1 1998/04/04 14:23:18 grubba Exp $
+//
+// The TELNET protocol as described by RFC 764 and others.
+//
+// Henrik Grubbström <grubba@idonex.se> 1998-04-04
+//
+
+#ifdef TELNET_DEBUG
+#define DWRITE(X)	werror(X)
+#else
+#define DWRITE(X)
+#endif /* TELNET_DEBUG */
+
+//. o protocol
+//.   Implementation of the TELNET protocol.
+class protocol
+{
+  //. + fd
+  //.   The connection.
+  static object fd;
+
+  //. + cb
+  //.   Mapping containing extra callbacks.
+  static private mapping cb;
+
+  //. + id
+  //.   Value to send to the callbacks.
+  static private mixed id;
+
+  //. + write_cb
+  //.   Write callback.
+  static private function(mixed|void:string) write_cb;
+
+  //. + read_cb
+  //.   Read callback.
+  static private 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
+  ]);
+
+  //. + to_send
+  //.   Data queued to be sent.
+  static private string to_send = "";
+
+  //. - send
+  //.   Queues data to be sent to the other end of the connection.
+  //. > s - String to send.
+  static private void send(string s)
+  {
+    to_send += s;
+  }
+
+  //. - 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()
+  {
+    if (!sizeof(to_send)) {
+      to_send = write_cb(id);
+    }
+    if (!to_send) {
+      // Support for delayed close.
+      fd->close();
+      fd = 0;
+    } else if (sizeof(to_send)) {
+      int n = fd->write(to_send);
+
+      to_send = to_send[n..];
+    }
+  }
+
+  //. + default_cb
+  //.   Mapping with the default handlers for TELNET commands.
+  static private mapping(string:function) default_cb = ([
+    "BRK":lambda() {
+	    destruct();
+	    throw(0);
+	  },
+    "AYT":lambda() {
+	    send("\377\361");	// NOP
+	  },
+    "WILL":lambda(int code) {
+	      send(sprintf("\377\376%c", code));	// DON'T xxx
+	   },
+    "DO":lambda(int code) {
+	   send(sprintf("\377\374%c", code));	// WON'T xxx
+	 },
+  ]);
+
+  //. + 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)
+  {
+    DWRITE(sprintf("TELNET: got_oob(\"%s\")\n", s));
+    synch = synch || (s == "\377");
+    if (cb["URG"]) {
+      cb["URG"](id, s);
+    }
+  }
+
+  //. + rest
+  //.   Contains data left over from the line-buffering.
+  static private string rest = "";
+
+  //. - 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)
+  {
+    DWRITE(sprintf("TELNET: got_data(\"%s\")\n", s));
+
+    if (sizeof(s) && (s[0] == 242)) {
+      DWRITE("TELNET: Data Mark\n");
+      // Data Mark handing.
+      s = s[1..];
+      sync = 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";
+
+	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"));
+
+	    int j;
+	    function fun;
+	    switch (name) {
+	    case 0:
+	      // FIXME: Should probably have a warning here.
+	      break;
+	    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;
+		}
+	      }
+	      a[i] = a[i][1..];
+	      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;
+	    case "EL":	// Erase Line
+	      for (j=0; j < i; j++) {
+		a[j] = "";
+	      }
+	      a[i] = a[i][1..];
+	      break;
+	    case "WILL":
+	    case "WON'T":
+	    case "DO":
+	    case "DON'T":
+	      if (fun = (cb[name] || default_cb[name])) {
+		fun(a[i][1]);
+	      }
+	      a[i] = a[i][2..];
+	      break;
+	    case "DM":	// Data Mark
+	      if (sync) {
+		for (j=0; j < i; j++) {
+		  a[j] = "";
+		}
+	      }
+	      a[i] = a[i][1..];
+	      sync = 0;
+	      break;
+	    }
+	  } else {
+	    a[i] = "\377";
+	    i++;
+	  }
+	}
+	line = a * "";
+      }
+      if (!lineno) {
+	line = rest + line;
+      }
+      if (lineno < (sizeof(lines)-1)) {
+	if ((!sync) && 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;
+      }
+    }
+  }
+
+  //. - set_write_callback
+  //.   Sets the callback to be called when it is clear to send.
+  //. > w_cb - The new read callback.
+  void set_write_callback(function(mixed|void:string) w_cb)
+  {
+    write_cb = w_cb;
+    fd->set_nonblocking(got_data, w_cb && send_data, close_cb, got_oob);
+  }
+
+  //. - create
+  //.   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);
+  }
+}