/*
* UDP port handling code. Version 0.61
* Written by Nostradamus for Zebedee.
* Developed from an original concept by Alvin@Sushi.
*/
#pragma strict_types
#include <newudp.h>
#include <socket.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", "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 file_name() returns. */
/* #define UDP_MASTER __MASTER_OBJECT__ */
/* #define UDP_MASTER SECURITY // CD */
#define UDP_MASTER "/single/master"
/* #define UDP_MASTER "obj/master" */
/* 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("root") */
#define SET_EUID seteuid(getuid())
/* Define these as appropriate if you do not have the relevant efuns. */
/* #define to_int(x) atoi(x) // MudOS & CD */
#define to_string(x) ("" + x) // CD
/* #define copy_mapping(x) copy(x) // MudOS */
#define copy_mapping(x) (x + ([ ])) // CD
/* #define send_imp(host, port, data) \
SECURITY->send_udp_message(host, port, data) // CD
/* Define if casts can only be used on unknown or mixed types. (CD) */
#define RESTRICTED_CASTS
/* Define if your system does not support the a[n..] indexing notation. (CD) */
#undef USE_EXTRACT
/* 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. */
#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 DELIMITER "|"
#define RETRY "_RETRY"
#define DEBUG(msg) if (find_player("pinkfish"))\
tell_object(find_player("pinkfish"), msg)
private mapping hosts, pending_data, incoming_packets;
private int packet_id;
private int socket;
void set_host_list();
varargs int send_udp(string mudname, mapping data, int expect_reply);
void receive_udp(string sender, string message);
int atoi(string str) {
int bing;
sscanf(str, bing);
return bing;
} /* atoi() */
mapping m_delete(mapping map, mixed key) {
map = map + ([ ]);
map_delete(map, key);
return map;
} /* m_delete() */
int send_imp(string host, string port, mixed data) {
int blue;
blue = socket_create(DATAGRAM, "out_read_callback", "out_close_callback");
if (blue >= 0) {
socket_write(blue, data, host+" "+port);
} else {
printf("Failure into the night.\n");
}
socket_close(blue);
return 1;
} /* send_imp() */
void setup_server() {
socket = socket_create(DATAGRAM, "in_read_callback", "in_close_callback");
if (socket >= 0) {
if (socket_bind(socket, LOCAL_UDP_PORT) < 0) {
printf("Failed to bind socket.\n");
socket_close(socket);
}
} else {
printf("Failed to create socket.\n");
}
} /* setup_server() */
#define m_indices(map) keys(map)
void in_read_callback(int socket, string message, string addr) {
string port;
sscanf(addr, "%s %s", addr, port);
receive_udp(addr, message);
} /* in_read_callback() */
#if !defined(COMPAT_FLAG) || defined(ZEBEDEE)
void create() {
#ifndef COMPAT_FLAG
SET_EUID;
#endif
#else
void reset(mixed arg) {
if (arg)
return;
#endif
packet_id = 0;
pending_data = ([ ]);
incoming_packets = ([ ]);
hosts = ([ ]);
set_host_list();
if (!this_player())
call_out("startup", 1);
} /* create() */
void set_host_list() {
mapping old_hosts;
mixed data;
old_hosts = copy_mapping(hosts);
if (data = read_file(HOST_FILE)) {
int i, stat;
string *local_cmds;
string name;
mixed tmp;
for(i = sizeof(data = explode(data, "\n")); i--; ) {
if (data[i] == "" || data[i][0] == '#')
continue;
if (sizeof(data[i] = explode(data[i], ":")) < 5) {
log_file(INETD_LOG_FILE, "*Parse error in hosts file: line " +
(i + 1) + "\n");
continue;
}
name = lower_case(data[i][HOST_NAME]);
/* Don't discard existing host int. */
if (tmp = old_hosts[name])
stat = tmp[HOST_STATUS];
else
stat = UNKNOWN;
if (member_array("*",
local_cmds = explode(data[i][LOCAL_COMMANDS], ",")) != -1)
local_cmds = local_cmds - ({ "*" }) + COMMANDS;
hosts[name] = ({
capitalize(data[i][HOST_NAME]),
data[i][HOST_IP],
to_int(data[i][HOST_UDP_PORT]),
local_cmds,
explode(data[i][HOST_COMMANDS], ","),
stat
});
}
}
} /* set_host_list() */
void startup() {
string *muds;
int i;
setup_server();
for(i = sizeof(muds = m_indices(hosts)); i--; )
send_udp(muds[i], ([ REQUEST: PING ]), 1);
} /* startup() */
mixed decode(string arg) {
if (arg[0] == '$')
#ifdef USE_EXTRACT
return extract(arg, 1);
#else
return arg[1..10000];
#endif
#ifdef RESTRICTED_CASTS
if (to_string(to_int(arg)) == arg)
return to_int(arg);
#else
if ((arg) == arg)
return arg;
#endif
return arg;
} /* decode() */
mixed decode_packet(string packet) {
string *data;
mapping ret;
string info, tmp;
mixed class_i;
int i;
data = explode(packet, DELIMITER);
/* If this packet has been split, handle buffering. */
if (data[0][0..strlen(PACKET)] == PACKET + ":") {
int id, n;
if (sscanf(
#ifdef USE_EXTRACT
extract(data[0], strlen(PACKET)+1)
#else
data[0][strlen(PACKET)+1..10000]
#endif
, "%s:%d:%d/%d", class_i, id, i, n) != 4)
return 0;
class_i = lower_case(class_i) + ":" + id;
if (pointerp(incoming_packets[class_i])) {
incoming_packets[class_i][i-1] =
#ifdef USE_EXTRACT
extract(packet, strlen(data[0])+1);
#else
packet[strlen(data[0])+1..10000];
#endif
if (member_array(0, incoming_packets[class_i]) == -1) {
ret = decode_packet(implode(incoming_packets[class_i], ""));
incoming_packets = m_delete(incoming_packets, class_i);
return ret;
}
} else {
incoming_packets[class_i] = allocate(n);
incoming_packets[class_i][i-1] =
#ifdef USE_EXTRACT
extract(packet, strlen(data[0])+1);
#else
packet[strlen(data[0])+1..10000];
#endif
/* If no timeout is running then start one. */
if (!pending_data[class_i])
call_out("incoming_time_out", REPLY_TIME_OUT, class_i);
}
return 1;
}
ret = ([ ]);
for(i = 0; i < sizeof(data); i++) {
if (sscanf(data[i], "%s:%s", tmp, info) != 2)
return 0;
switch((string)(class_i = decode(tmp))) {
case DATA:
return ret + ([ DATA: decode(
#ifdef USE_EXTRACT
extract(implode(data[i..sizeof(data)-1], DELIMITER)
, strlen(DATA)+1)
#else
implode(data[i..10000], DELIMITER)[strlen(DATA)+1..10000]
#endif
)
]);
default:
ret[class_i] = decode(info);
continue;
}
}
return ret;
} /* decode_packet() */
private int valid_request(mapping data) {
mixed host_data;
string req;
if (!data[NAME] || !data[UDP_PORT] || !(req = data[REQUEST])) {
log_file(INETD_LOG_FILE, DATE + ": Illegal packet.\n");
return 0;
}
if (host_data = hosts[lower_case(data[NAME])]) {
#if 0
if (data[HOST] != host_data[HOST_IP]) {
log_file(INETD_LOG_FILE, DATE + ": Host mismatch.\n");
return 0;
}
if (data[UDP_PORT] != host_data[HOST_UDP_PORT]) {
log_file(INETD_LOG_FILE, DATE + ": Port mismatch.\n");
return 0;
#else
if (data[HOST] != host_data[HOST_IP]) {
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]) {
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];
}
#endif
} else {
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 != PING && req != QUERY && req != REPLY &&
member_array(data[REQUEST], host_data[LOCAL_COMMANDS]) == -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"
]) );
log_file(INETD_LOG_FILE, DATE + ": Invalid request.\n");
return 0;
}
return 1;
} /* valid_request() */
void receive_udp(string sender, string packet) {
mapping data;
string req, err;
/*
#ifdef UDP_MASTER
if (!previous_object() ||
file_name(previous_object()) != UDP_MASTER) {
log_file(INETD_LOG_FILE, DATE + ": Illegal call of receive_udp() by " +
file_name(previous_object()) + "\n\n");
return;
}
#endif
*/
if (!mapp(data = decode_packet(packet))) {
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;
}
data[HOST] = sender;
if (!valid_request(data)) {
log_file(INETD_LOG_FILE, "Sender: " + sender + "\nPacket:\n" +
packet + "\n\n");
return;
}
hosts[lower_case(data[NAME])][HOST_STATUS] = UP;
req = data[REQUEST];
if (req == 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;
data[REQUEST] = pending[REQUEST];
#ifdef INETD_DIAGNOSTICS
data[PACKET_LOSS] = pending[PACKET_LOSS];
data[RESPONSE_TIME] = time() - pending[RESPONSE_TIME];
#endif
pending_data =
m_delete(pending_data, lower_case(data[NAME]) + ":" + data[ID]);
}
if (err = catch(
call_other(UDP_CMD_DIR + req, "udp_" + req, copy_mapping(data)))) {
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");
}
} /* recieve_udp() */
int match_mud_name(string mudname, string match_str) {
return mudname[0..strlen(match_str)-1] == match_str;
} /* match_mud_name() */
string encode(mixed arg) {
if (objectp(arg))
return file_name(arg);
if (stringp(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);
} /* encode() */
string encode_packet(mapping data) {
int i;
mixed indices;
string header, body, t1, t2, ret;
int data_flag;
for(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" + DELIMITER + "%s", t1, t2) ||
sscanf(body, "%s" + DELIMITER + "%s", t1, t2))
return 0;
if (ret)
ret +=
DELIMITER + header + ":" + body;
else
ret = header + ":" + body;
}
if (ret) {
if (data_flag)
ret += DELIMITER + DATA + ":" + encode(data[DATA]);
return ret;
}
} /* encode_packet() */
string *explode_packet(string packet, int len) {
if (strlen(packet) <= len)
return ({ packet });
return ({ packet[0..len-1] }) +
explode_packet(
#ifdef USE_EXTRACT
extract(packet, len)
#else
packet[len..10000]
#endif
, len);
} /* explode_packet() */
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 = filter_array(
m_indices(hosts), "match_mud_name", this_object(), mudname)) == 1)
host_data = hosts[mudname = names[0]];
else
#ifdef SEND_UDP_COMPAT
return (string)SEND_UDP_COMPAT(mudname, data, expect_reply);
#else
return "Unknown mud: " + capitalize(mudname) + "\n";
#endif
}
if (data[REQUEST] != PING &&
data[REQUEST] != QUERY &&
data[REQUEST] != REPLY &&
member_array("*", host_data[HOST_COMMANDS]) == -1 &&
member_array(data[REQUEST], host_data[HOST_COMMANDS]) == -1)
return capitalize(data[REQUEST]) + ": Command unavailable @" +
host_data[HOST_NAME] + "\n";
data += ([ NAME: LOCAL_NAME, UDP_PORT: LOCAL_UDP_PORT ]);
if (expect_reply) {
/* Don't use zero. */
data[ID] = ++packet_id;
/* Don't need copy_mapping() 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
call_out("reply_time_out", REPLY_TIME_OUT, mudname + ":" + packet_id);
}
if (!(packet = encode_packet(data))) {
if (expect_reply)
pending_data = m_delete(pending_data, mudname + ":" + packet_id);
log_file(INETD_LOG_FILE, DATE + ": Illegal packet sent by " +
file_name(previous_object()) + "\n\n");
return "inetd: Illegal packet.\n";
}
if (strlen(packet) <= MAX_PACKET_LEN)
packet_arr = ({ packet });
else {
string header;
int max;
header = PACKET + ":" + lower_case(LOCAL_NAME) + ":" +
(expect_reply ? packet_id : ++packet_id) + ":";
packet_arr = explode_packet(packet,
/* Allow 8 extra chars: 3 digits + "/" + 3 digits + DELIMITER */
MAX_PACKET_LEN - (strlen(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--; ) {
if (!send_imp(
host_data[HOST_IP], host_data[HOST_UDP_PORT], packet_arr[i]))
return "inetd: Error in sending packet.\n";
}
return 0;
} /* send_udp() */
void incoming_time_out(string id) {
incoming_packets = m_delete(incoming_packets, id);
} /* incoming_time_out() */
void reply_time_out(mixed 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 = copy_mapping(data);
send = m_delete(send, RETRY);
#ifdef INETD_DIAGNOSTICS
send = m_delete(send, PACKET_LOSS);
send = m_delete(send, RESPONSE_TIME);
#endif
call_out("reply_time_out", REPLY_TIME_OUT, id);
send_udp(data[NAME], send);
return;
}
data = m_delete(data, RETRY);
#ifdef INETD_DIAGNOSTICS
data = m_delete(data, RESPONSE_TIME);
#endif
catch(call_other(UDP_CMD_DIR + REPLY, "udp_" + REPLY,
data + ([ SYSTEM: TIME_OUT ])));
/* 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;
incoming_time_out(lower_case(data[NAME]) + ":" + id);
}
pending_data = m_delete(pending_data, id);
} /* reply_time_out() */
varargs mixed query(string what) {
switch(what) {
case "commands":
return COMMANDS;
case "hosts":
return copy_mapping(hosts);
case "pending":
return copy_mapping(pending_data);
case "incoming":
return copy_mapping(incoming_packets);
}
} /* query() */
int query_host(string str) {
if (hosts[str]) return 1;
} /* query_host() */
mixed query_host_info(string str) {
return hosts[str];
} /* query_host_info() */
void dest_me() {
socket_close(socket);
destruct(this_object());
} /* dest_me() */
void remove() {
dest_me();
} /* remove() */