ds2.9a12/bin/
ds2.9a12/extra/
ds2.9a12/extra/crat/
ds2.9a12/extra/creremote/
ds2.9a12/extra/mingw/
ds2.9a12/extra/wolfpaw/
ds2.9a12/fluffos-2.14-ds13/
ds2.9a12/fluffos-2.14-ds13/Win32/
ds2.9a12/fluffos-2.14-ds13/compat/
ds2.9a12/fluffos-2.14-ds13/compat/simuls/
ds2.9a12/fluffos-2.14-ds13/include/
ds2.9a12/fluffos-2.14-ds13/testsuite/
ds2.9a12/fluffos-2.14-ds13/testsuite/clone/
ds2.9a12/fluffos-2.14-ds13/testsuite/command/
ds2.9a12/fluffos-2.14-ds13/testsuite/data/
ds2.9a12/fluffos-2.14-ds13/testsuite/etc/
ds2.9a12/fluffos-2.14-ds13/testsuite/include/
ds2.9a12/fluffos-2.14-ds13/testsuite/inherit/
ds2.9a12/fluffos-2.14-ds13/testsuite/inherit/master/
ds2.9a12/fluffos-2.14-ds13/testsuite/log/
ds2.9a12/fluffos-2.14-ds13/testsuite/single/
ds2.9a12/fluffos-2.14-ds13/testsuite/single/tests/compiler/
ds2.9a12/fluffos-2.14-ds13/testsuite/single/tests/efuns/
ds2.9a12/fluffos-2.14-ds13/testsuite/single/tests/operators/
ds2.9a12/fluffos-2.14-ds13/testsuite/u/
ds2.9a12/lib/cmds/admins/
ds2.9a12/lib/cmds/common/
ds2.9a12/lib/cmds/creators/include/
ds2.9a12/lib/daemon/services/
ds2.9a12/lib/daemon/tmp/
ds2.9a12/lib/doc/
ds2.9a12/lib/doc/bguide/
ds2.9a12/lib/doc/efun/all/
ds2.9a12/lib/doc/efun/arrays/
ds2.9a12/lib/doc/efun/buffers/
ds2.9a12/lib/doc/efun/compile/
ds2.9a12/lib/doc/efun/floats/
ds2.9a12/lib/doc/efun/functions/
ds2.9a12/lib/doc/efun/general/
ds2.9a12/lib/doc/efun/mixed/
ds2.9a12/lib/doc/efun/numbers/
ds2.9a12/lib/doc/efun/parsing/
ds2.9a12/lib/doc/hbook/
ds2.9a12/lib/doc/help/classes/
ds2.9a12/lib/doc/help/races/
ds2.9a12/lib/doc/lfun/
ds2.9a12/lib/doc/lfun/all/
ds2.9a12/lib/doc/lfun/lib/abilities/
ds2.9a12/lib/doc/lfun/lib/armor/
ds2.9a12/lib/doc/lfun/lib/bank/
ds2.9a12/lib/doc/lfun/lib/bot/
ds2.9a12/lib/doc/lfun/lib/clay/
ds2.9a12/lib/doc/lfun/lib/clean/
ds2.9a12/lib/doc/lfun/lib/clerk/
ds2.9a12/lib/doc/lfun/lib/client/
ds2.9a12/lib/doc/lfun/lib/combat/
ds2.9a12/lib/doc/lfun/lib/connect/
ds2.9a12/lib/doc/lfun/lib/container/
ds2.9a12/lib/doc/lfun/lib/corpse/
ds2.9a12/lib/doc/lfun/lib/creator/
ds2.9a12/lib/doc/lfun/lib/daemon/
ds2.9a12/lib/doc/lfun/lib/damage/
ds2.9a12/lib/doc/lfun/lib/deterioration/
ds2.9a12/lib/doc/lfun/lib/donate/
ds2.9a12/lib/doc/lfun/lib/door/
ds2.9a12/lib/doc/lfun/lib/equip/
ds2.9a12/lib/doc/lfun/lib/file/
ds2.9a12/lib/doc/lfun/lib/fish/
ds2.9a12/lib/doc/lfun/lib/fishing/
ds2.9a12/lib/doc/lfun/lib/flashlight/
ds2.9a12/lib/doc/lfun/lib/follow/
ds2.9a12/lib/doc/lfun/lib/ftp_client/
ds2.9a12/lib/doc/lfun/lib/ftp_data_connection/
ds2.9a12/lib/doc/lfun/lib/fuel/
ds2.9a12/lib/doc/lfun/lib/furnace/
ds2.9a12/lib/doc/lfun/lib/genetics/
ds2.9a12/lib/doc/lfun/lib/holder/
ds2.9a12/lib/doc/lfun/lib/id/
ds2.9a12/lib/doc/lfun/lib/interactive/
ds2.9a12/lib/doc/lfun/lib/lamp/
ds2.9a12/lib/doc/lfun/lib/leader/
ds2.9a12/lib/doc/lfun/lib/light/
ds2.9a12/lib/doc/lfun/lib/limb/
ds2.9a12/lib/doc/lfun/lib/living/
ds2.9a12/lib/doc/lfun/lib/load/
ds2.9a12/lib/doc/lfun/lib/look/
ds2.9a12/lib/doc/lfun/lib/manipulate/
ds2.9a12/lib/doc/lfun/lib/meal/
ds2.9a12/lib/doc/lfun/lib/messages/
ds2.9a12/lib/doc/lfun/lib/player/
ds2.9a12/lib/doc/lfun/lib/poison/
ds2.9a12/lib/doc/lfun/lib/position/
ds2.9a12/lib/doc/lfun/lib/post_office/
ds2.9a12/lib/doc/lfun/lib/potion/
ds2.9a12/lib/doc/lfun/lib/room/
ds2.9a12/lib/doc/lfun/lib/server/
ds2.9a12/lib/doc/lfun/lib/spell/
ds2.9a12/lib/doc/lfun/lib/torch/
ds2.9a12/lib/doc/lfun/lib/vendor/
ds2.9a12/lib/doc/lfun/lib/virt_sky/
ds2.9a12/lib/doc/lfun/lib/weapon/
ds2.9a12/lib/doc/lfun/lib/worn_storage/
ds2.9a12/lib/doc/lpc/basic/
ds2.9a12/lib/doc/lpc/concepts/
ds2.9a12/lib/doc/lpc/constructs/
ds2.9a12/lib/doc/lpc/etc/
ds2.9a12/lib/doc/lpc/intermediate/
ds2.9a12/lib/doc/lpc/types/
ds2.9a12/lib/doc/misc/
ds2.9a12/lib/doc/old/
ds2.9a12/lib/domains/
ds2.9a12/lib/domains/Praxis/adm/
ds2.9a12/lib/domains/Praxis/attic/
ds2.9a12/lib/domains/Praxis/cemetery/mon/
ds2.9a12/lib/domains/Praxis/data/
ds2.9a12/lib/domains/Praxis/death/
ds2.9a12/lib/domains/Praxis/mountains/
ds2.9a12/lib/domains/Praxis/obj/armour/
ds2.9a12/lib/domains/Praxis/obj/magic/
ds2.9a12/lib/domains/Praxis/obj/weapon/
ds2.9a12/lib/domains/Praxis/orc_valley/
ds2.9a12/lib/domains/Ylsrim/
ds2.9a12/lib/domains/Ylsrim/adm/
ds2.9a12/lib/domains/Ylsrim/armor/
ds2.9a12/lib/domains/Ylsrim/broken/
ds2.9a12/lib/domains/Ylsrim/fish/
ds2.9a12/lib/domains/Ylsrim/meal/
ds2.9a12/lib/domains/Ylsrim/npc/
ds2.9a12/lib/domains/Ylsrim/obj/
ds2.9a12/lib/domains/Ylsrim/virtual/
ds2.9a12/lib/domains/Ylsrim/weapon/
ds2.9a12/lib/domains/campus/adm/
ds2.9a12/lib/domains/campus/etc/
ds2.9a12/lib/domains/campus/meals/
ds2.9a12/lib/domains/campus/save/
ds2.9a12/lib/domains/campus/txt/ai/charles/
ds2.9a12/lib/domains/campus/txt/ai/charles/bak2/
ds2.9a12/lib/domains/campus/txt/ai/charles/bak2/bak1/
ds2.9a12/lib/domains/campus/txt/ai/charly/
ds2.9a12/lib/domains/campus/txt/ai/charly/bak/
ds2.9a12/lib/domains/campus/txt/jenny/
ds2.9a12/lib/domains/cave/doors/
ds2.9a12/lib/domains/cave/etc/
ds2.9a12/lib/domains/cave/meals/
ds2.9a12/lib/domains/cave/weap/
ds2.9a12/lib/domains/default/creator/
ds2.9a12/lib/domains/default/doors/
ds2.9a12/lib/domains/default/etc/
ds2.9a12/lib/domains/default/vehicles/
ds2.9a12/lib/domains/default/virtual/
ds2.9a12/lib/domains/default/weap/
ds2.9a12/lib/domains/town/txt/shame/
ds2.9a12/lib/domains/town/virtual/
ds2.9a12/lib/domains/town/virtual/bottom/
ds2.9a12/lib/domains/town/virtual/space/
ds2.9a12/lib/estates/
ds2.9a12/lib/ftp/
ds2.9a12/lib/lib/comp/
ds2.9a12/lib/lib/daemons/
ds2.9a12/lib/lib/daemons/include/
ds2.9a12/lib/lib/lvs/
ds2.9a12/lib/lib/user/
ds2.9a12/lib/lib/virtual/
ds2.9a12/lib/log/
ds2.9a12/lib/log/adm/
ds2.9a12/lib/log/archive/
ds2.9a12/lib/log/chan/
ds2.9a12/lib/log/errors/
ds2.9a12/lib/log/law/adm/
ds2.9a12/lib/log/law/email/
ds2.9a12/lib/log/law/names/
ds2.9a12/lib/log/law/sites-misc/
ds2.9a12/lib/log/law/sites-register/
ds2.9a12/lib/log/law/sites-tempban/
ds2.9a12/lib/log/law/sites-watch/
ds2.9a12/lib/log/open/
ds2.9a12/lib/log/reports/
ds2.9a12/lib/log/router/
ds2.9a12/lib/log/secure/
ds2.9a12/lib/log/watch/
ds2.9a12/lib/obj/book_source/
ds2.9a12/lib/obj/include/
ds2.9a12/lib/powers/prayers/
ds2.9a12/lib/powers/spells/
ds2.9a12/lib/realms/template/adm/
ds2.9a12/lib/realms/template/area/armor/
ds2.9a12/lib/realms/template/area/npc/
ds2.9a12/lib/realms/template/area/obj/
ds2.9a12/lib/realms/template/area/room/
ds2.9a12/lib/realms/template/area/weap/
ds2.9a12/lib/realms/template/bak/
ds2.9a12/lib/realms/template/cmds/
ds2.9a12/lib/save/kills/o/
ds2.9a12/lib/secure/cfg/classes/
ds2.9a12/lib/secure/cmds/builders/
ds2.9a12/lib/secure/cmds/creators/include/
ds2.9a12/lib/secure/cmds/players/
ds2.9a12/lib/secure/cmds/players/include/
ds2.9a12/lib/secure/daemon/imc2server/
ds2.9a12/lib/secure/daemon/include/
ds2.9a12/lib/secure/lib/
ds2.9a12/lib/secure/lib/include/
ds2.9a12/lib/secure/lib/net/include/
ds2.9a12/lib/secure/lib/std/
ds2.9a12/lib/secure/log/adm/
ds2.9a12/lib/secure/log/bak/
ds2.9a12/lib/secure/log/intermud/
ds2.9a12/lib/secure/log/network/
ds2.9a12/lib/secure/modules/
ds2.9a12/lib/secure/npc/
ds2.9a12/lib/secure/obj/include/
ds2.9a12/lib/secure/room/
ds2.9a12/lib/secure/save/
ds2.9a12/lib/secure/save/backup/
ds2.9a12/lib/secure/save/boards/
ds2.9a12/lib/secure/tmp/
ds2.9a12/lib/secure/upgrades/files/
ds2.9a12/lib/secure/verbs/creators/
ds2.9a12/lib/std/board/
ds2.9a12/lib/std/lib/
ds2.9a12/lib/tmp/
ds2.9a12/lib/verbs/admins/include/
ds2.9a12/lib/verbs/builders/
ds2.9a12/lib/verbs/common/
ds2.9a12/lib/verbs/common/include/
ds2.9a12/lib/verbs/creators/
ds2.9a12/lib/verbs/creators/include/
ds2.9a12/lib/verbs/rooms/
ds2.9a12/lib/verbs/rooms/include/
ds2.9a12/lib/www/client/
ds2.9a12/lib/www/errors/
ds2.9a12/lib/www/images/
ds2.9a12/lib/www/lpmuds/downloads_files/
ds2.9a12/lib/www/lpmuds/intermud_files/
ds2.9a12/lib/www/lpmuds/links_files/
ds2.9a12/win32/
/* /secure/lib/net/ftp.c
 * a ftp server using the DeadSouls /secure/daemon/inet.c daemon
 * Constructed from lima-1.0a8 /secure/daemons/ftp_d.c
 * Dvarsk@Nightmare 990126
 *  o Redesigned for Nightmare LPMud/DeadSouls socket system.
 *    o created new RETR file retrieve function.
 *    o general conversion from daemon server to server socket
 *  o Added access checking.
 *  o Added RNFR, RNTO file rename.
 *  o Added RMD directory delete.
 *
 * Code implemented under lima.org general usage policy 
 *     as per Lima 1.0a8 USAGE document.
 */

