dw_fluffos_v2/
dw_fluffos_v2/fluffos-2.9-ds2.05/
dw_fluffos_v2/fluffos-2.9-ds2.05/ChangeLog.old/
dw_fluffos_v2/fluffos-2.9-ds2.05/Win32/
dw_fluffos_v2/fluffos-2.9-ds2.05/compat/
dw_fluffos_v2/fluffos-2.9-ds2.05/compat/simuls/
dw_fluffos_v2/fluffos-2.9-ds2.05/include/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/clone/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/command/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/data/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/etc/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/include/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/inherit/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/inherit/master/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/log/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/single/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/single/tests/compiler/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/single/tests/efuns/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/single/tests/operators/
dw_fluffos_v2/fluffos-2.9-ds2.05/testsuite/u/
dw_fluffos_v2/fluffos-2.9-ds2.05/tmp/
dw_fluffos_v2/fluffos-2.9-ds2.05/windows/
dw_fluffos_v2/lib/
dw_fluffos_v2/lib/binaries/cmds/
dw_fluffos_v2/lib/binaries/cmds/creator/
dw_fluffos_v2/lib/binaries/cmds/living/
dw_fluffos_v2/lib/binaries/cmds/player/
dw_fluffos_v2/lib/binaries/d/admin/obj/
dw_fluffos_v2/lib/binaries/d/liaison/
dw_fluffos_v2/lib/binaries/global/virtual/
dw_fluffos_v2/lib/binaries/global/virtual/setup_compiler/
dw_fluffos_v2/lib/binaries/obj/handlers/autodoc/
dw_fluffos_v2/lib/binaries/obj/handlers/terrain_things/
dw_fluffos_v2/lib/binaries/obj/misc/
dw_fluffos_v2/lib/binaries/obj/misc/buckets/
dw_fluffos_v2/lib/binaries/obj/monster/
dw_fluffos_v2/lib/binaries/obj/reactions/
dw_fluffos_v2/lib/binaries/obj/reagents/
dw_fluffos_v2/lib/binaries/secure/cmds/creator/
dw_fluffos_v2/lib/binaries/secure/master/
dw_fluffos_v2/lib/binaries/std/
dw_fluffos_v2/lib/binaries/std/dom/
dw_fluffos_v2/lib/binaries/std/effects/object/
dw_fluffos_v2/lib/binaries/std/guilds/
dw_fluffos_v2/lib/binaries/std/languages/
dw_fluffos_v2/lib/binaries/std/races/
dw_fluffos_v2/lib/binaries/std/room/
dw_fluffos_v2/lib/binaries/std/room/basic/
dw_fluffos_v2/lib/binaries/std/shops/
dw_fluffos_v2/lib/binaries/std/shops/inherit/
dw_fluffos_v2/lib/binaries/www/
dw_fluffos_v2/lib/cmds/guild-race/
dw_fluffos_v2/lib/cmds/guild-race/crafts/
dw_fluffos_v2/lib/cmds/guild-race/other/
dw_fluffos_v2/lib/cmds/playtester/
dw_fluffos_v2/lib/cmds/playtester/senior/
dw_fluffos_v2/lib/d/admin/
dw_fluffos_v2/lib/d/admin/log/
dw_fluffos_v2/lib/d/admin/mapper/31-10-01/mapmaker/event/
dw_fluffos_v2/lib/d/admin/meetings/
dw_fluffos_v2/lib/d/admin/obj/
dw_fluffos_v2/lib/d/admin/room/we_care/
dw_fluffos_v2/lib/d/admin/save/
dw_fluffos_v2/lib/d/dist/
dw_fluffos_v2/lib/d/dist/mtf/
dw_fluffos_v2/lib/d/dist/pumpkin/
dw_fluffos_v2/lib/d/dist/pumpkin/chars/
dw_fluffos_v2/lib/d/dist/pumpkin/desert/
dw_fluffos_v2/lib/d/dist/pumpkin/gumboot/
dw_fluffos_v2/lib/d/dist/pumpkin/hospital/
dw_fluffos_v2/lib/d/dist/pumpkin/inherit/
dw_fluffos_v2/lib/d/dist/pumpkin/map/
dw_fluffos_v2/lib/d/dist/pumpkin/plain/
dw_fluffos_v2/lib/d/dist/pumpkin/pumpkin/
dw_fluffos_v2/lib/d/dist/pumpkin/save/
dw_fluffos_v2/lib/d/dist/pumpkin/squash/
dw_fluffos_v2/lib/d/dist/pumpkin/terrain/
dw_fluffos_v2/lib/d/dist/pumpkin/woods/
dw_fluffos_v2/lib/d/dist/start/
dw_fluffos_v2/lib/d/learning/TinyTown/buildings/
dw_fluffos_v2/lib/d/learning/TinyTown/map/
dw_fluffos_v2/lib/d/learning/TinyTown/roads/
dw_fluffos_v2/lib/d/learning/add_command/
dw_fluffos_v2/lib/d/learning/arms_and_weps/
dw_fluffos_v2/lib/d/learning/chars/
dw_fluffos_v2/lib/d/learning/cutnpaste/
dw_fluffos_v2/lib/d/learning/examples/npcs/
dw_fluffos_v2/lib/d/learning/examples/player_houses/npcs/
dw_fluffos_v2/lib/d/learning/examples/terrain_map/basic/
dw_fluffos_v2/lib/d/learning/functions/
dw_fluffos_v2/lib/d/learning/handlers/
dw_fluffos_v2/lib/d/learning/help_topics/npcs/
dw_fluffos_v2/lib/d/learning/help_topics/objects/
dw_fluffos_v2/lib/d/learning/help_topics/rcs_demo/
dw_fluffos_v2/lib/d/learning/help_topics/rooms/
dw_fluffos_v2/lib/d/learning/help_topics/rooms/crowd/
dw_fluffos_v2/lib/d/learning/help_topics/rooms/situations/
dw_fluffos_v2/lib/d/learning/items/
dw_fluffos_v2/lib/d/learning/save/
dw_fluffos_v2/lib/d/liaison/
dw_fluffos_v2/lib/d/liaison/NEWBIE/doc/
dw_fluffos_v2/lib/d/liaison/NEWBIE/save/oldlog/
dw_fluffos_v2/lib/db/
dw_fluffos_v2/lib/doc/
dw_fluffos_v2/lib/doc/creator/
dw_fluffos_v2/lib/doc/creator/autodoc/include/reaction/
dw_fluffos_v2/lib/doc/creator/autodoc/include/ritual_system/
dw_fluffos_v2/lib/doc/creator/autodoc/include/talker/
dw_fluffos_v2/lib/doc/creator/autodoc/include/terrain_map/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/baggage/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/clock/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/clothing/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/cont_save/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/corpse/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/money/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/monster/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/scabbard/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/service_provider/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/state_changer/
dw_fluffos_v2/lib/doc/creator/autodoc/obj/wand/
dw_fluffos_v2/lib/doc/creator/autodoc/std/book_dir/
dw_fluffos_v2/lib/doc/creator/autodoc/std/key/
dw_fluffos_v2/lib/doc/creator/autodoc/std/learning/
dw_fluffos_v2/lib/doc/creator/autodoc/std/map/
dw_fluffos_v2/lib/doc/creator/autodoc/std/race/
dw_fluffos_v2/lib/doc/creator/autodoc/std/weapon_logic/
dw_fluffos_v2/lib/doc/creator/files/
dw_fluffos_v2/lib/doc/creator/policy/
dw_fluffos_v2/lib/doc/creator/room/
dw_fluffos_v2/lib/doc/effects/
dw_fluffos_v2/lib/doc/ideas/
dw_fluffos_v2/lib/doc/known_command/
dw_fluffos_v2/lib/doc/lpc/basic_manual/
dw_fluffos_v2/lib/doc/lpc/intermediate/
dw_fluffos_v2/lib/doc/new/add_command/
dw_fluffos_v2/lib/doc/new/handlers/
dw_fluffos_v2/lib/doc/new/living/
dw_fluffos_v2/lib/doc/new/living/race/
dw_fluffos_v2/lib/doc/new/living/spells/
dw_fluffos_v2/lib/doc/new/player/
dw_fluffos_v2/lib/doc/new/room/guild/
dw_fluffos_v2/lib/doc/new/room/outside/
dw_fluffos_v2/lib/doc/new/room/storeroom/
dw_fluffos_v2/lib/doc/object/
dw_fluffos_v2/lib/doc/playtesters/
dw_fluffos_v2/lib/doc/policy/
dw_fluffos_v2/lib/doc/weapons/
dw_fluffos_v2/lib/global/handlers/
dw_fluffos_v2/lib/global/virtual/setup_compiler/
dw_fluffos_v2/lib/include/
dw_fluffos_v2/lib/include/cmds/
dw_fluffos_v2/lib/include/effects/
dw_fluffos_v2/lib/include/npc/
dw_fluffos_v2/lib/include/shops/
dw_fluffos_v2/lib/net/daemon/chars/
dw_fluffos_v2/lib/net/inherit/
dw_fluffos_v2/lib/net/intermud3/
dw_fluffos_v2/lib/net/intermud3/services/
dw_fluffos_v2/lib/net/obj/
dw_fluffos_v2/lib/net/save/
dw_fluffos_v2/lib/net/smnmp/
dw_fluffos_v2/lib/net/snmp/
dw_fluffos_v2/lib/obj/amulets/
dw_fluffos_v2/lib/obj/b_day/
dw_fluffos_v2/lib/obj/examples/
dw_fluffos_v2/lib/obj/food/alcohol/
dw_fluffos_v2/lib/obj/food/chocolates/
dw_fluffos_v2/lib/obj/food/fruits/
dw_fluffos_v2/lib/obj/food/meat/
dw_fluffos_v2/lib/obj/food/nuts/
dw_fluffos_v2/lib/obj/food/seafood/
dw_fluffos_v2/lib/obj/food/vegetables/
dw_fluffos_v2/lib/obj/fungi/
dw_fluffos_v2/lib/obj/furnitures/artwork/
dw_fluffos_v2/lib/obj/furnitures/bathroom/
dw_fluffos_v2/lib/obj/furnitures/beds/
dw_fluffos_v2/lib/obj/furnitures/cabinets/
dw_fluffos_v2/lib/obj/furnitures/chairs/
dw_fluffos_v2/lib/obj/furnitures/chests/
dw_fluffos_v2/lib/obj/furnitures/clocks/
dw_fluffos_v2/lib/obj/furnitures/crockery/
dw_fluffos_v2/lib/obj/furnitures/cupboards/
dw_fluffos_v2/lib/obj/furnitures/cushions/
dw_fluffos_v2/lib/obj/furnitures/fake_plants/
dw_fluffos_v2/lib/obj/furnitures/lamps/
dw_fluffos_v2/lib/obj/furnitures/mirrors/
dw_fluffos_v2/lib/obj/furnitures/outdoor/
dw_fluffos_v2/lib/obj/furnitures/safes/
dw_fluffos_v2/lib/obj/furnitures/shelves/
dw_fluffos_v2/lib/obj/furnitures/sideboards/
dw_fluffos_v2/lib/obj/furnitures/sofas/
dw_fluffos_v2/lib/obj/furnitures/stoves/
dw_fluffos_v2/lib/obj/furnitures/tables/
dw_fluffos_v2/lib/obj/furnitures/wardrobes/
dw_fluffos_v2/lib/obj/handlers/
dw_fluffos_v2/lib/obj/handlers/autodoc/
dw_fluffos_v2/lib/obj/jewellery/anklets/
dw_fluffos_v2/lib/obj/jewellery/bracelets/
dw_fluffos_v2/lib/obj/jewellery/earrings/
dw_fluffos_v2/lib/obj/jewellery/misc/
dw_fluffos_v2/lib/obj/jewellery/necklaces/
dw_fluffos_v2/lib/obj/jewellery/rings/
dw_fluffos_v2/lib/obj/media/
dw_fluffos_v2/lib/obj/misc/buckets/
dw_fluffos_v2/lib/obj/misc/jars/
dw_fluffos_v2/lib/obj/misc/papers/
dw_fluffos_v2/lib/obj/misc/player_shop/
dw_fluffos_v2/lib/obj/misc/shops/
dw_fluffos_v2/lib/obj/misc/traps/
dw_fluffos_v2/lib/obj/monster/
dw_fluffos_v2/lib/obj/monster/godmother/
dw_fluffos_v2/lib/obj/monster/transport/
dw_fluffos_v2/lib/obj/plants/inherit/
dw_fluffos_v2/lib/obj/potions/
dw_fluffos_v2/lib/open/boards/
dw_fluffos_v2/lib/save/autodoc/
dw_fluffos_v2/lib/save/bank_accounts/
dw_fluffos_v2/lib/save/boards/frog/
dw_fluffos_v2/lib/save/books/bed_catalog/
dw_fluffos_v2/lib/save/creators/
dw_fluffos_v2/lib/save/mail/
dw_fluffos_v2/lib/save/mail/p/
dw_fluffos_v2/lib/save/soul/data/
dw_fluffos_v2/lib/save/tasks/
dw_fluffos_v2/lib/save/vaults/
dw_fluffos_v2/lib/secure/cmds/lord/
dw_fluffos_v2/lib/secure/config/
dw_fluffos_v2/lib/secure/items/
dw_fluffos_v2/lib/secure/player/
dw_fluffos_v2/lib/soul/
dw_fluffos_v2/lib/soul/i/
dw_fluffos_v2/lib/soul/j/
dw_fluffos_v2/lib/soul/k/
dw_fluffos_v2/lib/soul/o/
dw_fluffos_v2/lib/soul/q/
dw_fluffos_v2/lib/soul/to_approve/
dw_fluffos_v2/lib/soul/u/
dw_fluffos_v2/lib/soul/v/
dw_fluffos_v2/lib/soul/wish_list/
dw_fluffos_v2/lib/soul/y/
dw_fluffos_v2/lib/soul/z/
dw_fluffos_v2/lib/std/creator/
dw_fluffos_v2/lib/std/effects/
dw_fluffos_v2/lib/std/effects/attached/
dw_fluffos_v2/lib/std/effects/external/
dw_fluffos_v2/lib/std/effects/fighting/
dw_fluffos_v2/lib/std/effects/other/
dw_fluffos_v2/lib/std/environ/
dw_fluffos_v2/lib/std/guilds/
dw_fluffos_v2/lib/std/hospital/
dw_fluffos_v2/lib/std/house/
dw_fluffos_v2/lib/std/house/onebedhouse/
dw_fluffos_v2/lib/std/house/onebedhut/
dw_fluffos_v2/lib/std/house/tworoomflat/
dw_fluffos_v2/lib/std/languages/
dw_fluffos_v2/lib/std/liquids/
dw_fluffos_v2/lib/std/nationality/
dw_fluffos_v2/lib/std/nationality/accents/
dw_fluffos_v2/lib/std/nationality/accents/national/
dw_fluffos_v2/lib/std/nationality/accents/regional/
dw_fluffos_v2/lib/std/npc/goals/
dw_fluffos_v2/lib/std/npc/goals/basic/
dw_fluffos_v2/lib/std/npc/goals/misc/
dw_fluffos_v2/lib/std/npc/inherit/
dw_fluffos_v2/lib/std/npc/plans/
dw_fluffos_v2/lib/std/npc/plans/basic/
dw_fluffos_v2/lib/std/outsides/
dw_fluffos_v2/lib/std/races/shadows/
dw_fluffos_v2/lib/std/room/basic/topography/
dw_fluffos_v2/lib/std/room/controller/
dw_fluffos_v2/lib/std/room/controller/topography/
dw_fluffos_v2/lib/std/room/furniture/games/
dw_fluffos_v2/lib/std/room/furniture/inherit/
dw_fluffos_v2/lib/std/room/inherit/carriage/
dw_fluffos_v2/lib/std/room/inherit/topography/
dw_fluffos_v2/lib/std/room/punishments/
dw_fluffos_v2/lib/std/room/topography/area/
dw_fluffos_v2/lib/std/room/topography/iroom/
dw_fluffos_v2/lib/std/room/topography/milestone/
dw_fluffos_v2/lib/std/shadows/
dw_fluffos_v2/lib/std/shadows/attached/
dw_fluffos_v2/lib/std/shadows/curses/
dw_fluffos_v2/lib/std/shadows/disease/
dw_fluffos_v2/lib/std/shadows/fighting/
dw_fluffos_v2/lib/std/shadows/room/
dw_fluffos_v2/lib/std/shops/controllers/
dw_fluffos_v2/lib/std/shops/objs/
dw_fluffos_v2/lib/std/shops/player_shop/
dw_fluffos_v2/lib/std/shops/player_shop/office_code/
dw_fluffos_v2/lib/std/socket/
dw_fluffos_v2/lib/www/
dw_fluffos_v2/lib/www/external/autodoc/
dw_fluffos_v2/lib/www/external/java/telnet/Documentation/
dw_fluffos_v2/lib/www/external/java/telnet/Documentation/images/
dw_fluffos_v2/lib/www/external/java/telnet/examples/
dw_fluffos_v2/lib/www/external/java/telnet/tools/
dw_fluffos_v2/lib/www/pics/
dw_fluffos_v2/lib/www/secure/creator/
dw_fluffos_v2/lib/www/secure/editors/
dw_fluffos_v2/lib/www/secure/survey_results/
dw_fluffos_v2/win32/
/*  -*- LPC -*-  */
/*
 * $Locker: ceres $
 * $Id: ftpd.c,v 1.12 2003/03/21 02:28:03 ceres Exp ceres $
 * 
*/
/* 
 * FTP daemon, complete rewrite using classes.
 * By Turrican@Discworld, 21-4-96.
 */

