lima-1.0b5/
lima-1.0b5/driver/
lima-1.0b5/driver/ChangeLog.old/
lima-1.0b5/driver/Win32/
lima-1.0b5/driver/compat/
lima-1.0b5/driver/include/
lima-1.0b5/driver/testsuite/
lima-1.0b5/driver/testsuite/clone/
lima-1.0b5/driver/testsuite/command/
lima-1.0b5/driver/testsuite/data/
lima-1.0b5/driver/testsuite/etc/
lima-1.0b5/driver/testsuite/include/
lima-1.0b5/driver/testsuite/inherit/
lima-1.0b5/driver/testsuite/inherit/master/
lima-1.0b5/driver/testsuite/log/
lima-1.0b5/driver/testsuite/single/
lima-1.0b5/driver/testsuite/single/tests/compiler/
lima-1.0b5/driver/testsuite/single/tests/efuns/
lima-1.0b5/driver/testsuite/single/tests/operators/
lima-1.0b5/driver/testsuite/u/
lima-1.0b5/driver/tmp/
lima-1.0b5/etc/
lima-1.0b5/lib/WWW/help/
lima-1.0b5/lib/cmds/
lima-1.0b5/lib/cmds/create/
lima-1.0b5/lib/cmds/player/attic/
lima-1.0b5/lib/contrib/bboard/
lima-1.0b5/lib/contrib/boards/
lima-1.0b5/lib/contrib/marriage/
lima-1.0b5/lib/contrib/roommaker/
lima-1.0b5/lib/contrib/transient_effect/
lima-1.0b5/lib/daemons/channel/
lima-1.0b5/lib/daemons/imud/
lima-1.0b5/lib/data/
lima-1.0b5/lib/data/config/
lima-1.0b5/lib/data/links/
lima-1.0b5/lib/data/news/
lima-1.0b5/lib/data/players/
lima-1.0b5/lib/data/secure/
lima-1.0b5/lib/domains/
lima-1.0b5/lib/domains/std/2.4.5/maze1/
lima-1.0b5/lib/domains/std/2.4.5/npc/
lima-1.0b5/lib/domains/std/2.4.5/post_dir/
lima-1.0b5/lib/domains/std/2.4.5/sub/
lima-1.0b5/lib/domains/std/camera/
lima-1.0b5/lib/domains/std/config/
lima-1.0b5/lib/domains/std/cult/
lima-1.0b5/lib/domains/std/effects/
lima-1.0b5/lib/domains/std/misc/
lima-1.0b5/lib/domains/std/monsters/
lima-1.0b5/lib/domains/std/recorder/
lima-1.0b5/lib/domains/std/rooms/
lima-1.0b5/lib/domains/std/rooms/beach/
lima-1.0b5/lib/domains/std/rooms/labyrinth/
lima-1.0b5/lib/domains/std/school/
lima-1.0b5/lib/domains/std/school/O/
lima-1.0b5/lib/domains/std/spells/
lima-1.0b5/lib/domains/std/spells/stock-mage/
lima-1.0b5/lib/domains/std/spells/stock-priest/
lima-1.0b5/lib/help/
lima-1.0b5/lib/help/admin/
lima-1.0b5/lib/help/hints/General_Questions/
lima-1.0b5/lib/help/hints/Pirate_Quest/
lima-1.0b5/lib/help/player/
lima-1.0b5/lib/help/player/bin/
lima-1.0b5/lib/help/player/quests/
lima-1.0b5/lib/help/wizard/
lima-1.0b5/lib/help/wizard/coding/guilds/
lima-1.0b5/lib/help/wizard/coding/rooms/
lima-1.0b5/lib/help/wizard/lib/daemons/
lima-1.0b5/lib/help/wizard/lib/lfun/
lima-1.0b5/lib/help/wizard/lib/std/
lima-1.0b5/lib/help/wizard/mudos_doc/
lima-1.0b5/lib/help/wizard/mudos_doc/applies/
lima-1.0b5/lib/help/wizard/mudos_doc/applies/interactive/
lima-1.0b5/lib/help/wizard/mudos_doc/applies/parsing/
lima-1.0b5/lib/help/wizard/mudos_doc/concepts/
lima-1.0b5/lib/help/wizard/mudos_doc/driver/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/arrays/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/buffers/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/compile/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/filesystem/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/floats/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/functions/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/general/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/mappings/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/mixed/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/numbers/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/parsing/
lima-1.0b5/lib/help/wizard/mudos_doc/lpc/constructs/
lima-1.0b5/lib/help/wizard/mudos_doc/lpc/types/
lima-1.0b5/lib/include/driver/
lima-1.0b5/lib/log/
lima-1.0b5/lib/obj/admtool/
lima-1.0b5/lib/obj/admtool/internal/
lima-1.0b5/lib/obj/admtool/mudinfo/
lima-1.0b5/lib/obj/admtool/secure/
lima-1.0b5/lib/obj/secure/
lima-1.0b5/lib/obj/secure/cmd/
lima-1.0b5/lib/obj/secure/mailers/
lima-1.0b5/lib/obj/secure/shell/
lima-1.0b5/lib/obj/secure/shell/classes/
lima-1.0b5/lib/obj/tasktool/
lima-1.0b5/lib/obj/tasktool/internal/
lima-1.0b5/lib/open/
lima-1.0b5/lib/secure/
lima-1.0b5/lib/secure/cgi/
lima-1.0b5/lib/secure/modules/
lima-1.0b5/lib/secure/simul_efun/
lima-1.0b5/lib/std/adversary/
lima-1.0b5/lib/std/adversary/advancement/
lima-1.0b5/lib/std/adversary/armor/
lima-1.0b5/lib/std/adversary/blows/
lima-1.0b5/lib/std/adversary/death/
lima-1.0b5/lib/std/adversary/formula/
lima-1.0b5/lib/std/adversary/health/
lima-1.0b5/lib/std/adversary/pulse/
lima-1.0b5/lib/std/adversary/wield/
lima-1.0b5/lib/std/classes/event_info/
lima-1.0b5/lib/std/container/
lima-1.0b5/lib/std/living/
lima-1.0b5/lib/std/modules/contrib/
lima-1.0b5/lib/std/patterns/
lima-1.0b5/lib/std/race/
lima-1.0b5/lib/std/race/restricted/
lima-1.0b5/lib/std/room/
lima-1.0b5/lib/tmp/
lima-1.0b5/lib/trans/
lima-1.0b5/lib/trans/admincmds/
lima-1.0b5/lib/trans/obj/
lima-1.0b5/lib/wiz/
/* Do not remove the headers from this file! see /USAGE for more info. */