/* 
** 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 carriage returns
**    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 "/".
**
*/

#define LIB_FTP_CLIENT	"/secure/lib/net/ftp_client"

#include <lib.h>
#include <dirs.h>
#include <network.h>
#include <runtime_config.h>
#include "include/ftp.h"

inherit LIB_SOCKET;

private class  ftp_session Session;
private        string      Password  = 0;
private        mixed       outfile   = ([]);
private static int         MaxBuffer = get_config(__MAX_BYTE_TRANSFER__);
private static int         MaxFile   = get_config(__MAX_READ_FILE_SIZE__);
private static mapping     dispatch  = ([
  "user" : (: eventCmdUser :), "pass" : (: eventCmdPass :),
  "retr" : (: eventCmdRetr :), "stor" : (: eventCmdStor :),
  "nlst" : (: eventCmdNlst :), "list" : (: eventCmdList :),
  "cdup" : (: eventCmdCdup :), "quit" : (: eventCmdQuit :),
  "type" : (: eventCmdType :), "port" : (: eventCmdPort :),
  "noop" : (: eventCmdNoop :), "dele" : (: eventCmdDele :),
  "syst" : (: eventCmdSyst :), "rnfr" : (: eventCmdRnfr :),
  "rnto" : (: eventCmdRnto :), "stou" : (: eventCmdStou :),
  "cwd"  : (: eventCmdCwd  :), "mkd"  : (: eventCmdMkd  :),
  "pwd"  : (: eventCmdPwd  :), "rmd"  : (: eventCmdRmd  :),

]);