#include <network.h>

inherit SERVER;

#include <ftp.h>
#include <localtime.h>
#include <player_handler.h>

#define DELAY_LOG_FLUSH 5

private nosave mapping socket_info, data_sockets;
private nosave mapping _log_file_info = ([ ]);
private nosave int _log_file_flush_id;

protected void create() {
  server::create();
  seteuid("Root");
  socket_info = ([]);
  data_sockets = ([]);
  SetSocketType(STREAM);
  write(mud_name() + "\n");
  if(mud_name() != "Discworld")
    call_out("setup_ftp", 2);
  else
    destruct(this_object());
} /* create() */

/**
 * This method flushes out all the buffered stuff for the log files.
 */
private void flush_log_files() {
  string fname;
  string data;
  
  _log_file_flush_id = 0;
  foreach (fname, data in _log_file_info) {
      map_delete(_log_file_info, fname);
      unguarded((: write_file, fname, data :));
   }
   _log_file_info = ([ ]);
} /* flush_log_files() */

void log_write(string name, string fmt, mixed *args ...) {
    if (!_log_file_flush_id) {
     _log_file_flush_id = call_out((: flush_log_files :), DELAY_LOG_FLUSH);
  }
  if (!_log_file_info[name]) {
     _log_file_info[name] = "";
  }
  if (sizeof(args)) {
    _log_file_info[name] += sprintf(fmt, args ...);
  } else {
    _log_file_info[name] += fmt;
  }
}