/* 
** First draft / quickly hacked FTPD.
**
** Rust (rust@lima.mudlib.org) July 12, 1996
**
** There are commands this doesn't support.  If your client
** Seems to want some other command to work for some weird reason,
** and this FTPD won't do it, let me know and I'll add it in.
**
** Myth@Icon of Sin - Jan 19, 1997
**  o Fixed STOR:
**    o ascii: removed \r's
**    o binary: added a fileposition flag
**  o Added the SYST command.
**  o Added line checking to the read callback.
**  o Fixed send so that larger files can be handled without difficulty.
** Jan 21, 1997
**  o Fixed the mkd command.
** 
** Tigran Sept 16, 1997
**  o Fixed nlst and list (which are now identicle) so that they accept
**    flags.  This ftp server should now be able to be used by most 
**    GUI based clients, and should also work w/ ange-ftp and efs (from 
**    emacs and Xemacs.  Note there is still work that can be done here.
**    Acceptable flags are -a -l -C and -F
**
** Naebator (sgt@israel.ru) Jan 9, 1998
**  o Fixed LIST and NLST:
**    o Added -1 flag
**    o unless specified otherwize, forces -l flag on LIST and -1 flag on NLST.
**    o directories report size 0.
**  o If home directory does not exist, it defaults to "/".
** Tigran Jan 15, 1999
**  o Added passive mode connections.
** Loriel July 2001
**  o Tidied up passive mode to close old passive pipes etc.
*/

#include <socket.h>
#include <assert.h>
#include <log.h>
#include <clean_up.h>
#include <ftp_d.h>
#include <ports.h>

inherit M_ACCESS;

#define LTYPE_LIST 0
#define LTYPE_NLST 1

#define RESTRICT_PORTS
#define MIN_PORT 4000
#define MAX_PORT 4100
#define MAX_TRIES 10

private int lastport = MIN_PORT;
private nosave int idlecall;
private string iphost;
private nosave mapping passives = ([]);
private nosave mapping sessions = ([]);
private nosave mapping dataports = ([]);
private nosave mapping dispatch = 
([
  "user" : (: FTP_CMD_user :),
  "pass" : (: FTP_CMD_pass :),
  "retr" : (: FTP_CMD_retr :),
  "stor" : (: FTP_CMD_stor :),
  "nlst" : (: FTP_CMD_nlst :),
  "list" : (: FTP_CMD_list :),
  "pwd"  : (: FTP_CMD_pwd  :),
  "cdup" : (: FTP_CMD_cdup :),
  "cwd"  : (: FTP_CMD_cwd  :),
  "quit" : (: FTP_CMD_quit :),
  "type" : (: FTP_CMD_type :),
  "mkd"  : (: FTP_CMD_mkd  :),
  "port" : (: FTP_CMD_port :),
  "noop" : (: FTP_CMD_noop :),
  "dele" : (: FTP_CMD_dele :),
  "syst" : (: FTP_CMD_syst :),
  "pasv" : (: FTP_CMD_pasv :),
]);
private object sock;

