|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include <roxen.h> |
#include <module.h> |
#include <stat.h> |
|
|
|
#define FTP2_XTRA_HELP ({ "Report any bugs at http://community.roxen.com/crunch/" }) |
|
#define FTP2_TIMEOUT (5*60) |
|
|
#define FTP_USE_HANDLER_THREADS |
|
|
|
#ifdef FTP2_DEBUG |
# define DWRITE(X ...) werror(X) |
#else |
# define DWRITE(X ...) |
#endif |
|
|
#define LOCALE(X,Y) _DEF_LOCALE("prot_ftp",X,Y) |
|
|
|
#define BACKEND_CLOSE(FD) do { DWRITE("close\n"); FD->set_blocking(); call_out(FD->close, 0); FD = 0; } while(0) |
|
class RequestID2 |
{ |
inherit RequestID; |
|
mapping file; |
|
#ifdef FTP2_DEBUG |
protected void trace_enter(mixed a, mixed b) |
{ |
write("FTP: TRACE_ENTER(%O, %O)\n", a, b); |
} |
|
protected void trace_leave(mixed a) |
{ |
write("FTP: TRACE_LEAVE(%O)\n", a); |
} |
#endif /* FTP2_DEBUG */ |
|
void ready_to_receive() |
{ |
|
} |
|
Configuration configuration() |
{ |
return conf; |
} |
|
Stdio.File connection( ) |
{ |
return my_fd; |
} |
|
void send_result(mapping|void result) |
{ |
if (mappingp(result) && my_fd && my_fd->done) { |
my_fd->done(result); |
return; |
} |
|
error("Async sending with send_result() not supported yet.\n"); |
} |
|
object(RequestID2) clone_me() |
{ |
object(RequestID2) o = this_object(); |
return(object_program(o)(o)); |
} |
|
void end() |
{ |
} |
|
protected constant __num = ({ 0 }); |
int _num; |
|
void destroy() |
{ |
#ifdef FTP_REQUESTID_DEBUG |
report_debug("REQUESTID: Destroy request id #%d.\n", _num); |
#endif |
} |
|
void create(object|void m_rid) |
{ |
#ifdef FTP_REQUESTID_DEBUG |
_num = ++__num[0]; |
if (m_rid) { |
report_debug("REQUESTID: New request id #%d (CHILD to #%d).\n", |
_num, m_rid->_num); |
} else { |
report_debug("REQUESTID: New request id #%d (MASTER).\n", _num); |
} |
#else |
DWRITE("REQUESTID: New request id.\n"); |
#endif |
|
if (m_rid) { |
object o = this_object(); |
foreach(indices(m_rid), string var) { |
if (object_variablep(o, var)) { |
#ifdef DEBUG |
if (catch { |
#endif /* DEBUG */ |
o[var] = m_rid[var]; |
#ifdef DEBUG |
}) { |
report_error("FTP2: " |
"Failed to copy variable %s (value:%O)\n", |
var, m_rid[var]); |
} |
#endif /* DEBUG */ |
} |
} |
o->misc = m_rid->misc + ([]); |
} else { |
|
client = ({ "ftp" }); |
prot = "FTP"; |
clientprot = "FTP"; |
variables = FakedVariables(real_variables = ([])); |
misc = (["pref_languages": PrefLanguages()]); |
cookies = CookieJar(); |
throttle = ([]); |
client_var = ([]); |
request_headers = ([]); |
|
prestate = (<>); |
config = (<>); |
supports = (< "ftp", "images", "tables", >); |
pragma = (<>); |
rest_query = ""; |
extra_extension = ""; |
root_id = this_object(); |
} |
time = predef::time(1); |
#ifdef FTP2_DEBUG |
misc->trace_enter = trace_enter; |
misc->trace_leave = trace_leave; |
#endif /* FTP2_DEBUG */ |
} |
}; |
|
class FileWrapper |
{ |
protected string convert(string s); |
|
private function read_cb; |
private function close_cb; |
private mixed id; |
|
private object f; |
private string data; |
private object ftpsession; |
|
int is_file; |
|
protected void create(object f_, string data_, object ftpsession_) |
{ |
f = f_; |
data = data_; |
ftpsession = ftpsession_; |
|
is_file = f_->is_file; |
} |
|
private void read_callback(mixed i, string s) |
{ |
read_cb(id, convert(s)); |
ftpsession->touch_me(); |
} |
|
private void close_callback(mixed i) |
{ |
ftpsession->touch_me(); |
close_cb(id); |
} |
|
private void delayed_nonblocking(function w_cb) |
{ |
string d = data; |
data = 0; |
f->set_nonblocking(read_callback, w_cb, close_callback); |
if (d) { |
read_callback(0, d); |
} |
} |
|
void set_nonblocking(function r_cb, function w_cb, function c_cb) |
{ |
read_cb = r_cb; |
close_cb = c_cb; |
remove_call_out(delayed_nonblocking); |
if (r_cb) { |
if (data) { |
|
|
call_out(delayed_nonblocking, 0, w_cb); |
f->set_nonblocking(0, w_cb, 0); |
} else { |
f->set_nonblocking(read_callback, w_cb, close_callback); |
} |
} else { |
f->set_nonblocking(0, w_cb, 0); |
} |
} |
|
void set_blocking() |
{ |
if (data) { |
remove_call_out(delayed_nonblocking); |
} |
f->set_blocking(); |
} |
|
void set_id(mixed i) |
{ |
id = i; |
f->set_id(i); |
} |
|
int query_fd() |
{ |
return -1; |
} |
|
string read(int|void n) |
{ |
ftpsession->touch_me(); |
if (data) { |
if (n) { |
if (n < sizeof(data)) { |
string d = data[..n-1]; |
data = data[n..]; |
return convert(d); |
} else { |
string d = data; |
data = 0; |
return convert(d + f->read(n - sizeof(d))); |
} |
} else { |
string d = data; |
data = 0; |
return convert(d + f->read()); |
} |
} |
return(convert(f->read(n))); |
} |
|
void close() |
{ |
if (ftpsession) |
ftpsession->touch_me(); |
if (f) { |
f->set_blocking(); |
BACKEND_CLOSE(f); |
} |
} |
|
string query_address(int|void loc) |
{ |
if (!f->query_address) { |
werror("%O->query_address(%O)\n", f, loc); |
} |
return f->query_address(loc); |
} |
} |
|
class ToAsciiWrapper |
{ |
inherit FileWrapper; |
|
int converted; |
|
protected string convert(string s) |
{ |
converted += sizeof(s); |
return(replace(s, ({ "\r\n", "\n", "\r" }), ({ "\r\n", "\r\n", "\r\n" }))); |
} |
} |
|
class FromAsciiWrapper |
{ |
inherit FileWrapper; |
|
int converted; |
|
protected string convert(string s) |
{ |
converted += sizeof(s); |
#ifdef __NT__ |
|
return(replace(s, ({ "\r\n", "\n", "\r" }), ({ "\r\n", "\r\n", "\r\n" }))); |
#else /* !__NT__ */ |
#ifdef __MACOS__ |
return(replace(s, ({ "\r\n", "\n", "\r" }), ({ "\r", "\r", "\r" }))); |
#else /* !__MACOS__ */ |
return(replace(s, ({ "\r\n", "\n", "\r" }), ({ "\n", "\n", "\n" }))); |
#endif /* __MACOS__ */ |
#endif /* __NT__ */ |
} |
} |
|
|
class BinaryWrapper |
{ |
inherit FileWrapper; |
|
protected string convert(string s) |
{ |
return(s); |
} |
} |
|
|
|
class ToEBCDICWrapper |
{ |
inherit FileWrapper; |
|
int converted; |
|
protected Charset.Encoder converter = Charset.encoder("EBCDIC-US", ""); |
|
protected string convert(string s) |
{ |
converted += sizeof(s); |
return(converter->feed(s)->drain()); |
} |
} |
|
class FromEBCDICWrapper |
{ |
inherit FileWrapper; |
|
int converted; |
|
protected Charset.Decoder converter = Charset.decoder("EBCDIC-US"); |
|
protected string convert(string s) |
{ |
converted += sizeof(s); |
return(converter->feed(s)->drain()); |
} |
} |
|
|
class PutFileWrapper |
{ |
protected int response_code = 226; |
protected array(string) response = ({"Stored."}); |
protected string gotdata = ""; |
protected int closed, recvd; |
protected function other_read_callback; |
|
protected object from_fd; |
protected object session; |
protected object ftpsession; |
|
int is_file; |
|
protected void create(object from_fd_, object session_, object ftpsession_) |
{ |
from_fd = from_fd_; |
session = session_; |
ftpsession = ftpsession_; |
|
is_file = from_fd->is_file; |
} |
|
int bytes_received() |
{ |
return recvd; |
} |
|
int close(string|void how) |
{ |
DWRITE("FTP: PUT: close()\n"); |
ftpsession->touch_me(); |
if(how != "w" && !closed) { |
ftpsession->send(response_code, response); |
closed = 1; |
session->conf->received += recvd; |
session->file->len = recvd; |
session->conf->log(session->file, session); |
destruct(session); |
} |
if (how) { |
return from_fd->close(how); |
} else { |
BACKEND_CLOSE(from_fd); |
return 0; |
} |
} |
|
string read(mixed ... args) |
{ |
DWRITE("FTP: PUT: read()\n"); |
ftpsession->touch_me(); |
string r = from_fd->read(@args); |
if(stringp(r)) |
recvd += sizeof(r); |
return r; |
} |
|
protected mixed my_read_callback(mixed id, string data) |
{ |
DWRITE("FTP: PUT: my_read_callback(X, \"%s\")\n", data||""); |
ftpsession->touch_me(); |
if(stringp(data)) |
recvd += sizeof(data); |
return other_read_callback(id, data); |
} |
|
void set_read_callback(function read_callback) |
{ |
DWRITE("FTP: PUT: set_read_callback()\n"); |
ftpsession->touch_me(); |
if(read_callback) { |
other_read_callback = read_callback; |
from_fd->set_read_callback(my_read_callback); |
} else |
from_fd->set_read_callback(read_callback); |
} |
|
void set_nonblocking(function ... args) |
{ |
DWRITE("FTP: PUT: set_nonblocking()\n"); |
if(sizeof(args) && args[0]) { |
other_read_callback = args[0]; |
from_fd->set_nonblocking(my_read_callback, @args[1..]); |
} else |
from_fd->set_nonblocking(@args); |
} |
|
void set_blocking() |
{ |
from_fd->set_blocking(); |
} |
|
void set_id(mixed id) |
{ |
from_fd->set_id(id); |
} |
|
int write(string data) |
{ |
DWRITE("FTP: PUT: write(\"%s\")\n", data||""); |
|
ftpsession->touch_me(); |
|
int n, code; |
string msg; |
gotdata += data; |
while((n=search(gotdata, "\n"))>=0) { |
if(3==sscanf(gotdata[..n], "HTTP/%*s %d %[^\r\n]", code, msg) |
&& code>199) { |
if(code < 300) |
code = 226; |
else |
code = 550; |
response_code = code; |
response = ({msg}); |
} |
gotdata = gotdata[n+1..]; |
} |
return strlen(data); |
} |
|
void done(mapping result) |
{ |
if (result->error < 300) { |
response_code = 226; |
} else { |
response_code = 550; |
} |
|
|
if (result->rettext) |
response = result->rettext / "\n"; |
else |
response = ({Roxen.http_status_messages[result->error] || ""}); |
gotdata = result->data || ""; |
|
close(); |
} |
|
string query_address(int|void loc) |
{ |
if (!from_fd->query_address) { |
werror("%O->query_address(%O)\n", from_fd, loc); |
} |
return from_fd->query_address(loc); |
} |
} |
|
|
protected string name_from_uid(RequestID master_session, int uid) |
{ |
string res; |
|
|
if (!master_session->misc->username_from_uid) { |
master_session->misc->username_from_uid = ([]); |
} else if (res = master_session->misc->username_from_uid[uid]) { |
return res; |
} |
User user; |
foreach(master_session->conf->user_databases(), UserDB user_db) { |
if (user = user_db->find_user_from_uid(uid)) { |
master_session->misc->username_from_uid[uid] = res = user->name(); |
return res; |
} |
} |
master_session->misc->username_from_uid[uid] = res = |
(uid?((string)uid):"root"); |
return res; |
} |
|
|
|
#define LS_FLAG_A 0x00001 |
#define LS_FLAG_a 0x00002 |
#define LS_FLAG_b 0x00004 |
#define LS_FLAG_C 0x00008 |
#define LS_FLAG_d 0x00010 |
#define LS_FLAG_F 0x00020 |
#define LS_FLAG_f 0x00040 |
#define LS_FLAG_G 0x00080 |
#define LS_FLAG_h 0x00100 |
#define LS_FLAG_l 0x00200 |
#define LS_FLAG_m 0x00400 |
#define LS_FLAG_n 0x00800 |
#define LS_FLAG_r 0x01000 |
#define LS_FLAG_Q 0x02000 |
#define LS_FLAG_R 0x04000 |
#define LS_FLAG_S 0x08000 |
#define LS_FLAG_s 0x10000 |
#define LS_FLAG_t 0x20000 |
#define LS_FLAG_U 0x40000 |
#define LS_FLAG_v 0x80000 |
|
class LS_L(protected RequestID master_session, |
protected int|void flags) |
{ |
protected constant decode_mode = ({ |
({ S_IRUSR, S_IRUSR, 1, "r" }), |
({ S_IWUSR, S_IWUSR, 2, "w" }), |
({ S_IXUSR|S_ISUID, S_IXUSR, 3, "x" }), |
({ S_IXUSR|S_ISUID, S_ISUID, 3, "S" }), |
({ S_IXUSR|S_ISUID, S_IXUSR|S_ISUID, 3, "s" }), |
({ S_IRGRP, S_IRGRP, 4, "r" }), |
({ S_IWGRP, S_IWGRP, 5, "w" }), |
({ S_IXGRP|S_ISGID, S_IXGRP, 6, "x" }), |
({ S_IXGRP|S_ISGID, S_ISGID, 6, "S" }), |
({ S_IXGRP|S_ISGID, S_IXGRP|S_ISGID, 6, "s" }), |
({ S_IROTH, S_IROTH, 7, "r" }), |
({ S_IWOTH, S_IWOTH, 8, "w" }), |
({ S_IXOTH|S_ISVTX, S_IXOTH, 9, "x" }), |
({ S_IXOTH|S_ISVTX, S_ISVTX, 9, "T" }), |
({ S_IXOTH|S_ISVTX, S_IXOTH|S_ISVTX, 9, "t" }) |
}); |
|
protected constant months = ({ "Jan", "Feb", "Mar", "Apr", "May", "Jun", |
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }); |
|
string ls_l(string file, array st) |
{ |
DWRITE("ls_l(\"%s\")\n", file); |
|
int mode = st[0] & 007777; |
array(string) perm = "----------"/""; |
|
if (st[1] < 0) { |
perm[0] = "d"; |
} |
|
foreach(decode_mode, array(string|int) info) { |
if ((mode & info[0]) == info[1]) { |
perm[info[2]] = info[3]; |
} |
} |
|
mapping lt = localtime(st[3]); |
|
|
string user = (string)st[5]; |
string group = (string)st[6]; |
if (!(flags & LS_FLAG_n)) { |
|
if (!stringp(st[5])) { |
user = name_from_uid(master_session, st[5]); |
} |
|
if (!stringp(st[6])) { |
|
if (!st[6]) group = "wheel"; |
} |
} |
|
string ts; |
int now = time(1); |
|
|
if ((st[3] <= now - 15778800) || (st[3] > now)) { |
|
ts = sprintf("%s %02d %04d", |
months[lt->mon], lt->mday, 1900+lt->year); |
} else { |
|
ts = sprintf("%s %02d %02d:%02d", |
months[lt->mon], lt->mday, lt->hour, lt->min); |
} |
|
if (flags & LS_FLAG_G) { |
|
return sprintf("%s 1 %-10s %12d %s %s\n", perm*"", |
user, (st[1]<0? 512:st[1]), |
ts, file); |
} else { |
return sprintf("%s 1 %-10s %-6s %12d %s %s\n", perm*"", |
user, group, (st[1]<0? 512:st[1]), |
ts, file); |
} |
} |
} |
|
class LSFile |
{ |
protected inherit LS_L; |
|
protected string cwd; |
protected array(string) argv; |
protected object ftpsession; |
|
protected array(string) output_queue = ({}); |
protected int output_pos; |
protected string output_mode = "A"; |
|
protected mapping(string:array|object) stat_cache = ([]); |
|
protected Charset.Encoder conv; |
|
protected array|object stat_file(string long, RequestID|void session) |
{ |
array|object st = stat_cache[long]; |
if (zero_type(st)) { |
session = RequestID2(session || master_session); |
session->method = "DIR"; |
long = replace(long, "//", "/"); |
st = session->conf->stat_file(long, session); |
stat_cache[long] = st; |
destruct(session); |
} |
return st; |
} |
|
|
protected void output(string s) |
{ |
if(stringp(s)) { |
|
s = replace(s, "\n", "\r\n"); |
if (conv) { |
|
s = conv->feed(s)->drain(); |
} |
} |
output_queue += ({ s }); |
} |
|
protected string quote_non_print(string s) |
{ |
return(replace(s, ({ |
"\000", "\001", "\002", "\003", "\004", "\005", "\006", "\007", |
"\010", "\011", "\012", "\013", "\014", "\015", "\016", "\017", |
"\200", "\201", "\202", "\203", "\204", "\205", "\206", "\207", |
"\210", "\211", "\212", "\213", "\214", "\215", "\216", "\217", |
"\177", |
}), ({ |
"\\000", "\\001", "\\002", "\\003", "\\004", "\\005", "\\006", "\\007", |
"\\010", "\\011", "\\012", "\\013", "\\014", "\\015", "\\016", "\\017", |
"\\200", "\\201", "\\202", "\\203", "\\204", "\\205", "\\206", "\\207", |
"\\210", "\\211", "\\212", "\\213", "\\214", "\\215", "\\216", "\\217", |
"\\177", |
}))); |
} |
|
protected string list_files(array(string) files, string|void dir) |
{ |
dir = dir || cwd; |
|
DWRITE("FTP: LSFile->list_files(%O, \"%s\"\n", files, dir); |
|
if (!(flags & LS_FLAG_U)) { |
if (flags & LS_FLAG_S) { |
array(int) sizes = allocate(sizeof(files)); |
int i; |
for (i=0; i < sizeof(files); i++) { |
array|object st = stat_file(combine_path(dir, files[i])); |
if (st) { |
sizes[i] = st[1]; |
} else { |
|
files -= ({ files[i] }); |
} |
} |
sort(sizes, files); |
} else if (flags & LS_FLAG_t) { |
array(int) times = allocate(sizeof(files)); |
int i; |
for (i=0; i < sizeof(files); i++) { |
array|object st = stat_file(combine_path(dir, files[i])); |
if (st) { |
times[i] = -st[-4]; |
} else { |
|
files -= ({ files[i] }); |
} |
} |
sort(times, files); |
} else { |
sort(files); |
} |
if (flags & LS_FLAG_r) { |
files = reverse(files); |
} |
} |
|
string res = ""; |
int total; |
foreach(files, string short) { |
string long = combine_path(dir, short); |
array|object st = stat_file(long); |
if (st) { |
if (flags & LS_FLAG_Q) { |
|
|
short = "\"" + |
replace(short, |
({ "\n", "\r", "\\", "\"", "\'", " " }), |
({ "\\n", "\\r", "\\\\", "\\\"", "\\\'", "\\020" })) + |
"\""; |
} |
short = string_to_utf8(short); |
if (flags & LS_FLAG_F) { |
if (st[1] < 0) { |
|
short += "/"; |
} else if (st[0] & 0111) { |
|
short += "*"; |
} |
} |
int blocks = 1; |
if (st[1] >= 0) { |
blocks = (st[1] + 1023)/1024; |
} |
total += blocks; |
if (flags & LS_FLAG_s) { |
res += sprintf("%7d ", blocks); |
} |
if (flags & LS_FLAG_b) { |
short = quote_non_print(short); |
} |
if (flags & LS_FLAG_l) { |
res += ls_l(short, st); |
} else { |
res += short + "\n"; |
} |
} |
} |
switch (flags & (LS_FLAG_l|LS_FLAG_C|LS_FLAG_m)) { |
case LS_FLAG_C: |
res = sprintf("%#-79s\n", res); |
break; |
case LS_FLAG_m: |
res = sprintf("%=-79s\n", (res/"\n")*", "); |
break; |
case LS_FLAG_l: |
res = "total " + total + "\n" + res; |
break; |
default: |
break; |
} |
return(res); |
} |
|
#if constant (ADT.Stack) |
protected ADT.Stack dir_stack = ADT.Stack(); |
#else |
protected object(Stack.stack) dir_stack = Stack.stack(); |
#endif |
protected int name_directories; |
|
protected string fix_path(string s) |
{ |
return(combine_path(cwd, s)); |
} |
|
protected int(0..1) list_next_directory() |
{ |
if (dir_stack->ptr) { |
string short = dir_stack->pop(); |
string long = fix_path(short); |
|
if ((!sizeof(long)) || (long[-1] != '/')) { |
long += "/"; |
} |
RequestID session = RequestID2(master_session); |
session->method = "DIR"; |
|
mixed err; |
mapping(string:array) dir; |
err = catch { |
dir = session->conf->find_dir_stat(long, session); |
}; |
|
destruct(session); |
|
if (err) { |
report_error("FTP: LSFile->list_next_directory(): " |
"find_dir_stat(\"%s\") failed:\n" |
"%s\n", long, describe_backtrace(err)); |
} |
|
DWRITE("FTP: LSFile->list_next_directory(): " |
"find_dir_stat(\"%s\") => %O\n", long, dir); |
|
|
foreach(indices(dir||({})), string f) { |
stat_cache[combine_path(long, f)] = dir[f]; |
} |
|
dir = dir || ([]); |
|
if (flags & LS_FLAG_a) { |
if (long != "/") { |
dir[".."] = stat_file(combine_path(long,"../")); |
} |
dir["."] = stat_file(combine_path(long)); |
} |
|
string listing = ""; |
if (sizeof(dir)) { |
if (!(flags & LS_FLAG_A)) { |
foreach(indices(dir), string f) { |
if (sizeof(f) && (f[0] == '.')) { |
m_delete(dir, f); |
} |
} |
} else if (!(flags & LS_FLAG_a)) { |
foreach(indices(dir), string f) { |
if ((< ".", ".." >)[f]) { |
m_delete(dir, f); |
} |
} |
} |
if (flags & LS_FLAG_R) { |
foreach(indices(dir), string f) { |
if (!((<".","..">)[f])) { |
array(mixed) st = dir[f]; |
if (st && (st[1] < 0)) { |
if (short[-1] == '/') { |
dir_stack->push(short + f); |
} else { |
dir_stack->push(short + "/" + f); |
} |
} |
} |
} |
} |
if (sizeof(dir)) { |
listing = list_files(indices(dir), long); |
} else if (flags & LS_FLAG_l) { |
listing = "total 0\n"; |
} |
} else { |
DWRITE("FTP: LSFile->list_next_directory(): NO FILES!\n"); |
|
if (flags & LS_FLAG_l) { |
listing = "total 0\n"; |
} |
} |
if (name_directories) { |
listing = "\n" + short + ":\n" + listing; |
} |
if (listing != "") { |
output(listing); |
} |
session = RequestID2(master_session); |
session->method = "LIST"; |
session->not_query = long; |
session->conf->log(([ "error":200, "len":sizeof(listing) ]), session); |
} |
if (!dir_stack->ptr) { |
output(0); |
return 0; |
} else { |
name_directories = 1; |
return 1; |
} |
} |
|
void set_blocking() |
{ |
} |
|
int query_fd() |
{ |
return -1; |
} |
|
protected mixed id; |
|
void set_id(mixed i) |
{ |
id = i; |
} |
|
void fill_output_queue() |
{ |
if (!sizeof(output_queue) || output_queue[-1]) { |
while (list_next_directory()) |
; |
} |
} |
|
string read(int|void n, int|void not_all) |
{ |
DWRITE("FTP: LSFile->read(%d, %d)\n", n, not_all); |
|
ftpsession->touch_me(); |
|
while(sizeof(output_queue) <= output_pos) { |
|
output_queue = ({}); |
output_pos = 0; |
|
|
list_next_directory(); |
} |
string s = output_queue[output_pos]; |
|
if (s) { |
if (n && (n < sizeof(s))) { |
output_queue[output_pos] = s[n..]; |
s = s[..n-1]; |
} else { |
output_queue[output_pos++] = 0; |
} |
return s; |
} else { |
|
master_session->file = 0; |
return ""; |
} |
} |
|
void create(string cwd_, array(string) argv_, int flags_, |
object session_, string output_mode_, object ftpsession_) |
{ |
DWRITE("FTP: LSFile(\"%s\", %O, %08x, X, \"%s\")\n", |
cwd_, argv_, flags_, output_mode_); |
|
::create(session_, flags_); |
|
cwd = cwd_; |
argv = argv_; |
output_mode = output_mode_; |
ftpsession = ftpsession_; |
|
if (output_mode == "E") { |
|
conv = Charset.encoder("EBCDIC-US", ""); |
} |
|
array(string) files = allocate(sizeof(argv)); |
int n_files; |
|
foreach(argv, string short) { |
RequestID session = RequestID2(master_session); |
session->method = "LIST"; |
string long = fix_path(short); |
array|object st = stat_file(long, session); |
if (st) { |
if ((< -2, -3 >)[st[1]] && |
(!(flags & LS_FLAG_d))) { |
|
dir_stack->push(short); |
} else { |
files[n_files++] = short; |
} |
} else { |
output(short + ": not found\n"); |
session->conf->log(([ "error":404 ]), session); |
} |
destruct(session); |
} |
|
DWRITE("FTP: LSFile: %d files, %d directories\n", |
n_files, dir_stack->ptr); |
|
if (n_files) { |
if (n_files < sizeof(files)) { |
files -= ({ 0 }); |
} |
string s = list_files(files, cwd); |
output(s); |
RequestID session = RequestID2(master_session); |
session->not_query = Array.map(files, fix_path) * " "; |
session->method = "LIST"; |
session->conf->log(([ "error":200, "len":sizeof(s) ]), session); |
destruct(session); |
} |
if (dir_stack->ptr) { |
name_directories = dir_stack->ptr && |
((dir_stack->ptr > 1) || n_files); |
|
list_next_directory(); |
} else { |
output(0); |
} |
} |
} |
|
class TelnetSession { |
protected object fd; |
protected object conf; |
|
private mapping cb; |
private mixed id; |
protected function(mixed|void:string) write_cb; |
protected function(mixed, string:void) read_cb; |
protected function(mixed|void:void) close_cb; |
|
private constant TelnetCodes = ([ |
236:"EOF", |
237:"SUSP", |
238:"ABORT", |
239:"EOR", |
|
|
240:"SE", |
241:"NOP", |
242:"DM", |
243:"BRK", |
244:"IP", |
245:"AO", |
246:"AYT", |
247:"EC", |
248:"EL", |
249:"GA", |
250:"SB", |
251:"WILL", |
252:"WON'T", |
253:"DO", |
254:"DON'T", |
255:"IAC", |
]); |
|
|
private void got_data(mixed ignored, string s); |
private void send_data(); |
private void got_oob(mixed ignored, string s); |
|
void set_write_callback(function(mixed|void:string) w_cb) |
{ |
if (fd) { |
write_cb = w_cb; |
fd->set_nonblocking(got_data, w_cb && send_data, close_cb, got_oob); |
} |
} |
|
private string to_send = ""; |
private void send(string s) |
{ |
to_send += s; |
} |
|
private void send_data() |
{ |
if (!sizeof(to_send)) { |
to_send = write_cb(id); |
} |
if (fd) { |
if (!to_send) { |
|
BACKEND_CLOSE(fd); |
} else if (sizeof(to_send)) { |
int n = fd->write(to_send); |
|
if (n >= 0) { |
conf->hsent += n; |
|
to_send = to_send[n..]; |
|
if (sizeof(to_send)) { |
fd->set_write_callback(send_data); |
} |
} else { |
|
DWRITE("TELNET: write failed: errno:%d\n", fd->errno()); |
BACKEND_CLOSE(fd); |
} |
} else { |
|
|
|
fd->set_write_callback(0); |
|
report_warning("FTP2: Write callback with nothing to send.\n"); |
} |
} else { |
report_error("FTP2: Write callback with no fd.\n"); |
destruct(); |
} |
} |
|
private mapping(string:function) default_cb = ([ |
"BRK":lambda() { |
if (fd) { |
fd->close(); |
fd = 0; |
} |
destruct(); |
throw(0); |
}, |
"AYT":lambda() { |
send("\377\361"); |
}, |
"WILL":lambda(int code) { |
send(sprintf("\377\376%c", code)); |
}, |
"DO":lambda(int code) { |
send(sprintf("\377\374%c", code)); |
}, |
]); |
|
private int sync = 0; |
|
private void got_oob(mixed ignored, string s) |
{ |
DWRITE("TELNET: got_oob(\"%s\")\n", s); |
|
sync = sync || (s == "\377"); |
if (cb["URG"]) { |
cb["URG"](id, s); |
} |
} |
|
private string rest = ""; |
private void got_data(mixed ignored, string s) |
{ |
DWRITE("TELNET: got_data(\"%s\")\n", s); |
|
if (sizeof(s) && (s[0] == 242)) { |
DWRITE("TELNET: Data Mark\n"); |
|
s = s[1..]; |
sync = 0; |
} |
|
|
|
|
array lines = s/"\r\n"; |
|
|
s = sprintf("string(%d bytes)", sizeof(s)); |
|
int lineno; |
for(lineno = 0; lineno < sizeof(lines); lineno++) { |
string line = lines[lineno]; |
if (search(line, "\377") != -1) { |
array a = line / "\377"; |
|
string parsed_line = a[0]; |
int i; |
for (i=1; i < sizeof(a); i++) { |
string part = a[i]; |
if (sizeof(part)) { |
string name = TelnetCodes[part[0]]; |
|
DWRITE("TELNET: Code %s\n", name || "Unknown"); |
|
int j; |
function fun; |
switch (name) { |
case 0: |
|
break; |
default: |
if (fun = (cb[name] || default_cb[name])) { |
mixed err = catch { |
fun(); |
}; |
if (err) { |
throw(err); |
} else if (!zero_type(err)) { |
|
return; |
} |
} |
a[i] = a[i][1..]; |
break; |
case "EC": |
for (j=i; j--;) { |
if (sizeof(a[j])) { |
a[j] = a[j][..sizeof(a[j])-2]; |
break; |
} |
} |
a[i] = a[i][1..]; |
break; |
case "EL": |
for (j=0; j < i; j++) { |
a[j] = ""; |
} |
a[i] = a[i][1..]; |
break; |
case "WILL": |
case "WON'T": |
case "DO": |
case "DON'T": |
if (fun = (cb[name] || default_cb[name])) { |
fun(a[i][1]); |
} |
a[i] = a[i][2..]; |
break; |
case "DM": |
if (sync) { |
for (j=0; j < i; j++) { |
a[j] = ""; |
} |
} |
a[i] = a[i][1..]; |
sync = 0; |
break; |
} |
} else { |
a[i] = "\377"; |
i++; |
} |
} |
line = a * ""; |
} |
if (!lineno) { |
line = rest + line; |
} |
if (lineno < (sizeof(lines)-1)) { |
if ((!sync) && read_cb) { |
DWRITE("TELNET: Calling read_callback(X, \"%s\")\n", line); |
read_cb(id, line); |
} |
} else { |
DWRITE("TELNET: Partial line is \"%s\"\n", line); |
rest = line; |
} |
} |
} |
|
void create(object f, |
function(mixed,string:void) r_cb, |
function(mixed|void:string) w_cb, |
function(mixed|void:void) c_cb, |
mapping callbacks, mixed|void new_id) |
{ |
fd = f; |
cb = callbacks; |
|
read_cb = r_cb; |
write_cb = w_cb; |
close_cb = c_cb; |
id = new_id; |
|
fd->set_nonblocking(got_data, w_cb && send_data, close_cb, got_oob); |
} |
}; |
|
class FTPSession |
{ |
|
|
|
|
inherit TelnetSession; |
|
inherit "roxenlib"; |
|
private mapping(string:string|Locale.DeferredLocale) cmd_help = ([ |
|
|
|
"CLNT":LOCALE(1, "<sp> <client-name> <sp> <client-version> " |
"[<sp> <optional platform info>] (Set client name)"), |
|
|
"HOST":LOCALE(2, "<sp> hostname (Host name)"), |
|
|
"MDTM":LOCALE(3, "<sp> path-name (Modification time)"), |
"SIZE":LOCALE(4, "<sp> path-name (Size)"), |
"MLST":LOCALE(5, "<sp> path-name (Machine Processing List File)"), |
"MLSD":LOCALE(6, "<sp> path-name (Machine Processing List Directory)"), |
|
|
"LANG":LOCALE(7, "<sp> lang-tag (Change Interface Language)"), |
|
|
"EPRT":LOCALE(8, "<sp> <d>net-prt<d>net-addr<d>tcp-port<d> (Extended Address Port)"), |
"EPSV":LOCALE(9, "[<sp> net-prt|ALL] (Extended Address Passive Mode)"), |
|
|
"FEAT":LOCALE(10, "(Feature list)"), |
"OPTS":LOCALE(11, "<sp> command <sp> options (Set Command-specific Options)"), |
|
|
"AUTH":LOCALE(12, "security-mechanism (Authentication/Security Mechanism)"), |
"ADAT":LOCALE(13, "security-data (Authentication/Security Data)"), |
"PBSZ":LOCALE(14, "<sp> size (Protection Buffer SiZe)"), |
"PROT":LOCALE(15, "<sp> [ C | S | E | P ] (Data Channel Protection Level)"), |
"CCC":LOCALE(16, "(Clear Command Channel)"), |
"MIC":LOCALE(17, "command (Integrity Protected Command)"), |
"CONF":LOCALE(18, "command (Confidentiality Protected Command)"), |
"ENC":LOCALE(19, "command (Privacy Protected Command)"), |
|
|
"LPRT":LOCALE(20, "<sp> <long-host-port> (Long Port)"), |
"LPSV":LOCALE(21, "(Long Passive)"), |
|
|
|
|
"USER":LOCALE(22, "<sp> username (Change user)"), |
"PASS":LOCALE(23, "<sp> password (Change password)"), |
"ACCT":LOCALE(24, "<sp> <account-information> (Account)"), |
"CWD":LOCALE(25, "[ <sp> directory-name ] (Change working directory)"), |
"CDUP":LOCALE(26, "(Change to parent directory)"), |
"SMNT":LOCALE(27, "<sp> <pathname> (Structure mount)"), |
|
"REIN":LOCALE(28, "(Reinitialize)"), |
"QUIT":LOCALE(29, "(Terminate service)"), |
|
"PORT":LOCALE(30, "<sp> b0, b1, b2, b3, b4 (Set port IP and number)"), |
"PASV":LOCALE(31, "(Set server in passive mode)"), |
"TYPE":LOCALE(32, "<sp> [ A | E | I | L ] (Ascii, Ebcdic, Image, Local)"), |
"STRU":LOCALE(33, "<sp> <structure-code> (File structure)"), |
"MODE":LOCALE(34, "<sp> <mode-code> (Transfer mode)"), |
|
"ALLO":LOCALE(35, "<sp> <decimal-integer> [<sp> R <sp> <decimal-integer>]" |
" (Allocate space for file)"), |
"REST":LOCALE(36, "<sp> marker (Set restart marker)"), |
"STOR":LOCALE(37, "<sp> file-name (Store file)"), |
"STOU":LOCALE(38, "(Store file with unique name)"), |
"RETR":LOCALE(39, "<sp> file-name (Retreive file)"), |
"LIST":LOCALE(40, "[ <sp> <pathname> ] (List directory)"), |
"NLST":LOCALE(40, "[ <sp> <pathname> ] (List directory)"), |
"APPE":LOCALE(41, "<sp> <pathname> (Append file)"), |
"RNFR":LOCALE(42, "<sp> <pathname> (Rename from)"), |
"RNTO":LOCALE(43, "<sp> <pathname> (Rename to)"), |
"DELE":LOCALE(44, "<sp> file-name (Delete file)"), |
"RMD":LOCALE(45, "<sp> <pathname> (Remove directory)"), |
"MKD":LOCALE(46, "<sp> <pathname> (Make directory)"), |
"PWD":LOCALE(47, "(Return current directory)"), |
"ABOR":LOCALE(48, "(Abort current transmission)"), |
|
"SYST":LOCALE(49, "(Get type of operating system)"), |
"STAT":LOCALE(50, "[ <sp> <pathname> ] (Status for server/file)"), |
"HELP":LOCALE(51, "[ <sp> <string> ] (Give help)"), |
|
"SITE":LOCALE(52, "<sp> <string> (Site parameters)"), |
"NOOP":LOCALE(53, "(No operation)"), |
|
|
|
|
"XMKD":LOCALE(54, "<sp> path-name (Make directory)"), |
"XRMD":LOCALE(55, "<sp> path-name (Remove directory)"), |
"XPWD":LOCALE(47, "(Return current directory)"), |
"XCWD":LOCALE(25, "[ <sp> directory-name ] (Change working directory)"), |
"XCUP":LOCALE(26, "(Change to parent directory)"), |
|
|
"MAIL":LOCALE(56, "[<sp> <recipient name>] (Mail to user)"), |
"MSND":LOCALE(57, "[<sp> <recipient name>] (Mail send to terminal)"), |
"MSOM":LOCALE(58, "[<sp> <recipient name>] (Mail send to terminal or mailbox)"), |
"MSAM":LOCALE(59, "[<sp> <recipient name>] (Mail send to terminal and mailbox)"), |
"MRSQ":LOCALE(60, "[<sp> <scheme>] (Mail recipient scheme question)"), |
"MRCP":LOCALE(61, "<sp> <recipient name> (Mail recipient)"), |
|
|
"XRSQ":LOCALE(62, "[<sp> <scheme>] (Scheme selection)"), |
"XRCP":LOCALE(63, "<sp> <recipient name> (Recipient specification)"), |
|
|
"XSEN":LOCALE(64, "[<sp> <recipient name>] (Send to terminal)"), |
"XSEM":LOCALE(65, "[<sp> <recipient name>] (Send, mail if can\'t)"), |
"XMAS":LOCALE(66, "[<sp> <recipient name>] (Mail and send)"), |
|
|
"BYE":LOCALE(67, "(Logout)"), |
"BYTE":LOCALE(68, "<sp> <bits> (Byte size)"), |
"SOCK":LOCALE(69, "<sp> host-socket (Data socket)"), |
|
#if 0 |
|
"MLTO":LOCALE(70, "<sp> <recipient name> (Initiate mail to user)"), |
"FROM":LOCALE(71, "<sp> <sender name> (Mail from)"), |
"MTYP":LOCALE(72, "<sp> [ U | O | L ] (Mail type)"), |
"RECO":LOCALE(73, "[<sp> <mail unique id>] (Mail record)"), |
#if 0 |
|
"AUTH":LOCALE(74, "<sp> <author id> (Mail author)"), |
#endif |
"TITL":LOCALE(75, "<sp> <title> (Mail title/subject)"), |
"ACKN":LOCALE(76, "(Mail acknowledge)"), |
"TEXT":LOCALE(77, "(Mail text)"), |
"FILE":LOCALE(78, "<sp> <filename> (Mail file)"), |
"CITA":LOCALE(79, "<sp> <file name> (Mail citation)"), |
#endif |
|
|
"MLFL":LOCALE(80, "(Mail file)"), |
]); |
|
private mapping(string:string|Locale.DeferredLocale) site_help = ([ |
"CHMOD":LOCALE(81, "<sp> mode <sp> file"), |
"UMASK":LOCALE(82, "<sp> mode"), |
"PRESTATE":LOCALE(83, "<sp> prestate"), |
]); |
|
private mapping(string:string|Locale.DeferredLocale) opts_help = ([ |
"MLST":LOCALE(84, "<sp> <fact-list>"), |
]); |
|
private constant modes = ([ |
"A":"ASCII", |
"E":"EBCDIC", |
"I":"BINARY", |
"L":"LOCAL", |
]); |
|
private int time_touch = time(); |
|
private object(ADT.Queue) to_send = ADT.Queue(); |
|
private int end_marker = 0; |
|
void touch_me() |
{ |
time_touch = time(); |
} |
|
private string write_cb() |
{ |
touch_me(); |
|
if (to_send->is_empty()) { |
|
DWRITE("FTP2: write_cb(): Empty send queue.\n"); |
|
::set_write_callback(0); |
if (end_marker) { |
DWRITE("FTP2: write_cb(): Sending EOF.\n"); |
return(0); |
} |
DWRITE("FTP2: write_cb(): Sending \"\"\n"); |
return(""); |
} else { |
string|int s = to_send->get(); |
|
#if constant(SSL.File) |
if (s == 1) { |
DWRITE("FTP2: write_cb(): STARTTLS.\n"); |
|
|
|
|
|
if (!fd->renegotiate) { |
fd = SSL.File(fd, port_obj->ctx); |
master_session->my_fd = fd; |
fd->accept(); |
} |
|
::set_write_callback(write_cb); |
return ""; |
} else if (s == 2) { |
DWRITE("FTP2: write_cb(): ENDTLS.\n"); |
|
if (fd->renegotiate && |
!has_prefix(sprintf("%O", port_obj), "SSLProtocol")) { |
|
master_session->my_fd = fd = fd->shutdown(); |
|
if (fd) { |
|
::set_write_callback(write_cb); |
} |
} |
return ""; |
} |
#endif |
|
DWRITE("FTP2: write_cb(): Sending %O.\n", s); |
|
if ((to_send->is_empty()) && (!end_marker)) { |
::set_write_callback(0); |
} else if (stringp(to_send->peek())) { |
|
::set_write_callback(write_cb); |
} |
return(s); |
} |
} |
|
int(0..1) busy; |
|
#ifdef FTP_USE_HANDLER_THREADS |
#define next_cmd() call_out(low_next_cmd, 0) |
#else |
#define low_next_cmd() next_cmd() |
#endif |
|
void low_send(int code, array(string) data, int|void enumerate_all) |
{ |
DWRITE("FTP2: low_send(%d, %O)\n", code, data); |
|
if (!data || end_marker) { |
end_marker = 1; |
::set_write_callback(write_cb); |
return; |
} |
|
string s; |
int i; |
if (sizeof(data) > 1) { |
data[0] = sprintf("%03d-%s\r\n", code, data[i]); |
for (i = sizeof(data)-1; --i; ) { |
if (enumerate_all) { |
data[i] = sprintf("%03d-%s\r\n", code, data[i]); |
} else { |
data[i] = " " + data[i] + "\r\n"; |
} |
} |
} |
data[sizeof(data)-1] = sprintf("%03d %s\r\n", code, data[sizeof(data)-1]); |
s = data * ""; |
|
if (sizeof(s)) { |
if (to_send->is_empty()) { |
to_send->put(string_to_utf8(s)); |
::set_write_callback(write_cb); |
} else { |
to_send->put(string_to_utf8(s)); |
} |
} else { |
DWRITE("FTP2: send(): Nothing to send!\n"); |
} |
} |
|
void send(int code, array(string) data, int|void enumerate_all) |
{ |
DWRITE("FTP2: send(%d, %O)\n", code, data); |
|
low_send(code, data, enumerate_all); |
|
if (code >= 200) { |
|
busy = 0; |
next_cmd(); |
} |
} |
|
private RequestID master_session; |
|
private string dataport_addr; |
private int dataport_port; |
|
private string mode = "A"; |
|
private string cwd = "/"; |
|
private User auth_user; |
|
|
private string user; |
private string password; |
private int logged_in; |
|
private object curr_pipe; |
private int restart_point; |
|
private multiset|int allowed_shells = 0; |
|
|
|
|
|
string local_addr; |
int local_port; |
string e_mode = "1"; |
|
|
roxen.Protocol port_obj; |
|
|
|
|
|
protected string format_langlist() |
{ |
array(string) langs = Locale.list_languages("prot_ftp") + ({}); |
string current = roxen.get_locale(); |
|
foreach(langs; int i; string lang) { |
if (lang == current) { |
langs[i] = current + "*"; |
current = 0; |
break; |
} |
} |
|
if (current) { |
langs += ({ current + "*" }); |
} |
|
return sort(langs) * ";"; |
} |
|
protected void restore_locale() |
{ |
string lang = master_session->misc["accept-language"]; |
if (lang) { |
roxen.set_locale(lang); |
} else { |
roxen.set_locale(); |
} |
} |
|
|
|
|
|
private int check_shell(string shell) |
{ |
|
|
if (port_obj->query_option("shells") != "") { |
|
|
|
|
|
if (!allowed_shells) { |
object(Stdio.File) file = Stdio.File(); |
|
if (file->open(port_obj->query_option("shells"), "r")) { |
allowed_shells = |
aggregate_multiset(@(Array.map(file->read(0x7fffffff)/"\n", |
lambda(string line) { |
return(((((line/"#")[0])/"") - |
({" ", "\t"}))*""); |
} )-({""}))); |
DWRITE("ftp.pike: allowed_shells: %O\n", allowed_shells); |
} else { |
report_debug("ftp.pike: Failed to open shell database (%O)\n", |
port_obj->query_option("shells")); |
return 0; |
} |
} |
return(allowed_shells[shell]); |
} |
return 1; |
} |
|
private string fix_path(string s) |
{ |
mixed err = catch { s = utf8_to_string(s); }; |
if (String.width(s) > 8) { |
|
|
s = Unicode.normalize(s, "NFC"); |
} |
if (!sizeof(s)) { |
if (cwd[-1] == '/') { |
return(cwd); |
} else { |
return(cwd + "/"); |
} |
} else if (s[0] == '~') { |
return(combine_path("/", s)); |
} else if (s[0] == '/') { |
return(simplify_path(s)); |
} else { |
return(combine_path(cwd, s)); |
} |
} |
|
|
|
|
private object pasv_port; |
private function(object, mixed ...:void) pasv_callback; |
private mixed pasv_args; |
private array(object) pasv_accepted = ({}); |
|
void pasv_accept_callback(mixed id) |
{ |
DWRITE("FTP: pasv_accept_callback(%O)...\n", id); |
touch_me(); |
|
if(pasv_port) { |
object fd = pasv_port->accept(); |
if(fd) { |
|
|
|
|
|
array(string) remote = (fd->query_address()||"? ?")/" "; |
#ifdef FD_DEBUG |
mark_fd(fd->query_fd(), |
"ftp communication: -> "+remote[0]+":"+remote[1]); |
#endif |
if(pasv_callback) { |
pasv_callback(fd, "", @pasv_args); |
pasv_callback = 0; |
} else { |
pasv_accepted += ({ fd }); |
} |
} |
} |
} |
|
private void ftp_async_accept(function(object,mixed ...:void) fun, |
mixed ... args) |
{ |
DWRITE("FTP: async_accept(%O, %@O)...\n", fun, args); |
touch_me(); |
|
if (sizeof(pasv_accepted)) { |
fun(pasv_accepted[0], "", @args); |
pasv_accepted = pasv_accepted[1..]; |
} else { |
pasv_callback = fun; |
pasv_args = args; |
} |
} |
|
|
|
|
|
private void ftp_async_connect(function(object,string,mixed ...:void) fun, |
mixed ... args) |
{ |
DWRITE("FTP: async_connect(%O, %@O)...\n", fun, args); |
|
|
|
if (!dataport_addr) { |
DWRITE("FTP: No dataport specified.\n"); |
fun(0, "", @args); |
return; |
} |
|
object(Stdio.File)|object(SSL.File) f = Stdio.File(); |
|
|
|
|
object privs; |
#ifndef FTP2_USE_ANY_SOURCE_PORT |
if(local_port-1 < 1024 && geteuid()) |
privs = Privs("FTP: Opening the data connection on " + local_addr + |
":" + (local_port-1) + "."); |
|
if(!f->open_socket(local_port-1, local_addr)) |
{ |
privs = 0; |
DWRITE("FTP: socket(%d, %O) failed. Trying with any port.\n", |
local_port-1, local_addr); |
|
#endif |
if(!f->open_socket(0, local_addr)) |
{ |
DWRITE("FTP: socket(0, %O) failed. " |
"Trying with any port, any ip.\n", local_addr); |
if (!f->open_socket()) { |
DWRITE("FTP: socket() failed. Out of sockets?\n"); |
fun(0, 0, @args); |
destruct(f); |
return; |
} |
} |
#ifndef FTP2_USE_ANY_SOURCE_PORT |
} |
privs = 0; |
#endif |
|
Stdio.File raw_connection = f; |
|
f->set_nonblocking(lambda(mixed ignored, string data) { |
DWRITE("FTP: async_connect ok. Got data.\n"); |
f->set_nonblocking(0,0,0,0,0); |
fun(f, data, @args); |
}, |
lambda(mixed ignored) { |
DWRITE("FTP: async_connect ok.\n"); |
f->set_nonblocking(0,0,0,0,0); |
fun(f, "", @args); |
}, |
lambda(mixed ignored) { |
DWRITE("FTP: connect_and_send failed: %s (%d)\n", |
strerror(f->errno()), f->errno()); |
destruct(f); |
fun(0, 0, @args); |
}, |
lambda(mixed ignored) { |
DWRITE("FTP: connect_and_send failed (oob): %s (%d)\n", |
strerror(f->errno()), f->errno()); |
destruct(f); |
fun(0, 0, @args); |
}); |
|
#ifdef FD_DEBUG |
mark_fd(raw_connection->query_fd(), |
sprintf("ftp communication: %s:%d -> %s:%d", |
local_addr, local_port - 1, |
dataport_addr, dataport_port)); |
#endif |
|
if(mixed err = catch{ |
if (!(raw_connection->connect(dataport_addr, dataport_port))) { |
DWRITE("FTP: connect(%O, %O) failed with: %s!\n" |
"FTP: local_addr: %O:%O (%O)\n", |
dataport_addr, dataport_port, |
strerror(raw_connection->errno()), |
local_addr, local_port-1, |
raw_connection->is_open() && raw_connection->query_address(1)); |
destruct(f); |
fun(0, 0, @args); |
return; |
} |
}) { |
DWRITE("FTP: Illegal IP address (%s:%d) in async connect.\n", |
dataport_addr||"", dataport_port); |
DWRITE("FTP: %s\n", describe_backtrace(err)); |
destruct(f); |
fun(0, 0, @args); |
return; |
} |
} |
|
|
|
|
|
enum SSLMode { |
SSL_NONE = 0, |
SSL_ACTIVE = 1, |
SSL_PASSIVE = 2, |
SSL_ALL = 3, |
}; |
|
|
|
|
SSLMode use_ssl; |
|
private void send_done_callback(array(object) args) |
{ |
DWRITE("FTP: send_done_callback()\n"); |
|
object fd = args[0]; |
object session = args[1]; |
|
if(fd) |
{ |
|
if (fd->set_blocking) { |
fd->set_blocking(); |
} |
call_out(fd->close, 0); |
fd = 0; |
|
} |
curr_pipe = 0; |
|
if (session && session->file) { |
session->conf->log(session->file, session); |
session->file = 0; |
} |
destruct(session); |
send(226, ({ LOCALE(85, "Transfer complete.") })); |
} |
|
private mapping|array|object stat_file(string fname, |
object|void session) |
{ |
mapping file; |
|
session = RequestID2(session || master_session); |
session->method = "STAT"; |
session->not_query = fname; |
|
foreach(conf->first_modules(), function funp) { |
if ((file = funp(session))) { |
break; |
} |
} |
|
if (!file) { |
fname = replace(fname, "//", "/"); |
file = conf->stat_file(fname, session); |
} |
destruct(session); |
return file; |
} |
|
private int expect_argument(string cmd, string args) |
{ |
if ((< "", 0 >)[args]) { |
send(504, ({ sprintf(LOCALE(86, "Syntax: %s %s"), cmd, cmd_help[cmd]) })); |
return 0; |
} |
return 1; |
} |
|
private void send_error(string cmd, string f, mapping file, |
object session) |
{ |
switch(file && file->error) { |
case 301: |
case 302: |
if (file->extra_heads && file->extra_heads->Location) { |
send(504, ({ sprintf(LOCALE(87, "'%s': %s: Redirect to %O."), |
cmd, f, file->extra_heads->Location) })); |
} else { |
send(504, ({ sprintf(LOCALE(88, "'%s': %s: Redirect."), cmd, f) })); |
} |
break; |
case 401: |
send(530, ({ sprintf(LOCALE(89, "'%s': %s: Access denied."), |
cmd, f) })); |
break; |
case 403: |
send(451, ({ sprintf(LOCALE(90, "'%s': %s: Forbidden."), |
cmd, f) })); |
break; |
case 405: |
send(550, ({ sprintf(LOCALE(91, "'%s': %s: Method not allowed."), |
cmd, f) })); |
break; |
case 500: |
send(451, ({ sprintf(LOCALE(93, "'%s': Requested action aborted: " |
"local error in processing."), cmd) })); |
break; |
default: |
if (!file) { |
file = ([ "error":404 ]); |
} |
send(550, ({ sprintf(LOCALE(94, "'%s': %s: No such file or directory."), |
cmd, f) })); |
break; |
} |
session->conf->log(file, session); |
} |
|
private void send_welcome() |
{ |
string s = port_obj->query_option("FTPWelcome"); |
|
s = replace(s, |
({ "$roxen_version", "$roxen_build", "$full_version", |
"$pike_version", "$ident", |
"$site", }), |
({ roxen->roxen_ver, roxen->roxen_build, |
roxen->real_version, version(), roxen->version(), |
conf->query_name(), })); |
|
send(220, s/"\n", 1); |
} |
|
private mapping open_file(string fname, object session, string cmd) |
{ |
object|array|mapping file; |
|
file = stat_file(fname, session); |
|
|
|
session->not_query = fname; |
|
if (objectp(file) || arrayp(file)) { |
array|object st = file; |
file = 0; |
if (st && (st[1] < 0) && !((<"RMD", "XRMD", "CHMOD">)[cmd])) { |
send(550, ({ sprintf(LOCALE(95, "%s: not a plain file."), fname) })); |
return 0; |
} |
mixed err; |
if ((err = catch(file = conf->get_file(session)))) { |
report_error("FTP: Error opening file \"%s\"\n" |
"%s\n", fname, describe_backtrace(err)); |
send(550, ({ sprintf(LOCALE(96, "%s: Error, can't open file."), fname) })); |
return 0; |
} |
} else if ((< "STOR", "APPE", "MKD", "XMKD", "MOVE" >)[cmd]) { |
mixed err; |
if ((err = catch(file = conf->get_file(session)))) { |
report_error("FTP: Error opening file \"%s\"\n" |
"%s\n", fname, describe_backtrace(err)); |
send(550, ({ sprintf(LOCALE(96, "%s: Error, can't open file."), fname) })); |
return 0; |
} |
} |
|
|
|
session->file = file; |
|
if (!file || (file->error && (file->error >= 300))) { |
DWRITE("FTP: open_file(\"%s\") failed: %O\n", fname, file); |
send_error(cmd, fname, file, session); |
return 0; |
} |
|
|
|
if (file->data && String.width(file->data) > 8) |
file->data = session->output_encode(file->data, 0)[1]; |
|
file->full_path = fname; |
file->request_start = time(1); |
|
if (!file->len) { |
if (file->data) { |
file->len = sizeof(file->data); |
} |
if (objectp(file->file)) { |
file->len += file->file->stat()[1]; |
} |
} |
|
return file; |
} |
|
private void connected_to_send(object fd, string ignored, |
mapping file, object session) |
{ |
DWRITE("FTP: connected_to_send(%O, %O, %O, X)\n", fd, ignored, file); |
|
touch_me(); |
|
restore_locale(); |
|
if(!file->len) |
file->len = file->data?(stringp(file->data)?strlen(file->data):0):0; |
|
if (!file->mode) { |
file->mode = mode; |
} |
|
if(fd) |
{ |
if (file->len) { |
send(150, ({ sprintf(LOCALE(97, "Opening %s data connection for %s (%d bytes)."), |
modes[file->mode], file->full_path, file->len) })); |
} else { |
send(150, ({ sprintf(LOCALE(98, "Opening %s mode data connection for %s"), |
modes[file->mode], file->full_path) })); |
} |
|
SSLMode ssl_mask = SSL_ACTIVE; |
if (pasv_port) ssl_mask = SSL_PASSIVE; |
|
#if constant(SSL.File) |
if (use_ssl & ssl_mask) { |
DWRITE("FTP: Initiating SSL/TLS connection.\n"); |
|
|
|
|
|
|
|
|
|
fd = SSL.File(fd, port_obj->ctx); |
fd->accept(); |
DWRITE("FTP: Created an sslfile: %O\n", fd); |
} |
#endif |
} |
else |
{ |
send(425, ({ LOCALE(99, "Can't build data connect: Connection refused.") })); |
destruct(session); |
return; |
} |
switch(file->mode) { |
case "A": |
if (file->data) { |
file->data = replace(file->data, |
({ "\r\n", "\n", "\r" }), |
({ "\r\n", "\r\n", "\r\n" })); |
} |
if(objectp(file->file) && file->file->set_nonblocking) |
{ |
|
|
|
file->file = ToAsciiWrapper(file->file, 0, this_object()); |
} |
break; |
case "E": |
|
if (file->data) { |
Charset.Encoder conv = Charset.encoder("EBCDIC-US", ""); |
file->data = conv->feed(file->data)->drain(); |
} |
if(objectp(file->file) && file->file->set_nonblocking) |
{ |
|
|
|
|
file->file = ToEBCDICWrapper(file->file, 0, this_object()); |
} |
break; |
default: |
|
|
if (objectp(file->file) && file->file->set_nonblocking) { |
file->file = BinaryWrapper(file->file, 0, this_object()); |
} |
break; |
} |
|
#ifndef DISABLE_FTP_THROTTLING |
mapping throttle=session->throttle||([]); |
object pipe; |
if ( conf && |
((throttle->doit && conf->query("req_throttle")) || |
conf->throttler |
) ) { |
|
pipe=((program)"slowpipe")(); |
} else { |
|
pipe=((program)"fastpipe")(); |
throttle->doit=0; |
} |
if (throttle->doit) { |
throttle->rate=max(throttle->rate, |
conf->query("req_throttle_min")); |
pipe->throttle(throttle->rate, |
(int)(throttle->rate* |
conf->query("req_throttle_depth_mult")), |
0); |
} |
if (conf && conf->throttler) { |
pipe->assign_throttler(conf->throttler); |
} |
#else |
object pipe=((program)"fastpipe")(); |
#endif |
|
pipe->set_done_callback(send_done_callback, ({ fd, session }) ); |
master_session->file = session->file = file; |
if(stringp(file->data)) { |
pipe->write(file->data); |
} |
if(file->file) { |
file->file->set_blocking(); |
pipe->input(file->file, file->len); |
} |
curr_pipe = pipe; |
pipe->output(fd); |
} |
|
private void connected_to_receive(object fd, string data, string args) |
{ |
DWRITE("FTP: connected_to_receive(X, %O, %O)\n", data, args); |
|
touch_me(); |
|
restore_locale(); |
|
if (fd) { |
send(150, ({ sprintf(LOCALE(100, "Opening %s mode data connection for %s."), |
modes[mode], args) })); |
|
SSLMode ssl_mask = SSL_ACTIVE; |
if (pasv_port) ssl_mask = SSL_PASSIVE; |
|
#if constant(SSL.File) |
if (use_ssl & ssl_mask) { |
DWRITE("FTP: Initiating SSL/TLS connection.\n"); |
|
fd = SSL.File(fd, port_obj->ctx); |
fd->accept(); |
DWRITE("FTP: Created an sslfile: %O\n", fd); |
} |
#endif |
} else { |
send(425, ({ LOCALE(99, "Can't build data connect: Connection refused.") })); |
return; |
} |
|
data = data && sizeof(data) && data; |
|
switch(mode) { |
case "A": |
fd = FromAsciiWrapper(fd, data, this_object()); |
break; |
case "E": |
fd = FromEBCDICWrapper(fd, data, this_object()); |
return; |
default: |
|
fd = BinaryWrapper(fd, data, this_object()); |
break; |
} |
|
RequestID session = RequestID2(master_session); |
session->method = "PUT"; |
session->my_fd = PutFileWrapper(fd, session, this_object()); |
session->misc->len = 0x7fffffff; |
|
mapping file; |
if (file = open_file(args, session, "STOR")) { |
if (!(file->pipe)) { |
if (fd) { |
BACKEND_CLOSE(fd); |
} |
switch(file->error) { |
case 401: |
send(530, ({ sprintf(LOCALE(101, "%s: Need account for storing files."), args)})); |
break; |
case 413: |
send(550, ({ sprintf(LOCALE(102, "%s: Quota exceeded."), args) })); |
break; |
case 501: |
send(502, ({ sprintf(LOCALE(103, "%s: Command not implemented."), args) })); |
break; |
default: |
send(550, ({ sprintf(LOCALE(104, "%s: Error opening file."), args) })); |
break; |
} |
session->conf->log(file, session); |
destruct(session); |
return; |
} |
master_session->file = file; |
} else { |
|
if (fd) { |
BACKEND_CLOSE(fd); |
} |
destruct(session); |
} |
} |
|
private void discard_data_connection() { |
if(pasv_port && sizeof(pasv_accepted)) |
pasv_accepted = pasv_accepted[1..]; |
} |
|
private void connect_and_send(mapping file, object session) |
{ |
DWRITE("FTP: connect_and_send(%O)\n", file); |
|
if (pasv_port) { |
ftp_async_accept(connected_to_send, file, session); |
} else { |
ftp_async_connect(connected_to_send, file, session); |
} |
} |
|
private void connect_and_receive(string arg) |
{ |
DWRITE("FTP: connect_and_receive(\"%s\")\n", arg); |
|
if (pasv_port) { |
ftp_async_accept(connected_to_receive, arg); |
} else { |
ftp_async_connect(connected_to_receive, arg); |
} |
} |
|
|
|
|
|
|
array(string) my_combine_path_array(array(string) base, string part) |
{ |
if ((part == ".") || (part == "")) { |
if ((part == "") && (!sizeof(base))) { |
return(({""})); |
} else { |
return(base); |
} |
} else if ((part == "..") && sizeof(base) && |
(base[-1] != "..") && (base[-1] != "")) { |
base[-1] = part; |
return(base); |
} else { |
return(base + ({ part })); |
} |
} |
|
private string my_combine_path(string base, string part) |
{ |
if ((sizeof(part) && (part[0] == '/')) || |
(sizeof(base) && (base[0] == '/'))) { |
return(combine_path(base, part)); |
} |
|
int i; |
array(string) arr = ((base/"/") + (part/"/")) - ({ ".", "" }); |
foreach(arr, string part) { |
if ((part == "..") && i && (arr[i-1] != "..")) { |
i--; |
} else { |
arr[i++] = part; |
} |
} |
if (i) { |
return(arr[..i-1]*"/"); |
} else { |
return(""); |
} |
} |
|
private constant IFS = (<" ", "\t">); |
private constant Quote = (< "\'", "\"", "\`", "\\" >); |
private constant Specials = IFS|Quote; |
|
private array(string) split_command_line(string cmdline) |
{ |
|
int need_quoting; |
foreach(indices(Quote), string c) { |
if (need_quoting = (search(cmdline, c) >= 0)) { |
break; |
} |
} |
if (!need_quoting) { |
|
return ((replace(cmdline, "\t", " ")/" ") - ({ "" })); |
} |
|
array(string) res = ({}); |
string arg = 0; |
int argstart = 0; |
int i; |
for(i=0; i < sizeof(cmdline); i++) { |
string c; |
if (Specials[c = cmdline[i..i]]) { |
if (argstart < i) { |
arg = (arg || "") + cmdline[argstart..i-1]; |
} |
switch(c) { |
case "\"": |
case "\'": |
case "\`": |
|
int j = search(cmdline, c, i+1); |
if (j == -1) { |
|
|
j = sizeof(cmdline); |
} |
arg = (arg || "") + cmdline[i+1..j-1]; |
i = j; |
break; |
case "\\": |
i++; |
arg += cmdline[i..i]; |
break; |
case " ": |
case "\t": |
|
if (arg) { |
res += ({ arg }); |
arg = 0; |
} |
break; |
} |
argstart = i+1; |
} |
} |
if (argstart < i) { |
arg = (arg || "") + cmdline[argstart..]; |
} |
if (arg) { |
res += ({ arg }); |
} |
return res; |
} |
|
private array(string) glob_expand_command_line(string cmdline) |
{ |
DWRITE("glob_expand_command_line(\"%s\")\n", cmdline); |
|
array(string|array(string)) args = split_command_line(cmdline); |
|
int index; |
|
for(index = 0; index < sizeof(args); index++) { |
|
|
|
|
if (replace(args[index], ({"*", "?"}), ({ "", "" })) != args[index]) { |
|
|
|
array(string|array(string)) matches = ({ ({ }) }); |
multiset(string) paths; |
int i; |
foreach(my_combine_path("", args[index])/"/", string part) { |
paths = (<>); |
if (replace(part, ({"*", "?"}), ({ "", "" })) != part) { |
|
array(array(string)) new_matches = ({}); |
foreach(matches, array(string) path) { |
array(string) dir; |
RequestID id = RequestID2(master_session); |
id->method = "LIST"; |
dir = id->conf->find_dir(combine_path(cwd, path*"/")+"/", id); |
if (dir && sizeof(dir)) { |
dir = glob(part, dir); |
if ((< '*', '?' >)[part[0]]) { |
|
dir = Array.filter(dir, lambda(string f) { |
return (sizeof(f) && (f[0] != '.')); |
}); |
} |
foreach(sort(dir), string f) { |
array(string) arr = my_combine_path_array(path, f); |
string p = arr*"/"; |
if (!paths[p]) { |
paths[p] = 1; |
new_matches += ({ arr }); |
} |
} |
} |
destruct(id); |
} |
matches = new_matches; |
} else { |
|
|
for(i=0; i<sizeof(matches); i++) { |
matches[i] = my_combine_path_array(matches[i], part); |
string path = matches[i]*"/"; |
if (paths[path]) { |
matches[i] = 0; |
} else { |
paths[path] = 1; |
} |
} |
matches -= ({ 0 }); |
} |
if (!sizeof(matches)) { |
break; |
} |
} |
if (sizeof(matches)) { |
|
for (i=0; i < sizeof(matches); i++) { |
matches[i] *= "/"; |
} |
|
matches = Array.filter(matches, |
lambda(string short, string cwd, |
object m_id) { |
object id = RequestID2(m_id); |
id->method = "LIST"; |
id->not_query = combine_path(cwd, short); |
mapping res = |
id->conf->stat_file(id->not_query, id); |
destruct(id); |
return res; |
}, cwd, master_session); |
if (sizeof(matches)) { |
args[index] = matches; |
} |
} |
} |
if (stringp(args[index])) { |
|
args[index] = ({ my_combine_path("", args[index]) }); |
} |
} |
return(args * ({})); |
} |
|
|
|
|
|
private constant ls_options = ({ |
({ ({ "-A", "--almost-all" }), LS_FLAG_A, |
"do not list implied . and .." }), |
({ ({ "-a", "--all" }), LS_FLAG_a|LS_FLAG_A, |
"do not hide entries starting with ." }), |
({ ({ "-b", "--escape" }), LS_FLAG_b, |
"print octal escapes for nongraphic characters" }), |
({ ({ "-C" }), LS_FLAG_C, |
"list entries by columns" }), |
({ ({ "-d", "--directory" }), LS_FLAG_d, |
"list directory entries instead of contents" }), |
({ ({ "-F", "--classify" }), LS_FLAG_F, |
"append a character for typing each entry"}), |
({ ({ "-f" }), LS_FLAG_a|LS_FLAG_A|LS_FLAG_U, |
"do not sort, enable -aU, disable -lst" }), |
({ ({ "-G", "--no-group" }), LS_FLAG_G, |
"inhibit display of group information" }), |
({ ({ "-g" }), 0, |
"(ignored)" }), |
({ ({ "-h", "--help" }), LS_FLAG_h, |
"display this help and exit" }), |
({ ({ "-k", "--kilobytes" }), 0, |
"use 1024 blocks (ignored, always done anyway)" }), |
({ ({ "-L", "--dereference" }), 0, |
"(ignored)" }), |
({ ({ "-l" }), LS_FLAG_l, |
"use a long listing format" }), |
({ ({ "-m" }), LS_FLAG_m, |
"fill width with a comma separated list of entries" }), |
({ ({ "-n", "--numeric-uid-gid" }), LS_FLAG_n, |
"list numeric UIDs and GIDs instead of names" }), |
({ ({ "-o" }), LS_FLAG_l|LS_FLAG_G, |
"use long listing format without group info" }), |
({ ({ "-Q", "--quote-name" }), LS_FLAG_Q, |
"enclose entry names in double quotes" }), |
({ ({ "-R", "--recursive" }), LS_FLAG_R, |
"list subdirectories recursively" }), |
({ ({ "-r", "--reverse" }), LS_FLAG_r, |
"reverse order while sorting" }), |
({ ({ "-S" }), LS_FLAG_S, |
"sort by file size" }), |
({ ({ "-s", "--size" }), LS_FLAG_s, |
"print block size of each file" }), |
({ ({ "-t" }), LS_FLAG_t, |
"sort by modification time; with -l: show mtime" }), |
({ ({ "-U" }), LS_FLAG_U, |
"do not sort; list entries in directory order" }), |
({ ({ "-v", "--version" }), LS_FLAG_v, |
"output version information and exit" }), |
}); |
|
private array(array(string)|string|int) |
ls_getopt_args = Array.map(ls_options, |
lambda(array(array(string)|int|string) entry) { |
return({ entry[1], Getopt.NO_ARG, entry[0] }); |
}); |
|
private string ls_help(string ls) |
{ |
return sprintf("Usage: %s [OPTION]... [FILE]...\n" |
"List information about the FILEs " |
"(the current directory by default).\n" |
"Sort entries alphabetically if none " |
"of -cftuSUX nor --sort.\n" |
"\n" |
"%@s\n", |
ls, |
Array.map(ls_options, |
lambda(array entry) { |
if (sizeof(entry[0]) > 1) { |
return(sprintf(" %s, %-22s %s\n", |
@(entry[0]), entry[2])); |
} |
return(sprintf(" %s " |
" %s\n", |
entry[0][0], entry[2])); |
})); |
} |
|
void call_ls(array(string) argv) |
{ |
|
array options; |
mixed err; |
|
if (err = catch { |
options = Getopt.find_all_options(argv, ls_getopt_args, 1, 1); |
}) { |
send(550, (argv[0]+": "+err[0])/"\n"); |
discard_data_connection(); |
return; |
} |
|
int flags; |
|
foreach(options, array(int) option) { |
flags |= option[0]; |
} |
|
if (err = catch { |
argv = Getopt.get_args(argv, 1, 1); |
}) { |
send(550, (argv[0] + ": " + err[0])/"\n"); |
discard_data_connection(); |
return; |
} |
|
if (sizeof(argv) == 1) { |
argv += ({ "./" }); |
} |
|
RequestID session = RequestID2(master_session); |
session->method = "LIST"; |
|
session->not_query = Array.map(argv[1..], fix_path)*" "; |
|
mapping file = ([]); |
|
|
|
|
|
if (mode != "E") { |
file->mode = "A"; |
} else { |
file->mode = "E"; |
} |
|
if (flags & LS_FLAG_v) { |
file->data = "ls - builtin_ls 1.1\n"; |
} else if (flags & LS_FLAG_h) { |
file->data = ls_help(argv[0]); |
} else { |
if (flags & LS_FLAG_d) { |
flags &= ~LS_FLAG_R; |
} |
if (flags & (LS_FLAG_f|LS_FLAG_C|LS_FLAG_m)) { |
flags &= ~LS_FLAG_l; |
} |
if (flags & LS_FLAG_C) { |
flags &= ~LS_FLAG_m; |
} |
|
file->file = LSFile(cwd, argv[1..], flags, session, |
file->mode, this_object()); |
|
#ifdef FTP_USE_HANDLER_THREADS |
|
|
file->file && file->file->fill_output_queue(); |
#endif |
} |
|
if (!file->full_path) { |
file->full_path = argv[0]; |
} |
session->file = file; |
connect_and_send(file, session); |
} |
|
|
|
|
|
constant supported_mlst_facts = (< |
"size", "type", "modify", "charset", "media-type", |
"unix.mode", "unix.atime", "unix.ctime", "unix.uid", "unix.gid", |
"unix.ownername", "unix.groupname", |
>); |
|
multiset(string) current_mlst_facts = (< |
"size", "type", "modify", "unix.mode", |
"unix.ownername", "unix.groupname", |
>); |
|
protected string format_factlist(multiset(string) all, |
multiset(string)|void selected) |
{ |
if (!selected) selected = (<>); |
|
string ret = ""; |
foreach(sort(indices(all)), string fact) { |
ret += fact; |
if (selected[fact]) { |
ret += "*"; |
} |
ret += ";"; |
} |
return ret; |
} |
|
string make_MDTM(int t) |
{ |
mapping lt = gmtime(t); |
return sprintf("%04d%02d%02d%02d%02d%02d", |
lt->year + 1900, lt->mon + 1, lt->mday, |
lt->hour, lt->min, lt->sec); |
} |
|
mapping(string:string) make_MLSD_facts(string f, mapping(string:array) dir, |
object session) |
{ |
array st = dir[f]; |
|
mapping(string:string) facts = ([]); |
|
|
|
if (st[1] >= 0) { |
facts->size = (string)st[1]; |
facts->type = "file"; |
if (current_mlst_facts["media-type"]) { |
string|array(string) ct = session->conf->type_from_filename(f); |
if (arrayp(ct)) { |
ct = (sizeof(ct) > 1) && ct[1]; |
} |
facts["media-type"] = ct || "application/octet-stream"; |
} |
} else { |
if (!current_mlst_facts->type) { |
if ((< "..", "." >)[f]) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return 0; |
} |
} else { |
facts->type = ([ "..":"pdir", ".":"cdir" ])[f] || "dir"; |
} |
} |
|
facts->modify = make_MDTM(st[3]); |
|
facts->charset = "8bit"; |
|
|
|
|
|
|
|
|
facts["unix.atime"] = make_MDTM(st[2]); |
facts["unix.ctime"] = make_MDTM(st[4]); |
|
|
if (stringp(st[5])) { |
facts["unix.ownername"] = st[5]; |
} else { |
facts["unix.ownername"] = name_from_uid(master_session, st[5]); |
} |
if (stringp(st[6])) { |
facts["unix.groupname"] = st[6]; |
} else if (!st[6]) { |
facts["unix.groupname"] = "wheel"; |
} else { |
facts["unix.groupname"] = (string)st[6]; |
} |
|
|
|
facts["unix.mode"] = sprintf("0%o", st[0]); |
if (intp(st[5])) { |
facts["unix.uid"] = sprintf("%d", st[5]); |
} |
if (intp(st[6])) { |
facts["unix.gid"] = sprintf("%d", st[6]); |
} |
|
|
|
return facts & current_mlst_facts; |
} |
|
string format_MLSD_facts(mapping(string:string) facts) |
{ |
if (!facts) return 0; |
return map(sort(indices(facts)), |
lambda(string s, mapping f) { |
return s + "=" + f[s] + ";"; |
}, facts) * ""; |
} |
|
string make_MLSD_fact(string f, mapping(string:array) dir, |
object session) |
{ |
string facts = format_MLSD_facts(make_MLSD_facts(f, dir, session)); |
if (!facts) return 0; |
return facts + " " + f; |
} |
|
void send_MLSD_response(mapping(string:array) dir, object session) |
{ |
dir = dir || ([]); |
|
array(string) entries = |
Array.map(indices(dir), make_MLSD_fact, dir, session) - ({ 0 }); |
|
session->file->data = sizeof(entries) ? entries * "\r\n" + "\r\n" : "" ; |
|
session->file->mode = "I"; |
connect_and_send(session->file, session); |
} |
|
void send_MLST_response(mapping(string:array) dir, object session) |
{ |
dir = dir || ([]); |
|
|
send(250,({ LOCALE(105, "OK") }) + |
Array.map(indices(dir), make_MLSD_fact, dir, session) + |
({ LOCALE(105, "OK") }) ); |
} |
|
|
|
|
|
int login() |
{ |
int session_limit = port_obj->query_option("ftp_user_session_limit"); |
|
if (session_limit > 0) { |
|
if (session_limit <= (port_obj->ftp_sessions[user])) { |
return 0; |
} |
|
if (logged_in) { |
report_error("Internal error in session-handler."); |
return 1; |
} |
|
DWRITE("FTP2: Increasing # of sessions for user %O\n", user); |
port_obj->ftp_sessions[user]++; |
} |
logged_in = (user != 0) || -1; |
|
return 1; |
} |
|
void logout() |
{ |
if (!logged_in) return; |
|
int session_limit = port_obj->query_option("ftp_user_session_limit"); |
|
if (session_limit > 0) { |
|
DWRITE("FTP2: Decreasing # of sessions for user %O\n", user); |
if ((--port_obj->ftp_sessions[user]) < 0) { |
port_obj->ftp_sessions[user] = 0; |
} |
} |
logged_in = 0; |
} |
|
int check_login() |
{ |
int session_limit = port_obj->query_option("ftp_user_session_limit"); |
|
if (session_limit <= 0) return 1; |
|
if (session_limit <= (port_obj->ftp_sessions[user])) { |
return 0; |
} |
|
return 1; |
} |
|
Configuration find_conf() |
|
|
{ |
Configuration conf; |
foreach(port_obj->sorted_urls, string url) { |
mapping(string:mixed) port_info = port_obj->urls[url]; |
if (!port_info) { |
continue; |
} |
if (!conf) { |
conf = port_info->conf; |
} |
if (port_info->conf->query("default_server")) { |
conf = port_info->conf; |
return conf; |
} |
} |
return conf; |
} |
|
Configuration find_conf_for_host(string hostname) |
{ |
if (!hostname) { |
return 0; |
} |
hostname = lower_case(hostname); |
foreach(port_obj->sorted_urls, string url) { |
mapping(string:mixed) port_info = port_obj->urls[url]; |
if (!port_info) { |
continue; |
} |
if (glob(lower_case(port_info->hostname) + "*", hostname)) { |
return port_info->conf; |
} |
} |
|
|
|
|
foreach(port_obj->sorted_urls, string url) { |
mapping(string:mixed) port_info = port_obj->urls[url]; |
if (!port_info) { |
continue; |
} |
if (has_prefix(lower_case(port_info->hostname), hostname)) { |
return port_info->conf; |
} |
} |
return 0; |
} |
|
|
|
|
|
|
int epsv_only; |
|
void ftp_REIN(string|int args) |
{ |
logout(); |
|
|
|
|
|
m_delete(master_session->misc, "home"); |
|
dataport_addr = 0; |
dataport_port = 0; |
mode = "A"; |
cwd = "/"; |
auth_user = 0; |
user = password = 0; |
curr_pipe = 0; |
restart_point = 0; |
logged_in = 0; |
roxen.set_locale(); |
m_delete(master_session->misc, "host"); |
m_delete(master_session->misc, "accept-language"); |
master_session->cached_url_base = 0; |
master_session->misc->pref_languages->languages = ({}); |
master_session->conf = conf = find_conf(); |
if (pasv_port) { |
destruct(pasv_port); |
pasv_port = 0; |
} |
if (args != 1) { |
|
low_send(220, ({ LOCALE(106, "Server ready for new user.") })); |
|
|
|
|
|
to_send->put(2); |
use_ssl = SSL_NONE; |
|
busy = 0; |
next_cmd(); |
} |
} |
|
void ftp_HOST(string args) |
{ |
if (!expect_argument("HOST", args)) return; |
|
if (logged_in) { |
|
|
|
|
|
send(503, ({ LOCALE(204, "HOST not allowed after login.") })); |
return; |
} |
|
if (args[0] == '[') { |
|
if (args[-1] != ']') { |
send(501, ({ LOCALE(205, "Invalid HOST syntax.") })); |
return; |
} |
} else if (has_value(args, ":")) { |
send(501, ({ LOCALE(205, "Invalid HOST syntax.") })); |
return; |
} |
|
|
|
|
|
|
|
|
|
|
if (!roxen.is_ip(args)) { |
args = lower_case(args); |
|
Configuration new_conf = find_conf_for_host(args); |
|
if (!new_conf) { |
send(504, ({ LOCALE(206, "Unknown host.") })); |
return; |
} |
master_session->conf = conf = new_conf; |
master_session->misc->host = args; |
master_session->cached_url_base = 0; |
|
|
if (!conf->inited) { |
conf->enable_all_modules(); |
} |
} |
|
send_welcome(); |
} |
|
void ftp_AUTH(string args) |
{ |
if (!expect_argument("AUTH", args)) return; |
|
args = upper_case(replace(args, ({ " ", "\t" }), ({ "", "" }))); |
|
|
|
|
|
|
|
if (!(< "TLS", "SSL", "SSL-C", "TLS-C", "SSL-P", "TLS-P" >)[args]) { |
|
|
|
send(504, ({ LOCALE(107, "Unknown authentication mechanism.") })); |
return; |
} |
if ((port_obj->query_option("require_starttls") < 0) || |
!port_obj->ctx) { |
|
|
|
send(534, ({ LOCALE(108, "TLS not configured.") })); |
return; |
} |
|
|
|
|
|
|
|
|
ftp_REIN(1); |
|
|
low_send(234, ({ LOCALE(109, "TLS enabled.") })); |
|
|
|
fd->set_read_callback(0); |
fd->set_close_callback(0); |
|
|
to_send->put(1); |
|
|
|
if (args == "TLS-P") { |
|
use_ssl = SSL_ALL; |
} else if ((args == "SSL") || (args == "SSL-P")) { |
|
|
|
|
|
|
|
|
|
use_ssl = SSL_ACTIVE; |
} |
|
|
busy = 0; |
next_cmd(); |
} |
|
void ftp_CCC(string args) |
{ |
if (!fd->renegotiate) { |
|
send(533, ({ LOCALE(110, "Command connection not protected.") })); |
return; |
} |
if (master_session->my_fd->renegotiate) { |
|
send(534, ({ LOCALE(111, "Not allowed for ftps.") })); |
return; |
} |
|
low_send(200, ({ LOCALE(112, "TLS disabled.") })); |
to_send->put(2); |
|
busy = 0; |
next_cmd(); |
} |
|
void ftp_USER(string args) |
{ |
logout(); |
|
auth_user = 0; |
user = args; |
password = 0; |
logged_in = 0; |
cwd = "/"; |
master_session->method = "LOGIN"; |
if ((< 0, "", "ftp", "anonymous" >)[user]) { |
master_session->not_query = "Anonymous"; |
user = 0; |
if (port_obj->query_option("anonymous_ftp")) { |
if (check_login()) { |
#if 0 |
send(200, ({ LOCALE(113, "Anonymous ftp, at your service") })); |
#else /* !0 */ |
|
send(331, ({ LOCALE(114, "Anonymous ftp accepted, send " |
"your complete e-mail address as password.") })); |
#endif /* 0 */ |
conf->log(([ "error":200 ]), master_session); |
} else { |
send(530, ({ |
sprintf(LOCALE(115, "Too many anonymous users (%d)."), |
port_obj->query_option("ftp_user_session_limit")) |
})); |
conf->log(([ "error":403 ]), master_session); |
} |
} else { |
send(530, ({ LOCALE(116, "Anonymous ftp disabled") })); |
conf->log(([ "error":403 ]), master_session); |
} |
} else { |
if (port_obj->ctx && !fd->renegotiate && |
(port_obj->query_option("require_starttls") == 1)) { |
conf->log(([ "error":403 ]), master_session); |
send(530, ({ LOCALE(117, "You need to AUTH TLS first.") })); |
|
return; |
} |
if (check_login()) { |
send(331, ({ sprintf(LOCALE(118, "Password required for %s."), user) })); |
master_session->not_query = user; |
conf->log(([ "error":407 ]), master_session); |
} else { |
|
send(530, ({ |
sprintf(LOCALE(119, "Concurrent session limit (%d) exceeded " |
"for user \"%s\"."), |
port_obj->query_option("ftp_user_session_limit"), user) |
})); |
conf->log(([ "error":403 ]), master_session); |
user = 0; |
return; |
} |
} |
} |
|
void ftp_PASS(string args) |
{ |
if (!user) { |
if (port_obj->query_option("anonymous_ftp")) { |
if (login()) { |
send(230, ({ LOCALE(120, "Guest login ok, access restrictions apply.") })); |
master_session->method = "LOGIN"; |
master_session->not_query = "Anonymous User:"+args; |
conf->log(([ "error":200 ]), master_session); |
logged_in = -1; |
} else { |
send(530, ({ |
sprintf(LOCALE(115, "Too many anonymous users (%d)."), |
port_obj->query_option("ftp_user_session_limit")) |
})); |
conf->log(([ "error":403 ]), master_session); |
} |
} else { |
send(503, ({ LOCALE(121, "Login with USER first.") })); |
} |
return; |
} |
|
if (port_obj->ctx && !fd->renegotiate && |
(port_obj->query_option("require_starttls") == 1)) { |
|
|
|
conf->log(([ "error":403 ]), master_session); |
send(530, ({ LOCALE(117, "You need to AUTH TLS first.") })); |
|
return; |
} |
|
logout(); |
|
password = args||""; |
args = "CENSORED_PASSWORD"; |
master_session->method = "LOGIN"; |
master_session->realauth = user + ":" + password; |
master_session->not_query = user; |
|
master_session->misc->user = user; |
master_session->misc->password = password; |
|
|
m_delete(master_session->misc, "home"); |
|
RequestID2 session = RequestID2 (master_session); |
|
auth_user = session->conf->authenticate(session); |
|
if (!auth_user) { |
if (!port_obj->query_option("guest_ftp")) { |
send(530, ({ sprintf(LOCALE(122, "User %s access denied."), user) })); |
conf->log(([ "error":401 ]), session); |
} else { |
|
string u = user; |
user = 0; |
if (login()) { |
send(230, ({ sprintf(LOCALE(123, "Guest user %s logged in."), u) })); |
logged_in = -1; |
conf->log(([ "error":200 ]), session); |
DWRITE("FTP: Guest-user: %O\n", session->realauth); |
} else { |
send(530, ({ |
sprintf(LOCALE(124, "Too many anonymous/guest users (%d)."), |
port_obj->query_option("ftp_user_session_limit")) |
})); |
conf->log(([ "error":403 ]), session); |
} |
} |
destruct (session); |
return; |
} |
|
|
|
|
|
|
|
|
|
|
|
{ |
mapping ses_misc = session->misc, mses_misc = master_session->misc; |
foreach (({"authenticated_user", "user", "password", "uid", "gid", |
"gecos", "home", "shell"}), string field) { |
mixed val = ses_misc[field]; |
if (zero_type (val)) |
m_delete (mses_misc, field); |
else |
mses_misc[field] = val; |
} |
} |
|
if (!port_obj->query_option("named_ftp") || |
!check_shell(auth_user->shell())) { |
send(530, ({ LOCALE(125, "You are not allowed to use named-ftp."), |
LOCALE(126, "Try using anonymous, or check /etc/shells") })); |
conf->log(([ "error":402 ]), session); |
auth_user = 0; |
destruct (session); |
return; |
} |
|
if (!login()) { |
send(530, ({ |
sprintf(LOCALE(127, "Too many concurrent sessions (limit is %d)."), |
port_obj->query_option("ftp_user_session_limit")) |
})); |
conf->log(([ "error":403 ]), session); |
destruct (session); |
return; |
} |
|
if (stringp(auth_user->homedir())) { |
|
string home = auth_user->homedir(); |
if ((home == "") || (home[-1] != '/')) { |
home += "/"; |
} |
|
|
master_session->misc->home = home; |
|
RequestID2 stat_session = RequestID2(master_session); |
stat_session->method = "STAT"; |
array(int)|object st = conf->stat_file(home, stat_session); |
destruct(stat_session); |
|
if (st && (st[1] < 0)) { |
cwd = home; |
} |
} |
|
logged_in = 1; |
send(230, ({ sprintf(LOCALE(128, "User %s logged in."), user) })); |
conf->log(([ "error":202 ]), session); |
destruct (session); |
} |
|
void ftp_CWD(string args) |
{ |
if (!expect_argument("CWD", args)) { |
return; |
} |
|
string ncwd = fix_path(args); |
|
if ((ncwd == "") || (ncwd[-1] != '/')) { |
ncwd += "/"; |
} |
|
object session = RequestID2(master_session); |
session->method = "CWD"; |
session->not_query = ncwd; |
|
array|object st = conf->stat_file(ncwd, session); |
ncwd = session->not_query; |
if (!st) { |
send(550, ({ sprintf(LOCALE(129, "%s: No such file or directory, or access denied."), |
ncwd) })); |
session->conf->log(session->file || ([ "error":404 ]), session); |
destruct(session); |
return; |
} |
|
if (!(< -2, -3 >)[st[1]]) { |
send(504, ({ sprintf(LOCALE(130, "%s: Not a directory."), ncwd) })); |
session->conf->log(([ "error":400 ]), session); |
destruct(session); |
return; |
} |
|
|
cwd = ncwd; |
|
array(string) reply = ({ sprintf(LOCALE(131, "Current directory is now %s."), cwd) }); |
|
|
session->method = "GET"; |
array(string) files = conf->find_dir(cwd, session); |
|
if (files) { |
files = reverse(sort(Array.filter(files, lambda(string s) { |
return(s[..5] == "README"); |
}))); |
foreach(files, string f) { |
array|object st = conf->stat_file(replace(cwd + f, "//", "/"), |
session); |
|
if (st && (st[1] >= 0)) { |
reply = ({ sprintf(LOCALE(132, "Please read the file %s."), f), |
sprintf(LOCALE(133, "It was last modified %s - %d days ago."), |
ctime(st[3]) - "\n", |
(time(1) - st[3])/86400), |
"" }) + reply; |
} |
} |
} |
string message; |
catch { |
message = conf->try_get_file(cwd + ".message", session); |
}; |
if (message) { |
reply = (message/"\n")+({ "" })+reply; |
} |
|
session->method = "CWD"; |
send(250, reply); |
session->conf->log(([ "error":200, "len":sizeof(reply*"\n") ]), session); |
destruct(session); |
} |
|
void ftp_XCWD(string args) |
{ |
ftp_CWD(args); |
} |
|
void ftp_CDUP(string args) |
{ |
ftp_CWD("../"); |
} |
|
void ftp_XCUP(string args) |
{ |
ftp_CWD("../"); |
} |
|
void ftp_QUIT(string args) |
{ |
send(221, ({ LOCALE(134, "Bye! It was nice talking to you!") })); |
send(0, 0); |
|
master_session->method = "QUIT"; |
master_session->not_query = user || "Anonymous"; |
conf->log(([ "error":200 ]), master_session); |
|
|
ftp_REIN(1); |
} |
|
void ftp_BYE(string args) |
{ |
ftp_QUIT(args); |
} |
|
void ftp_PBSZ(string args) |
{ |
if (!expect_argument("PBSZ", args)) return; |
|
if (!fd->renegotiate) { |
send(536, ({ LOCALE(135, "Only allowed for authenticated command connections.") })); |
return; |
} |
|
send(200, ({ "PBSZ=0" })); |
} |
|
void ftp_PROT(string args) |
{ |
if (!expect_argument("PROT", args)) return; |
|
args = upper_case(replace(args, ({ " ", "\t" }), ({ "", "" }))); |
|
SSLMode wanted; |
switch(args) { |
case "C": |
wanted = SSL_NONE; |
break; |
case "S": |
case "E": |
case "P": |
wanted = SSL_ALL; |
break; |
default: |
send(504, ({ sprintf(LOCALE(136, "Unknown protection level: %s"), args) })); |
return; |
} |
|
if (!fd->renegotiate) { |
send(536, ({ LOCALE(137, "Only supported over TLS.") })); |
return; |
} |
|
use_ssl = wanted; |
send(200, ({ LOCALE(105, "OK") })); |
} |
|
void ftp_PORT(string args) |
{ |
if (epsv_only) { |
send(530, ({ LOCALE(138, "'PORT': Method not allowed in EPSV ALL mode.") })); |
return; |
} |
|
int a, b, c, d, e, f; |
|
if (sscanf(args||"", "%d,%d,%d,%d,%d,%d", a, b, c, d, e, f)<6) |
send(501, ({ LOCALE(139, "I don't understand your parameters.") })); |
else { |
dataport_addr = sprintf("%d.%d.%d.%d", a, b, c, d); |
dataport_port = e*256 + f; |
|
if (pasv_port) { |
destruct(pasv_port); |
} |
send(200, ({ sprintf(LOCALE(140, "PORT command ok (%s port %d)"), |
dataport_addr, dataport_port) })); |
} |
} |
|
void ftp_EPRT(string args) |
{ |
|
|
if (epsv_only) { |
send(530, ({ LOCALE(141, "'EPRT': Method not allowed in EPSV ALL mode.") })); |
return; |
} |
|
if (sizeof(args) < 3) { |
send(501, ({ LOCALE(139, "I don't understand your parameters.") })); |
return; |
} |
|
string delimiter = args[0..0]; |
if ((delimiter[0] <= 32) || (delimiter[0] >= 127)) { |
send(501, ({ LOCALE(142, "Invalid delimiter.") })); |
} |
array(string) segments = args/delimiter; |
|
if (sizeof(segments) != 5) { |
send(501, ({ LOCALE(139, "I don't understand your parameters.") })); |
return; |
} |
if (!(<"1","2">)[segments[1]]) { |
send(522, ({ LOCALE(143, "Network protocol not supported, use (1 or 2)") })); |
return; |
} |
if (segments[1] == "1") { |
|
if ((sizeof(segments[2]/".") != 4) || |
sizeof(replace(segments[2], ".0123456789"/"", allocate(11, "")))) { |
send(501, ({ sprintf(LOCALE(144, "Bad IPv4 address: '%s'"), segments[2]) })); |
return; |
} |
} else { |
|
|
if (sizeof(replace(lower_case(segments[2]), ".:0123456789abcdef"/"", |
allocate(18, "")))) { |
send(501, ({ sprintf(LOCALE(145, "Bad IPv6 address: '%s'"), segments[2]) })); |
return; |
} |
} |
if ((((int)segments[3]) <= 0) || (((int)segments[3]) > 65535)) { |
send(501, ({ sprintf(LOCALE(146, "Bad port number: '%s'"), segments[3]) })); |
return; |
} |
dataport_addr = segments[2]; |
dataport_port = (int)segments[3]; |
|
if (pasv_port) { |
destruct(pasv_port); |
} |
send(200, ({ sprintf(LOCALE(147, "EPRT command ok (%s port %d)"), |
dataport_addr, dataport_port) })); |
} |
|
void ftp_PASV(string args) |
{ |
|
int min; |
int max; |
|
if (epsv_only) { |
send(530, ({ LOCALE(148, "'PASV': Method not allowed in EPSV ALL mode.") })); |
return; |
} |
|
if (e_mode != "1") { |
send(530, ({ LOCALE(149, "'PASV': Method not allowed on IPv6 connections.") })); |
return; |
} |
|
if(pasv_port) |
destruct(pasv_port); |
|
pasv_port = Stdio.Port(0, pasv_accept_callback, local_addr); |
|
|
|
int port=(int)((pasv_port->query_address()/" ")[1]); |
|
min = port_obj->query_option("passive_port_min"); |
max = port_obj->query_option("passive_port_max"); |
if ((port < min) || (port > max)) { |
if (max > 65535) max = 65535; |
if (min < 0) min = 0; |
for (port = min; port <= max; port++) { |
if (pasv_port->bind(port, pasv_accept_callback, local_addr)) { |
break; |
} |
} |
if (port > max) { |
destruct(pasv_port); |
pasv_port = 0; |
send(452, ({ LOCALE(150, "Requested action aborted: Out of ports.") })); |
return; |
} |
} |
send(227, ({ LOCALE(151, "Entering Passive Mode.") + |
sprintf(" (%s,%d,%d)", |
replace(local_addr, ".", ","), |
(port>>8), (port&0xff)) })); |
} |
|
void ftp_EPSV(string args) |
{ |
|
|
int min; |
int max; |
|
if (!(< 0, e_mode >)[args]) { |
if (lower_case(args) == "all") { |
epsv_only = 1; |
send(200, ({ LOCALE(152, "Entering EPSV ALL mode.") })); |
} else { |
send(522, ({ sprintf(LOCALE(153, "Network protocol not supported, use %s."), |
e_mode) })); |
} |
return; |
} |
if (pasv_port) |
destruct(pasv_port); |
|
pasv_port = Stdio.Port(0, pasv_accept_callback, local_addr); |
|
|
|
int port=(int)((pasv_port->query_address()/" ")[1]); |
|
min = port_obj->query_option("passive_port_min"); |
max = port_obj->query_option("passive_port_max"); |
if ((port < min) || (port > max)) { |
if (max > 65535) max = 65535; |
if (min < 1) min = 1; |
for (port = min; port <= max; port++) { |
if (pasv_port->bind(port, pasv_accept_callback, local_addr)) { |
break; |
} |
} |
if (port > max) { |
destruct(pasv_port); |
pasv_port = 0; |
send(452, ({ LOCALE(150, "Requested action aborted: Out of ports.") })); |
return; |
} |
} |
send(229, ({ LOCALE(154, "Entering Extended Passive Mode") + |
sprintf(" (|||%d|)", port) })); |
} |
|
void ftp_TYPE(string args) |
{ |
if (!expect_argument("TYPE", args)) { |
return; |
} |
|
args = upper_case(replace(args, ({ " ", "\t" }), ({ "", "" }))); |
|
|
switch(args) { |
case "L8": |
case "L": |
case "I": |
mode = "I"; |
break; |
case "A": |
mode = "A"; |
break; |
case "E": |
mode = "E"; |
break; |
default: |
send(504, ({ sprintf(LOCALE(155, "'TYPE': Unknown type: %s"), args) })); |
return; |
} |
|
send(200, ({ sprintf(LOCALE(156, "Using %s mode for transferring files."), |
modes[mode]) })); |
} |
|
void ftp_RETR(string args) |
{ |
if (!expect_argument("RETR", args)) { |
return; |
} |
|
args = fix_path(args); |
|
RequestID session = RequestID2(master_session); |
|
session->method = "GET"; |
session->not_query = args; |
|
mapping file; |
if (file = open_file(args, session, "RETR")) { |
if (restart_point) { |
if (file->data) { |
if (sizeof(file->data) >= restart_point) { |
file->data = file->data[restart_point..]; |
restart_point = 0; |
} else { |
restart_point -= sizeof(file->data); |
m_delete(file, "data"); |
} |
} |
if (restart_point) { |
if (!(file->file && file->file->seek && |
(file->file->seek(restart_point) != -1))) { |
restart_point = 0; |
send(550, ({ LOCALE(157, "'RETR': Error restoring restart point.") })); |
discard_data_connection(); |
destruct(session); |
return; |
} |
restart_point = 0; |
} |
} |
|
connect_and_send(file, session); |
} |
else { |
discard_data_connection(); |
destruct(session); |
} |
} |
|
void ftp_STOR(string args) |
{ |
if (!expect_argument("STOR", args)) { |
return; |
} |
|
args = fix_path(args); |
|
connect_and_receive(args); |
} |
|
void ftp_REST(string args) |
{ |
if (!expect_argument("REST", args)) { |
return; |
} |
restart_point = (int)args; |
send(350, ({ LOCALE(158, "'REST' ok") })); |
} |
|
void ftp_ABOR(string args) |
{ |
if (curr_pipe) { |
catch { |
destruct(curr_pipe); |
}; |
curr_pipe = 0; |
send(426, ({ LOCALE(159, "Data transmission terminated.") })); |
} |
send(226, ({ LOCALE(160, "'ABOR' Completed.") })); |
} |
|
void ftp_PWD(string args) |
{ |
send(257, ({ sprintf(LOCALE(161, "\"%s\" is current directory."), cwd) })); |
} |
|
void ftp_XPWD(string args) |
{ |
ftp_PWD(args); |
} |
|
|
|
|
|
private string rename_from; |
|
void ftp_RNFR(string args) |
{ |
if (!expect_argument("RNFR", args)) { |
return; |
} |
args = fix_path(args); |
|
if (stat_file(args)) { |
send(350, ({ sprintf(LOCALE(162, "%s ok, waiting for destination name."), args) }) ); |
rename_from = args; |
} else { |
send(550, ({ sprintf(LOCALE(163, "%s: no such file or permission denied."),args) }) ); |
} |
} |
|
void ftp_RNTO(string args) |
{ |
if(!rename_from) { |
send(503, ({ LOCALE(164, "RNFR needed before RNTO.") })); |
return; |
} |
if (!expect_argument("RNTO", args)) { |
return; |
} |
args = fix_path(args); |
|
RequestID session = RequestID2(master_session); |
|
session->method = "MV"; |
session->misc->move_from = rename_from; |
session->not_query = args; |
if (open_file(args, session, "MOVE")) { |
send(250, ({ sprintf(LOCALE(165, "%s moved to %s."), rename_from, args) })); |
session->conf->log(([ "error":200 ]), session); |
} |
rename_from = 0; |
destruct(session); |
} |
|
|
void ftp_NLST(string args) |
{ |
|
|
array(string) argv = glob_expand_command_line("/usr/bin/ls " + (args||"")); |
|
call_ls(argv); |
} |
|
void ftp_LIST(string args) |
{ |
#ifdef FTP2_MLSD_KLUDGE |
ftp_MLSD(args); return; |
#endif |
|
ftp_NLST("-l " + (args||"")); |
} |
|
void ftp_MLST(string args) |
{ |
string long = fix_path(args || "."); |
|
RequestID session = RequestID2(master_session); |
|
session->method = "DIR"; |
|
array|object st = stat_file(long, session); |
|
if (st) { |
session->file = ([]); |
session->file->full_path = long; |
send_MLST_response(([ long: st ]), session); |
} else { |
send_error("MLST", args, session->file, session); |
} |
destruct(session); |
} |
|
void ftp_MLSD(string args) |
{ |
args = fix_path(args || "."); |
|
RequestID session = RequestID2(master_session); |
|
session->method = "DIR"; |
|
array|object st = stat_file(args, session); |
|
if (st && (st[1] < 0)) { |
if (args[-1] != '/') { |
args += "/"; |
} |
|
session->file = ([]); |
session->file->full_path = args; |
|
mapping(string:array(mixed)) dir = |
session->conf->find_dir_stat(args, session) || ([]); |
if (args != "/") { |
dir[".."] = stat_file(combine_path(args,"../")); |
} |
dir["."] = stat_file(combine_path(args)); |
|
send_MLSD_response(dir, session); |
|
} else { |
if (st) { |
session->file->error = 405; |
} |
send_error("MLSD", args, session->file, session); |
discard_data_connection(); |
destruct(session); |
} |
} |
|
void ftp_OPTS(string args) |
{ |
if ((< 0, "" >)[args]) { |
ftp_HELP("OPTS"); |
return; |
} |
|
array a = (args/" ") - ({ "" }); |
|
if (!sizeof(a)) { |
ftp_HELP("OPTS"); |
return; |
} |
a[0] = upper_case(a[0]); |
if (!opts_help[a[0]]) { |
send(502, ({ sprintf(LOCALE(166, "Bad OPTS command: '%s'"), a[0]) })); |
} else if (this_object()["ftp_OPTS_"+a[0]]) { |
this_object()["ftp_OPTS_"+a[0]](a[1..]); |
} else { |
send(502, ({ sprintf(LOCALE(167, "OPTS command '%s' is not currently supported."), |
a[0]) })); |
} |
} |
|
void ftp_OPTS_MLST(array(string) args) |
{ |
if (sizeof(args) != 1) { |
send(501, ({ sprintf(LOCALE(168, "'OPTS MLST %s': incorrect arguments"), |
args*" ") })); |
return; |
} |
|
multiset(string) new_mlst_facts = (<>); |
foreach(args[0]/";", string fact) { |
fact = lower_case(fact); |
if (!supported_mlst_facts[fact]) continue; |
new_mlst_facts[fact] = 1; |
} |
current_mlst_facts = new_mlst_facts; |
|
send(200, ({ sprintf("MLST OPTS %s", |
format_factlist(new_mlst_facts)) })); |
} |
|
void ftp_DELE(string args) |
{ |
if (!expect_argument("DELE", args)) { |
return; |
} |
|
args = fix_path(args); |
|
RequestID session = RequestID2(master_session); |
|
session->data = ""; |
session->misc->len = 0; |
session->method = "DELETE"; |
|
if (open_file(args, session, "DELE")) { |
send(250, ({ sprintf(LOCALE(169, "%s deleted."), args) })); |
session->conf->log(([ "error":200 ]), session); |
} |
destruct(session); |
} |
|
void ftp_RMD(string args) |
{ |
if (!expect_argument("RMD", args)) { |
return; |
} |
|
args = fix_path(args); |
|
RequestID session = RequestID2(master_session); |
|
session->data = ""; |
session->misc->len = 0; |
session->method = "DELETE"; |
|
array|object st = stat_file(args, session); |
|
if (!st) { |
send_error("RMD", args, session->file, session); |
destruct(session); |
return; |
} else if (st[1] != -2) { |
if (st[1] == -3) { |
send(504, ({ sprintf(LOCALE(170, "%s is a module mountpoint."), args) })); |
session->conf->log(([ "error":405 ]), session); |
} else { |
send(504, ({ sprintf(LOCALE(171, "%s is not a directory."), args) })); |
session->conf->log(([ "error":405 ]), session); |
} |
destruct(session); |
return; |
} |
|
if (open_file(args, session, "RMD")) { |
send(250, ({ sprintf(LOCALE(169, "%s deleted."), args) })); |
session->conf->log(([ "error":200 ]), session); |
} |
destruct(session); |
} |
|
void ftp_XRMD(string args) |
{ |
ftp_RMD(args); |
} |
|
void ftp_MKD(string args) |
{ |
if (!expect_argument("MKD", args)) { |
return; |
} |
|
args = fix_path(args); |
|
RequestID session = RequestID2(master_session); |
|
session->method = "MKDIR"; |
session->data = ""; |
session->misc->len = 0; |
|
if (open_file(args, session, "MKD")) { |
send(257, ({ sprintf(LOCALE(172, "\"%s\" created."), args) })); |
session->conf->log(([ "error":200 ]), session); |
} |
destruct(session); |
} |
|
void ftp_XMKD(string args) |
{ |
ftp_MKD(args); |
} |
|
void ftp_LANG(string args) |
{ |
args = lower_case(String.trim_all_whites(args || "")); |
if (sizeof(args)) { |
if (!roxen.set_locale(args)) { |
send(504, ({ sprintf(LOCALE(173, "Unsupported language: %s"), args) })); |
return; |
} |
master_session->misc->pref_languages->languages = ({ args }); |
master_session->misc["accept-language"] = args; |
} else { |
roxen.set_locale(); |
master_session->misc->pref_languages->languages = ({}); |
m_delete(master_session->misc, "accept-language"); |
} |
send(200, ({ sprintf(LOCALE(174, "Language set to %s"), roxen.get_locale()) })); |
} |
|
void ftp_SYST(string args) |
{ |
send(215, ({ "UNIX Type: L8: Roxen Information Server"})); |
} |
|
void ftp_CLNT(string args) |
{ |
if (!expect_argument("CLNT", args)) { |
return; |
} |
|
send(200, ({ LOCALE(175, "Ok, gottcha!")})); |
master_session->client = args/" " - ({ "" }); |
} |
|
void ftp_FEAT(string args) |
{ |
array a = sort(Array.filter(indices(cmd_help), |
lambda(string s) { |
return(this_object()["ftp_"+s]); |
})); |
if (!port_obj->ctx) { |
a -= ({ "AUTH" }); |
} |
if (master_session->my_fd->renegotiate) { |
|
a -= ({ "CCC" }); |
} |
a += ({ "TVFS" }); |
a += ({ "UTF8" }); |
a = Array.map(a, |
lambda(string s) { |
return(([ "REST":"REST STREAM", |
"MLST":sprintf("MLST %s", |
format_factlist(supported_mlst_facts, |
current_mlst_facts)), |
"MLSD":"", |
"AUTH":"AUTH TLS", |
"LANG":sprintf("LANG %s", format_langlist()), |
])[s] || s); |
}) - ({ "" }); |
|
send(211, ({ LOCALE(176, "The following features are supported:") }) + a + |
({ "END" })); |
} |
|
void ftp_MDTM(string args) |
{ |
if (!expect_argument("MDTM", args)) { |
return; |
} |
args = fix_path(args); |
mapping|array|object st = stat_file(args); |
|
if (!arrayp(st) && !objectp(st)) { |
send_error("MDTM", args, st, master_session); |
} else { |
send(213, ({ make_MDTM(st[3]) })); |
} |
} |
|
void ftp_SIZE(string args) |
{ |
if (!expect_argument("SIZE", args)) { |
return; |
} |
args = fix_path(args); |
|
mapping|array|object st = stat_file(args); |
|
if (!arrayp(st) && !objectp(st)) { |
send_error("SIZE", args, st, master_session); |
return; |
} |
int size = st[1]; |
if (size < 0) { |
send_error("SIZE", args, ([ "error":405, ]), master_session); |
|
} else { |
send(213, ({ (string)size })); |
} |
} |
|
void ftp_STAT(string args) |
{ |
|
|
|
|
|
|
|
|
|
if ((< "", 0 >)[args]) { |
|
|
|
|
|
|
string local_addr = fd->query_address(1); |
if (has_value(local_addr, ":")) { |
|
local_addr = "[" + replace(local_addr, " ", "]:"); |
} else { |
local_addr = replace(local_addr, " ", ":"); |
} |
string remote_addr = fd->query_address(); |
if (has_value(remote_addr, ":")) { |
|
remote_addr = "[" + replace(remote_addr, " ", "]:"); |
} else { |
remote_addr = replace(remote_addr, " ", ":"); |
} |
send(211, |
sprintf(LOCALE(177, "%s FTP server status:\n" |
"Version %s\n" |
"Listening on %s\n" |
"Connected to %s\n" |
"Logged in %s\n" |
"TYPE: %s, FORM: %s; STRUcture: %s; transfer MODE: %s\n" |
"End of status"), |
local_addr, |
roxen.version(), |
port_obj->sorted_urls * "\nListening on ", |
remote_addr, |
user?sprintf("as %s", user):"anonymously", |
(["A":"ASCII", "E":"EBCDIC", "I":"IMAGE", "L":"LOCAL"]) |
[mode], |
"Non-Print", |
"File", |
"Stream" |
)/"\n"); |
return; |
} |
string long = fix_path(args); |
mapping|array|object st = stat_file(long); |
|
if (!arrayp(st) && !objectp(st)) { |
send_error("STAT", long, st, master_session); |
} else { |
string s = LS_L(master_session)->ls_l(args, st); |
|
send(213, ({ |
sprintf(LOCALE(178, "Status of \"%s\"."), args), |
@((s/"\n") - ({""})), |
"End of Status", |
})); |
} |
} |
|
void ftp_NOOP(string args) |
{ |
send(200, ({ LOCALE(179, "Nothing done ok") })); |
} |
|
void ftp_HELP(string args) |
{ |
if ((< "", 0 >)[args]) { |
send(214, ({ |
LOCALE(180, "The following commands are recognized (* =>'s unimplemented):"), |
@(sprintf(" %#70s", sort(Array.map(indices(cmd_help), |
lambda(string s) { |
return(upper_case(s)+ |
(this_object()["ftp_"+s]? |
" ":"* ")); |
}))*"\n")/"\n"), |
@(FTP2_XTRA_HELP), |
})); |
} else { |
args = upper_case(args); |
if ((args/" ")[0] == "SITE") { |
array(string) a = (args/" ")-({""}); |
if (sizeof(a) == 1) { |
send(214, ({ LOCALE(181, "The following SITE commands are recognized:"), |
@(sprintf(" %#70s", sort(indices(site_help))*"\n")/"\n") |
})); |
} else if (site_help[a[1]]) { |
send(214, ({ sprintf(LOCALE(182, "Syntax: SITE %s %s"), |
a[1], site_help[a[1]]) })); |
} else { |
send(504, ({ sprintf(LOCALE(183, "Unknown SITE command %s."), a[1]) })); |
} |
} else if ((args/" ")[0] == "OPTS") { |
array(string) a = (args/" ")-({""}); |
if (sizeof(a) == 1) { |
send(214, ({ LOCALE(184, "The following OPTS commands are recognized:"), |
@(sprintf(" %#70s", sort(indices(opts_help))*"\n")/"\n") |
})); |
} else if (opts_help[a[1]]) { |
send(214, ({ sprintf(LOCALE(185, "Syntax: OPTS %s %s"), |
a[1], opts_help[a[1]]) })); |
} else { |
send(504, ({ sprintf(LOCALE(186, "Unknown OPTS command %s."), a[1]) })); |
} |
} else { |
if (cmd_help[args]) { |
send(214, ({ sprintf(LOCALE(187, "Syntax: %s %s%s"), args, |
cmd_help[args], |
|