lpmoo-1.2/etc/
lpmoo-1.2/mudlib/
lpmoo-1.2/mudlib/etc/
lpmoo-1.2/mudlib/include/
lpmoo-1.2/mudlib/include/moo/
lpmoo-1.2/mudlib/lpc/
lpmoo-1.2/mudlib/std/auto/
lpmoo-1.2/mudlib/std/bfuns/
/*
 * NAME:	connection.c
 * DESCRIPTION:	manage a generic network connection
 */

# define DEBUG  0

inherit "/std/core";
inherit "/std/string";
inherit "/std/data";

# if DEBUG
inherit "/std/vartext";
# else
# define var2str(x)  ""
# endif

# include <objects.h>
# include <moo/verb.h>
# include <moo/config.h>
# include <moo/command.h>
# include <moo/perms.h>

# define F_DESTRUCTING	0x01	/* to preserve this object in close() */
# define F_PROGRAMMING	0x02	/* set if we are in the middle of .program */
# define F_BACKDOOR	0x04	/* enabled backdoor eval? */
# define F_PREFIXED	0x08	/* seen PREFIX label? */
# define F_PROMPTED	0x10	/* seen prompt yet? */
# define F_BOOTSTRAP	0x20	/* currently bootstrapping? */
# define F_LPCONLY	0x40	/* not associated with a MOO object? */
# define F_TIMELIMIT	0x80	/* connection will timeout? */

static	string	ip_number;	/* TCP/IP address of this connection */
static	string	ip_name;	/* TCP/IP hostname of this connection */
private	int	port_number;	/* TCP/IP remote port number */
static	int	pfd;		/* pseudo file-descriptor */

private	int	time_connect;	/* time at connect */
private	int	time_input;	/* time at last input */

private	int	max_object;	/* for remembering the number of objects */

private	object	listener;	/* associated port object (usually #0) */
private	object	player;		/* associated player object */
static	int	player_id;	/* player object number (or negative num) */

private	int	timeout_handle;	/* for removing call_out(connect_timeout) */
private int	flags;		/* status flags */

static	mixed  *susp_data;	/* passing read information to a task */
private	string	oob_prefix;	/* out-of-band command prefix */

private	object	input_to_obj;	/* passing read information to LPC */
private	string	input_to_func;	/* LPC function */

private	string	prefix, suffix;	/* PREFIX, SUFFIX strings */
private	string	prompt;		/* prompt string */

/*
 * NAME:	create()
 * DESCRIPTION:	called when object is created
 */
static
void create(void)
{
  ::create();

  oob_prefix = CONFIG->query(CF_OOB_PREFIX);
}

void notify(string msg);

/*
 * NAME:	set_listener()
 * DESCRIPTION:	change our port listener object (usually #0)
 */
void set_listener(object ob)
{
  listener = ob;
}

/*
 * NAME:	get_listener()
 * DESCRIPTION:	return our port listener object (usually #0)
 */
object get_listener(void)
{
  return listener;
}

/*
 * NAME:	set_prefix()
 * DESCRIPTION:	modify or delete this connection's output prefix
 */
void set_prefix(string str)
{
  prefix = strlen(str) ? str : 0;
}

/*
 * NAME:	set_suffix()
 * DESCRIPTION:	modify or delete this connection's output suffix
 */
void set_suffix(string str)
{
  suffix = strlen(str) ? str : 0;
}

/*
 * NAME:	get_delimiters()
 * DESCRIPTION:	return the current output prefix and suffix
 */
string *get_delimiters(void)
{
  return ({ prefix, suffix });
}

/*
 * NAME:	set_prompt()
 * DESCRIPTION:	change this connection's prompt
 */
void set_prompt(string str)
{
  prompt = strlen(str) ? str : 0;
}

/*
 * NAME:	get_prompt()
 * DESCRIPTION:	return this connection's current prompt
 */
string get_prompt(void)
{
  return prompt ? prompt : "";
}

/*
 * NAME:	set_noecho()
 * DESCRIPTION:	to be overridden as necessary
 */
int set_noecho(void)
{
  return E_INVARG;
}