private mixed outfile=([]);

#ifdef ALLOW_ANON_FTP
private nosave string array anon_logins = ({"anonymous", "ftp"});
#endif

int query_lastport() { return lastport; }

varargs private string find_flags(string incoming)
{
  string array parts;
  string array flags=({});
  if(!incoming)
    return 0;
  parts=explode(incoming,
		" ");
  parts=filter(parts,
	       (: $1[0]=='-' :));
  foreach(string part in parts)
    flags+=explode(part,"");
  clean_array(flags);
  return implode(flags-=({"-"}),"");
}

varargs private string strip_flags(string incoming)
{
  string array parts;
  if(!incoming)
    return 0;
  parts=explode(incoming," ");
  parts=filter(parts,
	       (: $1[0]!='-' :));
  return implode(parts," ");
}

private void FTPLOGF(string format, string array args...)
{
  FTPLOG(sprintf(format, args...));
}

private string FTP_get_welcome_msg()
{
  return sprintf("220- %s FTP server ready.\n%s"
		 "220 Anonymous and wizard logins accepted.\n",
		 mud_name(),
		 is_file(FTP_WELCOME) ? "220- " +
		 replace_string(read_file(FTP_WELCOME), "\n", "\n220- ") 
		 +"\n": "");
}

private void FTP_handle_idlers()
{
  map_delete(sessions,0); /* Just make sure there's no stale stuff here */
  if(!sizeof(sessions))
    return;
  foreach(object socket, class ftp_session info in sessions)
  {
    if(info->dataPipe) /* Data connections are still active. */
    {
      info->idleTime = 0;
      continue;
    }
    info->idleTime += 60;
    if(info->idleTime >= MAX_IDLE_TIME+60)
    {
      FTPLOGF("%s idled out at %s.\n", capitalize(info->user), ctime(time()));
      socket->send(sprintf("421 Timeout. (%i seconds): closing control connection.\n",MAX_IDLE_TIME));
      map_delete(sessions,socket);
      destruct(socket);
    }
  }
  if(sizeof(keys(sessions)))
    idlecall=call_out( (:FTP_handle_idlers:), 60);
}

private void FTP_addSession(object socket)
{
  class ftp_session  newSession;

  newSession = new(class ftp_session);
  newSession->cmdPipe = socket;
#ifdef ALLOW_ANON_FTP
  socket->send(FTP_get_welcome_msg());
#else
  socket->send(sprintf("220- %s FTP server ready.\n220 Please login with your"
	       " wizard name.\n", mud_name()));
#endif
  map_delete(sessions,0); /* Just make sure there's no stale stuff here */
  if(!sizeof(sessions))
    {
      idlecall=call_out((:FTP_handle_idlers :), 60);
    }
  sessions[socket] = newSession;
}

private void FTP_write(object socket)
{
  class ftp_session	info;

  info = sessions[dataports[socket]];
  info->cmdPipe->send("226 Transfer complete.\n");
  info->dataPipe->remove();
}

private void FTP_read(object socket, string data)
{
  string                cmd, arg;
  class ftp_session	thisSession;
  function		dispatchTo;
  int i;
  /* If there is no data, it's a new connection. */
  if (!data)
    {
      assert(!sessions[socket]);
      FTP_addSession(socket);
      return;
    }

  thisSession = sessions[socket];

/* Check to make sure that the time idle is not greater than the maximum
 * idle time.  If it is remove the session and everything attached to it */ 
/*   if(thisSession->idleTime>MAX_IDLE_TIME) { */

/*     //    call_out((:destruct($(socket) ):),5); */
/*     return; */
/*   } */

  thisSession->idleTime = 0;
  
  if (!thisSession->command)
    thisSession->command="";

  data=replace_string(data,"\r","");
  thisSession->command+=data;
  if ((i=strsrch(thisSession->command,"\n"))==-1)
    return;
  data=thisSession->command[0..i-1];
  thisSession->command=thisSession->command[i+1..];

  thisSession->command=trim_spaces(thisSession->command);

/* get the command and argument. */
  if (!sscanf(data, "%s %s", cmd, arg))
    {
      cmd = data;
    }

/* lower_case the command... */
  cmd = lower_case(cmd);

/* If we're not connected, these are valid commands... */
  if (!thisSession->connected)
    {
      switch(cmd)
	{
	case "user":
	  FTP_CMD_user(thisSession, arg);
	  return;
	case "pass":
	  FTP_CMD_pass(thisSession, arg);
	  return;
	case "quit":
	  FTP_CMD_quit(thisSession, arg);
	  return;
	case "noop":
	  FTP_CMD_noop(thisSession, arg);
	  return;
	default:
	  socket->send("503 Log in with USER first.\n");
	  return;
	}
    }
  /* We are connected, so dispatch to the proper handler. */
  dispatchTo = dispatch[cmd];
  if (!dispatchTo)
    {
      /* Log command so we know what clients are trying to use */
      socket->send(sprintf("502 Unknown command %s.\n", cmd));
      return;
    }
  if(catch(evaluate(dispatchTo, thisSession, arg)))
     {
       FTPLOGF("%s caused a FAILURE with command '%s'.\n",
	    capitalize(thisSession->user), data);
       socket->send("550 Unknown failure.  Please report what you were doing "
		    "to the mud admin.\n");
     }
  return;
}

