/* Do not remove the headers from this file! see /USAGE for more info. */
/*
** oob.c -- code for handling I3's OOB services
**
** 960125, Deathblade: created
*/
#include <log.h>
#include <socket.h>
#include <ports.h>
mixed unguarded(mixed priv, function func);
void send_to_mud(string type, string mudname, mixed * message);
void log_error_rcv(string mudname, mixed * message);
void log_error_snd(string mudname, mixed * message);
void do_auth_mud_req(string mudname);
int validate_auth(string mudname, int provided_key);
string canon_mudname(string mudname);
mapping query_mudlist();
//void file_process_packet(object socket, mixed * message);
void file_has_outgoing(string remote_mudname);
void file_send_outgoing(string remote_mudname, object socket);
//void mail_process_packet(object socket, mixed * message);
void mail_has_outgoing(string remote_mudname);
void mail_send_outgoing(string remote_mudname, object socket);
/*
** This class is used to record the state of each OOB session. It is
** used in the oob_socket_map mapping, keyed by the connection's socket
** object. The same class object is referenced in the values of the
** oob_mudname_map mapping, keyed by the remote mudname.
**
** NOTE: while we are awaiting authentication to open a connection,
** an entry may exist in the oob_mudname_map, but not the corresponding
** oob_socket_map.
*/
class oob_info
{
object socket; /* the socket object */
string remote_mudname; /* the mud we're connected to */
int we_originated; /* did we originate the connection? */
string state; /* current connection state */
int activity_time; /* time of last message */
string addr; /* address of remote OOB port (for opening) */
function fail_func; /* Function to call if we couldn't open a connection. */
}
/* map socket objects to oob connection information. */
nosave private mapping oob_socket_map = ([ ]);
/* map remote (canonical) mudnames to oob connection information. */
nosave private mapping oob_mudname_map = ([ ]);
/*
** General OOB connection states: originator states and target states
*/
#define OOB_STATE_SEND_REQ "[o] sent oob-req; pausing for now"
#define OOB_STATE_SENT_AUTH "[o] sent auth-mud-req; awaiting auth-mud-reply"
#define OOB_STATE_SENT_BEGIN "[o] sent oob-begin; awaiting oob-begin"
#define OOB_STATE_SENT_DATA "[o] sent request; awaiting reply"
#define OOB_STATE_SENT_END "[o] sent oob-end; awaiting data or oob-end"
#define OOB_STATE_CLOSE_PENDING "[o] got oob-end; holding open"
#define OOB_STATE_WAIT_BEGIN "[t] opened; awaiting oob-begin"
#define OOB_STATE_WAIT_END "[t] sent oob-begin; awaiting data or oob-end"
#define OOB_STATE_WAIT_REPLY "[t] sent request; awaiting reply"
#define OOB_STATE_WAIT_CLOSE "[t] sent oob-end; awaiting data or close"
/* request and reply packets and their corresponding handlers */
nosave private mapping oob_requests = ([ ]);
nosave private mapping oob_replies = ([ ]);
/* how long to wait between oob-req and connection */
#define OOB_OPEN_DELAY 2
#define OOB_CLOSE_DELAY (2*60) /* keep it open in case of reuse */
/* authentication types */
#define OOB_AUTH_TYPE_NONE 0 /* none used */
#define OOB_AUTH_TYPE_MUD 1 /* auth-mud-req used */
/* torch connection info after this amount of inactivity */
#define OOB_INACTIVITY_TIMEOUT (10*60) /* 10 minute timeout */
#define OOB_CLEANUP_TIME 150 /* how often for timeout/cleanup */
void oob_cleanup();
nosave private function oob_cleanup_func = (: oob_cleanup :);
nosave private int oob_cleanup_running;
//### driver can't remove a func ptr callout. need a string
#define oob_cleanup_func "oob_cleanup"
/* the OOB listening socket */
nosave private object oob_socket;
private nomask void oob_close(class oob_info info)
{
if ( info->socket )
{
map_delete(oob_socket_map, info->socket);
catch(info->socket->remove());
}
map_delete(oob_mudname_map, info->remote_mudname);
}
private nomask int oob_send(class oob_info info, mixed * message)
{
DBBUG(message);
if ( catch(info->socket->send(message)) )
{
oob_close(info);
return 1;
}
return 0;
}
private nomask void oob_error(class oob_info info,
string errcode, string errmsg,
mixed * errpacket)
{
mixed * message = ({ errcode, errmsg, errpacket });
oob_send(info, ({ "error", 5,
mud_name(), 0,
info->remote_mudname, 0
}) + message);
log_error_snd(info->remote_mudname, message);
}
protected nomask void oob_svc_send(object socket, mixed * message)
{
class oob_info info = oob_socket_map[socket];
if ( !info )
error("socket is not an OOB socket\n");
oob_send(info, message);
}
protected nomask void oob_svc_error(object socket,
string errcode, string errmsg,
mixed * errpacket)
{
class oob_info info = oob_socket_map[socket];
if ( !info )
error("socket is not an OOB socket\n");
oob_error(info, errcode, errmsg, errpacket);
}
private nomask void oob_cleanup_map(mapping conn_map)
{
foreach ( class oob_info info in values(conn_map) )
if ( info->activity_time + OOB_INACTIVITY_TIMEOUT < time() ||
( info->state == OOB_STATE_CLOSE_PENDING &&
info->activity_time + OOB_CLOSE_DELAY < time() ) )
{
oob_close(info);
}
}
/* cleanup timed-out connections */
private nomask void oob_cleanup()
{
oob_cleanup_map(oob_mudname_map);
oob_cleanup_map(oob_socket_map);
if ( sizeof(oob_mudname_map) + sizeof(oob_socket_map) )
call_out(oob_cleanup_func, OOB_CLEANUP_TIME);
else
oob_cleanup_running = 0;
}
/* do we have outgoing information queued up for this mud? */
private nomask int oob_has_outgoing(string remote_mudname)
{
return ( mail_has_outgoing(remote_mudname) ||
file_has_outgoing(remote_mudname) );
}
private nomask void oob_send_outgoing(class oob_info info)
{
if ( mail_send_outgoing(info->remote_mudname, info->socket) )
return;
file_send_outgoing(info->remote_mudname, info->socket);
}
private nomask void oob_callback_read(object socket, mixed * message)
{
class oob_info info = oob_socket_map[socket];
function f_request;
function f_reply;
DBBUG(message);
if ( !message )
{
info = new(class oob_info);
info->socket = socket;
info->remote_mudname = "(not yet provided)";
info->state = OOB_STATE_WAIT_BEGIN;
info->activity_time = time();
oob_socket_map[socket] = info;
if ( !oob_cleanup_running )
{
call_out(oob_cleanup_func, OOB_CLEANUP_TIME);
oob_cleanup_running = 1;
}
return;
}
/* remember that we heard something from the other guy */
info->activity_time = time();
f_request = oob_requests[message[0]];
f_reply = oob_replies[message[0]];
/*
** If a request just came in and we're waiting for data or a close,
** then flip states to where we are expecting this data or an oob-end
** packet. e.g. remaining in this state would barf when an oob-end
** arrived.
** */
//### it would be nice in the I3 spec to have this transition be
//### explicit...
if ( f_request && info->state == OOB_STATE_WAIT_CLOSE )
info->state = OOB_STATE_WAIT_END;
/*
** If a request has come in and we're in the wrong state for it,
** then throw back an error and ignore the packet.
**
** Note: we must ignore the packet. If we aren't in one of these
** two states, then we have not authenticated the remote mud. It
** would be bad to allow that mud to deliver stuff to us :-)
*/
if ( f_request &&
info->state != OOB_STATE_SENT_END &&
info->state != OOB_STATE_WAIT_END )
{
oob_error(info, "bad-proto", "packet came at wrong time", message);
return;
}
switch ( message[0] )
{
//### it would be nice to have different packets for the originator vs.
//### the target mud
case "oob-begin":
if ( info->state == OOB_STATE_SENT_BEGIN )
{
/* this is a reply to our oob-begin; trigger outgoing data */
f_reply = (function)1;
}
else if ( info->state == OOB_STATE_WAIT_BEGIN )
{
/* somebody connected to us. validate their tokens. */
if ( !validate_auth(message[1], message[3]) )
{
LOG_D->log(LOG_I3_ERROR,
sprintf("%s failed the authentication test\n",
message[1]));
oob_close(info);
}
else
{
info->remote_mudname = message[1];
if ( oob_mudname_map[info->remote_mudname] )
{
#if 0 // Consistancy problems here... -- Rust
oob_error(info,
"bad-connection",
"you are already connected",
message);
oob_close(info);
return;
#endif
oob_close(oob_mudname_map[info->remote_mudname]);
}
oob_mudname_map[info->remote_mudname] = info;
oob_send(info, ({ "oob-begin", mud_name(), 0, 0 }));
info->state = OOB_STATE_WAIT_END;
}
}
else
{
oob_error(info, "bad-proto", "unexpected oob-begin", message);
}
break;
//### it would be nice to have different packets for the originator vs.
//### the target mud
case "oob-end":
if ( info->state == OOB_STATE_SENT_END )
{
/*
** We're the originator. If the target is done, then we can
** send more packets or close the connection.
*/
if ( oob_has_outgoing(info->remote_mudname) )
{
oob_send_outgoing(info);
info->state = OOB_STATE_SENT_DATA;
}
else
{
#if 0
// You can't do this, because then the other side can't
// init a connection -- Rust
/*
** Wait to close the connection. We may "reopen" the
** connection during this time period. Note that the
** period cleanup routine will close this as necessary.
*/
info->state = OOB_STATE_CLOSE_PENDING;
#else
oob_close(info);
#endif
}
}
else if ( info->state == OOB_STATE_WAIT_END )
{
/*
** We're the target. When the originator is done, then we
** can send some packets or indicate we have nothing.
*/
if ( oob_has_outgoing(info->remote_mudname) )
{
oob_send_outgoing(info);
info->state = OOB_STATE_WAIT_REPLY;
}
else
{
oob_send(info, ({ "oob-end", mud_name() }));
info->state = OOB_STATE_WAIT_CLOSE;
}
}
else
{
oob_error(info, "bad-proto", "unexpected oob-end", message);
}
break;
case "error":
BBUG(message);
log_error_rcv(info->remote_mudname, message[6..]);
break;
case "oob-error":
log_error_rcv(info->remote_mudname, message[1..]);
break;
default:
if ( f_request )
evaluate(f_request, info->remote_mudname, socket, message);
else if ( f_reply )
evaluate(f_reply, info->remote_mudname, socket, message);
else
oob_error(info, "unk-type", "unknown packet type", message);
break;
}
/*
** If we just finished processing a reply to one of our requests,
** then send more data or indicate we are done and transition to
** a new state (appropriate to whether we opened the connection
** or were connected to). Note that we should be in the SENT_DATA
** or the WAIT_REPLY state. We can remain in that state.
*/
if ( f_reply )
{
if ( oob_has_outgoing(info->remote_mudname) )
{
oob_send_outgoing(info);
}
else
{
oob_send(info, ({ "oob-end", mud_name() }));
if ( info->we_originated )
info->state = OOB_STATE_SENT_END;
else
info->state = OOB_STATE_WAIT_CLOSE;
}
}
}
private nomask void oob_callback_close(object socket)
{
class oob_info info = oob_socket_map[socket];
if ( info )
{
map_delete(oob_socket_map, info->socket);
map_delete(oob_mudname_map, info->remote_mudname);
}
}
private nomask void oob_open_connection(string mudname,
int auth_type, int auth_token)
{
class oob_info info = oob_mudname_map[mudname];
object skt;
DBBUG("opening connection");
if ( catch(skt =
unguarded(1, (: clone_object, SOCKET, SKT_STYLE_CONNECT_M,
info->addr,
(: oob_callback_read :),
(: oob_callback_close :) :) )) )
{
oob_close(info);
return;
}
info->socket = skt;
oob_socket_map[skt] = info;
if ( oob_send(info,
({ "oob-begin", mud_name(), auth_type, auth_token })) )
return;
info->state = OOB_STATE_SENT_BEGIN;
}
varargs nomask void oob_initiate_connection(string mudname, function fail_func)
{
class oob_info info;
mixed * mudinfo;
mudname = canon_mudname(mudname);
if ( !mudname )
error("unknown mud\n");
if ( !oob_has_outgoing(mudname) )
{
/* nothing to send to the other mud, so ignore the initiate */
return;
}
if ( info = oob_mudname_map[mudname] )
{
/*
** Already connected. See if we are in CLOSE_PENDING. If so,
** then send an outgoing request and move back to SENT_DATA.
*/
if ( info->state == OOB_STATE_CLOSE_PENDING)
{
oob_send_outgoing(info);
info->state = OOB_STATE_SENT_DATA;
}
return;
}
info = new(class oob_info);
info->remote_mudname = mudname;
info->we_originated = 1;
info->activity_time = time();
info->fail_func = fail_func;
oob_mudname_map[mudname] = info;
if ( !oob_cleanup_running )
{
call_out(oob_cleanup_func, OOB_CLEANUP_TIME);
oob_cleanup_running = 1;
}
mudinfo = query_mudlist()[mudname];
info->addr = mudinfo[1] + " " + mudinfo[3];
if ( mudinfo[11]["auth"] )
{
/* they have the auth service. send a request for a key. */
do_auth_mud_req(mudname);
info->state = OOB_STATE_SENT_AUTH;
}
else
{
/* send an oob-req to get them to open a port and then connect */
send_to_mud("oob-req", mudname, ({ }));
call_out((: oob_open_connection, mudname, OOB_AUTH_TYPE_NONE, 0 :),
OOB_OPEN_DELAY);
}
}
protected nomask void oob_handle_auth_mud_reply(string mudname, int session_key)
{
class oob_info info = oob_mudname_map[mudname];
if ( !info || info->state != OOB_STATE_SENT_AUTH )
return;
oob_open_connection(mudname, OOB_AUTH_TYPE_MUD, session_key);
}
protected nomask void oob_register_requests(mapping requests)
{
oob_requests += requests;
}
protected nomask void oob_register_replies(mapping replies)
{
oob_replies += replies;
}
nomask void oob_debug_close()
{
foreach ( string mudname, class oob_info info in oob_mudname_map )
oob_close(info);
foreach ( object socket, class oob_info info in oob_socket_map )
oob_close(info);
if ( oob_cleanup_running )
{
remove_call_out(oob_cleanup_func);
oob_cleanup_running = 0;
}
}
protected string stat_me()
{
class oob_info *info_list;
string result;
result = "\nOOB TCP SOCKET: ";
if ( oob_socket )
result += oob_socket->stat_me();
else
result += "<none>\n";
info_list = clean_array(values(oob_socket_map) + values(oob_mudname_map));
foreach ( class oob_info info in info_list)
{
int idle = time() - info->activity_time;
if ( !info->remote_mudname )
result += sprintf("Unknown incoming connection. Idle %d seconds.\n", idle);
else if ( info->we_originated )
result += sprintf("-> %s: %s. Idle %d seconds.\n",
info->remote_mudname, info->state, idle);
else
result += sprintf("<- %s: %s. Idle %d seconds.\n",
info->remote_mudname, info->state, idle);
}
result += sprintf("OOB cleanup is%s running.\n", oob_cleanup_running ? "" : " not");
return result;
}
protected nomask void oob_shutdown()
{
foreach ( object socket in keys(oob_socket_map) )
if ( objectp(socket) )
catch(socket->remove());
if ( oob_socket )
oob_socket->remove();
}
protected nomask void oob_startup()
{
string err;
err = catch(oob_socket = new(SOCKET, SKT_STYLE_LISTEN_M,
PORT_I3_TCP_OOB,
(: oob_callback_read :),
(: oob_callback_close :)));
if ( err )
{
oob_socket->remove();
oob_socket = 0;
error(err);
}
}