/*
 * NAME:	set_bootstrap()
 * DESCRIPTION:	the server is currently bootstrapping
 */
void set_bootstrap(int bool)
{
  if (bool)
    flags |= F_BOOTSTRAP;
  else
    flags &= ~F_BOOTSTRAP;
}

/*
 * NAME:	set_backdoor()
 * DESCRIPTION:	allow this connection to use the built-in eval
 */
void set_backdoor(int bool)
{
  if (bool)
    flags |= F_BACKDOOR;
  else
    flags &= ~F_BACKDOOR;
}

/*
 * NAME:	prefix()
 * DESCRIPTION:	send this connection's current output prefix, if any
 */
void prefix(void)
{
  if (prefix)
    notify(prefix);

  flags |= F_PREFIXED;
}

void send(string msg);

/*
 * NAME:	prompt()
 * DESCRIPTION:	send this connection's current prompt, if any
 */
void prompt(void)
{
  if (prompt && ! (flags & F_PROMPTED))
    send(prompt);

  flags |= F_PROMPTED;
}

/*
 * NAME:	suffix()
 * DESCRIPTION:	send this connection's current ouput suffix, if any
 */
void suffix(void)
{
  if (suffix && (flags & F_PREFIXED))
    notify(suffix);

  flags &= ~F_PREFIXED;

  if (! (flags & F_DESTRUCTING))
    prompt();
}

/*
 * NAME:	finish()
 * DESCRIPTION:	called after command processing is finished (or an error)
 */
void finish(void)
{
  suffix();
}

/*
 * NAME:	parse_line()
 * DESCRIPTION:	parse a line of input using MOO command parsing semantics
 */
private
string *parse_line(string line)
{
  int i, sz, special;
  int in_space, in_quote, start_word;
  string *words, argstr;

  if (! (sz = strlen(line)))
    return ({ "" });

  for (i = 0; i < sz && (line[i] == ' ' || line[i] == '\t'); ++i);

  if (i < sz)
    {
      switch (line[i])
	{
	case '\"':
	  special = 1, line = subst(line, i, 1, "say ");
	  break;
	case ':':
	  special = 1, line = subst(line, i, 1, "emote ");
	  break;
	case ';':
	  special = 1, line = subst(line, i, 1, "eval ");
	  break;
	}
    }

  in_space = 1;
  in_quote = 0;
  start_word = -1;

  words = ({ });
  for (i = 0, sz = strlen(line); i < sz; ++i)
    {
      if (in_space)
	{
	  if (line[i] == ' ')
	    continue;
	  in_space = 0;
	  start_word = i;
	  /* pass through */
	}

      if (line[i] == '\\')
	{
	  line = subst(line, i, 1, "");
	  --sz;
	  continue;
	}

      if (line[i] == '\"')
	{
	  line = subst(line, i, 1, "");
	  --sz; --i;
	  in_quote = (! in_quote);
	  continue;
	}

      if (line[i] == ' ' && (! in_quote))
	{
	  words += ({ line[start_word .. i - 1] });
	  in_space = 1;

	  if (! argstr)
	    argstr = line[i + 1 ..];

	  continue;
	}
    }

  if (! in_space)
    words += ({ line[start_word ..] });

  if (! argstr)
    argstr = "";

  if (! special && (sz = strlen(argstr)) && argstr[0] == ' ')
    {
      /* not special -- strip leading blanks */

      for (i = 0; i < sz && argstr[i] == ' '; ++i);
      argstr = argstr[i ..];
    }

  return ({ argstr }) + words;
}

/*
 * NAME:	filter_strings()
 * DESCRIPTION:	pull out strings from an array and lowercase them
 */
private
string *filter_strings(MOOVAL var)
{
  int i, j, sz;
  MOOVAL *list;
  string *strs;

  if (! LSTP(var))
    return ({ });

  list = LSTVAL(var);
  strs = allocate(sz = sizeof(list));
  for (i = 0, j = -1; i < sz; ++i)
    if (STRP(list[i]))
      strs[++j] = tolower(STRVAL(list[i]));

  return strs[.. j];
}

/*
 * NAME:	fetch_names()
 * DESCRIPTION:	return an array of nearby objects and names
 */