private void FTP_PASV_read(class ftp_session info, object socket, string text)
{
  info->dataPipe=socket;
/* I'm not sure I like this...but it shouldn't hurt anything resetting 
 * the callback function */
  info->dataPipe->set_write_callback((:FTP_write:));

/* We now know that the passive connection worked, so the listening socket
 * can be destroyed */
  destruct(passives[info->cmdPipe]);

// TODO ?? REMOVE passuves[info->cmdPipe] ??

  dataports[info->dataPipe] = info->cmdPipe;
  if(!text)
    return;
  switch(info->binary)
  {
    case 0:
      text=replace_string(text,"\r","");
      unguarded(info->priv, (:write_file($(info->targetFile), $(text)):));
      return;
    case 1:
      unguarded(info->priv, (:write_buffer($(info->targetFile), $(info->filepos), $(text)):));
      info->filepos+=sizeof(text);
      return;
    default:
      ENSURE(0);
  }
}

private void FTP_close(object socket)
{
  map_delete(sessions, socket);
}

private void create()
{
  sock = new(SOCKET, SKT_STYLE_LISTEN, PORT_FTP, (: FTP_read :),
	     (: FTP_close :));
  resolve(__HOST__,(: iphost=$2 :) );
}

private void FTP_DATA_read(object socket, mixed text)
{
  class ftp_session	info;
  info = sessions[dataports[socket]];
  switch(info->binary)
  {
    case 0:
      text=replace_string(text,"\r","");
      unguarded(info->priv, (:write_file($(info->targetFile), $(text)):));
      return;
    case 1:
      unguarded(info->priv, (:write_buffer($(info->targetFile), $(info->filepos), $(text)):));
      info->filepos+=sizeof(text);
      return;
    default:
      ENSURE(0);
  }
}

private void FTP_DATA_close(object socket)
{
  class ftp_session	info;
  info = sessions[dataports[socket]];
  if(info)
    info->cmdPipe->send("226 Transfer complete.\n");
}

private void FTP_CMD_user(class ftp_session info, string arg)
{
  NEEDS_ARG();
  arg = lower_case(arg);
  if(info->connected)
  {
    info->cmdPipe->send(sprintf("530 User %s access denied.\n", arg));
    return;
  }
  info->user = arg;

#ifdef ALLOW_ANON_FTP
  if(member_array(arg, anon_logins) != -1)
  {
    info->cmdPipe->send("331 Guest login ok, send your complete e-mail "
			  "address as password.\n");
    return;
  }
#endif

  info->cmdPipe->send(sprintf("331 Password required for %s.\n", arg));
  return;
}

private void FTP_CMD_pass(class ftp_session info, string arg)
{
  string	password;
  mixed	array userinfo;

  NEEDS_ARG();
  if(info->connected || !info->user)
  {
    info->cmdPipe->send("503 Login with USER first.\n");
    return;
  }

#ifdef ALLOW_ANON_FTP
  if(ANON_USER())
  {
    info->cmdPipe->send("230 guest login ok, access restrictions apply.\n");
    info->connected = 1;
    info->priv = 0;
    info->pwd = ANON_PREFIX;
    FTPLOGF("Anomymous login from %s (email = %s)\n", 
	      info->cmdPipe->address()[0], arg);
    return;
  }
#endif

  userinfo = unguarded(1,(:USER_D->query_variable($(info->user), 
						  ({"password"})):));
  if(arrayp(userinfo) && sizeof(userinfo))
    password = userinfo[0];
  else
  {
    info->cmdPipe->send("530 Login incorrect.\n");
    return;
  }
  if(crypt(arg, password) != password || !wizardp(info->user))
  {
    info->cmdPipe->send("530 Login incorrect.\n");
    return;
  }
  info->cmdPipe->send(sprintf("230 User %s logged in.\n", info->user));
  info->connected = 1;
  FTPLOGF("%s connected from %s.\n", capitalize(info->user), 
	      info->cmdPipe->address()[0]);

  if(adminp(info->user))
    info->priv = 1;
  else
    info->priv = info->user;

  info->pwd = join_path(WIZ_DIR,info->user);
  if (!is_directory(info->pwd))
    info->pwd = "/";
  return;
}