static void create(int fd, object owner){
    socket::create(fd, owner); 
    Session = new(class ftp_session);
    Session->cmdPipe = owner;
    Session->idleTime = 0;
}

nomask static int check_privs(string file, string oper) {
    string nom, tmp;
    int x;

    if(oper == "read" && (file == "/doc" || sscanf(file,"/doc/%*s" ))){ 
        return 1;
    }
    if( !sscanf(file, REALMS_DIRS "/%s", nom) ) {
        return 0;
    }
    if(file_privs(file) == Session->user){
        return 1;
    }
    if( sscanf(nom, "%s/%*s", tmp) ) {
        nom = tmp;
    }
    nom = user_path(nom)+"adm/access";
    if( file_size(nom+".c") < 0 ) {
        return 0;
    }
    catch(x = call_other(nom, "check_access", this_object(), "foo", 
        file, oper));
    return x;
}

mixed* clean_array(mixed* r) {
    int i, n;

    r = r & r; // sort.  sort_array() can't hack it.  And no, &= doesn't work.

    n = sizeof(r) - 1;
    while (i < n) {
        if (r[i] == r[i+1]) {
            int j = i+1;

            while (j < n && r[i] == r[j + 1])
                j++;

            r[i..j-1] = ({});
            n -= j - i;
        }
        i++;
    }

    return r;
}