private
mixed *fetch_names(void)
{
  int i;
  object ob, *list;
  string **names;

  list = player->get_contents();

  if (ob = player->get_location())
    list += ob->get_contents();

  for (names = allocate(i = sizeof(list)); i--; )
    {
      ob = list[i];
      names[i] = filter_strings(ob->safe_get_property("aliases")) +
	({ tolower(ob->get_name()) });
    }

  return ({ list, names });
}

/*
 * NAME:	match_object()
 * DESCRIPTION:	locate objects based on description
 */
private
int match_object(string descrip, object *list, string **names)
{
  int i, j, sz, count, match, len;

  if (! strlen(descrip))
    return MATCH_NOTHING;

  if (sscanf(descrip, "#%d", i) && MOOOBJ(i))
    return i;

  descrip = tolower(descrip);

  if (descrip == "me")
    return OBJNUM(player);
  if (descrip == "here")
    {
      object ob;

      ob = player->get_location();
      return ob ? OBJNUM(ob) : -1;
    }

  sz = sizeof(list);

  for (i = 0; i < sz; ++i)
    for (j = sizeof(names[i]); j--; )
      if (descrip == names[i][j])
	{
	  if (count++)
	    return MATCH_AMBIGUOUS;
	  match = OBJNUM(list[i]);
	  break;
	}

  if (count)
    return match;

  for (len = strlen(descrip), i = 0; i < sz; ++i)
    for (j = sizeof(names[i]); j--; )
      if (descrip == leftstr(names[i][j], len))
	{
	  if (count++)
	    return MATCH_AMBIGUOUS;
	  match = OBJNUM(list[i]);
	  break;
	}

  if (count)
    return match;

  return MATCH_FAIL;
}

/*
 * NAME:	parse_cmd()
 * DESCRIPTION:	finish parsing a MOO command
 */
private
mixed *parse_cmd(string *args)
{
  mixed *ret;
  string *split, **names;
  object *list;

  split = global->match_preposition(args);

  ret   = fetch_names();
  list  = ret[0];
  names = ret[1];

  return ({ match_object(split[0], list, names),
	    split[1] == "" ? MATCH_NOTHING : global->prep_code(split[1]),
	    match_object(split[2], list, names) }) + split;
}

/*
 * NAME:	server_msg()
 * DESCRIPTION:	display a server message to the connection
 */
static
void server_msg(string message)
{
  if (CONFIG->query(CF_SERVER_MSGS))
    notify(message);
}

/*
 * NAME:	destruct()
 * DESCRIPTION:	destroy this object (perhaps from call_out)
 */
static
void destruct(void)
{
  destruct_object(this_object());
}

/*
 * NAME:	boot()
 * DESCRIPTION:	terminate this user's connection
 */
varargs
void boot(string message)
{
  if (message)
    server_msg(message);

  flags |= F_DESTRUCTING;

  finish();
  destruct();
}

/*
 * NAME:	get_id()
 * DESCRIPTION:	return the pfd/object id of this connection
 */
int get_id(void)
{
  return player_id;
}

/*
 * NAME:	get_connection_name()
 * DESCRIPTION:	return a descriptive string for this connection
 */
string get_connection_name(void)
{
  return (string) pfd + " from " +
    ((ip_name && strlen(ip_name)) ? ip_name : ip_number);
}

/*
 * NAME:	description()
 * DESCRIPTION:	return a description string for this connection
 */
string description(void)
{
  return global->user_description(player_id) + " on " + get_connection_name();
}

/*
 * NAME:	connect_timeout()
 * DESCRIPTION:	user is taking too long
 */
static
void connect_timeout(void)
{
  global->log_msg("TIMEOUT: " + description());
  boot("*** Timed-out waiting for login. ***");
}

/*
 * NAME:	remove_timelimit()
 * DESCRIPTION:	stop limiting the user's idleness
 */
private
void remove_timelimit(void)
{
  if (flags & F_TIMELIMIT)
    {
      remove_call_out(timeout_handle);
      flags &= ~F_TIMELIMIT;
    }
}

/*
 * NAME:	set_timelimit()
 * DESCRIPTION:	limit the user's idleness
 */