private void FTP_CMD_quit(class ftp_session info, string arg)
{
  info->cmdPipe->send("221 Goodbye.\n");
  if(info->dataPipe)
    destruct(info->dataPipe);
  if(member_array(info->cmdPipe, keys(passives))>-1)
  {
    destruct(passives[info->cmdPipe]);
    map_delete(passives, info->cmdPipe);
  }
  FTPLOGF("%s QUIT at %s.\n", capitalize(info->user), ctime(time()));
  map_delete(sessions, info->cmdPipe);
  destruct(info->cmdPipe);

}

int next_port()
{
#ifdef RESTRICT_PORTS
  lastport ++;
  if(lastport>MAX_PORT+1)
    lastport = MIN_PORT+1;
  return lastport - 1;
#endif
  return 0;
}

private void FTP_CMD_pasv(class ftp_session info, string arg)
{
  string ip;
  int port_dec;
  int array port;
  int listen_socket;
  int tries;
  if(arg)
  {
    info->cmdPipe->send("500 command not understood.\n");
    return;
  }

  if(info->dataPipe)
    destruct(info->dataPipe);

  switch(info->binary)
  {
    case 0:
      while(!listen_socket && tries < MAX_TRIES)
        listen_socket = unguarded(1,(:new(SOCKET, SKT_STYLE_LISTEN, 
	            next_port(),
              (: FTP_PASV_read, $(info) :),
              (: FTP_DATA_close :) ) :) );
      break;
    case 1:
      while(!listen_socket && tries < MAX_TRIES)
        listen_socket = unguarded(1,(:new(SOCKET, SKT_STYLE_LISTEN_B, 
	            next_port(),
              (: FTP_PASV_read, $(info) :),
              (: FTP_DATA_close :) ) :) );
      break;
    default:
      return;
  }
  if(member_array(info->cmdPipe, keys(passives))>-1)
    destruct(passives[info->cmdPipe]);

//### ALSO KILL OFF OLD SESSIONS OF SAME USER ...??

  if(sizeof(sessions))
  {
    foreach(object skt, class ftp_session session in sessions)
    {
      if(session && (session->user == info->user) && (skt != info->cmdPipe))
      {
        if(passives[skt])
          destruct(passives[skt]);
        map_delete(sessions, skt);
        map_delete(passives, skt);
        destruct(skt);
      }
    }
  }
  passives[info->cmdPipe]=listen_socket;
  port_dec=listen_socket->local_port();
  port=({port_dec>>8,port_dec%256});
  info->cmdPipe->send(sprintf("227 Entering Passive mode. (%s,%i,%i)\n",
			      replace_string(iphost,".",","),
			      port[0],
			      port[1]) );
}

private void FTP_CMD_port(class ftp_session info, string arg)
{
  string 	ip, *parts;
  int		port;
  NEEDS_ARG();

  parts = explode(arg, ",");
  if(sizeof(parts)!=6)
  {
    info->cmdPipe->send("550 Failed command.\n");
    destruct(info->dataPipe);
    return;
  }
  ip = implode(parts[0..3],".");
/* Make a 16 bit port # out of 2 8 bit values.  */
  port = (to_int(parts[4]) << 8) + to_int(parts[5]);
  
//tell_user("loriel", sprintf("info %O", info));
  if(info->dataPipe)
    destruct(info->dataPipe);

  switch(info->binary)
  {
    case 0:
      info->dataPipe = unguarded(1,(:new(SOCKET, SKT_STYLE_CONNECT, 
					 sprintf("%s %d", $(ip), $(port)),
					 (: FTP_DATA_read :),
					 (: FTP_DATA_close :) ):));
      break;
    case 1:
      info->dataPipe = unguarded(1,(:new(SOCKET, SKT_STYLE_CONNECT_B, 
					 sprintf("%s %d", $(ip), $(port)),
					 (: FTP_DATA_read :),
					 (: FTP_DATA_close :) ):));
      break;
    default:
      return;
  }
/* map the data port to the cmd port so we can find the cmd port when
 * we're given the data port. */
  dataports[info->dataPipe] = info->cmdPipe;
  info->dataPipe->set_write_callback((:FTP_write:));
  info->cmdPipe->send("200 PORT command successful.\n");
//tell_user("loriel", sprintf("info %O", info));
  return;
}

