From 33ef435c8c8077366a8291fd6cc301ac4a215338 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Niels=20M=C3=B6ller?= <nisse@lysator.liu.se>
Date: Thu, 13 Mar 1997 21:02:58 +0100
Subject: [PATCH] New SSL module

Rev: lib/modules/SSL.pmod/TODO:1.1
Rev: lib/modules/SSL.pmod/alert.pike:1.1
Rev: lib/modules/SSL.pmod/cipher.pike:1.1
Rev: lib/modules/SSL.pmod/connection.pike:1.1
Rev: lib/modules/SSL.pmod/constants.pike:1.1
Rev: lib/modules/SSL.pmod/context.pike:1.1
Rev: lib/modules/SSL.pmod/handshake.pike:1.1
Rev: lib/modules/SSL.pmod/https.pike:1.1
Rev: lib/modules/SSL.pmod/packet.pike:1.1
Rev: lib/modules/SSL.pmod/queue.pike:1.1
Rev: lib/modules/SSL.pmod/server.pike:1.1
Rev: lib/modules/SSL.pmod/session.pike:1.1
Rev: lib/modules/SSL.pmod/sslport.pike:1.1
Rev: lib/modules/SSL.pmod/state.pike:1.1
Rev: lib/modules/SSL.pmod/struct.pike:1.1
---
 lib/modules/SSL.pmod/TODO            |   7 +
 lib/modules/SSL.pmod/alert.pike      |  32 ++
 lib/modules/SSL.pmod/cipher.pike     | 202 ++++++++++++
 lib/modules/SSL.pmod/connection.pike | 277 ++++++++++++++++
 lib/modules/SSL.pmod/constants.pike  | 153 +++++++++
 lib/modules/SSL.pmod/context.pike    |  99 ++++++
 lib/modules/SSL.pmod/handshake.pike  | 470 +++++++++++++++++++++++++++
 lib/modules/SSL.pmod/https.pike      | 177 ++++++++++
 lib/modules/SSL.pmod/packet.pike     | 115 +++++++
 lib/modules/SSL.pmod/queue.pike      |  50 +++
 lib/modules/SSL.pmod/server.pike     |   4 +
 lib/modules/SSL.pmod/session.pike    | 131 ++++++++
 lib/modules/SSL.pmod/sslport.pike    | 234 +++++++++++++
 lib/modules/SSL.pmod/state.pike      |  94 ++++++
 lib/modules/SSL.pmod/struct.pike     | 160 +++++++++
 15 files changed, 2205 insertions(+)
 create mode 100644 lib/modules/SSL.pmod/TODO
 create mode 100644 lib/modules/SSL.pmod/alert.pike
 create mode 100644 lib/modules/SSL.pmod/cipher.pike
 create mode 100644 lib/modules/SSL.pmod/connection.pike
 create mode 100644 lib/modules/SSL.pmod/constants.pike
 create mode 100644 lib/modules/SSL.pmod/context.pike
 create mode 100644 lib/modules/SSL.pmod/handshake.pike
 create mode 100644 lib/modules/SSL.pmod/https.pike
 create mode 100644 lib/modules/SSL.pmod/packet.pike
 create mode 100644 lib/modules/SSL.pmod/queue.pike
 create mode 100644 lib/modules/SSL.pmod/server.pike
 create mode 100644 lib/modules/SSL.pmod/session.pike
 create mode 100644 lib/modules/SSL.pmod/sslport.pike
 create mode 100644 lib/modules/SSL.pmod/state.pike
 create mode 100644 lib/modules/SSL.pmod/struct.pike

