/* -*- LPC -*- */
/*
* $Locker: ceres $
* $Id: ftpd.c,v 1.12 2003/03/21 02:28:03 ceres Exp ceres $
*
*/
/*
* FTP daemon, complete rewrite using classes.
* By Turrican@Discworld, 21-4-96.
*/
#include <network.h>
inherit SERVER;
#include <ftp.h>
#include <localtime.h>
#include <player_handler.h>
#define DELAY_LOG_FLUSH 5
private nosave mapping socket_info, data_sockets;
private nosave mapping _log_file_info = ([ ]);
private nosave int _log_file_flush_id;
protected void create() {
server::create();
seteuid("Root");
socket_info = ([]);
data_sockets = ([]);
SetSocketType(STREAM);
write(mud_name() + "\n");
if(mud_name() != "Discworld")
call_out("setup_ftp", 2);
else
destruct(this_object());
} /* create() */
/**
* This method flushes out all the buffered stuff for the log files.
*/
private void flush_log_files() {
string fname;
string data;
_log_file_flush_id = 0;
foreach (fname, data in _log_file_info) {
map_delete(_log_file_info, fname);
unguarded((: write_file, fname, data :));
}
_log_file_info = ([ ]);
} /* flush_log_files() */
void log_write(string name, string fmt, mixed *args ...) {
if (!_log_file_flush_id) {
_log_file_flush_id = call_out((: flush_log_files :), DELAY_LOG_FLUSH);
}
if (!_log_file_info[name]) {
_log_file_info[name] = "";
}
if (sizeof(args)) {
_log_file_info[name] += sprintf(fmt, args ...);
} else {
_log_file_info[name] += fmt;
}
}
protected void setup_ftp() {
int x;
if ((x = eventCreateSocket(FTP_PORT)) < 0) {
if (this_object()) destruct(this_object());
return;
}
call_out("check_connections", 5*60);
} /* setup_ftp() */
/* returns an array of users connected to ftpd */
string *query_connections() {
class session *vals, val;
string *list;
list = ({ });
vals = (class session *)values(socket_info);
foreach (val in vals) {
if (val->user_name) {
if (val->logged_in) {
list += ({ capitalize(val->user_name) });
} else {
list += ({ "login" });
}
}
}
return list;
} /* query_connections() */
protected string ls(string path, int mask) {
string *files, tmp, tmp2, creator, domain;
int i, j, s, current_time;
mixed *xfiles, *stats;
#ifdef DEBUG
TP(sprintf("ls(%s,%d)\n",path,mask));
#endif
/* If the path is a directory, get contents uynless MASK_D is set */
if (!(mask & MASK_D) && file_size(path) == -2) {
if (path[ <1 ] == '/')
path += "*";
else
path += "/*";
}
/* begin narrow columnar "nlst" */
if (!(MASK_L & mask)) {
files = get_dir(path);
/* can only happen if permissions are messed up at account level */
if (!files)
return "";
if (!(MASK_A & mask))
files -= ({ ".", ".." });
if (!(i = sizeof( files )))
return "";
/* no wild cards...must have been the exact pathname to a file */
if (strsrch(path, '*') == -1 && strsrch(path, '?') == -1) {
return files[0] + "\n";
}
/* remove globber at end of path, leave a trailing slash */
j = strsrch(path, '/', -1);
path = path[0..j];
while (i--) {
/* scan next level down for files */
tmp = sprintf("%s%s/", path, files[i]);
if (MASK_F & mask) {
if (strsrch(tmp, "/./") != -1 || strsrch(tmp, "/../") != -1) {
files[i] += "/";
continue;
}
if (file_size(tmp) == -2)
files[i] += "/";
else if (stat(tmp[0..<2])[2])
files[i] += "*";
}
}
if (MASK_C & mask)
return sprintf("%-#70s\n", implode(files, "\n"));
else
return implode(files, "\n") + "\n";
}
/* don't recurse */
if (!(MASK_R & mask)) {
/* begin long "list" */
xfiles = get_dir(path, -1);
if (!(mask & MASK_A))
xfiles = filter_array(xfiles, "check_dots", this_object());
if (!xfiles || !(s = sizeof( xfiles )))
return "total 0\n";
files = allocate(s);
/* the Unix-like file permissions are mainly for effect...hopefully it
* isn't too much, since anything more would likely be too cpu intensive
* and cause it to max eval... */
creator = (string)master()->author_file(path);
if (!creator) {
creator = "Root";
}
domain = (string)master()->domain_file(path);
if (!domain) {
domain = "Root";
}
i = strsrch(path, '/', -1);
if (i >= 0)
path = path[0..i];
current_time = time();
for (i = 0; i < s; i++) {
/* process timestamp */
tmp2 = ctime((xfiles[i])[2]); /* get last modified timestamp */
if ((xfiles[i])[2] + (6 * 30 * 24 * 60 * 60) < current_time ||
(xfiles[i])[2] - (60 * 60) > current_time ) {
/* MMM DD YYYY */
tmp = sprintf("%s %s", tmp2[4..9], tmp2[20..23]);
} else {
/* MMM DD hh:mm */
tmp = tmp2[4..15];
}
j = (xfiles[i])[1]; /* get filesize */
if (j == -2) {
/* directory */
files[i] = sprintf("drwxrwxr-x 0 %-8s %-8s 0 %12s %s",
creator, domain, tmp,
(xfiles[i])[0]+((MASK_F & mask)?"/":""));
} else {
/* file */
stats = stat(path + (xfiles[i])[0]);
files[i] = sprintf("-rw%crw-r-- 1 %-8s %-8s %8d %12s %s",
sizeof(stats) > 1 && stats[2] ? 'x' : '-',
creator, domain, j, tmp,
(xfiles[i])[0] + (sizeof(stats) > 1 && stats[2] &&
(MASK_F & mask)?"*":""));
}
}
return sprintf("total %i\n",s)+implode(files, "\n")+"\n";
}
/* begin long recursive "list" WARNING! still experimental */
if( path[<1 .. <1] != "*" )
return ls( path, (mask & ~MASK_R) );
path = path[ 0 .. <3 ];
files = ({ "" });
tmp = "";
while( sizeof( files ) ) {
reset_eval_cost();
if( files[ 0 ] == "" )
tmp += ls( path + "/*", (mask & ~MASK_R) );
else
tmp += "\n" +files[ 0 ][ 1 .. <1 ] + ":\n" +
ls( path + files[ 0 ] + "/*", (mask & ~MASK_R) );
xfiles = get_dir( path + files[ 0 ] + "/*", -1 );
if( xfiles && ( s = sizeof( xfiles ) ) ) {
for (i = 0; i < s; i++) {
j = (xfiles[i])[1]; /* get filesize */
if ((j == -2) && (xfiles[i][0] != ".") && (xfiles[i][0] != "..")) {
files += ({ files[ 0 ] +"/"+ xfiles[i][0] });
}
}
}
files = files[ 1 .. <1 ];
}
return tmp;
} /* ls() */
protected void data_conn(int fd, string mess, string name, int type) {
int new_fd, ret, data_mode;
string data_mode_name, addr;
class session sess = (class session)socket_info[fd];
class dataconn t;
if (type == STRING || (type == FILE && sess->type == STRING)) {
data_mode_name = "ASCII";
data_mode = STREAM;
} else {
data_mode_name = "BINARY";
data_mode = STREAM_BINARY;
}
t = new(class dataconn);
t->path = name;
t->data = (type == STRING ? replace_string(mess, "\n", "\r\n") : mess);
t->pos = sess->offset;
t->parent_fd = fd;
t->type = type;
t->len = (type == STRING ? strlen(mess) : file_size(mess));
if (sess->pasv_fd != -1) {
if (sess->pasv_cb) {
TP("Accepting after delay...\n");
new_fd = socket_accept(sess->pasv_fd, "data_read_callback",
"data_write_callback");
if (new_fd < 0) {
eventWrite(fd, "425 Can't open data connection.\r\n");
socket_close(sess->pasv_fd);
sess->pasv_fd = -1;
return;
}
socket_close(sess->pasv_fd);
sess->pasv_fd = new_fd;
} else {
TP("No connection yet...\n");
data_sockets[sess->pasv_fd] = t;
return;
}
} else {
sess->use_default = 1;
if (sess->data_fd != -1) {
eventWrite(fd, "425 Can't open data connection.\r\n");
return;
}
new_fd = socket_create(data_mode, "data_read_callback",
"data_close_callback");
if (new_fd < 0) {
eventWrite(fd, "425 Can't create data socket.\r\n");
return;
}
if (!sess->data_addr) {
eventWrite(fd, "425 Can't open data connection.\r\n");
socket_close(new_fd);
return;
}
sscanf(socket_address(fd, 1), "%s %*d", addr);
addr = sprintf("%s %d", addr, (FTP_PORT - 1));
TP(sprintf("socket_bind(%d, 0, %s)\n", new_fd, addr));
if ((ret = socket_bind(new_fd, 0, addr)) < 0) {
eventWrite(fd, sprintf("425 Can't build data connection: %s.\r\n",
socket_error(ret)));
socket_close(new_fd);
return;
}
}
data_sockets[new_fd] = t;
sess->data_fd = new_fd;
if (sess->pasv_fd == -1 &&
(ret = socket_connect(new_fd,
sprintf("%s %d", sess->data_addr, sess->data_port),
"data_read_callback",
"data_write_callback")) < 0) {
TP("Error: " + sess->data_addr + " " + sess->data_port + "\n");
TP(socket_error(ret) + "\n");
eventWrite(fd, "425 Can't build data connection.\r\n");
sess->data_fd = -1;
socket_close(new_fd);
map_delete(data_sockets, new_fd);
return;
}
eventWrite(fd, sprintf("150 Opening %s mode data connection for %s "
"(%d bytes).\r\n", data_mode_name, name, t->len));
if (sess->pasv_fd != -1)
data_write_callback(new_fd);
} /* data_conn() */
protected void read_connection(int fd, string path, int append) {
int new_fd, ret, data_mode;
string data_mode_name, opath, addr;
class dataconn t;
class session sess = (class session)socket_info[fd];
if (sess->type == BINARY) {
data_mode_name = "BINARY";
data_mode = STREAM_BINARY;
} else {
data_mode_name = "ASCII";
data_mode = STREAM;
}
opath = path;
if (append != 1) {
path = path + ".ftptmp";
if (file_size(path) > -1)
catch(rm(path));
}
t = new(class dataconn);
t->path = path;
t->parent_fd = fd;
t->pos = (!append?0:(file_size(opath)==-1?0:file_size(opath)));
t->type = DOWNLOAD;
t->append = append;
if (sess->pasv_fd != -1) {
if (sess->pasv_cb) {
new_fd = socket_accept(sess->pasv_fd, "data_read_callback",
"data_write_callback");
if (new_fd < 0) {
eventWrite(fd, "425 Can't open data connection.\r\n");
socket_close(sess->pasv_fd);
sess->pasv_fd = -1;
return;
}
socket_close(sess->pasv_fd);
sess->pasv_fd = new_fd;
} else {
data_sockets[sess->pasv_fd] = t;
return;
}
} else {
sess->use_default = 1;
if (sess->data_fd != -1) {
eventWrite(fd, "425 Can't open data connection.\r\n");
return;
}
new_fd = socket_create(data_mode, "data_read_callback",
"data_close_callback");
if (new_fd < 0) {
eventWrite(fd, "425 Can't create data socket.\r\n");
return;
}
sscanf(socket_address(fd, 1), "%s %*d", addr);
addr = sprintf("%s %d", addr, (FTP_PORT - 1));
TP(sprintf("socket_bind(%d, 0, %s)\n", new_fd, addr));
if ((ret = socket_bind(new_fd, 0, addr)) < 0) {
eventWrite(fd, sprintf("425 Can't build data connection: %s.\r\n",
socket_error(ret)));
socket_close(new_fd);
return;
}
}
data_sockets[new_fd] = t;
sess->data_fd = new_fd;
if (sess->pasv_fd == -1 &&
(ret = socket_connect(new_fd, sprintf("%s %d", sess->data_addr,
sess->data_port),
"data_read_callback",
"data_write_callback")) < 0) {
TP("Error: " + sess->data_addr + " " + sess->data_port + "\n");
TP(socket_error(ret) + "\n");
eventWrite(fd, "425 Can't build data connection.\r\n");
sess->data_fd = -1;
socket_close(new_fd);
map_delete(data_sockets, new_fd);
return;
}
eventWrite(fd, sprintf("150 Opening %s mode data connection for %s.\r\n",
data_mode_name, opath));
} /* read_connection() */
protected void passive(class session sess) {
int new_fd, ret, data_mode;
string addr;
if (sess->pasv_fd != -1) {
/* Already in passive mode... */
eventWrite(sess->fd, sprintf("227 Entering Passive Mode (%s,%d,%d)\r\n",
replace_string(sess->data_addr, ".", ","),
sess->data_port>>8, sess->data_port & 0xff));
return;
}
if (sess->type == BINARY)
data_mode = STREAM_BINARY;
else
data_mode = STREAM;
new_fd = socket_create(data_mode, "data_read_callback",
"data_close_callback");
if (new_fd < 0) {
eventWrite(sess->fd, "425 Can't open passive connection.\r\n");
return;
}
/* A second argument of 0 to socket_bind() means 'pick any port you
like, we don't care'. Unfortunately, there's no way to get
the port number back... */
sscanf(socket_address(sess->fd, 1), "%s %*d", addr);
addr = sprintf("%s %d", addr, 0);
TP(sprintf("socket_bind(%d, 0, %s)\n", new_fd, addr));
if ((ret = socket_bind(new_fd, 0, addr)) < 0) {
eventWrite(sess->fd, "425 Can't open passive connection.\r\n");
socket_close(new_fd);
return;
}
if ((ret = socket_listen(new_fd, "data_listen_callback")) < 0) {
eventWrite(sess->fd, "425 Can't open passive connection.\r\n");
socket_close(new_fd);
return;
}
data_sockets[new_fd] = new(class dataconn, parent_fd : sess->fd);
sess->pasv_fd = new_fd;
/* This doesn't work... It returns all zeroes. */
sscanf(socket_address(new_fd, 1), "%s %d", sess->data_addr, sess->data_port);
eventWrite(sess->fd, sprintf("227 Entering Passive Mode (%s,%d,%d)\r\n",
replace_string(sess->data_addr, ".", ","),
sess->data_port>>8, sess->data_port & 0xff));
} /* passive() */
protected void data_listen_callback(int fd) {
class dataconn dc = (class dataconn)data_sockets[fd];
class session sess;
int new_fd;
string data_mode_name;
if (!classp(dc)) {
/* Hm. No longer around. Oh well. */
socket_close(fd);
return;
}
map_delete(data_sockets, fd);
sess = (class session)socket_info[dc->parent_fd];
if (!classp(sess)) {
socket_close(fd);
return;
}
if (dc->type) {
TP("Accepting...\n");
if (sess->type == BINARY)
data_mode_name = "BINARY";
else
data_mode_name = "ASCII";
new_fd = socket_accept(fd, "data_read_callback", "data_write_callback");
if (new_fd < 0) {
eventWrite(sess->fd, "425 Can't open data connection.\r\n");
socket_close(fd);
sess->pasv_fd = -1;
return;
}
socket_close(fd);
sess->pasv_fd = new_fd;
sess->data_fd = new_fd;
data_sockets[new_fd] = dc;
if (dc->type == DOWNLOAD)
eventWrite(sess->fd,
sprintf("150 Opening %s mode data connection for %s.\r\n",
data_mode_name, dc->path[0..<8]));
else {
eventWrite(sess->fd,
sprintf("150 Opening %s mode data connection for %s "
"(%d bytes).\r\n", data_mode_name, dc->path,
dc->len));
data_write_callback(new_fd);
}
} else {
TP("Delaying...\n");
sess->pasv_cb = 1;
return;
}
/* We delay accepting the connection until later. */
}
protected void data_read_callback(int fd, mixed mess) {
int pfd;
class dataconn dcon = (class dataconn)data_sockets[fd];
class session sess;
if (dcon->type != DOWNLOAD)
return;
pfd = dcon->parent_fd;
if (undefinedp((sess = (class session)socket_info[pfd])))
return; /* not a data connection, or was orphaned */
sess->last_data = time();
if (stringp(mess))
mess = replace_string(mess, "\r", "");
#ifdef DEBUG_RECEIVE
TP("received from " + dcon->pos + " size " +
(stringp(mess)?strlen(mess):sizeof(mess)) + ".\n");
#endif
write_buffer(dcon->path, dcon->pos, mess);
dcon->pos += (stringp(mess)?strlen(mess):sizeof(mess));
} /* data_read_callback() */
void data_close_callback(int fd) {
int pfd;
class session sess;
class dataconn dcon = (class dataconn)data_sockets[fd];
if (!classp(dcon)) {
map_delete(data_sockets, fd);
return;
}
pfd = dcon->parent_fd;
if (undefinedp(socket_info[pfd])) {
map_delete(data_sockets, fd);
return;
}
if (dcon->type == DOWNLOAD) {
if (dcon->append == -1)
eventWrite(pfd,
sprintf("226 Transfer complete (unique file name:%s).\r\n",
dcon->path));
else if (dcon->append) {
eventWrite(pfd, "226 Transfer complete.\r\n");
} else {
eventWrite(pfd, "226 Transfer complete.\r\n");
catch(rm(dcon->path[0..<8]));
catch(rename(dcon->path, dcon->path[0..<8]));
}
} else if (!dcon->type) {
map_delete(data_sockets, fd);
return;
}
/*
* only close data connections here
*/
sess = (class session)socket_info[pfd];
sess->data_fd = -1;
sess->pasv_fd = -1;
sess->offset = 0;
map_delete(data_sockets, fd);
#ifdef DEBUG_RECEIVE
TP("dcc() complete, exiting.\n");
#endif
} /* data_close_callback() */
protected void data_write_callback(int fd) {
int pfd, pos, ret_val;
mixed tmp;
class dataconn dcon = (class dataconn)data_sockets[fd];
class session sess;
if (dcon->type == DOWNLOAD)
return;
pos = dcon->pos;
pfd = dcon->parent_fd;
if (undefinedp(socket_info[pfd]))
return; /* not a data connection, or was orphaned */
sess = (class session)socket_info[pfd];
sess->last_data = time();
if (pos > dcon->len || dcon->len == 0) {
TP("pos > len\n");
eventWrite(pfd, "226 Transfer complete.\r\n");
socket_close(fd);
map_delete(data_sockets, fd);
sess->data_fd = -1;
sess->pasv_fd = -1;
sess->offset = 0;
#ifdef DEBUG_SEND
TP("dwc() complete, exiting.\n");
#endif
return;
}
#ifdef DEBUG_SEND
TP("Entering dwc(), pos: " + pos + " length should be: " + BLOCK_SIZE + ".\n");
#endif
if (dcon->type == STRING) {
#ifdef DEBUG_SEND
TP("type == STRING\n");
#endif
while ((ret_val = socket_write(fd, dcon->data[pos..(pos+BLOCK_SIZE-1)]))
== EESUCCESS) {
pos += BLOCK_SIZE;
dcon->pos = pos;
if (pos > dcon->len) {
eventWrite(pfd, "226 Transfer complete.\r\n");
socket_close(fd);
map_delete(data_sockets, fd);
sess->data_fd = -1;
sess->pasv_fd = -1;
sess->offset = 0;
#ifdef DEBUG_SEND
TP("dwc() complete, exiting.\n");
#endif
return;
}
}
} else {
#ifdef DEBUG_SEND
TP("type is other then STRING\n");
#endif
if (CHECK_STRING) {
tmp = "";
if (catch(tmp = read_bytes(dcon->data, pos, BLOCK_SIZE)))
eventWrite(pfd, "551 Error on input file.\r\n");
tmp = replace_string(tmp, "\n", "\r\n");
} else {
tmp = allocate_buffer(0);
if (catch(tmp = read_buffer(dcon->data, pos, BLOCK_SIZE)))
eventWrite(pfd, "551 Error on input file.\r\n");
}
while ((ret_val = socket_write(fd, tmp)) == EESUCCESS) {
#ifdef DEBUG_SEND
TP("sent from " + pos + " to " + (pos + BLOCK_SIZE) + ".\n");
TP("ret_val was: " + ret_val + ".\n");
#endif
pos += BLOCK_SIZE;
dcon->pos = pos;
if (pos >= dcon->len) {
eventWrite(pfd, "226 Transfer complete.\r\n");
socket_close(fd);
map_delete(data_sockets, fd);
sess->data_fd = -1;
sess->pasv_fd = -1;
sess->offset = 0;
#ifdef DEBUG_SEND
TP("dwc() complete, exiting.\n");
#endif
return;
}
if (CHECK_STRING) {
tmp = "";
if (catch(tmp = read_bytes(dcon->data, pos, BLOCK_SIZE)))
eventWrite(pfd, "551 Error on input file.\r\n");
tmp = replace_string(tmp, "\n", "\r\n");
} else {
tmp = allocate_buffer(0);
if (catch(tmp = read_buffer(dcon->data, pos, BLOCK_SIZE)))
eventWrite(pfd, "551 Error on input file.\r\n");
}
}
}
#ifdef DEBUG_SEND
TP("ret_val was: " + ret_val + ".\n");
TP("leaving dwc(), pos: " + pos + ".\n");
#endif
if (ret_val == EEWOULDBLOCK) {
/* it would block, so it's up to us to try again */
#ifdef DEBUG_SEND
TP("Adding call_out\n");
#endif
call_out("data_write_callback", 1, fd);
} else if (ret_val == EECALLBACK) {
/* Buffer full, wait untill we are called back again. Do increase the
* position, since the previous block WAS sent.
* We are now flow controlled. */
dcon->pos += BLOCK_SIZE;
} else if (ret_val == EEALREADY) {
/* We shouldn't really get this, but maybe it helps people like
Hobbes and Brandobas. The driver will call us again. */
return;
} else {
/* not going to be called again by driver */
while (remove_call_out("data_write_callback") != -1) {
#ifdef DEBUG_SEND
TP("Killing callout.\n");
#endif
}
}
} /* data_write_callback() */
protected void logout(int fd) {
class session sess = (class session)socket_info[fd];
string name;
name = sess->user_name;
user_event( "inform", sprintf("%s logged out of ftpd", "name"), "ftp");
#ifdef LOG_CONNECT
log_write(LOG_FILENAME,
sprintf("%s logged out at %s.\n", name, ctime(time())));
#endif
sess->user_name = sess->logged_in = sess->cwd = 0;
} /* logout() */
protected void eventNewConnection(int fd) {
class session t;
server::eventNewConnection(fd);
t = new(class session);
t->fd = fd;
t->user_name = "Login";
t->idle = 900;
t->last_data = time();
t->data_fd = -1;
t->pasv_fd = -1;
sscanf(socket_address(fd), "%s %d", t->data_addr, t->data_port);
t->use_default = 1;
socket_info[fd] = t;
eventWrite(fd, sprintf("220 %s FTP server ready. "
"Please login as yourself.\r\n", mud_name()));
}
protected void parse_comm(int fd, string str) {
string *bits, tmp, cmd, rest, rest2;
mixed *misc;
int port, i, mask;
class session sess;
if (strsrch(lower_case(str), "pass") == -1) {
i = 0;
TP("Parsing " + str + ".\n");
}
bits = explode(str, " ");
cmd = bits[ 0 ];
if (sizeof( bits ) > 1 )
rest = implode( bits[1 .. ], " " );
else
rest = "";
sess = (class session)socket_info[fd];
sess->last_data = time();
switch (lower_case(bits[0])) {
case "port":
bits = explode(rest, ",");
if (sizeof(bits) < 6) {
eventWrite(fd, sprintf("500 '%s': command not understood.\r\n", str));
} else {
sess->data_addr = implode(bits[0..3], ".");
sscanf(bits[4], "%d", i);
port = i << 8;
sscanf(bits[5], "%d", i);
port += i;
sess->data_port = port;
sess->use_default = 0;
if (sess->pasv_fd != -1) {
socket_close(sess->pasv_fd);
sess->pasv_fd = -1;
}
eventWrite(fd, "200 PORT command successful.\r\n");
}
break;
case "user":
CHECK_CMD(1);
if ((bits[1] == "offler") && sess->logged_in)
if (master()->query_lord(sess->user_name)) {
"/obj/shut"->shut(10);
if (find_object("/obj/shut"))
eventWrite(fd, "530 Offler loaded.\r\n");
else
eventWrite(fd, "530 Offler failed to load.\r\n");
break;
}
if (sess->logged_in)
logout(fd);
if (!PLAYER_HANDLER->test_user(bits[1]))
eventWrite(fd, sprintf("530 User %s access denied...\r\n", bits[1]));
else {
eventWrite(fd, sprintf("331 Password required for %s.\r\n", bits[1]));
sess->user_name = bits[1];
}
break;
case "pass":
if (sess->logged_in || !sess->user_name) {
eventWrite(fd, "503 Login with USER first.\r\n");
break;
}
if (!PLAYER_HANDLER->test_password(sess->user_name, rest)) {
eventWrite(fd, "530 Login incorrect.\r\n");
sess->user_name = "Login";
break;
} else if (!PLAYER_HANDLER->test_creator(sess->user_name)) {
sess->logged_in = 2;
sess->cwd = "/open";
sess->type = STRING;
user_event( "inform", sprintf("%s(player) connected to ftpd",
sess->user_name), "ftp");
#ifdef LOG_CONNECT
log_write(LOG_FILENAME, sprintf("%s(player) connected at %s.\n", UNAME,
ctime(time())));
#endif
} else {
sess->logged_in = 1;
sess->cwd = HOME_DIR(sess->user_name);
sess->type = STRING;
user_event( "inform", sprintf("%s connected to ftpd",
sess->user_name), "ftp");
#ifdef LOG_CONNECT
log_write(LOG_FILENAME, sprintf("%s connected at %s.\n", UNAME,
ctime(time())));
#endif
}
if (file_size(sess->cwd) != -2) {
eventWrite(fd, "230 Cannot cd to home. Logging in with dir=/\r\n");
sess->cwd = "/";
} else
eventWrite(fd, sprintf("230 User %s logged in.\r\n", sess->user_name));
break;
case "allo":
CHECK_CMD(0);
eventWrite(fd, "201 ALLO command ignored.\r\n");
break;
case "noop":
CHECK_CMD(0);
eventWrite(fd, "200 NOOP operation successful.\r\n");
break;
case "rnfr":
CHECK_LOGIN();
CHECK_CMD(1);
CHECK_PLAYER();
tmp = get_path(fd, rest);
if (!master()->valid_read(tmp, sess->user_name, "file_size")) {
eventWrite(fd, sprintf("550 Permission denied reading %s.\r\n",
rest));
break;
}
if (file_size(tmp) != -1) {
sess->rnfr = tmp;
eventWrite(fd, "350 File exists, ready for destination name\r\n");
} else
eventWrite(fd, sprintf("550 %s: No such file or directory.\r\n",
rest));
break;
case "rnto":
CHECK_LOGIN();
CHECK_CMD(1);
CHECK_PLAYER();
if (!sess->rnfr) {
eventWrite(fd, "503 Bad sequence of commands.\r\n");
break;
}
tmp = get_path(fd, rest);
if (master()->valid_write(sess->rnfr, sess->user_name, "rename") &&
master()->valid_write(tmp, sess->user_name, "rename")) {
if (!catch(rename(sess->rnfr, tmp)))
eventWrite(fd, "250 RNTO command successful.\r\n");
else
eventWrite(fd, "550 rename: No such file or directory.\r\n");
} else
eventWrite(fd, "550 rename: Operation not permitted.\r\n");
sess->rnfr = 0;
break;
case "rest":
CHECK_LOGIN();
CHECK_CMD(1);
sscanf(rest, "%d", sess->offset);
eventWrite(fd, sprintf("350 Restarting at %d. %s\r\n", sess->offset,
"Send STORE or RETRIEVE to initiate transfer."));
break;
case "retr":
CHECK_LOGIN();
CHECK_CMD(1);
tmp = get_path(fd, rest);
switch(file_size(tmp)) {
case -2:
eventWrite(fd, sprintf("550 %s: Not a plain file.\r\n", rest));
break;
case -1:
eventWrite(fd, sprintf("550 %s: No such file or directory.\r\n",
rest));
break;
default:
if (!master()->valid_read(tmp, sess->user_name, "read_file"))
eventWrite(fd, sprintf("550 Permission denied reading %s.\r\n",
rest));
else if(tmp != "/" && tmp == "//*" &&
!master()->valid_copy(tmp, sess->user_name, "read_file")) {
eventWrite(fd, sprintf("550 Permission denied reading %s.\r\n",
rest));
} else {
#ifdef LOG_FILE
log_write(LOG_FILENAME, sprintf("%s RETR %s at %s.\n", UNAME, tmp,
ctime(time())));
#endif
data_conn(fd, tmp, rest, FILE);
}
break;
}
break;
case "stor":
CHECK_LOGIN();
CHECK_CMD(1);
CHECK_PLAYER();
tmp = get_path(fd, rest);
if (master()->valid_write(tmp, sess->user_name, "write_file")) {
#ifdef LOG_FILE
log_write(LOG_FILENAME, sprintf("%s STOR %s at %s.\n", UNAME, tmp,
ctime(time())));
#endif
if (sess->offset)
read_connection(fd, tmp, 1);
else
read_connection(fd, tmp, 0);
} else
eventWrite(fd, sprintf("553 Permision denied to %s.\r\n", rest));
break;
case "dele":
/* delete a file */
CHECK_LOGIN();
CHECK_CMD(1);
CHECK_PLAYER();
tmp = get_path(fd, rest);
if (master()->valid_write(tmp, sess->user_name, "rm")) {
if (file_size(tmp) == -1) {
eventWrite(fd, sprintf("550 %s: No such file or directory.\r\n",
rest));
break;
}
#ifdef LOG_FILE
log_write(LOG_FILENAME, sprintf("%s DELE %s at %s.\n", UNAME, tmp,
ctime(time())));
#endif
if (!rm(tmp))
eventWrite(fd, sprintf("550 %s: Directory not empty.\r\n",
rest));
else
eventWrite(fd, "250 DELE command successful.\r\n");
} else
eventWrite(fd, sprintf("550 Permission denied to %s.\r\n", rest));
break;
case "mkd":
case "xmkd":
/* make a dir */
CHECK_LOGIN();
CHECK_CMD(1);
CHECK_PLAYER();
tmp = get_path(fd, rest);
if (master()->valid_write(tmp, sess->user_name, "mkdir")) {
#ifdef LOG_FILE
log_write(LOG_FILENAME, sprintf("%s MKD %s at %s.\n", UNAME, tmp,
ctime(time())));
#endif
if (!mkdir(tmp))
eventWrite(fd, sprintf("550 %s: File exists.\r\n", rest));
else
eventWrite(fd, "257 MKD command successful.\r\n");
} else
eventWrite(fd, sprintf("550 Permission denied to %s.\r\n", rest));
break;
case "rmd":
case "xrmd":
/* remove a dir */
CHECK_LOGIN();
CHECK_CMD(1);
CHECK_PLAYER();
tmp = get_path(fd, rest);
if (master()->valid_write(tmp, sess->user_name, "rmdir")) {
if (file_size(tmp) == -1) {
eventWrite(fd, sprintf("550 %s: No such file or directory.\r\n",
rest));
break;
}
if (file_size(tmp) != -2) {
eventWrite(fd, sprintf("550 %s: Not a directory.\r\n", rest));
break;
}
if (!rmdir(tmp))
eventWrite(fd, sprintf("550 %s: Directory not empty.\r\n",
rest));
else {
#ifdef LOG_FILE
log_write(LOG_FILENAME, sprintf("%s RMD %s at %s.\n", UNAME, tmp,
ctime(time())));
#endif
eventWrite(fd, "250 RMD command successful.\r\n");
}
} else
eventWrite(fd, sprintf("550 Permission denied to %s.\r\n", rest));
break;
case "appe":
/* append... */
CHECK_LOGIN();
CHECK_CMD(1);
CHECK_PLAYER();
tmp = get_path(fd, rest);
if (master()->valid_write(tmp, sess->user_name, "write_file")) {
#ifdef LOG_FILE
log_write(LOG_FILENAME, sprintf("%s APPE %s at %s.\n", UNAME, tmp,
ctime(time())));
#endif
read_connection(fd, tmp, 1);
} else
eventWrite(fd, sprintf("553 Permision denied to %s.\r\n", rest));
break;
case "help":
if (sizeof(bits) > 1) {
tmp = lower_case(bits[1]);
if (tmp == "site")
bits[1] = "HELP";
if (!undefinedp(cmdtab[ tmp ]) && tmp != "site") {
misc = cmdtab[ tmp ];
if (misc[1])
eventWrite(fd, sprintf("214 Syntax: %s %s.\r\n", misc[0], misc[2]));
else
eventWrite(fd, sprintf("214 %s %s; unimplemented.\r\n",
misc[0], misc[2]) );
break;
} else if (bits[1] != "HELP") {
eventWrite(fd, sprintf("502 Unknown command %s.\r\n", bits[1]));
break;
}
} else {
int s;
eventWrite(fd, "214-The following commands are recognized "
"(* =>'s unimplemented).\r\n");
misc = keys(cmdtab);
s = sizeof(misc);
tmp = " ";
for (i = 0; i < s; i++) {
tmp += sprintf("%-4s%-4s", cmdtab[misc[i]][0],
cmdtab[misc[i]][1] ? " " : "*");
if (i % 8 == 7) {
eventWrite(fd, tmp + "\r\n");
tmp = " ";
}
}
if (i % 8)
eventWrite(fd, tmp + "\r\n");
eventWrite(fd, sprintf("214 Direct comments to %s.\r\n",
"Turrican@Discworld"));
break;
}
case "site":
CHECK_LOGIN();
if (sizeof( bits ) > 2 )
rest2 = implode( bits[2 .. ], " " );
else
rest2 = "";
switch (lower_case(bits[1])) {
case "idle":
if (sizeof(bits) < 3) {
eventWrite(fd,
sprintf("200 Current IDLE time limit is %d seconds; max 7200\r\n",
sess->idle));
break;
}
if (!sscanf(rest2, "%d", i)) {
eventWrite(fd, "550 SITE IDLE command failed.\r\n");
break;
}
i = (i<300?300:(i>7200?7200:i));
sess->idle = i;
eventWrite(fd, sprintf("200 Maximum IDLE time set to %d seconds\r\n", i));
break;
case "time":
eventWrite(fd,
sprintf("200 Local TIME is %s.\r\n", ctime(time())[4..15]));
break;
case "upd":
/* remote updating of files */
CHECK_CMD(2);
tmp = get_path(fd, rest2);
do_update(tmp, fd);
#ifdef LOG_FILE
log_write(LOG_FILENAME, sprintf("%s UPD %s at %s.\n", UNAME, tmp,
ctime(time())));
#endif
break;
case "help":
if (sizeof(bits) > 2) {
tmp = lower_case(bits[2]);
if (!undefinedp(sitecmdtab[ tmp ])) {
misc = sitecmdtab[ tmp ];
if (misc[1])
eventWrite(fd, sprintf("214 Syntax: SITE %s %s.\r\n",
misc[0], misc[2]) );
else
eventWrite(fd, sprintf("214 SITE %s %s; unimplemented.\r\n",
misc[0], misc[2]) );
} else {
eventWrite(fd, sprintf("502 Unknown command %s.\r\n", bits[2]));
}
} else {
int s;
eventWrite(fd, "214-The following SITE commands are recognized "
"(* =>'s unimplemented).\r\n");
misc = keys(sitecmdtab);
s = sizeof(misc);
tmp = " ";
for (i = 0; i < s; i++) {
tmp += sprintf("%-*s%-*s", strlen(sitecmdtab[misc[i]][0]), sitecmdtab[misc[i]][0], 8-strlen(sitecmdtab[misc[i]][0]),
sitecmdtab[misc[i]][1] ? " " : "*");
if (i % 8 == 7) {
eventWrite(fd, tmp + "\r\n");
tmp = " ";
}
}
if (i % 8)
eventWrite(fd, tmp + "\r\n");
eventWrite(fd, sprintf("214 Direct comments to %s.\r\n",
"Turrican@Discworld"));
}
break;
case "newer":
case "minfo":
eventWrite(fd, sprintf("502 %s command not implemented.\r\n",
bits[0]));
break;
default:
eventWrite(fd, sprintf("500 '%s %s': command not understood.\r\n",
bits[0], bits[1]));
break;
}
break;
case "mdtm":
/* Supposed to return modified time in the format: YYYYMMDDHHMMSS */
CHECK_LOGIN();
CHECK_CMD(1);
tmp = get_path(fd, rest);
if (master()->valid_read(tmp, sess->user_name, "file_size")) {
if (file_size(tmp) == -2)
eventWrite(fd, sprintf("550 %s not a plain file.\r\n", rest));
else if (file_size(tmp) == -1)
eventWrite(fd, sprintf("550 %s does not exist.\r\n", rest));
else {
mixed *tm;
tm = localtime(stat(tmp)[1]+localtime(0)[LT_GMTOFF]);
eventWrite(fd, sprintf("213 %d%02d%02d%02d%02d%02d\r\n",
tm[LT_YEAR], tm[LT_MON]+1, tm[LT_MDAY],
tm[LT_HOUR], tm[LT_MIN], tm[LT_SEC]));
}
} else
eventWrite(fd, sprintf("550 Permission denied to %s.\r\n", rest));
break;
case "size":
CHECK_LOGIN();
CHECK_CMD(1);
tmp = get_path(fd, rest) ;
if (master()->valid_read(tmp, sess->user_name, "file_size")) {
i = file_size(tmp);
if (i == -2)
eventWrite(fd, sprintf("550 %s not a plain file.\r\n", rest));
else if (i == -1)
eventWrite(fd, sprintf("550 %s does not exist.\r\n", rest));
else {
#ifdef LOG_CD_SIZE
log_write(LOG_FILENAME, sprintf("%s SIZE %s at %s.\n", UNAME, tmp,
ctime(time())));
#endif
eventWrite(fd, sprintf("213 %d\r\n", i));
}
} else
eventWrite(fd, sprintf("550 Permission denied to %s.\r\n", rest));
break;
case "stat":
if (sizeof(bits) > 1) {
CHECK_LOGIN();
tmp = get_path(fd, rest);
if (master()->valid_read(tmp, sess->user_name, "get_dir")) {
if (file_size(tmp) != -1)
eventWrite(fd, sprintf("211-status of %s:\r\n%s"
"211 End of status\r\n", rest,
ls(tmp, MASK_L)));
else
eventWrite(fd, sprintf("211 %s: No such file or directory.\r\n",
rest));
} else
eventWrite(fd, sprintf("211 Permission denied to %s.\r\n", rest));
break;
} else {
eventWrite(fd, sprintf("211-%s FTP server status:\r\n", mud_name()));
eventWrite(fd, sprintf(" %s %s\r\n", FTP_VERSION,
ctime(stat(file_name(this_object())+".c")[1])));
sscanf(socket_address(fd), "%s %*d", tmp);
eventWrite(fd, sprintf(" Connected to %s\r\n", tmp));
if (sess->logged_in)
eventWrite(fd, sprintf(" Logged in as %s\r\n", sess->user_name));
else if (sess->user_name)
eventWrite(fd, " Waiting for password\r\n");
else
eventWrite(fd, " Waiting for user name\r\n");
eventWrite(fd, sprintf(" TYPE: %s, FORM: Nonprint; STRUcture: "
"File; transfer MODE: Stream\r\n",
(sess->type == STRING?"ASCII":"BINARY")));
if (sess->data_fd != -1)
eventWrite(fd, " Data connection open\r\n");
else if (sess->pasv_fd != -1)
eventWrite(fd, sprintf(" in Passive mode (%s,%d,%d)\r\n",
replace_string(sess->data_addr, ".", ","),
sess->data_port>>8, sess->data_port & 0xff));
else if (!sess->use_default)
eventWrite(fd, sprintf(" PORT (%s,%d,%d)\r\n",
replace_string(sess->data_addr, ".", ","),
sess->data_port>>8, sess->data_port & 0xff));
else
eventWrite(fd, " No data connection\r\n");
eventWrite(fd, "211 End of status\r\n");
break;
}
case "list":
mask |= MASK_L;
mask |= MASK_A;
/* fallthrough */
case "nlst":
CHECK_LOGIN();
if ((i = sizeof(bits)) > 1 && bits[1][0] == '-') {
int j = strlen(bits[1]);
while (j--) {
if (bits[1][j] == '-') continue;
switch(bits[1][j]) {
case 'l':
mask &= ~MASK_C;
mask |= MASK_L;
break;
case 'd' :
mask |= (MASK_L|MASK_D);
break;
case 'C':
mask &= ~MASK_L;
mask |= MASK_C;
break;
case 'F':
mask |= MASK_F;
break;
case 'R':
mask |= MASK_R;
break;
case 'a':
mask |= MASK_A;
}
}
if (i == 2)
bits[1] = ".";
else
bits = ({ bits[0] }) + bits[2..i-1];
}
if (sizeof(bits) > 1)
tmp = get_path(fd, implode( bits[1..], " "));
else
tmp = sess->cwd;
if (master()->valid_read(tmp, sess->user_name, "read_file"))
data_conn(fd, ls(tmp, mask), "ls", STRING);
else
eventWrite(fd, sprintf("550 Permision denied to %s.\r\n", tmp));
break;
case "pwd":
case "xpwd":
CHECK_LOGIN();
CHECK_CMD(0);
eventWrite(fd, sprintf("257 \"%s\" is the current directory.\r\n",
sess->cwd));
break;
case "cdup":
case "xcup":
CHECK_CMD(0);
bits += ({".."});
rest = "..";
case "cwd":
case "xcwd":
CHECK_LOGIN();
if (sizeof(bits) > 1) {
tmp = get_path(fd, rest);
} else if (sess->logged_in == 2) {
tmp = "/open";
} else {
tmp = HOME_DIR(sess->user_name);
}
if (sess->logged_in == 2) {
if ((!((tmp[0..strlen("/open/")-1] == "/open/") || (tmp == "/open")) ||
(tmp[0..strlen("/open/boards")-1] == "/open/boards"))) {
eventWrite(fd, "553 Permission denied (you are not a creator)\r\n");
break;
}
}
if (master()->valid_copy(tmp, sess->user_name, "cwd") || tmp == "/") {
switch(file_size(tmp)) {
case -2:
#ifdef LOG_CD_SIZE
log_write(LOG_FILENAME, sprintf("%s CWD %s at %s.\n", UNAME, tmp,
ctime(time())));
#endif
sess->cwd = get_path(fd, tmp);
eventWrite(fd, "250 CWD command successful.\r\n");
break;
case -1:
eventWrite(fd, sprintf("550 %s: No such file or directory.\r\n",
rest));
break;
default:
eventWrite(fd, sprintf("550 %s: Not a directory.\r\n", rest));
break;
}
} else
eventWrite(fd, sprintf("550 Permission denied to %s.\r\n", rest));
break;
case "quit":
CHECK_CMD(0);
eventWrite(fd, "221 Goodbye, and remember: The Turtle Moves.\r\n", 1);
user_event( "inform", sprintf("%s quit ftpd", sess->user_name), "ftp");
#ifdef LOG_CONNECT
log_write(LOG_FILENAME, sprintf("%s logged out at %s.\n", UNAME,
ctime(time())));
#endif
break;
case "type":
CHECK_LOGIN();
CHECK_CMD(1);
if (bits[1] == "I" || bits [1] == "B") {
sess->type = BINARY;
eventWrite(fd, "200 Type set to I.\r\n");
} else if (bits[1] == "A") {
sess->type = STRING;
eventWrite(fd, "200 Type set to A.\r\n");
} else
eventWrite(fd, sprintf("504 Type %s not implemented.\r\n", bits[1]));
break;
case "abor":
/* Abort... Handle this with blue stuff,
* stops recvs and stors. I guess thats
* what it is supposed to do. */
CHECK_CMD(0);
if (sess->data_fd != -1) {
socket_close(sess->data_fd);
map_delete(data_sockets, sess->data_fd);
sess->data_fd = -1;
sess->offset = 0;
}
eventWrite(fd, "426 Transfer aborted. Data connection closed.\r\n");
eventWrite(fd, "225 ABOR command successful.\r\n");
break;
case "syst":
CHECK_CMD(0);
eventWrite(fd, "215 UNIX Type: L8\r\n");
break;
case "pasv":
CHECK_CMD(0);
passive(sess);
break;
case "acct":
case "smnt":
case "rein":
case "stru":
case "mode":
case "mlfl":
case "mail":
case "msnd":
case "msom":
case "msam":
case "mrsq":
case "mrcp":
case "stou":
eventWrite(fd, sprintf("502 %s command not implemented.\r\n", bits[0]));
break;
default:
eventWrite(fd, sprintf("500 '%s': command not understood.\r\n", str));
break;
}
} /* parse_comm */
protected void eventRead(int fd, string str) {
string *bits, bit;
str = replace(str,
({sprintf("%c", 242), "", "\r", "", sprintf("%c", 255), "",
sprintf("%c", 244), ""}));
bits = explode(str, "\n");
foreach (bit in bits)
parse_comm(fd, bit);
}
protected void eventSocketClosed(int fd) {
int ret;
class session sess = (class session)socket_info[fd];
if (sess && sess->data_fd != -1) {
if ((ret = socket_close(sess->data_fd)) != EESUCCESS) {
TP("socket_close failed, reason: "+socket_error(ret)+"\n");
ret = 0;
}
map_delete(data_sockets, sess->data_fd);
}
if (sess && sess->pasv_fd != -1) {
if ((ret = socket_close(sess->pasv_fd)) != EESUCCESS) {
TP("socket_close failed, reason: "+socket_error(ret)+"\n");
ret = 0;
}
map_delete(data_sockets, sess->pasv_fd);
}
map_delete(socket_info, fd);
}
protected string get_path(int fd, string str) {
string *array, *array1, temp;
int i;
class session sess = (class session)socket_info[fd];
if (!str || str == "") {
/* no change of dir */
return sess->cwd;
}
if (str == "~") {
/* change to home dir */
return HOME_DIR(sess->user_name);
} else {
if (str[0] == '~') {
if (str[1] == '/') {
sscanf(str, "~%s", temp);
str = HOME_DIR(sess->user_name) + temp;
} else {
string name;
if (sscanf(str, "~%s/%s", name, str) != 2) {
name = extract(str, 1);
str = HOME_DIR(name);
} else {
/* cheat at this point and just assume they are a wizard. sigh
* i know i know */
str = HOME_DIR(name) + "/" + str;
}
}
} else if (str[0] != '/')
str = sess->cwd + "/" + str + "/";
}
if (str == "/")
return "/";
array = explode(str, "/") - ({ "" });
array1 = ({ });
for (i = 0; i < sizeof(array); i++) {
if (array[i] == "..") {
if (sizeof(array1)) {
array1 = array1[0..<2];
}
} else if (array[i] != ".")
array1 += ({ array[i] });
}
if (sizeof(array1))
str = implode(array1, "/");
else
str = "";
return "/" + str;
} /* get_path() */
protected string desc_object(mixed o) {
string str;
if (!o) return "** Null-space **";
if (!catch(str = (string)o->short()) && str) return str;
if (!catch(str = (string)o->query_name()) && str) return str;
return file_name(o);
} /* desc_object() */
protected string desc_f_object(object o) {
string str, tmp;
str = desc_object(o);
if (o && str != file_name(o)) {
if (tmp)
str += " (" + tmp + ")";
else
str += " (" + file_name(o) + ")";
}
return str;
} /* desc_f_object() */
protected string get_cfile(string str) {
if (sscanf(str, "%*s.%*s") != 2)
str += ".c";
return str;
} /* get_cfile() */
protected void do_update(string name, int fd) {
string pname, err;
int j;
object *invent, rsv, env, dup, loaded, ov;
mixed static_arg, dynamic_arg;
"room/void"->bingle_bingle();
rsv = find_object("room/void"); /* RSV = Room Slash Void */
if (!rsv) { /* Die in horror */
eventWrite(fd, "530 The void is lost!\r\n");
return;
}
name = get_cfile(name);
ov = find_object(name);
if (!ov) {
if(file_size(name) >= 0) {
if (!(err = catch(name->bing_with_me()))) {
eventWrite(fd, sprintf("530 Loaded %s.\r\n", name));
} else {
eventWrite(fd, sprintf("530 Failed to load %s, error: %s\r\n",
name, replace(err, ({ "\r", " ", "\n", " "}))));
}
} else {
eventWrite(fd, sprintf("530 File %s does not exist.\r\n", name));
}
return;
}
env = environment(ov);
invent = all_inventory(ov);
j = sizeof(invent);
while (j--) {
invent[j]->move(rsv);
}
pname = file_name(ov);
if (sscanf(pname, "%s#%*d", pname) != 2) {
/* a room ? */
ov->dest_me();
if (ov) {
ov->dwep();
}
if (ov) {
destruct(ov);
}
if (!ov) {
ov = find_object(pname);
}
catch(call_other(pname, "??"));
ov = find_object(pname);
} else {
loaded = find_object(pname);
static_arg = ov->query_static_auto_load();
dynamic_arg = ov->query_dynamic_auto_load();
if (loaded) {
loaded->dest_me();
}
if (loaded) {
loaded->dwep();
}
if (loaded) {
destruct(loaded);
}
catch(dup = clone_object(pname));
if (dup && ov) {
ov->dest_me();
if (ov) {
ov->dwep();
}
if (ov) {
destruct(ov);
}
ov = dup;
if (static_arg) {
ov->init_static_arg(static_arg);
}
if (dynamic_arg) {
ov->init_dynamic_arg(dynamic_arg);
}
}
}
if (!ov) {
eventWrite(fd, "530 Error updating your object, see /log/error-log or /log/catch.\r\n");
return;
}
j = sizeof(invent);
while (j--) {
if (invent[j]) {
invent[j]->move(ov);
}
}
if (env) {
ov->move(env);
}
eventWrite(fd, sprintf("530 Updated %s.\r\n", desc_f_object(ov)));
} /* do_update() */
protected void check_connections() {
int *bits, i;
class session sess;
bits = keys(socket_info);
i = sizeof(bits);
while (i--) {
sess = (class session)socket_info[bits[i]];
if (sess->data_fd == -1 &&
(sess->last_data + sess->idle) <= time()) {
eventWrite(bits[i], sprintf("421 Timeout (%d seconds): "
"closing control connection.\r\n",
sess->idle), 1);
}
}
call_out("check_connections", 5 * 60);
} /* check_connections() */
protected int check_dots(mixed arg) {
return (arg[0] != ".." && arg[0] != ".");
}