Weitgehender Neubau auf intermud-2.5.
Benutzt moderne LDMud-3.5-Sprachmittel wie structs.
diff --git a/secure/inetd.c b/secure/inetd.c
index d015602..a139de6 100644
--- a/secure/inetd.c
+++ b/secure/inetd.c
@@ -1,808 +1,51 @@
-/*
- * UDP port handling code. Version 0.7a
- * Written by Nostradamus for Zebedee.
- * Developed from an original concept by Alvin@Sushi.
- */
+#pragma strict_types, save_types, rtt_checks, pedantic
+#pragma no_clone, no_shadow, no_inherit
 
-#pragma strict_types
-#pragma no_clone
-#pragma no_shadow
-#pragma no_inherit
-#pragma verbose_errors
-//#pragma pedantic
-#pragma no_range_check
-#pragma no_warn_deprecated
+// inherit the basic inetd
+inherit __DIR__/_inetd.c
 
-/*
 #include <living/comm.h>
-#define ZDEBUG(x)        if (find_player("zesstra"))\
-            find_player("zesstra")->ReceiveMsg(x, \
-                MT_DEBUG|MSG_DONT_STORE,0,"IM: ",this_object())
-*/
-#define ZDEBUG(x)
 
-#include <udp.h>
-
-/* --- Configurable definitions. --- */
-
-/* CD muds will probably need these include file. */
-/* #include <std.h> */
-/* #include "/config/sys/local.h" */
-
-/* Public commands which will be accessible to any unlisted muds.
- * PING, QUERY and REPLY are included by default. */
-#define COMMANDS \
-({ "channel", "finger", "tell", "who", "mail", "www", "htmlwho", "locate" })
-
-//({ "channel", "finger", "ftp", "locate", "man", "tell", "who" })
-
-/* Define this to the object that receives incoming packets and passes
- * them to the inetd. Undefine for no _receive_udp() security.
- * NOTE: The string must be of the format that object_name() returns. */
-#define UDP_MASTER                __MASTER_OBJECT__
-
-/* How to set the euid for this object if using native mode.
- * Ensure that it can read the INETD_HOSTS file. */
-#define SET_EUID                seteuid(getuid())
-
-/* Define this if you are running another intermud package concurrently. */
-/* #define RECEIVE_UDP_COMPAT(sender, packet) \
-                UDP_MANAGER->incoming_udp(sender, packet)        // CD */
-
-/* Define this if you are running another intermud package concurrently and
- * have a compatability module for it. */
-/* #define SEND_UDP_COMPAT(mudname, data, expect_reply) \
-                "/secure/udp_compat"->_send_udp(mudname, data, expect_reply) */
-
-/* The maximum number of characters we can send in one packet.
- * You may need to reduce this, but 512 should be safe. */
-#define MAX_PACKET_LEN        1024
-
-/* You shouldn't need to change anything below. */
-
-#define USE_OLD_DELIMITER
-//#define DELIMITER_COMPAT
-#define USE_OLD_DATA_FORMAT
-
-#ifdef ZEBEDEE
-#include <defs.h>
-#endif
-
-#ifndef DATE
-#define DATE                ctime(time())[4..15]
-#endif
-
-/* --- End of Config. Do not alter anything below. --- */
-
-#define UNKNOWN                0
-#define UP                time()
-#define DOWN                (-time())
-
-#define NEW_DELIMITER        "\n"
-#ifdef USE_OLD_DELIMITER
-#define DELIMITER        "|"
-#else
-#define DELIMITER        "\n"
-#endif
-#define OLD_DELIMITER        "|"
-#define HOSTFILE_DEL        ":"
-#define HOSTFILE_DEL2        ","
-#define RETRY                "_RETRY"
-
-private nosave mapping hosts, pending_data, incoming_packets;
-private nosave mapping static_host_list;
-private nosave string *received_ids;
-private nosave int packet_id;
-
-void set_host_list();
-varargs string _send_udp(string mudname, mapping data, int expect_reply);
-
-#define DEBUG(msg)
-
-void my_create() {
-#ifndef COMPAT_FLAG
-    SET_EUID;
-#endif
-    packet_id = 0;
-    pending_data = ([ ]);
-    incoming_packets = ([ ]);
-    hosts = ([ ]);
-    received_ids = ({ });
-    set_host_list();
-    if (!this_player())
-        call_out("startup", 1, 0);
+// and implement mud-individual stuff that should be implemented as needed. If
+// the default is fine for your mud, keep it.
+protected void set_user()
+{
+  seteuid(getuid());
 }
 
-#ifdef CREATE_FUN
-CREATE_FUN() {
-#elif !defined(COMPAT_FLAG) || defined(ZEBEDEE)
-void create() {
-#else
-void reset(arg) {
-    if (arg)
-        return;
-#endif
-    my_create();
+protected void save_me()
+{
+  save_object(__DIR__"ARCH/inetd");
 }
 
-status check_system_field(mapping data, string field) {
-    return data[SYSTEM] && member(data[SYSTEM], field) != -1;
+protected int restore_me()
+{
+  return restore_object(__DIR__"ARCH/inetd");
 }
 
-mapping add_system_field(mapping data, string field) {
-    if (data[SYSTEM]) {
-        if (!check_system_field(data, field))
-            data[SYSTEM] += ({ field });
-    }
-    else
-        data[SYSTEM] = ({ field });
-    return data;
+protected void export_peer_list(mapping list)
+{
 }
 
-private mapping read_host_list(string file) {
-
-  mixed data = (read_file(file));
-  if (!data) {
-    log_file(INETD_LOG_FILE, "*Error in reading host file: "
-        + file +"\n\n");
-    return 0;
-  }
-
-  data = old_explode(data, "\n");
-  mapping new_hosts = m_allocate(sizeof(data),1);
-
-  foreach(string line: data) {
-    if (line == "" || line[0] == '#')
-      continue;
-    mixed fields = old_explode(line, HOSTFILE_DEL);
-    if (sizeof(fields) < 5) {
-      log_file(INETD_LOG_FILE, sprintf(
-            "*Parse error in hosts file: %s, Line: %s\n", file, line));
-      continue;
-    }
-
-    string name = lower_case(fields[HOST_NAME]);
-    if (member(new_hosts, name))
-      continue; // mud already in list
-
-    string *local_cmds = old_explode(fields[LOCAL_COMMANDS],HOSTFILE_DEL2);
-    if (member(local_cmds,"*") != -1)
-        local_cmds = local_cmds - ({ "*" }) + COMMANDS;
-    
-    new_hosts[name] = ({ capitalize(fields[HOST_NAME]),
-                     fields[HOST_IP],
-                     to_int(fields[HOST_UDP_PORT]),
-                     local_cmds,
-                     old_explode(fields[HOST_COMMANDS], HOSTFILE_DEL2),
-                     UNKNOWN
-                   });
-    /*
-     * Get existing host status from current active host lost as long as the
-     * IP and UDP ports are the same.
-     */
-    if (hosts[name] &&
-        hosts[name][HOST_IP] == new_hosts[name][HOST_IP] &&
-        hosts[name][HOST_UDP_PORT] == new_hosts[name][HOST_UDP_PORT])
-        new_hosts[name][HOST_STATUS] = hosts[name][HOST_STATUS];
-  }
-  return new_hosts;
+protected void debug_msg(string msg)
+{
+  object z=find_player("zesstra");
+  if (z)
+    z->ReceiveMsg(msg, MT_DEBUG, 0, "IM: ", this_object());
 }
 
-/*
- * Read the INETD_HOSTS file and set the "hosts" mapping from it.
- * Retain existing HOST_STATUS fields.
- * Reads INETD_HOSTSS and INETD_HOSTS.dump
- * Hosts from INETD_HOSTS are included in the static host list.
- */
-void set_host_list() {
-
-  // read the static host file and the last host file dump.
-  mapping static_hosts = read_host_list(HOST_FILE);
-  if (!static_hosts)
-      return; // retain the old list(s)
-
-  // remember the static hosts
-  static_host_list = m_reallocate(static_hosts,0);
-  
-  // read the last host file dump and add the static hosts. Then the static
-  // hosts have precedence over the ones from the dump.
-  hosts = (read_host_list(HOST_FILE".dump") || ([])) + static_hosts;
-
+public void receive(string host, string msg, int hostport)
+{
+  if (previous_object() == master())
+    process_fragment(host, msg, hostport);
 }
 
-mixed get_hosts() {return copy(hosts);}
-
-int  dump_hosts_list() {
-  
-  write_file(HOST_FILE+".dump", sprintf(
-    "#Hostlist dump from "MUDNAME", created %s\n", strftime()), 1);
-
-  foreach(string mudname, mixed tmp : hosts) {
-    // skip muds we did not hear from for 2 days.
-//    if (tmp[HOST_STATUS] + 172800 < time())
-//      continue;
-    write_file(HOST_FILE+".dump",
-        sprintf("%s:%s:%d:%s:%s\n",tmp[0],tmp[1],tmp[2],implode(tmp[3],","),
-          implode(tmp[4],",")));
-  }
-  return 1;
-}
-
-/*
- * Make a PING request to all muds in the "hosts" mapping to set
- * HOST_STATUS information.
- * But don't ping all muds at once, because that may overflow the callout
- * table during mud startup, when hundreds of objects make callouts.
- */
-void startup(string *muds) {
-
-    if (!pointerp(muds))
-      muds=m_indices(hosts);
-    if (!sizeof(muds))
-      return;
-    string *part;
-    if (sizeof(muds) > 9)
-      part=muds[0..9];
-    else
-      part=muds;
-    foreach(string mud: part) 
-      _send_udp(mud, ([ REQUEST: PING ]), 1);
-    muds -= part;
-    if (sizeof(muds))
-      call_out(#'startup, 4, muds);
-}
-
-/*
- * Remove a buffered packet from the "incoming_packets" mapping.
- */
-void remove_incoming(string id) {
-    m_delete(incoming_packets, id);
-}
-
-/*
- * Decode a string from a UDP packet.
- * Returns:   The actual value of the argument (either int or string)
- */
-mixed decode(string arg) {
-
-    if (sizeof(arg) && arg[0] == '$')
-        return arg[1..];
-#ifdef RESTRICTED_CASTS
-    if (to_string(to_int(arg)) == arg)
-        return to_int(arg);
-#else
-    if ((string)((int)arg) == arg)
-        return (int)arg;
-#endif
-    return arg;
-}
-
-/*
- * Decode a UDP packet.
- * Arguments: UDP packet as a string.
- * Returns:   The decoded information as a mapping, 1 for succes but no
- *            output (buffered packet), or 0 on error.
- */
-mixed decode_packet(string packet, string delimiter) {
-    string *data;
-    mapping ret;
-    string info, tmp;
-    mixed class;
-    int i, id, n;
-
-    /* If this packet has been split, handle buffering. */
-    if (packet[0..sizeof(PACKET)] == PACKET + ":") {
-        if (sscanf(packet, PACKET + ":%s:%d:%d/%d" + delimiter + "%s",
-        class, id, i, n, tmp) != 5)
-            return 0;
-        class = lower_case(class) + ":" + id;
-        if (pointerp(incoming_packets[class])) {
-            incoming_packets[class][i-1] = tmp;
-            if (member(incoming_packets[class], 0) == -1) {
-                ret =
-                decode_packet(implode(incoming_packets[class], ""), delimiter);
-                remove_incoming(class);
-                return ret;
-            }
-        } else {
-            incoming_packets[class] = allocate(n);
-            incoming_packets[class][i-1] = tmp;
-/* Is it possible to already have a timeout running here ?!? */
-            /* If no timeout is running then start one. */
-            if (!pending_data[class]) {
-                call_out("remove_incoming",
-                REPLY_TIME_OUT + REPLY_TIME_OUT * RETRIES, class);
-            } else {
-                DEBUG("\nINETD: *** Buffered packet Timeout already running! ***\n");
-            }
-        }
-        return 1;
-    }
-    ret = ([ ]);
-    for(i = 0, n = sizeof(data = old_explode(packet, delimiter)); i < n; i++) {
-        /* DATA fields can be denoted by a preceeding blank field. */
-        if (data[i] == "") {
-            tmp = DATA;
-            /* Test for illegal packet length (no DATA) */
-            if (++i >= n)
-                return 0;
-            info = data[i];
-        }
-        else if (sscanf(data[i], "%s:%s", tmp, info) != 2)
-            return 0;
-        switch((string)(class = decode(tmp))) {
-            case DATA:
-#ifdef USE_EXTRACT
-                return ret + ([ DATA: decode(implode(
-                    ({ info }) + extract(data, i+1), delimiter)) ]);
-#else
-                return ret + ([ DATA: decode(implode(
-                    ({ info }) + data[i+1..], delimiter)) ]);
-#endif
-            case SYSTEM:
-                ret[class] = old_explode(info, ":");
-                continue;
-            default:
-                ret[class] = decode(info);
-                continue;
-        }
-    }
-    return ret;
-}
-
-/* Check wether a UDP packet was valid.
- * Logs are made and "host" information is updated as appropriate.
- * Arguments: Decoded UDP packet (mapping)
- * Returns:   0 for valid packets, an error string otherwise.
- */
-string valid_request(mapping data) {
-    mixed host_data;
-    string *muds;
-    string req;
-    int i;
-    
-    if (!data[NAME] || !data[UDP_PORT])
-        return DATE + ": Illegal packet.\n";
-    if (host_data = hosts[lower_case(data[NAME])]) {
-        if (data[HOST] != host_data[HOST_IP]) {
-            if (data[NAME] == LOCAL_NAME)
-                return DATE + ": *** FAKE MUD ***\n";
-            log_file(INETD_LOG_FILE, DATE + ": Host change:\n" +
-            host_data[HOST_NAME] + ": " + host_data[HOST_IP] + " -> " +
-            data[HOST] + "\n\n");
-            host_data[HOST_IP] = data[HOST];
-        }
-        if (data[UDP_PORT] != host_data[HOST_UDP_PORT]) {
-            if (data[NAME] == LOCAL_NAME)
-                return DATE + ": *** FAKE MUD ***\n";
-            log_file(INETD_LOG_FILE, DATE + ": Port change:\n" +
-                host_data[HOST_NAME] + " (" + host_data[HOST_IP] + "): " +
-                host_data[HOST_UDP_PORT] + " -> " + data[UDP_PORT] + "\n\n");
-            host_data[HOST_UDP_PORT] = data[UDP_PORT];
-        }
-    } else {
-        if (lower_case(data[NAME]) == lower_case(LOCAL_NAME))
-            return DATE + ": *** FAKE MUD ***\n";
-        for(i = sizeof(muds = m_indices(hosts)); i--; ) {
-            host_data = hosts[muds[i]];
-            if (data[HOST] == host_data[HOST_IP] &&
-            data[UDP_PORT] == host_data[HOST_UDP_PORT]) {
-                log_file(INETD_LOG_FILE, DATE + ": Name change:\n" +
-                    host_data[HOST_NAME] + " (" + host_data[HOST_IP] +
-                    ") -> " + data[NAME] + "\n\n");
-                host_data[HOST_NAME] = data[NAME];
-                hosts[lower_case(data[NAME])] = host_data;
-                m_delete(hosts, muds[i]);
-                i = -2;
-                break;
-            }
-        }
-        if (i != -2) {
-            host_data = hosts[lower_case(data[NAME])] = ({
-                data[NAME],
-                data[HOST],
-                data[UDP_PORT],
-                COMMANDS,
-                ({ "*" }),
-                UP
-            });
-            log_file(INETD_LOG_FILE, DATE + ": New mud.\n" + data[NAME] + ":" +
-            data[HOST] + ":" + data[UDP_PORT] + "\n\n");
-        }
-    }
-    if (!(req = data[REQUEST]))
-        return DATE + ": System message.\n";
-    if (req != PING &&
-        req != QUERY &&
-        req != REPLY &&
-        member(host_data[LOCAL_COMMANDS], req) == -1)
-    {
-        /* This should probably send a system message too. */
-        _send_udp(host_data[HOST_NAME], ([
-            REQUEST: REPLY,
-            RECIPIENT: data[SENDER],
-            ID: data[ID],
-            DATA: "Invalid request @" + LOCAL_NAME + ": " +
-                capitalize(data[REQUEST]) + "\n"
-        ]) );
-        return DATE + ": Invalid request.\n";
-    }
-    return 0;
-}
-
-/*
- * Incoming UDP packets are sent to this function to be interpretted.
- * The packet is decoded, checked for validity, HOST_STATUS is updated
- * and the appropriate udp module called.
- * Arguments: Senders IP address (string)
- *            UDP packet (string)
- */
-void _receive_udp(string sender, string packet) {
-    mapping data;
-    string req, err, id;
-
-#ifdef UDP_MASTER
-    if (!previous_object() ||
-    object_name(previous_object()) != UDP_MASTER) {
-        log_file(INETD_LOG_FILE, DATE + ": Illegal call of _receive_udp() by " +
-        object_name(previous_object()) + "\n\n");
-        return;
-    }
-#endif
-
-    ZDEBUG(sprintf("%O -> %.500O\n",sender, packet));
-    if (
-#ifdef DELIMITER_COMPAT
-#ifdef USE_EXTRACT
-        (!(data = decode_packet(packet, NEW_DELIMITER))
-#else
-        (!(data = decode_packet(packet, NEW_DELIMITER))
-#endif
-        || (data[HOST] = sender) && (err = valid_request(data))) &&
-        (!(data = decode_packet(packet, OLD_DELIMITER)) ||
-        (data[HOST] = sender) && (err = valid_request(data)))
-#else
-#ifdef USE_EXTRACT
-        !(data = decode_packet(packet, DELIMITER))
-#else
-        !(data = decode_packet(packet, DELIMITER))
-#endif
-#endif
-    ) {
-        if (!data)
-#ifdef RECEIVE_UDP_COMPAT
-            RECEIVE_UDP_COMPAT(sender, packet);
-#else
-            log_file(INETD_LOG_FILE, DATE + ": Received invalid packet.\nSender: " +
-            sender + "\nPacket:\n" + packet + "\n\n");
-#endif
-        return;
-    }
-#ifdef DELIMITER_COMPAT
-    if (!mappingp(data))
-        return;
-    if (err)
-#else
-          if (!mappingp(data))
-                  return;
-    data[HOST] = sender;
-    if (err = valid_request(data))
-#endif
-    {
-        log_file(INETD_LOG_FILE, err + "Sender: " + sender + "\nPacket:\n" +
-        packet + "\n\n");
-        return;
-    }
-    hosts[lower_case(data[NAME])][HOST_STATUS] = UP;
-    if ((req = data[REQUEST]) == REPLY) {
-            mapping pending;
-
-        /* If we can't find the reply in the pending list then bin it. */
-        if (!(pending = pending_data[lower_case(data[NAME]) + ":" + data[ID]]))
-            return;
-        /* Set data[REQUEST] correctly, but still send to (req = REPLY) */
-        data[REQUEST] = pending[REQUEST];
-#ifdef INETD_DIAGNOSTICS
-        data[PACKET_LOSS] = pending[PACKET_LOSS];
-        data[RESPONSE_TIME] = time() - pending[RESPONSE_TIME] + 1;
-#endif
-#if 0
-/* channel replies may not include a recipient, and shouldn't have one set */
-        /* Restore the RECIPIENT in replies if none given and it is known. */
-        if (!data[RECIPIENT] && pending[SENDER])
-            data[RECIPIENT] = pending[SENDER];
-#endif
-        m_delete(pending_data, lower_case(data[NAME]) + ":" + data[ID]);
-    }
-    else if (data[ID]) {
-      id = lower_case(data[NAME]) + ":" + data[ID];
-      if (member(received_ids, id) == -1)
-      {
-        received_ids += ({ id });
-        call_out("remove_received_id",
-              REPLY_TIME_OUT + REPLY_TIME_OUT * (RETRIES + 1), id);
-      }
-      else
-            add_system_field(data, REPEAT);
-    }
-    if (err = catch(
-        call_other(UDP_CMD_DIR + req, "udp_" + req, deep_copy(data));publish))
-    {
-        _send_udp(data[NAME], ([
-            REQUEST: REPLY,
-            RECIPIENT: data[SENDER],
-            ID: data[ID],
-            DATA: capitalize(req)+ " request failed @" + LOCAL_NAME + ".\n"
-        ]) );
-        log_file(INETD_LOG_FILE, DATE + ": " + data[REQUEST] + " from " +
-        data[NAME] + " failed.\n" + err + packet + "\n\n");
-    }
-}
-
-int do_match(string mudname, string match_str) {
-    return mudname[0..sizeof(match_str)-1] == match_str;
-}
-
-#ifdef NO_CLOSURES
-
-status greater(mixed a, mixed b) {
-    return a > b;
-}
-
-string *expand_mud_name(string name) {
-    return sort_array(
-        filter(m_indices(hosts), "do_match", this_object(), name),
-        "greater",
-        this_object()
-    );
-}
-
-#else
-
-string *expand_mud_name(string name) {
-    return sort_array(
-        filter(m_indices(hosts), #'do_match, name),
-        #'>
-    );
-}
-
-#endif
-
-string encode(mixed arg) {
-    if (objectp(arg))
-        return object_name(arg);
-    if (stringp(arg) && sizeof(arg) &&
-        (arg[0] == '$' ||
-#ifdef RESTRICTED_CASTS
-    to_string(to_int(arg)) == (string)arg))
-#else
-    (string)to_int(arg) == (string)arg))
-#endif
-        return "$" + arg;
-    return to_string(arg);
-}
-
-string encode_packet(mapping data) {
-    int i;
-    mixed indices;
-    string header, body, t1, t2;
-    string *ret;
-    status data_flag;
-
-    for(ret = ({ }), i = sizeof(indices = m_indices(data)); i--; ) {
-        if (indices[i] == DATA) {
-            data_flag = 1;
-            continue;
-        }
-        header = encode(indices[i]);
-        body = encode(data[indices[i]]);
-        if (sscanf(header, "%s:%s", t1, t2) ||
-            sscanf(header + body, "%s" + DELIMITER + "%s", t1, t2)
-        )
-            return 0;
-        
-        ret += ({ header + ":" + body });
-    }
-    if (data_flag)
-#ifdef USE_OLD_DATA_FORMAT
-        ret += ({ DATA + ":" + encode(data[DATA]) });
-#else
-        ret += ({ "", encode(data[DATA]) });
-#endif
-    return implode(ret, DELIMITER);
-}
-
-// Funktion explode_
-// Die Funktion zerlegt den String packet in gleich lange Teilstrings
-// der Laenge len und gibt die Teilstrings als Array zurueck. Der letzte
-// Teilstring kann kuerzer sein als die anderen.
-string *explode_packet(string packet, int len) {
-
-  int ptr, m_ptr,size;
-  string *result;
-
-  // Variablen initialisieren
-  m_ptr=ptr=0;
-  size=sizeof(packet);
-  result=({});
-
-  // Um Arrayadditionen zu vermeiden wird vorher allokiert. Die Division 
-  // durch 0 ist nicht abgefangen, da bei len=0 was im Aufruf falch ist.
-  result=allocate((size/len+1));
-
-  while (ptr<size) {
-    result[m_ptr] = // Aktuellen Teilstring speichern
-#ifdef USE_EXTRACT
-            extract(packet,ptr,ptr+len-1);
-#else
-            packet[ptr..ptr+len-1]; 
-#endif
-    ptr+=len;// Neuen Pointer im String berechnen
-    m_ptr++; // Neuen Pointer im Mapping berechnen. Hier nutze ich eine
-             // Variable mehr als noetig, weil das billiger ist, als jedes
-             // mal ptr=m_ptr*len auszurechnen. Lieber zwei Additionen als 
-             // eine Multiplikation und eine Addtion.
-  }
-
-  // Wenn size/len aufgeht, ist das Result-Feld zu gross. Dann bleibt 
-  // ein Element leer, das wird hier gestrippt. Das ist billiger als
-  // jedesmal auszurechnen, ob size/len aufgeht.
-  return result-({0});
- 
-}
-
-varargs string _send_udp(string mudname, mapping data, int expect_reply) {
-    mixed host_data;
-    string *packet_arr;
-    string packet;
-    int i;
-
-    mudname = lower_case(mudname);
-    if (!(host_data = hosts[mudname])) {
-        string *names;
-
-        if (sizeof(names = expand_mud_name(mudname)) == 1)
-            host_data = hosts[mudname = names[0]];
-        else
-#ifdef SEND_UDP_COMPAT
-            return (string)SEND_UDP_COMPAT(mudname, data, expect_reply);
-#else
-        if (!sizeof(names))
-            return "Unbekannter Mudname: " + capitalize(mudname) + "\n";
-        else
-            return break_string("Mudname ("+capitalize(mudname)+
-            ") nicht eindeutig, es passen: "+implode(map(names,#'capitalize),", ")+
-            ".\n",78);
-#endif
-    }
-    
-    if (data[REQUEST] != PING &&
-        data[REQUEST] != QUERY &&
-        data[REQUEST] != REPLY &&
-        member(host_data[HOST_COMMANDS], "*") == -1 &&
-        member(host_data[HOST_COMMANDS], data[REQUEST]) == -1)
-        return capitalize(data[REQUEST]) + ": Command unavailable @" +
-                   host_data[HOST_NAME] + "\n";
-    
-    data[NAME] = LOCAL_NAME;
-    data[UDP_PORT] = LOCAL_UDP_PORT;
-    
-    if (expect_reply) {
-        /* Don't use zero. */
-        data[ID] = ++packet_id;
-        /* Don't need deep_copy() as we are changing the mapping size. */
-        pending_data[mudname + ":" + packet_id] =
-#ifdef INETD_DIAGNOSTICS
-        data + ([ NAME: host_data[HOST_NAME], RESPONSE_TIME: time() ]);
-#else
-        data + ([ NAME: host_data[HOST_NAME] ]);
-#endif
-    }
-    if (!(packet = encode_packet(data))) {
-        if (expect_reply)
-            pending_data = m_copy_delete(pending_data, mudname + ":" + packet_id);
-        log_file(INETD_LOG_FILE, DATE + ": Illegal packet sent by " +
-        object_name(previous_object()) + "\n\n");
-        return "inetd: Illegal packet.\n";
-    }
-    if (expect_reply)
-        call_out("reply_time_out", REPLY_TIME_OUT, mudname + ":" + packet_id);
-
-    if (sizeof(packet) <= MAX_PACKET_LEN)
-        packet_arr = ({ packet });
-    else {
-        string header;
-        int max;
-
-        /* Be careful with the ID.  data[ID] could have been set up by RETRY */
-        header =
-            PACKET + ":" + lower_case(LOCAL_NAME) + ":" +
-            ((expect_reply || data[REQUEST] != REPLY)&& data[ID] ?
-            data[ID] : ++packet_id) + ":";
-        
-        /* Allow 8 extra chars: 3 digits + "/" + 3 digits + DELIMITER */
-        packet_arr = explode_packet(packet,
-            MAX_PACKET_LEN - (sizeof(header) + 8));
-
-        for(i = max = sizeof(packet_arr); i--; )
-            packet_arr[i] =
-            header + (i+1) + "/" + max + DELIMITER + packet_arr[i];
-    }
-    
-    for(i = sizeof(packet_arr); i--; )
-    {
-      ZDEBUG(sprintf("%O <- %.500O\n",host_data[HOST_IP], packet_arr[i]));
-      if (!send_udp(
-        host_data[HOST_IP], host_data[HOST_UDP_PORT], packet_arr[i]))
-            return "inetd: Error in sending packet.\n";
-    }
-    return 0;
-}
-
-void reply_time_out(string id) {
-    mapping data;
-
-    if (data = pending_data[id]) {
-        object ob;
-
-#ifdef INETD_DIAGNOSTICS
-        data[PACKET_LOSS]++;
-#endif
-        if (data[RETRY] < RETRIES) {
-            mapping send;
-
-            data[RETRY]++;
-            /* We must use a copy so the NAME field in pending_data[id]
-             * isn't corrupted by _send_udp(). */
-            send = deep_copy(data);
-            send = m_copy_delete(send, RETRY);
-#ifdef INETD_DIAGNOSTICS
-            send = m_copy_delete(send, PACKET_LOSS);
-            send = m_copy_delete(send, RESPONSE_TIME);
-#endif
-            call_out("reply_time_out", REPLY_TIME_OUT, id);
-            _send_udp(data[NAME], send);
-            return;
-        }
-        data = m_copy_delete(data, RETRY);
-#ifdef INETD_DIAGNOSTICS
-        data = m_copy_delete(data, RESPONSE_TIME);
-#endif
-        catch(call_other(UDP_CMD_DIR + REPLY, "udp_" + REPLY,
-        add_system_field(data, TIME_OUT));publish);
-        /* It's just possible this was removed from the host list. */
-        if (hosts[lower_case(data[NAME])])
-            hosts[lower_case(data[NAME])][HOST_STATUS] = DOWN;
-        remove_incoming(lower_case(data[NAME]) + ":" + id);
-    }
-    pending_data = m_copy_delete(pending_data, id);
-}
-
-void remove_received_id(string id) {
-    received_ids -= ({ id });
-}
-
-varargs mixed query(string what, mixed extra1, mixed extra2) {
-    mixed data;
-
-    switch(what) {
-        case "commands":
-            return COMMANDS;
-        case "hosts":
-            return deep_copy(hosts);
-        case "pending":
-            return deep_copy(pending_data);
-        case "incoming":
-            return deep_copy(incoming_packets);
-        case "received":
-            return ({ }) + received_ids;
-        /* args: "valid_request", request, mudname */
-        case "valid_request":
-            if (data = hosts[extra2])
-                return member(data[HOST_COMMANDS], "*") != -1 ||
-                        member(data[HOST_COMMANDS], extra1) != -1;
-            return 0;
-    }
-    return(0);
+protected void create()
+{
+  configure_host((<host_s> name: "MG-Test-"+__HOST_NAME__,
+                  ip: "127.0.0.1",
+                  port: driver_info(DI_UDP_PORT),
+        ));
+  ::create();
 }
 
diff --git a/secure/inetd/channel.c b/secure/inetd/channel.c
new file mode 100644
index 0000000..a2276ff
--- /dev/null
+++ b/secure/inetd/channel.c
@@ -0,0 +1,169 @@
+#pragma strict_types, save_types, rtt_checks, pedantic
+#pragma no_clone, no_shadow, no_inherit
+
+inherit __DIR__"i_service";
+
+#include <intermud.h>
+#include <regexp.h>
+
+#define CHMASTER "/p/daemon/channeld"
+#define COMMAND  "cmd"
+#define CHANNEL  "channel"
+
+public string service_name()
+{
+  return "channel";
+}
+
+public string name(int egal)
+{
+  return currentname || "<Intermud>";
+}
+
+varargs private string getName(mixed x, int fall) {
+
+  mixed o = closurep(x) ? query_closure_object(x) : x;
+  if(stringp(o) && sizeof(o) && (x = find_object(o)))
+    o = x;
+
+  // Objekte
+  if (objectp(o))
+  {
+    // Froesche mit Namen versorgen.
+    if (o->QueryProp(P_FROG))
+      return "Frosch "+capitalize(getuid(o));
+    // Default (Unsichtbare als "Jemand" (s. Name()))
+    return o->Name(fall, 2)||"<Unbekannt>";
+  }
+  // Strings
+  else if (stringp(o) && sizeof(o)) {
+    if (o[0] == '/')
+    {
+      // unsichtbare Objekte...
+      int p = strstr(o, "$");
+      if (p != -1)
+        return o[p+1..];
+      else
+        // doch nicht unsichtbar
+        return (fall == WESSEN ? o+"s" : o);
+    }
+    else
+      // nicht unsichtbar
+      return (fall == WESSEN ? o+"s" : o);
+  }
+  // Fall-through
+  return "<Unbekannt>";
+}
+
+// NOT USED we received a reply to one of our messages.
+//protected void recv_channel_reply(int id, mapping request, mapping response)
+//{
+//}
+
+// we received an intermud channel message
+protected void recv_request(mapping data)
+{
+  /* Compatability with older systems. */
+  if (!data[CHANNEL])
+    data[CHANNEL] = data["CHANNEL"];
+  if (!data[COMMAND])
+    data[COMMAND] = data["CMD"];
+  if (!data[DATA])
+    data[DATA]="";
+
+  if (!stringp(data[CHANNEL]) || !sizeof(data[CHANNEL])
+      || !stringp(data[DATA]) || !sizeof(data[DATA])
+      || !stringp(data[NAME]) || !sizeof(data[NAME])
+      || !stringp(data[SENDER]) || !sizeof(data[SENDER]))
+    return 0;
+
+  data[DATA]= regreplace(data[DATA],"[:^print:]|\n","",1);
+
+  int type;
+  switch(data[COMMAND])
+  {
+  case "list":
+    /* Request for a list of people listening to a certain channel. */
+    mapping l = CHMASTER->list(this_object());
+    string channel = lower_case(data[CHANNEL]);
+    string *members;
+    string ret;
+    if(mappingp(l))
+    {
+        if(stringp(channel) && sizeof(channel) && pointerp(l[channel]))
+        {
+          members=sort_array(map(l[channel][I_MEMBER],#'getName, WER),#'>);
+        }
+    }
+    if (members && sizeof(members))
+      ret = "[" + data[CHANNEL] + "@" + INETD->PeerName() + "] Listening:\n"
+            + implode(members, "\n");
+    else
+      ret = "Nobody listening / channel unknown.\n";
+
+    INETD->send(data[NAME], ([ REQUEST: REPLY, ID: data[ID],
+                               RECIPIENT: data[SENDER],
+                               DATA: ret ]),
+                0);
+    return 0;
+  case "emote": /* A channel emote. */
+    if (data["EMOTE"] == 1)
+      type = MSG_EMOTE;
+    else
+      type = MSG_GEMOTE;
+      break;
+  default: /* A regular channel message. */
+    type = MSG_SAY;
+    break;
+  }
+
+  currentname = capitalize(data[SENDER])+"@"+capitalize(data[NAME]);
+  CHMASTER->send(capitalize(data[CHANNEL]), this_object(),
+                 data[DATA], type);
+  currentname = 0;
+}
+
+public string ChannelMessage(mixed* msg, int nonint)
+{
+  if ( previous_object() != find_object(CHMASTER))
+      return 0;
+
+  string channel=msg[0];
+  object sender=msg[1];
+  string channel_message=msg[2];
+  int msg_type = msg[3];
+
+  if(sender == this_object()) return 0;
+
+  string sender_name = getName(sender, msg_type == MSG_GEMOTE ? WESSEN : WER);
+
+  mapping request = ([REQUEST: "channel", SENDER: sender,
+                      "CHANNEL": lower_case(channel),
+                      DATA: channel_message ]);
+
+  switch(msg_type)
+  {
+  case MSG_GEMOTE:
+    request["EMOTE"] = 2;
+    request["CMD"] = "emote";
+    break;
+  case MSG_EMOTE:
+    request["EMOTE"] = 1;
+    request["CMD"] = "emote";
+    break;
+  case MSG_SAY:
+  default:
+    request["CMD"] = "";
+    break;
+  }
+
+  INETD->broadcast("channel", request, 0);
+
+  return 0;
+}
+
+public int request(string mudname, string|int data)
+{
+  raise_error("request should not be used, use ChannelMessage()!\n");
+}
+
diff --git a/secure/inetd/finger.c b/secure/inetd/finger.c
new file mode 100644
index 0000000..2acd7b2
--- /dev/null
+++ b/secure/inetd/finger.c
@@ -0,0 +1,49 @@
+#pragma strict_types, save_types, rtt_checks, pedantic
+#pragma no_clone, no_shadow, no_inherit
+
+inherit __DIR__"i_service";
+
+#include <intermud.h>
+#include <regexp.h>
+
+string last_finger;
+
+public string service_name()
+{
+  return "finger";
+}
+
+string QueryLastFinger()
+{
+  return last_finger;
+}
+
+// we received an intermud channel message
+protected void recv_request(mapping data)
+{
+  last_finger=capitalize(to_string(data[SENDER]))+"@"+data[NAME];
+  INETD->send(data[NAME], ([
+        REQUEST: REPLY,
+        RECIPIENT: data[SENDER],
+        ID: data[ID],
+        DATA: "/p/daemon/finger"->finger_single(data[DATA])
+    ]) );
+}
+
+public int finger_remote(string name, string mud)
+{
+  int ret = request(mud, name);
+  if (ret > 0)
+    write("Anfrage abgeschickt.\n");
+  else
+  {
+    switch(ret)
+    {
+      default:
+        write("Es trat ein Fehler beim Versenden der Anfrage auf.\n");
+        break;
+    }
+  }
+  return 1;
+}
+
diff --git a/secure/inetd/i_service.c b/secure/inetd/i_service.c
new file mode 100644
index 0000000..b27606d
--- /dev/null
+++ b/secure/inetd/i_service.c
@@ -0,0 +1,88 @@
+#pragma strict_types, save_types, rtt_checks, pedantic
+#pragma no_clone, no_shadow, no_inherit
+
+#include <intermud.h>
+#include <regexp.h>
+#include <living/comm.h>
+#include <defines.h>
+#include <daemon/mand.h>
+
+protected nosave string currentname = "INETD-service";
+
+public string service_name()
+{
+  return "undefined";
+}
+
+public string name(int egal)
+{
+  return currentname || "INETD-"+service_name();
+}
+
+public string Name(int egal)
+{
+  return capitalize(name(egal));
+}
+
+// we received a reply to one of our requests.
+// Note: if response is 0, the peer did not respond (timeout)...
+protected void recv_reply(int id, mapping request, mapping response)
+{
+  if(response)
+  {
+    if (stringp(response[RECIPIENT]))
+    {
+      object ob = find_player(response[RECIPIENT])
+                  || find_object(response[RECIPIENT]);
+      if (ob)
+      {
+          currentname = capitalize(data[SENDER])+"@"+capitalize(data[NAME]);
+          ob->ReceiveMsg(regreplace(response[DATA],"[:^print:]|\n","",1),
+                         MT_NOTIFICATION, service_name(), 0, ME);
+          currentname = 0;
+    }
+  }
+  else
+  {
+    // no response, timeout...
+    if (request[SENDER] && stringp(request[SENDER]))
+    {
+      object ob = find_player(request[SENDER])
+                  || find_object(request[SENDER]);
+      if (ob)
+          ob->ReceiveMsg("Das Mud \'" + request[NAME] +
+                         "\' konnte nicht erreicht werden.\n",
+                         MT_NOTIFICATION, 0, service_name(), ME);
+    }
+  }
+}
+
+// we received an intermud request
+protected void recv_request(mapping data)
+{
+  // implement it!
+  raise_error("recv_request() has to be implemented!\n");
+}
+
+// send request via intermud
+public int request(string mudname, string|int data)
+{
+  return INETD->send(mudname, ([REQUEST: servicename(),
+                                DATA: data,
+                                SENDER: getuid(previous_object())]),
+                  #'recv_reply) > 0;
+}
+
+protected void create()
+{
+  INETD->register_service(service_name(), #'recv_request);
+  currentname = "INETD-"+service_name();
+}
+
+public varargs int remove(int silent)
+{
+  INETD->unregister_service(service_name());
+  destruct(this_object());
+}
+
+
diff --git a/secure/inetd/inetd.c b/secure/inetd/inetd.c
new file mode 100644
index 0000000..7973d4f
--- /dev/null
+++ b/secure/inetd/inetd.c
@@ -0,0 +1,53 @@
+#pragma strict_types, save_types, rtt_checks, pedantic
+#pragma no_clone, no_shadow, no_inherit
+
+// inherit the basic inetd
+inherit __DIR__"inetd_base.c";
+
+#include <living/comm.h>
+#include <driver_info.h>
+
+// and implement mud-individual stuff that should be implemented as needed. If
+// the default is fine for your mud, keep it.
+protected void set_user()
+{
+  seteuid(getuid());
+}
+
+protected void save_me()
+{
+//  save_object(__DIR__"ARCH/inetd");
+}
+
+protected int restore_me()
+{
+//  return restore_object(__DIR__"ARCH/inetd");
+}
+
+protected void export_peer_list(mapping list)
+{
+}
+
+protected void debug_msg(string msg, int severity)
+{
+  object z=find_player("zesstra");
+  if (z)
+    z->ReceiveMsg(msg, MT_DEBUG, 0, "IM: ", this_object());
+}
+
+public void receive(string host, string msg, int hostport)
+{
+  if (previous_object() == master())
+    process_fragment(host, msg, hostport);
+}
+
+protected void create()
+{
+  configure_host((<host_s> name: "MG-Test-"+__HOST_NAME__,
+                  ip: __HOST_IP_NUMBER__,
+                  port: driver_info(DI_UDP_PORT),
+                  mtu: 1024,
+        ));
+  ::create();
+}
+
diff --git a/secure/inetd/inetd_base.c b/secure/inetd/inetd_base.c
new file mode 100644
index 0000000..7ec6c28
--- /dev/null
+++ b/secure/inetd/inetd_base.c
@@ -0,0 +1,1272 @@
+/*
+
+   //TODO: implement retries
+ */
+
+#define protected public
+
+#pragma strict_types, save_types, rtt_checks, pedantic
+#pragma no_clone, no_shadow
+
+#include <tls.h>
+#include <config.h>
+#include <driver_info.h>
+#include <lpctypes.h>
+#include <strings.h>
+
+// data structures
+struct host_s {
+    string name;
+    string key;
+    string ip;
+    int port;
+    int last_contact;
+    int first_contact;
+    int reputation;
+    string *services;
+    int im_version;
+    int ncttl;
+    int mtu;
+    mapping received;
+};
+
+struct fragment_s {
+    string header;
+    string buf;
+};
+
+struct packet_s {
+    int id;
+    int timestamp;
+    int pversion;
+    int pflags;
+    struct fragment_s *fragments;
+    string peername;
+    mapping data;
+    string buf;
+};
+
+struct service_s {
+    string name;
+    string obname;
+    string fun;
+    closure recv_cb;
+};
+
+struct request_s {
+    int id;
+    struct packet_s packet;
+    closure callback;
+    int timeout;
+};
+
+// Prototypes & Defines
+protected void init();
+void ping_many_muds(string *muds);
+protected int calc_ncttl(struct host_s p);
+protected void send_helo(string name);
+
+// mud-compatibility layer - might implement efuns with lfuns if not available
+// on the specific mud.
+inherit __DIR__"inetd_compat";
+
+// Mapping of known peers with their names as keys.
+mapping peers = ([]);
+
+// our own offered services
+mapping services = ([]);
+
+// our own host_s structure - it also contains our _private_ key.
+struct host_s self = (<host_s>);
+
+// reset counter for housekeeping
+int reset_counter;
+
+nosave int last_packet_id = 0;
+nosave mapping requests = ([]);
+
+// Minimum protocol version to accept (default 0)
+int min_protocol_version = 0;
+
+// *** lfuns in this section might be re-defined to suit the individual muds.
+protected void debug_msg(string msg, int severity)
+{
+  write(msg);
+}
+
+protected void set_user()
+{
+  seteuid(getuid());
+}
+
+protected void save_me()
+{
+}
+
+protected int restore_me()
+{
+}
+
+protected void export_peer_list(mapping list)
+{
+}
+
+// *** end of mud-specific lfuns ***
+
+private string *sys_fields = ({HOST, NAME, PACKET, UDP_PORT, SYSTEM, VERSION,
+                               FLAGS, SIGNATURE,
+                               PACKET_LOSS, RESPONSE_TIME});
+
+// *** public interface
+public void register_service(string name, string fun)
+{
+  struct service_s serv;
+
+  closure cl = symbol_function(fun, previous_object());
+  if (!cl)
+    raise_error("register_service(): function not public.\n");
+
+  if (member(services, name))
+    serv = services[name];
+  else
+    serv = (<service_s>);
+
+  serv->name = name;
+  serv->obname = object_name(previous_object());
+  serv->fun = fun;
+  serv->recv_cb = cl;
+  services[name] = serv;
+  self->services = m_indices(services);
+  debug_msg(sprintf("Service registered: %O\n",serv),40);
+}
+
+public void unregister_service(string name)
+{
+  struct service_s serv = services[name];
+  if (!structp(serv))
+    return;
+  // wenn von aussen gerufen, nur registriertes Objekt selber.
+  if (extern_call() && serv->obname != object_name(previous_object()))
+    raise_error("Service can only be unregistered by service object.\n");
+  m_delete(services, name);
+  self->services = m_indices(services);
+  debug_msg(sprintf("Service unregistered: %s\n",name),40);
+}
+
+public string *QueryPeerList()
+{
+  return m_indices(peers);
+}
+
+// TODO: remove, for debug only
+public struct host_s QueryPeer(string name)
+{
+  return peers[name];
+}
+
+public string PeerName()
+{
+  return self->name;
+}
+
+// **************** internal implementation ***********************
+
+
+protected void configure_host(struct host_s new_self)
+{
+  new_self->first_contact = time();
+  new_self->last_contact = time();
+  new_self->reputation = __INT_MAX__;
+  new_self->ncttl = __INT_MAX__;
+  //if (!sizeof(new_self->services))
+  //  raise_error("Illegal to configure peer without services!\n");
+  if(new_self->key)
+    new_self->im_version=2500;
+  else
+    new_self->im_version=2000;
+  if (new_self->mtu < 1024)
+    raise_error("The minimum UDP length (MTU) must >= 1024 bytes!\n");
+  // ok, uebernehmen
+  self = new_self;
+  debug_msg(sprintf("localhost configured: %O\n",self),30);
+}
+
+// Updates (or adds a new) peer.
+protected struct host_s add_new_peer(struct host_s p)
+{
+  // TODO: validate
+  // This must not happen, the caller must check for existence to prevent
+  // hijacking peer names...
+  if (member(peers, p->name))
+    raise_error("Attempt to add existing peer!\n");
+  if (p->mtu < 1024) p->mtu = 1024;
+  p->received = ([]);
+  peers[p->name] = p;
+  debug_msg(sprintf("new peer added: %O\n",p),50);
+  return p;
+}
+
+// Updates (or adds a new) peer.
+protected void update_peer(struct host_s p)
+{
+  //TODO validate
+  struct host_s old = peers[p->name];
+  if (!old)
+    peers[p->name] = p;
+  else
+  {
+    // we always keep some data...
+    old->key = p->key;
+    old->ip = p->ip;
+    old->port = p->port;
+    if (p->last_contact > old->last_contact)
+        old->last_contact = p->last_contact;
+    if (p->first_contact < old->first_contact)
+        old->first_contact = p->first_contact;
+    old->services=p->services;
+    old->reputation = p->reputation;
+    old->im_version = p->im_version;
+    if (p->mtu >= 1024) old->mtu = p->mtu;
+    old->ncttl = p->ncttl;
+    // keep packet fragments, don't update received.
+    debug_msg(sprintf("peer updated: %O\n",self),50);
+  }
+}
+
+// remove a peer from the known peer list
+protected void kill_peer(string name)
+{
+  m_delete(peers, name);
+  debug_msg(sprintf("peer deleteted: %s\n",name),50);
+}
+
+// Update last_contact value to current time.
+protected void touch_peer(string name)
+{
+  struct host_s p = peers[name];
+  if (p)
+    p->last_contact = time();
+}
+
+// imports old style host lists. Should only be done once.
+public int import_host_list(string file) {
+
+  int res;
+  mixed data = (read_file(file));
+  if (!data) {
+    debug_msg("*Error in reading host file: " + file +"\n\n", 100);
+    return 0;
+  }
+
+  data = old_explode(data, "\n");
+
+  foreach(string line: data)
+  {
+    if (line == "" || line[0] == '#')
+      continue;
+    mixed fields = old_explode(line, HOSTFILE_DEL);
+    if (sizeof(fields) < 5) {
+      debug_msg(sprintf(
+            "*Parse error in hosts file: %s, Line: %s\n", file, line), 100);
+      continue;
+    }
+
+    struct host_s p = (<host_s>);
+    p->name = lower_case(fields[HOSTLIST_NAME]);
+    // skip already known peers
+    if (member(peers, p->name))
+      continue; // mud already in list
+
+    p->services = old_explode(fields[HOSTLIST_LOCAL_COMMANDS],HOSTFILE_DEL2);
+    if (member(p->services,"*") != -1)
+        p->services = p->services - ({ "*" }) + DEFAULT_COMMANDS;
+    p->ip = fields[HOSTLIST_IP];
+    p->port = to_int(fields[HOSTLIST_UDP_PORT]);
+    // we fake first and last contact, otherwise it would be expired if not online.
+    p->first_contact = time();
+    p->last_contact = time();
+    p->mtu = 1024;
+    p->ncttl = calc_ncttl(p);
+
+    add_new_peer(p);
+    ++res;
+  }
+  debug_msg(sprintf("old-stype host list imported: %s, %d new peers\n",
+                    file,res),50);
+  return res;
+}
+
+
+protected void init()
+{
+  if (!self)
+    raise_error("Can't init/startup without configuration!\n");
+
+  // Register the core services. Must be callother for previous_object being
+  // ourself
+  this_object()->register_service(PING, "recv_ping");
+  this_object()->register_service(QUERY, "recv_query");
+  this_object()->register_service(REPLY, "recv_reply");
+  this_object()->register_service(HELO, "recv_helo");
+  debug_msg(sprintf("init() performed, pinging known peers.\n"),30);
+  ping_many_muds(m_indices(peers));
+}
+
+protected string encode(mixed v)
+{
+  switch(typeof(v))
+  {
+    case T_NUMBER:
+      return to_string(v);
+    case T_STRING:
+      return "$"+v;
+    default:
+      // convert into a string representation - hopefully the receiver
+      // knows what to do with it.
+      return sprintf("$%Q",v);
+  }
+}
+
+#define SIGLENGTH 32+4 // length of signature including "$S:|"
+
+protected void sign_fragment(struct fragment_s f)
+{
+  if (self->key)
+  {
+    f->header=sprintf("%s$S:%s|", f->header,
+                      hmac(TLS_HASH_SHA512, self->name, f->buf));
+    debug_msg(sprintf("signed fragment: %s, %s.\n",f->header,f->buf),90);
+  }
+  else
+    f->header="";
+}
+
+
+// creates a packet structure with fragments to send (can be only one, if the
+// packet does not need to be split).
+protected struct packet_s packetize(struct host_s dest, mapping data)
+{
+  // some field names in the packet are reserved and must not be used by
+  // users, they are added by the inetd.
+  if (sizeof(data & sys_fields))
+    raise_error(sprintf(
+          "Data must not contain reserved system fields: %O.\n",
+          data & sys_fields));
+
+  // create packet structure
+  struct packet_s packet = (<packet_s>
+                            id: ++last_packet_id, timestamp: time(),
+                            pversion: self->im_version, pflags: 0,
+                            fragments: 0, peername: 0, data: data, buf: 0);
+
+  // add the system fields (NAME, V, F must be the first fields!)
+  // the ID field is special, because maybe the packet is an answer to a
+  // request. In this case, we must keep the original ID
+  if (!member(data, ID))
+    data[ID] = packet->id;
+  else
+  {
+    if (data[REQUEST] != REPLY)
+        raise_error(sprintf("ID only permitted in reply packets!"));
+  }
+  // NAME is added below
+  data[UDP_PORT] = self->port;
+
+  // create an array of fields (key:value pairs)
+  string data_field;
+  string *  fields = allocate(sizeof(data)+3);
+  fields[0] = sprintf("NAME:%s", self->name);
+  fields[1] = sprintf("V:%d", packet->pversion);
+  fields[2] = sprintf("F:%d", packet->pflags);
+  int cindex = 3;
+  foreach(string key, mixed val : data)
+  {
+    // the DATA field must be added last, so remember its encoded string.
+    // Also, it is not checked for reserved characters
+    if (key == DATA)
+      data_field = sprintf("%s:%s", encode(DATA), encode(val));
+    else
+    {
+      if (!stringp(key) || !sizeof(key))
+        raise_error(sprintf("send((): Illegal field name %.30O in data "
+                            "payload.\n",key));
+      string ekey = encode(key);
+      string eval = encode(val);
+      // check key and value strings for reserved characters
+      if (regmatch(ekey,"[|:]") || regmatch(eval, "[|:]"))
+        raise_error(sprintf("send(): Field name or value contains reserved "
+                            "character: %.50s,%.50s\n", ekey, eval));
+      fields[cindex] = sprintf("%s:%s",ekey,eval);
+      ++cindex;
+    }
+  }
+  // add the DATA field if existing
+  if (data_field)
+    fields[cindex] = data_field;
+
+  // write the string buffer
+  packet->buf = implode(fields, "|");
+
+  // now split into fragments if packet too large for one.
+  int mtu = min(self->mtu, dest->mtu);
+  if ((mtu - SIGLENGTH - sizeof(packet->buf)) < 0)
+  {
+    // multiple fragments, each starting with
+    // PKT:peername:packet-id:packet-number/total-packets|
+    // fsize is the maximum payload length per fragment.
+    string fheader = sprintf("PKT:%s:%d:", self->name, packet->id);
+    int fsize = mtu - SIGLENGTH - sizeof(fheader)
+                - 8; // sizeof("nnn/mmm|")
+    string buffer = packet->buf;
+    int fcount = sizeof(buffer) / fsize;
+    // if there is a modulo left, we need one additional fragment
+    if (sizeof(buffer) % fsize)
+      ++fcount;
+    if (fcount > 999)    // too many fragments?
+      raise_error("packet too long to send.\n");
+    // allocate the complete fragment array.
+    packet->fragments = allocate(fcount);
+    foreach(int i: fcount)
+    {
+      struct fragment_s f = (<fragment_s>
+                     header:sprintf("%s%d/%d|",fheader,i+1,fcount),
+                     buf: buffer[i*fsize .. min((i+1)*fsize, sizeof(buffer))-1]
+          );
+      sign_fragment(f);
+      packet->fragments[i] = f;
+      debug_msg(sprintf("Created fragment %d/%d\n", i,fcount),90);
+    }
+  }
+  else
+  {
+    // one fragment, and the fragment header will be empty.
+    struct fragment_s f = (<fragment_s> header: "",
+                           buf: packet->buf);
+    sign_fragment(f);
+    packet->fragments = ({f});
+    debug_msg(sprintf("Created fragment 1/1\n"),90);
+  }
+  return packet;
+}
+
+// encodes the mapping data into a packet and sends it to <dest>. The
+// key-value pairs of <data> are converted into intermud fields. The keys must
+// be strings and only the DATA field can contain : or |.
+// Also, <data> must not contain any reserved field names.
+// If the caller expects an answer, <cb> must contain a calleable closure. In
+// this case, the inetd remembers the request. When the answer arrives, the
+// callback <cb> is called with the request id (int) and a mapping (the answer
+// packet) as arguments. The return value of send() is in this case the
+// request id used for storage.
+// <= 0 for error, > 0 for success.
+public int send(string dest, mapping data, closure cb)
+{
+  if (!sizeof(data))
+    raise_error("Illegal sending empty packets.");
+  if (!sizeof(dest))
+    raise_error("Can't send without destination: %O.\n");
+  // find peer data. If not found, abort and tell the caller
+  struct host_s peer = peers[dest];
+  if (!structp(peer))
+    return -1;
+
+  if (member(peer->services, data[REQUEST]) == -1)
+    raise_error("Remote host doesn't offer service "+
+                data[REQUEST] + ".\n");
+
+  // then packetize the data, i.e. create packet structure, encode into buffer
+  // and split into fragments as needed and sign fragments.
+  // There will be at least one fragment structure.
+  struct packet_s packet = packetize(peer, data);
+
+  // loop over all fragments and send them
+  foreach(struct fragment_s f : packet->fragments)
+  {
+    debug_msg(sprintf("%O <- %.500O\n",peer->ip, f->header + f->buf), 100);
+    // TODO: error handling
+    send_udp(peer->ip, peer->port, f->header + f->buf);
+  }
+  // delete the buffer
+  packet->buf = 0;
+  // delete the fragments until we support re-transmission of fragments
+  packet->fragments = 0;
+
+  // we keep the original data for reference, if storing the request.
+
+  // if an answer is expected, there is a callback closure. Then we store the
+  // request with a timeout of 120s.
+  if (cb)
+  {
+    struct request_s r = (<request_s> packet->id, packet, cb, time()+120);
+    requests[r->id] = r;
+    debug_msg(sprintf("Answer expected, request stored\n"),80);
+  }
+
+  return packet->id;
+}
+
+// like send(), but sends to all peers offering the service <service>. But
+// limited to peers we had contact with in the last 24h.
+public int broadcast(string service, mapping data, closure cb)
+{
+  if (!service)
+    raise_error("No service given!\n");
+  if (!sizeof(data))
+    raise_error("Illegal sending empty packets.");
+//TODO: check if we could send the same packet to all peers, instead of
+//packetizing for each peer as well.
+  int ret;
+  int tlc_cutoff = time() - 86400;
+  foreach(string dest, struct host_s peer : peers)
+  {
+    if (peer->last_contact >= tlc_cutoff
+        && member(peer->service, service) != -1)
+    {
+      send(dest, data, cb);
+      ++ret;
+    }
+  }
+  return ret;
+}
+
+protected string|int decode(string arg)
+{
+    if (sizeof(arg) && arg[0] == '$')
+        return arg[1..];
+    if (to_string(to_int(arg)) == arg)
+        return to_int(arg);
+
+    return arg;
+}
+
+// must be validated before. Parses the packet buffer into the given packet
+// structure and returns it. In case of errors (e.g. malformed packets), 0 is
+// returned.
+protected struct packet_s parse_packet(struct packet_s packet)
+{
+  string *fields = explode(packet->buf, "|");
+  // The DATA field may contain any number of | which don't signify a field.
+  // Therefore counting | over-estimates the number of fields. To prevent
+  // excessive memory allocation, we limit the allocation to 32 fields. The
+  // mapping may still grow if there are really more fields.
+  packet->data = m_allocate(min(sizeof(fields),32));
+  int cindex;
+  while (cindex < sizeof(fields))
+  {
+    string header, info;
+    /// DATA fields can be denoted by a preceeding blank field :-/
+    if (!sizeof(fields[cindex]))
+    {
+      ++cindex;
+      header = DATA;
+      //Test for illegal packet length (no DATA)
+      if (cindex >= sizeof(fields))
+        return 0;
+      // take the first "field" of DATA (the rest is added below)
+      info = fields[cindex];
+    }
+    else if (sscanf(fields[cindex], "%s:%s", header, info) != 2)
+      return 0;
+    header = decode(header);
+    if (header == DATA)
+    {
+      // add the rest of the packet and combine back into one string if
+      // necessary
+      if (cindex < sizeof(fields)-1)
+        info = implode(({info}) + fields[cindex+1 ..], "|");
+      // and we have finished after decoding info
+      cindex = sizeof(fields);
+    }
+    packet->data[header] = decode(info);
+    ++cindex;
+  }
+  // only allow printable characters for these fields.
+  response[SENDER] =
+             regreplace(response[SENDER],"[:^print:]|\n","",1);
+  response[NAME] =
+             regreplace(response[NAME],"[:^print:]|\n","",1);
+  response[RECIPIENT] =
+             regreplace(response[RECIPIENT],"[:^print:]|\n","",1);
+
+  return packet;
+}
+
+protected void defragment_packet(struct packet_s p)
+{
+  if (sizeof(p->fragments))
+  {
+    p->buf = "";
+    foreach(struct fragment_s f : p->fragments)
+    {
+      p->buf += f->buf;
+    }
+    p->fragments = 0;
+  }
+}
+
+protected int validate_signature(string signature, string buf, struct host_s src)
+{
+  if (!src->key)
+    return 0;
+  // TODO: debug purpose
+  return 1;
+}
+
+// returns a packet_s or 0 for invalid or incomplete (!) packet. In both
+// cases, the caller should not do anything. In case of incomplete packets,
+// the fragment will be stored, if it was valid.
+protected struct packet_s validate_fragment(string buffer, struct host_s sender)
+{
+  string signature, pname;
+  int packetid, fragno, totalfrags, version, flags;
+  struct packet_s packet;
+  struct fragment_s f;
+
+  if (sizeof(buffer) > MAX_UDP_LENGTH)
+    return 0;
+
+  if (sscanf(buffer, "S:%s|NAME:%s|V:%d|$F:%d|%~s",
+             signature, pname, version, flags) == 5)
+  {
+    // non-fragmented v2.5+ packet
+    debug_msg(sprintf("Received v2.5+ packet.\n"),90);
+    pname = lower_case(pname);
+    packet = (<packet_s> timestamp: time(), pversion: version,
+              pflags: (flags & ~FL_VALID_SIGNATURE),
+              buf: buffer[strstr(buffer,"|")+1..]
+             );
+    debug_msg(sprintf("Received v2.5+ packet from %s.\n",pname),90);
+    sender = peers[pname];
+    // if we don't know the sender yet, we create a new peer with its name.
+    // The ip and port will be updated during parsing.
+    if (!sender)
+    {
+      sender = add_new_peer( (<host_s> name: pname,
+                          first_contact: time(),
+                          last_contact: time(),
+                          mtu: 1024,
+                          im_version : version,
+                          received: m_allocate(5)
+                         ));
+      // but if we can't create one, we discard the packet
+      if (!sender)
+        return 0;
+    }
+    packet->peername = pname;
+    // try to check the packet signature and record the result, if
+    // successful.
+    if (validate_signature(signature, packet->buf, sender))
+    {
+      packet->pflags |= FL_VALID_SIGNATURE;
+    }
+  }
+  else if (sscanf(buffer, "PKT:%.1s:%d:%d/%d|%~s",
+                  pname, packetid, fragno, totalfrags) == 5)
+  {
+    // this is a fragmented packet
+    // we at least check if the info about fragmentation is sane.
+    if (totalfrags > 999 || fragno > totalfrags
+        || totalfrags < 1 || fragno < 1)
+      return 0;
+
+    pname=lower_case(pname);
+
+    // is it even a IM 2.5+ packet? (want to avoid copying the buffer,
+    // therefore this approach)
+    if (sscanf(buffer, "PKT:%.1~s:%~d:%~d/%~d|$S:%.10s|%~s",
+               signature) == 6)
+    {
+      // create fragment (we know, there are at least two |)
+      int first_pipe = strstr(buffer,"|");
+      int sig_end = strstr(buffer, "|", first_pipe+1);
+      f = (<fragment_s> header: buffer[0..first_pipe],
+                      buf: buffer[sig_end+1 ..]
+          );
+      debug_msg(sprintf("Received v2.5+ packet %d, fragment %d/%d from %s.\n"
+                ,packetid, fragno, totalfrags, pname),90);
+    }
+    else
+    {
+      // create fragment (we know, there is at least one |)
+      debug_msg(sprintf("Received v2 packet %d, fragment %d/%d from %s.\n"
+                ,packetid, fragno, totalfrags, pname),90);
+      int header_end = strstr(buffer, "|");
+      f = (<fragment_s> header: buffer[0..header_end],
+                      buf: buffer[header_end+1 ..]
+          );
+    }
+
+    // get the sender
+    sender = peers[pname];
+    // if we don't know the sender yet, we create a new peer with its name.
+    // The ip and port will be updated during parsing, when the packet is
+    // complete.
+    if (!sender)
+    {
+      // if we can't create one, we discard the fragment
+      sender = add_new_peer( (<host_s> name: pname,
+                          first_contact: time(),
+                          last_contact: time(),
+                          mtu: 1024,
+                          received: m_allocate(5)
+                         ));
+      if (!sender)
+        return 0;
+    }
+    // did we already received a fragment? - get its packet
+    packet = sender->received[packetid];
+    // if not create new packet structure and add it to the receive queue of
+    // the peer
+    if (!packet)
+    {
+      packet = (<packet_s> id : packetid, timestamp: time(),
+                peername: pname,
+                fragments : allocate(totalfrags, 0)
+               );
+      m_add(sender->received, packetid, packet);
+    }
+
+    // now we can also check the signature, if there is one.
+    if (signature &&
+        validate_signature(signature, f->header+f->buf, sender))
+    {
+         packet->pflags |= FL_VALID_SIGNATURE;
+    }
+    else
+    {
+      // if there is no valid signature, but we received a fragment of this
+      // packet with valid signature, we discard the fragment.
+      if (packet->pflags & FL_VALID_SIGNATURE)
+      {
+        debug_msg(sprintf("Received fragment %d with invalid signature in "
+                          "signed packet %d from %s. Discarding fragment.\n"
+                          ,fragno, packetid, pname),40);
+        return 0;
+      }
+    }
+
+    // add fragment to its slot - if we receive a fragment twice, the last one
+    // arriving wins.
+    packet->fragments[fragno] = f;
+    // if not all fragments are received, we end processing here and wait for
+    // more.
+    if (member(packet->fragments, 0))
+      return 0;
+  }
+  else
+  {
+    // we assume a non-fragmented legacy packet, no real validation
+    // possible.
+    packet = (<packet_s> timestamp: time(),
+              buf: buffer
+             );
+
+    debug_msg(sprintf("Received v2 packet.\n"),90);
+  }
+  // at this point we have a complete packet - but we may have to defragment
+  // it yet.
+  if (packet->fragments)
+  {
+    defragment_packet(packet);
+    // and remove it from the receive queue of the sender (note, if we have
+    // fragments in the packet, we also have a valid sender at this point)
+    m_delete(sender->received, packet->id);
+    // and check for versions and flags - will be there for IM 2.5+ packets.
+    // Note: if the sender claims a version < 2000 here, this is also fine.
+    // If not there, we assume IM 2/Zebedee.
+    if (sscanf(buffer, "NAME:%~s|V:%d|$F:%d|%~s",
+               version, flags) == 4)
+    {
+      packet->pversion = version;
+      // store flags, but do not set the FL_VALID_SIGNATURE flag ;-)
+      packet->pflags |= (flags & ~FL_VALID_SIGNATURE);
+    }
+    else
+      packet->pversion = 2000;
+  }
+
+  // if the packet has a version >= 2500, it must be signed. If not, we
+  // discard it.
+  if (packet->pversion >= 2500
+      && !(packet->pflags & FL_VALID_SIGNATURE))
+  {
+    // But there is one exception: if we don't have a key for the
+    // peer yet, we accept the packet anyway. This is needed to receive its
+    // public key, which we are going to ask them (soon).
+    if (sender->key)
+    {
+      debug_msg(sprintf("Received unsigned packet from v2.5+ peer with "
+                        "key. Discarding."),40);
+      return 0; // key, but no valid signature - discard it.
+    }
+  }
+
+  // Maybe we accept only packets conforming to a specific protocol version
+  // (or newer). This can be used to accept only signated packets.
+  // also we don't accept protocol version older than we already saw for this
+  // peer to prevent downgrade attacks
+  if (packet->pversion < min_protocol_version)
+    return 0;
+
+  return packet;
+}
+
+/* Check wether a UDP request was valid. Is done after validating and parsing
+ * the packet (and all its fragments).
+ * Logs are made and "host" information is updated as appropriate.
+ * Arguments: Decoded UDP packet (struct packet_s)
+ *            sending peer (struct host_s)
+ * Returns:   0 for valid packets, an erro  r string otherwise.
+ */
+string validate_request(struct packet_s p, struct host_s sender)
+{
+  // If we have no peername, it was a legacy packet. Try to find the NAME in
+  // the packet payload.
+  if (!p->peername)
+  {
+    if(!member(p->data, NAME))
+      return "Name of sending peer not given.\n";
+    p->peername = lower_case(p->data[NAME]);
+  }
+
+  if (p->peername == self->name)
+    return "Someone tried to fake ourself!\n";
+
+  // if needed, check the payload for the ID. The ID may be absent...
+  if (!p->id)
+    p->id = p->data[ID];
+  // ... unless this is a reply to a request. And also, a reply can only be
+  // valid, when know about the initial request sent by us.
+  struct request_s req;
+  if (p->data[REQUEST] == REPLY)
+  {
+    if (!p->id)
+      return "Reply packet without packet ID.\n";
+    // and we take note of the request while we are at it, in case we need it.
+    req = requests[p->id];
+    if (!structp(req))
+      return "Reply packet without request.\n";
+  }
+
+  // if we still have no sender (legacy non-fragmented packets), we finally
+  // create one, because we know the sender by name now.
+  sender = peers[p->peername];
+  if (!sender)
+  {
+    // but if we can't create one, we discard the request
+    sender = add_new_peer( (<host_s> name: p->peername,
+                        first_contact: time(),
+                        last_contact: time(),
+                        mtu: 1024,
+                        received: m_allocate(3),
+                       ));
+    if (!sender)
+      return "New peer, but peer list too full.\n";
+  }
+
+  // we don't accept protocol version older than we already saw for this
+  // peer to prevent downgrade attacks.
+  if (p->pversion < sender->im_version)
+    return "Downgraded peer intermud version.";
+  else
+    sender->im_version = p->pversion;
+
+  // if we have a packet with version >= 2500 and no key for the peer
+  // yet, we ask them by sending a HELO packet (which also transmits our
+  // public key).
+  // Note: if we have a packet with version >= 2500, no valid signature, but
+  // already a key for the peer, validate_fragment() would already have
+  // discarded the packet/fragment and we would not be here.
+  // Of course, don't HELO, if this is a helo.
+  if (p->pversion >= 2500 && !sender->key
+      && p->data[REQUEST] != HELO)
+  {
+    // also don't HELO, if this is a response to a HELO...
+    if (!req || req->packet->data[REQUEST] != HELO)
+      send_helo(p->peername);
+  }
+
+  // Note: HELO packets or HELO reponses in packets without valid signature
+  // can't survive until here if we know a key for the peer. Therefore, we
+  // don't have to validate that specifically.
+
+  // TODO: e.g. some requests are allowed only in signed packets.
+
+  return 0; // request ok!
+}
+
+// processes the received packet or fragment. Must be called from the
+// function called by the mudlib master in this object.
+protected void process_fragment(string host, string msg, int hostport)
+{
+  // Catch if someone tries to fake ourself.
+  if (host == self->ip && hostport == self->port)
+    return;
+  debug_msg(sprintf("Received packet from %s:%d.\n",
+                    host, hostport), 100);
+  // First (try to) validate the received fragment (or packet).
+  struct host_s sender;
+  struct packet_s p = validate_fragment(msg, &sender);
+  // If we got a packet structure, the packet is complete and all the
+  // fragments were valid. If not, the packet is either invalid (and to be
+  // discarded) or not complete yet. In both cases, processing ends here.
+  if (!p)
+    return;
+
+  // first parse the packet into a mapping. And again, when not successful,
+  // 0 will be returned.
+  p = parse_packet(p);
+  if (!p)
+    return;
+
+  // record sending host data, may be important for validating the request.
+  p->data[HOST] = host;
+  p->data[UDP_PORT] = hostport;
+
+  debug_msg(sprintf("Packet valid and complete.\n"), 90);
+
+  // Validate the request...
+  string reason = validate_request(p, &sender);
+  if (reason)
+  {
+    debug_msg("validate_request(): discarding request: "+reason+"\n", 30);
+    return; //discard
+  }
+  sender->last_contact = time();
+  // if we received the packet from a host/port combination that is different
+  // from the one we know for this peers, we update the ip address and port.
+  // Note: if the packet was signed, the origin is proved by now. If not...
+  // well... We believe it now.
+  if (sender->ip != host || sender->port != hostport)
+  {
+    debug_msg(sprintf("Updating address of peer %s from %s:%d to %s:%d.\n",
+                      sender->name, sender->ip, sender->port,
+                      host, hostport), 40);
+    sender->ip = host;
+    sender->port = hostport;
+  }
+
+  touch_peer(sender->name);
+
+  // then execute/forward it.
+  struct service_s srv = services[p->data[REQUEST]];
+  if (srv)
+  {
+    if (!srv->recv_cb)
+   {
+      srv->recv_cb = symbol_function(srv->fun, find_object(srv->obname));
+      if (!srv->recv_cb)
+      {
+        // If no callable, the service is unregistered.
+        unregister_service(srv->name);
+        return;
+      }
+    }
+    funcall(srv->recv_cb, p->data);
+  }
+  else
+    // unknown service. Discard for now. //TODO:: send reply
+    return;
+
+} // process_fragment()
+
+
+// returns an increase in reputation (may be 0),
+// may be overloaded in inheritees.
+protected int auto_promote_peer(struct host_s p)
+{
+  // only if we heard from that peer in the last day.
+  if (p->last_contact + 86400 < time())
+    return 0;
+  // TODO: decide if auto-promotion above n should be tied to having a key.
+  if (p->reputation < 1
+      && p->first_contact < time() - 7*86400)
+    return 1;
+  if (p->reputation < 2
+      && p->first_contact < time() - 30*86400)
+    return 1;
+  if (p->reputation < 3
+      && p->first_contact < time() - 12*30*86400)
+    return 1;
+
+  return 0;
+}
+
+// Calculate the not-connected-time-to-live depending on the reputation. May
+// be overloaded in inheritees.
+protected int calc_ncttl(struct host_s p)
+{
+  switch(p->reputation)
+  {
+    case 0:
+      return 3*86400;
+    case 1:
+      return 14*86400;
+    case 2:
+      return 3*30*86400;
+    case 3:
+      return 6*30*86400;
+    default:
+      return 12*30*86400;
+  }
+}
+
+// checks if a peer should be expired (forgotten) and deletes it from our
+// known peer list if necessary.
+protected int check_and_delete_peer(struct host_s peer)
+{
+  if (peer->last_contact + peer->ncttl < time())
+  {
+    m_delete(peers, peer->name);
+  }
+}
+
+void reset()
+{
+  set_next_reset(600);
+  // housekeeping of peer list every 60 resets (6h).
+  reset_counter = ++reset_counter % 60;
+
+  // expire stale requests
+  foreach(int id, struct request_s r : requests)
+  {
+    if (r->timeout < time())
+    {
+      // in case of timeouts we call the callback, but with no data
+      funcall(r->callback, id, r->packet->data, 0);
+      m_delete(requests, id);
+    }
+  }
+
+  // peer housekeeping: increse reputation, expire peers
+  // expire fragments in all peers
+  foreach(string name, struct host_s peer : peers)
+  {
+    // should we do housekeeping of peer list?
+    if (!reset_counter)
+    {
+      // first check if peer should be expired, because we had no contact for an
+      // extended time.
+      if (check_and_delete_peer(peer))
+        continue;
+      // check if the reputation of a peer can be auto-promoted
+      peer->reputation += auto_promote_peer(peer);
+      peer->ncttl = calc_ncttl(peer);
+    }
+
+    if (!mappingp(peer->received) || !sizeof(peer->received))
+      continue;
+
+    // check all incomplete fragments
+    foreach(int id, struct packet_s pack : peer->received)
+    {
+      if (pack->timestamp + 120 < time())
+        m_delete(peer->received, id);
+      //TODO: send info to sender?
+    }
+  }
+}
+
+// *** Core services every implementation must have.
+
+// we received a reply to one of our pings
+protected void recv_ping_reply(int id, mapping request, mapping response)
+{
+  //TODO: what should we do?
+}
+
+// we received a ping request
+protected void recv_ping(mapping data)
+{
+  send(data[NAME], ([ID: data[ID], REQUEST:REPLY,
+                     DATA: self->name +" is alive!\n"]), 0);
+}
+
+// sends a ping request to a peer
+protected void send_ping(string mud)
+{
+  send(mud, ([ REQUEST: PING ]), #'recv_ping_reply);
+}
+
+// We received a reply for one of our QUERY requests.
+protected void recv_query_reply(int id, mapping request, mapping response)
+{
+  //TODO: what do we do with the information?
+}
+
+// send a query request asking for <prop>
+public void send_query(string name, string prop)
+{
+  if (!stringp(name) || !stringp(prop))
+    return;
+  send(name, ([REQUEST: QUERY, DATA: prop,
+               SENDER: getuid(previous_object()) ]), #'recv_query_reply);
+}
+
+// we received a reply request.
+protected void recv_query(mapping data)
+{
+  mapping ret;
+  switch(data[DATA])
+  {
+    case "commands":
+      ret = ([DATA: implode(self->services, ":") ]);
+      break;
+    case "email":
+      ret = ([DATA: EMAIL]);
+      break;
+    case "hosts":
+      string tmp="";
+      foreach(struct host_s p : peers)
+      {
+        tmp += p->name + ":" + p->ip + ":" + p->port
+               + ":" + implode(p->services, ",") + ":" +
+               implode(p->services, ",") + "\n";
+      }
+      ret = ([DATA: trim(tmp, TRIM_RIGHT, "\n") ]);
+      break;
+    case "inetd":
+    case "version":
+      ret = ([DATA: self->im_version ]);
+      break;
+    case "list":
+      ret = ([DATA: "commands,email,hosts,inetd,version,mud_port" ]);
+      break;
+    case "mud_port":
+      ret = ([DATA: query_mud_port() ]);
+      break;
+    case "time":
+      ret = ([DATA: time()]);
+      break;
+    default:
+      return; // Just ignore request for the time being.
+  }
+  ret[REQUEST] = REPLY;
+  ret[RECIPIENT] = data[SENDER];
+  ret[ID] = data[ID];
+  ret["QUERY"] = data[DATA];  //TODO: this is not right, right?
+
+  send(data[NAME], ret, 0);
+}
+
+// Called, when we receive a reply of OUR HELO request.
+protected void recv_helo_reply(int id, mapping request, mapping response)
+{
+  struct host_s peer = peers[response[NAME]];
+  // if we already have a key for the peer, we accept data and answer only if
+  // the packet was correctly signed. Although the check should be redundant,
+  // because then the packet should have been discarded earlier. But for this,
+  // better safe, than sorry.
+  if (peer->key
+      && !(response[FLAGS] & FL_VALID_SIGNATURE))
+    raise_error("Unexpected unsigned packed from "
+                + peer->name + ".\n");
+
+  mapping theirinfo = json_parse(response[DATA]);
+  // yes - this is a way to update a key... Sending a HELO packet signed
+  // with the old key
+  peer->key = response["pkey"];
+  if (response["mtu"] >= 1024)
+    peer->mtu = min(response["mtu"], MAX_UDP_LENGTH);
+  if (pointerp(data["services"]))
+    peer->services = data["services"];
+}
+
+// we received a HELO packet, send back our info.
+protected void recv_helo(mapping data)
+{
+  struct host_s peer = peers[data[NAME]];
+  // if we already have a key for the peer, we accept data and answer only if
+  // the packet was correctly signed. Although the check should be redundant,
+  // because then the packet should have been discarded earlier. But for this,
+  // better safe, than sorry.
+  if (peer->key
+      && !(data[FLAGS] & FL_VALID_SIGNATURE))
+    raise_error("Unexpected unsigned packed from "
+                + peer->name + ".\n");
+
+  mapping theirinfo = json_parse(data[DATA]);
+  // yes - this is a way to update a key... Sending a HELO packet signed
+  // with the old key
+  peer->key = theirinfo["pkey"];
+  if (theirinfo["mtu"] >= 1024)
+    peer->mtu = min(theirinfo["mtu"], MAX_UDP_LENGTH);
+  if (pointerp(theirinfo["services"]))
+    peer->services = theirinfo["services"];
+
+  mapping ourinfo = (["mtu": self->mtu, "pkey": self->key,
+                      "services": self->services ]);
+  send(data[NAME], ([ID: data[ID], REQUEST:REPLY,
+                     DATA: json_serialize(ourinfo) ]), 0 );
+}
+
+// send a HELO packet.
+protected void send_helo(string name)
+{
+  mapping ourinfo = (["mtu": self->mtu, "pkey": self->key,
+                      "services": self->services]);
+  send(name, ([REQUEST:HELO,
+                     DATA: json_serialize(ourinfo) ]), #'recv_helo_reply );
+}
+
+// We received some reply for one of our requests - find our initial request
+// and forward the data to the callback.
+protected void recv_reply(mapping data)
+{
+  // validate_request ensured that we have the initial request.
+  int pid = data[ID];
+  debug_msg(sprintf("Received answer for request %d\n",pid),90);
+  struct request_s req = requests[pid];
+  m_delete(requests, pid);
+  funcall(req->callback, pid, req->packet->data, data);
+}
+
+/*
+ * Make a PING request to all muds in the "hosts" mapping to set
+ * HOSTLIST_STATUS information.
+ * But don't ping all muds at once, because that may overflow the callout
+ * table during mud startup, when hundreds of objects make callouts.
+ */
+void ping_many_muds(string *muds)
+{
+  if (!pointerp(muds))
+    muds=m_indices(peers);
+  if (!sizeof(muds))
+    return;
+  string *part;
+  if (sizeof(muds) > 9)
+    part=muds[0..9];
+  else
+    part=muds;
+  foreach(string mud: part)
+    send_ping(mud);
+  muds -= part;
+  if (sizeof(muds))
+    call_out(#'ping_many_muds, 4, muds);
+}
+
+protected void create()
+{
+  if (object_name(this_object()) == __FILE__[0..<3])
+  {
+    set_next_reset(-1);
+    return;
+  }
+  set_user();
+  restore_me();
+  init();
+}
+
+protected void create_super()
+{
+  set_next_reset(-1);
+}
+
+int remove(int silent)
+{
+  reset();
+  save_me();
+  destruct(this_object());
+  return 1;
+}
+
diff --git a/secure/inetd/inetd_compat.c b/secure/inetd/inetd_compat.c
new file mode 100644
index 0000000..373b376
--- /dev/null
+++ b/secure/inetd/inetd_compat.c
@@ -0,0 +1,15 @@
+#pragma strict_types, save_types, rtt_checks, pedantic
+#pragma no_clone, no_shadow
+
+// portability - lfuns implementing efuns from LDMud
+protected string _json_serialize(mixed v)
+{
+  return json_serialize(v);
+}
+
+protected mixed _json_parse(string s)
+{
+  return json_parse(s);
+}
+
+
diff --git a/secure/inetd/man.c b/secure/inetd/man.c
new file mode 100644
index 0000000..c7218e0
--- /dev/null
+++ b/secure/inetd/man.c
@@ -0,0 +1,114 @@
+#pragma strict_types, save_types, rtt_checks, pedantic
+#pragma no_clone, no_shadow, no_inherit
+
+inherit __DIR__"i_service";
+
+#include <intermud.h>
+#include <regexp.h>
+#include <living/comm.h>
+#include <defines.h>
+#include <daemon/mand.h>
+
+#define INETD_MANPAGE_FOUND "[ManD@%s] Folgende Seite wurde gefunden: %s\n"
+//                              mud                                 file
+#define INETD_NO_MANPAGE    "[ManD@%s] Keine Hilfeseite gefunden fuer '%s'.\n"
+//                              mud                                 page
+#define INETD_MANPAGES      "[ManD@%s] Die folgenden Seiten passen:\n" \
+                             "%'-'78.78s\n%s%'-'78.78s\n"
+//                                mud  "" pagelist ""
+#define INETD_MAN_REQUESTED "%s@%s: Abfrage abgeschickt.\n"
+//                             page  mud#ifndef LOCAL_NAME
+
+#define LOCAL_NAME "MorgenGrauen"
+
+// we received a reply to one of our requests.
+// Note: if response is 0, the peer did not respond (timeout)...
+protected void recv_reply(int id, mapping request, mapping response)
+{
+  if(response)
+  {
+    if (stringp(response[RECIPIENT]))
+    {
+      object ob = find_player(response[RECIPIENT])
+                  || find_object(response[RECIPIENT]);
+      if (ob)
+      {
+          ob->ReceiveMsg(regreplace(response[DATA],"[:^print:]|\n","",1),
+                         MT_NOTIFICATION, "hilfe", 0, ME);
+    }
+  }
+  else
+  {
+    // no response, timeout...
+    if (request[SENDER] && stringp(request[SENDER]))
+    {
+      object ob = find_player(request[SENDER])
+                  || find_object(request[SENDER]);
+      if (ob)
+          ob->ReceiveMsg("Das Mud \'" + request[NAME] +
+                         "\' konnte nicht erreicht werden.\n",
+                         MT_NOTIFICATION, "hilfe", "remote man: ", ME);
+    }
+  }
+}
+
+// we received an intermud manpage request
+protected void recv_request(mapping data)
+{
+  string manpage = data[DATA];
+  string *tmp = explode(manpage,"/");
+  if (sizeof(tmp)>1)
+  {
+    if (file_size(MAND_DOCDIR+manpage)>=0)
+      tmp=({tmp[<1], manpage});
+    else
+      tmp=({});
+  }
+  else
+    tmp=(string *)call_other(MAND,"locate",data[DATA],0);
+  // Gilden ausfiltern
+  mapping pages = ([]);
+  int index=sizeof(tmp);
+  while(index--)
+  {
+    if (tmp[1][0..1]!="g.") pages[tmp[index]]=tmp[index-1];
+    index--;
+  }
+  string ret;
+  switch(sizeof(pages))
+  {
+    case 0:
+      ret=sprintf(INETD_NO_MANPAGE,LOCAL_NAME,manpage);
+      break;
+    case 1:
+      tmp=m_indices(pages)[0];
+      ret=sprintf(INETD_MANPAGE_FOUND,LOCAL_NAME,pages[tmp]);
+      index=0;
+      while(manpage=read_file(MAND_DOCDIR+tmp,index))
+      {
+        ret+=manpage;
+        index+=MAX_READ_FILE_LEN;
+      }
+      break;
+    default:
+      ret=sprintf(INETD_MANPAGES,LOCAL_NAME,"",
+                  break_string(implode(m_values(pages)," "),78),"");
+      break;
+  }
+  INETD->send(data[NAME],
+              ([REQUEST: REPLY, RECIPIENT: data[SENDER], ID: data[ID],
+                DATA: ret
+                ]),
+              0);
+}
+
+// Old interface. Request remote man page via intermud
+public string send_request(string mudname, string pagename)
+{
+  if (request(mudname, pagename))
+      return sprintf(INETD_MAN_REQUESTED,pagename,mudname);
+
+  return "Fehler beim Anfordern.\n";
+}
+
+
diff --git a/secure/inetd/tell.c b/secure/inetd/tell.c
new file mode 100644
index 0000000..539cca1
--- /dev/null
+++ b/secure/inetd/tell.c
@@ -0,0 +1,160 @@
+#pragma strict_types, save_types, rtt_checks, pedantic
+#pragma no_clone, no_shadow, no_inherit
+
+inherit __DIR__"i_service";
+
+#include <intermud.h>
+#include <regexp.h>
+#include <living/comm.h>
+#include <defines.h>
+
+// we received a reply to one of our messages.
+// Note: if response is 0, the peer did not respond (timeout)...
+protected void recv_tell_reply(int id, mapping request, mapping response)
+{
+  if(response)
+  {
+    if (stringp(response[RECIPIENT]))
+    {
+      object ob = find_player(response[RECIPIENT])
+                  || find_object(response[RECIPIENT]);
+      if (ob)
+      {
+          currentname = capitalize(data[SENDER])+"@"+capitalize(data[NAME]);
+          ob->ReceiveMsg(regreplace(response[DATA],"[:^print:]|\n","",1),
+                         MT_COMM|MT_FAR, MA_TELL, 0, ME);
+          currentname = 0;
+      }
+  }
+  else
+  {
+    // no response, timeout...
+    if (request[SENDER] && stringp(request[SENDER]))
+    {
+      object ob = find_player(request[SENDER])
+                  || find_object(request[SENDER]);
+      if (ob)
+          ob->ReceiveMsg("Das Mud \'" + request[NAME] +
+                         "\' konnte nicht erreicht werden.\n",
+                         MT_NOTIFICATION, MA_TELL, Name(0), ME);
+    }
+  }
+}
+
+// we received an intermud tell message
+protected void recv_tell(mapping data)
+{
+  object ob;
+  string msg;
+
+  if (data[RECIPIENT] &&
+       (ob = find_player(lower_case(data[RECIPIENT]))) &&
+       interactive(ob))
+  {
+    if (!stringp(data[SENDER]) || !sizeof(data[SENDER]))
+        data[SENDER]="<Unknown>";
+
+    currentname = data[SENDER] + +"@"+data[NAME];
+
+    if (!stringp(data[DATA]) || !sizeof(data[DATA]))
+        msg="<Nichts>";
+    else
+        msg= regreplace(data[DATA],"[:^print:]|\n","",1);
+
+    message_prefix=capitalize(data[SENDER])+"@"+data[NAME]+
+                   " teilt Dir mit: ";
+
+    int ret = ob->ReceiveMsg(msg, MT_COMM|MT_FAR,
+                             MA_TELL, message_prefix, ME);
+    currentname = 0;
+
+    // Wenn Empfaenger unsichtbar ist, bekommt der Absender immer die gleiche
+    // Meldung, egal was das Ergebnis von ReceiveMsg() ist.
+    if (ob->QueryProp(P_INVIS))
+    {
+      msg ="\nRoot@"MUDNAME": Spieler "+
+                        capitalize(data[RECIPIENT])+
+                        " finde ich in "MUDNAME" nicht!\n";
+    }
+    // Sonst kriegt er eine sinnvolle Angabe.
+    else
+    {
+      switch(ret)
+      {
+        case MSG_DELIVERED:
+          msg = break_string(data[DATA], 78,
+                             "Du teilst "+capitalize(data[RECIPIENT]) + "@"
+                             LOCAL_NAME + " mit: ");
+          if(ob->QueryProp(P_AWAY))
+            msg=sprintf("%s%s@"MUDNAME" ist gerade nicht da: %s\n",
+                       msg, ob->name(WER),ob->QueryProp(P_AWAY));
+          else if ((i=query_idle(ob))>=600)
+          { // 10 Mins
+            string away;
+            if (i<3600)
+                away=time2string("%m %M",i);
+            else
+                away=time2string("%h %H und %m %M",i);
+            msg = sprintf("%s%s@"MUDNAME" ist seit %s voellig untaetig.\n",
+                          msg, ob->Name(WER), away);
+          }
+          break;
+        case MSG_BUFFERED:
+          msg= ob->Name(WER) + " moechte gerade nicht gestoert werden."
+              "Die Mitteilung wurde von einem kleinen Kobold in Empfang "
+              "genommen. Er wird sie spaeter weiterleiten!";
+          break;
+        case MSG_IGNORED:
+        case MSG_VERB_IGN:
+        case MSG_MUD_IGN:
+          msg = ob->Name(WER) + " hoert gar nicht zu, was Du sagst.";
+          break;
+        case MSG_SENSE_BLOCK:
+          msg = ob->Name(WER) + " kann Dich leider nicht wahrnehmen.";
+              break;
+        case MSG_BUFFER_FULL:
+          msg = ob->Name(WER) + " moechte gerade nicht gestoert werden."
+              "Die Mitteilung ging verloren, denn der Kobold kann sich "
+              "nichts mehr merken!";
+            break;
+        default:
+          msg = ob->Name(WER) + " hat Deine Nachricht leider nicht "
+              "mitbekommen.";
+          break;
+      }
+    }
+  }
+  // Spieler nicht gefunden.
+  else
+      msg ="\nRoot@"MUDNAME": Spieler "+
+                        capitalize(data[RECIPIENT])+
+                        " finde ich in "MUDNAME" nicht!\n";
+
+  INETD->send(data[NAME],
+              ([REQUEST: REPLY, RECIPIENT: data[SENDER],
+                ID: data[ID],
+                DATA: msg]), 0);
+
+}
+
+public int send(string name, string mud, string msg)
+{
+  return INETD->send(mud,
+                     ([REQUEST: "tell", RECIPIENT: name,
+                       SENDER: getuid(previous_object()),
+                       DATA: msg
+                       ]), #'recv_tell_reply);
+}
+
+protected void create()
+{
+  INETD->register_service("tell", #'recv_tell);
+}
+
+public varargs int remove(int silent)
+{
+  INETD->unregister_service("tell");
+  destruct(this_object());
+}
+
+
diff --git a/secure/inetd/who.c b/secure/inetd/who.c
new file mode 100644
index 0000000..cc2bdef
--- /dev/null
+++ b/secure/inetd/who.c
@@ -0,0 +1,68 @@
+#pragma strict_types, save_types, rtt_checks, pedantic
+#pragma no_clone, no_shadow, no_inherit
+
+inherit __DIR__"i_service";
+
+#include <intermud.h>
+#include <config.h>
+
+int maxtoday, maxever;
+
+public string service_name()
+{
+  return "who";
+}
+
+public void reset()
+{
+  string tmp1=read_file("/etc/maxusers.ever",0,1);
+  string tmp2=read_file("/etc/maxusers",0,1);
+  if (stringp(tmp1)&&sizeof(tmp1)) sscanf(tmp1,"%d %~s",maxever);
+  if (stringp(tmp2)&&sizeof(tmp2)) sscanf(tmp2,"%d %~s",maxtoday);
+}
+
+protected void create()
+{
+  ::create();
+  reset();
+}
+
+string adjust(string str,int wid) {
+  return sprintf("%*|s",wid,str);
+}
+
+// we received an intermud who request
+protected void recv_request(mapping data)
+{
+  string *lines="/obj/werliste"->QueryWhoListe(0, 1);
+  string wholiste=implode(lines,"\n");
+
+  lines=({
+    "*------------------------------------------------------------------------*",
+    "","","","",
+    "*------------------------------------------------------------------------*"
+  });
+  string header=MUDNAME", LDMud "+__VERSION__;
+  lines[1]="|"+adjust(header,sizeof(lines[0])-2)+"|";
+  header="Adresse: MG.Mud.DE (87.79.24.60) 23 (alternativ 4711)";
+  lines[2]="|"+adjust(header,sizeof(lines[0])-2)+"|";
+  header="Uptime: "+uptime();
+  lines[3]="|"+adjust(header,sizeof(lines[0])-2)+"|";
+  header=_MUDLIB_NAME_"-Mudlib "_MUDLIB_VERSION_;
+  lines[4]="|"+adjust(header,sizeof(lines[0])-2)+"|";
+  header=implode(lines,"\n");
+
+  wholiste=header+"\n"+wholiste+
+    sprintf("\n*** Anwesende im "MUDNAME": Max. heute %d, Rekord %d\n",
+        maxtoday,maxever);
+
+  INETD->send(data[NAME],
+              ([REQUEST: REPLY, ID: data[ID],
+                RECIPIENT: data[SENDER],
+                DATA: wholiste
+                ]), 0);
+}
+
+//TODO: request() ?
+//public int request(string mudname, string|int data)
+
diff --git a/secure/inetd/www.c b/secure/inetd/www.c
new file mode 100644
index 0000000..610a656
--- /dev/null
+++ b/secure/inetd/www.c
@@ -0,0 +1,143 @@
+#pragma strict_types, save_types, rtt_checks, pedantic
+#pragma no_clone, no_shadow, no_inherit
+
+inherit __DIR__"i_service";
+
+#include <intermud.h>
+#include <properties.h>
+
+#include <udp.h>
+#include <www.h>
+
+#undef DEBUG 
+
+private mixed pending; // pending udp requests
+
+public string service_name()
+{
+  return "www";
+}
+
+// HTMLunescape -- try to resolve %<hex> escape codes from HTML/WWW
+private string HTMLunescape(string char)
+{
+  int len;
+  if(!char || !(len = sizeof(char))) return "";
+  if(char[0] == '%' && len = 3) {
+    int val, ch;
+    while(--len) {
+      switch(char[len]) {
+      case '0'..'9':
+        val = (int)char[len..len];
+        break;
+      case 'A'..'F':
+        val = char[len]-55;
+        break;
+      }
+      if(len < 2) val <<= 4;
+      ch += val;
+    }
+    return sprintf("%c", ch);
+  }
+  return char;
+}
+
+private string translate(string str)
+{
+  return implode(map(regexplode(str, "[%].."), #'HTMLunescape/*'*/), "");
+}
+
+// decode() -- decode the input cmds string
+private mapping decode(string input)
+{
+  mixed tmp; int i;
+  mapping cmds;
+  cmds = ([]);
+  i = sizeof(tmp = old_explode(translate(input), "&"));
+  while(i--)
+  {
+    if(sizeof(tmp[i] = old_explode(tmp[i], "=")) == 2)
+      cmds[tmp[i][0]] = tmp[i][1];
+  }
+  return cmds;
+}
+
+// put() -- put together a key and a value
+private string put(string key, mapping val)
+{
+  return key+"="+val[key];
+}
+// encode() -- encode the input cmds string
+private string encode(mapping input)
+{ return implode(map(m_indices(input), #'put/*'*/, input), "&"); }
+
+
+private string exch(string str, mapping to)
+{
+  if(!to[str]) return str;
+  return to[str];
+}
+
+private string xcode(string str, mapping to)
+{
+  return implode(map(regexplode(str, implode(m_indices(to), "|")),
+                           #'exch/*'*/, to), "");
+}
+
+public void Send(mapping data, string text, mixed back)
+{
+  if(strstr(object_name(previous_object()), __DIR__)) 
+      return;
+
+  if(!data && !pending)
+      return;
+  else if(!data && pending)
+  {
+    data = pending[0]; back = pending[1]; pending = 0;
+  }
+  INETD->send(data[NAME],
+              ([
+                REQUEST: REPLY,
+                RECIPIENT: data[SENDER],
+                ID: data[ID],
+                "URL":data[DATA],
+                DATA: "\n\n"+text+"\n"
+                ]),
+              0);
+}
+
+// we received an intermud request
+protected void recv_request(mapping data)
+{
+  string text, error;
+  string back; int size;
+  mapping cmds;
+
+  error = catch(size = sizeof(cmds = decode(data[DATA])));
+
+  if(cmds[BACK])
+    back = xcode(cmds[BACK], (["XampX":"&", "XeqX":"="]));
+  cmds[BACK] = xcode(encode(cmds-([BACK])), (["&":"XampX", "=":"XeqX"]));
+  if(error ||
+     error=catch(text=(object_name(this_object())+"."+cmds[REQ])->Request(cmds)))
+  {
+#ifdef DEBUG
+    text = "<H1>Fehler: "+error+"</H1><HR><H3>Kontext:</H3>"
+   + "<PRE>"+sprintf("%O", data)+"</PRE>";
+#else
+    text = "<H1>Fehler: Anfrage konnte nicht bearbeitet werden!</H1>";
+#endif
+    log_file(WWWLOG, "ERROR: "+error[0..<2]+", DATA FOLLOWS:\n");
+  }
+
+  log_file(WWWLOG, sprintf("[%s] %s\n", ctime(time())[4..15], data[DATA]));
+
+  if(cmds[REQ] == R_INTERMUD && !text)
+  {
+    pending = ({data, back});
+    return 0;
+  }
+  pending = 0;
+  funcall(#'Send, data, text, back);
+}
+
diff --git a/secure/inetd/www.finger.c b/secure/inetd/www.finger.c
new file mode 100644
index 0000000..36abb39
--- /dev/null
+++ b/secure/inetd/www.finger.c
@@ -0,0 +1,72 @@
+// MorgenGrauen MUDlib
+//
+// www.finger.c
+//
+// $Id: www.finger.c 8755 2014-04-26 13:13:40Z Zesstra $
+
+#pragma strict_types, save_types, rtt_checks, pedantic
+#pragma no_clone, no_shadow, no_inherit
+
+#include <properties.h>
+#include <www.h>
+#include <regexp.h>
+
+public string Request(mapping cmds)
+{
+  if(!sizeof(cmds) || !stringp(cmds[USER]))
+    return ERROR("Kein Nutzer angegeben!");
+  /*
+   * Kann ja sein, dass ein Spieler auf die Idee kommt, HTML-Tags
+   * in seine Beschreibung einzubauen. Unsere Seite ist aber schon
+   * interaktiv genug. (Anm: Nur <.*>-Vorkommnisse zu ersetzen nutzt
+   * nix, da man auch mit einzelnen Zeichen Schaden machen kann.
+   */
+  string result = regreplace(FINGER("-a "+cmds[USER]), "<","\\&lt;",1); 
+  result = regreplace(result, ">","\\&gt;",1);
+  string *reslines = explode(result,"\n");
+  /*
+   * Grund des kommenden Codeblocks ist , dass manche Spieler ihre
+   * Homepage mit "http://mg.mud.de" angeben, andere nur"mg.mud.de" 
+   * schreiben. Damit aber der Browser den Link als absolut interpretiert, 
+   * muss das http:// davor stehen, und zwar nur einmal. 
+   */
+  string *tmp = regexp(reslines,"^Homepage:");
+  if (sizeof(tmp)&&stringp(tmp[0])&&sizeof(tmp[0])>16) {
+	  string tmp2;
+    string quoted = regreplace(tmp[0],"([[\\]+*?.\\\\])","\\\\\\1", 1);
+    if (tmp[0][10..16]=="http://")
+      tmp2=sprintf("Homepage: <A HREF=\"%s\">%s</A>",
+		      tmp[0][10..],tmp[0][10..]);
+    else
+      tmp2=sprintf("Homepage: <A HREF=\"http://%s\">%s</A>",
+		      tmp[0][10..],tmp[0][10..]);
+    result = regreplace(result,quoted,tmp2,1);
+  }
+  tmp = regexp(reslines,"^Avatar-URI:");
+  if (sizeof(tmp)) {
+     result = regreplace(result,
+             "Avatar-URI: ([^\n]*)",
+             "Avatar-URI: <a href=\\1>\\1</a>",1); 
+    if (sizeof(regexp(({tmp[0]}),"http[s]{0,1}://",RE_PCRE))) {
+      string uri = regreplace(tmp[0], "Avatar-URI: ([^\n]*)", "\\1",1);
+      result = "<img src=\""+uri+"\" height=150 alt=\"" + capitalize(cmds[USER])
+               + "\" /><br>"
+               + result;
+    }
+  }
+
+  result = regreplace(result,
+		      "E-Mail-Adresse: ([^\n]*)",
+		      "E-Mail-Adresse: Bitte nachfragen...",1);
+
+  result = regreplace(result,
+		      "Messenger: ([^\n]*)",
+		      "Messenger: Bitte nachfragen...", 1);
+
+  result = regreplace(result,
+		      "ICQ: ([^\n]*)",
+		      "ICQ: Bitte nachfragen...", 1);
+
+  return "<H2>Wer ist "+capitalize(cmds[USER])+"?</H2><HR>"
+    +"<PRE>"+result+"</PRE>";
+}
diff --git a/secure/inetd/www.news.c b/secure/inetd/www.news.c
new file mode 100644
index 0000000..55c1c1c
--- /dev/null
+++ b/secure/inetd/www.news.c
@@ -0,0 +1,215 @@
+// MorgenGrauen MUDlib
+//
+// www.news.c -- WWW frontend for reading mud news
+//
+// $Id: www.news.c 8755 2014-04-26 13:13:40Z Zesstra $
+
+#pragma strict_types, save_types, rtt_checks, pedantic
+#pragma no_clone, no_shadow, no_inherit
+
+#include <www.h>
+
+#define DBG(x)  tell_object(find_player("hate"), sprintf("DBG: %O\n", x))
+
+#define NEWSD "/secure/news"
+
+#define N_GROUP 0
+#define N_AUTHOR 1
+#define N_TID 2
+#define N_DATE 3
+#define N_TITLE 4
+#define N_ARTICLE 5
+
+varargs private string url(string group, string article)
+{
+  return "\""+MUDWWW+"?"+REQ+"="+R_NEWS
+    +(group?"&"+GROUP+"="+group:"")
+    +(article?"&"+ARTICLE+"="+article:"")+"\"";
+}
+
+varargs private string make_link(string text, string group, string article)
+{
+  if(!text || !sizeof(text)) text = "-Unbenannt-";
+  return "<A HREF="+url(group, article)+">"+text+"</A>";
+}
+
+private string GroupList()
+{
+  string *list, ret;
+  int i, t;
+
+  list = NEWSD->GetGroups();
+  for (i = 0, ret = ""; i < sizeof(list); i++)
+    ret += sprintf("[%3d Artikel, %-6s] %s\n",
+                   sizeof(NEWSD->GetNotes(list[i])),
+                   dtime(t = NEWSD->GetNewsTime(list[i]))[5..11],
+                   make_link(list[i],list[i]+":"+t));
+  return "<H2>&Uuml;bersicht</H2>"
+        +"<H3>["+sizeof(list)+" Gruppen, "
+        +"letzte &Auml;nderung "+dtime(NEWSD->GetNewsTime())+"]</H3>"
+        +"<PRE>" + ret +"</PRE>";
+}
+
+#define THREAD(a) ("~#! rn="+(a[N_AUTHOR])+" rt="+(a[N_DATE])+ \
+                   " rg="+(a[N_GROUP]))
+#define TEXPR "rn=[^\n ]*|rt=[^\n ]*|rg=[^\n ]*"
+
+private mixed tinfo(mixed article)
+{
+  mixed tmp, info;
+  string rn, rt, rg, tid;
+  int j, k;
+
+  tmp = regexp(old_explode(article[N_ARTICLE], "\n"), "^~#!");
+  for(j = sizeof(tmp); j--;) {
+    mixed line;
+    line = old_explode(tmp[j], " ");
+    for(k = sizeof(line); k--;) {
+      if(line[k][0..1]=="rn") rn = line[k][3..];
+      if(line[k][0..1]=="rt") rt = line[k][3..];
+      if(line[k][0..1]=="rg") rg = line[k][3..];
+      if(line[k][0..2]=="tid") tid = line[k][4..];
+    }
+  }
+  if(!tid) tid = ""+article[N_DATE];
+  return ({ rn, rt, rg, tid });
+}
+
+#define RN  0
+#define RT  1
+#define RG  2
+#define TID 3
+
+private int thread(mixed article, int i, mapping t)
+{
+  mixed info;
+  info = tinfo(article);
+
+  if(info[TID]) {
+    t[info[TID]]++;
+    t[info[TID], 1] = sprintf("%3.3d [%-12s %-6s]%-3s %s\n",
+            i+1,
+            article[N_AUTHOR]+":",
+            dtime(article[N_DATE])[5..11],
+            (t[info[TID]] - 1) ? "+"+(t[info[TID]]-1) : " - ",
+            make_link(article[N_TITLE],
+          article[N_GROUP], to_string(i)))
+      + (t[info[TID], 1] ? t[info[TID], 1] : "");
+    t[info[TID], 2] = info;
+    if(article[N_DATE] > to_int(t[info[TID], 3]))
+      t[info[TID], 3] = ""+article[N_DATE];
+    return 1;
+  }
+}
+
+private int expired(mixed *list, int i)
+{
+  mixed info;
+  info = tinfo(list[i]);
+  for(i--; i >= 0; i--)
+    if(list[i][N_AUTHOR] == info[RN] &&
+       list[i][N_DATE] == to_int(info[RT]))
+      return 0;
+  return 1;
+}
+
+private string ArticleList(string group)
+{
+  string *list, ret;
+  mapping t;
+  int i;
+
+  list = NEWSD->GetNotes(group = old_explode(group, ":")[0]);
+  if (!pointerp(list)) {
+  return "<H2>Gruppe: "+group+"</H2>"
+    "<H3>existiert nicht.</H3>"
+    "["+make_link("Gruppen&uuml;bersicht")+"]";
+  }
+  t = m_allocate(0,4);
+  for (i = sizeof(list)-1, ret = ""; i >= 0; i--)
+    if(!thread(list[i], i, t) || expired(list, i))
+    {
+      int ttmp;
+      string tid;
+      mixed tt; tt = tinfo(list[i]);
+      ttmp = t[tid = tt[TID]] - 1;
+      ret = sprintf("%3.3d [%-12s %-6s]%-3s %s\n",
+		    i+1,
+                    list[i][N_AUTHOR]+":",
+                    dtime(list[i][N_DATE])[5..11],
+                    ttmp > 0 ? "+"+ttmp : " - ",
+                    make_link((string)list[i][N_TITLE]
+            +(ttmp > 0 ?
+        " ("+dtime(to_int(t[tid, 3]))[5..11]
+        +dtime(to_int(t[tid, 3]))[17..]+")" : ""),
+            group, to_string(i)+":"+t[tid, 3])) + ret;
+    }
+
+  return "<H2>Gruppe: "+group+"</H2>"
+    +"<H3>["+sizeof(list)+" Artikel, "
+    +"letzte &Auml;nderung "+dtime(NEWSD->GetNewsTime(group))+"]</H3>"
+    +"<PRE>" + ret + "</PRE>"
+    +"["+make_link("Gruppen&uuml;bersicht")+"]";
+}
+
+private varargs string Message(string group, mixed article)
+{
+  mixed text, tmp, ttmp, next, prev, info;
+  string art;
+  mapping t;
+  int i;
+
+  if (!article) article = 0;
+  else article = to_int(old_explode(article, ":")[0]);
+
+  tmp = NEWSD->GetNotes(group = old_explode(group, ":")[0]);
+  if (pointerp(tmp) && (article >= sizeof(tmp)))
+      return("Artikel nicht gefunden, soviele Artikel hat diese Rubrik "
+	  "nicht!\n");
+
+  text = tmp[article];
+
+  t = m_allocate(0,4);
+  for(i = sizeof(tmp)-1; i > article; i--)
+    thread(tmp[i], i, t);
+  next = "N&auml;chster Artikel";
+  prev = "Voriger Artikel";
+
+  art = implode((ttmp = old_explode(text[N_ARTICLE], "\n"))
+                - regexp(ttmp, "^~#!"), "\n");
+
+  art = regreplace(art, "<", "\\&lt;", 1);
+  art = regreplace(art, ">", "\\&gt;", 1);
+  art = regreplace(art, "([a-zA-Z][a-zA-Z]*://[^ \n\t][^ \n\t]*)", "<a href=\"\\1\">\\1</a>", 1);
+
+  info = tinfo(text);
+
+  return "<H1>" + text[N_TITLE] + "</H1><HR>\n"
+       + "<H3>" + group + ": " + text[N_AUTHOR]
+       + " (Artikel " + (article + 1) + ", " + dtime(text[N_DATE]) + ")\n</H3>"
+       + (info[RN] ? ("<H4>Antwort auf "+info[RN]+
+          (expired(tmp, article) ? " (verfallen)" : "")+"</H4>")
+    : "")
+       + "<PRE>" + art + "</PRE>\n"
+       + (t[info[TID]] ?
+          "<HR>Weitere Artikel zu diesem Thema:"
+  + "<PRE>" + t[info[TID], 1] + "</PRE><HR>" : "")
+       + " ["+(article < sizeof(tmp)-1 ? make_link(next, group,to_string(article+1)) :
+              next)+"]"
+       + " ["+(article ? make_link(prev, group, to_string(article-1)) : prev)+"]"
+       + " ["+make_link("Artikel&uuml;bersicht", group)+"]"
+       + " ["+make_link("Gruppen&uuml;bersicht")+"]";
+}
+
+public string Request(mapping cmds)
+{
+  string text;
+  if(!cmds[GROUP]) text = GroupList();
+  else
+    if(!cmds[ARTICLE]) text = ArticleList(cmds[GROUP]);
+    else text = Message(cmds[GROUP], cmds[ARTICLE]);
+
+  return "<H2>"+MUDNAME+" Zeitung</H2><HR>"
+        +text;
+}
+
diff --git a/secure/inetd/www.who.c b/secure/inetd/www.who.c
new file mode 100644
index 0000000..e5b2526
--- /dev/null
+++ b/secure/inetd/www.who.c
@@ -0,0 +1,33 @@
+#pragma strict_types, save_types, rtt_checks, pedantic
+#pragma no_clone, no_shadow, no_inherit
+
+#include <www.h>
+
+private string MakeLink(mixed entry)
+{
+  string nm;
+  int idx;
+
+  entry[1] = regreplace(entry[1], "<", "\\&lt;", 1);
+  entry[1] = regreplace(entry[1], ">", "\\&gt;", 1);
+  nm = getuid(entry[0]);
+  if(nm == " R O O T ") return "<TT>"+entry[1][0..2]+"</TT>"+entry[1][3..];
+  idx = strstr(lower_case(entry[1]), nm);
+  return "<TT>"+entry[1][0..2]+"</TT>"+entry[1][3..idx-1]
+       + "<A HREF=\""+MUDWWW+"?"+REQ+"="+R_FINGER+"&"+USER+"="+nm+"\"><B>"
+       + entry[1][idx..idx = idx+sizeof(nm)]
+       + "</B></A>"
+       + entry[1][idx+1..];
+}
+
+public string Request(mapping cmds)
+{
+  string *who, *list; int i, s;
+  if (!sizeof(cmds)) return ERROR("Anfrage ung&uuml;ltig!");
+  who = allocate(s = sizeof(list = WHO));
+  for(i = s; i--; i > 0) 
+    who[i] = MakeLink(list[s - i - 1]);
+  // who = map(WHO, #'MakeLink/*'*/);
+  return "<H2>Wer ist gerade in "MUDNAME"?</H2><HR>"
+       + "<OL><LI>"+implode(who, "\n<LI>")+"</OL>";
+}
diff --git a/secure/inetd_compat.c b/secure/inetd_compat.c
new file mode 100644
index 0000000..373b376
--- /dev/null
+++ b/secure/inetd_compat.c
@@ -0,0 +1,15 @@
+#pragma strict_types, save_types, rtt_checks, pedantic
+#pragma no_clone, no_shadow
+
+// portability - lfuns implementing efuns from LDMud
+protected string _json_serialize(mixed v)
+{
+  return json_serialize(v);
+}
+
+protected mixed _json_parse(string s)
+{
+  return json_parse(s);
+}
+
+
diff --git a/sys/intermud.h b/sys/intermud.h
new file mode 100644
index 0000000..12ee567
--- /dev/null
+++ b/sys/intermud.h
@@ -0,0 +1,52 @@
+#define INETD "/secure/inetd/inetd"
+
+// ****** Defines for field names ****************
+#define RECIPIENT       "RCPNT"
+#define REQUEST         "REQ"
+#define SENDER          "SND"
+/* The DATA field should be used to store the main body of any packet. */
+#define DATA            "DATA"
+/* These headers are reserved for system use only. */
+#define HOST            "HST"
+#define ID              "ID"
+#define NAME            "NAME"
+#define PACKET          "PKT"
+#define UDP_PORT        "UDP"
+#define SYSTEM          "SYS"
+#define VERSION         "V"
+#define FLAGS           "F"
+#define SIGNATURE       "S"
+
+/* Reserved headers for diagnostics. */
+#define PACKET_LOSS     "PKT_LOSS"
+#define RESPONSE_TIME   "TIME"
+
+/* --- Standard REQUESTs --- */
+#define PING            "ping"
+#define QUERY           "query"
+#define REPLY           "reply"
+#define HELO            "helo"
+
+// packet properties
+#define MAX_UDP_LENGTH 65536
+// packet flags
+#define FL_VALID_SIGNATURE 0x1
+
+/* --- Index macros for host arrays in old-style host lists --- */
+#define HOSTLIST_NAME       0
+#define HOSTLIST_IP         1
+#define HOSTLIST_UDP_PORT   2
+#define HOSTLIST_LOCAL_COMMANDS  3
+#define HOSTLIST_COMMANDS   4
+#define HOSTLIST_STATUS     5
+#define HOSTFILE_DEL        ":"
+#define HOSTFILE_DEL2        ","
+
+/* Mud / Admin email address. */
+#define EMAIL        "mud@mg.mud.de"
+
+// Public commands which are usually accessible.
+#define DEFAULT_COMMANDS \
+({ PING, QUERY, REPLY, "channel", "finger", "tell", "who", "mail",\
+    "www", "htmlwho", "locate" })
+