protected void setup_ftp() {
  int x;

  if ((x = eventCreateSocket(FTP_PORT)) < 0) {
    if (this_object()) destruct(this_object());
    return;
  }
  call_out("check_connections", 5*60);
} /* setup_ftp() */

/* returns an array of users connected to ftpd */
string *query_connections() {
  class session *vals, val;
  string *list;

  list = ({ });
  vals = (class session *)values(socket_info);
  foreach (val in vals) {
    if (val->user_name) {
      if (val->logged_in) {
        list += ({ capitalize(val->user_name) });
      } else {
        list += ({ "login" });
      }
    }
  }
  return list;
} /* query_connections() */

protected string ls(string path, int mask) {
   string *files, tmp, tmp2, creator, domain;
   int i, j, s, current_time;
   mixed *xfiles, *stats;
   
#ifdef DEBUG
    TP(sprintf("ls(%s,%d)\n",path,mask));
#endif
   /* If the path is a directory, get contents uynless MASK_D is set */
   if (!(mask & MASK_D) && file_size(path) == -2) {
      if (path[ <1 ] == '/')
         path += "*";
      else
         path += "/*";
   }
   
   /* begin narrow columnar "nlst" */
   if (!(MASK_L & mask)) {
      files = get_dir(path);
      /* can only happen if permissions are messed up at account level */
      if (!files)
         return "";
      if (!(MASK_A & mask))
         files -= ({ ".", ".." });
      if (!(i = sizeof( files )))
         return "";
      /* no wild cards...must have been the exact pathname to a file */
      if (strsrch(path, '*') == -1 && strsrch(path, '?') == -1) {
         return files[0] + "\n";
      }
      /* remove globber at end of path, leave a trailing slash */
      j = strsrch(path, '/', -1);
      path = path[0..j];
      while (i--) {
         /* scan next level down for files */
         tmp = sprintf("%s%s/", path, files[i]);
         if (MASK_F & mask) {
            if (strsrch(tmp, "/./") != -1 || strsrch(tmp, "/../") != -1) {
               files[i] += "/";
               continue;
            }
            if (file_size(tmp) == -2)
               files[i] += "/";
            else if (stat(tmp[0..<2])[2])
               files[i] += "*";
         }
      }
      if (MASK_C & mask)
         return sprintf("%-#70s\n", implode(files, "\n"));
      else
         return implode(files, "\n") + "\n";
   }
   
   /* don't recurse */
   if (!(MASK_R & mask)) {
      /* begin long "list" */
      xfiles = get_dir(path, -1);
      if (!(mask & MASK_A))
         xfiles = filter_array(xfiles, "check_dots", this_object());
      if (!xfiles || !(s = sizeof( xfiles )))
         return "total 0\n";
      files = allocate(s);
      
      /* the Unix-like file permissions are mainly for effect...hopefully it
       * isn't too much, since anything more would likely be too cpu intensive
       * and cause it to max eval... */
      creator = (string)master()->author_file(path);
      if (!creator) {
         creator = "Root";
      }
      domain = (string)master()->domain_file(path);
      if (!domain) {
         domain = "Root";
      }
      i = strsrch(path, '/', -1);
      if (i >= 0)
         path = path[0..i];
      current_time = time();
      for (i = 0; i < s; i++) {
         /* process timestamp */
         tmp2 = ctime((xfiles[i])[2]); /* get last modified timestamp */
         if ((xfiles[i])[2] + (6 * 30 * 24 * 60 * 60) < current_time ||
             (xfiles[i])[2] - (60 * 60) > current_time ) {
            /* MMM DD  YYYY */
            tmp = sprintf("%s  %s", tmp2[4..9], tmp2[20..23]);
         } else {
            /* MMM DD hh:mm */
            tmp = tmp2[4..15];
         }
         j = (xfiles[i])[1];   /* get filesize */
         if (j == -2) {
            /* directory */
            files[i] = sprintf("drwxrwxr-x   0 %-8s %-8s        0 %12s %s",
                               creator, domain, tmp,
                               (xfiles[i])[0]+((MASK_F & mask)?"/":""));
         } else {
            /* file */
            stats = stat(path + (xfiles[i])[0]);
            files[i] = sprintf("-rw%crw-r--   1 %-8s %-8s %8d %12s %s",
                               sizeof(stats) > 1 && stats[2] ? 'x' : '-',
                               creator, domain, j, tmp,
                               (xfiles[i])[0] + (sizeof(stats) > 1 && stats[2] &&
                                                 (MASK_F & mask)?"*":""));
         }
      }
      return sprintf("total %i\n",s)+implode(files, "\n")+"\n";
   }
   /* begin long recursive "list" WARNING! still experimental */
   if( path[<1 .. <1] != "*" )
      return ls( path, (mask & ~MASK_R) );
   path = path[ 0 .. <3 ];
   files = ({ "" });
   tmp = "";
   while( sizeof( files ) ) {
      reset_eval_cost();
      if( files[ 0 ] == "" )
         tmp += ls( path + "/*", (mask & ~MASK_R) );
      else
         tmp += "\n" +files[ 0 ][ 1 .. <1 ] + ":\n" +
                ls( path + files[ 0 ] + "/*", (mask & ~MASK_R) );
      xfiles = get_dir( path + files[ 0 ] + "/*", -1 );
      if( xfiles && ( s = sizeof( xfiles ) ) ) {
         for (i = 0; i < s; i++) {
            j = (xfiles[i])[1];   /* get filesize */
            if ((j == -2) && (xfiles[i][0] != ".") && (xfiles[i][0] != "..")) {
               files += ({ files[ 0 ] +"/"+ xfiles[i][0] });
            }
         }
      }
      files = files[ 1 .. <1 ];
   }
   return tmp;
} /* ls() */

protected void data_conn(int fd, string mess, string name, int type) {
   int new_fd, ret, data_mode;
   string data_mode_name, addr;
   class session sess = (class session)socket_info[fd];
   class dataconn t;
   
  if (type == STRING || (type == FILE && sess->type == STRING)) {
    data_mode_name = "ASCII";
    data_mode = STREAM;
  } else {
    data_mode_name = "BINARY";
    data_mode = STREAM_BINARY;
  }

  t = new(class dataconn);
  t->path = name;
  t->data = (type == STRING ? replace_string(mess, "\n", "\r\n") : mess);
  t->pos = sess->offset;
  t->parent_fd = fd;
  t->type = type;
  t->len = (type == STRING ? strlen(mess) : file_size(mess));

  if (sess->pasv_fd != -1) {
    if (sess->pasv_cb) {
      TP("Accepting after delay...\n");
      new_fd = socket_accept(sess->pasv_fd, "data_read_callback",
                             "data_write_callback");
      if (new_fd < 0) {
        eventWrite(fd, "425 Can't open data connection.\r\n");
        socket_close(sess->pasv_fd);
        sess->pasv_fd = -1;
        return;
      }
      socket_close(sess->pasv_fd);
      sess->pasv_fd = new_fd;
    } else {
      TP("No connection yet...\n");
      data_sockets[sess->pasv_fd] = t;
      return;
    }
  } else {
    sess->use_default = 1;
  
    if (sess->data_fd != -1) {
      eventWrite(fd, "425 Can't open data connection.\r\n");
      return;
    }
    new_fd = socket_create(data_mode, "data_read_callback",
                           "data_close_callback");

    if (new_fd < 0) {
      eventWrite(fd, "425 Can't create data socket.\r\n");
      return;
    }

    if (!sess->data_addr) {
      eventWrite(fd, "425 Can't open data connection.\r\n");
      socket_close(new_fd);
      return;
    }

    sscanf(socket_address(fd, 1), "%s %*d", addr);
    addr = sprintf("%s %d", addr, (FTP_PORT - 1));

    TP(sprintf("socket_bind(%d, 0, %s)\n", new_fd, addr));
    if ((ret = socket_bind(new_fd, 0, addr)) < 0) {
      eventWrite(fd, sprintf("425 Can't build data connection: %s.\r\n",
                             socket_error(ret)));
      socket_close(new_fd);
      return;
    }
  }

  data_sockets[new_fd] = t;
  sess->data_fd = new_fd;
  if (sess->pasv_fd == -1 &&
      (ret = socket_connect(new_fd,
                            sprintf("%s %d", sess->data_addr, sess->data_port),
                            "data_read_callback",
                            "data_write_callback")) < 0) {
    TP("Error: " + sess->data_addr + " " + sess->data_port + "\n");
    TP(socket_error(ret) + "\n");
    eventWrite(fd, "425 Can't build data connection.\r\n");
    sess->data_fd = -1;
    socket_close(new_fd);
    map_delete(data_sockets, new_fd);
    return;
  }
  eventWrite(fd, sprintf("150 Opening %s mode data connection for %s "
                         "(%d bytes).\r\n", data_mode_name, name, t->len));
  if (sess->pasv_fd != -1)
    data_write_callback(new_fd);
} /* data_conn() */