private
void set_timelimit(void)
{
  int seconds;

  remove_timelimit();

  if (seconds = CONFIG->query(CF_CONNECT_TIMEOUT))
    {
      timeout_handle = call_out("connect_timeout", seconds);
      flags |= F_TIMELIMIT;
    }
}

/*
 * NAME:	connect_player()
 * DESCRIPTION:	(re)connect a player
 */
private
int connect_player(object ob, int id, int created)
{
  object other_conn;

  remove_timelimit();
  time_connect = time();

  player = ob;

  if (other_conn = global->get_connection_obj(player))
    {
      global->redirect(this_object(), player_id, player);
      other_conn->boot("*** Redirecting connection to new port ***");
      server_msg("*** Redirecting old connection to this port ***");

      player_id = id;
      return 1;  /* reconnected */
    }
  else
    {
      global->connect(this_object(), player_id, player);
      server_msg(created ? "*** Created ***" : "*** Connected ***");

      player_id = id;
      return 0;  /* connected */
    }
}

/*
 * NAME:	connect_lpc()
 * DESCRIPTION:	attach this connection to an LPC object
 */
static
void connect_lpc(object obj)
{
  remove_timelimit();
  time_connect = time();

  input_to_obj  = obj;
  input_to_func = "receive_message";

  flags |= F_LPCONLY;

  global->connect_lpc(this_object(), player_id, obj);
  server_msg("*** Connected to " + object_name(obj) + " ***");

  obj->open();
}

/*
 * NAME:	do_login_command()
 * DESCRIPTION:	process a line of input for an unlogged-in connection
 */
void do_login_command(JS_PROTO, string *args, string line)
{
  object ob;
  string verb;
  int i;

  JS_BEGIN;

  max_object = global->get_max_object();

  if (! listener)
    return;

  JS_PREP(1);
  RET = listener->call_verb(JS_DATA(1), "do_login_command",
		    server_vars(player_id, listener, "do_login_command",
				line, LSTVAL(STRLIST2MOO(args))...));
  JS_END;

  if (player_id >= 0 ||
      (! STRP(RET) &&
       (! OBJP(RET) ||
	! (ob = MOOOBJ(OBJVAL(RET))) ||
	! ob->is_player())))
    return;

  if (STRP(RET))	/* LPC object */
    {
      object obj;

      catch(obj = load_object(STRVAL(RET)));

      if (obj == 0)
	return;

      connect_lpc(obj);
      return;
    }

  /* logged in */

  if (OBJVAL(RET) > max_object)	/* user created */
    {
      connect_player(ob, OBJVAL(RET), 1);
      verb = "user_created";
    }
  else	/* regular (re)connection */
    {
      verb = connect_player(ob, OBJVAL(RET), 0) ?
	"user_reconnected" : "user_connected";
    }

  if (! listener)
    return;

  JS_PREP(2);
  RET = listener->call_verb(JS_DATA(2), verb,
			    server_vars(OBJVAL(RET), listener, verb, 0, RET));
  JS_END;

  JS_END;
}

/*
 * NAME:	input_to()
 * DESCRIPTION:	prepare to return the next input line to LPC
 */
int input_to(object where, string what)
{
  if (susp_data || input_to_obj)
    return 0;

  input_to_obj  = where;
  input_to_func = what;

  return 1;
}

/*
 * NAME:	seed_read()
 * DESCRIPTION:	prepare to return the next input line for a read()
 */
int seed_read(JS_PROTO, int binary, mixed *jumpstack)
{
  if (susp_data || input_to_obj)
    return E_INVARG;

  susp_data = ({ JS_DATA(bjump), binary, jumpstack });

  return E_NONE;
}

/*
 * NAME:	unseed()
 * DESCRIPTION:	the suspended read() task was killed
 */
void unseed(void)
{
  susp_data = 0;
}

/*
 * NAME:	builtin_eval()
 * DESCRIPTION:	evaluate MOO code unconditionally (backdoor)
 */
