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]), "<","\\<",1);
+ result = regreplace(result, ">","\\>",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>Übersicht</H2>"
+ +"<H3>["+sizeof(list)+" Gruppen, "
+ +"letzte Ä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ü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 Änderung "+dtime(NEWSD->GetNewsTime(group))+"]</H3>"
+ +"<PRE>" + ret + "</PRE>"
+ +"["+make_link("Gruppenü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ächster Artikel";
+ prev = "Voriger Artikel";
+
+ art = implode((ttmp = old_explode(text[N_ARTICLE], "\n"))
+ - regexp(ttmp, "^~#!"), "\n");
+
+ art = regreplace(art, "<", "\\<", 1);
+ art = regreplace(art, ">", "\\>", 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übersicht", group)+"]"
+ + " ["+make_link("Gruppenü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], "<", "\\<", 1);
+ entry[1] = regreplace(entry[1], ">", "\\>", 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ü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" })
+