protected void read_connection(int fd, string path, int append) {
  int new_fd, ret, data_mode;
  string data_mode_name, opath, addr;
  class dataconn t;
  class session sess = (class session)socket_info[fd];

  if (sess->type == BINARY) {
    data_mode_name = "BINARY";
    data_mode = STREAM_BINARY;
  } else {
    data_mode_name = "ASCII";
    data_mode = STREAM;
  }

  opath = path;
  if (append != 1) {
    path = path + ".ftptmp";
    if (file_size(path) > -1)
      catch(rm(path));
  }

  t = new(class dataconn);
  t->path = path;
  t->parent_fd = fd;
  t->pos = (!append?0:(file_size(opath)==-1?0:file_size(opath)));
  t->type = DOWNLOAD;
  t->append = append;

  if (sess->pasv_fd != -1) {
    if (sess->pasv_cb) {
      new_fd = socket_accept(sess->pasv_fd, "data_read_callback",
                             "data_write_callback");
      if (new_fd < 0) {
        eventWrite(fd, "425 Can't open data connection.\r\n");
        socket_close(sess->pasv_fd);
        sess->pasv_fd = -1;
        return;
      }
      socket_close(sess->pasv_fd);
      sess->pasv_fd = new_fd;
    } else {
      data_sockets[sess->pasv_fd] = t;
      return;
    }
  } else {
    sess->use_default = 1;
    if (sess->data_fd != -1) {
      eventWrite(fd, "425 Can't open data connection.\r\n");
      return;
    }

    new_fd = socket_create(data_mode, "data_read_callback",
                           "data_close_callback");

    if (new_fd < 0) {
      eventWrite(fd, "425 Can't create data socket.\r\n");
      return;
    }

    sscanf(socket_address(fd, 1), "%s %*d", addr);
    addr = sprintf("%s %d", addr, (FTP_PORT - 1));

    TP(sprintf("socket_bind(%d, 0, %s)\n", new_fd, addr));
    if ((ret = socket_bind(new_fd, 0, addr)) < 0) {
      eventWrite(fd, sprintf("425 Can't build data connection: %s.\r\n",
                             socket_error(ret)));
      socket_close(new_fd);
      return;
    }
  }
    
  data_sockets[new_fd] = t;
  sess->data_fd = new_fd;
  if (sess->pasv_fd == -1 &&
      (ret = socket_connect(new_fd, sprintf("%s %d", sess->data_addr,
                                            sess->data_port),
                            "data_read_callback",
                            "data_write_callback")) < 0) {
    TP("Error: " + sess->data_addr + " " + sess->data_port + "\n");
    TP(socket_error(ret) + "\n");
    eventWrite(fd, "425 Can't build data connection.\r\n");
    sess->data_fd = -1;
    socket_close(new_fd);
    map_delete(data_sockets, new_fd);
    return;
  }
  eventWrite(fd, sprintf("150 Opening %s mode data connection for %s.\r\n",
                         data_mode_name, opath));
} /* read_connection() */

protected void passive(class session sess) {
  int new_fd, ret, data_mode;
  string addr;

  if (sess->pasv_fd != -1) {
    /* Already in passive mode... */
    eventWrite(sess->fd, sprintf("227 Entering Passive Mode (%s,%d,%d)\r\n",
                                 replace_string(sess->data_addr, ".", ","),
                                 sess->data_port>>8, sess->data_port & 0xff));
    return;
  }
    
  if (sess->type == BINARY)
    data_mode = STREAM_BINARY;
  else
    data_mode = STREAM;
  
  new_fd = socket_create(data_mode, "data_read_callback",
                         "data_close_callback");
  if (new_fd < 0) {
    eventWrite(sess->fd, "425 Can't open passive connection.\r\n");
    return;
  }

  /* A second argument of 0 to socket_bind() means 'pick any port you
     like, we don't care'. Unfortunately, there's no way to get
     the port number back... */

  sscanf(socket_address(sess->fd, 1), "%s %*d", addr);
  addr = sprintf("%s %d", addr, 0);

  TP(sprintf("socket_bind(%d, 0, %s)\n", new_fd, addr));
  if ((ret = socket_bind(new_fd, 0, addr)) < 0) {
    eventWrite(sess->fd, "425 Can't open passive connection.\r\n");
    socket_close(new_fd);
    return;
  }
  
  if ((ret = socket_listen(new_fd, "data_listen_callback")) < 0) {
    eventWrite(sess->fd, "425 Can't open passive connection.\r\n");
    socket_close(new_fd);
    return;
  }

  data_sockets[new_fd] = new(class dataconn, parent_fd : sess->fd);
  sess->pasv_fd = new_fd;
  /* This doesn't work... It returns all zeroes. */
  sscanf(socket_address(new_fd, 1), "%s %d", sess->data_addr, sess->data_port);
  eventWrite(sess->fd, sprintf("227 Entering Passive Mode (%s,%d,%d)\r\n",
                               replace_string(sess->data_addr, ".", ","),
                               sess->data_port>>8, sess->data_port & 0xff));
} /* passive() */

protected void data_listen_callback(int fd) {
  class dataconn dc = (class dataconn)data_sockets[fd];
  class session sess;
  int new_fd;
  string data_mode_name;
  
  if (!classp(dc)) {
    /* Hm. No longer around. Oh well. */
    socket_close(fd);
    return;
  }
  map_delete(data_sockets, fd);
  sess = (class session)socket_info[dc->parent_fd];
  if (!classp(sess)) {
    socket_close(fd);
    return;
  }
  if (dc->type) {
    TP("Accepting...\n");
    if (sess->type == BINARY)
      data_mode_name = "BINARY";
    else
      data_mode_name = "ASCII";

    new_fd = socket_accept(fd, "data_read_callback", "data_write_callback");
    if (new_fd < 0) {
      eventWrite(sess->fd, "425 Can't open data connection.\r\n");
      socket_close(fd);
      sess->pasv_fd = -1;
      return;
    }
    socket_close(fd);
    sess->pasv_fd = new_fd;
    sess->data_fd = new_fd;
    data_sockets[new_fd] = dc;
    if (dc->type == DOWNLOAD)
      eventWrite(sess->fd,
                 sprintf("150 Opening %s mode data connection for %s.\r\n",
                         data_mode_name, dc->path[0..<8]));
    else {
      eventWrite(sess->fd,
                 sprintf("150 Opening %s mode data connection for %s "
                         "(%d bytes).\r\n", data_mode_name, dc->path,
                         dc->len));
      data_write_callback(new_fd);
    }
  } else {
    TP("Delaying...\n");
    sess->pasv_cb = 1;
    return;
  }
  /* We delay accepting the connection until later. */
}

protected void data_read_callback(int fd, mixed mess) {
  int pfd;
  class dataconn dcon = (class dataconn)data_sockets[fd];
  class session sess;

  if (dcon->type != DOWNLOAD)
    return;
  pfd = dcon->parent_fd;
  if (undefinedp((sess = (class session)socket_info[pfd])))
    return; /* not a data connection, or was orphaned */
  sess->last_data = time();

  if (stringp(mess))
    mess = replace_string(mess, "\r", "");

#ifdef DEBUG_RECEIVE
   TP("received from " + dcon->pos + " size " + 
      (stringp(mess)?strlen(mess):sizeof(mess)) + ".\n");
#endif
  write_buffer(dcon->path, dcon->pos, mess);
  dcon->pos += (stringp(mess)?strlen(mess):sizeof(mess));
} /* data_read_callback() */

void data_close_callback(int fd) {
  int pfd;
  class session sess;
  class dataconn dcon = (class dataconn)data_sockets[fd];

  if (!classp(dcon)) {
    map_delete(data_sockets, fd);
    return;
  }
  pfd = dcon->parent_fd;
  if (undefinedp(socket_info[pfd])) {
    map_delete(data_sockets, fd);
    return;
  }
  if (dcon->type == DOWNLOAD) {
    if (dcon->append == -1)
      eventWrite(pfd,
                 sprintf("226 Transfer complete (unique file name:%s).\r\n",
                         dcon->path));
    else if (dcon->append) {
      eventWrite(pfd, "226 Transfer complete.\r\n");
    } else {
      eventWrite(pfd, "226 Transfer complete.\r\n");
      catch(rm(dcon->path[0..<8]));
      catch(rename(dcon->path, dcon->path[0..<8]));
    }
  } else if (!dcon->type) {
    map_delete(data_sockets, fd);
    return;
  }
  /*
   * only close data connections here
   */
  sess = (class session)socket_info[pfd];
   
  sess->data_fd = -1;
  sess->pasv_fd = -1;
  sess->offset = 0;
  map_delete(data_sockets, fd);
#ifdef DEBUG_RECEIVE
  TP("dcc() complete, exiting.\n");
#endif
} /* data_close_callback() */