private void do_list(class ftp_session info, string arg, int ltype)
{
  string array 	files;
  string flags;
  string output;
  int isfile;
  
  flags=find_flags(arg);
  arg=strip_flags(arg);
  if(!arg || arg == "")
      arg = ".";
  
  /* Check to make sure we aren't really looking at a file.
   * canonical_file() mucks this up a bit later on so the check
   * is necessary */ 
  if(arg[<2..]=="/.") 
    if(is_file(arg[0..<3])) 
      isfile=1;
  
  arg = evaluate_path(arg, info->pwd);
  ANON_CHECK(arg);
  if(unguarded(1, (:is_directory($(arg)):)))
      arg = join_path(arg, "*");
  if(unguarded(info->priv, (:file_size(base_path($(arg))):)) == -1)
    {
      info->cmdPipe->send(sprintf("550 %s: No such file OR directory.\n", arg));
      destruct(info->dataPipe);
      return;
    }
  if(isfile)
    files=({});
  else
    files = unguarded(info->priv, (:get_dir($(arg),-1):));
  if(!files&&!isfile)
    {
	  info->cmdPipe->send(sprintf("550 %s: Permission denied.\n",arg));
	  destruct(info->dataPipe);
	  return;
    }
  if(flags)
    if(strsrch(flags,'a')==-1)
      files = filter(files, (: member_array($1[0], ({".",".."})) == -1 :));
  if(!sizeof(files)&&!isfile)
    {
      info->cmdPipe->send("550 No files found.\n");
      destruct(info->dataPipe);
      return;
    }

  /* in case of LIST imply -l */
  /* in case of NLST imply -1 */
  if (ltype == LTYPE_LIST)
    {
      if (flags)
	{
	  if ( (strsrch(flags, 'l') == -1) &&
	       (strsrch(flags, 'C') == -1) &&
	       (strsrch(flags, '1') == -1) )
	    flags += "l";
	}
      else
	flags = "l";
    }
  else
    {
      if (flags)
	{
	  if ( (strsrch(flags, 'l') == -1) &&
	       (strsrch(flags, 'C') == -1) &&
	       (strsrch(flags, '1') == -1) )
	    flags += "1";
	}
      else
	flags = "1";
    }

  /* Check the flags for output now */

  /* Check the F flag */
  if(strsrch(flags,'F')>-1)
    {
      foreach(mixed array file in files)
	if(file[1]==-2)
	  file[0]=sprintf("%s/",file[0]);
    }
  /* The C flag */
  if(strsrch(flags,'C')>-1)
    {
      int lines;
      int size;
      int i;
      if((strsrch(flags,'l')>-1) || (strsrch(flags,'1')>-1))
	{
	  info->cmdPipe->send("550: LIST -C flag is incompatible with -1 or -l.\n");
	  destruct(info->dataPipe);
	  return;
	}
      lines=((size=sizeof(files))/3)+1;
      output="";
      for(i=0;i<lines;i++)
	{
	  mixed array these_files;
	  if((i*3+2)<size)
	      these_files=files[(i*3)..(i*3+2)];
	  else if(i*3<size)
	    {
	      these_files=files[(i*3)..];
	      while(sizeof(these_files)<3)
		these_files+=({ ({"",0,0}) });
	    }
	  else 
	    break;
	  output=sprintf("%s%-=25s %-=25s %-=25s\n",
			 output,
			 these_files[0][0],
			 these_files[1][0],
			 these_files[2][0]
			 );
	}
    }
  if(strsrch(flags,'l')>-1)
    {
      if(strsrch(flags,'1')>-1)
	{
	  info->cmdPipe->send("550: LIST -l and -1 flags incompatible.\n");
	  destruct(info->dataPipe);
	  return;
	}
      
      output = implode(map(files, 
			   (:sprintf("%s %3i %=9s %=8s %=7s %s%5s %s",
				     $1[1]==-2?"drwxrwxr-x":"-rw-rw-r--",
				     1,
				     lower_case(replace_string(mud_name(), " ", "_"))[0..7],
				     lower_case(replace_string(mud_name(), " ", "_"))[0..7],
				     $1[1]==-2?"0":sprintf("%d",$1[1]),
				     ctime($1[2])[4..10],
				     (time()-$1[2])>31536000?ctime($1[2])[20..]:ctime($1[2])[11..15],
				     $1[0])
			    :)),"\n");
    }


  if(strsrch(flags,'1')>-1)
    output=implode(map(files,(:sprintf("%s",$1[0]) :)),"\n");

  info->cmdPipe->send("150 Opening ascii mode data connection for file list\n");
  info->dataPipe->send(implode(explode(output,"\n"), "\r\n")+"\r\n");
  return;
}