void builtin_eval(JS_PROTO, string code)
{
  object vobj;
  mixed *ast;
  int i, sz, flags;
  string err;

  JS_BEGIN;

  ast = parser->main(code + ";");

  if (! ast[0])
    {
      ast = ast[1];
      for (i = 0, sz = sizeof(ast); i < sz; ++i)
	notify(ast[i]);
      notify(sz + " error" + (sz == 1 ? "." : "s."));
      return;
    }

  vobj = global->compile_verb(ast[1], 0);

  /* don't call vobj->ref(); let it self-destruct after executing */

  flags = IF_DEBUG | IF_WIZARD;

  info  = ({ flags, player_id,
	     ({ OBJ(player_id),		/* player */
		OBJ(-1),		/* this */
		OBJ(player_id),		/* caller */
		LST( ({ }) ),		/* args */
		STR(""),		/* argstr */
		STR(""),		/* verb */
		OBJ(-1),		/* dobj */
		STR(""),		/* dobjstr */
		STR(""),		/* prepstr */
		OBJ(-1),		/* iobj */
		STR(""),		/* iobjstr */
		STD_VARS }),
	     0, "", -1, player_id, global->take_task(),
	     0, vobj, 0, global->get_max_depth(), 0 });

  JS_PREP(1);
  RET = vobj->main(JS_DATA(1));
  JS_END;

  debug("stack == " + var2str(stack));
  notify("=> " + moo2str(RET, 1));

  JS_END;
}

/*
 * NAME:	do_oob_command()
 * DESCRIPTION:	process an out-of-band command
 */
void do_oob_command(JS_PROTO, string *args, string line)
{
  JS_BEGIN;

  if (listener)
    {
      JS_PREP(1);
      RET = listener->call_verb(JS_DATA(1), "do_out_of_band_command",
			server_vars(player_id, listener,
				    "do_out_of_band_command",
				    line, LSTVAL(STRLIST2MOO(args))...));
      JS_END;
    }

  JS_END;
}

/*
 * NAME:	program()
 * DESCRIPTION:	initiate programming of a verb
 */
private
void program(string obj, string verb)
{
  mixed *ret;
  object ob, *list;
  int objnum;
  string **names;

  ret   = fetch_names();
  list  = ret[0];
  names = ret[1];

  objnum = match_object(obj, list, names);

  if (! (ob = MOOOBJ(objnum)))
    { notify("I don't see \"" + obj + "\" here."); return; }

  notify("** Warning: `.program' is not supported.");
  notify("Now ignoring program for " + ob->get_name() +
	       ":" + verb + "(x).  Use \".\" to end.");
  flags |= F_PROGRAMMING;
}

/*
 * NAME:	program_line()
 * DESCRIPTION:	receive a line of MOO code
 */
private
void program_line(string line)
{
  if (line == ".")
    {
      flags &= ~F_PROGRAMMING;
      notify("Stopped programming.");
      prompt();
    }
}

/*
 * NAME:	do_read()
 * DESCRIPTION:	pass a line of input to a suspended read() task
 */
static
void do_read(string line)
{
  mixed *stack, *info, *js;
  int binary;

  stack  = susp_data[0];
  info   = susp_data[1];
  binary = susp_data[4];
  js     = susp_data[5];

  susp_data = 0;
  global->task_started(info[I_TASKID]);

  stack[SP + 1] = line ? (binary ? BUF(line) : STR(line)) : ERR(E_INVARG);

  debug("stack == " + var2str(stack));

  call_other(js[1], js[2], js[3]...);
}

/*
 * NAME:	do_input_to()
 * DESCRIPTION:	pass input to an LPC object
 */
private
mixed do_input_to(string line)
{
  object where;
  string what;

  where = input_to_obj;
  what  = input_to_func;

  if (! (flags & F_LPCONLY))
    {
      input_to_obj  = 0;
      input_to_func = 0;
    }

  return call_other(where, what, line);
}

/*
 * NAME:	do_huh()
 * DESCRIPTION:	a command was not recognized
 */