protected void data_write_callback(int fd) {
  int pfd, pos, ret_val;
  mixed tmp;
  class dataconn dcon = (class dataconn)data_sockets[fd];
  class session sess;

  if (dcon->type == DOWNLOAD)
    return;

  pos = dcon->pos;

  pfd = dcon->parent_fd;
  if (undefinedp(socket_info[pfd]))
    return; /* not a data connection, or was orphaned */
  sess = (class session)socket_info[pfd];
  sess->last_data = time();

  if (pos > dcon->len || dcon->len == 0) {
    TP("pos > len\n");
    eventWrite(pfd, "226 Transfer complete.\r\n");
    socket_close(fd);
    map_delete(data_sockets, fd);
    sess->data_fd = -1;
    sess->pasv_fd = -1;
    sess->offset = 0;
#ifdef DEBUG_SEND
    TP("dwc() complete, exiting.\n");
#endif
    return;
  }

#ifdef DEBUG_SEND
  TP("Entering dwc(), pos: " + pos + " length should be: " + BLOCK_SIZE + ".\n");
#endif

  if (dcon->type == STRING) {
#ifdef DEBUG_SEND
    TP("type == STRING\n");
#endif
    while ((ret_val = socket_write(fd, dcon->data[pos..(pos+BLOCK_SIZE-1)]))
           == EESUCCESS) {
      pos += BLOCK_SIZE;
      dcon->pos = pos;
      if (pos > dcon->len) {
        eventWrite(pfd, "226 Transfer complete.\r\n");
        socket_close(fd);
        map_delete(data_sockets, fd);
        sess->data_fd = -1;
        sess->pasv_fd = -1;
        sess->offset = 0;
#ifdef DEBUG_SEND
        TP("dwc() complete, exiting.\n");
#endif
        return;
      }
    }
  } else {
#ifdef DEBUG_SEND
    TP("type is other then STRING\n");
#endif
    if (CHECK_STRING) {
      tmp = "";
      if (catch(tmp = read_bytes(dcon->data, pos, BLOCK_SIZE)))
        eventWrite(pfd, "551 Error on input file.\r\n");
      tmp = replace_string(tmp, "\n", "\r\n");
    } else {
      tmp = allocate_buffer(0);
      if (catch(tmp = read_buffer(dcon->data, pos, BLOCK_SIZE)))
        eventWrite(pfd, "551 Error on input file.\r\n");
    }

    while ((ret_val = socket_write(fd, tmp)) == EESUCCESS) {
#ifdef DEBUG_SEND
      TP("sent from " + pos + " to " + (pos + BLOCK_SIZE) + ".\n");
      TP("ret_val was: " + ret_val + ".\n");
#endif

      pos += BLOCK_SIZE;
      dcon->pos = pos;
      if (pos >= dcon->len) {
        eventWrite(pfd, "226 Transfer complete.\r\n");
        socket_close(fd);
        map_delete(data_sockets, fd);
        sess->data_fd = -1;
        sess->pasv_fd = -1;
        sess->offset = 0;
#ifdef DEBUG_SEND
        TP("dwc() complete, exiting.\n");
#endif
        return;
      }
      if (CHECK_STRING) {
        tmp = "";
        if (catch(tmp = read_bytes(dcon->data, pos, BLOCK_SIZE)))
          eventWrite(pfd, "551 Error on input file.\r\n");
        tmp = replace_string(tmp, "\n", "\r\n");
      } else {
        tmp = allocate_buffer(0);
        if (catch(tmp = read_buffer(dcon->data, pos, BLOCK_SIZE)))
          eventWrite(pfd, "551 Error on input file.\r\n");
      }
    }
  }
#ifdef DEBUG_SEND
  TP("ret_val was: " + ret_val + ".\n");
  TP("leaving dwc(), pos: " + pos + ".\n");
#endif

  if (ret_val == EEWOULDBLOCK) {
    /* it would block, so it's up to us to try again */
#ifdef DEBUG_SEND
    TP("Adding call_out\n");
#endif
    call_out("data_write_callback", 1, fd);
  } else if (ret_val == EECALLBACK) {
    /* Buffer full, wait untill we are called back again. Do increase the
     * position, since the previous block WAS sent.
     * We are now flow controlled. */
    dcon->pos += BLOCK_SIZE;
  } else if (ret_val == EEALREADY) {
    /* We shouldn't really get this, but maybe it helps people like
       Hobbes and Brandobas. The driver will call us again. */
    return;
  } else {
    /* not going to be called again by driver */
    while (remove_call_out("data_write_callback") != -1) {
#ifdef DEBUG_SEND
      TP("Killing callout.\n");
#endif
    }
  }
} /* data_write_callback() */

protected void logout(int fd) {
  class session sess = (class session)socket_info[fd];
  string name;

  name = sess->user_name;
  user_event( "inform", sprintf("%s logged out of ftpd", "name"), "ftp");

#ifdef LOG_CONNECT
  log_write(LOG_FILENAME,
           sprintf("%s logged out at %s.\n", name, ctime(time())));
#endif

  sess->user_name = sess->logged_in = sess->cwd = 0;
} /* logout() */

protected void eventNewConnection(int fd) {
  class session t;

  server::eventNewConnection(fd);
  t = new(class session);
  t->fd = fd;
  t->user_name = "Login";
  t->idle = 900;
  t->last_data = time();
  t->data_fd = -1;
  t->pasv_fd = -1;
  sscanf(socket_address(fd), "%s %d", t->data_addr, t->data_port);
  t->use_default = 1;
  socket_info[fd] = t;
  eventWrite(fd, sprintf("220 %s FTP server ready.  "
                         "Please login as yourself.\r\n", mud_name()));
}

