Weitgehender Neubau auf intermud-2.5.
Benutzt moderne LDMud-3.5-Sprachmittel wie structs.
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;
+}
+