dsI/bin/
dsI/extra/creremote/
dsI/extra/mingw/
dsI/extra/wolfpaw/
dsI/fluffos-2.7-ds2.018/
dsI/fluffos-2.7-ds2.018/ChangeLog.old/
dsI/fluffos-2.7-ds2.018/Win32/
dsI/fluffos-2.7-ds2.018/compat/
dsI/fluffos-2.7-ds2.018/compat/simuls/
dsI/fluffos-2.7-ds2.018/testsuite/
dsI/fluffos-2.7-ds2.018/testsuite/clone/
dsI/fluffos-2.7-ds2.018/testsuite/command/
dsI/fluffos-2.7-ds2.018/testsuite/data/
dsI/fluffos-2.7-ds2.018/testsuite/etc/
dsI/fluffos-2.7-ds2.018/testsuite/include/
dsI/fluffos-2.7-ds2.018/testsuite/inherit/
dsI/fluffos-2.7-ds2.018/testsuite/inherit/master/
dsI/fluffos-2.7-ds2.018/testsuite/log/
dsI/fluffos-2.7-ds2.018/testsuite/single/
dsI/fluffos-2.7-ds2.018/testsuite/single/tests/compiler/
dsI/fluffos-2.7-ds2.018/testsuite/single/tests/efuns/
dsI/fluffos-2.7-ds2.018/testsuite/single/tests/operators/
dsI/fluffos-2.7-ds2.018/testsuite/u/
dsI/fluffos-2.7-ds2.018/tmp/
dsI/lib/cfg/
dsI/lib/cmds/common/
dsI/lib/cmds/creators/include/
dsI/lib/cmds/creators/include/SCCS/
dsI/lib/daemon/services/
dsI/lib/doc/
dsI/lib/domains/Ylsrim/
dsI/lib/domains/Ylsrim/adm/
dsI/lib/domains/Ylsrim/armour/
dsI/lib/domains/Ylsrim/broken/
dsI/lib/domains/Ylsrim/fish/
dsI/lib/domains/Ylsrim/meal/
dsI/lib/domains/Ylsrim/npc/
dsI/lib/domains/Ylsrim/virtual/
dsI/lib/domains/Ylsrim/weapon/
dsI/lib/domains/default/creator/
dsI/lib/domains/default/etc/
dsI/lib/domains/default/room/
dsI/lib/lib/comp/
dsI/lib/lib/lvs/
dsI/lib/lib/user/
dsI/lib/lib/virtual/
dsI/lib/obj/
dsI/lib/obj/include/
dsI/lib/realms/
dsI/lib/save/kills/a/
dsI/lib/save/kills/b/
dsI/lib/save/kills/f/
dsI/lib/save/kills/m/
dsI/lib/save/kills/q/
dsI/lib/save/kills/r/
dsI/lib/secure/cfg/
dsI/lib/secure/cfg/classes/
dsI/lib/secure/cfg/races/SCCS/
dsI/lib/secure/cmds/creators/include/
dsI/lib/secure/cmds/players/
dsI/lib/secure/cmds/players/include/
dsI/lib/secure/daemon/include/
dsI/lib/secure/lib/
dsI/lib/secure/lib/include/
dsI/lib/secure/lib/net/
dsI/lib/secure/lib/net/include/
dsI/lib/secure/lib/std/
dsI/lib/secure/obj/
dsI/lib/secure/obj/include/
dsI/lib/secure/save/
dsI/lib/spells/
dsI/lib/verbs/admins/include/
dsI/lib/verbs/common/
dsI/lib/verbs/common/include/
dsI/lib/verbs/creators/
dsI/lib/verbs/creators/include/
dsI/lib/verbs/players/include/SCCS/
dsI/lib/verbs/rooms/
dsI/lib/verbs/rooms/include/
dsI/lib/www/
dsI/v22.2b14/
dsI/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 \r's
**    o binary: added a fileposition flag
**  o Added the SYST command.
**  o Added line checking to the read callback.
**  o Fixed send so that larger files can be handled without difficulty.
** Jan 21, 1997
**  o Fixed the mkd command.
** 
** Tigran Sept 16, 1997
**  o Fixed nlst and list (which are now identicle) so that they accept
**    flags.  This ftp server should now be able to be used by most 
**    GUI based clients, and should also work w/ ange-ftp and efs (from 
**    emacs and Xemacs.  Note there is still work that can be done here.
**    Acceptable flags are -a -l -C and -F
**
** Naebator (sgt@israel.ru) Jan 9, 1998
**  o Fixed LIST and NLST:
**    o Added -1 flag
**    o unless specified otherwize, forces -l flag on LIST and -1 flag on NLST.
**    o directories report size 0.
**  o If home directory does not exist, it defaults to "/".
**
*/

#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 nosave 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){
    string password;
    mixed	array userinfo;

    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);
    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, "\r", "");
            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.\r\n",0);
    Session->dataPipe->eventWrite(implode(explode(output,"\n"), "\r\n")+"\r\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, "\r", "");
    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;
}