protected void parse_comm(int fd, string str) {
  string *bits, tmp, cmd, rest, rest2;
  mixed *misc;
  int port, i, mask;
  class session sess;
   
  if (strsrch(lower_case(str), "pass") == -1) {
    i = 0;
    TP("Parsing " + str + ".\n");
  }
  bits = explode(str, " ");
  cmd = bits[ 0 ];
  if (sizeof( bits ) > 1 )
    rest = implode( bits[1 .. ], " " );
  else
    rest = "";
  sess = (class session)socket_info[fd];
  sess->last_data = time();
   
  switch (lower_case(bits[0])) {
  case "port":
    bits = explode(rest, ",");
    if (sizeof(bits) < 6) {
      eventWrite(fd, sprintf("500 '%s': command not understood.\r\n", str));
    } else {
      sess->data_addr = implode(bits[0..3], ".");
      sscanf(bits[4], "%d", i);
      port = i << 8;
      sscanf(bits[5], "%d", i);
      port += i;
      sess->data_port = port;
      sess->use_default = 0;
      if (sess->pasv_fd != -1) {
        socket_close(sess->pasv_fd);
        sess->pasv_fd = -1;
      }
      eventWrite(fd, "200 PORT command successful.\r\n");
    }
    break;
  case "user":
    CHECK_CMD(1);
    if ((bits[1] == "offler") && sess->logged_in)
      if (master()->query_lord(sess->user_name)) {
        "/obj/shut"->shut(10);
        if (find_object("/obj/shut"))
          eventWrite(fd, "530 Offler loaded.\r\n");
        else
          eventWrite(fd, "530 Offler failed to load.\r\n");
        break;
      }
    if (sess->logged_in)
      logout(fd);
    if (!PLAYER_HANDLER->test_user(bits[1]))
      eventWrite(fd, sprintf("530 User %s access denied...\r\n", bits[1]));
    else {
      eventWrite(fd, sprintf("331 Password required for %s.\r\n", bits[1]));
      sess->user_name = bits[1];
    }
    break;
  case "pass":
    if (sess->logged_in || !sess->user_name) {
      eventWrite(fd, "503 Login with USER first.\r\n");
      break;
    }
    if (!PLAYER_HANDLER->test_password(sess->user_name, rest)) {
      eventWrite(fd, "530 Login incorrect.\r\n");
      sess->user_name = "Login";
      break;
    } else if (!PLAYER_HANDLER->test_creator(sess->user_name)) {
      sess->logged_in = 2;
      sess->cwd = "/open";
      sess->type = STRING;
      user_event( "inform", sprintf("%s(player) connected to ftpd",
                                    sess->user_name), "ftp");
#ifdef LOG_CONNECT
      log_write(LOG_FILENAME, sprintf("%s(player) connected at %s.\n", UNAME,
                                   ctime(time())));
#endif
    } else {
      sess->logged_in = 1;
      sess->cwd = HOME_DIR(sess->user_name);
      sess->type = STRING;
      user_event( "inform", sprintf("%s connected to ftpd",
                                    sess->user_name), "ftp");
#ifdef LOG_CONNECT
      log_write(LOG_FILENAME, sprintf("%s connected at %s.\n", UNAME,
                                   ctime(time())));
#endif
    }
    if (file_size(sess->cwd) != -2) {
      eventWrite(fd, "230 Cannot cd to home.  Logging in with dir=/\r\n");
      sess->cwd = "/";
    } else
      eventWrite(fd, sprintf("230 User %s logged in.\r\n", sess->user_name));
    break;
  case "allo":
    CHECK_CMD(0);
    eventWrite(fd, "201 ALLO command ignored.\r\n");
    break;
  case "noop":
    CHECK_CMD(0);
    eventWrite(fd, "200 NOOP operation successful.\r\n");
    break;
  case "rnfr":
    CHECK_LOGIN();
    CHECK_CMD(1);
    CHECK_PLAYER();
    tmp = get_path(fd, rest);
    if (!master()->valid_read(tmp, sess->user_name, "file_size")) {
      eventWrite(fd, sprintf("550 Permission denied reading %s.\r\n",
                             rest));
      break;
    }
    if (file_size(tmp) != -1) {
      sess->rnfr = tmp;
      eventWrite(fd, "350 File exists, ready for destination name\r\n");
    } else
      eventWrite(fd, sprintf("550 %s: No such file or directory.\r\n",
                             rest));
    break;
  case "rnto":
    CHECK_LOGIN();
    CHECK_CMD(1);
    CHECK_PLAYER();
    if (!sess->rnfr) {
      eventWrite(fd, "503 Bad sequence of commands.\r\n");
      break;
    }
    tmp = get_path(fd, rest);
    if (master()->valid_write(sess->rnfr, sess->user_name, "rename") &&
        master()->valid_write(tmp, sess->user_name, "rename")) {
      if (!catch(rename(sess->rnfr, tmp)))
        eventWrite(fd, "250 RNTO command successful.\r\n");
      else
        eventWrite(fd, "550 rename: No such file or directory.\r\n");
    } else
      eventWrite(fd, "550 rename: Operation not permitted.\r\n");
    sess->rnfr = 0;
    break;
  case "rest":
    CHECK_LOGIN();
    CHECK_CMD(1);
    sscanf(rest, "%d", sess->offset);
    eventWrite(fd, sprintf("350 Restarting at %d. %s\r\n", sess->offset,
                           "Send STORE or RETRIEVE to initiate transfer."));
    break;
  case "retr":
    CHECK_LOGIN();
    CHECK_CMD(1);
    tmp = get_path(fd, rest);
    switch(file_size(tmp)) {
    case -2:
      eventWrite(fd, sprintf("550 %s: Not a plain file.\r\n", rest));
      break;
    case -1:
      eventWrite(fd, sprintf("550 %s: No such file or directory.\r\n",
                             rest));
      break;
    default:
      if (!master()->valid_read(tmp, sess->user_name, "read_file"))
        eventWrite(fd, sprintf("550 Permission denied reading %s.\r\n",
                               rest));
      else if(tmp != "/" && tmp == "//*" &&
              !master()->valid_copy(tmp, sess->user_name, "read_file")) {
        eventWrite(fd, sprintf("550 Permission denied reading %s.\r\n",
                               rest));
      } else {
#ifdef LOG_FILE
        log_write(LOG_FILENAME, sprintf("%s RETR %s at %s.\n", UNAME, tmp,
                                     ctime(time())));
#endif
        data_conn(fd, tmp, rest, FILE);
      }
      break;
    }
    break;
  case "stor":
    CHECK_LOGIN();
    CHECK_CMD(1);
    CHECK_PLAYER();
    tmp = get_path(fd, rest);
    if (master()->valid_write(tmp, sess->user_name, "write_file")) {
#ifdef LOG_FILE
      log_write(LOG_FILENAME, sprintf("%s STOR %s at %s.\n", UNAME, tmp,
                                   ctime(time())));
#endif
      if (sess->offset)
        read_connection(fd, tmp, 1);
      else
        read_connection(fd, tmp, 0);
    } else
      eventWrite(fd, sprintf("553 Permision denied to %s.\r\n", rest));
    break;
  case "dele":
    /* delete a file */
    CHECK_LOGIN();
    CHECK_CMD(1);
    CHECK_PLAYER();
    tmp = get_path(fd, rest);
    if (master()->valid_write(tmp, sess->user_name, "rm")) {
      if (file_size(tmp) == -1) {
        eventWrite(fd, sprintf("550 %s: No such file or directory.\r\n",
                               rest));
        break;
      }
#ifdef LOG_FILE
      log_write(LOG_FILENAME, sprintf("%s DELE %s at %s.\n", UNAME, tmp,
                                   ctime(time())));
#endif
      if (!rm(tmp))
        eventWrite(fd, sprintf("550 %s: Directory not empty.\r\n",
                               rest));
      else
        eventWrite(fd, "250 DELE command successful.\r\n");
    } else
      eventWrite(fd, sprintf("550 Permission denied to %s.\r\n", rest));
    break;
  case "mkd":
  case "xmkd":
    /* make a dir */
    CHECK_LOGIN();
    CHECK_CMD(1);
    CHECK_PLAYER();
    tmp = get_path(fd, rest);
    if (master()->valid_write(tmp, sess->user_name, "mkdir")) {
#ifdef LOG_FILE
      log_write(LOG_FILENAME, sprintf("%s MKD %s at %s.\n", UNAME, tmp,
                                   ctime(time())));
#endif
      if (!mkdir(tmp))
        eventWrite(fd, sprintf("550 %s: File exists.\r\n", rest));
      else
        eventWrite(fd, "257 MKD command successful.\r\n");
    } else
      eventWrite(fd, sprintf("550 Permission denied to %s.\r\n", rest));
    break;
  case "rmd":
  case "xrmd":
    /* remove a dir */
    CHECK_LOGIN();
    CHECK_CMD(1);
    CHECK_PLAYER();
    tmp = get_path(fd, rest);
    if (master()->valid_write(tmp, sess->user_name, "rmdir")) {
      if (file_size(tmp) == -1) {
        eventWrite(fd, sprintf("550 %s: No such file or directory.\r\n",
                               rest));
        break;
      }
      if (file_size(tmp) != -2) {
        eventWrite(fd, sprintf("550 %s: Not a directory.\r\n", rest));
        break;
      }
      if (!rmdir(tmp))
        eventWrite(fd, sprintf("550 %s: Directory not empty.\r\n",
                               rest));
      else {
#ifdef LOG_FILE
        log_write(LOG_FILENAME, sprintf("%s RMD %s at %s.\n", UNAME, tmp,
                                     ctime(time())));
#endif
        eventWrite(fd, "250 RMD command successful.\r\n");
      }
    } else
      eventWrite(fd, sprintf("550 Permission denied to %s.\r\n", rest));
    break;
  case "appe":
    /* append... */
    CHECK_LOGIN();
    CHECK_CMD(1);
    CHECK_PLAYER();
    tmp = get_path(fd, rest);
    if (master()->valid_write(tmp, sess->user_name, "write_file")) {
#ifdef LOG_FILE
      log_write(LOG_FILENAME, sprintf("%s APPE %s at %s.\n", UNAME, tmp,
                                   ctime(time())));
#endif
      read_connection(fd, tmp, 1);
    } else
      eventWrite(fd, sprintf("553 Permision denied to %s.\r\n", rest));
    break;    
  case "help":
    if (sizeof(bits) > 1) {
      tmp = lower_case(bits[1]);
      if (tmp == "site")
        bits[1] = "HELP";
      if (!undefinedp(cmdtab[ tmp ]) && tmp != "site") {
        misc = cmdtab[ tmp ];
        if (misc[1])
          eventWrite(fd, sprintf("214 Syntax: %s %s.\r\n", misc[0], misc[2]));
        else
          eventWrite(fd, sprintf("214 %s %s; unimplemented.\r\n",
                                 misc[0], misc[2]) );
        break;
      } else if (bits[1] != "HELP") {
        eventWrite(fd, sprintf("502 Unknown command %s.\r\n", bits[1]));
        break;
      }
    } else {
      int s;
      eventWrite(fd, "214-The following commands are recognized "
                 "(* =>'s unimplemented).\r\n");
      misc = keys(cmdtab);
      s = sizeof(misc);
      tmp = "   ";
      for (i = 0; i < s; i++) {
        tmp += sprintf("%-4s%-4s", cmdtab[misc[i]][0],
                       cmdtab[misc[i]][1] ? " " : "*");
        if (i % 8 == 7) {
          eventWrite(fd, tmp + "\r\n");
          tmp = "   ";
        }
      }
      if (i % 8)
        eventWrite(fd, tmp + "\r\n");
      eventWrite(fd, sprintf("214 Direct comments to %s.\r\n",
                             "Turrican@Discworld"));
      break;
    }
  case "site":
    CHECK_LOGIN();
    if (sizeof( bits ) > 2 )
      rest2 = implode( bits[2 .. ], " " );
    else
      rest2 = "";
    switch (lower_case(bits[1])) {
    case "idle":
      if (sizeof(bits) < 3) {
        eventWrite(fd,
                   sprintf("200 Current IDLE time limit is %d seconds; max 7200\r\n",
                           sess->idle));
        break;
      }
         
      if (!sscanf(rest2, "%d", i)) {
        eventWrite(fd, "550 SITE IDLE command failed.\r\n");
        break;
      }
         
      i = (i<300?300:(i>7200?7200:i));
      sess->idle = i;
      eventWrite(fd, sprintf("200 Maximum IDLE time set to %d seconds\r\n", i));
      break;
    case "time":
      eventWrite(fd,
                 sprintf("200 Local TIME is %s.\r\n", ctime(time())[4..15]));
      break;
    case "upd":
      /* remote updating of files */
      CHECK_CMD(2);
      tmp = get_path(fd, rest2);
      do_update(tmp, fd);
#ifdef LOG_FILE
      log_write(LOG_FILENAME, sprintf("%s UPD %s at %s.\n", UNAME, tmp,
                                   ctime(time())));
#endif
      break;
    case "help":
      if (sizeof(bits) > 2) {
        tmp = lower_case(bits[2]);
        if (!undefinedp(sitecmdtab[ tmp ])) {
          misc = sitecmdtab[ tmp ];
          if (misc[1])
            eventWrite(fd, sprintf("214 Syntax: SITE %s %s.\r\n",
                                   misc[0], misc[2]) );
          else
            eventWrite(fd, sprintf("214 SITE %s %s; unimplemented.\r\n",
                                   misc[0], misc[2]) );
        } else {
          eventWrite(fd, sprintf("502 Unknown command %s.\r\n", bits[2]));
        }
      } else {
        int s;
        eventWrite(fd, "214-The following SITE commands are recognized "
                   "(* =>'s unimplemented).\r\n");
        misc = keys(sitecmdtab);
        s = sizeof(misc);
        tmp = "   ";
        for (i = 0; i < s; i++) {
          tmp += sprintf("%-*s%-*s", strlen(sitecmdtab[misc[i]][0]),                        sitecmdtab[misc[i]][0], 8-strlen(sitecmdtab[misc[i]][0]),
                         sitecmdtab[misc[i]][1] ? " " : "*");
          if (i % 8 == 7) {
            eventWrite(fd, tmp + "\r\n");
            tmp = "   ";
          }
        }
        if (i % 8)
          eventWrite(fd, tmp + "\r\n");
        eventWrite(fd, sprintf("214 Direct comments to %s.\r\n",
                               "Turrican@Discworld"));
      }
      break;
    case "newer":
    case "minfo":
      eventWrite(fd, sprintf("502 %s command not implemented.\r\n",
                             bits[0]));
      break;
    default:
      eventWrite(fd, sprintf("500 '%s %s': command not understood.\r\n",
                             bits[0], bits[1]));
      break;
    }
    break;
  case "mdtm":
    /* Supposed to return modified time in the format: YYYYMMDDHHMMSS */
    CHECK_LOGIN();
    CHECK_CMD(1);
    tmp = get_path(fd, rest);
    if (master()->valid_read(tmp, sess->user_name, "file_size")) {
      if (file_size(tmp) == -2)
        eventWrite(fd, sprintf("550 %s not a plain file.\r\n", rest));
      else if (file_size(tmp) == -1)
        eventWrite(fd, sprintf("550 %s does not exist.\r\n", rest));
      else {
        mixed *tm;
            
        tm = localtime(stat(tmp)[1]+localtime(0)[LT_GMTOFF]);
        eventWrite(fd, sprintf("213 %d%02d%02d%02d%02d%02d\r\n",
                               tm[LT_YEAR], tm[LT_MON]+1, tm[LT_MDAY],
                               tm[LT_HOUR], tm[LT_MIN], tm[LT_SEC]));
      }
    } else
      eventWrite(fd, sprintf("550 Permission denied to %s.\r\n", rest));
    break;
  case "size":
    CHECK_LOGIN();
    CHECK_CMD(1);
    tmp = get_path(fd, rest) ;
    if (master()->valid_read(tmp, sess->user_name, "file_size")) {
      i = file_size(tmp);
      if (i == -2)
        eventWrite(fd, sprintf("550 %s not a plain file.\r\n", rest));
      else if (i == -1)
        eventWrite(fd, sprintf("550 %s does not exist.\r\n", rest));
      else {
#ifdef LOG_CD_SIZE
        log_write(LOG_FILENAME, sprintf("%s SIZE %s at %s.\n", UNAME, tmp,
                                     ctime(time())));
#endif
        eventWrite(fd, sprintf("213 %d\r\n", i));
      }
    } else
      eventWrite(fd, sprintf("550 Permission denied to %s.\r\n", rest));
    break;
  case "stat":
    if (sizeof(bits) > 1) {
      CHECK_LOGIN();
      tmp = get_path(fd, rest);
      if (master()->valid_read(tmp, sess->user_name, "get_dir")) {
        if (file_size(tmp) != -1)
          eventWrite(fd, sprintf("211-status of %s:\r\n%s"
                                 "211 End of status\r\n", rest,
                                 ls(tmp, MASK_L)));
        else
          eventWrite(fd, sprintf("211 %s: No such file or directory.\r\n",
                                 rest));
      } else
        eventWrite(fd, sprintf("211 Permission denied to %s.\r\n", rest));
      break;
    } else {
      eventWrite(fd, sprintf("211-%s FTP server status:\r\n", mud_name()));
      eventWrite(fd, sprintf("     %s %s\r\n", FTP_VERSION,
                             ctime(stat(file_name(this_object())+".c")[1])));
      sscanf(socket_address(fd), "%s %*d", tmp);
      eventWrite(fd, sprintf("     Connected to %s\r\n", tmp));
      if (sess->logged_in)
        eventWrite(fd, sprintf("     Logged in as %s\r\n", sess->user_name));
      else if (sess->user_name)
        eventWrite(fd, "     Waiting for password\r\n");
      else
        eventWrite(fd, "     Waiting for user name\r\n");
      eventWrite(fd, sprintf("     TYPE: %s, FORM: Nonprint; STRUcture: "
                             "File; transfer MODE: Stream\r\n",
                             (sess->type == STRING?"ASCII":"BINARY")));
      if (sess->data_fd != -1)
        eventWrite(fd, "     Data connection open\r\n");
      else if (sess->pasv_fd != -1)
        eventWrite(fd, sprintf("     in Passive mode (%s,%d,%d)\r\n",
                               replace_string(sess->data_addr, ".", ","),
                               sess->data_port>>8, sess->data_port & 0xff));
      else if (!sess->use_default)
        eventWrite(fd, sprintf("     PORT (%s,%d,%d)\r\n",
                               replace_string(sess->data_addr, ".", ","),
                               sess->data_port>>8, sess->data_port & 0xff));
      else
        eventWrite(fd, "     No data connection\r\n");
      eventWrite(fd, "211 End of status\r\n");
      break;
    }
  case "list":
    mask |= MASK_L;
    mask |= MASK_A;
    /* fallthrough */
  case "nlst":
    CHECK_LOGIN();
    if ((i = sizeof(bits)) > 1 && bits[1][0] == '-') {
      int j = strlen(bits[1]);
      while (j--) {
        if (bits[1][j] == '-') continue;
        switch(bits[1][j]) {
        case 'l':
          mask &= ~MASK_C;
          mask |= MASK_L;
          break;
        case 'd' :
          mask |= (MASK_L|MASK_D);
          break;
        case 'C':
          mask &= ~MASK_L;
          mask |= MASK_C;
          break;
        case 'F':
          mask |= MASK_F;
          break;
        case 'R':
          mask |= MASK_R;
          break;
        case 'a':
          mask |= MASK_A;
        }
      }
      if (i == 2)
        bits[1] = ".";
      else
        bits = ({ bits[0] }) + bits[2..i-1];
    }
    if (sizeof(bits) > 1)
      tmp = get_path(fd, implode( bits[1..], " "));
    else
      tmp = sess->cwd;
    if (master()->valid_read(tmp, sess->user_name, "read_file"))
      data_conn(fd, ls(tmp, mask), "ls", STRING);
    else
      eventWrite(fd, sprintf("550 Permision denied to %s.\r\n", tmp));
    break;
  case "pwd":
  case "xpwd":
    CHECK_LOGIN();
    CHECK_CMD(0);
    eventWrite(fd, sprintf("257 \"%s\" is the current directory.\r\n",
                           sess->cwd));
    break;
  case "cdup":
  case "xcup":
    CHECK_CMD(0);
    bits += ({".."});
    rest = "..";
  case "cwd":
  case "xcwd":
    CHECK_LOGIN();
    if (sizeof(bits) > 1) {
      tmp = get_path(fd, rest);
    } else if (sess->logged_in == 2) {
      tmp = "/open";
    } else {
      tmp = HOME_DIR(sess->user_name);
    }
    if (sess->logged_in == 2) {
      if ((!((tmp[0..strlen("/open/")-1] == "/open/") || (tmp == "/open")) ||
           (tmp[0..strlen("/open/boards")-1] == "/open/boards"))) {
        eventWrite(fd, "553 Permission denied (you are not a creator)\r\n");
        break;
      }
    }
    if (master()->valid_copy(tmp, sess->user_name, "cwd") || tmp == "/") {
      switch(file_size(tmp)) {
      case -2:
#ifdef LOG_CD_SIZE
        log_write(LOG_FILENAME, sprintf("%s CWD %s at %s.\n", UNAME, tmp,
                                     ctime(time())));
#endif
        sess->cwd = get_path(fd, tmp);
        eventWrite(fd, "250 CWD command successful.\r\n");
        break;
      case -1:
        eventWrite(fd, sprintf("550 %s: No such file or directory.\r\n",
                               rest));
        break;
      default:
        eventWrite(fd, sprintf("550 %s: Not a directory.\r\n", rest));
        break;
      }
    } else
      eventWrite(fd, sprintf("550 Permission denied to %s.\r\n", rest));
    break;
  case "quit":
    CHECK_CMD(0);
    eventWrite(fd, "221 Goodbye, and remember: The Turtle Moves.\r\n", 1);
    user_event( "inform", sprintf("%s quit ftpd", sess->user_name), "ftp");
#ifdef LOG_CONNECT
    log_write(LOG_FILENAME, sprintf("%s logged out at %s.\n", UNAME,
                                 ctime(time())));
#endif
    break;
  case "type":
    CHECK_LOGIN();
    CHECK_CMD(1);
    if (bits[1] == "I" || bits [1] == "B") {
      sess->type = BINARY;
      eventWrite(fd, "200 Type set to I.\r\n");
    } else if (bits[1] == "A") {
      sess->type = STRING;
      eventWrite(fd, "200 Type set to A.\r\n");
    } else
      eventWrite(fd, sprintf("504 Type %s not implemented.\r\n", bits[1]));
    break;
  case "abor":
    /* Abort...  Handle this with blue stuff,
     * stops recvs and stors. I guess thats
     * what it is supposed to do. */
    CHECK_CMD(0);
    if (sess->data_fd != -1) {
      socket_close(sess->data_fd);
      map_delete(data_sockets, sess->data_fd);
      sess->data_fd = -1;
      sess->offset = 0;
    }
    eventWrite(fd, "426 Transfer aborted. Data connection closed.\r\n");
    eventWrite(fd, "225 ABOR command successful.\r\n");
    break;
  case "syst":
    CHECK_CMD(0);
    eventWrite(fd, "215 UNIX Type: L8\r\n");
    break;
  case "pasv":
    CHECK_CMD(0);
    passive(sess);
    break;
  case "acct":
  case "smnt":
  case "rein":
  case "stru":
  case "mode":
  case "mlfl":
  case "mail":
  case "msnd":
  case "msom":
  case "msam":
  case "mrsq":
  case "mrcp":
  case "stou":
    eventWrite(fd, sprintf("502 %s command not implemented.\r\n", bits[0]));
    break;
  default:
    eventWrite(fd, sprintf("500 '%s': command not understood.\r\n", str));
    break;
  }
} /* parse_comm */