private void FTP_CMD_list(class ftp_session info, string arg)
{
/* Make sure the dataPort is connected, otherwise this will error */
  if(!info->dataPipe)
  {
    call_out( (: FTP_CMD_list :),2,info,arg);
    return;
  }
  do_list(info, arg, LTYPE_LIST);
}

private void FTP_CMD_nlst(class ftp_session info,string arg)
{
  /* Make sure the dataPort is connected, otherwise this will error */
  if(!info->dataPipe)
  {
    call_out( (: FTP_CMD_nlst :),2,info,arg);
    return;
  }
  
  do_list(info,arg, LTYPE_NLST);
}

private void FTP_CMD_retr(class ftp_session info, string arg)
{
  string	target_file;
  int           i;

  /* Make sure the dataPort is connected, otherwise this will error */
  if(!info->dataPipe)
  {
    call_out( (: FTP_CMD_retr :),2,info,arg);
    return;
  }
  NEEDS_ARG();
  
  target_file = evaluate_path(arg, info->pwd);

  ANON_CHECK(target_file);

  if(unguarded(info->priv, (:is_directory($(target_file)):)))
  {
    info->cmdPipe->send(sprintf("550 %s: Can't retrieve (it's a directory)."
       "\n", target_file));
    destruct(info->dataPipe);
    return;
  }
  if(!unguarded(info->priv, (:is_file($(target_file)):)))
  {
    info->cmdPipe->send(sprintf("550 %s: No such file OR directory.\n", 
        target_file));
    destruct(info->dataPipe);
    return;
  }
  if(!unguarded(info->priv, (:file_size($(target_file)):)))
  {
    info->cmdPipe->send(sprintf("550 %s: File contains nothing.\n",
        target_file));
    destruct(info->dataPipe);
    return;
  }
  
  switch(info->binary)
  {
    case 0:	
      i=file_size(target_file);
      FTPLOGF("%s GOT %s.\n", capitalize(info->user), target_file);

      outfile[info->dataPipe]=({target_file,0,0,info->cmdPipe});
      info->dataPipe->set_write_callback((: FTP_CMD_retr_callback :));

      info->cmdPipe->send(sprintf("150 Opening ascii mode data connection for "
				  "%s (%d bytes).\n", target_file, i));

      info->dataPipe->send(FTP_CMD_retr_callback(info->dataPipe));
      break;
    case 1:
      i=file_size(target_file);

      outfile[info->dataPipe]=({target_file,1,0,info->cmdPipe});
      info->dataPipe->set_write_callback((: FTP_CMD_retr_callback :));

      info->cmdPipe->send(sprintf("150 Opening binary mode data connection "
				  "for %s (%d bytes).\n", target_file, i));

      info->dataPipe->send(FTP_CMD_retr_callback(info->dataPipe));
      break;
    default:
      ENSURE(0);
  }
}

void FTP_CMD_pwd(class ftp_session info, string arg)
{
  info->cmdPipe->send(sprintf("257 \"%s\" is current directory.\n", info->pwd));
}

void FTP_CMD_noop(class ftp_session info, string arg)
{
    info->cmdPipe->send("221 NOOP command successful.\n");
}

private void FTP_CMD_stor(class ftp_session info, string arg)
{
/* Make sure the dataPort is connected, otherwise this will error */
  if(!info->dataPipe)
  {
    call_out( (: FTP_CMD_stor :),2,info,arg);
    return;
  }

  NEEDS_ARG();

  arg = evaluate_path(arg, info->pwd);


#ifndef ANON_CAN_PUT
#ifdef ALLOW_ANON_FTP
  if(ANON_USER())
  {
    info->cmdPipe->send("550 Permission denied.\n");
    destruct(info->dataPipe);
    return;
  }
#endif
#else
  ANON_CHECK(arg);
#endif

  if(!unguarded(info->priv,(:is_directory(base_path($(arg))):)))
  {
    info->cmdPipe->send("553 No such directory to store into.\n");
    destruct(info->dataPipe);
    return;
  }
  info->targetFile = arg;
  if(unguarded(info->priv, (:is_file($(arg)):)))
  {
    if(!unguarded(info->priv, (:rm($(arg)):)))
    {
      info->cmdPipe->send(sprintf("550 %s: Permission denied.\n", arg));
      destruct(info->dataPipe);
      return;
    }
  }
  else if(!unguarded(info->priv, (:write_file($(arg), ""):)))
  {
    info->cmdPipe->send(sprintf("550 %s: Permission denied.\n", arg));
    destruct(info->dataPipe);
    return;
  }
  FTPLOGF("%s PUT %s.\n", capitalize(info->user), arg);

/* Reset the file position flag. */
  info->filepos=0;
  info->cmdPipe->send(sprintf("150 Opening %s mode data connection for %s.\n",
			      info->binary ? "binary" : "ascii",
			      arg));
}