private string find_flags(string arg){
    string array parts;
    string array flags=({});

    parts = filter(explode(arg, " "), (: $1[0]=='-' :));
    foreach(string part in parts) flags += explode(part,"");
    clean_array(flags);
    return implode(flags -= ({"-"}) ,"");
}

private string strip_flags(string arg){
    string array parts;

    parts = filter(explode(arg," "), (: $1[0] != '-' :));
    return implode(parts, " ");
}

string FindPrevDir( string path ) {
    string array parts = explode(path, "/");

    if(sizeof(parts) == 1) return path;
    parts = parts [0..<2];
    return "/" + implode(parts, "/");   
}


private void idle_time_out(){
    if(Session->dataPipe){ /* Data connections are still active. */
        Session->idleTime = 0;
    }
    else{
        Session->idleTime += 60;
        if(Session->idleTime > MAX_IDLE_TIME + 60){
            eventWrite("426 Idle time too long, connection closed.\n",1);
            Destruct();
            return;
        }
    }
    call_out("idle_time_out", 60);
}

private string GetFtpWelcomeMsg(){
    return sprintf("220- %s FTP server ready.\n%s"
      "220 Please login with your creator name or anonymous.\n",
      mud_name(),
      file_exists(FTP_WELCOME) ? "220- " +
      replace_string(read_file(FTP_WELCOME), "\n", "\n220- ") 
      +"\n": "");

}

string GetKeyName(){ return Session->user; }

string GetUniqueFileName(string arg){
    string array parts = explode(arg, "/");
    string path, file, sufx = "";
    int i = 0;

    if(sizeof(parts) == 1){
        path = "/";
        file = parts[0];
    }
    else{
        path = "/" + implode(parts [0..<2], "/");
        file  = parts [<1];
    }
    if(sscanf(file, "%s.%s", file, sufx) ==2){
        sufx = "." + sufx;
    }
    sscanf(file, "%s~%d", file, i);
    i++;
    arg = absolute_path(path, sprintf("%s~%d%s", file, i, sufx));
    while( file_exists(arg) ){
        i++;
        arg = absolute_path(path, sprintf("%s~%d%s", file, i, sufx));
    }
    return arg;
}

void StartService(){
    eventWrite(GetFtpWelcomeMsg(),0);
    call_out("idle_time_out", 60);
}

private void eventDestructDataPipe(mixed f){
    if(Session->dataPipe){
        Session->dataPipe->SetClose(f);
        Session->dataPipe->eventDestruct();
    }
}

void Destruct(){
    remove_call_out("idle_time_out");
    eventDestructDataPipe(0);
    ::Destruct();
}

private void eventCmdUser(string arg){
    if(!arg){ 
        eventWrite("500 command not understood.\n",0); 
        return; 
    }
    arg = lower_case(arg);
    if(Session->connected){
        eventWrite(sprintf("530 User %s access denied.\n", arg),0);
        return;
    }
    Session->user = arg;
#ifdef ALLOW_ANON_FTP
    if(member_array(arg, ({"anonymous", "ftp"})) != -1){
        eventWrite("331 Guest login ok, send your complete e-mail "
          "address as password.\n",0);
        return;
    }
#endif
    eventWrite(sprintf("331 Password required for %s.\n", arg),0);
    return;
}