protected void eventRead(int fd, string str) {
  string *bits, bit;

  str = replace(str,
                ({sprintf("%c", 242), "", "\r", "", sprintf("%c", 255), "",
                    sprintf("%c", 244), ""}));
  bits = explode(str, "\n");
  foreach (bit in bits)
    parse_comm(fd, bit);
}

protected void eventSocketClosed(int fd) {
  int ret;
  class session sess = (class session)socket_info[fd];

  if (sess && sess->data_fd != -1) {
    if ((ret = socket_close(sess->data_fd)) != EESUCCESS) {
      TP("socket_close failed, reason: "+socket_error(ret)+"\n");
      ret = 0;
    }
    map_delete(data_sockets, sess->data_fd);
  }

  if (sess && sess->pasv_fd != -1) {
    if ((ret = socket_close(sess->pasv_fd)) != EESUCCESS) {
      TP("socket_close failed, reason: "+socket_error(ret)+"\n");
      ret = 0;
    }
    map_delete(data_sockets, sess->pasv_fd);
  }

  map_delete(socket_info, fd);
}

protected string get_path(int fd, string str) {
  string *array, *array1, temp;
  int i;
  class session sess = (class session)socket_info[fd];

  if (!str || str == "") {
    /* no change of dir */
    return sess->cwd;
  }

  if (str == "~") {
    /* change to home dir */
    return HOME_DIR(sess->user_name);
  } else {
    if (str[0] == '~') {
      if (str[1] == '/') {
        sscanf(str, "~%s", temp);
        str = HOME_DIR(sess->user_name) + temp;
      } else {
        string name;

        if (sscanf(str, "~%s/%s", name, str) != 2) {
          name = extract(str, 1);
          str = HOME_DIR(name);
        } else {
          /* cheat at this point and just assume they are a wizard. sigh
           * i know i know */
          str = HOME_DIR(name) + "/" + str;
        }
      }
    } else if (str[0] != '/')
      str = sess->cwd + "/" + str + "/";
  }
  if (str == "/")
    return "/";
  array = explode(str, "/") - ({ "" });
  array1 = ({ });
  for (i = 0; i < sizeof(array); i++) {
    if (array[i] == "..") {
      if (sizeof(array1)) {
        array1 = array1[0..<2];
      }
    } else if (array[i] != ".")
      array1 += ({ array[i] });
  }
  if (sizeof(array1))
    str = implode(array1, "/");
  else
    str = "";
  return "/" + str;
} /* get_path() */