int do_huh(JS_PROTO, object room, string verb, mixed *parsed)
{
  JS_BEGIN;

  JS_PREP(1);
  RET = room->call_verb(JS_INIT, "huh",
		({ OBJ(player_id),			/* player */
		   OBJ_OBJNUM(room),			/* this */
		   OBJ(player_id),			/* caller */
		   STRLIST2MOO(parsed[C_ARGS]),		/* args */
		   STR(parsed[C_ARGSTR]),		/* argstr */
		   STR(verb),				/* verb */
		   OBJ(parsed[C_DOBJ]),			/* dobj */
		   STR(parsed[C_DOBJSTR]),		/* dobjstr */
		   STR(parsed[C_PREPSTR]),		/* prepstr */
		   OBJ(parsed[C_IOBJ]),			/* iobj */
		   STR(parsed[C_IOBJSTR]),		/* iobjstr */
		   STD_VARS }) );
  JS_END;

  return ! STWP(RET);

  JS_END;
}

/*
 * NAME:	do_command()
 * DESCRIPTION:	give the db a chance to process this command
 */
int do_command(JS_PROTO, string *args, string line)
{
  JS_BEGIN;

  if (! listener)
    return 0;

  JS_PREP(1);
  if (catch(RET = listener->call_verb(JS_DATA(1), "do_command",
		      server_vars(player_id, listener, "do_command",
				  line, LSTVAL(STRLIST2MOO(args))...))))
    return 0;
  JS_END;

  return TRUTHOF(RET);

  JS_END;
}

/*
 * NAME:	want_binary()
 * DESCRIPTION:	return true iff we want to process input as binary data
 */
static
int want_binary(void)
{
  return susp_data && susp_data[4];
}

/*
 * NAME:	process_data()
 * DESCRIPTION:	handle raw binary input
 */
static
void process_data(string data)
{
  time_input = time();
  flags &= ~F_PROMPTED;

  if (flags & F_TIMELIMIT)
    set_timelimit();

  /* susp_data != 0 because want_binary() was called before we got here */

  do_read(data);
}

/*
 * NAME:	sanitize()
 * DESCRIPTION:	make a string contain only valid MOO string characters
 */
private
string sanitize(string str)
{
  int i, c;

  for (i = strlen(str); i--; )
    if (((c = str[i]) < ' ' && c != '\t') || c > '~')
      str = str[.. i - 1] + str[i + 1 ..];

  return str;
}

/*
 * NAME:	process_line()
 * DESCRIPTION:	handle a single line of input
 */
static
void process_line(string line)
{
  object room, ob;
  string *args, verb, lverb, argstr;
  mixed *parsed;
  int i;

  time_input = time();
  flags &= ~F_PROMPTED;

  if (flags & F_TIMELIMIT)
    set_timelimit();

  /* check for LPC input_to() */
  if (input_to_obj || (flags & F_LPCONLY))
    {
      if (input_to_obj && (do_input_to(line) || (flags & F_LPCONLY)))
	return;

      if (flags & F_LPCONLY)
	{
	  boot("*** LPC connection broken ***");
	  return;
	}
    }

  line = sanitize(line);

  /* check for out-of-band command */
  if (oob_prefix && strlen(line) >= strlen(oob_prefix) &&
      line[.. strlen(oob_prefix) - 1] == oob_prefix)
    {
      args = parse_line(line);
      do_oob_command(JS_INIT, args[1 ..], line);
      return;
    }

  /* check for suspended read() */
  if (susp_data)
    {
      do_read(line);
      return;
    }

  /* check for .program text */
  if (flags & F_PROGRAMMING)
    {
      program_line(line);
      return;
    }

  /* check for ;; backdoor */
  if ((flags & F_BACKDOOR) && strlen(line) >= 2 && line[0 .. 1] == ";;")
    {
      builtin_eval(JS_INIT, line[2 ..]);
      return;
    }

  args   = parse_line(line);

  argstr = args[0];
  args   = args[1 ..];

  /* check for un-logged-in connection */
  if (! player)
    {
      do_login_command(JS_INIT, args, line);
      return;
    }

  /* try in-db command parsing */
  if (do_command(JS_INIT, args, line))
    return;

  /* check for empty line */
  if (! sizeof(args))
    return;

  verb = args[0];

  /* check for built-in verbs */
  if (verb == "PREFIX")
    {
      set_prefix(argstr);
      return;
    }
  if (verb == "SUFFIX")
    {
      set_suffix(argstr);
      return;
    }

  if (verbname_match(".pr*ogram", verb) && programmerp(player_id))
    {
      string obj, vname;

      if (sizeof(args) != 2)
	{
	  notify("Usage:  .program object:verb");
	  return;
	}

      if (sscanf(args[1], "%s:%s", obj, vname) != 2)
	{
	  notify("You must specify a verb; " +
		       "use the format object:verb.");
	  return;
	}

      program(obj, vname);
      return;
    }

  /* regular command; do parsing */

  parsed = ({ args[1 ..], argstr }) + parse_cmd(args);

  prefix();

  lverb = tolower(verb);

  if (player->command(lverb, verb, parsed, player, 0))
    return;

  room = player->get_location();
  if (room && room->command(lverb, verb, parsed, player, 0))
    return;

  ob = MOOOBJ(parsed[C_DOBJ]);
  if (ob && ob->command(lverb, verb, parsed, player, 0))
    return;

  ob = MOOOBJ(parsed[C_IOBJ]);
  if (ob && ob->command(lverb, verb, parsed, player, 0))
    return;

  if (! room || ! do_huh(JS_INIT, room, verb, parsed))
    notify(CONFIG->query(CF_HUH_FAILED_MSG));
}