private void eventCmdPswd(string arg){

    if(!arg){ 
        eventWrite("500 command not understood.\n",0); 
        return; 
    }
    if(Session->connected || !Session->user){
        eventWrite("503 Login with USER first.\n",0);
        return;
    }

#ifdef ALLOW_ANON_FTP
    if(member_array(arg, ({"anonymous", "ftp"})) != -1){
        eventWrite("230 guest login ok, access restrictions apply.\n",0);
        Session->connected = 1;
        Session->priv = 0;
        Session->pwd = ANON_PREFIX;
        log_file("reports/network_connect", "Anomymous login "
          "(email = %s)\n", arg); 
        return;
    }
#endif
    restore_object(DIR_CRES "/" + Session->user[0..0] + "/" +
      Session->user, 1 );
    if(!Password || Password != crypt(arg, Password) ) {
        log_file("reports/network_connect", "Attempted login as %s\n",
          Session->user);
        eventWrite("530 Login incorrect.\n", 1);
        return;
    }
    eventWrite(sprintf("230 User %s logged in.\n", Session->user),0);
    log_file("reports/network_connect", "User %s logged in.\n",Session->user);
    Session->connected = 1;
    Session->priv = "" + Session->user;
    Session->pwd = absolute_path(REALMS_DIRS, Session->user);
    if (file_size(Session->pwd) != -2) Session->pwd = ANON_PREFIX;
    return;
}

private void eventCmdQuit(string arg){
    eventWrite("221 Goodbye.\n", 1);
}

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

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

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

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

    ret = read_buffer(outfile[ob][0], start, length);
    if (outfile[ob][2] >= outfile[ob][4]) {
        map_delete(outfile, ob);
    }
    return ret;
}

private void eventReadFtpData(mixed text){
    switch(Session->binary){
    case 0:
        text=replace_string(text, CARRIAGE_RETURN, "");
        write_file(Session->targetFile, text);
        return;
    case 1:
        write_buffer(Session->targetFile, Session->filepos, text);
        Session->filepos += sizeof(text);
        return;
    default:
        error(sprintf("Assertion failed: \"##x\" (File: %s)\n",  __FILE__));
    }
}

private void eventCmdPort(string arg){
    string ip, *parts;
    int port;

    if(!arg){ 
        eventWrite("500 command not understood.\n",0); 
        return; 
    }
    parts = explode(arg, ",");
    if(sizeof(parts) != 6){
        eventWrite("550 Failed command.\n",0);
        return;
    }
    ip = implode(parts[0..3],".");
    port = (to_int(parts[4]) << 8) + to_int(parts[5]);
    if(Session->dataPipe) eventDestructDataPipe(0);
    Session->dataPipe = new(LIB_FTP_CLIENT);
    Session->dataPipe->SetSocketType(Session->binary?STREAM_BINARY:STREAM);
    Session->dataPipe->eventCreateSocket(ip, port);
    Session->dataPipe->SetDestructOnClose(1);
    Session->dataPipe->SetRead((:eventReadFtpData:));
    Session->dataPipe->SetClose((:eventWrite("226 Transfer complete.\n",0):));
    eventWrite("200 PORT command successful.\n",0);
    return;
}