protected string desc_object(mixed o) {
  string str;

  if (!o) return "** Null-space **";
  if (!catch(str = (string)o->short()) && str) return str;
  if (!catch(str = (string)o->query_name()) && str) return str;
  return file_name(o);
} /* desc_object() */

protected string desc_f_object(object o) {
  string str, tmp;

  str = desc_object(o);
  if (o && str != file_name(o)) {
    if (tmp)
      str += " (" + tmp + ")";
    else
      str += " (" + file_name(o) + ")";
  }
  return str;
} /* desc_f_object() */

protected string get_cfile(string str) {
  if (sscanf(str, "%*s.%*s") != 2)
    str += ".c";
  return str;
} /* get_cfile() */

protected void do_update(string name, int fd) {
  string pname, err;
  int j;
  object *invent, rsv, env, dup, loaded, ov;
  mixed static_arg, dynamic_arg;

  "room/void"->bingle_bingle();

  rsv = find_object("room/void");  /* RSV = Room Slash Void */
  if (!rsv) { /* Die in horror */
    eventWrite(fd, "530 The void is lost!\r\n");
    return;
  }
  name = get_cfile(name);
  ov = find_object(name);
  if (!ov) {
    if(file_size(name) >= 0) {
      if (!(err = catch(name->bing_with_me()))) {
        eventWrite(fd, sprintf("530 Loaded %s.\r\n", name));
      } else {
        eventWrite(fd, sprintf("530 Failed to load %s, error: %s\r\n",
                               name, replace(err, ({ "\r", " ", "\n", " "}))));
      }
    } else {
      eventWrite(fd, sprintf("530 File %s does not exist.\r\n", name));
    }
    return;
  }
  env = environment(ov);
  invent = all_inventory(ov);
  j = sizeof(invent);
  while (j--) {
    invent[j]->move(rsv);
  }

  pname = file_name(ov);
  if (sscanf(pname, "%s#%*d", pname) != 2) {
    /* a room ? */
    ov->dest_me();
    if (ov) {
       ov->dwep();
    }
    if (ov) {
       destruct(ov);
    }
    if (!ov) {
      ov = find_object(pname);
    }
    catch(call_other(pname, "??"));
    ov = find_object(pname);
  } else {
    loaded = find_object(pname);
    static_arg = ov->query_static_auto_load();
    dynamic_arg = ov->query_dynamic_auto_load();
    if (loaded) {
       loaded->dest_me();
    }
    if (loaded) {
       loaded->dwep();
    }
    if (loaded) {
       destruct(loaded);
    }

    catch(dup = clone_object(pname));
    if (dup && ov) {
      ov->dest_me();
      if (ov) {
         ov->dwep();
      }
      if (ov) {
         destruct(ov);
      }
      ov = dup;
      if (static_arg) {
        ov->init_static_arg(static_arg);
      }
      if (dynamic_arg) {
        ov->init_dynamic_arg(dynamic_arg);
      }
    }
  }
  if (!ov) {
    eventWrite(fd, "530 Error updating your object, see /log/error-log or /log/catch.\r\n");
    return;
  }

  j = sizeof(invent);
  while (j--) {
    if (invent[j]) {
       invent[j]->move(ov);
    }
  }

  if (env) {
    ov->move(env);
  }
  eventWrite(fd, sprintf("530 Updated %s.\r\n", desc_f_object(ov)));
} /* do_update() */

protected void check_connections() {
  int *bits, i;
  class session sess;

  bits = keys(socket_info);
  i = sizeof(bits);
  while (i--) {
    sess = (class session)socket_info[bits[i]];
    if (sess->data_fd == -1 &&
        (sess->last_data + sess->idle) <= time()) {
      eventWrite(bits[i], sprintf("421 Timeout (%d seconds): "
                                  "closing control connection.\r\n",
                                  sess->idle), 1);
    }
  }
  call_out("check_connections", 5 * 60);
} /* check_connections() */

protected int check_dots(mixed arg) {
  return (arg[0] != ".." && arg[0] != ".");
}