private void FTP_CMD_cdup(class ftp_session info, string arg)
{
  FTP_CMD_cwd(info, "..");
}

private void FTP_CMD_cwd(class ftp_session info, string arg)
{
  string	newpath;

  NEEDS_ARG();
  newpath = evaluate_path(arg, info->pwd);

  ANON_CHECK(newpath);

  if(!unguarded(info->priv, (:is_directory($(newpath)):)))
  {
    info->cmdPipe->send(sprintf("550 %s: No such file or directory.\n", 
        newpath));
    return;
  }
  info->pwd = newpath;
  info->cmdPipe->send("250 CWD command successful.\n");
}

private void FTP_CMD_mkd(class ftp_session info, string arg)
{
  NEEDS_ARG();

  arg = evaluate_path(arg, info->pwd);

#ifndef ANON_CAN_PUT
#ifdef  ALLOW_ANON_FTP
  if(ANON_USER())
  {
    info->cmdPipe->send("550 Permission denied.\n");
    destruct(info->dataPipe);
    return;
  }
#endif
#else
  ANON_CHECK(arg);
#endif

  if(!unguarded(info->priv, (:is_directory(base_path($(arg))):)))
  {
    info->cmdPipe->send(sprintf("550 %s: No such directory.\n",
        base_path(arg)));
    return;
  }
  if(unguarded(info->priv, (:file_size($(arg)):)) != -1)
  {
    info->cmdPipe->send(sprintf("550 %s: File exists.\n", arg));
    return;
  }

  if(!unguarded(info->priv, (:mkdir($(arg)):)))
  {
    info->cmdPipe->send(sprintf("550 %s: Permission denied.\n", arg));
    return;
  }
  info->cmdPipe->send("257 MKD command successful.\n");
  return;
}

private void FTP_CMD_type(class ftp_session info, string arg)
{
  NEEDS_ARG();
  switch(arg)
  {
    case "a":
    case "A":
      info->binary = 0;
      info->cmdPipe->send("200 Type set to A.\n");
      return;
    case "i":
    case "I":
      info->binary = 1;
      info->cmdPipe->send("200 Type set to I.\n");
      return;
    default:
      info->cmdPipe->send("550 Unknown file type.\n");
      return;
  }
}

private void FTP_CMD_dele(class ftp_session info, string arg)
{
  NEEDS_ARG();
  arg = evaluate_path(arg, info->pwd);

  ANON_CHECK(arg);

  if(!unguarded(info->priv, (: is_file($(arg)):)))
  {
    info->cmdPipe->send(sprintf("550 %s: No such file OR directory.\n", arg));
    return;
  }
  if(!unguarded(info->priv, (: rm($(arg)) :)))
  {
    info->cmdPipe->send(sprintf("550 %s: Permission denied.\n",arg));
    return;
  }
  FTPLOGF("%s DELETED %s.\n", capitalize(info->user), arg);
  info->cmdPipe->send("250 DELE command successful.\n");
}

void remove()
{
  class ftp_session item;

  foreach(item in values(sessions))
  {
    if(objectp(item->cmdPipe))
  	  destruct(item->cmdPipe);

    if(objectp(item->dataPipe))
	    destruct(item->dataPipe);
  }
  
  destruct(sock);
  remove_call_out(idlecall);
}

int clean_up() { return 0; }

string array list_users()
{
  return map(values(sessions), (: ((class ftp_session)$1)->connected ? 
	    ((class ftp_session)$1)->user : "(login)" :));
}

private void FTP_CMD_syst(class ftp_session info, string arg)
{
  info->cmdPipe->send("215 UNIX Mud Name: "+mud_name()+"\n");
}

string FTP_CMD_retr_callback(object ob)
{
  int start,length;
  mixed ret;

  if (!ob || undefinedp(outfile[ob]))
    return 0;

  start=outfile[ob][2];
  length=FTP_BLOCK_SIZE;
  outfile[ob][2]+=length;

  if (start+length>file_size(outfile[ob][0]))
    length=file_size(outfile[ob][0])-start;

  ret=read_buffer(outfile[ob][0],start,length);

  if (start+length>=file_size(outfile[ob][0]))
  {
    map_delete(outfile,ob);
    ob->set_write_callback((:FTP_write:));
  }

  return ret;
}

mapping query_passives() { return copy(passives); }

mapping query_sessions() { return copy(sessions); }

mapping query_dataports() { return copy(dataports); }

string query_iphost() { return iphost; }