private void do_list( string arg, int ltype){
    string array 	files;
    string flags;
    string output;
    buffer data = allocate_buffer(MaxBuffer);

    if(arg){
        flags = find_flags(arg);
        arg   = strip_flags(arg);
    }

    if(!arg || arg == "") arg = ".";
    /* This hack added by Tigran because things like /secure/master.c/.
     * evaluate and cause havoc w/ ftp clients like efs for Xemacs and
     * ange-ftp for Emacs.  Besides it shouldn't happen anyways */
    if(arg[<2..]=="/." && file_exists(arg[0..<3]) ){
        eventWrite(sprintf("550 %s: No such file OR directory.\n",arg),0);
        eventDestructDataPipe(0);
        return;
    }
    arg = absolute_path(Session->pwd, arg);

#ifdef ALLOW_ANON_FTP
    if(member_array(Session->user, ({"anonymous", "ftp"})) != -1 
      && arg[0..(strlen(ANON_PREFIX)-1)] != ANON_PREFIX) { 
        eventDestructDataPipe((:eventWrite("550 Pemission denied.\n",0):); 
          return; 
      }
#endif
        if(file_size(arg) == -2) {      
            arg = (arg[<1] != '/')? arg+"/"+"*":arg+"*";
        }
        if(file_size(FindPrevDir(arg)) == -1){
            eventWrite(sprintf("550 %s: No such file OR directory.\n", arg),0);
            eventDestructDataPipe(0);
            return;
        }
        if(!(files = get_dir(arg, -1))){
            eventWrite(sprintf("550 %s: Permission denied.\n",arg),0);
            eventDestructDataPipe(0);
            return;
        }
        if(flags){
            if(strsrch(flags,'a') == -1)
                files = filter(files, (: member_array($1[0], ({".",".."})) == -1 :));
        }
        if(!sizeof(files)){
            eventWrite("550 No files found.\n",0);
            eventDestructDataPipe(0);
            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";
        }
        if(strsrch(flags,'F') > -1){
            foreach(mixed array file in files)
                if(file[1]==-2) file[0]=sprintf("%s/",file[0]);
        }
        if(strsrch(flags,'C')>-1){
            int lines;
            int size;
            int i;

            if((strsrch(flags,'l') > -1 ) || (strsrch(flags,'1') > -1)){
                eventWrite("550: LIST -C flag is incompatible with -1 or -l.\n",0);
                eventDestructDataPipe(0);
                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){
                eventWrite("550: LIST -l and -1 flags incompatible.\n",0);
                eventDestructDataPipe(0);
                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");
        eventWrite("150 Opening ASCII mode data connection for file list."+CARRIAGE_RETURN+"\n",0);
        Session->dataPipe->eventWrite(implode(explode(output,"\n"), CARRIAGE_RETURN+"\n")+CARRIAGE_RETURN+"\n");
        return;
    }

    private void eventCmdList(string arg){
        if(!check_privs(absolute_path(Session->pwd, arg), "read")){
            eventDestructDataPipe((:eventWrite("550 Pemission denied.\n",0):));
            return;
        }
        do_list(arg, LTYPE_LIST); 
    }

    private void eventCmdNlst(string arg){
        if(!check_privs(absolute_path(Session->pwd, arg), "read")){
            eventDestructDataPipe((:eventWrite("550 Pemission denied.\n",0):));
            return;
        }
        do_list(arg, LTYPE_NLST); 
    }

    private void eventCmdRetr(string arg){
        string target_file;
        int i;

        if(!arg){ 
            eventDestructDataPipe((:eventWrite("500 command not understood.\n",0):)); 
            return; 
        }
        target_file = absolute_path(Session->pwd, arg);
        if(member_array(Session->user, ({"anonymous", "ftp"})) != -1 
          && target_file[0..(strlen(ANON_PREFIX)-1)] != ANON_PREFIX) { 
            eventDestructDataPipe((:eventWrite("550 Pemission denied.\n",0):));
            return; 
        }
        i = file_size(target_file);
        switch(i){
        case -2:
            eventWrite(sprintf("550 %s: Can't retrieve (it's a directory).\n",
                target_file),0);
            eventDestructDataPipe(0);
            return;
        case -1:
            eventWrite(sprintf("550 %s: No such file OR directory.\n", 
                target_file),0);
            eventDestructDataPipe(0);
            return;
        case 0:
            eventWrite(sprintf("550 %s: File contains nothing.\n",
                target_file),0);
            eventDestructDataPipe(0);
            return;
        default:
            if(i > MaxFile){ 
                eventWrite(sprintf("550 %s: File size too large.\n",
                    target_file),0);
                eventDestructDataPipe(0);
                return;
            }
        }
        if(!check_privs(target_file, "read")){
            eventDestructDataPipe((:eventWrite("550 Pemission denied.\n",0):));
            return;
        }
        switch(Session->binary){
        case 0:	
            outfile[Session->dataPipe]=({target_file,0,0,Session->cmdPipe, i});
            eventWrite(sprintf("150 Opening ascii mode data connection for "
                "%s (%d bytes).\n", target_file, i),0);
            Session->dataPipe->SetWrite((: RetrieveCmdCallback :));

            Session->dataPipe->eventWrite(RetrieveCmdCallback(Session->dataPipe));
            break;
        case 1:
            outfile[Session->dataPipe]=({target_file,1,0,Session->cmdPipe, i});
            eventWrite(sprintf("150 Opening binary mode data connection "
                "for %s (%d bytes).\n", target_file, i),0);
            Session->dataPipe->SetWrite((: RetrieveCmdCallback :));

            Session->dataPipe->eventWrite(RetrieveCmdCallback(Session->dataPipe));
            break;
        default:
            error(sprintf("Assertion failed: \"##x\" (File: %s)\n",  __FILE__));
        }
    }

    void eventCmdPwd(string arg){
        eventWrite(sprintf("257 \"%s\" is current directory.\n", Session->pwd),0);
    }

    void eventCmdNoop(string arg){
        eventWrite("221 NOOP command successful.\n",0);
    }

    private void eventCmdStor(string arg){
        if(!arg){ 
            eventDestructDataPipe((:eventWrite("500 command not understood.\n",0):)); 
            return; 
        }
        arg = absolute_path(Session->pwd, arg);
#ifndef ANON_CAN_PUT
        if(member_array(Session->user, ({"anonymous", "ftp"})) != -1){
            eventDestructDataPipe((:eventWrite, "550 Pemission denied.\n", 0:)); 
            return;
        }
#else
        if(member_array(Session->user, ({"anonymous", "ftp"})) != -1 
          && arg[0..(strlen(ANON_PREFIX)-1)] != ANON_PREFIX) { 
            eventDestructDataPipe((:eventWrite, "550 Pemission denied.\n", 0:)); 
            return; 
        }
#endif
        if(file_size(FindPrevDir(arg)) != -2 ){
            eventDestructDataPipe((:eventWrite, 
                "553 No such directory to store into.\n", 0:));
            return;
        }
        if(!check_privs(arg, "write")){
            eventDestructDataPipe((:eventWrite("550 Pemission denied.\n",0):));
            return;
        }
        Session->targetFile = arg;
        if( file_exists(arg) ){
            if( !rm( arg ) ){
                eventWrite(sprintf("550 %s: Permission denied.\n", arg),0);
                eventDestructDataPipe(0);
                return;
            }
        }
        else if(!write_file( arg, "") ){
            eventWrite(sprintf("550 %s: Permission denied.\n", arg),0);
            eventDestructDataPipe(0);
            return;
        }
        Session->filepos = 0;
        eventWrite(sprintf("150 Opening %s mode data connection for %s.\n",
            Session->binary ? "binary" : "ascii", arg),0);    
    }

    private void eventCmdStou(string arg){
        if(!arg){ 
            eventDestructDataPipe((:eventWrite("500 command not understood.\n",0):)); 
            return; 
        }
        arg = absolute_path(Session->pwd, arg);
#ifndef ANON_CAN_PUT
        if(member_array(Session->user, ({"anonymous", "ftp"})) != -1){
            eventDestructDataPipe((:eventWrite, "550 Pemission denied.\n", 0:)); 
            return;
        }
#else
        if(member_array(Session->user, ({"anonymous", "ftp"})) != -1 
          && arg[0..(strlen(ANON_PREFIX)-1)] != ANON_PREFIX) { 
            eventDestructDataPipe((:eventWrite, "550 Pemission denied.\n", 0:)); 
            return; 
        }
#endif
        if(file_size(FindPrevDir(arg)) != -2 ){
            eventDestructDataPipe((:eventWrite, 
                "553 No such directory to store into.\n", 0:));
            return;
        }
        if(!check_privs(arg, "write")){
            eventDestructDataPipe((:eventWrite("550 Pemission denied.\n",0):));
            return;
        }
        if( file_exists(arg)){
            arg = GetUniqueFileName(arg);
        }
        Session->targetFile = arg;
        if(!write_file( arg, "") ){
            eventWrite(sprintf("550 %s: Permission denied.\n", arg),0);
            eventDestructDataPipe(0);
            return;
        }
        Session->filepos = 0;
        eventWrite(sprintf("150 Opening %s mode data connection for %s.\n",
            Session->binary ? "binary" : "ascii", arg),0);    
    }

    private void eventCmdCwd(string arg){
        string newpath;
        if(!arg){ 
            eventWrite("500 command not understood.\n",0); 
            return; 
        }
        newpath = absolute_path(Session->pwd, arg);
        if(member_array(Session->user, ({"anonymous", "ftp"})) != -1 
          && newpath[0..(strlen(ANON_PREFIX)-1)] != ANON_PREFIX) { 
            eventWrite("550 Pemission denied.\n",0); 
            return; 
        }
        if(!check_privs(newpath, "read")){
            eventWrite("550 Pemission denied.\n",0);
            return;
        }
        if( file_size(newpath) != -2 ){
            eventWrite(sprintf("550 %s: No such directory.\n", newpath),0);
            return;
        }
        Session->pwd = newpath;
        eventWrite("250 CWD command successful.\n",0);
    }

    private void eventCmdCdup(string arg){ eventCmdCwd(".."); }

    private void eventCmdMkd(string arg){
        if(!arg){ 
            eventWrite("500 command not understood.\n",0); 
            return; 
        }
        arg = absolute_path(Session->pwd, arg);
#ifndef ANON_CAN_PUT
        if(member_array(Session->user, ({"anonymous", "ftp"})) != -1){
            eventWrite("550 Permission denied.\n",0);
            return;
        }
#else
        if(member_array(Session->user, ({"anonymous", "ftp"})) != -1 
          && arg[0..(strlen(ANON_PREFIX)-1)] != ANON_PREFIX) { 
            eventWrite("550 Pemission denied.\n",0); 
            return; 
        }
#endif
        if( file_size(FindPrevDir(arg)) != -2 ){
            eventWrite(sprintf("550 %s: No such directory.\n",
                FindPrevDir(arg)),0);
            return;
        }
        if(unguarded((:file_size($(arg)):)) != -1){
            eventWrite(sprintf("550 %s: File exists.\n", arg),0);
            return;
        }
        if(!check_privs(arg, "write")){
            eventWrite("550 Pemission denied.\n",0);
            return;
        }
        if( !mkdir(arg) ){
            eventWrite(sprintf("550 %s: Permission denied.\n", arg),0);
            return;
        }
        eventWrite("257 MKD command successful.\n",0);
        return;
    }

    private void eventCmdType(string arg){
        if(!arg){ 
            eventWrite("500 command not understood.\n",0); 
            return; 
        }
        arg = lower_case(arg);
        switch(arg){
        case "a":
            Session->binary = 0;
            eventWrite("200 Type set to A.\n",0);
            return;
        case "i":
            Session->binary = 1;
            eventWrite("200 Type set to I.\n",0);
            return;
        default:
            eventWrite("550 Unknown file type.\n",0);
            return;
        }
    }

    private void eventCmdDele(string arg){
        if(!arg){ 
            eventWrite("500 command not understood.\n",0); 
            return; 
        }
        arg = absolute_path( Session->pwd, arg);
        if(member_array(Session->user, ({"anonymous", "ftp"})) != -1 
          && arg[0..(strlen(ANON_PREFIX)-1)] != ANON_PREFIX) { 
            eventWrite("550 Pemission denied.\n",0); 
            return; 
        }
        if( !file_exists(arg) ){
            eventWrite(sprintf("550 %s: No such file OR directory.\n", arg),0);
            return;
        }
        if(!check_privs(arg, "write")){
            eventWrite("550 Pemission denied.\n",0);
            return;
        }
        if( !rm(arg) ){
            eventWrite(sprintf("550 %s: Permission denied.\n",arg),0);
            return;
        }
        eventWrite("250 DELE command successful.\n",0);
    }

    private void eventCmdSyst(string arg) {
        eventWrite("215 UNIX Mud Name: "+mud_name()+"\n",0);
    }

    private void eventCmdRnfr(string arg) {
        if(!arg){ 
            eventWrite("500 command not understood.\n",0); 
            return; 
        }
        arg = absolute_path( Session->pwd, arg);
        if(member_array(Session->user, ({"anonymous", "ftp"})) != -1 
          && arg[0..(strlen(ANON_PREFIX)-1)] != ANON_PREFIX) { 
            eventWrite("550 Pemission denied.\n",0); 
            return; 
        }
        if( !file_exists(arg) ){
            eventWrite(sprintf("550 %s: No such file OR directory.\n", arg),0);
            return;
        }
        if(!check_privs(arg, "write")){
            eventWrite("550 Pemission denied.\n",0);
            return;
        }
        Session->renamefrom = arg;
        eventWrite("350 Input new name for " + arg + ".\n",0);
    }

    private void eventCmdRnto(string arg) {
        if(!arg){ 
            eventWrite("500 command not understood.\n",0); 
            return; 
        }
        arg = absolute_path( Session->pwd, arg);
        if(member_array(Session->user, ({"anonymous", "ftp"})) != -1 
          && arg[0..(strlen(ANON_PREFIX)-1)] != ANON_PREFIX) { 
            eventWrite("550 Pemission denied.\n",0); 
            return; 
        }
        if( !Session->renamefrom ){
            eventWrite(sprintf("550 You must first specify a file to rename.\n", 
                arg),0);
            return;
        }
        if( !file_exists(Session->renamefrom) ){
            eventWrite(sprintf("550 %s: No such file OR directory.\n", arg),0);
            Session->renamefrom = 0;
            return;
        }
        if(!check_privs(Session->renamefrom, "write")){
            eventWrite("550 Pemission denied.\n",0);
            Session->renamefrom = 0;
            return;
        }
        if(!check_privs(FindPrevDir(arg), "write")){
            eventWrite("550 Pemission denied.\n",0);
            Session->renamefrom = 0;
            return;
        }
        if(rename(Session->renamefrom, arg)){
            eventWrite(sprintf("553 %s: Invalid desitination name.\n", arg),0);
        }
        eventWrite(sprintf("250 RNTO %s --> %s.\n", Session->renamefrom, arg),0);
        Session->renamefrom = 0;
    }

    private void eventCmdRmd(string arg) {
        if(!arg){ 
            eventWrite("500 command not understood.\n",0); 
            return; 
        }
        arg = absolute_path( Session->pwd, arg);
        if(member_array(Session->user, ({"anonymous", "ftp"})) != -1 
          && arg[0..(strlen(ANON_PREFIX)-1)] != ANON_PREFIX) { 
            eventWrite("550 Pemission denied.\n",0); 
            return; 
        }
        if( file_size(arg) != -2 ){
            eventWrite(sprintf("550 %s: No such file OR directory.\n", arg),0);
            return;
        }
        if(!check_privs(arg, "write")){
            eventWrite("550 Pemission denied.\n",0);
            return;
        }
        if( !rmdir(arg) ){
            eventWrite(sprintf("550 %s: Directory not empty.\n",arg),0);
            return;
        }
        eventWrite("250 RMD command successful.\n",0);
    }
    void eventRead(string data){
        string cmd, arg;
        function dispatchTo;
        int i;

        if (!(Session->command)) Session->command = "";

        data = replace_string(data, CARRIAGE_RETURN, "");
        Session->command += data;
        if ((i = strsrch(Session->command, "\n")) == -1) return;
        data=Session->command[0..i-1];
        Session->command=Session->command[i+1..];
        Session->command = trim(Session->command);
        if (!sscanf(data, "%s %s", cmd, arg)) cmd = data;
        cmd = lower_case(cmd);

        if (!Session->connected){
            switch(cmd){
            case "user":
                eventCmdUser(arg);
                return;
            case "pass":
                eventCmdPswd(arg);
                return;
            case "quit":
                eventCmdQuit(arg);
                return;
            case "noop":
                eventCmdNoop(arg);
                return;
            default:
                eventWrite("503 Log in with USER first.\n",0);
                return;
            }    
        }
        Session->idleTime = 0;
        dispatchTo = dispatch[cmd];
        if (!dispatchTo){
            log_file("reports/network_error",
              sprintf("ftp - unknown command: %s\n",cmd)); 
            eventWrite(sprintf("502 Unknown command %s.\n", cmd),0);
            return;
        }
        if(catch(evaluate(dispatchTo, arg))){
            eventWrite("550 Unknown failure.  Please report what you were doing "
              "to the mud admin.\n",0);
        }
        return;
    }