Roxen.git / server / plugins / protocols / ftp.pike

version» Context lines:

Roxen.git/server/plugins/protocols/ftp.pike:1: + // This is a roxen protocol module. + // Copyright © 1997 - 2001, Roxen IS.    -  + /* +  * FTP protocol mk 2 +  * +  * $Id: ftp.pike,v 2.80 2002/04/11 12:24:48 anders Exp $ +  * +  * Henrik Grubbström <grubba@roxen.com> +  */ +  + /* +  * TODO: +  * +  * How much is supposed to be logged? +  */ +  + /* +  * Relevant RFC's: +  * +  * RFC 764 TELNET PROTOCOL SPECIFICATION +  * RFC 765 FILE TRANSFER PROTOCOL +  * RFC 959 FILE TRANSFER PROTOCOL (FTP) +  * RFC 1123 Requirements for Internet Hosts -- Application and Support +  * RFC 1579 Firewall-Friendly FTP +  * RFC 1635 How to Use Anonymous FTP +  * +  * RFC's describing extra commands: +  * +  * RFC 683 FTPSRV - Tenex extension for paged files +  * RFC 737 FTP Extension: XSEN +  * RFC 743 FTP extension: XRSQ/XRCP +  * RFC 775 DIRECTORY ORIENTED FTP COMMANDS +  * RFC 949 FTP unique-named store command +  * RFC 1639 FTP Operation Over Big Address Records (FOOBAR) +  * RFC 2428 FTP Extensions for IPv6 and NATs +  * +  * IETF draft 12 Extended Directory Listing, TVFS, +  * and Restart Mechanism for FTP +  * +  * RFC's with recomendations and discussions: +  * +  * RFC 607 Comments on the File Transfer Protocol +  * RFC 614 Response to RFC 607, "Comments on the File Transfer Protocol" +  * RFC 624 Comments on the File Transfer Protocol +  * RFC 640 Revised FTP Reply Codes +  * RFC 691 One More Try on the FTP +  * RFC 724 Proposed Official Standard for the +  * Format of ARPA Network Messages +  * +  * RFC's describing gateways and proxies: +  * +  * RFC 1415 FTP-FTAM Gateway Specification +  * +  * More or less obsolete RFC's: +  * +  * RFC 412 User FTP documentation +  * *RFC 438 FTP server-server interaction +  * *RFC 448 Print files in FTP +  * *RFC 458 Mail retrieval via FTP +  * *RFC 463 FTP comments and response to RFC 430 +  * *RFC 468 FTP data compression +  * *RFC 475 FTP and network mail system +  * *RFC 478 FTP server-server interaction - II +  * *RFC 479 Use of FTP by the NIC Journal +  * *RFC 480 Host-dependent FTP parameters +  * *RFC 505 Two solutions to a file transfer access problem +  * *RFC 506 FTP command naming problem +  * *RFC 520 Memo to FTP group: Proposal for File Access Protocol +  * *RFC 532 UCSD-CC Server-FTP facility +  * RFC 542 File Transfer Protocol for the ARPA Network +  * RFC 561 Standardizing Network Mail Headers +  * *RFC 571 Tenex FTP problem +  * *RFC 630 FTP error code usage for more reliable mail service +  * *RFC 686 Leaving well enough alone +  * *RFC 697 CWD Command of FTP +  * RFC 751 SURVEY OF FTP MAIL AND MLFL +  * RFC 754 Out-of-Net Host Addresses for Mail +  * +  * (RFC's marked with * are not available from http://www.roxen.com/rfc/) +  */ +  +  + #include <config.h> + #include <module.h> + #include <stat.h> +  + //#define FTP2_DEBUG +  + #define FTP2_XTRA_HELP ({ "Report any bugs at http://community.roxen.com/crunch/" }) +  + #define FTP2_TIMEOUT (5*60) +  + // #define Query(X) conf->variables[X][VAR_VALUE] +  + #ifdef FTP2_DEBUG + # define DWRITE(X ...) werror(X) + #else + # define DWRITE(X ...) + #endif +  + #if constant(thread_create) + #define BACKEND_CLOSE(FD) do { DWRITE("close\n"); FD->set_blocking(); call_out(FD->close, 0); FD = 0; } while(0) + #else /* !constant(thread_create) */ + #define BACKEND_CLOSE(FD) do { DWRITE("close\n"); FD->set_blocking(); FD->close(); FD = 0; } while(0) + #endif /* constant(thread_create) */ +  + class RequestID2 + { +  inherit RequestID; +  +  mapping file; +  + #ifdef FTP2_DEBUG +  static void trace_enter(mixed a, mixed b) +  { +  write("FTP: TRACE_ENTER(%O, %O)\n", a, b); +  } +  +  static void trace_leave(mixed a) +  { +  write("FTP: TRACE_LEAVE(%O)\n", a); +  } + #endif /* FTP2_DEBUG */ +  +  void ready_to_receive() +  { +  // FIXME: Should hook the STOR reply to this function. +  } +  +  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() +  { +  } +  +  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]; +  report_debug("REQUESTID: New request id #%d.\n", _num); + #else +  DWRITE("REQUESTID: New request id.\n"); + #endif +  +  if (m_rid) { +  object o = this_object(); +  foreach(indices(m_rid), string var) { +  if (!(< "create", "connection", "configuration", +  "__INIT", "clone_me", "end", "ready_to_receive", +  "send", "scan_for_query", "send_result", "misc", +  "url_base", "set_response_header", +  "add_response_header", +  "destroy", "_num", "__num">)[var]) { + #ifdef FTP2_DEBUG +  if (catch { + #endif /* FTP2_DEBUG */ +  o[var] = m_rid[var]; + #ifdef FTP2_DEBUG +  }) { +  report_error("FTP2: " +  "Failed to copy variable %s (value:%O)\n", +  var, m_rid[var]); +  } + #endif /* FTP2_DEBUG */ +  } +  } +  o["misc"] = m_rid["misc"] + ([ ]); +  } else { +  // Defaults... +  client = ({ "ftp" }); +  prot = "FTP"; +  clientprot = "FTP"; +  real_variables = ([]); +  misc = ([]); +  cookies = ([]); +  throttle = ([]); +  client_var = ([]); +  request_headers = ([]); +  +  prestate = (<>); +  config = (<>); +  supports = (< "ftp", "images", "tables", >); +  pragma = (<>); +  rest_query = ""; +  extra_extension = ""; +  } +  time = predef::time(1); + #ifdef FTP2_DEBUG +  misc->trace_enter = trace_enter; +  misc->trace_leave = trace_leave; + #endif /* FTP2_DEBUG */ +  } + }; +  + class FileWrapper + { +  static string convert(string s); +  +  static private function read_cb; +  static private function close_cb; +  static private mixed id; +  +  static private object f; +  static private string data; +  static private object ftpsession; +  +  int is_file; +  +  static void create(object f_, string data_, object ftpsession_) +  { +  f = f_; +  data = data_; +  ftpsession = ftpsession_; +  +  is_file = f_->is_file; +  } +  +  static private void read_callback(mixed i, string s) +  { +  read_cb(id, convert(s)); +  ftpsession->touch_me(); +  } +  +  static private void close_callback(mixed i) +  { +  close_cb(id); +  if (f) { +  BACKEND_CLOSE(f); +  } +  ftpsession->touch_me(); +  } +  +  static 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) { +  // We need to call r_cb as soon as possible, but we can't do it here +  // and we can't enable the read_callback just yet to maintain order. +  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() +  { +  ftpsession->touch_me(); +  if (f) { +  f->set_blocking(); +  BACKEND_CLOSE(f); +  } +  } + } +  + class ToAsciiWrapper + { +  inherit FileWrapper; +  +  int converted; +  +  static 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; +  +  static string convert(string s) +  { +  converted += sizeof(s); + #ifdef __NT__ +  // This replace shouldn't be needed, but we're paranoid. +  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__ */ +  } + } +  + // This one is needed for touch_me() to be called as needed. + class BinaryWrapper + { +  inherit FileWrapper; +  +  static string convert(string s) +  { +  return(s); +  } + } +  + // EBCDIC Wrappers here. +  + class ToEBCDICWrapper + { +  inherit FileWrapper; +  +  int converted; +  +  static object converter = Locale.Charset.encoder("EBCDIC-US", ""); +  +  static string convert(string s) +  { +  converted += sizeof(s); +  return(converter->feed(s)->drain()); +  } + } +  + class FromEBCDICWrapper + { +  inherit FileWrapper; +  +  int converted; +  +  static object converter = Locale.Charset.decoder("EBCDIC-US"); +  +  static string convert(string s) +  { +  converted += sizeof(s); +  return(converter->feed(s)->drain()); +  } + } +  +  + class PutFileWrapper + { +  static int response_code = 226; +  static string response = "Stored."; +  static string gotdata = ""; +  static int closed, recvd; +  static function other_read_callback; +  +  static object from_fd; +  static object session; +  static object ftpsession; +  +  int is_file; +  +  static void create(object from_fd_, object session_, object ftpsession_) +  { +  from_fd = from_fd_; +  session = session_; +  ftpsession = ftpsession_; +  +  is_file = from_fd->is_file; +  } +  + #include <variables.h> +  +  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); +  session->file = 0; +  session->my_fd = from_fd; +  } +  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; +  } +  +  static 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; +  } +  +  // Cut away the code. +  response = ((result->rettext || errors[result->error])/" ")[1..] * " "; +  gotdata = result->data || ""; +  +  close(); +  } +  +  string query_address(int|void loc) +  { +  return from_fd->query_address(loc); +  } + } +  +  + // Simulated /usr/bin/ls pipe +  + #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(static RequestID master_session, +  static int|void flags) + { +  static 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" }) +  }); +  +  static constant months = ({ "Jan", "Feb", "Mar", "Apr", "May", "Jun", +  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }); +  +  static string name_from_uid(int uid) +  { +  User user; +  foreach(master_session->conf->user_databases(), UserDB user_db) { +  if (user = user_db->find_user_from_uid(uid)) { +  return user->name(); +  } +  } +  return (uid?((string)uid):"root"); +  } +  +  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]); +  +  // NOTE: SiteBuilder may set st[5] and st[6] to strings. +  string user = (string)st[5]; +  string group = (string)st[6]; +  if (!(flags & LS_FLAG_n)) { +  // Use symbolic names for uid and gid. +  if (!stringp(st[5])) { +  user = name_from_uid(st[5]); +  } +  +  // FIXME: Convert st[6] to symbolic group name. +  } +  +  string ts; +  int now = time(1); +  // Half a year: +  // 365.25*24*60*60/2 = 15778800 +  if ((st[3] <= now - 15778800) || (st[3] > now)) { +  // Month Day Year +  ts = sprintf("%s %02d %04d", +  months[lt->mon], lt->mday, 1900+lt->year); +  } else { +  // Month Day Hour:minute +  ts = sprintf("%s %02d %02d:%02d", +  months[lt->mon], lt->mday, lt->hour, lt->min); +  } +  +  if (flags & LS_FLAG_G) { +  // No group. +  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 + { +  static inherit LS_L; +  +  static string cwd; +  static array(string) argv; +  static object ftpsession; +  +  static array(string) output_queue = ({}); +  static int output_pos; +  static string output_mode = "A"; +  +  static mapping(string:array|object) stat_cache = ([]); +  +  static object conv; +  +  static array|object stat_file(string long, RequestID|void session) +  { +  array|object st = stat_cache[long]; +  if (zero_type(st)) { +  if (!session) { +  session = RequestID2(master_session); +  session->method = "DIR"; +  } +  long = replace(long, "//", "/"); +  st = session->conf->stat_file(long, session); +  stat_cache[long] = st; +  } +  return st; +  } +  +  // FIXME: Should convert output somewhere below. +  static void output(string s) +  { +  if(stringp(s)) { +  // ls is always ASCII-mode... +  s = replace(s, "\n", "\r\n"); +  if (conv) { +  // EBCDIC or potentially other charsets. +  s = conv->feed(s)->drain(); +  } +  } +  output_queue += ({ s }); +  } +  +  static 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", +  }))); +  } +  +  static 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 { +  // Should not happen, but... +  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]; // Note: Negative time. +  } else { +  // Should not happen, but... +  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) { +  // Enclose in quotes. +  // Space needs to be quoted to be compatible with -m +  short = "\"" + +  replace(short, +  ({ "\n", "\r", "\\", "\"", "\'", " " }), +  ({ "\\n", "\\r", "\\\\", "\\\"", "\\\'", "\\020" })) + +  "\""; +  } +  if (flags & LS_FLAG_F) { +  if (st[1] < 0) { +  // Directory +  short += "/"; +  } else if (st[0] & 0111) { +  // Executable +  short += "*"; +  } +  } +  int blocks = 1; +  if (st[1] >= 0) { +  blocks = (st[1] + 1023)/1024; // Blocks are 1KB. +  } +  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) +  static ADT.Stack dir_stack = ADT.Stack(); + #else +  static object(Stack.stack) dir_stack = Stack.stack(); + #endif +  static int name_directories; +  +  static string fix_path(string s) +  { +  return(combine_path(cwd, s)); +  } +  +  static void 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); +  }; +  +  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); +  +  // Put them in the stat cache. +  foreach(indices(dir||({})), string f) { +  stat_cache[combine_path(long, f)] = dir[f]; +  } +  +  if ((flags & LS_FLAG_a) && +  (long != "/")) { +  if (dir) { +  dir[".."] = stat_file(combine_path(long,"../")); +  } else { +  dir = ([ "..":stat_file(combine_path(long,"../")) ]); +  } +  } +  string listing = ""; +  if (dir && 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); // End marker. +  } else { +  name_directories = 1; +  } +  } +  +  void set_blocking() +  { +  } +  +  int query_fd() +  { +  return -1; +  } +  +  static mixed id; +  +  void set_id(mixed i) +  { +  id = i; +  } +  +  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) { +  // Clean anything pending in the queue. +  output_queue = ({}); +  output_pos = 0; +  +  // Generate some more output... +  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 { +  // EOF +  master_session->file = 0; // Avoid extra log-entry. +  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") { +  // EBCDIC +  conv = Locale.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))) { +  // Directory +  dir_stack->push(short); +  } else { +  files[n_files++] = short; +  } +  } else { +  output(short + ": not found\n"); +  session->conf->log(([ "error":404 ]), 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); // May modify dir_stack (-R) +  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); +  } +  if (dir_stack->ptr) { +  name_directories = dir_stack->ptr && +  ((dir_stack->ptr > 1) || n_files); +  +  list_next_directory(); +  } else { +  output(0); +  } +  } + } +  + class TelnetSession { +  static object fd; +  static object conf; +  +  static private mapping cb; +  static private mixed id; +  static private function(mixed|void:string) write_cb; +  static private function(mixed, string:void) read_cb; +  static private function(mixed|void:void) close_cb; +  +  static private constant TelnetCodes = ([ +  236:"EOF", // End Of File +  237:"SUSP", // Suspend Process +  238:"ABORT", // Abort Process +  239:"EOR", // End Of Record +  +  // The following ones are specified in RFC 959: +  240:"SE", // Subnegotiation End +  241:"NOP", // No Operation +  242:"DM", // Data Mark +  243:"BRK", // Break +  244:"IP", // Interrupt Process +  245:"AO", // Abort Output +  246:"AYT", // Are You There +  247:"EC", // Erase Character +  248:"EL", // Erase Line +  249:"GA", // Go Ahead +  250:"SB", // Subnegotiation +  251:"WILL", // Desire to begin/confirmation of option +  252:"WON'T", // Refusal to begin/continue option +  253:"DO", // Request to begin/confirmation of option +  254:"DON'T", // Demand/confirmation of stop of option +  255:"IAC", // Interpret As Command +  ]); +  +  // Some prototypes needed by Pike 0.5 +  static private void got_data(mixed ignored, string s); +  static private void send_data(); +  static 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); +  } +  } +  +  static private string to_send = ""; +  static private void send(string s) +  { +  to_send += s; +  } +  +  static private void send_data() +  { +  if (!sizeof(to_send)) { +  to_send = write_cb(id); +  } +  if (fd) { +  if (!to_send) { +  // Support for delayed close. +  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 { +  // Error. +  DWRITE("TELNET: write failed: errno:%d\n", fd->errno()); +  BACKEND_CLOSE(fd); +  } +  } else { +  // Nothing to send for the moment. +  +  // FIXME: Is this the correct use? +  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(); +  } +  } +  +  static private mapping(string:function) default_cb = ([ +  "BRK":lambda() { +  destruct(); +  throw(0); +  }, +  "AYT":lambda() { +  send("\377\361"); // NOP +  }, +  "WILL":lambda(int code) { +  send(sprintf("\377\376%c", code)); // DON'T xxx +  }, +  "DO":lambda(int code) { +  send(sprintf("\377\374%c", code)); // WON'T xxx +  }, +  ]); +  +  static private int sync = 0; +  +  static 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); +  } +  } +  +  static private string rest = ""; +  static 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"); +  // Data Mark handing. +  s = s[1..]; +  sync = 0; +  } +  +  // A single read() can contain multiple or partial commands +  // RFC 1123 4.1.2.10 +  +  array lines = s/"\r\n"; +  +  // Censor the raw string. +  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: +  // FIXME: Should probably have a warning here. +  break; +  default: +  if (fun = (cb[name] || default_cb[name])) { +  mixed err = catch { +  fun(); +  }; +  if (err) { +  throw(err); +  } else if (!zero_type(err)) { +  // We were just destructed. +  return; +  } +  } +  a[i] = a[i][1..]; +  break; +  case "EC": // Erase Character +  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": // Erase Line +  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": // Data Mark +  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 + { +  // However, a server-FTP MUST be capable of +  // accepting and refusing Telnet negotiations (i.e., sending +  // DON'T/WON'T). RFC 1123 4.1.2.12 +  +  inherit TelnetSession; +  +  inherit "roxenlib"; +  +  static private constant cmd_help = ([ +  // FTP commands in reverse RFC order. +  +  // The following is a command suggested by the author of ncftp. +  "CLNT":"<sp> <client-name> <sp> <client-version> " +  "[<sp> <optional platform info>] (Set client name)", +  +  // The following are in +  // "Extended Directory Listing, TVFS, and Restart Mechanism for FTP" +  // IETF draft 4. +  "FEAT":"(Feature list)", +  "MDTM":"<sp> path-name (Modification time)", +  "SIZE":"<sp> path-name (Size)", +  "MLST":"<sp> path-name (Machine Processing List File)", +  "MLSD":"<sp> path-name (Machine Processing List Directory)", +  "OPTS":"<sp> command <sp> options (Set Command-specific Options)", +  +  // These are from RFC 2428 +  "EPRT":"<sp> <d>net-prt<d>net-addr<d>tcp-port<d> (Extended Address Port)", +  "EPSV":"[<sp> net-prt|ALL] (Extended Address Passive Mode)", +  +  // These are in RFC 1639 +  "LPRT":"<sp> <long-host-port> (Long Port)", +  "LPSV":"(Long Passive)", +  +  // Commands in the order from RFC 959 +  +  // Login +  "USER":"<sp> username (Change user)", +  "PASS":"<sp> password (Change password)", +  "ACCT":"<sp> <account-information> (Account)", +  "CWD":"[ <sp> directory-name ] (Change working directory)", +  "CDUP":"(Change to parent directory)", +  "SMNT":"<sp> <pathname> (Structure mount)", +  // Logout +  "REIN":"(Reinitialize)", +  "QUIT":"(Terminate service)", +  // Transfer parameters +  "PORT":"<sp> b0, b1, b2, b3, b4 (Set port IP and number)", +  "PASV":"(Set server in passive mode)", +  "TYPE":"<sp> [ A | E | I | L ] (Ascii, Ebcdic, Image, Local)", +  "STRU":"<sp> <structure-code> (File structure)", +  "MODE":"<sp> <mode-code> (Transfer mode)", +  // File action commands +  "ALLO":"<sp> <decimal-integer> [<sp> R <sp> <decimal-integer>]" +  " (Allocate space for file)", +  "REST":"<sp> marker (Set restart marker)", +  "STOR":"<sp> file-name (Store file)", +  "STOU":"(Store file with unique name)", +  "RETR":"<sp> file-name (Retreive file)", +  "LIST":"[ <sp> <pathname> ] (List directory)", +  "NLST":"[ <sp> <pathname> ] (List directory)", +  "APPE":"<sp> <pathname> (Append file)", +  "RNFR":"<sp> <pathname> (Rename from)", +  "RNTO":"<sp> <pathname> (Rename to)", +  "DELE":"<sp> file-name (Delete file)", +  "RMD":"<sp> <pathname> (Remove directory)", +  "MKD":"<sp> <pathname> (Make directory)", +  "PWD":"(Return current directory)", +  "ABOR":"(Abort current transmission)", +  // Informational commands +  "SYST":"(Get type of operating system)", +  "STAT":"[ <sp> <pathname> ] (Status for server/file)", +  "HELP":"[ <sp> <string> ] (Give help)", +  // Miscellaneous commands +  "SITE":"<sp> <string> (Site parameters)", // Has separate help +  "NOOP":"(No operation)", +  +  // Old "Experimental commands" +  // These are in RFC 775 +  // Required by RFC 1123 4.1.3.1 +  "XMKD":"<sp> path-name (Make directory)", +  "XRMD":"<sp> path-name (Remove directory)", +  "XPWD":"(Return current directory)", +  "XCWD":"[ <sp> directory-name ] (Change working directory)", +  "XCUP":"(Change to parent directory)", +  +  // These are in RFC 765 but not in RFC 959 +  "MAIL":"[<sp> <recipient name>] (Mail to user)", +  "MSND":"[<sp> <recipient name>] (Mail send to terminal)", +  "MSOM":"[<sp> <recipient name>] (Mail send to terminal or mailbox)", +  "MSAM":"[<sp> <recipient name>] (Mail send to terminal and mailbox)", +  "MRSQ":"[<sp> <scheme>] (Mail recipient scheme question)", +  "MRCP":"<sp> <recipient name> (Mail recipient)", +  +  // These are in RFC 743 +  "XRSQ":"[<sp> <scheme>] (Scheme selection)", +  "XRCP":"<sp> <recipient name> (Recipient specification)", +  +  // These are in RFC 737 +  "XSEN":"[<sp> <recipient name>] (Send to terminal)", +  "XSEM":"[<sp> <recipient name>] (Send, mail if can\'t)", +  "XMAS":"[<sp> <recipient name>] (Mail and send)", +  +  // These are in RFC 542 +  "BYE":"(Logout)", +  "BYTE":"<sp> <bits> (Byte size)", +  "SOCK":"<sp> host-socket (Data socket)", +  +  // This one is referenced in a lot of old RFCs +  "MLFL":"(Mail file)", +  ]); +  +  static private constant site_help = ([ +  "CHMOD":"<sp> mode <sp> file", +  "UMASK":"<sp> mode", +  "PRESTATE":"<sp> prestate", +  ]); +  +  static private constant modes = ([ +  "A":"ASCII", +  "E":"EBCDIC", +  "I":"BINARY", +  "L":"LOCAL", +  ]); +  +  static private int time_touch = time(); +  +  static private object(ADT.Queue) to_send = ADT.Queue(); +  +  static private int end_marker = 0; +  +  void touch_me() +  { +  time_touch = time(); +  } +  +  static 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); // Mark EOF +  } +  DWRITE("FTP2: write_cb(): Sending \"\"\n"); +  return(""); // Shouldn't happen, but... +  } else { +  string s = to_send->get(); +  +  DWRITE("FTP2: write_cb(): Sending \"%s\"\n", s); +  +  if ((to_send->is_empty()) && (!end_marker)) { +  ::set_write_callback(0); +  } else { +  ::set_write_callback(write_cb); +  } +  return(s); +  } +  } +  +  void send(int code, array(string) data, int|void enumerate_all) +  { +  DWRITE("FTP2: 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(s); +  ::set_write_callback(write_cb); +  } else { +  to_send->put(s); +  } +  } else { +  DWRITE("FTP2: send(): Nothing to send!\n"); +  } +  } +  +  static private RequestID master_session; +  +  static private string dataport_addr; +  static private int dataport_port; +  +  static private string mode = "A"; +  +  static private string cwd = "/"; +  +  static private User auth_user; +  //! Authenticated user. +  +  static private string user; +  static private string password; +  static private int logged_in; +  +  static private object curr_pipe; +  static private int restart_point; +  +  static private multiset|int allowed_shells = 0; +  +  // On a multihomed server host, the default data transfer port +  // (L-1) MUST be associated with the same local IP address as +  // the corresponding control connection to port L. +  // RFC 1123 4.1.2.12 +  string local_addr; +  int local_port; +  +  // The listen port object +  roxen.Protocol port_obj; +  +  /* +  * Misc +  */ +  +  static private int check_shell(string shell) +  { +  // FIXME: Should the shell database be protocol specific or +  // virtual-server specific? +  if (port_obj->query_option("shells") != "") { +  // FIXME: Hmm, the cache will probably be empty almost always +  // since it's part of the FTPsession object. +  // Oh, well, shouldn't matter much unless you have *lots* of +  // shells. +  // /grubba 1998-05-21 +  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; +  } +  +  static private string fix_path(string s) +  { +  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)); +  } +  } +  +  /* +  * PASV handling +  */ +  static private object pasv_port; +  static private function(object, mixed:void) pasv_callback; +  static private mixed pasv_args; +  static private array(object) pasv_accepted = ({}); +  +  void pasv_accept_callback(mixed id) +  { +  touch_me(); +  +  if(pasv_port) { +  object fd = pasv_port->accept(); +  if(fd) { +  // On a multihomed server host, the default data transfer port +  // (L-1) MUST be associated with the same local IP address as +  // the corresponding control connection to port L. +  // RFC 1123 4.1.2.12 +  +  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 }); +  } +  } +  } +  } +  +  static 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; +  } +  } +  +  /* +  * PORT handling +  */ +  +  static private void ftp_async_connect(function(object,string,mixed ...:void) fun, +  mixed ... args) +  { +  DWRITE("FTP: async_connect(%O, %@O)...\n", fun, args); +  +  // More or less copied from socket.pike +  +  if (!dataport_addr) { +  DWRITE("FTP: No dataport specified.\n"); +  fun(0, "", @args); +  return; +  } +  +  object(Stdio.File) f = Stdio.File(); +  +  // FIXME: Race-condition: open_socket() for other connections will fail +  // until the socket has been connected. +  +  object privs; +  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); +  +  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; +  } +  } +  } +  privs = 0; +  +  f->set_nonblocking(lambda(mixed ignored, string data) { +  DWRITE("FTP: async_connect ok. Got data.\n"); +  f->set_nonblocking(0,0,0); +  fun(f, data, @args); +  }, +  lambda(mixed ignored) { +  DWRITE("FTP: async_connect ok.\n"); +  f->set_nonblocking(0,0,0); +  fun(f, "", @args); +  }, +  lambda(mixed ignored) { +  DWRITE("FTP: connect_and_send failed\n"); +  destruct(f); +  fun(0, 0, @args); +  }); +  + #ifdef FD_DEBUG +  mark_fd(f->query_fd(), sprintf("ftp communication: %s:%d -> %s:%d", +  local_addr, local_port - 1, +  dataport_addr, dataport_port)); + #endif +  +  if(catch(f->connect(dataport_addr, dataport_port))) { +  DWRITE("FTP: Illegal internet address in connect in async comm.\n"); +  destruct(f); +  fun(0, 0, @args); +  return; +  } +  } +  +  /* +  * Data connection handling +  */ +  static 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(); // Force close() to flush any buffers. +  } +  BACKEND_CLOSE(fd); +  } +  curr_pipe = 0; +  +  if (session && session->file) { +  session->conf->log(session->file, session); +  session->file = 0; +  } +  +  send(226, ({ "Transfer complete." })); +  } +  +  static private mapping|array|object stat_file(string fname, +  object|void session) +  { +  mapping file; +  +  if (!session) { +  session = RequestID2(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, "//", "/"); +  return(conf->stat_file(fname, session)); +  } +  return(file); +  } +  +  static private int expect_argument(string cmd, string args) +  { +  if ((< "", 0 >)[args]) { +  send(504, ({ sprintf("Syntax: %s %s", cmd, cmd_help[cmd]) })); +  return 0; +  } +  return 1; +  } +  +  static 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("'%s': %s: Redirect to %O.", +  cmd, f, file->extra_heads->Location) })); +  } else { +  send(504, ({ sprintf("'%s': %s: Redirect.", cmd, f) })); +  } +  break; +  case 401: +  case 403: +  send(530, ({ sprintf("'%s': %s: Access denied.", +  cmd, f) })); +  break; +  case 405: +  send(530, ({ sprintf("'%s': %s: Method not allowed.", +  cmd, f) })); +  break; +  case 500: +  send(451, ({ sprintf("'%s': Requested action aborted: " +  "local error in processing.", cmd) })); +  break; +  default: +  if (!file) { +  file = ([ "error":404 ]); +  } +  send(550, ({ sprintf("'%s': %s: No such file or directory.", +  cmd, f) })); +  break; +  } +  session->conf->log(file, session); +  } +  +  static private int open_file(string fname, object session, string cmd) +  { +  object|array|mapping file; +  +  file = stat_file(fname, session); +  +  if (objectp(file) || arrayp(file)) { +  array|object st = file; +  file = 0; +  if (st && (st[1] < 0) && !((<"RMD", "XRMD", "CHMOD">)[cmd])) { +  send(550, ({ sprintf("%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("%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("%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; +  } +  +  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 1; +  } +  +  static private void connected_to_send(object fd, string ignored, +  mapping file, object session) +  { +  DWRITE("FTP: connected_to_send(X, %O, %O, X)\n", ignored, file); +  +  touch_me(); +  +  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("Opening %s data connection for %s (%d bytes).", +  modes[file->mode], file->full_path, file->len) })); +  } else { +  send(150, ({ sprintf("Opening %s mode data connection for %s", +  modes[file->mode], file->full_path) })); +  } +  } +  else +  { +  send(425, ({ "Can't build data connect: Connection refused." })); +  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) +  { +  // The list_stream object doesn't support nonblocking I/O, +  // but converts to ASCII anyway, so we don't have to do +  // anything about it. +  file->file = ToAsciiWrapper(file->file, 0, this_object()); +  } +  break; +  case "E": +  // EBCDIC handling here. +  if (file->data) { +  object conv = Locale.Charset.encoder("EBCDIC-US", ""); +  file->data = conv->feed(file->data)->drain(); +  } +  if(objectp(file->file) && file->file->set_nonblocking) +  { +  // The list_stream object doesn't support nonblocking I/O, +  // but converts to ASCII anyway, so we don't have to do +  // anything about it. +  // But EBCDIC doen't work... +  file->file = ToEBCDICWrapper(file->file, 0, this_object()); +  } +  break; +  default: +  // "I" and "L" +  // Binary -- no conversion needed. +  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 +  ) ) { + // report_debug("ftp: using slowpipe\n"); +  pipe=((program)"slowpipe")(); +  } else { + // report_debug("ftp: using fastpipe\n"); +  pipe=((program)"fastpipe")(); //will use Stdio.sendfile if possible +  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) { //we are sure to be using slowpipe +  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); +  } +  +  static private void connected_to_receive(object fd, string data, string args) +  { +  DWRITE("FTP: connected_to_receive(X, %O, %O)\n", data, args); +  +  touch_me(); +  +  if (fd) { +  send(150, ({ sprintf("Opening %s mode data connection for %s.", +  modes[mode], args) })); +  } else { +  send(425, ({ "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: // "I" and "L" +  // Binary, no need to do anything. +  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; +  +  if (open_file(args, session, "STOR")) { +  if (!(session->file->pipe)) { +  if (fd) { +  BACKEND_CLOSE(fd); +  } +  switch(session->file->error) { +  case 401: +  send(530, ({ sprintf("%s: Need account for storing files.", args)})); +  break; +  case 413: +  send(550, ({ sprintf("%s: Quota exceeded.", args) })); +  break; +  case 501: +  send(502, ({ sprintf("%s: Command not implemented.", args) })); +  break; +  default: +  send(550, ({ sprintf("%s: Error opening file.", args) })); +  break; +  } +  session->conf->log(session->file, session); +  return; +  } +  master_session->file = session->file; +  } else { +  // Error message has already been sent. +  if (fd) { +  BACKEND_CLOSE(fd); +  } +  } +  } +  +  static private void discard_data_connection() { +  if(pasv_port && sizeof(pasv_accepted)) +  pasv_accepted = pasv_accepted[1..]; +  } +  +  static 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); +  } +  } +  +  static 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); +  } +  } +  +  /* +  * Command-line simulation +  */ +  +  // NOTE: base is modified destructably! +  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 })); +  } +  } +  +  static private string my_combine_path(string base, string part) +  { +  if ((sizeof(part) && (part[0] == '/')) || +  (sizeof(base) && (base[0] == '/'))) { +  return(combine_path(base, part)); +  } +  // Combine two relative paths. +  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(""); +  } +  } +  +  static private constant IFS = (<" ", "\t">); +  static private constant Quote = (< "\'", "\"", "\`", "\\" >); +  static private constant Specials = IFS|Quote; +  +  static private array(string) split_command_line(string cmdline) +  { +  // Check if we need to handle quoting at all... +  int need_quoting; +  foreach(indices(Quote), string c) { +  if (need_quoting = (search(cmdline, c) >= 0)) { +  break; +  } +  } +  if (!need_quoting) { +  // The easy case... +  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 "\`": +  // NOTE: We handle all of the above as \'. +  int j = search(cmdline, c, i+1); +  if (j == -1) { +  // No endquote! +  // Simulate one at EOL. +  j = sizeof(cmdline); +  } +  arg = (arg || "") + cmdline[i+1..j-1]; +  i = j; +  break; +  case "\\": +  i++; +  arg += cmdline[i..i]; +  break; +  case " ": +  case "\t": +  // IFS +  if (arg) { +  res += ({ arg }); +  arg = 0; +  } +  break; +  } +  argstart = i+1; +  } +  } +  if (argstart < i) { +  arg = (arg || "") + cmdline[argstart..]; +  } +  if (arg) { +  res += ({ arg }); +  } +  return res; +  } +  +  static 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++) { +  +  // Glob-expand args[index] +  +  array (int) st; +  +  // FIXME: Does not check if "*" or "?" was quoted! +  if (replace(args[index], ({"*", "?"}), ({ "", "" })) != args[index]) { +  +  // Globs in the file-name. +  +  array(string|array(string)) matches = ({ ({ }) }); +  multiset(string) paths; // Used to filter out duplicates. +  int i; +  foreach(my_combine_path("", args[index])/"/", string part) { +  paths = (<>); +  if (replace(part, ({"*", "?"}), ({ "", "" })) != part) { +  // Got a glob. +  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]]) { +  // Glob-expanding does not expand to files starting with '.' +  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 }); +  } +  } +  } +  } +  matches = new_matches; +  } else { +  // No glob +  // Just add the part. Modify matches in-place. +  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)) { +  // Array => string +  for (i=0; i < sizeof(matches); i++) { +  matches[i] *= "/"; +  } +  // Filter out non-existing or forbiden files/directories +  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); +  return(id->conf->stat_file(id->not_query, +  id)); +  }, cwd, master_session); +  if (sizeof(matches)) { +  args[index] = matches; +  } +  } +  } +  if (stringp(args[index])) { +  // No glob +  args[index] = ({ my_combine_path("", args[index]) }); +  } +  } +  return(args * ({})); +  } +  +  /* +  * LS handling +  */ +  +  static 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" }), +  }); +  +  static 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] }); +  }); +  +  static 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) +  { +  /* Parse options */ +  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"; +  // For logging purposes... +  session->not_query = Array.map(argv[1..], fix_path)*" "; +  +  mapping file = ([]); +  +  // The listings returned by a LIST or NLST command SHOULD use an +  // implied TYPE AN, unless the current type is EBCDIC, in which +  // case an implied TYPE EN SHOULD be used. +  // RFC 1123 4.1.2.7 +  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()); +  } +  +  if (!file->full_path) { +  file->full_path = argv[0]; +  } +  session->file = file; +  connect_and_send(file, session); +  } +  +  /* +  * Listings for Machine Processing +  */ +  +  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); +  } +  +  string make_MLSD_fact(string f, mapping(string:array) dir, object session) +  { +  array st = dir[f]; +  +  mapping(string:string) facts = ([]); +  +  // Construct the facts here. +  +  facts["UNIX.mode"] = st[0]; +  +  if (st[1] >= 0) { +  facts->size = (string)st[1]; +  facts->type = "File"; +  facts["media-type"] = session->conf->type_from_filename(f) || +  "application/octet-stream"; +  } else { +  facts->type = ([ "..":"pdir", ".":"cdir" ])[f] || "dir"; +  } +  +  facts->modify = make_MDTM(st[3]); +  +  facts->charset = "8bit"; +  +  // Construct and return the answer. +  +  return(Array.map(indices(facts), lambda(string s, mapping f) { +  return s + "=" + f[s]; +  }, facts) * ";" + " " + f); +  } +  +  void send_MLSD_response(mapping(string:array) dir, object session) +  { +  dir = dir || ([]); +  +  array f = indices(dir); +  +  session->file->data = sizeof(f) ? +  (Array.map(f, make_MLSD_fact, dir, session) * "\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,({ "OK" }) + +  Array.map(indices(dir), make_MLSD_fact, dir, session) + +  ({ "OK" }) ); +  } +  +  /* +  * Session handling +  */ +  +  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; // Session limit reached. +  } +  +  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; // Session limit reached. +  } +  +  return 1; +  } +  +  /* +  * FTP commands begin here +  */ +  +  // Set to 1 by EPSV ALL. +  int epsv_only; +  +  void ftp_REIN(string|int args) +  { +  logout(); +  +  // FIXME: What about EPSV ALL mode? RFC 2428 doesn't say. +  // I guess that it shouldn't be reset. +  +  // Compatibility... +  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; +  if (pasv_port) { +  destruct(pasv_port); +  pasv_port = 0; +  } +  if (args != 1) { +  // Not called by QUIT. +  send(220, ({ "Server ready for new user." })); +  } +  } +  +  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, ({ "Anonymous ftp, at your service" })); + #else /* !0 */ +  // ncftp doesn't like the above answer -- stupid program! +  send(331, ({ "Anonymous ftp accepted, send " +  "your complete e-mail address as password." })); + #endif /* 0 */ +  conf->log(([ "error":200 ]), master_session); +  } else { +  send(530, ({ +  sprintf("Too many anonymous users (%d).", +  port_obj->query_option("ftp_user_session_limit")) +  })); +  conf->log(([ "error":403 ]), master_session); +  } +  } else { +  send(530, ({ "Anonymous ftp disabled" })); +  conf->log(([ "error":403 ]), master_session); +  } +  } else { +  if (check_login()) { +  send(331, ({ sprintf("Password required for %s.", user) })); +  master_session->not_query = user; +  conf->log(([ "error":407 ]), master_session); +  } else { +  // Session limit exceeded. +  send(530, ({ +  sprintf("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, ({ "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("Too many anonymous users (%d).", +  port_obj->query_option("ftp_user_session_limit")) +  })); +  conf->log(([ "error":403 ]), master_session); +  } +  } else { +  send(503, ({ "Login with USER first." })); +  } +  return; +  } +  +  logout(); +  +  password = args||""; +  args = "CENSORED_PASSWORD"; // Censored in case of backtrace. +  master_session->method = "LOGIN"; +  master_session->realauth = user + ":" + password; +  master_session->not_query = user; +  +  master_session->misc->user = user; // Loophole for new API +  master_session->misc->password = password; // Otherwise we have to emulate +  // the Authentication header +  // Compatibility... +  m_delete(master_session->misc, "home"); +  +  auth_user = master_session->conf->authenticate(master_session); +  +  if (!auth_user) { +  if (!port_obj->query_option("guest_ftp")) { +  send(530, ({ sprintf("User %s access denied.", user) })); +  conf->log(([ "error":401 ]), master_session); +  } else { +  // Guest user. +  string u = user; +  user = 0; +  if (login()) { +  send(230, ({ sprintf("Guest user %s logged in.", u) })); +  logged_in = -1; +  conf->log(([ "error":200 ]), master_session); +  DWRITE("FTP: Guest-user: %O\n", master_session->realauth); +  } else { +  send(530, ({ +  sprintf("Too many anonymous/guest users (%d).", +  port_obj->query_option("ftp_user_session_limit")) +  })); +  conf->log(([ "error":403 ]), master_session); +  } +  } +  return; +  } +  +  // Authentication successful +  +  if (!port_obj->query_option("named_ftp") || +  !check_shell(auth_user->shell())) { +  send(530, ({ "You are not allowed to use named-ftp.", +  "Try using anonymous, or check /etc/shells" })); +  conf->log(([ "error":402 ]), master_session); +  auth_user = 0; +  return; +  } +  +  if (!login()) { +  send(530, ({ +  sprintf("Too many concurrent sessions (limit is %d).", +  port_obj->query_option("ftp_user_session_limit")) +  })); +  conf->log(([ "error":403 ]), master_session); +  return; +  } +  +  if (stringp(auth_user->homedir())) { +  // Check if it is possible to cd to the users home-directory. +  string home = auth_user->homedir(); +  if ((home == "") || (home[-1] != '/')) { +  home += "/"; +  } +  +  // Compatibility... +  master_session->misc->home = home; +  +  array(int)|object st = conf->stat_file(home, master_session); +  +  if (st && (st[1] < 0)) { +  cwd = home; +  } +  } +  logged_in = 1; +  send(230, ({ sprintf("User %s logged in.", user) })); +  conf->log(([ "error":202 ]), master_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; // Makes internal redirects to work. +  if (!st) { +  send(550, ({ sprintf("%s: No such file or directory, or access denied.", +  ncwd) })); +  session->conf->log(session->file || ([ "error":404 ]), session); +  return; +  } +  +  if (!(< -2, -3 >)[st[1]]) { +  send(504, ({ sprintf("%s: Not a directory.", ncwd) })); +  session->conf->log(([ "error":400 ]), session); +  return; +  } +  +  // CWD Successfull +  cwd = ncwd; +  +  array(string) reply = ({ sprintf("Current directory is now %s.", cwd) }); +  +  // Check for .messages etc +  session->method = "GET"; // Important +  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("Please read the file %s.", f), +  sprintf("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"; // Restore it again. +  send(250, reply); +  session->conf->log(([ "error":200, "len":sizeof(reply*"\n") ]), 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, ({ "Bye! It was nice talking to you!" })); +  send(0, 0); // EOF marker. +  +  master_session->method = "QUIT"; +  master_session->not_query = user || "Anonymous"; +  conf->log(([ "error":200 ]), master_session); +  +  // Reinitialize the connection. +  ftp_REIN(1); +  } +  +  void ftp_BYE(string args) +  { +  ftp_QUIT(args); +  } +  +  void ftp_PORT(string args) +  { +  if (epsv_only) { +  send(530, ({ "'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, ({ "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, ({ "PORT command ok ("+dataport_addr+ +  " port "+dataport_port+")" })); +  } +  } +  +  void ftp_EPRT(string args) +  { +  if (epsv_only) { +  send(530, ({ "'EPRT': Method not allowed in EPSV ALL mode." })); +  return; +  } +  +  if (sizeof(args) < 3) { +  send(501, ({ "I don't understand your parameters." })); +  return; +  } +  +  string delimiter = args[0..0]; +  if ((delimiter[0] <= 32) || (delimiter[0] >= 127)) { +  send(501, ({ "Invalid delimiter." })); +  } +  array(string) segments = args/delimiter; +  +  if (sizeof(args) != 4) { +  send(501, ({ "I don't understand your parameters." })); +  return; +  } +  if (segments[1] != "1") { +  // FIXME: No support for IPv6 yet. +  send(522, ({ "Network protocol not supported, use (1)" })); +  return; +  } +  if ((sizeof(segments[2]/".") != 4) || +  sizeof(replace(segments[2], ".0123456789"/"", allocate(11, "")))) { +  send(501, ({ sprintf("Bad IPv4 address: '%s'", segments[2]) })); +  return; +  } +  if (!((int)segments[3])) { +  send(501, ({ sprintf("Bad port number: '%s'", segments[3]) })); +  return; +  } +  dataport_addr = segments[2]; +  dataport_port = (int)segments[3]; +  +  if (pasv_port) { +  destruct(pasv_port); +  } +  send(200, ({ "EPRT command ok ("+dataport_addr+ +  " port "+dataport_port+")" })); +  } +  +  void ftp_PASV(string args) +  { +  // Required by RFC 1123 4.1.2.6 +  int min; +  int max; +  +  if (epsv_only) { +  send(530, ({ "'PASV': Method not allowed in EPSV ALL mode." })); +  return; +  } +  +  if(pasv_port) +  destruct(pasv_port); +  +  pasv_port = Stdio.Port(0, pasv_accept_callback, local_addr); +  /* FIXME: Hmm, getting the address from an anonymous port seems not +  * to work on NT... +  */ +  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, ({ "Requested action aborted: Out of ports." })); +  return; +  } +  } +  send(227, ({ sprintf("Entering Passive Mode. (%s,%d,%d)", +  replace(local_addr, ".", ","), +  (port>>8), (port&0xff)) })); +  } +  +  void ftp_EPSV(string args) +  { +  // Specified by RFC 2428: +  // Extensions for IPv6 and NATs. +  int min; +  int max; +  +  if (args && args != "1") { +  if (lower_case(args) == "all") { +  epsv_only = 1; +  send(200, ({ "Entering EPSV ALL mode." })); +  } else { +  // FIXME: No support for IPv6 yet. +  send(522, ({ "Network protocol not supported, use (1)" })); +  } +  return; +  } +  if (pasv_port) +  destruct(pasv_port); +  pasv_port = Stdio.Port(0, pasv_accept_callback, local_addr); +  /* FIXME: Hmm, getting the address from an anonymous port seems not +  * to work on NT... +  */ +  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, ({ "Requested action aborted: Out of ports." })); +  return; +  } +  } +  send(229, ({ sprintf("Entering Extended Passive Mode (|||%d|)", +  /* "1", local_addr,*/ port) })); +  } +  +  void ftp_TYPE(string args) +  { +  if (!expect_argument("TYPE", args)) { +  return; +  } +  +  args = upper_case(replace(args, ({ " ", "\t" }), ({ "", "" }))); +  +  // I and L8 are required by RFC 1123 4.1.2.1 +  switch(args) { +  case "L8": +  case "L": +  case "I": +  mode = "I"; +  break; +  case "A": +  mode = "A"; +  break; +  case "E": +  mode = "E"; +  break; +  default: +  send(504, ({ "'TYPE': Unknown type:"+args })); +  return; +  } +  +  send(200, ({ sprintf("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; +  +  if (open_file(args, session, "RETR")) { +  if (restart_point) { +  if (session->file->data) { +  if (sizeof(session->file->data) >= restart_point) { +  session->file->data = session->file->data[restart_point..]; +  restart_point = 0; +  } else { +  restart_point -= sizeof(session->file->data); +  m_delete(session->file, "data"); +  } +  } +  if (restart_point) { +  if (!(session->file->file && session->file->file->seek && +  (session->file->file->seek(restart_point) != -1))) { +  restart_point = 0; +  send(550, ({ "'RETR': Error restoring restart point." })); +  discard_data_connection(); +  return; +  } +  restart_point = 0; +  } +  } +  +  connect_and_send(session->file, session); +  } +  else +  discard_data_connection(); +  } +  +  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, ({ "'REST' ok" })); +  } +  +  void ftp_ABOR(string args) +  { +  if (curr_pipe) { +  catch { +  destruct(curr_pipe); +  }; +  curr_pipe = 0; +  send(426, ({ "Data transmission terminated." })); +  } +  send(226, ({ "'ABOR' Completed." })); +  } +  +  void ftp_PWD(string args) +  { +  send(257, ({ sprintf("\"%s\" is current directory.", cwd) })); +  } +  +  void ftp_XPWD(string args) +  { +  ftp_PWD(args); +  } +  +  /* +  * Handling of file moving +  */ +  +  static private string rename_from; // rename from +  +  void ftp_RNFR(string args) +  { +  if (!expect_argument("RNFR", args)) { +  return; +  } +  args = fix_path(args); +  +  RequestID session = RequestID2(master_session); +  +  session->method = "STAT"; +  +  if (stat_file(args, session)) { +  send(350, ({ sprintf("%s ok, waiting for destination name.", args) }) ); +  rename_from = args; +  } else { +  send(550, ({ sprintf("%s: no such file or permission denied.",args) }) ); +  } +  } +  +  void ftp_RNTO(string args) +  { +  if(!rename_from) { +  send(503, ({ "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("%s moved to %s.", rename_from, args) })); +  session->conf->log(([ "error":200 ]), session); +  } +  rename_from = 0; +  } +  +  +  void ftp_NLST(string args) +  { +  // ftp_MLST(args); return; +  +  array(string) argv = glob_expand_command_line("/usr/bin/ls " + (args||"")); +  +  call_ls(argv); +  } +  +  void ftp_LIST(string args) +  { +  // ftp_MLSD(args); return; +  +  ftp_NLST("-l " + (args||"")); +  } +  +  void ftp_MLST(string args) +  { +  args = fix_path(args || "."); +  +  RequestID session = RequestID2(master_session); +  +  session->method = "DIR"; +  +  array|object st = stat_file(args, session); +  +  if (st) { +  session->file = ([]); +  session->file->full_path = args; +  send_MLST_response(([ args:st ]), session); +  } else { +  send_error("MLST", args, session->file, 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; +  send_MLSD_response(session->conf->find_dir_stat(args, session), session); +  } else { +  if (st) { +  session->file->error = 405; +  } +  send_error("MLSD", args, session->file, session); +  discard_data_connection(); +  } +  } +  +  void ftp_DELE(string args) +  { +  if (!expect_argument("DELE", args)) { +  return; +  } +  +  args = fix_path(args); +  +  RequestID session = RequestID2(master_session); +  +  session->data = 0; +  session->misc->len = 0; +  session->method = "DELETE"; +  +  if (open_file(args, session, "DELE")) { +  send(250, ({ sprintf("%s deleted.", args) })); +  session->conf->log(([ "error":200 ]), session); +  return; +  } +  } +  +  void ftp_RMD(string args) +  { +  if (!expect_argument("RMD", args)) { +  return; +  } +  +  args = fix_path(args); +  +  RequestID session = RequestID2(master_session); +  +  session->data = 0; +  session->misc->len = 0; +  session->method = "DELETE"; +  +  array|object st = stat_file(args, session); +  +  if (!st) { +  send_error("RMD", args, session->file, session); +  return; +  } else if (st[1] != -2) { +  if (st[1] == -3) { +  send(504, ({ sprintf("%s is a module mountpoint.", args) })); +  session->conf->log(([ "error":405 ]), session); +  } else { +  send(504, ({ sprintf("%s is not a directory.", args) })); +  session->conf->log(([ "error":405 ]), session); +  } +  return; +  } +  +  if (open_file(args, session, "RMD")) { +  send(250, ({ sprintf("%s deleted.", args) })); +  session->conf->log(([ "error":200 ]), session); +  return; +  } +  } +  +  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 = 0; +  session->misc->len = 0; +  +  if (open_file(args, session, "MKD")) { +  send(257, ({ sprintf("\"%s\" created.", args) })); +  session->conf->log(([ "error":200 ]), session); +  return; +  } +  } +  +  void ftp_XMKD(string args) +  { +  ftp_MKD(args); +  } +  +  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, ({ "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]); +  })); +  a = Array.map(a, +  lambda(string s) { +  return(([ "REST":"REST STREAM", +  "MLST":"MLST UNIX.mode;size;type;modify;charset;media-type", +  "MLSD":"", +  ])[s] || s); +  }) - ({ "" }); +  +  send(211, ({ "The following features are supported:" }) + a + +  ({ "END" })); +  } +  +  void ftp_MDTM(string args) +  { +  if (!expect_argument("MDTM", args)) { +  return; +  } +  args = fix_path(args); +  RequestID session = RequestID2(master_session); +  session->method = "STAT"; +  mapping|array|object st = stat_file(args, session); +  +  if (!arrayp(st) && !objectp(st)) { +  send_error("MDTM", args, st, session); +  return; +  } +  send(213, ({ make_MDTM(st[3]) })); +  } +  +  void ftp_SIZE(string args) +  { +  if (!expect_argument("SIZE", args)) { +  return; +  } +  args = fix_path(args); +  +  RequestID session = RequestID2(master_session); +  session->method = "STAT"; +  mapping|array|object st = stat_file(args, session); +  +  if (!arrayp(st) && !objectp(st)) { +  send_error("SIZE", args, st, session); +  return; +  } +  int size = st[1]; +  if (size < 0) { +  send_error("SIZE", args, ([ "error":405, ]), session); +  return; +  // size = 512; +  } +  send(213, ({ (string)size })); +  } +  +  void ftp_STAT(string args) +  { +  // According to RFC 1123 4.1.3.3, this command can be sent during +  // a file-transfer. +  // RFC 959 4.1.3: +  // The command may be sent during a file transfer (along with the +  // Telnet IP and Synch signals--see the Section on FTP Commands) +  // in which case the server will respond with the status of the +  // operation in progress, [...] +  // FIXME: This is not supported yet. +  +  if ((< "", 0 >)[args]) { +  /* RFC 959 4.1.3: +  * If no argument is given, the server should return general +  * status information about the server FTP process. This +  * should include current values of all transfer parameters and +  * the status of connections. +  */ +  send(211, +  sprintf("%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", +  replace(fd->query_address(1), " ", ":"), +  roxen.version(), +  port_obj->sorted_urls * "\nListening on ", +  replace(fd->query_address(), " ", ":"), +  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); +  RequestID session = RequestID2(master_session); +  session->method = "STAT"; +  mapping|array|object st = stat_file(long); +  +  if (!arrayp(st) && !objectp(st)) { +  send_error("STAT", long, st, session); +  return; +  } +  +  string s = LS_L(master_session)->ls_l(args, st); +  +  send(213, sprintf("status of \"%s\":\n" +  "%s" +  "End of Status", args, s)/"\n"); +  } +  +  void ftp_NOOP(string args) +  { +  send(200, ({ "Nothing done ok" })); +  } +  +  void ftp_HELP(string args) +  { +  if ((< "", 0 >)[args]) { +  send(214, ({ +  "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 if ((args/" ")[0] == "SITE") { +  array(string) a = (upper_case(args)/" ")-({""}); +  if (sizeof(a) == 1) { +  send(214, ({ "The following SITE commands are recognized:", +  @(sprintf(" %#70s", sort(indices(site_help))*"\n")/"\n") +  })); +  } else if (site_help[a[1]]) { +  send(214, ({ sprintf("Syntax: SITE %s %s", a[1], site_help[a[1]]) })); +  } else { +  send(504, ({ sprintf("Unknown SITE command %s.", a[1]) })); +  } +  } else { +  args = upper_case(args); +  if (cmd_help[args]) { +  send(214, ({ sprintf("Syntax: %s %s%s", args, +  cmd_help[args], +  (this_object()["ftp_"+args]? +  "":"; unimplemented")) })); +  } else { +  send(504, ({ sprintf("Unknown command %s.", args) })); +  } +  } +  } +  +  void ftp_SITE(string args) +  { +  // Extended commands. +  // Site specific commands are required to be part of the site command +  // by RFC 1123 4.1.2.8 +  +  if ((< 0, "" >)[args]) { +  ftp_HELP("SITE"); +  return; +  } +  +  array a = (args/" ") - ({ "" }); +  +  if (!sizeof(a)) { +  ftp_HELP("SITE"); +  return; +  } +  a[0] = upper_case(a[0]); +  if (!site_help[a[0]]) { +  send(502, ({ sprintf("Bad SITE command: '%s'", a[0]) })); +  } else if (this_object()["ftp_SITE_"+a[0]]) { +  this_object()["ftp_SITE_"+a[0]](a[1..]); +  } else { +  send(502, ({ sprintf("SITE command '%s' is not currently supported.", +  a[0]) })); +  } +  } +  +  void ftp_SITE_CHMOD(array(string) args) +  { +  if (sizeof(args) < 2) { +  send(501, ({ sprintf("'SITE CHMOD %s': incorrect arguments", +  args*" ") })); +  return; +  } +  +  int mode; +  foreach(args[0] / "", string m) +  // We do this loop, instead of using a sscanf or cast to be able +  // to catch arguments which aren't an octal number like 0891. +  { +  mode *= 010; +  if(m[0] < '0' || m[0] > '7') +  { +  // This is not an octal number... +  mode = -1; +  break; +  } +  mode += (int)("0"+m); +  } +  if(mode == -1 || mode > 0777) +  { +  send(501, ({ "SITE CHMOD: mode should be between 0 and 0777" })); +  return; +  } +  +  string fname = fix_path(args[1..]*" "); +  RequestID session = RequestID2(master_session); +  +  session->method = "CHMOD"; +  session->misc->mode = mode; +  session->not_query = fname; +  if (open_file(fname, session, "CHMOD")) { +  send(250, ({ sprintf("Changed permissions of %s to 0%o.", +  fname, mode) })); +  session->conf->log(([ "error":200 ]), session); +  } +  } +  +  void ftp_SITE_UMASK(array(string) args) +  { +  if (sizeof(args) < 1) { +  send(501, ({ sprintf("'SITE UMASK %s': incorrect arguments", +  args*" ") })); +  return; +  } +  +  int mode; +  foreach(args[0] / "", string m) +  // We do this loop, instead of using a sscanf or cast to be able +  // to catch arguments which aren't an octal number like 0891. +  { +  mode *= 010; +  if(m[0] < '0' || m[0] > '7') +  { +  // This is not an octal number... +  mode = -1; +  break; +  } +  mode += (int)("0"+m); +  } +  if(mode == -1 || mode > 0777) +  { +  send(501, ({ "SITE UMASK: mode should be between 0 and 0777" })); +  return; +  } +  +  master_session->misc->umask = mode; +  send(250, ({ sprintf("Umask set to 0%o.", mode) })); +  } +  +  void ftp_SITE_PRESTATE(array(string) args) +  { +  if (!sizeof(args)) { +  master_session->prestate = (<>); +  send(200, ({ "Prestate cleared" })); +  } else { +  master_session->prestate = aggregate_multiset(@((args*" ")/","-({""}))); +  send(200, ({ "Prestate set" })); +  } +  } +  +  static private void timeout() +  { +  if (fd) { +  int t = (time() - time_touch); +  if (t > FTP2_TIMEOUT) { +  // Recomended by RFC 1123 4.1.3.2 +  send(421, ({ "Connection timed out." })); +  send(0,0); +  if (master_session->file) { +  if (objectp(master_session->file->file)) { +  destruct(master_session->file->file); +  } +  if (objectp(master_session->file->pipe)) { +  destruct(master_session->file->pipe); +  } +  } +  if (objectp(pasv_port)) { +  destruct(pasv_port); +  } +  master_session->method = "QUIT"; +  master_session->not_query = user || "Anonymous"; +  master_session->conf->log(([ "error":408 ]), master_session); +  } else { +  // Not time yet to sever the connection. +  call_out(timeout, FTP2_TIMEOUT + 30 - t); +  } +  } else { +  // We ought to be dead already... +  DWRITE("FTP2: Timeout on dead connection.\n"); +  destruct(); +  } +  } +  +  static private void got_command(mixed ignored, string line) +  { +  DWRITE("FTP2: got_command(X, \"%s\")\n", line); +  +  touch_me(); +  +  string cmd = line; +  string args; +  int i; +  +  if (line == "") { +  // The empty command. +  // Some stupid ftp-proxies send this. +  return; // Even less than a NOOP. +  } +  +  if ((i = search(line, " ")) != -1) { +  cmd = line[..i-1]; +  args = line[i+1..] - "\0"; +  } +  cmd = upper_case(cmd); +  +  if ((< "PASS" >)[cmd]) { +  // Censor line, so that the password doesn't show +  // in backtraces. +  line = cmd + " CENSORED_PASSWORD"; +  } +  + #if 0 +  if (!conf->extra_statistics) { +  conf->extra_statistics = ([ "ftp": (["commands":([ cmd:1 ])])]); +  } else if (!conf->extra_statistics->ftp) { +  conf->extra_statistics->ftp = (["commands":([ cmd:1 ])]); +  } else if (!conf->extra_statistics->ftp->commands) { +  conf->extra_statistics->ftp->commands = ([ cmd:1 ]); +  } else { +  conf->extra_statistics->ftp->commands[cmd]++; +  } + #endif /* 0 */ +  +  if (cmd_help[cmd]) { +  if (!logged_in) { +  if (!(< "REIN", "USER", "PASS", "SYST", +  "ACCT", "QUIT", "ABOR", "HELP" >)[cmd]) { +  send(530, ({ "You need to login first." })); +  +  return; +  } +  } +  if (this_object()["ftp_"+cmd]) { +  conf->requests++; + #if 1 +  mixed err; +  if (err = catch { +  this_object()["ftp_"+cmd](args); +  }) { +  report_error("Internal server error in FTP2\n" +  "Handling command %O\n%s\n", +  line, describe_backtrace(err)); +  } + #else +  roxen->handle(lambda(function f, string args, string line) { +  mixed err; +  if (err = catch { +  f(args); +  }) { +  report_error("Internal server error in FTP2\n" +  "Handling command %O\n%s\n", +  line, describe_backtrace(err)); +  } +  }, this_object()["ftp_"+cmd], args, line); + #endif +  } else { +  send(502, ({ sprintf("'%s' is not currently supported.", cmd) })); +  } +  } else { +  send(502, ({ sprintf("Unknown command '%s'.", cmd) })); +  } +  +  touch_me(); +  } +  +  void con_closed() +  { +  DWRITE("FTP2: con_closed()\n"); +  +  logout(); +  +  master_session->method = "QUIT"; +  master_session->not_query = user || "Anonymous"; +  conf->log(([ "error":204, "request_time":(time(1)-master_session->time) ]), +  master_session); +  +  if (fd) { +  fd->close(); +  } +  if (pasv_port) { +  destruct(pasv_port); +  pasv_port = 0; +  } +  // Make sure we disappear... +  destruct(); +  } +  +  void destroy() +  { +  DWRITE("FTP2: destroy()\n"); +  +  logout(); +  +  port_obj->sessions--; +  } +  +  void create(object fd, object c) +  { +  port_obj = c; +  +  // FIXME: Only supports one configuration! +  conf = port_obj->urls[port_obj->sorted_urls[0]]->conf; +  +  // Support delayed loading. +  if (!conf->inited) { +  conf->enable_all_modules(); +  } +  + #if 0 +  werror("FTP: conf:%O\n" +  "FTP:urls:%O\n", +  mkmapping(indices(conf), values(conf)), port_obj->urls); + #endif /* 0 */ +  +  master_session = RequestID2(); +  master_session->remoteaddr = (fd->query_address()/" ")[0]; +  master_session->conf = conf; +  master_session->port_obj = c; +  master_session->my_fd = fd; +  master_session->misc->defaulted = 1; +  ::create(fd, got_command, 0, con_closed, ([])); +  +  array a = fd->query_address(1)/" "; +  local_addr = a[0]; +  local_port = (int)a[1]; +  +  call_out(timeout, FTP2_TIMEOUT); +  +  string s = c->query_option("FTPWelcome"); +  +  s = replace(s, +  ({ "$roxen_version", "$roxen_build", "$full_version", +  "$pike_version", "$ident", }), +  ({ roxen->__roxen_version__, roxen->__roxen_build__, +  roxen->real_version, version(), roxen->version() })); +  +  send(220, s/"\n", 1); +  } + }; +  + void create(object f, object c) + { +  if (f) +  { +  c->sessions++; +  c->ftp_users++; +  FTPSession(f, c); +  } + }   Newline at end of file added.