diff --git a/.gitattributes b/.gitattributes
index 8a122021056a39159965e1c8bc879f5f0befb8f9..13dd39325a299430fe835d3263895a39afeb2256 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -38,6 +38,7 @@ testfont binary
 /lib/modules/LR.pmod/scanner.pike foreign_ident
 /lib/modules/MIME.pmod foreign_ident
 /lib/modules/Protocols.pmod/Ident.pmod foreign_ident
+/lib/modules/Protocols.pmod/Line.pmod foreign_ident
 /lib/modules/Protocols.pmod/TELNET.pmod foreign_ident
 /lib/modules/Protocols.pmod/X.pmod/Atom.pmod foreign_ident
 /lib/modules/Protocols.pmod/X.pmod/Auth.pmod foreign_ident
diff --git a/lib/modules/Protocols.pmod/Line.pmod b/lib/modules/Protocols.pmod/Line.pmod
new file mode 100644
index 0000000000000000000000000000000000000000..c5902244a06cde20e921c2c04f72a55f9708f196
--- /dev/null
+++ b/lib/modules/Protocols.pmod/Line.pmod
@@ -0,0 +1,151 @@
+/*
+ * $Id: Line.pmod,v 1.1 1998/05/27 20:56:39 grubba Exp $
+ *
+ * Line-buffered protocol handling.
+ *
+ * Henrik Grubbström 1998-05-27
+ */
+
+class simple
+{
+  static object con;
+
+  function handle_data;
+  void handle_command(string data);
+
+  static string multi_line_buffer = "";
+  static void _handle_command(string line)
+  {
+    if (handle_data) {
+      if (line != ".") {
+	multi_line_buffer += line + "\r\n";
+      } else {
+	function handle = handle_data;
+	string data = multi_line_buffer;
+	handle_data = 0;
+	multi_line_buffer = "";
+	handle(data);
+      }
+    } else {
+      handle_command(line);
+    }
+  }
+
+  static string read_buffer = "";
+  static void read_callback(mixed ignored, string data)
+  {
+    read_buffer += data;
+
+    while(1) {
+      int i = search(read_buffer, "\r\n");
+      if (i == -1) {
+	return;
+      }
+      data = read_buffer[..i-1];			// Not the "\r\n".
+      read_buffer = read_buffer[i+2..];
+      _handle_command(data);
+
+      if (read_buffer == "") {
+	return;
+      }
+    }
+  }
+
+  object(ADT.queue) send_q = ADT.queue();
+
+  static string write_buffer = "";
+  static void write_callback(mixed ignored)
+  {
+    while (!sizeof(write_buffer)) {
+      if (send_q->is_empty()) {
+	con->set_write_callback(0);
+	return;
+      } else {
+	write_buffer = send_q->get();
+	if (!write_buffer) {
+	  // EOF
+	  con->set_write_callback(0);
+	  con->close();
+	  catch { destruct(con); };
+	  con = 0;
+	  return;
+	}
+      }
+    }
+    int w = con->write(write_buffer);
+    if (w > 0) {
+      write_buffer = write_buffer[w..];
+      if (!sizeof(write_buffer)) {
+	if (send_q->is_empty()) {
+	  con->set_write_callback(0);
+	} else {
+	  write_buffer = send_q->get();
+	  if (!write_buffer) {
+	    // EOF
+	    con->set_write_callback(0);
+	    con->close();
+	    catch { destruct(con); };
+	    con = 0;
+	  }
+	}
+      }
+    } else {
+      // Failed to write!
+      werror("write_callback(): write() failed!\n");
+		     
+      con->set_write_callback(0);
+      con->close();
+      con = 0;
+    }
+  }
+
+  void disconnect()
+  {
+    // Delayed disconnect.
+    send_q->put(0);
+    con->set_write_callback(write_callback);
+    con->set_read_callback(0);
+  }
+    
+  static void close_callback()
+  {
+    if (handle_data || sizeof(read_buffer) || sizeof(multi_line_buffer)) {
+      werror("close_callback(): Unexpected close!\n");
+    }
+    con->close();
+    con = 0;
+  }
+
+  void create(object con_)
+  {
+    con = con_;
+    con->set_nonblocking(read_callback, 0, close_callback);
+  }
+};
+
+class smtp_style
+{
+  inherit simple;
+
+  constant errorcodes = ([]);
+
+  void send(int code, array(string)|string|void lines)
+  {
+    lines = lines || errorcodes[code] || "Error";
+
+    if (stringp(lines)) {
+      lines /= "\n";
+    }
+
+    string init = sprintf("%03d", code);
+    string res = "";
+
+    int i;
+    for(i=0; i < sizeof(lines)-1; i++) {
+      res += init + "-" + lines[i] + "\r\n";
+    }
+    res += init + " " + lines[-1] + "\r\n";
+    send_q->put(res);
+    con->set_write_callback(write_callback);
+  }
+};