diff --git a/lib/modules/SSL.pmod/TODO b/lib/modules/SSL.pmod/TODO
new file mode 100644
index 0000000000..3116bf27e8
--- /dev/null
+++ b/lib/modules/SSL.pmod/TODO
@@ -0,0 +1,7 @@
+SSL TODO-list, 970307
+
+* ASN.1 support
+* Client connections
+* Splitting of large handshake packets
+* Combining small application packets
+* Blocking I/O
diff --git a/lib/modules/SSL.pmod/alert.pike b/lib/modules/SSL.pmod/alert.pike
new file mode 100644
index 0000000000..577814a76c
--- /dev/null
+++ b/lib/modules/SSL.pmod/alert.pike
@@ -0,0 +1,32 @@
+/* alert.pike
+ *
+ */
+
+inherit "packet" : packet;
+
+int level;
+int description;
+
+string message;
+mixed trace;
+
+constant is_alert = 1;
+
+object create(int l, int d, string|void m, mixed|void t)
+{
+  if (! ALERT_levels[l])
+    throw( ({ "SSL.alert->create: Invalid level\n", backtrace() }));
+  if (! ALERT_descriptions[d])
+    throw( ({ "SSL.alert->create: Invalid description\n", backtrace() }));    
+
+  level = l;
+  description = d;
+  message = m;
+  trace = t;
+
+  packet::create();
+  packet::content_type = PACKET_alert;
+  packet::protocol_version = ({ 3, 0 });
+  packet::fragment = sprintf("%c%c", level, description);
+}
+    
diff --git a/lib/modules/SSL.pmod/cipher.pike b/lib/modules/SSL.pmod/cipher.pike
new file mode 100644
index 0000000000..3e90eacf61
--- /dev/null
+++ b/lib/modules/SSL.pmod/cipher.pike
@@ -0,0 +1,202 @@
+/* cipher.pike
+ *
+ */
+
+inherit "constants";
+
+class CipherSpec {
+  program bulk_cipher_algorithm;
+  int cipher_type;
+  program mac_algorithm;
+  int is_exportable;
+  int hash_size;
+  int key_material;
+  int iv_size;
+}
+
+class mac_none
+{
+  /* Dummy MAC algorithm */
+//  string hash_raw(string data) { return ""; }
+  string hash(string data, object seq_num) { return ""; }
+}
+
+class mac_sha
+{
+  string pad_1 =  "6666666666666666666666666666666666666666";
+  string pad_2 = ("\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\"
+		  "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\");
+
+  program algorithm = Crypto.sha;
+
+  string secret;
+
+  string hash_raw(string data)
+  {
+    object h = algorithm();
+    h->update(data);
+    return h->digest();
+  }
+  
+  string hash(object packet, object seq_num)
+  {
+    string s = sprintf("%~8s%c%2c%s",
+		       "\0\0\0\0\0\0\0\0", seq_num->digits(256),
+		       packet->content_type, strlen(packet->fragment),
+		       packet->fragment);
+//    werror(sprintf("SSL.cipher: hashing '%s'\n", s));
+    return hash_raw(secret + pad_2 +
+		    hash_raw(secret + pad_1 + s));
+  }
+
+  string hash_master(string data, string|void s)
+  {
+    s = s || secret;
+    return hash_raw(s + pad_2 +
+		hash_raw(data + s + pad_1));
+  }
+
+  void create (string|void s)
+  {
+    secret = s || "";
+  }
+}
+
+class mac_md5 {
+  inherit mac_sha;
+
+  string pad_1 =  "666666666666666666666666666666666666666666666666";
+  string pad_2 = ("\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\"
+		  "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\");
+  
+  program algorithm = Crypto.md5;
+}
+
+class crypt_none
+{
+  /* Dummy stream cipher */
+  object set_encrypt_key(string k) { return this_object(); }
+  object set_decrypt_key(string k) { return this_object(); }  
+  string crypt(string s) { return s; }
+}
+
+class des
+{
+  inherit Crypto.des_cbc : c;
+
+  object set_encrypt_key(string k)
+  {
+    c::set_encrypt_key(Crypto.des_parity(k));
+    return this_object();
+  }
+
+  object set_decrypt_key(string k)
+  {
+    c::set_decrypt_key(Crypto.des_parity(k));
+    return this_object();
+  }
+}
+
+class des3
+{
+  inherit Crypto.des3_cbc : c;
+
+  object set_encrypt_key(string k)
+  {
+    c::set_encrypt_key(Crypto.des_parity(k));
+    return this_object();
+  }
+
+  object set_decrypt_key(string k)
+  {
+    c::set_decrypt_key(Crypto.des_parity(k));
+    return this_object();
+  }
+}
+
+class rsa_auth
+{
+}
+
+/* Return array of auth_method, cipher_spec */
+array lookup(int suite)
+{
+  object res = CipherSpec();
+  int ke_method;
+  
+  array algorithms = CIPHER_SUITES[suite];
+  if (!algorithms)
+    return 0;
+
+  ke_method = algorithms[0];
+
+  switch(algorithms[1])
+  {
+  case CIPHER_rc4:
+    res->bulk_cipher_algorithm = Crypto.rc4;
+    res->cipher_type = CIPHER_stream;
+    res->is_exportable = 0;
+    res->key_material = 16;
+    res->iv_size = 0;
+    break;
+  case CIPHER_rc4_40:
+    res->bulk_cipher_algorithm = Crypto.rc4;
+    res->cipher_type = CIPHER_stream;
+    res->is_exportable = 1;
+    res->key_material = 5;
+    res->iv_size = 0;
+    break;
+  case CIPHER_des:
+    res->bulk_cipher_algorithm = des;
+    res->cipher_type = CIPHER_block;
+    res->is_exportable = 0;
+    res->key_material = 7;
+    res->iv_size = 8;
+    break;
+  case CIPHER_3des:
+    res->bulk_cipher_algorithm = des3;
+    res->cipher_type = CIPHER_block;
+    res->is_exportable = 0;
+    res->key_material = 16;
+    res->iv_size = 8;
+    break;
+  case CIPHER_idea:
+    res->bulk_cipher_algorithm = Crypto.idea_cbc;
+    res->cipher_type = CIPHER_block;
+    res->is_exportable = 0;
+    res->key_material = 16;
+    res->iv_size = 8;
+    break;
+  case CIPHER_null:
+    res->bulk_cipher_algorithm = crypt_none;
+    res->cipher_type = CIPHER_stream;
+    res->is_exportable = 1;
+    res->key_material = 0;
+    res->iv_size = 0;
+    break;
+  default:
+    return 0;
+  }
+
+  switch(algorithms[2])
+  {
+  case HASH_sha:
+    res->mac_algorithm = mac_sha;
+    res->hash_size = 20;
+    break;
+  case HASH_md5:
+    res->mac_algorithm = mac_md5;
+    res->hash_size = 16;
+    break;
+  case 0:
+    res->mac_algorithm = mac_none;
+    res->hash_size = 0;
+    break;
+  default:
+    return 0;
+  }
+
+  return ({ ke_method, res });
+}
+
+  
diff --git a/lib/modules/SSL.pmod/connection.pike b/lib/modules/SSL.pmod/connection.pike
new file mode 100644
index 0000000000..d3e95429b1
--- /dev/null
+++ b/lib/modules/SSL.pmod/connection.pike
@@ -0,0 +1,277 @@
+/* connection.pike
+ *
+ * SSL packet layer
+ */
+
+
+object current_read_state;
+object current_write_state;
+string left_over;
+object packet;
+
+int dying;
+int closing;
+
+inherit "constants";
+inherit "handshake";
+
+constant Queue = (program) "queue";
+constant State = (program) "state";
+
+inherit Queue : alert;
+inherit Queue : urgent;
+inherit Queue : application;
+
+void create(int is_server)
+{
+  handshake::create(is_server);
+  alert::create();
+  urgent::create();
+  application::create();
+  current_read_state = State(this_object());
+  current_write_state = State(this_object());
+}
+
+object recv_packet(string data)
+{
+  mixed res;
+
+//  werror(sprintf("SSL.connection->recv_packet('%s')\n", data));
+  if (left_over || !packet)
+  {
+    packet = Packet(2048);
+    res = packet->recv( (left_over || "")  + data);
+  }
+  else
+    res = packet->recv(data);
+
+  if (stringp(res))
+  { /* Finished a packet */
+    left_over = res;
+    return current_read_state->decrypt_packet(packet);
+  }
+  else /* Partial packet read, or error */
+    left_over = 0;
+  return res;
+}
+
+void send_packet(object packet, int|void fatal)
+{
+//  werror(sprintf("SSL.connection->send_packet: type %d, '%s'\n",
+//		 packet->content_type, packet->fragment));
+  switch (packet->content_type)
+  {
+  default:
+    throw( ({"SSL.connection->send_packet: internal error\n", backtrace() }) );
+  case PACKET_alert:
+    alert::put(packet);
+    if (packet->description == ALERT_close_notify)
+      {
+	if (packet->level == ALERT_fatal)
+	  fatal = 1;
+	else
+	  closing = 1;
+      }
+    break;
+  case PACKET_application_data:
+    application::put(packet);
+    break;
+    /* Handshake and and change cipher must use the same queue */
+  case PACKET_change_cipher_spec:
+  case PACKET_handshake:
+    urgent::put(packet);
+    break;
+  }
+  if (fatal)
+    dying = 1;
+}
+
+/* Returns a string of data to be written, "" if there's no pending packets,
+ * 1 if the connection is being closed politely, and -1 if the connection
+ * died unexpectedly. */
+string|int to_write()
+{
+  string res = 0;
+
+  object packet = alert::get();
+  if (!packet)
+  {
+    if (dying)
+      return closing ? 1 : -1;
+    packet = urgent::get() || application::get();
+  }
+  if (packet)
+    {
+      werror(sprintf("SSL.connection: writing packet of type %d, '%s'\n",
+		     packet->content_type, packet->fragment[..6]));
+      res = current_write_state->encrypt_packet(packet)->send();
+      if (packet->content_type == PACKET_change_cipher_spec)
+	current_write_state = pending_write_state;
+    }
+  else
+    res = closing ? 1 : "";
+  return res;
+}
+
+int handle_alert(string s)
+{
+  int level = s[0];
+  int description = s[1];
+
+  if (! (ALERT_levels[level] && ALERT_descriptions[description]))
+  {
+    send_packet(Alert(ALERT_fatal, ALERT_unexpected_message,
+		      "SSL.connection->handle_alert: invalid alert\n", backtrace()));
+    return -1;
+  }
+  if (level == ALERT_fatal)
+  {
+    werror(sprintf("SSL.connection: Fatal alert %d\n", description));
+    return -1;
+  }
+  if (description == ALERT_close_notify)
+  {
+    closing = 1;
+    return 0;
+  }
+  if (description == ALERT_no_certificate)
+  {
+    if ((certificate_state == CERT_requested) && (context->auth_level == AUTHLEVEL_ask))
+    {
+      certificate_state = CERT_no_certificate;
+      return 0;
+    } else {
+      send_packet(Alert(ALERT_fatal, ((certificate_state == CERT_requested)
+			       ? ALERT_handshake_failure
+				: ALERT_unexpected_message)));
+      return -1;
+    }
+  }
+  else
+    werror(sprintf("SSL.connection: Received warning alert %d\n", description));
+  return 0;
+}
+
+int handle_change_cipher(int c)
+{
+  if (!expect_change_cipher || (c != 1))
+  {
+    send_packet(Alert(ALERT_fatal, ALERT_unexpected_message));
+    return -1;
+  }
+  else
+  {
+    current_read_state = pending_read_state;
+    expect_change_cipher = 0;
+    return 0;
+  }
+}
+
+string alert_buffer = "";
+string handshake_buffer = "";
+int handshake_finished = 0;
+
+/* Returns a string of application data, or -1 if a fatal error occured */
+string|int got_data(string s)
+{
+  string res = "";
+  object packet;
+  while (packet = recv_packet(s))
+  {
+    s = "";
+
+    if (packet->is_alert)
+    { /* Reply alert */
+      werror("Bad recieved packet\n");
+      send_packet(packet);
+      if (packet->level == ALERT_fatal)
+	return -1;
+    }
+    else
+    {
+      werror(sprintf("SSL.connection: recieved packet of type %d\n",
+		     packet->content_type));
+      switch (packet->content_type)
+      {
+      case PACKET_alert:
+       {
+	 int i;
+	 mixed err = 0;
+	 alert_buffer += packet->fragment;
+	 for (i = 0;
+	      !err && ((strlen(alert_buffer) - i) >= 2);
+	      i+= 2)
+	   err = handle_alert(alert_buffer[i..i+1]);
+
+	 alert_buffer = alert_buffer[i..];
+	 if (err)
+	   return err;
+	 break;
+       }
+      case PACKET_change_cipher_spec:
+       {
+	 int i;
+	 int err;
+	 for (i = 0; (i < strlen(packet->fragment)); i++)
+	 {
+	   err = handle_change_cipher(packet->fragment[i]);
+	   werror(sprintf("tried change_cipher: %d\n", err));
+	   if (err)
+	     return err;
+	 }
+	 break;
+       }
+      case PACKET_handshake:
+       {
+	 if (expect_change_cipher)
+	 {
+	   /* No change_cipher message was recieved */
+	   send_packet(Alert(ALERT_fatal, ALERT_unexpected_message));
+	   return -1;
+	 }
+	 mixed err;
+	 int len;
+	 handshake_buffer += packet->fragment;
+
+	 while (strlen(handshake_buffer) >= 4)
+	 {
+	   sscanf(handshake_buffer, "%*c%3c", len);
+	   if (strlen(handshake_buffer) < (len + 4))
+	     break;
+	   err = handle_handshake(handshake_buffer[0],
+				  handshake_buffer[4..len + 3],
+				  handshake_buffer[.. len + 3]);
+	   handshake_buffer = handshake_buffer[len + 4..];
+	   if (err < 0)
+	     return err;
+	   if (err > 0)
+	     handshake_finished = 1;
+	 }
+	 break;
+       }
+      case PACKET_application_data:
+	if (!handshake_finished)
+	{
+	  send_packet(Alert(ALERT_fatal, ALERT_unexpected_message));
+	  return -1;
+	}
+	res += packet->fragment;
+	break;
+      case PACKET_V2:
+       {
+	 mixed err = handle_handshake(HANDSHAKE_hello_v2,
+				      packet->fragment[1 .. ],
+				      packet->fragment);
+	 if (err)
+	   return err;
+       }
+      }
+    }
+  }
+  return res;
+}
+
+void server()
+{
+  handshake_state = STATE_server_wait_for_hello;
+}
diff --git a/lib/modules/SSL.pmod/constants.pike b/lib/modules/SSL.pmod/constants.pike
new file mode 100644
index 0000000000..af7eb68e60
--- /dev/null
+++ b/lib/modules/SSL.pmod/constants.pike
@@ -0,0 +1,153 @@
+/* constants.pike
+ *
+ */
+
+/* Packet types */
+constant PACKET_change_cipher_spec = 20;
+constant PACKET_alert              = 21;
+constant PACKET_handshake          = 22;
+constant PACKET_application_data   = 23;
+constant PACKET_types = (< PACKET_change_cipher_spec,
+			   PACKET_alert,
+			   PACKET_handshake,
+			   PACKET_application_data >);
+constant PACKET_V2 = -1; /* Backwards compatibility */
+
+constant PACKET_MAX_SIZE = 0x4000;
+
+/* Cipher specification */
+constant CIPHER_stream   = 0;
+constant CIPHER_block    = 1;
+constant CIPHER_types = (< CIPHER_stream, CIPHER_block >);
+
+constant CIPHER_null     = 0;
+constant CIPHER_rc4      = 1;
+constant CIPHER_rc4_40   = 2;
+constant CIPHER_rc2      = 3;
+constant CIPHER_des      = 4;
+constant CIPHER_3des     = 5;
+constant CIPHER_des40    = 6;
+constant CIPHER_fortezza = 7;
+constant CIPHER_idea	 = 8;
+constant CIPHER_algorithms = (< CIPHER_null, 
+				CIPHER_rc4,
+				CIPHER_rc4_40,
+				CIPHER_rc2,
+				CIPHER_des,
+				CIPHER_3des,
+				CIPHER_des40,
+				CIPHER_fortezza,
+				CIPHER_idea >);
+
+constant HASH_md5      = 1;
+constant HASH_sha      = 2;
+constant HASH_hashes = (< HASH_md5, HASH_sha >);
+
+/* Key exchange */
+constant KE_rsa	= 1;
+constant KE_dh	= 2;
+constant KE_dms	= 3;
+
+/* Compression methods */
+constant COMPRESSION_null = 0;
+
+/* Alert messages */
+constant ALERT_warning			= 1;
+constant ALERT_fatal			= 2;
+constant ALERT_levels = (< ALERT_warning, ALERT_fatal >);
+
+constant ALERT_close_notify             = 0;
+constant ALERT_unexpected_message       = 10;
+constant ALERT_bad_record_mac           = 20;
+constant ALERT_decompression_failure    = 30;
+constant ALERT_handshake_failure        = 40;
+constant ALERT_no_certificate           = 41;
+constant ALERT_bad_certificate          = 42;
+constant ALERT_unsupported_certificate  = 43;
+constant ALERT_certificate_revoked      = 44;
+constant ALERT_certificate_expired      = 45;
+constant ALERT_certificate_unknown      = 46;
+constant ALERT_illegal_parameter        = 47;
+constant ALERT_descriptions = (< ALERT_close_notify,
+ 				 ALERT_unexpected_message,
+ 				 ALERT_bad_record_mac,
+ 				 ALERT_decompression_failure,
+ 				 ALERT_handshake_failure,
+ 				 ALERT_no_certificate,
+ 				 ALERT_bad_certificate,
+ 				 ALERT_unsupported_certificate,
+ 				 ALERT_certificate_revoked,
+ 				 ALERT_certificate_expired,
+ 				 ALERT_certificate_unknown,
+ 				 ALERT_illegal_parameter >);
+ 			      
+constant CONNECTION_client 	= 0;
+constant CONNECTION_server 	= 1;
+constant CONNECTION_client_auth = 2;
+
+/* Cipher suites */
+constant SSL_null_with_null_null 		= 0x0000;
+constant SSL_rsa_with_null_md5			= 0x0001;
+constant SSL_rsa_with_null_sha			= 0x0002;
+constant SSL_rsa_export_with_rc4_40_md5		= 0x0003;
+constant SSL_rsa_with_rc4_128_md5		= 0x0004;
+constant SSL_rsa_with_rc4_128_sha		= 0x0005;
+constant SSL_rsa_export_with_rc2_cbc_40_md5	= 0x0006;
+constant SSL_rsa_with_idea_cbc_sha		= 0x0007;
+constant SSL_rsa_export_with_des40_cbc_sha	= 0x0008;
+constant SSL_rsa_with_des_cbc_sha		= 0x0009;
+constant SSL_rsa_with_3des_ede_cbc_sha		= 0x000a; 
+constant SSL_dh_dss_export_with_des40_cbc_sha	= 0x000b;
+constant SSL_dh_dss_with_des_cbc_sha		= 0x000c;
+constant SSL_dh_dss_with_3des_ede_cbc_sha	= 0x000d;
+constant SSL_dh_rsa_export_with_des40_cbc_sha	= 0x000e;
+constant SSL_dh_rsa_with_des_cbc_sha		= 0x000f;
+constant SSL_dh_rsa_with_3des_ede_cbc_sha	= 0x0010;
+constant SSL_dhe_dss_export_with_des40_cbc_sha	= 0x0011;
+constant SSL_dhe_dss_with_des_cbc_sha		= 0x0012;
+constant SSL_dhe_dss_with_3des_ede_cbc_sha	= 0x0013;
+constant SSL_dhe_rsa_export_with_des40_cbc_sha	= 0x0014;
+constant SSL_dhe_rsa_with_des_cbc_sha		= 0x0015;
+constant SSL_dhe_rsa_with_3des_ede_cbc_sha	= 0x0016; 
+constant SSL_dh_anon_export_with_rc4_40_md5	= 0x0017;
+constant SSL_dh_anon_with_rc4_128_md5		= 0x0018;
+constant SSL_dh_anon_export_with_des40_cbc_sha	= 0x0019;
+constant SSL_dh_anon_with_des_cbc_sha		= 0x001a;
+constant SSL_dh_anon_with_3des_ede_cbc_sha	= 0x001b; 
+constant SSL_fortezza_dms_with_null_sha		= 0x001c;
+constant SSL_fortezza_dms_with_fortezza_cbc_sha	= 0x001d;
+constant SSL_fortezza_dms_with_rc4_128_sha	= 0x001e;
+
+constant CIPHER_SUITES =
+([ SSL_null_with_null_null :    	({ 0, 0, 0 }),
+   SSL_rsa_with_null_md5 :      	({ KE_rsa, 0, HASH_md5 }),			   
+   SSL_rsa_with_null_sha :      	({ KE_rsa, 0, HASH_sha }),
+   SSL_rsa_export_with_rc4_40_md5 :	({ KE_rsa, CIPHER_rc4_40, HASH_md5 }),
+   SSL_rsa_with_rc4_128_sha :   	({ KE_rsa, CIPHER_rc4, HASH_sha }),
+   SSL_rsa_with_idea_cbc_sha :  	({ KE_rsa, CIPHER_idea, HASH_sha }),
+   SSL_rsa_with_des_cbc_sha :   	({ KE_rsa, CIPHER_des, HASH_sha }),
+   SSL_rsa_with_3des_ede_cbc_sha :	({ KE_rsa, CIPHER_3des, HASH_sha }) ]);
+
+constant HANDSHAKE_hello_v2		= -1; /* Backwards compatibility */
+constant HANDSHAKE_hello_request	= 0;
+constant HANDSHAKE_client_hello		= 1;
+constant HANDSHAKE_server_hello		= 2;
+constant HANDSHAKE_certificate		= 11;
+constant HANDSHAKE_server_key_exchange	= 12;
+constant HANDSHAKE_certificate_request	= 13;
+constant HANDSHAKE_server_hello_done	= 14;
+constant HANDSHAKE_certificate_verify	= 15;
+constant HANDSHAKE_client_key_exchange	= 16;
+constant HANDSHAKE_finished		= 20;
+
+constant AUTHLEVEL_none		= 1;
+constant AUTHLEVEL_ask		= 2;
+constant AUTHLEVEL_require	= 3;
+
+constant AUTH_rsa_sign		= 1;
+constant AUTH_dss_sign		= 2;
+constant AUTH_rsa_fixed_dh	= 3;
+constant AUTH_dss_fixed_dh	= 4;
+constant AUTH_rsa_ephemeral_dh	= 5;
+constant AUTH_dss_ephemeral_dh	= 6;
+constant AUTH_fortezza_dms	= 20;
diff --git a/lib/modules/SSL.pmod/context.pike b/lib/modules/SSL.pmod/context.pike
new file mode 100644
index 0000000000..b5b3ad9fa9
--- /dev/null
+++ b/lib/modules/SSL.pmod/context.pike
@@ -0,0 +1,99 @@
+/* context.pike
+ *
+ * Keeps track of global data for an SSL server,
+ * such as preferred encryption algorithms and session cache.
+ */
+
+inherit "constants";
+
+int auth_level;
+
+object rsa;  /* Servers private key */
+
+function(int:string) random; /* Random number generator */
+
+/* Chain of X509.v3 certificates
+ * Senders certificate first, root certificate last .*/
+array(string) certificates; 
+
+/* For client authentication */
+array(string) authorities; /* List of authorities distinguished names */
+
+array(int) preferred_auth_methods =
+({ AUTH_rsa_sign });
+
+array(int) preferred_suites =
+({ SSL_rsa_with_idea_cbc_sha,
+   SSL_rsa_with_rc4_128_sha,
+   SSL_rsa_with_rc4_128_md5,
+   SSL_rsa_with_3des_ede_cbc_sha,
+   SSL_rsa_with_des_cbc_sha,
+//   SSL_rsa_export_with_rc4_40_md5,
+   SSL_rsa_with_null_sha,
+   SSL_rsa_with_null_md5
+});
+
+array(int) preferred_compressors =
+({ COMPRESSION_null });
+
+constant Session = (program) "session";
+constant Queue = (program) "queue";
+
+int use_cache = 1;
+int session_lifetime = 600; /* Time to remember a session, in seconds */
+
+/* Session cache */
+object active_sessions;  /* Queue of pairs (time, id), in cronological order */
+mapping(string:object(Session)) session_cache;
+
+int session_number; /* Incremented for each session, and used when constructing the
+		     * session id */
+
+void forget_old_sessions()
+{
+  int t = time() - session_lifetime;
+  array pair;
+  while ( (pair = active_sessions->peek())
+	  && (pair[0] < t))
+    session_cache[active_sessions->get()[1]] = 0;
+}
+
+object lookup_session(string id)
+{
+  if (use_cache)
+  {
+    forget_old_sessions();
+    return session_cache[id];
+  }
+  else
+    return 0;
+}
+
+object new_session()
+{
+  object s = Session();
+  s->identity = (use_cache) ? sprintf("%4c%4c", time(), session_number++) : "";
+  return s;
+}
+
+void record_session(object s)
+{
+  if (use_cache && s->identity)
+  {
+    active_sessions->put( ({ time(), s->identity }) );
+    session_cache[s->identity] = s;
+  }
+}
+
+void purge_session(object s)
+{
+  if (s->identity)
+    session_cache[s->identity] = 0;
+  /* There's no need to remove the id from the active_sessions queue */
+}
+
+void create()
+{
+  active_sessions = Queue();
+  session_cache = ([ ]);
+}
diff --git a/lib/modules/SSL.pmod/handshake.pike b/lib/modules/SSL.pmod/handshake.pike
new file mode 100644
index 0000000000..e8776e4245
--- /dev/null
+++ b/lib/modules/SSL.pmod/handshake.pike
@@ -0,0 +1,470 @@
+/* handshake.pike
+ *
+ */
+
+// int is_server;
+
+inherit "cipher";
+
+object session;
+object context;
+
+object pending_read_state;
+object pending_write_state;
+
+/* State variables */
+
+constant STATE_server_wait_for_hello		= 1;
+constant STATE_server_wait_for_client		= 2;
+constant STATE_server_wait_for_finish		= 3;
+constant STATE_server_wait_for_verify		= 4;
+
+constant STATE_client_wait_for_hello		= 10;
+constant STATE_client_wait_for_server		= 11;
+constant STATE_client_wait_for_finish		= 12;
+int handshake_state;
+
+constant CERT_none = 0;
+constant CERT_requested = 1;
+constant CERT_recieved = 2;
+constant CERT_no_certificate = 3;
+int certificate_state;
+
+int expect_change_cipher; /* Reset to 0 if a change_cipher message is recieved */
+
+int reuse;
+
+string my_random;
+string other_random;
+
+constant Struct = (program) "struct";
+constant Session = (program) "session";
+constant Packet = (program) "packet";
+constant Alert = (program) "alert";
+
+/* Defined in connection.pike */
+void send_packet(object packet, int|void fatal);
+
+string handshake_messages;
+
+object handshake_packet(int type, string data)
+{
+  /* Perhaps one need to split large packages? */
+  object packet = Packet();
+  packet->content_type = PACKET_handshake;
+  packet->fragment = sprintf("%c%3c%s", type, strlen(data), data);
+  handshake_messages += packet->fragment;
+  return packet;
+}
+
+object server_hello_packet()
+{
+  object struct = Struct();
+  /* Build server_hello message */
+  struct->put_int(3,1); struct->put_int(0,1); /* version */
+  struct->put_fix_string(my_random);
+  struct->put_var_string(session->identity, 1);
+  struct->put_int(session->cipher_suite, 2);
+  struct->put_int(session->compression_algorithm, 1);
+
+  string data = struct->pop_data();
+  werror(sprintf("SSL.handshake: Server hello: '%s'\n", data));
+  return handshake_packet(HANDSHAKE_server_hello, data);
+}
+
+int reply_new_session(array(int) cipher_suites, array(int) compression_methods)
+{
+  reuse = 0;
+  session = context->new_session();
+
+//  werror(sprintf("ciphers: me: %O, client: %O\n",
+//		   context->preferred_suites, cipher_suites)); 
+//  werror(sprintf("compr: me: %O, client: %O\n",
+//		   context->preferred_compressors, compression_methods)); 
+  cipher_suites &= context->preferred_suites;
+  if (sizeof(cipher_suites))
+    session->set_cipher_suite(cipher_suites[0]);
+  else
+  {
+    send_packet(Alert(ALERT_fatal, ALERT_handshake_failure));
+    return -1;
+  }
+  
+  compression_methods &= context->preferred_compressors;
+  if (sizeof(compression_methods))
+    session->set_compression_method(compression_methods[0]);
+  else
+  {
+    send_packet(Alert(ALERT_fatal, ALERT_handshake_failure));
+    return -1;
+  }
+  
+  send_packet(server_hello_packet());
+  
+  /* Send Certificate, ServerKeyExchange and CertificateRequest as
+   * appropriate, and then ServerHelloDone.
+   */
+  if (context->certificates)
+  {
+    object struct = Struct();
+    
+    int len = `+( @ Array.map(context->certificates, strlen));
+//    werror(sprintf("SSL.handshake: certificate_message size %d\n", len));
+    struct->put_int(len + 3 * sizeof(context->certificates), 3);
+    foreach(context->certificates, string cert)
+      struct->put_var_string(cert, 3);
+    send_packet(handshake_packet(HANDSHAKE_certificate, struct->pop_data()));
+  }
+  if (0)
+  {
+    /* Send a ServerKeyExchange message. Not supported, so far. */
+  }
+  if (context->auth_level >= AUTHLEVEL_ask)
+  {
+    /* Send a CertificateRequest message */
+    object struct = Struct();
+    struct->put_var_array(context->preferred_auth_methods, 1, 1);
+
+    int len = `+(@ Array.map(context->authorities, strlen));
+    struct->put_int(len + 2 * sizeof(context->authorities), 2);
+    foreach(context->authorities, string auth)
+      struct->put_var_string(auth, 2);
+    send_packet(handshake_packet(HANDSHAKE_certificate_request,
+				 struct->pop_data()));
+    certificate_state = CERT_requested;
+  }
+  send_packet(handshake_packet(HANDSHAKE_server_hello_done, ""));
+  return 0;
+}
+
+object change_cipher_packet()
+{
+  object packet = Packet();
+  packet->content_type = PACKET_change_cipher_spec;
+  packet->fragment = "\001";
+  return packet;
+}
+
+string hash_messages(string sender)
+{
+  return mac_md5(session->master_secret)->hash_master(handshake_messages + sender) +
+    mac_sha(session->master_secret)->hash_master(handshake_messages + sender);
+}
+
+object finished_packet(string sender)
+{
+  return handshake_packet(HANDSHAKE_finished, hash_messages(sender));
+}
+
+string server_derive_master_secret(string data)
+{
+  string res = "";
+  switch(session->ke_method)
+  {
+  default:
+    throw( ({ "SSL.handshake: internal error\n", backtrace() }) );
+  case 0:
+    return 0;
+  case KE_rsa:
+   {
+     /* Decrypt the pre_master_secret */
+     string s = context->rsa->decrypt(data);
+//     werror(sprintf("premaster_secret: '%s'\n", s));
+     if (!s || (strlen(s) != 48) || (s[0] != 3))
+       return 0;
+     if (s[1] > 0)
+       werror("SSL.handshake: Newer version detected in key exchange message.\n");
+     object sha = mac_sha();
+     object md5 = mac_md5();
+     foreach( ({ "A", "BB", "CCC" }), string cookie)
+       res += md5->hash_raw(s + sha->hash_raw(cookie + s +
+					      other_random + my_random));
+     break;
+   }
+  }
+//  werror(sprintf("master: '%s'\n", res));
+  return res;
+}
+
+/* return 0 if handshake is in progress, 1 if finished, -1 if there's a
+ * fatal error. */
+int handle_handshake(int type, string data, string raw)
+{
+  object input = Struct(data);
+
+  werror(sprintf("SSL.handshake: state %d, type %d\n", handshake_state, type));
+  
+  switch(handshake_state)
+  {
+  default:
+    throw( ({ "SSL.handshake: internal error\n", backtrace() }) );
+  case STATE_server_wait_for_hello:
+   {
+     array(int) cipher_suites;
+
+     /* Reset all extra state variables */
+     expect_change_cipher = certificate_state = 0;
+    
+     handshake_messages = raw;
+     my_random = sprintf("%4c%s", time(), context->random(28));
+
+     switch(type)
+     {
+     default:
+       send_packet(Alert(ALERT_fatal, ALERT_unexpected_message,
+			 "SSL.session->handle_handshake: unexpected message\n",
+			 backtrace()));
+       return -1;
+     case HANDSHAKE_client_hello:
+      {
+	array(int) version;
+	string id;
+	int cipher_len;
+	array(int) cipher_suites;
+	array(int) compression_methods;
+             
+	if (catch{
+	  version = input->get_fix_array(1, 2);
+	  other_random = input->get_fix_string(32);
+	  id = input->get_var_string(1);
+	  cipher_len = input->get_int(2);
+	  cipher_suites = input->get_fix_array(2, cipher_len/2);
+	  compression_methods = input->get_var_array(1, 1);
+	} || (version[0] != 3) || (cipher_len & 1))
+	{
+	  send_packet(Alert(ALERT_fatal, ALERT_unexpected_message));
+	  return -1;
+	}
+
+	if (!input->is_empty())
+	  werror("SSL.connection->handle_handshake: "
+		 "extra data in hello message ignored\n");
+      
+	if (version[1] > 0)
+	  werror(sprintf("SSL.connection->handle_handshake: "
+			 "Version %d.%d hello detected\n", @version));
+
+	if (strlen(id))
+	  werror(sprintf("Looking up session %s\n", id));
+	session = strlen(id) && context->lookup_session(id);
+	if (session)
+	{
+	  werror(sprintf("Reusing session %s\n", id));
+	  /* Reuse session */
+	  reuse = 1;
+	  if (! ( (cipher_suites & ({ session->cipher_suite }))
+		  && (compression_methods & ({ session->compression_algorithm }))))
+	  {
+	    send_packet(Alert(ALERT_fatal, ALERT_handshake_failure));
+	    return -1;
+	  }
+	  send_packet(server_hello_packet());
+
+	  array res = session->new_server_states(other_random, my_random);
+	  pending_read_state = res[0];
+	  pending_write_state = res[1];
+	  send_packet(change_cipher_packet());
+	  send_packet(finished_packet("SRVR"));
+	  expect_change_cipher = 1;
+	 
+	  handshake_state = STATE_server_wait_for_finish;
+	} else {
+	  /* New session, do full handshake. */
+	  
+	  int err = reply_new_session(cipher_suites, compression_methods);
+	  if (err)
+	    return err;
+	  handshake_state = STATE_server_wait_for_client;
+	}
+	break;
+      }
+     case HANDSHAKE_hello_v2:
+      {
+	werror("SSL.handshake: SSL2 hello message recieved\n");
+	int ci_len;
+	int id_len;
+	int ch_len;
+	array(int) version;
+	if (catch{
+	  version = input->get_fix_array(1, 2);
+	  ci_len = input->get_int(2);
+	  id_len = input->get_int(2);
+	  ch_len = input->get_int(2);
+	} || (ci_len % 3) || !ci_len || (id_len) || (ch_len < 16)
+	|| (version[0] != 3))
+	{
+	  send_packet(Alert(ALERT_fatal, ALERT_unexpected_message));
+	  return -1;
+	}
+
+	if (version[1] > 0)
+	  werror(sprintf("SSL.connection->handle_handshake: "
+			 "Version %d.%d hello detected\n", @version));
+	
+
+	string challenge;
+	if (catch{
+	  cipher_suites = input->get_fix_array(3, ci_len/3);
+	  challenge = input->get_fix_string(ch_len);
+	} || !input->is_empty()) 
+	{
+	  send_packet(Alert(ALERT_fatal, ALERT_unexpected_message));
+	  return -1;
+	}
+	other_random = ("\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + challenge)[..31];
+	int err = reply_new_session(cipher_suites, ({ COMPRESSION_null }) );
+	if (err)
+	  return err;
+	handshake_state = STATE_server_wait_for_client;
+	
+	break;
+      }
+     }
+     break;
+   }
+  case STATE_server_wait_for_finish:
+    switch(type)
+    {
+    default:
+      send_packet(Alert(ALERT_fatal, ALERT_unexpected_message,
+			"SSL.session->handle_handshake: unexpected message\n",
+			backtrace()));
+      return -1;
+    case HANDSHAKE_finished:
+     {
+       string my_digest = hash_messages("CLNT");
+       string digest;
+       if (catch {
+	 digest = input->get_fix_string(36);
+       } || !input->is_empty())
+       {
+	 send_packet(Alert(ALERT_fatal, ALERT_unexpected_message));
+	 return -1;
+       }
+       if (my_digest != digest)
+       {
+	 send_packet(Alert(ALERT_fatal, ALERT_unexpected_message));
+	 return -1;
+       }
+       handshake_messages += raw; /* Second hash includes this message,
+				   * the first doesn't */
+       /* Handshake complete */
+       
+       if (!reuse)
+       {
+	 send_packet(change_cipher_packet());
+	 send_packet(finished_packet("SRVR"));
+	 expect_change_cipher = 1;
+	 context->record_session(session); /* Cache this session */
+       }
+       handshake_state = STATE_server_wait_for_hello;
+
+       return 1;
+     }   
+    }
+    break;
+  case STATE_server_wait_for_client:
+    handshake_messages += raw;
+    switch(type)
+    {
+    default:
+      send_packet(Alert(ALERT_fatal, ALERT_unexpected_message,
+			"SSL.session->handle_handshake: unexpected message\n",
+			backtrace()));
+      return -1;
+    case HANDSHAKE_client_key_exchange:
+//      werror("client_key_exchange\n");
+      if (certificate_state == CERT_requested)
+      { /* Certificate should be sent before key exchange message */
+	send_packet(Alert(ALERT_fatal, ALERT_unexpected_message,
+			  "SSL.session->handle_handshake: unexpected message\n",
+			  backtrace()));
+	return -1;
+      }
+      if (!(session->master_secret = server_derive_master_secret(data)))
+      {
+	send_packet(Alert(ALERT_fatal, ALERT_unexpected_message,
+			  "SSL.session->handle_handshake: unexpected message\n",
+			  backtrace()));
+	return -1;
+      }
+      array res = session->new_server_states(other_random, my_random);
+      pending_read_state = res[0];
+      pending_write_state = res[1];
+
+//      werror(sprintf("certificate_state: %d\n", certificate_state));
+      if (certificate_state != CERT_recieved)
+      {
+	handshake_state = STATE_server_wait_for_finish;
+	expect_change_cipher = 1;
+      }
+      else
+	handshake_state = STATE_server_wait_for_verify;
+      
+      break;
+    case HANDSHAKE_certificate:
+     {
+       if (certificate_state == CERT_requested)
+       {
+	 send_packet(Alert(ALERT_fatal, ALERT_unexpected_message,
+			   "SSL.session->handle_handshake: unexpected message\n",
+			   backtrace()));
+	 return -1;
+       }
+       if (catch {
+	 session->client_certificate = input->get_var_string(3);
+       } || !input->is_empty())
+       {
+	 send_packet(Alert(ALERT_fatal, ALERT_unexpected_message,
+			   "SSL.session->handle_handshake: unexpected message\n",
+			   backtrace()));
+	 return -1;
+       }	
+       certificate_state = CERT_recieved;
+       break;
+     }
+    }
+    break;
+  case STATE_server_wait_for_verify:
+    handshake_messages += raw;
+    switch(type)
+    {
+    default:
+      send_packet(Alert(ALERT_fatal, ALERT_unexpected_message,
+			"SSL.session->handle_handshake: unexpected message\n",
+			backtrace()));
+      return -1;
+    case HANDSHAKE_certificate_verify:
+      session->client_challenge =
+	mac_md5(session->master_secret)->hash_master(handshake_messages) +
+	mac_sha(session->master_secret)->hash_master(handshake_messages);
+      session->client_signature = data;
+      handshake_messages += raw;
+      handshake_state = STATE_server_wait_for_finish;
+      expect_change_cipher = 1;
+      break;
+    }
+    break;
+
+  case STATE_client_wait_for_hello:
+   {
+   }
+  case STATE_client_wait_for_server:
+   {
+   }
+  case STATE_client_wait_for_finish:
+   {
+   }
+  }
+//  werror(sprintf("SSL.handshake: messages = '%s'\n", handshake_messages));
+  return 0;
+}
+
+void create(int is_server)
+{
+  if (is_server)
+    handshake_state = STATE_server_wait_for_hello;
+  else
+    throw( ({ "SSL.handshake->create: client handshake not implemented\n",
+		backtrace() }) );
+}
diff --git a/lib/modules/SSL.pmod/https.pike b/lib/modules/SSL.pmod/https.pike
new file mode 100644
index 0000000000..1b73024001
--- /dev/null
+++ b/lib/modules/SSL.pmod/https.pike
@@ -0,0 +1,177 @@
+/* test_server.pike
+ *
+ * echoes packets over the SSL-packet layer
+ */
+
+#define PORT 25678
+
+import Stdio;
+
+inherit "sslport";
+
+string my_certificate = MIME.decode_base64(
+  "MIIBxDCCAW4CAQAwDQYJKoZIhvcNAQEEBQAwbTELMAkGA1UEBhMCREUxEzARBgNV\n"
+  "BAgTClRodWVyaW5nZW4xEDAOBgNVBAcTB0lsbWVuYXUxEzARBgNVBAoTClRVIEls\n"
+  "bWVuYXUxDDAKBgNVBAsTA1BNSTEUMBIGA1UEAxMLZGVtbyBzZXJ2ZXIwHhcNOTYw\n"
+  "NDMwMDUzNjU4WhcNOTYwNTMwMDUzNjU5WjBtMQswCQYDVQQGEwJERTETMBEGA1UE\n"
+  "CBMKVGh1ZXJpbmdlbjEQMA4GA1UEBxMHSWxtZW5hdTETMBEGA1UEChMKVFUgSWxt\n"
+  "ZW5hdTEMMAoGA1UECxMDUE1JMRQwEgYDVQQDEwtkZW1vIHNlcnZlcjBcMA0GCSqG\n"
+  "SIb3DQEBAQUAA0sAMEgCQQDBB6T7bGJhRhRSpDESxk6FKh3iKKrpn4KcDtFM0W6s\n"
+  "16QSPz6J0Z2a00lDxudwhJfQFkarJ2w44Gdl/8b+de37AgMBAAEwDQYJKoZIhvcN\n"
+  "AQEEBQADQQB5O9VOLqt28vjLBuSP1De92uAiLURwg41idH8qXxmylD39UE/YtHnf\n"
+  "bC6QS0pqetnZpQj1yEsjRTeVfuRfANGw\n");
+
+string my_key = MIME.decode_base64(
+  "MIIBOwIBAAJBAMEHpPtsYmFGFFKkMRLGToUqHeIoqumfgpwO0UzRbqzXpBI/PonR\n"
+  "nZrTSUPG53CEl9AWRqsnbDjgZ2X/xv517fsCAwEAAQJBALzUbJmkQm1kL9dUVclH\n"
+  "A2MTe15VaDTY3N0rRaZ/LmSXb3laiOgBnrFBCz+VRIi88go3wQ3PKLD8eQ5to+SB\n"
+  "oWECIQDrmq//unoW1+/+D3JQMGC1KT4HJprhfxBsEoNrmyIhSwIhANG9c0bdpJse\n"
+  "VJA0y6nxLeB9pyoGWNZrAB4636jTOigRAiBhLQlAqhJnT6N+H7LfnkSVFDCwVFz3\n"
+  "eygz2yL3hCH8pwIhAKE6vEHuodmoYCMWorT5tGWM0hLpHCN/z3Btm38BGQSxAiAz\n"
+  "jwsOclu4b+H8zopfzpAaoB8xMcbs0heN+GNNI0h/dQ==\n");
+
+class conn {
+  import Stdio;
+
+  object sslfile;
+
+  string message = "<html><head><title>SSL-3 server</title></head>\n"
+  "<body><h1>This is a minimal SSL-3 http server</h1>\n"
+  "<hr><it>/nisse</it></body></html>\n";
+  int index = 0;
+
+  void do_write()
+  {
+    if (index < strlen(message))
+    {
+      int written = sslfile->write(message[index..]);
+      if (written > 0)
+	index += written;
+      else
+	sslfile->close();
+    }
+    if (index == strlen(message))
+      sslfile->close();
+  }
+  
+  void read_callback(mixed id, string data)
+  {
+    werror("Recieved: '" + data + "'\n");
+    do_write();
+  }
+
+  void write_callback(mixed id)
+  {
+    do_write();
+  }
+
+  void create(object f)
+  {
+    sslfile = f;
+    sslfile->set_nonblocking(read_callback, write_callback, 0);
+  }
+}
+
+class no_random {
+  object rc4 = Crypto.rc4();
+  
+  void create(string|void secret)
+  {
+    if (!secret)
+      secret = sprintf("Foo!%4c", time());
+    object sha = Crypto.sha();
+    sha->update(secret);
+    rc4->set_encrypt_key(sha->digest());
+  }
+
+  string read(int size)
+  {
+    return rc4->crypt(replace(allocate(size), 0, "\021") * "");
+  }
+}
+
+/* ad-hoc asn.1-decoder */
+
+class ber_decode {
+  inherit "struct";
+
+  array get_asn1()
+  {
+    int tag = get_int(1);
+    int len;
+    string contents;
+    
+    werror(sprintf("decoding tag %x\n", tag));
+    if ( (tag & 0x1f) == 0x1f)
+      throw( ({ "high tag numbers is not supported\n", backtrace() }) );
+    int len = get_int(1);
+    if (len & 0x80)
+      len = get_int(len & 0x7f);
+    
+    werror(sprintf("len : %d\n", len));
+
+    contents = get_fix_string(len);
+    werror(sprintf("contents: %O\n", contents));
+    if (tag & 0x20)
+    {
+      object seq = object_program(this_object())(contents);
+      array res = ({ });
+      while(! seq->is_empty())
+      {
+	array elem = seq->get_asn1();
+	// werror(sprintf("elem: %O\n", elem));
+	res += ({ elem });
+      }
+      return ({ tag, res });
+    }
+    else
+      return ({ tag, contents });
+  }
+}
+
+/* PKCS#1 Private key structure:
+
+RSAPrivateKey ::= SEQUENCE {
+  version Version,
+  modulus INTEGER, -- n
+  publicExponent INTEGER, -- e
+  privateExponent INTEGER, -- d
+  prime1 INTEGER, -- p
+  prime2 INTEGER, -- q
+  exponent1 INTEGER, -- d mod (p-1)
+  exponent2 INTEGER, -- d mod (q-1)
+  coefficient INTEGER -- (inverse of q) mod p }
+
+Version ::= INTEGER
+
+*/
+
+void my_accept_callback(object f)
+{
+  werror("Accept!\n");
+  conn(f->accept());
+}
+
+int main()
+{
+  werror(sprintf("Cert: '%s'\n", Crypto.string_to_hex(my_certificate)));
+  werror(sprintf("Key:  '%s'\n", Crypto.string_to_hex(my_key)));
+//  werror(sprintf("Decoded cert: %O\n", ber_decode(my_certificate)->get_asn1()));
+  array key = ber_decode(my_key)->get_asn1()[1];
+  werror(sprintf("Decoded key: %O\n", key));
+  object n = Gmp.mpz(key[1][1], 256);
+  object e = Gmp.mpz(key[2][1], 256);
+  object d = Gmp.mpz(key[3][1], 256);
+  object p = Gmp.mpz(key[4][1], 256);
+  object q = Gmp.mpz(key[5][1], 256);
+
+  werror(sprintf("n =  %s\np =  %s\nq =  %s\npq = %s\n",
+		 n->digits(), p->digits(), q->digits(), (p*q)->digits()));
+  rsa = Crypto.rsa();
+  rsa->set_public_key(n, e);
+  rsa->set_private_key(d);
+  certificates = ({ my_certificate });
+  random = no_random()->read;
+  werror("Starting\n");
+  return bind(PORT, my_accept_callback) ? -17 : 17;
+}
diff --git a/lib/modules/SSL.pmod/packet.pike b/lib/modules/SSL.pmod/packet.pike
new file mode 100644
index 0000000000..0fcbdaa899
--- /dev/null
+++ b/lib/modules/SSL.pmod/packet.pike
@@ -0,0 +1,115 @@
+/* packet.pike
+ *
+ * SSL Record Layer */
+
+inherit "constants";
+
+constant SUPPORT_V2 = 1;
+
+int content_type;
+array(int) protocol_version;
+string fragment;  /* At most 2^14 */
+
+constant HEADER_SIZE = 5;
+
+private string buffer;
+private int needed_chars;
+
+int marginal_size;
+
+// constant Alert = (program) "alert";
+#define Alert ((program) "alert")
+
+void create(void|int extra)
+{
+  marginal_size = extra;
+  buffer = "";
+  needed_chars = HEADER_SIZE;
+  protocol_version = ({ 3, 0 });
+}
+
+object check_size(int|void extra)
+{
+  marginal_size = extra;
+  return (strlen(fragment) > (PACKET_MAX_SIZE + extra))
+    ? Alert(ALERT_fatal, ALERT_unexpected_message) : 0;
+}
+
+/* Called with data read from network.
+ *
+ * Return string of leftover data if packet is complete, otherwise 0.
+ * If there's an error, an alert object is returned.
+ */
+
+object|string recv(string data)
+{
+  buffer += data;
+  while (strlen(buffer) >= needed_chars)
+  {
+//    werror(sprintf("SSL.packet->recv: needed = %d, avail = %d\n",
+//		     needed_chars, strlen(buffer)));
+    if (needed_chars == HEADER_SIZE)
+    {
+      content_type = buffer[0];
+      int length;
+      if (! PACKET_types[content_type] )
+      {
+	if (SUPPORT_V2)
+	{
+//	  werror(sprintf("SSL.packet: Recieving SSL2 packet '%s'\n", buffer[..4]));
+
+	  content_type = PACKET_V2;
+	  if ( (!(buffer[0] & 0x80)) /* Support only short SSL2 headers */
+	       || (buffer[2] != 1))
+	    return Alert(ALERT_fatal, ALERT_unexpected_message);
+	  length = ((buffer[0] & 0x7f) << 8 | buffer[1]
+		    - 3);
+//	  werror(sprintf("SSL2 length = %d\n", length));
+	  protocol_version = values(buffer[3..4]);
+	}
+	else
+	  return Alert(ALERT_fatal, ALERT_unexpected_message,
+		       "SSL.packet->recv: invalid type\n", backtrace());
+      } else {
+	protocol_version = values(buffer[1..2]);
+	sscanf(buffer[3..4], "%2c", length);
+	if ( (length <= 0) || (length > (PACKET_MAX_SIZE + marginal_size)))
+	  return Alert(ALERT_fatal, ALERT_unexpected_message);
+      }
+      if (protocol_version[0] != 3)
+	return Alert(ALERT_fatal, ALERT_unexpected_message,
+		     sprintf("SSL.packet->send: Version %d is not supported\n",
+			     protocol_version[0]), backtrace());
+      if (protocol_version[1] > 0)
+	werror(sprintf("SSL.packet->recv: recieved version %d.%d packet\n",
+		       @ protocol_version));
+      
+      needed_chars += length;
+    } else {
+      if (content_type == PACKET_V2)
+	fragment = buffer[2 .. needed_chars - 1];
+      else
+	fragment = buffer[HEADER_SIZE .. needed_chars - 1];
+      return buffer[needed_chars ..];
+    }
+  }
+  return 0;
+}
+
+string send()
+{
+  if (! PACKET_types[content_type] )
+    throw( ({ "SSL.packet->send: invalid type", backtrace() }) );
+  
+  if (protocol_version[0] != 3)
+    throw( ({ sprintf("SSL.packet->send: Version %d is not supported\n",
+		      protocol_version[0]), backtrace() }) );
+  if (protocol_version[1] > 0)
+    werror(sprintf("SSL.packet->send: recieved version %d.%d packet\n",
+		   @ protocol_version));
+  if (strlen(fragment) > (PACKET_MAX_SIZE + marginal_size))
+    throw( ({ "SSL.packet->send: maximum packet size exceeded\n",
+		backtrace() }) );
+  return sprintf("%c%c%c%2c%s", content_type, @protocol_version,
+		 strlen(fragment), fragment);
+}
diff --git a/lib/modules/SSL.pmod/queue.pike b/lib/modules/SSL.pmod/queue.pike
new file mode 100644
index 0000000000..9e0ba58e97
--- /dev/null
+++ b/lib/modules/SSL.pmod/queue.pike
@@ -0,0 +1,50 @@
+/* queue.pike
+ *
+ * A FIFO queue. Used by connection*
+ */
+
+#define QUEUE_SIZE 100
+
+array l;
+int head;
+int tail;
+
+void create()
+{
+  l = allocate(QUEUE_SIZE);
+  head = tail = 0;
+}
+
+void put(mixed item)
+{
+  if (head == sizeof(l))
+  {
+    l = l[tail ..];
+    head -= tail;
+    tail = 0;
+    l += allocate(sizeof(l) + QUEUE_SIZE);
+  }
+  l[head++] = item;
+//  werror(sprintf("Queue->put: %O\n", l[tail..head-1]));
+}
+
+mixed get()
+{
+//  werror(sprintf("Queue->get: %O\n", l[tail..head-1]));
+  mixed res;
+  if (tail == head)
+    return 0;
+  res = l[tail];
+  l[tail++] = 0;
+  return res;
+}
+
+mixed peek()
+{
+  return (tail < head) && l[tail];
+}
+
+void flush()
+{
+  create();
+}
diff --git a/lib/modules/SSL.pmod/server.pike b/lib/modules/SSL.pmod/server.pike
new file mode 100644
index 0000000000..806d3e77f9
--- /dev/null
+++ b/lib/modules/SSL.pmod/server.pike
@@ -0,0 +1,4 @@
+/* server.pike
+ *
+ */
+
diff --git a/lib/modules/SSL.pmod/session.pike b/lib/modules/SSL.pmod/session.pike
new file mode 100644
index 0000000000..a7be305eac
--- /dev/null
+++ b/lib/modules/SSL.pmod/session.pike
@@ -0,0 +1,131 @@
+/* session.pike
+ *
+ */
+
+inherit "cipher" : cipher;
+
+// object server;
+
+string identity; /* Identifies the session to the server */
+int compression_algorithm;
+int cipher_suite;
+object cipher_spec;
+int ke_method;
+string master_secret; /* 48 byte secret shared between client and server */
+
+constant Struct = (program) "struct";
+constant State = (program) "state";
+
+void set_cipher_suite(int suite)
+{
+  array res = cipher::lookup(suite);
+  cipher_suite = suite;
+  ke_method = res[0];
+  cipher_spec = res[1];
+}
+
+void set_compression_method(int compr)
+{
+  if (compr != COMPRESSION_null)
+    throw( ({ "SSL.session->set_compression_method: Method not supported\n",
+		backtrace() }) );
+  compression_algorithm = compr;
+}
+
+string generate_key_block(string client_random, string server_random)
+{
+  int required = 2 * (cipher_spec->key_material +
+		       cipher_spec->hash_size +
+		       cipher_spec->iv_size);
+  object sha = mac_sha();
+  object md5 = mac_md5();
+  int i = 0;
+  string key = "";
+  while (strlen(key) < required)
+  {
+    i++;
+    string cookie = replace(allocate(i), 0, sprintf("%c", 64+i)) * "";
+//    werror(sprintf("cookie '%s'\n", cookie));
+    key += md5->hash_raw(master_secret +
+			 sha->hash_raw(cookie + master_secret +
+				       server_random + client_random));
+  }
+//  werror(sprintf("key_block: '%s'\n", key));
+  return key;
+}
+
+array new_server_states(string client_random, string server_random)
+{
+  object key_data = Struct(generate_key_block(client_random, server_random));
+  object write_state = State(this_object());
+  object read_state = State(this_object());
+
+  write(sprintf("client_random: '%s'\nserver_random: '%s'\n",
+		client_random, server_random));
+  read_state->mac = cipher_spec->
+    mac_algorithm(key_data->get_fix_string(cipher_spec->hash_size));
+  write_state->mac = cipher_spec->
+    mac_algorithm(key_data->get_fix_string(cipher_spec->hash_size));
+  read_state->crypt = cipher_spec->bulk_cipher_algorithm();
+  read_state->crypt->
+    set_decrypt_key(key_data->get_fix_string(cipher_spec->key_material));
+
+  write_state->crypt = cipher_spec->bulk_cipher_algorithm();
+  write_state->crypt->
+    set_encrypt_key(key_data->get_fix_string(cipher_spec->key_material));
+
+  if (cipher_spec->iv_size)
+  {
+    read_state->crypt->
+      set_iv(key_data->get_fix_string(cipher_spec->iv_size));
+    write_state->crypt->
+      set_iv(key_data->get_fix_string(cipher_spec->iv_size));
+  }
+  return ({ read_state, write_state });
+}
+
+array new_client_states(string client_random, string server_random)
+{
+  object key_data = Struct(generate_key_block(client_random, server_random));
+  object write_state = State(this_object());
+  object read_state = State(this_object());
+
+  write_state->mac = cipher_spec->
+    mac_algorithm(key_data->get_fix_string(cipher_spec->hash_size));
+  read_state->mac = cipher_spec->
+    mac_algorithm(key_data->get_fix_string(cipher_spec->hash_size));
+
+  write_state->crypt = cipher_spec->bulk_cipher_algorithm();
+  write_state->crypt->
+    set_encrypt_key(key_data->get_fix_string(cipher_spec->key_material));
+
+  read_state->crypt = cipher_spec->bulk_cipher_algorithm();
+  read_state->crypt->
+    set_decrypt_key(key_data->get_fix_string(cipher_spec->key_material));
+
+  if (cipher_spec->iv_size)
+  {
+    write_state->crypt->
+      set_iv(key_data->get_fix_string(cipher_spec->iv_size));
+    read_state->crypt->
+      set_iv(key_data->get_fix_string(cipher_spec->iv_size));
+  }
+  return ({ read_state, write_state });
+}
+    
+#if 0
+void create(int is_s, int|void auth)
+{
+  is_server = is_s;
+  if (is_server)
+  {
+    handshake_state = STATE_SERVER_WAIT_FOR_HELLO;
+    auth_type = auth || AUTH_none;
+  }
+  else
+  {
+    handshake_state = STATE_CLIENT_WAIT_FOR_HELLO;
+    auth_type = auth || AUTH_require;
+  }
+}
+#endif
diff --git a/lib/modules/SSL.pmod/sslport.pike b/lib/modules/SSL.pmod/sslport.pike
new file mode 100644
index 0000000000..47cc3303c8
--- /dev/null
+++ b/lib/modules/SSL.pmod/sslport.pike
@@ -0,0 +1,234 @@
+/* sslport.pike
+ *
+ */
+
+inherit Stdio.Port : socket;
+inherit "context";
+
+function(object:void) accept_callback;
+
+class sslfile
+{
+  inherit Stdio.File : socket;
+  inherit "connection" : connection;
+
+  object context;
+  
+  string read_buffer; /* Data that is recieved before there is any
+		       * read_callback */
+  string write_buffer; /* Data to be written */
+  function(mixed,string:void) read_callback;
+  function(mixed:void) write_callback;
+  function(mixed:void) close_callback;
+
+  int connected; /* 1 if the connect callback has been called */
+
+  private void die(int status)
+  {
+    if (status < 0)
+    {
+      /* Other end closed without sending a close_notify alert */
+      context->purge_session(this_object());
+      werror("SSL.sslfile: Killed\n");
+    }
+    if (close_callback)
+      close_callback(socket::query_id());
+  }
+  
+  /* Return 0 if the connection is still alive,
+   * 1 if it was closed politely, and -1 if it died unexpectedly
+   */
+  private int try_write()
+  {
+    int|string data = to_write();
+//    werror(sprintf("sslport->try_write: %O\n", data));
+    if (stringp(data))
+    {
+      write_buffer += data;
+      if (strlen(write_buffer))
+      {
+	int written = socket::write(write_buffer);
+	if (written > 0)
+	  write_buffer = write_buffer[written..];
+	else
+	  werror("SSL.sslfile->try_write: write failed\n");
+      }
+      return 0;
+    }
+    socket::close();
+    return data;
+  }
+
+  void close()
+  {
+    send_packet(Alert(ALERT_warning, ALERT_close_notify));
+    try_write();
+    read_callback = 0;
+    write_callback = 0;
+    close_callback = 0;
+  }
+
+  int write(string s)
+  {
+    object packet;
+    int res;
+    while(strlen(s))
+    {
+      packet = Packet();
+      packet->content_type = PACKET_application_data;
+      packet->fragment = s[..PACKET_MAX_SIZE-1];
+      send_packet(packet);
+      s = s[PACKET_MAX_SIZE..];
+    }
+    (res = try_write()) && die(res);
+  }
+
+  string read(mixed ...args)
+  {
+    throw( ({ "SSL->sslfile: read() is not supported.\n", backtrace() }) );
+  }
+  
+  private void ssl_read_callback(mixed id, string s)
+  {
+    werror(sprintf("sslfile->ssl_read_callback\n"));
+    string|int data = got_data(s);
+    if (stringp(data))
+    {
+      werror(sprintf("sslfile: application_data: '%s'\n", data));
+      if (strlen(data))
+      {
+	read_buffer += data;
+	if (!connected)
+	{
+	  connected = 1;
+	  context->accept_callback(this_object());
+	}
+	if (read_callback && strlen(read_buffer))
+	{
+	  read_callback(id, read_buffer + data);
+	  read_buffer = "";
+	}
+      }
+    }
+    else
+    {
+      if (data < 0)
+	/* Fatal error, remove from session cache */
+	context->purge_session(this_object());
+    }
+    try_write() || (close_callback && close_callback());
+  }
+  
+  private void ssl_write_callback(mixed id)
+  {
+    werror("SSL.sslport: ssl_write_callback\n");
+    int res;
+    if ( !(res = try_write()) && !strlen(write_buffer)
+	 && handshake_finished && write_callback)
+    {
+      write_callback(id);
+      res = try_write();
+    }
+    if (res)
+      die(res);
+  }
+
+  private void ssl_close_callback(mixed id)
+  {
+    werror("SSL.sslport: ssl_close_callback\n");
+    socket::close();
+    die(-1);
+  }
+  
+  void set_read_callback(function(mixed,string:void) r)
+  {
+    read_callback = r;
+  }
+
+  void set_write_callback(function(mixed:void) w)
+  {
+    write_callback = w;
+  }
+
+  void set_close_callback(function(mixed:void) c)
+  {
+    close_callback = c;
+  }
+  
+  void set_nonblocking(function ...args)
+  {
+    switch (sizeof(args))
+    {
+    case 0:
+      break;
+    case 3:
+      set_read_callback(args[0]);
+      set_write_callback(args[1]);
+      set_close_callback(args[2]);
+      break;
+    default:
+      throw( ({ "SSL.sslfile->set_blocking: Wrong number of arguments\n",
+		  backtrace() }) );
+    }
+  }
+
+  void set_blocking()
+  {
+    throw( ({ "SSL.sslfile->set_blocking: Not supported\n",
+		backtrace() }) );
+  }
+
+  object accept()
+  {
+    /* Dummy method, for compatibility with Stdio.Port */
+    return this_object();
+  }
+  
+  void create(object f, object c)
+  {
+    context = c;
+    read_buffer = write_buffer = "";
+    socket::assign(f);
+    socket::set_nonblocking(ssl_read_callback, ssl_write_callback, ssl_close_callback);
+    connection::create(1);
+  }
+}
+
+void ssl_callback(mixed id)
+{
+  object f = id->socket_accept();
+  if (f)
+    sslfile(f, this_object());
+}
+
+void set_id(mixed id)
+{
+  throw( ({ "SSL.sslport->set_id: Not supported\n", backtrace() }) );
+}
+
+mixed query_id()
+{
+  throw( ({ "SSL.sslport->query_id: Not supported\n", backtrace() }) );
+}
+
+int bind(int port, function callback, string|void ip)
+{
+  accept_callback = callback;
+  return socket::bind(port, ssl_callback, ip);
+}
+
+int listen_fd(int fd, function callback)
+{
+  accept_callback = callback;
+  return socket::listen_fd(fd, callback);
+}
+
+object socket_accept()
+{
+  return socket::accept();
+}
+
+int accept()
+{
+  throw( ({ "SSL.sslport->accept: Not supported\n", backtrace() }) );
+}
diff --git a/lib/modules/SSL.pmod/state.pike b/lib/modules/SSL.pmod/state.pike
new file mode 100644
index 0000000000..13e19c194a
--- /dev/null
+++ b/lib/modules/SSL.pmod/state.pike
@@ -0,0 +1,94 @@
+/* state.pike
+ *
+ */
+
+inherit "constants";
+
+object session;
+
+// string my_random;
+// string other_random;  
+object mac;
+object crypt;
+object compress;
+
+object(Gmp.mpz) seq_num;    /* Bignum, values 0, .. 2^64-1 are valid */
+
+constant Alert = (program) "alert";
+
+void create(object s)
+{
+  session = s;
+  seq_num = Gmp.mpz(0);
+}
+
+
+/* Destructively decrypt a packet. Returns an Alert object if
+ * there was an error, otherwise 0. */
+object decrypt_packet(object packet)
+{
+  if (crypt)
+  {
+    string msg;
+//    werror("Trying decrypt..\n");
+    msg = crypt->crypt(packet->fragment); 
+    if (! msg)
+      return Alert(ALERT_fatal, ALERT_unexpected_message);
+    if (session->cipher_spec->cipher_type == CIPHER_block)
+      if (catch { msg = crypt->unpad(msg); })
+	return Alert(ALERT_fatal, ALERT_unexpected_message);
+    packet->fragment = msg;
+  }
+
+//  werror(sprintf("Decrypted_packet '%s'\n", packet->fragment));
+
+  if (mac)
+  {
+//    werror("Trying mac verification...\n");
+    int length = strlen(packet->fragment) - session->cipher_spec->hash_size;
+    string digest = packet->fragment[length ..];
+    packet->fragment = packet->fragment[.. length - 1];
+    
+    if (digest != mac->hash(packet, seq_num))
+      return Alert(ALERT_fatal, ALERT_bad_record_mac);
+    seq_num += 1;
+  }
+
+  if (compress)
+  {
+//    werror("Trying decompression...\n");
+    string msg;
+    msg = compress(packet->fragment);
+    if (!msg)
+      return Alert(ALERT_fatal, ALERT_unexpected_message);
+    packet->fragment = msg;
+  }
+  return packet->check_size() || packet;
+}
+
+object encrypt_packet(object packet)
+{
+  string digest;
+  
+  if (compress)
+  {
+    packet->fragment = compress(packet->fragment);
+  }
+
+  if (mac)
+    digest = mac->hash(packet, seq_num);
+  else
+    digest = "";
+  seq_num += 1;
+  
+  if (crypt)
+  {
+    packet->fragment = crypt->crypt(packet->fragment + digest);
+    if (session->cipher_spec->cipher_type == CIPHER_block)
+      packet->fragment += crypt->pad();
+  }
+  else
+    packet->fragment += digest;
+
+  return packet->check_size(2048) || packet;
+}
diff --git a/lib/modules/SSL.pmod/struct.pike b/lib/modules/SSL.pmod/struct.pike
new file mode 100644
index 0000000000..e6062ddbe2
--- /dev/null
+++ b/lib/modules/SSL.pmod/struct.pike
@@ -0,0 +1,160 @@
+/* struct.pike
+ *
+ * Read and write structures from strings.
+ */
+
+string buffer;
+int index;
+
+void create(void|string s)
+{
+  buffer = s || "";
+  index = 0;
+}
+
+void add_data(string s)
+{
+  buffer += s;
+}
+
+string pop_data()
+{
+  string res = buffer;
+  create();
+  return res;
+}
+  
+void put_int(int i, int len)
+{
+  add_data(sprintf("%*c", len, i));
+}
+
+void put_var_string(string s, int len)
+{
+  put_int(strlen(s), len);
+  add_data(s);
+}
+
+void put_fix_string(string s)
+{
+  add_data(s);
+}
+
+void put_fix_array(array(int) data, int item_size)
+{
+  foreach(data, int i)
+    put_int(i, item_size);
+}
+
+void put_var_array(array(int) data, int item_size, int len)
+{
+  put_int(sizeof(data), len);
+  put_fix_array(data, item_size);
+}
+
+mixed get_int(int len)
+{
+  mixed i;
+  if ( (strlen(buffer) - index) < len)
+    throw( ({ "SSL.struct->get_int: no data\n", backtrace() }) );
+  if (len <= 3)
+  {
+    sscanf(buffer, "%*" + (string) index +"s%" + (string) len + "c", i);
+  }
+  else
+    i = Gmp.mpz(buffer[index .. index+len-1], 256);
+  index += len;
+  return i;
+}
+
+string get_fix_string(int len)
+{
+  string res;
+  
+  if ((strlen(buffer) - index) < len)
+    throw( ({ "SSL.struct->get_fix_string: no data\n", backtrace() }) );
+  res = buffer[index .. index + len - 1];
+  index += len;
+  return res;
+}
+
+string get_var_string(int len)
+{
+  return get_fix_string(get_int(len));
+}
+
+array(mixed) get_fix_array(int item_size, int size)
+{
+  array(mixed) res = allocate(size);
+  for(int i = 0; i<size; i++)
+    res[i] = get_int(item_size);
+  return res;
+}
+
+array(mixed) get_var_array(int item_size, int len)
+{
+  return get_fix_array(item_size, get_int(len));
+}
+
+int is_empty()
+{
+  return (index == strlen(buffer));
+}
+	       
+#if 0
+
+constant FIELD_int = 1;
+constant FIELD_string = 2;
+
+class field
+{
+  string name;  /* Name of field */
+  int type;
+  int len;      /* For integers: length,
+		 * for strings: index to the field that holds the length */
+}
+
+
+
+array(object(field)) description;
+array(function) conversions;
+
+class parser
+{
+  string buffer = "";
+  object info;
+  int field; /* Field being read */
+  object o; /* The object to fill in */
+  object me;
+
+  mapping(int:function(string, object:void)) conversions;
+
+  create(object i)
+  {
+    info = i;
+    o = i->prog();
+    me = this_object();
+  }
+  
+  object|string recv(string data)
+  {
+    buffer += data;
+    while(strlen(buffer) >= info->needed_chars[field])
+    {
+      object err;
+      if (err = info->conversions[field](me))
+	return err;
+      field++;
+      if (field >= sizeof(info->description))
+	return buffer[info->needed_chars[field-1]..];
+    }
+    return 0;
+  }
+}
+
+array compile()
+{
+  conversions = 
+
+
+#endif
-- 
GitLab