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

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

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

inherit LIB_SOCKET;

private void eventCmdPasv(string arg);

private class  ftp_session Session;
private        string      Password  = 0;
private        string      host_ip  = "127.0.0.1";
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  :),
  "pasv" : (: eventCmdPasv :), "passive" : (: eventCmdPasv :)

]);

private        int         PassivePort = -1;
private        int         passive = 0;

static void create(int fd, object owner){
    socket::create(fd, owner); 
    Session = new(class ftp_session);
    Session->cmdPipe = owner;
    Session->idleTime = 0;
    if((!HOST_IP || HOST_IP == "127.0.0.1") && sizeof(query_intermud_ip()))
	host_ip = query_intermud_ip();
}

private void eventReadFtpData(mixed text){
    //tc("ftp data: "+identify(text),"green");
    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 eventDestructDataPipe(mixed f){
    if(Session->dataPipe){
	Session->dataPipe->SetClose(f);
	Session->dataPipe->eventDestruct();
    }
}

private void eventCmdPasv(string arg)
{
    //tc("entered passive mode");

    if(arg)
    {
	eventWrite("500 command not understood.\n",0);
	return;
    }
    if(!passive) passive = 1;
    else passive = 0;
    if(Session->dataPipe) eventDestructDataPipe(0);
    Session->dataPipe = new(LIB_FTP_DATA_CONN);
    Session->dataPipe->SetSocketType(Session->binary?STREAM_BINARY:STREAM);
    Session->dataPipe->SetPassiveMode(1);
    Session->dataPipe->eventCreateSocket(0, PassivePort);
    Session->dataPipe->SetDestructOnClose(1);
    Session->dataPipe->SetRead((: eventReadFtpData :));
    Session->dataPipe->SetClose((: eventWrite("226 Transfer complete.\n",0)
      :));
    eventWrite(sprintf("227 Entering Passive Mode (%s,%d,%d)\n",
	implode(explode(host_ip, "."), ","), PassivePort >> 8, PassivePort & 0x00FF));
    //implode(explode(host_ip, "."), ","), PassivePort, 0 ));
    return;
}

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(){
    int port;
    //tc("startservice","blue");

    /* Determine Port */
    //tc("startservice");

    for(port=MIN_PASV_PORT;port <= MAX_PASV_PORT;port++)
	if(member_array(port, FTP_PORT_TRACKER_D->QueryUsedPorts()) == -1)
	    break;

    if(port > MAX_PASV_PORT)
    {
	//tc("550 out of ports.\n", "blue");
	eventWrite("550 out of ports.\n", 0);
	Destruct();
	return;
    }

    PassivePort = port;
    FTP_PORT_TRACKER_D->AddUsedPort(PassivePort);

    eventWrite(GetFtpWelcomeMsg(),0);
    call_out("idle_time_out", 60);
    //tc("end startservice","blue");

}

void Destruct(){
    FTP_PORT_TRACKER_D->RemoveUsedPort(PassivePort);
    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 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 = new(LIB_FTP_DATA_CONN);
    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;
    }