/*
 * NAME:	resolved_name()
 * DESCRIPTION:	called to receive this connection's host name when it is known
 */
void resolved_name(string name)
{
  ip_name = name;
}

/*
 * NAME:	init()
 * DESCRIPTION:	setup initial connection data
 */
static
void init(string msg)
{
  time_connect = 0;
  time_input   = time();

  ip_number    = query_ip_number(this_object());
  ip_name      = DNS->get_name(ip_number, "resolved_name");

  pfd          = global->assign_pfd(this_object());
  player_id    = global->accept(this_object(), msg);
}

/*
 * NAME:	open()
 * DESCRIPTION:	called by DGD when connection is opened
 */
static
void open(void)
{
  init("ACCEPT");

  if (flags & F_BOOTSTRAP)
    boot("*** The server is currently bootstrapping (try again later) ***");
  else if (flags & F_BACKDOOR)
    notify("*** Backdoor access for this connection enabled ***");
  else
    {
      set_timelimit();
      process_line("");
    }
}

/*
 * NAME:	client_booted()
 * DESCRIPTION:	called by close() when the client has closed the connection
 */
void client_booted(JS_PROTO)
{
  JS_BEGIN;

  global->log_msg("CLIENT DISCONNECTED: " + description());

  if (player && listener)
    {
      JS_PREP(1);
      RET = listener->call_verb(JS_DATA(1), "user_client_disconnected",
			server_vars(player_id, listener,
				    "user_client_disconnected",
				    0, OBJ(player_id)));
      JS_END;
    }

  JS_END;
}

/*
 * NAME:	cancel_susp()
 * DESCRIPTION:	rid susp_data
 */
static
void cancel_susp(void)
{
  if (susp_data)
    call_out("do_read", 0, 0);
}

/*
 * NAME:	close()
 * DESCRIPTION:	called by DGD when connection is closed / object destructed
 */
static
void close(void)
{
  cancel_susp();

  call_out("destruct", 1);

  if ((flags & F_LPCONLY) && input_to_obj)
    input_to_obj->close();

  if (! (flags & F_DESTRUCTING))
    client_booted(JS_INIT);
}

/*
 * NAME:	get_connected_seconds()
 * DESCRIPTION:	return number of seconds this user has been connected
 */
int get_connected_seconds(void)
{
  return time_connect ? time() - time_connect : -1;
}

/*
 * NAME:	get_idle_seconds()
 * DESCRIPTION:	return number of seconds this user has been idle
 */
int get_idle_seconds(void)
{
  return time() - time_input;
}

/*
 * NAME:	set_port_number()
 * DESCRIPTION:	called to configure the remote TCP/IP port for this connection
 */
void set_port_number(int num)
{
  port_number = num;
}

/*
 * NAME:	get_port_number()
 * DESCRIPTION:	return this connection's remote port number
 */
int get_port_number(void)
{
  return port_number;
}