blob: 7ec6c28d8b70a394547ed0495476531e07004d8a [file] [log] [blame]
/*
//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;
}