/* Do not remove the headers from this file! see /USAGE for more info. */
/*
** First draft / quickly hacked FTPD.
**
** Rust (rust@lima.mudlib.org) July 12, 1996
**
** There are commands this doesn't support. If your client
** Seems to want some other command to work for some weird reason,
** and this FTPD won't do it, let me know and I'll add it in.
**
** Myth@Icon of Sin - Jan 19, 1997
** o Fixed STOR:
** o ascii: removed \r's
** o binary: added a fileposition flag
** o Added the SYST command.
** o Added line checking to the read callback.
** o Fixed send so that larger files can be handled without difficulty.
** Jan 21, 1997
** o Fixed the mkd command.
**
** Tigran Sept 16, 1997
** o Fixed nlst and list (which are now identicle) so that they accept
** flags. This ftp server should now be able to be used by most
** GUI based clients, and should also work w/ ange-ftp and efs (from
** emacs and Xemacs. Note there is still work that can be done here.
** Acceptable flags are -a -l -C and -F
**
** Naebator (sgt@israel.ru) Jan 9, 1998
** o Fixed LIST and NLST:
** o Added -1 flag
** o unless specified otherwize, forces -l flag on LIST and -1 flag on NLST.
** o directories report size 0.
** o If home directory does not exist, it defaults to "/".
** Tigran Jan 15, 1999
** o Added passive mode connections.
** Loriel July 2001
** o Tidied up passive mode to close old passive pipes etc.
*/
#include <socket.h>
#include <assert.h>
#include <log.h>
#include <clean_up.h>
#include <ftp_d.h>
#include <ports.h>
inherit M_ACCESS;
#define LTYPE_LIST 0
#define LTYPE_NLST 1
#define RESTRICT_PORTS
#define MIN_PORT 4000
#define MAX_PORT 4100
#define MAX_TRIES 10
private int lastport = MIN_PORT;
private nosave int idlecall;
private string iphost;
private nosave mapping passives = ([]);
private nosave mapping sessions = ([]);
private nosave mapping dataports = ([]);
private nosave mapping dispatch =
([
"user" : (: FTP_CMD_user :),
"pass" : (: FTP_CMD_pass :),
"retr" : (: FTP_CMD_retr :),
"stor" : (: FTP_CMD_stor :),
"nlst" : (: FTP_CMD_nlst :),
"list" : (: FTP_CMD_list :),
"pwd" : (: FTP_CMD_pwd :),
"cdup" : (: FTP_CMD_cdup :),
"cwd" : (: FTP_CMD_cwd :),
"quit" : (: FTP_CMD_quit :),
"type" : (: FTP_CMD_type :),
"mkd" : (: FTP_CMD_mkd :),
"port" : (: FTP_CMD_port :),
"noop" : (: FTP_CMD_noop :),
"dele" : (: FTP_CMD_dele :),
"syst" : (: FTP_CMD_syst :),
"pasv" : (: FTP_CMD_pasv :),
]);
private object sock;
private mixed outfile=([]);
#ifdef ALLOW_ANON_FTP
private nosave string array anon_logins = ({"anonymous", "ftp"});
#endif
int query_lastport() { return lastport; }
varargs private string find_flags(string incoming)
{
string array parts;
string array flags=({});
if(!incoming)
return 0;
parts=explode(incoming,
" ");
parts=filter(parts,
(: $1[0]=='-' :));
foreach(string part in parts)
flags+=explode(part,"");
clean_array(flags);
return implode(flags-=({"-"}),"");
}
varargs private string strip_flags(string incoming)
{
string array parts;
if(!incoming)
return 0;
parts=explode(incoming," ");
parts=filter(parts,
(: $1[0]!='-' :));
return implode(parts," ");
}
private void FTPLOGF(string format, string array args...)
{
FTPLOG(sprintf(format, args...));
}
private string FTP_get_welcome_msg()
{
return sprintf("220- %s FTP server ready.\n%s"
"220 Anonymous and wizard logins accepted.\n",
mud_name(),
is_file(FTP_WELCOME) ? "220- " +
replace_string(read_file(FTP_WELCOME), "\n", "\n220- ")
+"\n": "");
}
private void FTP_handle_idlers()
{
map_delete(sessions,0); /* Just make sure there's no stale stuff here */
if(!sizeof(sessions))
return;
foreach(object socket, class ftp_session info in sessions)
{
if(info->dataPipe) /* Data connections are still active. */
{
info->idleTime = 0;
continue;
}
info->idleTime += 60;
if(info->idleTime >= MAX_IDLE_TIME+60)
{
FTPLOGF("%s idled out at %s.\n", capitalize(info->user), ctime(time()));
socket->send(sprintf("421 Timeout. (%i seconds): closing control connection.\n",MAX_IDLE_TIME));
map_delete(sessions,socket);
destruct(socket);
}
}
if(sizeof(keys(sessions)))
idlecall=call_out( (:FTP_handle_idlers:), 60);
}
private void FTP_addSession(object socket)
{
class ftp_session newSession;
newSession = new(class ftp_session);
newSession->cmdPipe = socket;
#ifdef ALLOW_ANON_FTP
socket->send(FTP_get_welcome_msg());
#else
socket->send(sprintf("220- %s FTP server ready.\n220 Please login with your"
" wizard name.\n", mud_name()));
#endif
map_delete(sessions,0); /* Just make sure there's no stale stuff here */
if(!sizeof(sessions))
{
idlecall=call_out((:FTP_handle_idlers :), 60);
}
sessions[socket] = newSession;
}
private void FTP_write(object socket)
{
class ftp_session info;
info = sessions[dataports[socket]];
info->cmdPipe->send("226 Transfer complete.\n");
info->dataPipe->remove();
}
private void FTP_read(object socket, string data)
{
string cmd, arg;
class ftp_session thisSession;
function dispatchTo;
int i;
/* If there is no data, it's a new connection. */
if (!data)
{
assert(!sessions[socket]);
FTP_addSession(socket);
return;
}
thisSession = sessions[socket];
/* Check to make sure that the time idle is not greater than the maximum
* idle time. If it is remove the session and everything attached to it */
/* if(thisSession->idleTime>MAX_IDLE_TIME) { */
/* // call_out((:destruct($(socket) ):),5); */
/* return; */
/* } */
thisSession->idleTime = 0;
if (!thisSession->command)
thisSession->command="";
data=replace_string(data,"\r","");
thisSession->command+=data;
if ((i=strsrch(thisSession->command,"\n"))==-1)
return;
data=thisSession->command[0..i-1];
thisSession->command=thisSession->command[i+1..];
thisSession->command=trim_spaces(thisSession->command);
/* get the command and argument. */
if (!sscanf(data, "%s %s", cmd, arg))
{
cmd = data;
}
/* lower_case the command... */
cmd = lower_case(cmd);
/* If we're not connected, these are valid commands... */
if (!thisSession->connected)
{
switch(cmd)
{
case "user":
FTP_CMD_user(thisSession, arg);
return;
case "pass":
FTP_CMD_pass(thisSession, arg);
return;
case "quit":
FTP_CMD_quit(thisSession, arg);
return;
case "noop":
FTP_CMD_noop(thisSession, arg);
return;
default:
socket->send("503 Log in with USER first.\n");
return;
}
}
/* We are connected, so dispatch to the proper handler. */
dispatchTo = dispatch[cmd];
if (!dispatchTo)
{
/* Log command so we know what clients are trying to use */
socket->send(sprintf("502 Unknown command %s.\n", cmd));
return;
}
if(catch(evaluate(dispatchTo, thisSession, arg)))
{
FTPLOGF("%s caused a FAILURE with command '%s'.\n",
capitalize(thisSession->user), data);
socket->send("550 Unknown failure. Please report what you were doing "
"to the mud admin.\n");
}
return;
}
private void FTP_PASV_read(class ftp_session info, object socket, string text)
{
info->dataPipe=socket;
/* I'm not sure I like this...but it shouldn't hurt anything resetting
* the callback function */
info->dataPipe->set_write_callback((:FTP_write:));
/* We now know that the passive connection worked, so the listening socket
* can be destroyed */
destruct(passives[info->cmdPipe]);
// TODO ?? REMOVE passuves[info->cmdPipe] ??
dataports[info->dataPipe] = info->cmdPipe;
if(!text)
return;
switch(info->binary)
{
case 0:
text=replace_string(text,"\r","");
unguarded(info->priv, (:write_file($(info->targetFile), $(text)):));
return;
case 1:
unguarded(info->priv, (:write_buffer($(info->targetFile), $(info->filepos), $(text)):));
info->filepos+=sizeof(text);
return;
default:
ENSURE(0);
}
}
private void FTP_close(object socket)
{
map_delete(sessions, socket);
}
private void create()
{
sock = new(SOCKET, SKT_STYLE_LISTEN, PORT_FTP, (: FTP_read :),
(: FTP_close :));
resolve(__HOST__,(: iphost=$2 :) );
}
private void FTP_DATA_read(object socket, mixed text)
{
class ftp_session info;
info = sessions[dataports[socket]];
switch(info->binary)
{
case 0:
text=replace_string(text,"\r","");
unguarded(info->priv, (:write_file($(info->targetFile), $(text)):));
return;
case 1:
unguarded(info->priv, (:write_buffer($(info->targetFile), $(info->filepos), $(text)):));
info->filepos+=sizeof(text);
return;
default:
ENSURE(0);
}
}
private void FTP_DATA_close(object socket)
{
class ftp_session info;
info = sessions[dataports[socket]];
if(info)
info->cmdPipe->send("226 Transfer complete.\n");
}
private void FTP_CMD_user(class ftp_session info, string arg)
{
NEEDS_ARG();
arg = lower_case(arg);
if(info->connected)
{
info->cmdPipe->send(sprintf("530 User %s access denied.\n", arg));
return;
}
info->user = arg;
#ifdef ALLOW_ANON_FTP
if(member_array(arg, anon_logins) != -1)
{
info->cmdPipe->send("331 Guest login ok, send your complete e-mail "
"address as password.\n");
return;
}
#endif
info->cmdPipe->send(sprintf("331 Password required for %s.\n", arg));
return;
}
private void FTP_CMD_pass(class ftp_session info, string arg)
{
string password;
mixed array userinfo;
NEEDS_ARG();
if(info->connected || !info->user)
{
info->cmdPipe->send("503 Login with USER first.\n");
return;
}
#ifdef ALLOW_ANON_FTP
if(ANON_USER())
{
info->cmdPipe->send("230 guest login ok, access restrictions apply.\n");
info->connected = 1;
info->priv = 0;
info->pwd = ANON_PREFIX;
FTPLOGF("Anomymous login from %s (email = %s)\n",
info->cmdPipe->address()[0], arg);
return;
}
#endif
userinfo = unguarded(1,(:USER_D->query_variable($(info->user),
({"password"})):));
if(arrayp(userinfo) && sizeof(userinfo))
password = userinfo[0];
else
{
info->cmdPipe->send("530 Login incorrect.\n");
return;
}
if(crypt(arg, password) != password || !wizardp(info->user))
{
info->cmdPipe->send("530 Login incorrect.\n");
return;
}
info->cmdPipe->send(sprintf("230 User %s logged in.\n", info->user));
info->connected = 1;
FTPLOGF("%s connected from %s.\n", capitalize(info->user),
info->cmdPipe->address()[0]);
if(adminp(info->user))
info->priv = 1;
else
info->priv = info->user;
info->pwd = join_path(WIZ_DIR,info->user);
if (!is_directory(info->pwd))
info->pwd = "/";
return;
}
private void FTP_CMD_quit(class ftp_session info, string arg)
{
info->cmdPipe->send("221 Goodbye.\n");
if(info->dataPipe)
destruct(info->dataPipe);
if(member_array(info->cmdPipe, keys(passives))>-1)
{
destruct(passives[info->cmdPipe]);
map_delete(passives, info->cmdPipe);
}
FTPLOGF("%s QUIT at %s.\n", capitalize(info->user), ctime(time()));
map_delete(sessions, info->cmdPipe);
destruct(info->cmdPipe);
}
int next_port()
{
#ifdef RESTRICT_PORTS
lastport ++;
if(lastport>MAX_PORT+1)
lastport = MIN_PORT+1;
return lastport - 1;
#endif
return 0;
}
private void FTP_CMD_pasv(class ftp_session info, string arg)
{
string ip;
int port_dec;
int array port;
int listen_socket;
int tries;
if(arg)
{
info->cmdPipe->send("500 command not understood.\n");
return;
}
if(info->dataPipe)
destruct(info->dataPipe);
switch(info->binary)
{
case 0:
while(!listen_socket && tries < MAX_TRIES)
listen_socket = unguarded(1,(:new(SOCKET, SKT_STYLE_LISTEN,
next_port(),
(: FTP_PASV_read, $(info) :),
(: FTP_DATA_close :) ) :) );
break;
case 1:
while(!listen_socket && tries < MAX_TRIES)
listen_socket = unguarded(1,(:new(SOCKET, SKT_STYLE_LISTEN_B,
next_port(),
(: FTP_PASV_read, $(info) :),
(: FTP_DATA_close :) ) :) );
break;
default:
return;
}
if(member_array(info->cmdPipe, keys(passives))>-1)
destruct(passives[info->cmdPipe]);
//### ALSO KILL OFF OLD SESSIONS OF SAME USER ...??
if(sizeof(sessions))
{
foreach(object skt, class ftp_session session in sessions)
{
if(session && (session->user == info->user) && (skt != info->cmdPipe))
{
if(passives[skt])
destruct(passives[skt]);
map_delete(sessions, skt);
map_delete(passives, skt);
destruct(skt);
}
}
}
passives[info->cmdPipe]=listen_socket;
port_dec=listen_socket->local_port();
port=({port_dec>>8,port_dec%256});
info->cmdPipe->send(sprintf("227 Entering Passive mode. (%s,%i,%i)\n",
replace_string(iphost,".",","),
port[0],
port[1]) );
}
private void FTP_CMD_port(class ftp_session info, string arg)
{
string ip, *parts;
int port;
NEEDS_ARG();
parts = explode(arg, ",");
if(sizeof(parts)!=6)
{
info->cmdPipe->send("550 Failed command.\n");
destruct(info->dataPipe);
return;
}
ip = implode(parts[0..3],".");
/* Make a 16 bit port # out of 2 8 bit values. */
port = (to_int(parts[4]) << 8) + to_int(parts[5]);
//tell_user("loriel", sprintf("info %O", info));
if(info->dataPipe)
destruct(info->dataPipe);
switch(info->binary)
{
case 0:
info->dataPipe = unguarded(1,(:new(SOCKET, SKT_STYLE_CONNECT,
sprintf("%s %d", $(ip), $(port)),
(: FTP_DATA_read :),
(: FTP_DATA_close :) ):));
break;
case 1:
info->dataPipe = unguarded(1,(:new(SOCKET, SKT_STYLE_CONNECT_B,
sprintf("%s %d", $(ip), $(port)),
(: FTP_DATA_read :),
(: FTP_DATA_close :) ):));
break;
default:
return;
}
/* map the data port to the cmd port so we can find the cmd port when
* we're given the data port. */
dataports[info->dataPipe] = info->cmdPipe;
info->dataPipe->set_write_callback((:FTP_write:));
info->cmdPipe->send("200 PORT command successful.\n");
//tell_user("loriel", sprintf("info %O", info));
return;
}
private void do_list(class ftp_session info, string arg, int ltype)
{
string array files;
string flags;
string output;
int isfile;
flags=find_flags(arg);
arg=strip_flags(arg);
if(!arg || arg == "")
arg = ".";
/* Check to make sure we aren't really looking at a file.
* canonical_file() mucks this up a bit later on so the check
* is necessary */
if(arg[<2..]=="/.")
if(is_file(arg[0..<3]))
isfile=1;
arg = evaluate_path(arg, info->pwd);
ANON_CHECK(arg);
if(unguarded(1, (:is_directory($(arg)):)))
arg = join_path(arg, "*");
if(unguarded(info->priv, (:file_size(base_path($(arg))):)) == -1)
{
info->cmdPipe->send(sprintf("550 %s: No such file OR directory.\n", arg));
destruct(info->dataPipe);
return;
}
if(isfile)
files=({});
else
files = unguarded(info->priv, (:get_dir($(arg),-1):));
if(!files&&!isfile)
{
info->cmdPipe->send(sprintf("550 %s: Permission denied.\n",arg));
destruct(info->dataPipe);
return;
}
if(flags)
if(strsrch(flags,'a')==-1)
files = filter(files, (: member_array($1[0], ({".",".."})) == -1 :));
if(!sizeof(files)&&!isfile)
{
info->cmdPipe->send("550 No files found.\n");
destruct(info->dataPipe);
return;
}
/* in case of LIST imply -l */
/* in case of NLST imply -1 */
if (ltype == LTYPE_LIST)
{
if (flags)
{
if ( (strsrch(flags, 'l') == -1) &&
(strsrch(flags, 'C') == -1) &&
(strsrch(flags, '1') == -1) )
flags += "l";
}
else
flags = "l";
}
else
{
if (flags)
{
if ( (strsrch(flags, 'l') == -1) &&
(strsrch(flags, 'C') == -1) &&
(strsrch(flags, '1') == -1) )
flags += "1";
}
else
flags = "1";
}
/* Check the flags for output now */
/* Check the F flag */
if(strsrch(flags,'F')>-1)
{
foreach(mixed array file in files)
if(file[1]==-2)
file[0]=sprintf("%s/",file[0]);
}
/* The C flag */
if(strsrch(flags,'C')>-1)
{
int lines;
int size;
int i;
if((strsrch(flags,'l')>-1) || (strsrch(flags,'1')>-1))
{
info->cmdPipe->send("550: LIST -C flag is incompatible with -1 or -l.\n");
destruct(info->dataPipe);
return;
}
lines=((size=sizeof(files))/3)+1;
output="";
for(i=0;i<lines;i++)
{
mixed array these_files;
if((i*3+2)<size)
these_files=files[(i*3)..(i*3+2)];
else if(i*3<size)
{
these_files=files[(i*3)..];
while(sizeof(these_files)<3)
these_files+=({ ({"",0,0}) });
}
else
break;
output=sprintf("%s%-=25s %-=25s %-=25s\n",
output,
these_files[0][0],
these_files[1][0],
these_files[2][0]
);
}
}
if(strsrch(flags,'l')>-1)
{
if(strsrch(flags,'1')>-1)
{
info->cmdPipe->send("550: LIST -l and -1 flags incompatible.\n");
destruct(info->dataPipe);
return;
}
output = implode(map(files,
(:sprintf("%s %3i %=9s %=8s %=7s %s%5s %s",
$1[1]==-2?"drwxrwxr-x":"-rw-rw-r--",
1,
lower_case(replace_string(mud_name(), " ", "_"))[0..7],
lower_case(replace_string(mud_name(), " ", "_"))[0..7],
$1[1]==-2?"0":sprintf("%d",$1[1]),
ctime($1[2])[4..10],
(time()-$1[2])>31536000?ctime($1[2])[20..]:ctime($1[2])[11..15],
$1[0])
:)),"\n");
}
if(strsrch(flags,'1')>-1)
output=implode(map(files,(:sprintf("%s",$1[0]) :)),"\n");
info->cmdPipe->send("150 Opening ascii mode data connection for file list\n");
info->dataPipe->send(implode(explode(output,"\n"), "\r\n")+"\r\n");
return;
}
private void FTP_CMD_list(class ftp_session info, string arg)
{
/* Make sure the dataPort is connected, otherwise this will error */
if(!info->dataPipe)
{
call_out( (: FTP_CMD_list :),2,info,arg);
return;
}
do_list(info, arg, LTYPE_LIST);
}
private void FTP_CMD_nlst(class ftp_session info,string arg)
{
/* Make sure the dataPort is connected, otherwise this will error */
if(!info->dataPipe)
{
call_out( (: FTP_CMD_nlst :),2,info,arg);
return;
}
do_list(info,arg, LTYPE_NLST);
}
private void FTP_CMD_retr(class ftp_session info, string arg)
{
string target_file;
int i;
/* Make sure the dataPort is connected, otherwise this will error */
if(!info->dataPipe)
{
call_out( (: FTP_CMD_retr :),2,info,arg);
return;
}
NEEDS_ARG();
target_file = evaluate_path(arg, info->pwd);
ANON_CHECK(target_file);
if(unguarded(info->priv, (:is_directory($(target_file)):)))
{
info->cmdPipe->send(sprintf("550 %s: Can't retrieve (it's a directory)."
"\n", target_file));
destruct(info->dataPipe);
return;
}
if(!unguarded(info->priv, (:is_file($(target_file)):)))
{
info->cmdPipe->send(sprintf("550 %s: No such file OR directory.\n",
target_file));
destruct(info->dataPipe);
return;
}
if(!unguarded(info->priv, (:file_size($(target_file)):)))
{
info->cmdPipe->send(sprintf("550 %s: File contains nothing.\n",
target_file));
destruct(info->dataPipe);
return;
}
switch(info->binary)
{
case 0:
i=file_size(target_file);
FTPLOGF("%s GOT %s.\n", capitalize(info->user), target_file);
outfile[info->dataPipe]=({target_file,0,0,info->cmdPipe});
info->dataPipe->set_write_callback((: FTP_CMD_retr_callback :));
info->cmdPipe->send(sprintf("150 Opening ascii mode data connection for "
"%s (%d bytes).\n", target_file, i));
info->dataPipe->send(FTP_CMD_retr_callback(info->dataPipe));
break;
case 1:
i=file_size(target_file);
outfile[info->dataPipe]=({target_file,1,0,info->cmdPipe});
info->dataPipe->set_write_callback((: FTP_CMD_retr_callback :));
info->cmdPipe->send(sprintf("150 Opening binary mode data connection "
"for %s (%d bytes).\n", target_file, i));
info->dataPipe->send(FTP_CMD_retr_callback(info->dataPipe));
break;
default:
ENSURE(0);
}
}
void FTP_CMD_pwd(class ftp_session info, string arg)
{
info->cmdPipe->send(sprintf("257 \"%s\" is current directory.\n", info->pwd));
}
void FTP_CMD_noop(class ftp_session info, string arg)
{
info->cmdPipe->send("221 NOOP command successful.\n");
}
private void FTP_CMD_stor(class ftp_session info, string arg)
{
/* Make sure the dataPort is connected, otherwise this will error */
if(!info->dataPipe)
{
call_out( (: FTP_CMD_stor :),2,info,arg);
return;
}
NEEDS_ARG();
arg = evaluate_path(arg, info->pwd);
#ifndef ANON_CAN_PUT
#ifdef ALLOW_ANON_FTP
if(ANON_USER())
{
info->cmdPipe->send("550 Permission denied.\n");
destruct(info->dataPipe);
return;
}
#endif
#else
ANON_CHECK(arg);
#endif
if(!unguarded(info->priv,(:is_directory(base_path($(arg))):)))
{
info->cmdPipe->send("553 No such directory to store into.\n");
destruct(info->dataPipe);
return;
}
info->targetFile = arg;
if(unguarded(info->priv, (:is_file($(arg)):)))
{
if(!unguarded(info->priv, (:rm($(arg)):)))
{
info->cmdPipe->send(sprintf("550 %s: Permission denied.\n", arg));
destruct(info->dataPipe);
return;
}
}
else if(!unguarded(info->priv, (:write_file($(arg), ""):)))
{
info->cmdPipe->send(sprintf("550 %s: Permission denied.\n", arg));
destruct(info->dataPipe);
return;
}
FTPLOGF("%s PUT %s.\n", capitalize(info->user), arg);
/* Reset the file position flag. */
info->filepos=0;
info->cmdPipe->send(sprintf("150 Opening %s mode data connection for %s.\n",
info->binary ? "binary" : "ascii",
arg));
}
private void FTP_CMD_cdup(class ftp_session info, string arg)
{
FTP_CMD_cwd(info, "..");
}
private void FTP_CMD_cwd(class ftp_session info, string arg)
{
string newpath;
NEEDS_ARG();
newpath = evaluate_path(arg, info->pwd);
ANON_CHECK(newpath);
if(!unguarded(info->priv, (:is_directory($(newpath)):)))
{
info->cmdPipe->send(sprintf("550 %s: No such file or directory.\n",
newpath));
return;
}
info->pwd = newpath;
info->cmdPipe->send("250 CWD command successful.\n");
}
private void FTP_CMD_mkd(class ftp_session info, string arg)
{
NEEDS_ARG();
arg = evaluate_path(arg, info->pwd);
#ifndef ANON_CAN_PUT
#ifdef ALLOW_ANON_FTP
if(ANON_USER())
{
info->cmdPipe->send("550 Permission denied.\n");
destruct(info->dataPipe);
return;
}
#endif
#else
ANON_CHECK(arg);
#endif
if(!unguarded(info->priv, (:is_directory(base_path($(arg))):)))
{
info->cmdPipe->send(sprintf("550 %s: No such directory.\n",
base_path(arg)));
return;
}
if(unguarded(info->priv, (:file_size($(arg)):)) != -1)
{
info->cmdPipe->send(sprintf("550 %s: File exists.\n", arg));
return;
}
if(!unguarded(info->priv, (:mkdir($(arg)):)))
{
info->cmdPipe->send(sprintf("550 %s: Permission denied.\n", arg));
return;
}
info->cmdPipe->send("257 MKD command successful.\n");
return;
}
private void FTP_CMD_type(class ftp_session info, string arg)
{
NEEDS_ARG();
switch(arg)
{
case "a":
case "A":
info->binary = 0;
info->cmdPipe->send("200 Type set to A.\n");
return;
case "i":
case "I":
info->binary = 1;
info->cmdPipe->send("200 Type set to I.\n");
return;
default:
info->cmdPipe->send("550 Unknown file type.\n");
return;
}
}
private void FTP_CMD_dele(class ftp_session info, string arg)
{
NEEDS_ARG();
arg = evaluate_path(arg, info->pwd);
ANON_CHECK(arg);
if(!unguarded(info->priv, (: is_file($(arg)):)))
{
info->cmdPipe->send(sprintf("550 %s: No such file OR directory.\n", arg));
return;
}
if(!unguarded(info->priv, (: rm($(arg)) :)))
{
info->cmdPipe->send(sprintf("550 %s: Permission denied.\n",arg));
return;
}
FTPLOGF("%s DELETED %s.\n", capitalize(info->user), arg);
info->cmdPipe->send("250 DELE command successful.\n");
}
void remove()
{
class ftp_session item;
foreach(item in values(sessions))
{
if(objectp(item->cmdPipe))
destruct(item->cmdPipe);
if(objectp(item->dataPipe))
destruct(item->dataPipe);
}
destruct(sock);
remove_call_out(idlecall);
}
int clean_up() { return 0; }
string array list_users()
{
return map(values(sessions), (: ((class ftp_session)$1)->connected ?
((class ftp_session)$1)->user : "(login)" :));
}
private void FTP_CMD_syst(class ftp_session info, string arg)
{
info->cmdPipe->send("215 UNIX Mud Name: "+mud_name()+"\n");
}
string FTP_CMD_retr_callback(object ob)
{
int start,length;
mixed ret;
if (!ob || undefinedp(outfile[ob]))
return 0;
start=outfile[ob][2];
length=FTP_BLOCK_SIZE;
outfile[ob][2]+=length;
if (start+length>file_size(outfile[ob][0]))
length=file_size(outfile[ob][0])-start;
ret=read_buffer(outfile[ob][0],start,length);
if (start+length>=file_size(outfile[ob][0]))
{
map_delete(outfile,ob);
ob->set_write_callback((:FTP_write:));
}
return ret;
}
mapping query_passives() { return copy(passives); }
mapping query_sessions() { return copy(sessions); }
mapping query_dataports() { return copy(dataports); }
string query_iphost() { return iphost; }