Roxen.git / server / protocols / ftp.pike

version» Context lines:

Roxen.git/server/protocols/ftp.pike:1:   // This is a roxen protocol module.   // Copyright © 1997 - 2009, Roxen IS.      /*    * FTP protocol mk 2    * -  * $Id: ftp.pike,v 2.142 2010/08/09 09:12:17 grubba Exp $ +  * $Id$    *    * Henrik Grubbström <grubba@roxen.com>    */      /*    * TODO:    *    * How much is supposed to be logged?    */   
Roxen.git/server/protocols/ftp.pike:27:    *    * 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 2228 FTP Security Extensions +  * RFC 2389 Feature negotiation mechanism for the FTP    * RFC 2428 FTP Extensions for IPv6 and NATs -  +  * RFC 2640 Internationalization of the FTP +  * RFC 3659 Extensions to FTP +  * RFC 5797 FTP Command and Extension Registry +  * RFC 7151 FTP HOST Command for Virtual Hosts    * -  * 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 4217 Securing FTP with TLS    *    * RFC's describing gateways and proxies:    *    * RFC 1415 FTP-FTAM Gateway Specification -  +  * RFC 6384 FTP Application Layer Gateway (ALG) for IPv4-to-IPv6 Translation    *    * 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 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://rfc.roxen.com/) +  * (RFCs are available from http://pike.lysator.liu.se/docs/ietf/rfc/).    */       - #include <config.h> + #include <roxen.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)    -  + // Enable the use of handler threads. + #define FTP_USE_HANDLER_THREADS +    // #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) + //<locale-token project="prot_ftp">LOCALE</locale-token> + #define LOCALE(X,Y) _DEF_LOCALE("prot_ftp",X,Y) + // end of the locale related stuff +  +    #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    protected void trace_enter(mixed a, mixed b)    {
Roxen.git/server/protocols/ftp.pike:402:   }      // EBCDIC Wrappers here.      class ToEBCDICWrapper   {    inherit FileWrapper;       int converted;    -  protected object converter = Locale.Charset.encoder("EBCDIC-US", ""); +  protected Charset.Encoder converter = Charset.encoder("EBCDIC-US", "");       protected string convert(string s)    {    converted += sizeof(s);    return(converter->feed(s)->drain());    }   }      class FromEBCDICWrapper   {    inherit FileWrapper;       int converted;    -  protected object converter = Locale.Charset.decoder("EBCDIC-US"); +  protected Charset.Decoder converter = Charset.decoder("EBCDIC-US");       protected string convert(string s)    {    converted += sizeof(s);    return(converter->feed(s)->drain());    }   }         class PutFileWrapper
Roxen.git/server/protocols/ftp.pike:577:    string query_address(int|void loc)    {    if (!from_fd->query_address) {    werror("%O->query_address(%O)\n", from_fd, loc);    }    return from_fd->query_address(loc);    }   }       + protected string name_from_uid(RequestID master_session, int uid) + { +  string res; +  // NB: find_user_from_uid() can be quite slow(!), so we +  // cache the result for the duration of the connection. +  if (!master_session->misc->username_from_uid) { +  master_session->misc->username_from_uid = ([]); +  } else if (res = master_session->misc->username_from_uid[uid]) { +  return res; +  } +  User user; +  foreach(master_session->conf->user_databases(), UserDB user_db) { +  if (user = user_db->find_user_from_uid(uid)) { +  master_session->misc->username_from_uid[uid] = res = user->name(); +  return res; +  } +  } +  master_session->misc->username_from_uid[uid] = res = +  (uid?((string)uid):"root"); +  return res; + } +    // 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
Roxen.git/server/protocols/ftp.pike:624:    ({ S_IROTH, S_IROTH, 7, "r" }),    ({ S_IWOTH, S_IWOTH, 8, "w" }),    ({ S_IXOTH|S_ISVTX, S_IXOTH, 9, "x" }),    ({ S_IXOTH|S_ISVTX, S_ISVTX, 9, "T" }),    ({ S_IXOTH|S_ISVTX, S_IXOTH|S_ISVTX, 9, "t" })    });       protected constant months = ({ "Jan", "Feb", "Mar", "Apr", "May", "Jun",    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" });    -  protected 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";    }
Roxen.git/server/protocols/ftp.pike:660:    }       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]); +  user = name_from_uid(master_session, st[5]);    }       if (!stringp(st[6])) {    // FIXME: Convert st[6] to symbolic group name.    if (!st[6]) group = "wheel";    }    }       string ts;    int now = time(1);
Roxen.git/server/protocols/ftp.pike:710:    protected string cwd;    protected array(string) argv;    protected object ftpsession;       protected array(string) output_queue = ({});    protected int output_pos;    protected string output_mode = "A";       protected mapping(string:array|object) stat_cache = ([]);    -  protected object conv; +  protected Charset.Encoder conv;       protected array|object stat_file(string long, RequestID|void session)    {    array|object st = stat_cache[long];    if (zero_type(st)) {    session = RequestID2(session || master_session);    session->method = "DIR";    long = replace(long, "//", "/");    st = session->conf->stat_file(long, session);    stat_cache[long] = st;
Roxen.git/server/protocols/ftp.pike:813:    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" })) +    "\"";    } +  short = string_to_utf8(short);    if (flags & LS_FLAG_F) {    if (st[1] < 0) {    // Directory    short += "/";    } else if (st[0] & 0111) {    // Executable    short += "*";    }    }    int blocks = 1;
Roxen.git/server/protocols/ftp.pike:868:   #else    protected object(Stack.stack) dir_stack = Stack.stack();   #endif    protected int name_directories;       protected string fix_path(string s)    {    return(combine_path(cwd, s));    }    -  protected void list_next_directory() +  protected int(0..1) list_next_directory()    {    if (dir_stack->ptr) {    string short = dir_stack->pop();    string long = fix_path(short);       if ((!sizeof(long)) || (long[-1] != '/')) {    long += "/";    }    RequestID session = RequestID2(master_session);    session->method = "DIR";
Roxen.git/server/protocols/ftp.pike:902:    }       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 = dir || ([]); +  +  if (flags & LS_FLAG_a) { +  if (long != "/") {    dir[".."] = stat_file(combine_path(long,"../")); -  } else { -  dir = ([ "..":stat_file(combine_path(long,"../")) ]); +     } -  +  dir["."] = stat_file(combine_path(long));    } -  +     string listing = ""; -  if (dir && sizeof(dir)) { +  if (sizeof(dir)) {    if (!(flags & LS_FLAG_A)) {    foreach(indices(dir), string f) {    if (sizeof(f) && (f[0] == '.')) {    m_delete(dir, f);    }    }    } else if (!(flags & LS_FLAG_a)) {    foreach(indices(dir), string f) {    if ((< ".", ".." >)[f]) {    m_delete(dir, f);
Roxen.git/server/protocols/ftp.pike:964:    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. +  return 0;    } else {    name_directories = 1; -  +  return 1;    }    }       void set_blocking()    {    }       int query_fd()    {    return -1;    }       protected mixed id;       void set_id(mixed i)    {    id = i;    }    -  +  void fill_output_queue() +  { +  if (!sizeof(output_queue) || output_queue[-1]) { +  while (list_next_directory()) +  ; +  } +  } +     string read(int|void n, int|void not_all)    {    DWRITE("FTP: LSFile->read(%d, %d)\n", n, not_all);       ftpsession->touch_me();       while(sizeof(output_queue) <= output_pos) {    // Clean anything pending in the queue.    output_queue = ({});    output_pos = 0;
Roxen.git/server/protocols/ftp.pike:1031:       ::create(session_, flags_);       cwd = cwd_;    argv = argv_;    output_mode = output_mode_;    ftpsession = ftpsession_;       if (output_mode == "E") {    // EBCDIC -  conv = Locale.Charset.encoder("EBCDIC-US", ""); +  conv = Charset.encoder("EBCDIC-US", "");    }       array(string) files = allocate(sizeof(argv));    int n_files;       foreach(argv, string short) {    RequestID session = RequestID2(master_session);    session->method = "LIST";    string long = fix_path(short);    array|object st = stat_file(long, session);
Roxen.git/server/protocols/ftp.pike:1089:    }    }   }      class TelnetSession {    protected object fd;    protected object conf;       private mapping cb;    private mixed id; -  private function(mixed|void:string) write_cb; -  private function(mixed, string:void) read_cb; -  private function(mixed|void:void) close_cb; +  protected function(mixed|void:string) write_cb; +  protected function(mixed, string:void) read_cb; +  protected function(mixed|void:void) close_cb;       private constant TelnetCodes = ([    236:"EOF", // 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
Roxen.git/server/protocols/ftp.pike:1347:   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";    -  private constant cmd_help = ([ +  private mapping(string:string|Locale.DeferredLocale) 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)", +  "CLNT":LOCALE(1, "<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 7151 +  "HOST":LOCALE(2, "<sp> hostname (Host name)"),    -  +  // These are from RFC 3659 +  "MDTM":LOCALE(3, "<sp> path-name (Modification time)"), +  "SIZE":LOCALE(4, "<sp> path-name (Size)"), +  "MLST":LOCALE(5, "<sp> path-name (Machine Processing List File)"), +  "MLSD":LOCALE(6, "<sp> path-name (Machine Processing List Directory)"), +  +  // These are from RFC 2640 +  "LANG":LOCALE(7, "<sp> lang-tag (Change Interface Language)"), +     // 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)", +  "EPRT":LOCALE(8, "<sp> <d>net-prt<d>net-addr<d>tcp-port<d> (Extended Address Port)"), +  "EPSV":LOCALE(9, "[<sp> net-prt|ALL] (Extended Address Passive Mode)"),    -  +  // These are from RFC 2389 +  "FEAT":LOCALE(10, "(Feature list)"), +  "OPTS":LOCALE(11, "<sp> command <sp> options (Set Command-specific Options)"), +     // These are from RFC 2228 (FTP Security Extensions) -  "AUTH":"security-mechanism (Authentication/Security Mechanism)", -  "ADAT":"security-data (Authentication/Security Data)", -  "PBSZ":"<sp> size (Protection Buffer SiZe)", -  "PROT":"<sp> [ C | S | E | P ] (Data Channel Protection Level)", -  "CCC":"(Clear Command Channel)", -  "MIC":"command (Integrity Protected Command)", -  "CONF":"command (Confidentiality Protected Command)", -  "ENC":"command (Privacy Protected Command)", +  "AUTH":LOCALE(12, "security-mechanism (Authentication/Security Mechanism)"), +  "ADAT":LOCALE(13, "security-data (Authentication/Security Data)"), +  "PBSZ":LOCALE(14, "<sp> size (Protection Buffer SiZe)"), +  "PROT":LOCALE(15, "<sp> [ C | S | E | P ] (Data Channel Protection Level)"), +  "CCC":LOCALE(16, "(Clear Command Channel)"), +  "MIC":LOCALE(17, "command (Integrity Protected Command)"), +  "CONF":LOCALE(18, "command (Confidentiality Protected Command)"), +  "ENC":LOCALE(19, "command (Privacy Protected Command)"),       // These are in RFC 1639 -  "LPRT":"<sp> <long-host-port> (Long Port)", -  "LPSV":"(Long Passive)", +  "LPRT":LOCALE(20, "<sp> <long-host-port> (Long Port)"), +  "LPSV":LOCALE(21, "(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)", +  "USER":LOCALE(22, "<sp> username (Change user)"), +  "PASS":LOCALE(23, "<sp> password (Change password)"), +  "ACCT":LOCALE(24, "<sp> <account-information> (Account)"), +  "CWD":LOCALE(25, "[ <sp> directory-name ] (Change working directory)"), +  "CDUP":LOCALE(26, "(Change to parent directory)"), +  "SMNT":LOCALE(27, "<sp> <pathname> (Structure mount)"),    // Logout -  "REIN":"(Reinitialize)", -  "QUIT":"(Terminate service)", +  "REIN":LOCALE(28, "(Reinitialize)"), +  "QUIT":LOCALE(29, "(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)", +  "PORT":LOCALE(30, "<sp> b0, b1, b2, b3, b4 (Set port IP and number)"), +  "PASV":LOCALE(31, "(Set server in passive mode)"), +  "TYPE":LOCALE(32, "<sp> [ A | E | I | L ] (Ascii, Ebcdic, Image, Local)"), +  "STRU":LOCALE(33, "<sp> <structure-code> (File structure)"), +  "MODE":LOCALE(34, "<sp> <mode-code> (Transfer mode)"),    // 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)", +  "ALLO":LOCALE(35, "<sp> <decimal-integer> [<sp> R <sp> <decimal-integer>]" +  " (Allocate space for file)"), +  "REST":LOCALE(36, "<sp> marker (Set restart marker)"), +  "STOR":LOCALE(37, "<sp> file-name (Store file)"), +  "STOU":LOCALE(38, "(Store file with unique name)"), +  "RETR":LOCALE(39, "<sp> file-name (Retreive file)"), +  "LIST":LOCALE(40, "[ <sp> <pathname> ] (List directory)"), +  "NLST":LOCALE(40, "[ <sp> <pathname> ] (List directory)"), +  "APPE":LOCALE(41, "<sp> <pathname> (Append file)"), +  "RNFR":LOCALE(42, "<sp> <pathname> (Rename from)"), +  "RNTO":LOCALE(43, "<sp> <pathname> (Rename to)"), +  "DELE":LOCALE(44, "<sp> file-name (Delete file)"), +  "RMD":LOCALE(45, "<sp> <pathname> (Remove directory)"), +  "MKD":LOCALE(46, "<sp> <pathname> (Make directory)"), +  "PWD":LOCALE(47, "(Return current directory)"), +  "ABOR":LOCALE(48, "(Abort current transmission)"),    // Informational commands -  "SYST":"(Get type of operating system)", -  "STAT":"[ <sp> <pathname> ] (Status for server/file)", -  "HELP":"[ <sp> <string> ] (Give help)", +  "SYST":LOCALE(49, "(Get type of operating system)"), +  "STAT":LOCALE(50, "[ <sp> <pathname> ] (Status for server/file)"), +  "HELP":LOCALE(51, "[ <sp> <string> ] (Give help)"),    // Miscellaneous commands -  "SITE":"<sp> <string> (Site parameters)", // Has separate help -  "NOOP":"(No operation)", +  "SITE":LOCALE(52, "<sp> <string> (Site parameters)"),// Has separate help +  "NOOP":LOCALE(53, "(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)", +  "XMKD":LOCALE(54, "<sp> path-name (Make directory)"), +  "XRMD":LOCALE(55, "<sp> path-name (Remove directory)"), +  "XPWD":LOCALE(47, "(Return current directory)"), +  "XCWD":LOCALE(25, "[ <sp> directory-name ] (Change working directory)"), +  "XCUP":LOCALE(26, "(Change to parent directory)"),       // 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)", +  "MAIL":LOCALE(56, "[<sp> <recipient name>] (Mail to user)"), +  "MSND":LOCALE(57, "[<sp> <recipient name>] (Mail send to terminal)"), +  "MSOM":LOCALE(58, "[<sp> <recipient name>] (Mail send to terminal or mailbox)"), +  "MSAM":LOCALE(59, "[<sp> <recipient name>] (Mail send to terminal and mailbox)"), +  "MRSQ":LOCALE(60, "[<sp> <scheme>] (Mail recipient scheme question)"), +  "MRCP":LOCALE(61, "<sp> <recipient name> (Mail recipient)"),       // These are in RFC 743 -  "XRSQ":"[<sp> <scheme>] (Scheme selection)", -  "XRCP":"<sp> <recipient name> (Recipient specification)", +  "XRSQ":LOCALE(62, "[<sp> <scheme>] (Scheme selection)"), +  "XRCP":LOCALE(63, "<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)", +  "XSEN":LOCALE(64, "[<sp> <recipient name>] (Send to terminal)"), +  "XSEM":LOCALE(65, "[<sp> <recipient name>] (Send, mail if can\'t)"), +  "XMAS":LOCALE(66, "[<sp> <recipient name>] (Mail and send)"),       // These are in RFC 542 -  "BYE":"(Logout)", -  "BYTE":"<sp> <bits> (Byte size)", -  "SOCK":"<sp> host-socket (Data socket)", +  "BYE":LOCALE(67, "(Logout)"), +  "BYTE":LOCALE(68, "<sp> <bits> (Byte size)"), +  "SOCK":LOCALE(69, "<sp> host-socket (Data socket)"),    -  + #if 0 +  // These are in RFC 475 +  "MLTO":LOCALE(70, "<sp> <recipient name> (Initiate mail to user)"), +  "FROM":LOCALE(71, "<sp> <sender name> (Mail from)"), +  "MTYP":LOCALE(72, "<sp> [ U | O | L ] (Mail type)"), +  "RECO":LOCALE(73, "[<sp> <mail unique id>] (Mail record)"), + #if 0 +  // NB: Conflicts with AUTH from RFC 2228 above. +  "AUTH":LOCALE(74, "<sp> <author id> (Mail author)"), + #endif +  "TITL":LOCALE(75, "<sp> <title> (Mail title/subject)"), +  "ACKN":LOCALE(76, "(Mail acknowledge)"), +  "TEXT":LOCALE(77, "(Mail text)"), +  "FILE":LOCALE(78, "<sp> <filename> (Mail file)"), +  "CITA":LOCALE(79, "<sp> <file name> (Mail citation)"), + #endif +     // This one is referenced in a lot of old RFCs -  "MLFL":"(Mail file)", +  "MLFL":LOCALE(80, "(Mail file)"),    ]);    -  private constant site_help = ([ -  "CHMOD":"<sp> mode <sp> file", -  "UMASK":"<sp> mode", -  "PRESTATE":"<sp> prestate", +  private mapping(string:string|Locale.DeferredLocale) site_help = ([ +  "CHMOD":LOCALE(81, "<sp> mode <sp> file"), +  "UMASK":LOCALE(82, "<sp> mode"), +  "PRESTATE":LOCALE(83, "<sp> prestate"),    ]);    -  +  private mapping(string:string|Locale.DeferredLocale) opts_help = ([ +  "MLST":LOCALE(84, "<sp> <fact-list>"), +  ]); +     private constant modes = ([    "A":"ASCII",    "E":"EBCDIC",    "I":"BINARY",    "L":"LOCAL",    ]);       private int time_touch = time();       private object(ADT.Queue) to_send = ADT.Queue();
Roxen.git/server/protocols/ftp.pike:1500:    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(); +  string|int s = to_send->get();    -  DWRITE("FTP2: write_cb(): Sending \"%s\"\n", s); + #if constant(SSL.File) +  if (s == 1) { +  DWRITE("FTP2: write_cb(): STARTTLS.\n");    -  +  // NB: This callback is only called when the send buffers +  // are empty, and it is thus safe to switch to TLS. +  +  // Switch to TLS. +  if (!fd->renegotiate) { +  fd = SSL.File(fd, port_obj->ctx); +  master_session->my_fd = fd; +  fd->accept(); +  } +  // Restore the callbacks in the new SSL connection. +  ::set_write_callback(write_cb); +  return ""; +  } else if (s == 2) { +  DWRITE("FTP2: write_cb(): ENDTLS.\n"); +  +  if (fd->renegotiate && +  !has_prefix(sprintf("%O", port_obj), "SSLProtocol")) { +  // Deactive StartTLS connection. +  master_session->my_fd = fd = fd->shutdown(); +  +  if (fd) { +  // Move the callbacks back to the raw connection. +  ::set_write_callback(write_cb); +  } +  } +  return ""; +  } + #endif +  +  DWRITE("FTP2: write_cb(): Sending %O.\n", s); +     if ((to_send->is_empty()) && (!end_marker)) {    ::set_write_callback(0); -  } else { +  } else if (stringp(to_send->peek())) { +  // Not about to switch TLS mode.    ::set_write_callback(write_cb);    }    return(s);    }    }       int(0..1) busy;    -  void send(int code, array(string) data, int|void enumerate_all) + #ifdef FTP_USE_HANDLER_THREADS + #define next_cmd() call_out(low_next_cmd, 0) + #else + #define low_next_cmd() next_cmd() + #endif +  +  void low_send(int code, array(string) data, int|void enumerate_all)    { -  DWRITE("FTP2: send(%d, %O)\n", code, data); +  DWRITE("FTP2: low_send(%d, %O)\n", code, data);       if (!data || end_marker) {    end_marker = 1;    ::set_write_callback(write_cb); -  if (code >= 200) { -  // Command finished, get the next. -  busy = 0; -  next_cmd(); -  } +     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); +  to_send->put(string_to_utf8(s));    ::set_write_callback(write_cb);    } else { -  to_send->put(s); +  to_send->put(string_to_utf8(s));    }    } else {    DWRITE("FTP2: send(): Nothing to send!\n");    } -  +  } +  +  void send(int code, array(string) data, int|void enumerate_all) +  { +  DWRITE("FTP2: send(%d, %O)\n", code, data); +  +  low_send(code, data, enumerate_all); +     if (code >= 200) {    // Command finished, get the next.    busy = 0;    next_cmd();    }    }       private RequestID master_session;       private string dataport_addr;
Roxen.git/server/protocols/ftp.pike:1595:    // the corresponding control connection to port L.    // RFC 1123 4.1.2.12    string local_addr;    int local_port;    string e_mode = "1"; /* IPv4 */       // The listen port object    roxen.Protocol port_obj;       /* +  * Locale & Language handling +  */ +  +  protected string format_langlist() +  { +  array(string) langs = Locale.list_languages("prot_ftp") + ({}); +  string current = roxen.get_locale(); +  +  foreach(langs; int i; string lang) { +  if (lang == current) { +  langs[i] = current + "*"; +  current = 0; +  break; +  } +  } +  +  if (current) { +  langs += ({ current + "*" }); +  } +  +  return sort(langs) * ";"; +  } +  +  protected void restore_locale() +  { +  string lang = master_session->misc["accept-language"]; +  if (lang) { +  roxen.set_locale(lang); +  } else { +  roxen.set_locale(); +  } +  } +  +  /*    * Misc    */       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.
Roxen.git/server/protocols/ftp.pike:1632:    return 0;    }    }    return(allowed_shells[shell]);    }    return 1;    }       private string fix_path(string s)    { +  mixed err = catch { s = utf8_to_string(s); }; +  if (String.width(s) > 8) { +  // Wide, so it might contain combiners. +  // Combine them if they are there. +  s = Unicode.normalize(s, "NFC"); +  }    if (!sizeof(s)) {    if (cwd[-1] == '/') {    return(cwd);    } else {    return(cwd + "/");    }    } else if (s[0] == '~') {    return(combine_path("/", s));    } else if (s[0] == '/') {    return(simplify_path(s));
Roxen.git/server/protocols/ftp.pike:1673:    // 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 (use_ssl) { -  fd = SSL.sslfile (fd, port_obj->ctx); -  DWRITE("FTP: Created an sslfile: %O\n", fd); -  } +     if(pasv_callback) {    pasv_callback(fd, "", @pasv_args);    pasv_callback = 0;    } else {    pasv_accepted += ({ fd });    }    }    }    }   
Roxen.git/server/protocols/ftp.pike:1719:    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(); +  object(Stdio.File)|object(SSL.File) f = Stdio.File();       // FIXME: Race-condition: open_socket() for other connections will fail    // until the socket has been connected.       object privs; -  + #ifndef FTP2_USE_ANY_SOURCE_PORT    if(local_port-1 < 1024 && geteuid())    privs = Privs("FTP: Opening the data connection on " + local_addr +    ":" + (local_port-1) + ".");       if(!f->open_socket(local_port-1, local_addr))    {    privs = 0;    DWRITE("FTP: socket(%d, %O) failed. Trying with any port.\n",    local_port-1, local_addr);    -  + #endif    if(!f->open_socket(0, local_addr))    {    DWRITE("FTP: socket(0, %O) failed. "    "Trying with any port, any ip.\n", local_addr);    if (!f->open_socket()) {    DWRITE("FTP: socket() failed. Out of sockets?\n");    fun(0, 0, @args);    destruct(f);    return;    }    } -  + #ifndef FTP2_USE_ANY_SOURCE_PORT    }    privs = 0; -  + #endif       Stdio.File raw_connection = f;    -  if (use_ssl) { -  f = (object) SSL.sslfile (f, port_obj->ctx, 1, 0); -  } -  +     f->set_nonblocking(lambda(mixed ignored, string data) {    DWRITE("FTP: async_connect ok. Got data.\n");    f->set_nonblocking(0,0,0,0,0);    fun(f, data, @args);    },    lambda(mixed ignored) {    DWRITE("FTP: async_connect ok.\n");    f->set_nonblocking(0,0,0,0,0);    fun(f, "", @args);    },    lambda(mixed ignored) { -  DWRITE("FTP: connect_and_send failed\n"); +  DWRITE("FTP: connect_and_send failed: %s (%d)\n", +  strerror(f->errno()), f->errno());    destruct(f);    fun(0, 0, @args);    },    lambda(mixed ignored) { -  DWRITE("FTP: connect_and_send failed\n"); +  DWRITE("FTP: connect_and_send failed (oob): %s (%d)\n", +  strerror(f->errno()), f->errno());    destruct(f);    fun(0, 0, @args);    });      #ifdef FD_DEBUG    mark_fd(raw_connection->query_fd(),    sprintf("ftp communication: %s:%d -> %s:%d",    local_addr, local_port - 1,    dataport_addr, dataport_port));   #endif    -  if(catch{ +  if(mixed err = catch{    if (!(raw_connection->connect(dataport_addr, dataport_port))) {    DWRITE("FTP: connect(%O, %O) failed with: %s!\n"    "FTP: local_addr: %O:%O (%O)\n",    dataport_addr, dataport_port,    strerror(raw_connection->errno()), -  local_addr, local_port-1, raw_connection->query_address(1)); +  local_addr, local_port-1, +  raw_connection->is_open() && raw_connection->query_address(1));    destruct(f);    fun(0, 0, @args);    return;    }    }) { -  DWRITE("FTP: Illegal internet address in connect in async comm.\n"); +  DWRITE("FTP: Illegal IP address (%s:%d) in async connect.\n", +  dataport_addr||"", dataport_port); +  DWRITE("FTP: %s\n", describe_backtrace(err));    destruct(f);    fun(0, 0, @args);    return;    }    }       /*    * Data connection handling    */ -  +  +  enum SSLMode { +  SSL_NONE = 0, +  SSL_ACTIVE = 1, +  SSL_PASSIVE = 2, +  SSL_ALL = 3, +  }; +  +  // Set to SSL_ALL by PROT S,E and P. +  // Cleared by PROT C. +  // Set to SSL_ACTIVE by AUTH SSL. +  SSLMode use_ssl; +     private void send_done_callback(array(object) args)    {    DWRITE("FTP: send_done_callback()\n");       object fd = args[0];    object session = args[1];       if(fd)    {    //DWRITE("FTP: fd: %O: %O\n", fd, mkmapping(indices(fd), values(fd)));
Roxen.git/server/protocols/ftp.pike:1829:    fd = 0;    //BACKEND_CLOSE(fd);    }    curr_pipe = 0;       if (session && session->file) {    session->conf->log(session->file, session);    session->file = 0;    }    destruct(session); -  send(226, ({ "Transfer complete." })); +  send(226, ({ LOCALE(85, "Transfer complete.") }));    }       private mapping|array|object stat_file(string fname,    object|void session)    {    mapping file;       session = RequestID2(session || master_session);    session->method = "STAT";    session->not_query = fname;
Roxen.git/server/protocols/ftp.pike:1858:    fname = replace(fname, "//", "/");    file = conf->stat_file(fname, session);    }    destruct(session);    return file;    }       private int expect_argument(string cmd, string args)    {    if ((< "", 0 >)[args]) { -  send(504, ({ sprintf("Syntax: %s %s", cmd, cmd_help[cmd]) })); +  send(504, ({ sprintf(LOCALE(86, "Syntax: %s %s"), cmd, cmd_help[cmd]) }));    return 0;    }    return 1;    }       private void send_error(string cmd, string f, mapping file,    object session)    {    switch(file && file->error) {    case 301:    case 302:    if (file->extra_heads && file->extra_heads->Location) { -  send(504, ({ sprintf("'%s': %s: Redirect to %O.", +  send(504, ({ sprintf(LOCALE(87, "'%s': %s: Redirect to %O."),    cmd, f, file->extra_heads->Location) }));    } else { -  send(504, ({ sprintf("'%s': %s: Redirect.", cmd, f) })); +  send(504, ({ sprintf(LOCALE(88, "'%s': %s: Redirect."), cmd, f) }));    }    break;    case 401: -  send(530, ({ sprintf("'%s': %s: Access denied.", +  send(530, ({ sprintf(LOCALE(89, "'%s': %s: Access denied."),    cmd, f) }));    break;    case 403: -  send(451, ({ sprintf("'%s': %s: Forbidden.", +  send(451, ({ sprintf(LOCALE(90, "'%s': %s: Forbidden."),    cmd, f) }));    break;    case 405: -  send(550, ({ sprintf("'%s': %s: Method not allowed.", +  send(550, ({ sprintf(LOCALE(91, "'%s': %s: Method not allowed."),    cmd, f) }));    break;    case 500: -  send(451, ({ sprintf("'%s': Requested action aborted: " -  "local error in processing.", cmd) })); +  send(451, ({ sprintf(LOCALE(93, "'%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.", +  send(550, ({ sprintf(LOCALE(94, "'%s': %s: No such file or directory."),    cmd, f) }));    break;    }    session->conf->log(file, session);    }       private mapping open_file(string fname, object session, string cmd)    {    object|array|mapping file;       file = stat_file(fname, session);       // The caller is assumed to have made a new session object for us    // but not to set not_query in it..    session->not_query = fname;       if (objectp(file) || arrayp(file)) {    array|object st = file;    file = 0;    if (st && (st[1] < 0) && !((<"RMD", "XRMD", "CHMOD">)[cmd])) { -  send(550, ({ sprintf("%s: not a plain file.", fname) })); +  send(550, ({ sprintf(LOCALE(95, "%s: not a plain file."), fname) }));    return 0;    }    mixed err;    if ((err = catch(file = conf->get_file(session)))) {    report_error("FTP: Error opening file \"%s\"\n"    "%s\n", fname, describe_backtrace(err)); -  send(550, ({ sprintf("%s: Error, can't open file.", fname) })); +  send(550, ({ sprintf(LOCALE(96, "%s: Error, can't open file."), fname) }));    return 0;    }    } else if ((< "STOR", "APPE", "MKD", "XMKD", "MOVE" >)[cmd]) {    mixed err;    if ((err = catch(file = conf->get_file(session)))) {    report_error("FTP: Error opening file \"%s\"\n"    "%s\n", fname, describe_backtrace(err)); -  send(550, ({ sprintf("%s: Error, can't open file.", fname) })); +  send(550, ({ sprintf(LOCALE(96, "%s: Error, can't open file."), fname) }));    return 0;    }    }       // file is a mapping.       session->file = file;       if (!file || (file->error && (file->error >= 300))) {    DWRITE("FTP: open_file(\"%s\") failed: %O\n", fname, file);
Roxen.git/server/protocols/ftp.pike:1975:    return file;    }       private void connected_to_send(object fd, string ignored,    mapping file, object session)    {    DWRITE("FTP: connected_to_send(%O, %O, %O, X)\n", fd, ignored, file);       touch_me();    +  restore_locale(); +     if(!file->len)    file->len = file->data?(stringp(file->data)?strlen(file->data):0):0;       if (!file->mode) {    file->mode = mode;    }       if(fd)    {    if (file->len) { -  send(150, ({ sprintf("Opening %s data connection for %s (%d bytes).", +  send(150, ({ sprintf(LOCALE(97, "Opening %s data connection for %s (%d bytes)."),    modes[file->mode], file->full_path, file->len) }));    } else { -  send(150, ({ sprintf("Opening %s mode data connection for %s", +  send(150, ({ sprintf(LOCALE(98, "Opening %s mode data connection for %s"),    modes[file->mode], file->full_path) }));    } -  +  +  SSLMode ssl_mask = SSL_ACTIVE; +  if (pasv_port) ssl_mask = SSL_PASSIVE; +  + #if constant(SSL.File) +  if (use_ssl & ssl_mask) { +  DWRITE("FTP: Initiating SSL/TLS connection.\n"); +  +  // RFC 4217 7: +  // For i) and ii), the FTP client MUST be the TLS client and the FTP +  // server MUST be the TLS server. +  // +  // That is to say, it does not matter which side initiates the +  // connection with a connect() call or which side reacts to the +  // connection via the accept() call; the FTP client, as defined in +  // [RFC-959], is always the TLS client, as defined in [RFC-2246]. +  fd = SSL.File(fd, port_obj->ctx); +  fd->accept(); +  DWRITE("FTP: Created an sslfile: %O\n", fd);    } -  + #endif +  }    else    { -  send(425, ({ "Can't build data connect: Connection refused." })); +  send(425, ({ LOCALE(99, "Can't build data connect: Connection refused.") }));    destruct(session);    return;    }    switch(file->mode) {    case "A":    if (file->data) {    file->data = replace(file->data,    ({ "\r\n", "\n", "\r" }),    ({ "\r\n", "\r\n", "\r\n" }));    }
Roxen.git/server/protocols/ftp.pike:2016:    {    // 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", ""); +  Charset.Encoder conv = 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());    }
Roxen.git/server/protocols/ftp.pike:2085:    curr_pipe = pipe;    pipe->output(fd);    }       private void connected_to_receive(object fd, string data, string args)    {    DWRITE("FTP: connected_to_receive(X, %O, %O)\n", data, args);       touch_me();    +  restore_locale(); +     if (fd) { -  send(150, ({ sprintf("Opening %s mode data connection for %s.", +  send(150, ({ sprintf(LOCALE(100, "Opening %s mode data connection for %s."),    modes[mode], args) })); -  +  +  SSLMode ssl_mask = SSL_ACTIVE; +  if (pasv_port) ssl_mask = SSL_PASSIVE; +  + #if constant(SSL.File) +  if (use_ssl & ssl_mask) { +  DWRITE("FTP: Initiating SSL/TLS connection.\n"); +  +  fd = SSL.File(fd, port_obj->ctx); +  fd->accept(); +  DWRITE("FTP: Created an sslfile: %O\n", fd); +  } + #endif    } else { -  send(425, ({ "Can't build data connect: Connection refused." })); +  send(425, ({ LOCALE(99, "Can't build data connect: Connection refused.") }));    return;    }       data = data && sizeof(data) && data;       switch(mode) {    case "A":    fd = FromAsciiWrapper(fd, data, this_object());    break;    case "E":
Roxen.git/server/protocols/ftp.pike:2121:    session->misc->len = 0x7fffffff;       mapping file;    if (file = open_file(args, session, "STOR")) {    if (!(file->pipe)) {    if (fd) {    BACKEND_CLOSE(fd);    }    switch(file->error) {    case 401: -  send(530, ({ sprintf("%s: Need account for storing files.", args)})); +  send(530, ({ sprintf(LOCALE(101, "%s: Need account for storing files."), args)}));    break;    case 413: -  send(550, ({ sprintf("%s: Quota exceeded.", args) })); +  send(550, ({ sprintf(LOCALE(102, "%s: Quota exceeded."), args) }));    break;    case 501: -  send(502, ({ sprintf("%s: Command not implemented.", args) })); +  send(502, ({ sprintf(LOCALE(103, "%s: Command not implemented."), args) }));    break;    default: -  send(550, ({ sprintf("%s: Error opening file.", args) })); +  send(550, ({ sprintf(LOCALE(104, "%s: Error opening file."), args) }));    break;    }    session->conf->log(file, session);    destruct(session);    return;    }    master_session->file = file;    } else {    // Error message has already been sent.    if (fd) {
Roxen.git/server/protocols/ftp.pike:2533:    }    if (flags & (LS_FLAG_f|LS_FLAG_C|LS_FLAG_m)) {    flags &= ~LS_FLAG_l;    }    if (flags & LS_FLAG_C) {    flags &= ~LS_FLAG_m;    }       file->file = LSFile(cwd, argv[1..], flags, session,    file->mode, this_object()); +  + #ifdef FTP_USE_HANDLER_THREADS +  // Create the listing synchronously when running in +  // a handler thread. +  file->file && file->file->fill_output_queue(); + #endif    }       if (!file->full_path) {    file->full_path = argv[0];    }    session->file = file;    connect_and_send(file, session);    }       /*    * Listings for Machine Processing    */    -  +  constant supported_mlst_facts = (< +  "size", "type", "modify", "charset", "media-type", +  "unix.mode", "unix.atime", "unix.ctime", "unix.uid", "unix.gid", +  "unix.ownername", "unix.groupname", +  >); +  +  multiset(string) current_mlst_facts = (< +  "size", "type", "modify", "unix.mode", +  "unix.ownername", "unix.groupname", +  >); +  +  protected string format_factlist(multiset(string) all, +  multiset(string)|void selected) +  { +  if (!selected) selected = (<>); +  +  string ret = ""; +  foreach(sort(indices(all)), string fact) { +  ret += fact; +  if (selected[fact]) { +  ret += "*"; +  } +  ret += ";"; +  } +  return ret; +  } +     string make_MDTM(int t)    {    mapping lt = gmtime(t);    return sprintf("%04d%02d%02d%02d%02d%02d",    lt->year + 1900, lt->mon + 1, lt->mday,    lt->hour, lt->min, lt->sec);    }    -  string make_MLSD_fact(string f, mapping(string:array) dir, object session) +  mapping(string:string) make_MLSD_facts(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"; +  facts->type = "file"; +  if (current_mlst_facts["media-type"]) { +  string|array(string) ct = session->conf->type_from_filename(f); +  if (arrayp(ct)) { +  ct = (sizeof(ct) > 1) && ct[1]; +  } +  facts["media-type"] = ct || "application/octet-stream"; +  }    } else { -  +  if (!current_mlst_facts->type) { +  if ((< "..", "." >)[f]) { +  // RFC 3659 7.5.1.2: +  // +  // The type=cdir fact indicates the listed entry contains a +  // pathname of the directory whose contents are listed. An +  // entry of this type will only be returned as a part of the +  // result of an MLSD command when the type fact is included, +  // and provides a name for the listed directory, and facts +  // about that directory. In a sense, it can be viewed as +  // representing the title of the listing, in a machine +  // friendly format. It may appear at any point of the +  // listing, it is not restricted to appearing at the start, +  // though frequently may do so, and may occur multiple +  // times. It MUST NOT be included if the type fact is not +  // included, or there would be no way for the user-PI to +  // distinguish the name of the directory from an entry in +  // the directory. +  return 0; +  } +  } else {    facts->type = ([ "..":"pdir", ".":"cdir" ])[f] || "dir";    } -  +  }    -  facts->modify = make_MDTM(st[3]); +  facts->modify = make_MDTM(st[3]); /* mtime */       facts->charset = "8bit";    -  // Construct and return the answer. +  // FIXME: Consider adding support for the "unique" fact. +  // Typically based on dev-no + inode-no.    -  return(Array.map(indices(facts), lambda(string s, mapping f) { -  return s + "=" + f[s]; -  }, facts) * ";" + " " + f); +  // FIXME: Consider adding support for the "perm" fact. +  +  // Facts from +  // https://www.iana.org/assignments/os-specific-parameters/os-specific-parameters.xml +  facts["unix.atime"] = make_MDTM(st[2]); /* atime */ +  facts["unix.ctime"] = make_MDTM(st[4]); /* ctime */ +  +  // NOTE: SiteBuilder may set st[5] and st[6] to strings. +  if (stringp(st[5])) { +  facts["unix.ownername"] = st[5]; +  } else { +  facts["unix.ownername"] = name_from_uid(master_session, st[5]);    } -  +  if (stringp(st[6])) { +  facts["unix.groupname"] = st[6]; +  } else if (!st[6]) { +  facts["unix.groupname"] = "wheel"; +  } else { +  facts["unix.groupname"] = (string)st[6]; +  }    -  +  // Defacto standard facts here. +  // Cf eg https://github.com/giampaolo/pyftpdlib +  facts["unix.mode"] = sprintf("0%o", st[0]); /* mode */ +  if (intp(st[5])) { +  facts["unix.uid"] = sprintf("%d", st[5]); /* uid */ +  } +  if (intp(st[6])) { +  facts["unix.gid"] = sprintf("%d", st[6]); /* gid */ +  } +  +  // filter and return the answer. +  +  return facts & current_mlst_facts; +  } +  +  string format_MLSD_facts(mapping(string:string) facts) +  { +  if (!facts) return 0; +  return map(sort(indices(facts)), +  lambda(string s, mapping f) { +  return s + "=" + f[s] + ";"; +  }, facts) * ""; +  } +  +  string make_MLSD_fact(string f, mapping(string:array) dir, +  object session) +  { +  string facts = format_MLSD_facts(make_MLSD_facts(f, dir, session)); +  if (!facts) return 0; +  return facts + " " + f; +  } +     void send_MLSD_response(mapping(string:array) dir, object session)    {    dir = dir || ([]);    -  array f = indices(dir); +  array(string) entries = +  Array.map(indices(dir), make_MLSD_fact, dir, session) - ({ 0 });    -  session->file->data = sizeof(f) ? -  (Array.map(f, make_MLSD_fact, dir, session) * "\r\n") + "\r\n" : -  "" ; +  session->file->data = sizeof(entries) ? entries * "\r\n" + "\r\n" : "" ;       session->file->mode = "I";    connect_and_send(session->file, session);    }       void send_MLST_response(mapping(string:array) dir, object session)    {    dir = dir || ([]); -  send(250,({ "OK" }) + +  // NB: MLST expands "." and "..", so make_MLSD_fact() won't +  // return zero here. +  send(250,({ LOCALE(105, "OK") }) +    Array.map(indices(dir), make_MLSD_fact, dir, session) + -  ({ "OK" }) ); +  ({ LOCALE(105, "OK") }) );    }       /*    * Session handling    */       int login()    {    int session_limit = port_obj->query_option("ftp_user_session_limit");   
Roxen.git/server/protocols/ftp.pike:2668:       return 1;    }       /*    * FTP commands begin here    */       // Set to 1 by EPSV ALL.    int epsv_only; -  // Set to 1 by PROT S,E and P, cleared by PROT C. -  int use_ssl; +        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; -  +  roxen.set_locale(); +  m_delete(master_session->misc, "accept-language"); +  master_session->misc->pref_languages->languages = ({});    if (pasv_port) {    destruct(pasv_port);    pasv_port = 0;    }    if (args != 1) { -  // Not called by QUIT. -  send(220, ({ "Server ready for new user." })); +  // Not called by QUIT or AUTH. +  low_send(220, ({ LOCALE(106, "Server ready for new user.") })); +  +  // RFC 4217 13: +  // When this command is processed by the server, the TLS +  // session(s) MUST be cleared and the control and data +  // connections revert to unprotected, clear communications. +  to_send->put(2); // End TLS marker. +  use_ssl = SSL_NONE; +  +  busy = 0; +  next_cmd();    }    }    -  +  void ftp_AUTH(string args) +  { +  if (!expect_argument("AUTH", args)) return; +  +  args = upper_case(replace(args, ({ " ", "\t" }), ({ "", "" }))); +  +  // RFC 4217 17: +  // To request the TLS protocol in accordance with this document, +  // the client MUST use 'TLS' +  // +  // To maintain backward compatibility with older versions of this +  // document, the server SHOULD accept 'TLS-C' as a synonym for 'TLS'. +  if (!(< "TLS", "SSL", "SSL-C", "TLS-C", "SSL-P", "TLS-P" >)[args]) { +  // RFC 2228 AUTH: +  // If the server does not understand the named security mechanism, it +  // should respond with reply code 504. +  send(504, ({ LOCALE(107, "Unknown authentication mechanism.") })); +  return; +  } +  if ((port_obj->query_option("require_starttls") < 0) || +  !port_obj->ctx) { +  // RFC 2228 AUTH: +  // If the server is not willing to accept the named security +  // mechanism, it should respond with reply code 534. +  send(534, ({ LOCALE(108, "TLS not configured.") })); +  return; +  } +  // RFC 2228 AUTH: +  // The AUTH command, if accepted, removes any state associated with +  // prior FTP Security commands. The server must also require that the +  // user reauthorize (that is, reissue some or all of the USER, PASS, +  // and ACCT commands) in this case (see section 4 for an explanation +  // of "authorize" in this context). +  // +  // RFC 4217 4.2 requires REIN. +  ftp_REIN(1); +  +  // Inform the client that we agree to switch to TLS. +  low_send(234, ({ LOCALE(109, "TLS enabled.") })); +  +  // Make sure not to read any more from the fd before +  // the TLS handshaking is done. +  fd->set_read_callback(0); +  fd->set_close_callback(0); +  +  // Switch to TLS marker. +  to_send->put(1); +  +  // Compatibility with early draft-murray-auth-ftp-ssl +  // (drafts of RFC 4217). +  if (args == "TLS-P") { +  // AUTH TLS-P: Enable PROT P by default. +  use_ssl = SSL_ALL; +  } else if ((args == "SSL") || (args == "SSL-P")) { +  // AUTH SSL: Enable PROT P by default in active mode. +  // +  // Use SSL/TLS for the data connection in active mode but +  // not in passive mode. This behaviour probably has to do +  // with the server initiating the connection in passive mode, +  // which would imply it to be the SSL/TLS client. +  // +  // cf RFC 4217 (AUTH TLS) which solves this by having +  // the server being the SSL/TLS server in both modes. +  use_ssl = SSL_ACTIVE; +  } +  // NB: AUTH SSL-C is "Don't encrypt data channel". +  +  busy = 0; +  next_cmd(); +  } +  +  void ftp_CCC(string args) +  { +  if (!fd->renegotiate) { +  // Not AUTH TLS +  send(533, ({ LOCALE(110, "Command connection not protected.") })); +  return; +  } +  if (master_session->my_fd->renegotiate) { +  // ftps +  send(534, ({ LOCALE(111, "Not allowed for ftps.") })); +  return; +  } +  +  low_send(200, ({ LOCALE(112, "TLS disabled.") })); +  to_send->put(2); // Disable TLS marker. +  +  busy = 0; +  next_cmd(); +  } +     void ftp_USER(string args)    {    logout();       auth_user = 0;    user = args;    password = 0;    logged_in = 0;    cwd = "/";    master_session->method = "LOGIN"; -  if ((< 0, "ftp", "anonymous" >)[user]) { +  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" })); +  send(200, ({ LOCALE(113, "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." })); +  send(331, ({ LOCALE(114, "Anonymous ftp accepted, send " +  "your complete e-mail address as password.") }));   #endif /* 0 */    conf->log(([ "error":200 ]), master_session);    } else {    send(530, ({ -  sprintf("Too many anonymous users (%d).", +  sprintf(LOCALE(115, "Too many anonymous users (%d)."),    port_obj->query_option("ftp_user_session_limit"))    }));    conf->log(([ "error":403 ]), master_session);    }    } else { -  send(530, ({ "Anonymous ftp disabled" })); +  send(530, ({ LOCALE(116, "Anonymous ftp disabled") }));    conf->log(([ "error":403 ]), master_session);    }    } else { -  +  if (port_obj->ctx && !fd->renegotiate && +  (port_obj->query_option("require_starttls") == 1)) { +  conf->log(([ "error":403 ]), master_session); +  send(530, ({ LOCALE(117, "You need to AUTH TLS first.") })); +  +  return; +  }    if (check_login()) { -  send(331, ({ sprintf("Password required for %s.", user) })); +  send(331, ({ sprintf(LOCALE(118, "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\".", +  sprintf(LOCALE(119, "Concurrent session limit (%d) exceeded " +  "for user \"%s\"."),    port_obj->query_option("ftp_user_session_limit"), user)    }));    conf->log(([ "error":403 ]), master_session);    user = 0;    return;    }    }    }       void ftp_PASS(string args)    {    if (!user) {    if (port_obj->query_option("anonymous_ftp")) {    if (login()) { -  send(230, ({ "Guest login ok, access restrictions apply." })); +  send(230, ({ LOCALE(120, "Guest login ok, access restrictions apply.") }));    master_session->method = "LOGIN";    master_session->not_query = "Anonymous User:"+args;    conf->log(([ "error":200 ]), master_session);    logged_in = -1;    } else {    send(530, ({ -  sprintf("Too many anonymous users (%d).", +  sprintf(LOCALE(115, "Too many anonymous users (%d)."),    port_obj->query_option("ftp_user_session_limit"))    }));    conf->log(([ "error":403 ]), master_session);    }    } else { -  send(503, ({ "Login with USER first." })); +  send(503, ({ LOCALE(121, "Login with USER first.") }));    }    return;    }    -  +  if (port_obj->ctx && !fd->renegotiate && +  (port_obj->query_option("require_starttls") == 1)) { +  // NB: Reachable through the following exotic command sequence: +  // +  // AUTH TLS, USER, PASS, CCC, PASS +  conf->log(([ "error":403 ]), master_session); +  send(530, ({ LOCALE(117, "You need to AUTH TLS 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");       RequestID2 session = RequestID2 (master_session);       auth_user = session->conf->authenticate(session);       if (!auth_user) {    if (!port_obj->query_option("guest_ftp")) { -  send(530, ({ sprintf("User %s access denied.", user) })); +  send(530, ({ sprintf(LOCALE(122, "User %s access denied."), user) }));    conf->log(([ "error":401 ]), session);    } else {    // Guest user.    string u = user;    user = 0;    if (login()) { -  send(230, ({ sprintf("Guest user %s logged in.", u) })); +  send(230, ({ sprintf(LOCALE(123, "Guest user %s logged in."), u) }));    logged_in = -1;    conf->log(([ "error":200 ]), session);    DWRITE("FTP: Guest-user: %O\n", session->realauth);    } else {    send(530, ({ -  sprintf("Too many anonymous/guest users (%d).", +  sprintf(LOCALE(124, "Too many anonymous/guest users (%d)."),    port_obj->query_option("ftp_user_session_limit"))    }));    conf->log(([ "error":403 ]), session);    }    }    destruct (session);    return;    }       // Authentication successful
Roxen.git/server/protocols/ftp.pike:2842:    mixed val = ses_misc[field];    if (zero_type (val))    m_delete (mses_misc, field);    else    mses_misc[field] = val;    }    }       if (!port_obj->query_option("named_ftp") ||    !check_shell(auth_user->shell())) { -  send(530, ({ "You are not allowed to use named-ftp.", -  "Try using anonymous, or check /etc/shells" })); +  send(530, ({ LOCALE(125, "You are not allowed to use named-ftp."), +  LOCALE(126, "Try using anonymous, or check /etc/shells") }));    conf->log(([ "error":402 ]), session);    auth_user = 0;    destruct (session);    return;    }       if (!login()) {    send(530, ({ -  sprintf("Too many concurrent sessions (limit is %d).", +  sprintf(LOCALE(127, "Too many concurrent sessions (limit is %d)."),    port_obj->query_option("ftp_user_session_limit"))    }));    conf->log(([ "error":403 ]), session);    destruct (session);    return;    }       if (stringp(auth_user->homedir())) {    // Check if it is possible to cd to the users home-directory.    string home = auth_user->homedir();
Roxen.git/server/protocols/ftp.pike:2881:    stat_session->method = "STAT";    array(int)|object st = conf->stat_file(home, stat_session);    destruct(stat_session);       if (st && (st[1] < 0)) {    cwd = home;    }    }       logged_in = 1; -  send(230, ({ sprintf("User %s logged in.", user) })); +  send(230, ({ sprintf(LOCALE(128, "User %s logged in."), user) }));    conf->log(([ "error":202 ]), session);    destruct (session);    }       void ftp_CWD(string args)    {    if (!expect_argument("CWD", args)) {    return;    }   
Roxen.git/server/protocols/ftp.pike:2905:    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.", +  send(550, ({ sprintf(LOCALE(129, "%s: No such file or directory, or access denied."),    ncwd) }));    session->conf->log(session->file || ([ "error":404 ]), session);    destruct(session);    return;    }       if (!(< -2, -3 >)[st[1]]) { -  send(504, ({ sprintf("%s: Not a directory.", ncwd) })); +  send(504, ({ sprintf(LOCALE(130, "%s: Not a directory."), ncwd) }));    session->conf->log(([ "error":400 ]), session);    destruct(session);    return;    }       // CWD Successfull    cwd = ncwd;    -  array(string) reply = ({ sprintf("Current directory is now %s.", cwd) }); +  array(string) reply = ({ sprintf(LOCALE(131, "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.", +  reply = ({ sprintf(LOCALE(132, "Please read the file %s."), f), +  sprintf(LOCALE(133, "It was last modified %s - %d days ago."),    ctime(st[3]) - "\n",    (time(1) - st[3])/86400),    "" }) + reply;    }    }    }    string message;    catch {    message = conf->try_get_file(cwd + ".message", session);    };
Roxen.git/server/protocols/ftp.pike:2976:    ftp_CWD("../");    }       void ftp_XCUP(string args)    {    ftp_CWD("../");    }       void ftp_QUIT(string args)    { -  send(221, ({ "Bye! It was nice talking to you!" })); +  send(221, ({ LOCALE(134, "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_PBSZ(string args)    { -  if (!expect_argument("PROT", args)) return; +  if (!expect_argument("PBSZ", args)) return;    -  +  if (!fd->renegotiate) { +  send(536, ({ LOCALE(135, "Only allowed for authenticated command connections.") })); +  return; +  } +     send(200, ({ "PBSZ=0" }));    }       void ftp_PROT(string args)    {    if (!expect_argument("PROT", args)) return;       args = upper_case(replace(args, ({ " ", "\t" }), ({ "", "" }))); -  +  +  SSLMode wanted;    switch(args) {    case "C": // Clear. -  use_ssl = 0; +  wanted = SSL_NONE;    break;    case "S": // Safe.    case "E": // Confidential.    case "P": // Private. -  if (!port_obj->ctx) { -  send(536, ({ sprintf("Only supported over FTPS") })); -  return; -  } -  use_ssl = 1; +  wanted = SSL_ALL;    break;    default: -  send(504, ({ sprintf("Unknown protection level: %s", args) })); +  send(504, ({ sprintf(LOCALE(136, "Unknown protection level: %s"), args) }));    return;    } -  send(200, ({ "OK" })); +  +  if (!fd->renegotiate) { +  send(536, ({ LOCALE(137, "Only supported over TLS.") })); +  return;    }    -  +  use_ssl = wanted; +  send(200, ({ LOCALE(105, "OK") })); +  } +     void ftp_PORT(string args)    {    if (epsv_only) { -  send(530, ({ "'PORT': Method not allowed in EPSV ALL mode." })); +  send(530, ({ LOCALE(138, "'PORT': Method not allowed in EPSV ALL mode.") }));    return;    }       int a, b, c, d, e, f;       if (sscanf(args||"", "%d,%d,%d,%d,%d,%d", a, b, c, d, e, f)<6) -  send(501, ({ "I don't understand your parameters." })); +  send(501, ({ LOCALE(139, "I don't understand your parameters.") }));    else {    dataport_addr = sprintf("%d.%d.%d.%d", a, b, c, d);    dataport_port = e*256 + f;       if (pasv_port) {    destruct(pasv_port);    } -  send(200, ({ "PORT command ok ("+dataport_addr+ -  " port "+dataport_port+")" })); +  send(200, ({ sprintf(LOCALE(140, "PORT command ok (%s port %d)"), +  dataport_addr, dataport_port) }));    }    }       void ftp_EPRT(string args)    {    // Specified by RFC 2428:    // Extensions for IPv6 and NATs.    if (epsv_only) { -  send(530, ({ "'EPRT': Method not allowed in EPSV ALL mode." })); +  send(530, ({ LOCALE(141, "'EPRT': Method not allowed in EPSV ALL mode.") }));    return;    }       if (sizeof(args) < 3) { -  send(501, ({ "I don't understand your parameters." })); +  send(501, ({ LOCALE(139, "I don't understand your parameters.") }));    return;    }       string delimiter = args[0..0];    if ((delimiter[0] <= 32) || (delimiter[0] >= 127)) { -  send(501, ({ "Invalid delimiter." })); +  send(501, ({ LOCALE(142, "Invalid delimiter.") }));    }    array(string) segments = args/delimiter;       if (sizeof(segments) != 5) { -  send(501, ({ "I don't understand your parameters." })); +  send(501, ({ LOCALE(139, "I don't understand your parameters.") }));    return;    }    if (!(<"1","2">)[segments[1]]) { -  send(522, ({ "Network protocol not supported, use (1 or 2)" })); +  send(522, ({ LOCALE(143, "Network protocol not supported, use (1 or 2)") }));    return;    }    if (segments[1] == "1") {    // IPv4.    if ((sizeof(segments[2]/".") != 4) ||    sizeof(replace(segments[2], ".0123456789"/"", allocate(11, "")))) { -  send(501, ({ sprintf("Bad IPv4 address: '%s'", segments[2]) })); +  send(501, ({ sprintf(LOCALE(144, "Bad IPv4 address: '%s'"), segments[2]) }));    return;    }    } else {    // IPv6.    // FIXME: Improve the validation?    if (sizeof(replace(lower_case(segments[2]), ".:0123456789abcdef"/"",    allocate(18, "")))) { -  send(501, ({ sprintf("Bad IPv6 address: '%s'", segments[2]) })); +  send(501, ({ sprintf(LOCALE(145, "Bad IPv6 address: '%s'"), segments[2]) }));    return;    }    }    if ((((int)segments[3]) <= 0) || (((int)segments[3]) > 65535)) { -  send(501, ({ sprintf("Bad port number: '%s'", segments[3]) })); +  send(501, ({ sprintf(LOCALE(146, "Bad port number: '%s'"), segments[3]) }));    return;    }    dataport_addr = segments[2];    dataport_port = (int)segments[3];       if (pasv_port) {    destruct(pasv_port);    } -  send(200, ({ "EPRT command ok ("+dataport_addr+ -  " port "+dataport_port+")" })); +  send(200, ({ sprintf(LOCALE(147, "EPRT command ok (%d port %d)"), +  dataport_addr, 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." })); +  send(530, ({ LOCALE(148, "'PASV': Method not allowed in EPSV ALL mode.") }));    return;    }       if (e_mode != "1") { -  send(530, ({ "'PASV': Method not allowed on IPv6 connections." })); +  send(530, ({ LOCALE(149, "'PASV': Method not allowed on IPv6 connections.") }));    return;    }       if(pasv_port)    destruct(pasv_port);       pasv_port = Stdio.Port(0, pasv_accept_callback, local_addr);    /* FIXME: Hmm, getting the address from an anonymous port seems not    * to work on NT...    */
Roxen.git/server/protocols/ftp.pike:3143:    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." })); +  send(452, ({ LOCALE(150, "Requested action aborted: Out of ports.") }));    return;    }    } -  send(227, ({ sprintf("Entering Passive Mode. (%s,%d,%d)", +  send(227, ({ LOCALE(151, "Entering Passive Mode.") + +  sprintf(" (%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 (!(< 0, e_mode >)[args]) {    if (lower_case(args) == "all") {    epsv_only = 1; -  send(200, ({ "Entering EPSV ALL mode." })); +  send(200, ({ LOCALE(152, "Entering EPSV ALL mode.") }));    } else { -  send(522, ({ "Network protocol not supported, use " + e_mode + "." })); +  send(522, ({ sprintf(LOCALE(153, "Network protocol not supported, use %s."), +  e_mode) }));    }    return;    }    if (pasv_port)    destruct(pasv_port);       pasv_port = Stdio.Port(0, pasv_accept_callback, local_addr);    /* FIXME: Hmm, getting the address from an anonymous port seems not    * to work on NT...    */
Roxen.git/server/protocols/ftp.pike:3190:    if (max > 65535) max = 65535;    if (min < 1) min = 1;    for (port = min; port <= max; port++) {    if (pasv_port->bind(port, pasv_accept_callback, local_addr)) {    break;    }    }    if (port > max) {    destruct(pasv_port);    pasv_port = 0; -  send(452, ({ "Requested action aborted: Out of ports." })); +  send(452, ({ LOCALE(150, "Requested action aborted: Out of ports.") }));    return;    }    } -  send(229, ({ sprintf("Entering Extended Passive Mode (|||%d|)", -  /* "1", local_addr,*/ port) })); +  send(229, ({ LOCALE(154, "Entering Extended Passive Mode") + +  sprintf(" (|||%d|)", /* "1", local_addr,*/ port) }));    }       void ftp_TYPE(string args)    {    if (!expect_argument("TYPE", args)) {    return;    }       args = upper_case(replace(args, ({ " ", "\t" }), ({ "", "" })));   
Roxen.git/server/protocols/ftp.pike:3220:    case "I":    mode = "I";    break;    case "A":    mode = "A";    break;    case "E":    mode = "E";    break;    default: -  send(504, ({ "'TYPE': Unknown type:"+args })); +  send(504, ({ sprintf(LOCALE(155, "'TYPE': Unknown type: %s"), args) }));    return;    }    -  send(200, ({ sprintf("Using %s mode for transferring files.", +  send(200, ({ sprintf(LOCALE(156, "Using %s mode for transferring files."),    modes[mode]) }));    }       void ftp_RETR(string args)    {    if (!expect_argument("RETR", args)) {    return;    }       args = fix_path(args);
Roxen.git/server/protocols/ftp.pike:3257:    restart_point = 0;    } else {    restart_point -= sizeof(file->data);    m_delete(file, "data");    }    }    if (restart_point) {    if (!(file->file && file->file->seek &&    (file->file->seek(restart_point) != -1))) {    restart_point = 0; -  send(550, ({ "'RETR': Error restoring restart point." })); +  send(550, ({ LOCALE(157, "'RETR': Error restoring restart point.") }));    discard_data_connection();    destruct(session);    return;    }    restart_point = 0;    }    }       connect_and_send(file, session);    }
Roxen.git/server/protocols/ftp.pike:3291:       connect_and_receive(args);    }       void ftp_REST(string args)    {    if (!expect_argument("REST", args)) {    return;    }    restart_point = (int)args; -  send(350, ({ "'REST' ok" })); +  send(350, ({ LOCALE(158, "'REST' ok") }));    }       void ftp_ABOR(string args)    {    if (curr_pipe) {    catch {    destruct(curr_pipe);    };    curr_pipe = 0; -  send(426, ({ "Data transmission terminated." })); +  send(426, ({ LOCALE(159, "Data transmission terminated.") }));    } -  send(226, ({ "'ABOR' Completed." })); +  send(226, ({ LOCALE(160, "'ABOR' Completed.") }));    }       void ftp_PWD(string args)    { -  send(257, ({ sprintf("\"%s\" is current directory.", cwd) })); +  send(257, ({ sprintf(LOCALE(161, "\"%s\" is current directory."), cwd) }));    }       void ftp_XPWD(string args)    {    ftp_PWD(args);    }       /*    * Handling of file moving    */
Roxen.git/server/protocols/ftp.pike:3330:    private string rename_from; // rename from       void ftp_RNFR(string args)    {    if (!expect_argument("RNFR", args)) {    return;    }    args = fix_path(args);       if (stat_file(args)) { -  send(350, ({ sprintf("%s ok, waiting for destination name.", args) }) ); +  send(350, ({ sprintf(LOCALE(162, "%s ok, waiting for destination name."), args) }) );    rename_from = args;    } else { -  send(550, ({ sprintf("%s: no such file or permission denied.",args) }) ); +  send(550, ({ sprintf(LOCALE(163, "%s: no such file or permission denied."),args) }) );    }    }       void ftp_RNTO(string args)    {    if(!rename_from) { -  send(503, ({ "RNFR needed before RNTO." })); +  send(503, ({ LOCALE(164, "RNFR needed before RNTO.") }));    return;    }    if (!expect_argument("RNTO", args)) {    return;    }    args = fix_path(args);       RequestID session = RequestID2(master_session);       session->method = "MV";    session->misc->move_from = rename_from;    session->not_query = args;    if (open_file(args, session, "MOVE")) { -  send(250, ({ sprintf("%s moved to %s.", rename_from, args) })); +  send(250, ({ sprintf(LOCALE(165, "%s moved to %s."), rename_from, args) }));    session->conf->log(([ "error":200 ]), session);    }    rename_from = 0;    destruct(session);    }          void ftp_NLST(string args)    {    // 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; + #ifdef FTP2_MLSD_KLUDGE +  ftp_MLSD(args); return; + #endif       ftp_NLST("-l " + (args||""));    }       void ftp_MLST(string args)    { -  args = fix_path(args || "."); +  string long = fix_path(args || ".");       RequestID session = RequestID2(master_session);       session->method = "DIR";    -  array|object st = stat_file(args, session); +  array|object st = stat_file(long, session);       if (st) {    session->file = ([]); -  session->file->full_path = args; -  send_MLST_response(([ args:st ]), session); +  session->file->full_path = long; +  send_MLST_response(([ long: st ]), session);    } else {    send_error("MLST", args, session->file, session);    }    destruct(session);    }       void ftp_MLSD(string args)    {    args = fix_path(args || ".");   
Roxen.git/server/protocols/ftp.pike:3415:       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); +  +  mapping(string:array(mixed)) dir = +  session->conf->find_dir_stat(args, session) || ([]); +  if (args != "/") { +  dir[".."] = stat_file(combine_path(args,"../")); +  } +  dir["."] = stat_file(combine_path(args)); +  +  send_MLSD_response(dir, session);    // NOTE: send_MLSD_response is asynchronous!    } else {    if (st) {    session->file->error = 405;    }    send_error("MLSD", args, session->file, session);    discard_data_connection();    destruct(session);    }    }    -  +  void ftp_OPTS(string args) +  { +  if ((< 0, "" >)[args]) { +  ftp_HELP("OPTS"); +  return; +  } +  +  array a = (args/" ") - ({ "" }); +  +  if (!sizeof(a)) { +  ftp_HELP("OPTS"); +  return; +  } +  a[0] = upper_case(a[0]); +  if (!opts_help[a[0]]) { +  send(502, ({ sprintf(LOCALE(166, "Bad OPTS command: '%s'"), a[0]) })); +  } else if (this_object()["ftp_OPTS_"+a[0]]) { +  this_object()["ftp_OPTS_"+a[0]](a[1..]); +  } else { +  send(502, ({ sprintf(LOCALE(167, "OPTS command '%s' is not currently supported."), +  a[0]) })); +  } +  } +  +  void ftp_OPTS_MLST(array(string) args) +  { +  if (sizeof(args) != 1) { +  send(501, ({ sprintf(LOCALE(168, "'OPTS MLST %s': incorrect arguments"), +  args*" ") })); +  return; +  } +  +  multiset(string) new_mlst_facts = (<>); +  foreach(args[0]/";", string fact) { +  fact = lower_case(fact); +  if (!supported_mlst_facts[fact]) continue; +  new_mlst_facts[fact] = 1; +  } +  current_mlst_facts = new_mlst_facts; +  +  send(200, ({ sprintf("MLST OPTS %s", +  format_factlist(new_mlst_facts)) })); +  } +     void ftp_DELE(string args)    {    if (!expect_argument("DELE", args)) {    return;    }       args = fix_path(args);       RequestID session = RequestID2(master_session);       session->data = "";    session->misc->len = 0;    session->method = "DELETE";       if (open_file(args, session, "DELE")) { -  send(250, ({ sprintf("%s deleted.", args) })); +  send(250, ({ sprintf(LOCALE(169, "%s deleted."), args) }));    session->conf->log(([ "error":200 ]), session);    }    destruct(session);    }       void ftp_RMD(string args)    {    if (!expect_argument("RMD", args)) {    return;    }
Roxen.git/server/protocols/ftp.pike:3470:    session->method = "DELETE";       array|object st = stat_file(args, session);       if (!st) {    send_error("RMD", args, session->file, session);    destruct(session);    return;    } else if (st[1] != -2) {    if (st[1] == -3) { -  send(504, ({ sprintf("%s is a module mountpoint.", args) })); +  send(504, ({ sprintf(LOCALE(170, "%s is a module mountpoint."), args) }));    session->conf->log(([ "error":405 ]), session);    } else { -  send(504, ({ sprintf("%s is not a directory.", args) })); +  send(504, ({ sprintf(LOCALE(171, "%s is not a directory."), args) }));    session->conf->log(([ "error":405 ]), session);    }    destruct(session);    return;    }       if (open_file(args, session, "RMD")) { -  send(250, ({ sprintf("%s deleted.", args) })); +  send(250, ({ sprintf(LOCALE(169, "%s deleted."), args) }));    session->conf->log(([ "error":200 ]), session);    }    destruct(session);    }       void ftp_XRMD(string args)    {    ftp_RMD(args);    }   
Roxen.git/server/protocols/ftp.pike:3507:       args = fix_path(args);       RequestID session = RequestID2(master_session);       session->method = "MKDIR";    session->data = "";    session->misc->len = 0;       if (open_file(args, session, "MKD")) { -  send(257, ({ sprintf("\"%s\" created.", args) })); +  send(257, ({ sprintf(LOCALE(172, "\"%s\" created."), args) }));    session->conf->log(([ "error":200 ]), session);    }    destruct(session);    }       void ftp_XMKD(string args)    {    ftp_MKD(args);    }    -  +  void ftp_LANG(string args) +  { +  args = lower_case(String.trim_all_whites(args || "")); +  if (sizeof(args)) { +  if (!roxen.set_locale(args)) { +  send(504, ({ sprintf(LOCALE(173, "Unsupported language: %s"), args) })); +  return; +  } +  master_session->misc->pref_languages->languages = ({ args }); +  master_session->misc["accept-language"] = args; +  } else { +  roxen.set_locale(); +  master_session->misc->pref_languages->languages = ({}); +  m_delete(master_session->misc, "accept-language"); +  } +  send(200, ({ sprintf(LOCALE(174, "Language set to %s"), roxen.get_locale()) })); +  } +     void ftp_SYST(string args)    {    send(215, ({ "UNIX Type: L8: Roxen Information Server"}));    }       void ftp_CLNT(string args)    {    if (!expect_argument("CLNT", args)) {    return;    }    -  send(200, ({ "Ok, gottcha!"})); +  send(200, ({ LOCALE(175, "Ok, gottcha!")}));    master_session->client = args/" " - ({ "" });    }       void ftp_FEAT(string args)    {    array a = sort(Array.filter(indices(cmd_help),    lambda(string s) {    return(this_object()["ftp_"+s]);    })); -  +  if (!port_obj->ctx) { +  a -= ({ "AUTH" }); +  } +  if (master_session->my_fd->renegotiate) { +  // ftps. +  a -= ({ "CCC" }); +  } +  a += ({ "TVFS" }); // RFC 3659 +  a += ({ "UTF8" }); // RFC 2640    a = Array.map(a,    lambda(string s) {    return(([ "REST":"REST STREAM", -  "MLST":"MLST UNIX.mode;size;type;modify;charset;media-type", +  "MLST":sprintf("MLST %s", +  format_factlist(supported_mlst_facts, +  current_mlst_facts)),    "MLSD":"", -  +  "AUTH":"AUTH TLS", +  "LANG":sprintf("LANG %s", format_langlist()),    ])[s] || s);    }) - ({ "" });    -  send(211, ({ "The following features are supported:" }) + a + +  send(211, ({ LOCALE(176, "The following features are supported:") }) + a +    ({ "END" }));    }       void ftp_MDTM(string args)    {    if (!expect_argument("MDTM", args)) {    return;    }    args = fix_path(args);    mapping|array|object st = stat_file(args);
Roxen.git/server/protocols/ftp.pike:3621:    local_addr = replace(local_addr, " ", ":");    }    string remote_addr = fd->query_address();    if (has_value(remote_addr, ":")) {    // IPv6.    remote_addr = "[" + replace(remote_addr, " ", "]:");    } else {    remote_addr = replace(remote_addr, " ", ":");    }    send(211, -  sprintf("%s FTP server status:\n" +  sprintf(LOCALE(177, "%s FTP server status:\n"    "Version %s\n"    "Listening on %s\n"    "Connected to %s\n"    "Logged in %s\n"    "TYPE: %s, FORM: %s; STRUcture: %s; transfer MODE: %s\n" -  "End of status", +  "End of status"),    local_addr,    roxen.version(),    port_obj->sorted_urls * "\nListening on ",    remote_addr,    user?sprintf("as %s", user):"anonymously",    (["A":"ASCII", "E":"EBCDIC", "I":"IMAGE", "L":"LOCAL"])    [mode],    "Non-Print",    "File",    "Stream"
Roxen.git/server/protocols/ftp.pike:3649:    return;    }    string long = fix_path(args);    mapping|array|object st = stat_file(long);       if (!arrayp(st) && !objectp(st)) {    send_error("STAT", long, st, master_session);    } else {    string s = LS_L(master_session)->ls_l(args, st);    -  send(213, sprintf("status of \"%s\":\n" -  "%s" -  "End of Status", args, s)/"\n"); +  send(213, ({ +  sprintf(LOCALE(178, "Status of \"%s\"."), args), +  @((s/"\n") - ({""})), +  "End of Status", +  }));    }    }       void ftp_NOOP(string args)    { -  send(200, ({ "Nothing done ok" })); +  send(200, ({ LOCALE(179, "Nothing done ok") }));    }       void ftp_HELP(string args)    {    if ((< "", 0 >)[args]) {    send(214, ({ -  "The following commands are recognized (* =>'s unimplemented):", +  LOCALE(180, "The following commands are recognized (* =>'s unimplemented):"),    @(sprintf(" %#70s", sort(Array.map(indices(cmd_help),    lambda(string s) {    return(upper_case(s)+    (this_object()["ftp_"+s]?    " ":"* "));    }))*"\n")/"\n"),    @(FTP2_XTRA_HELP),    })); -  } else if ((args/" ")[0] == "SITE") { -  array(string) a = (upper_case(args)/" ")-({""}); +  } else { +  args = upper_case(args); +  if ((args/" ")[0] == "SITE") { +  array(string) a = (args/" ")-({""});    if (sizeof(a) == 1) { -  send(214, ({ "The following SITE commands are recognized:", +  send(214, ({ LOCALE(181, "The following SITE commands are recognized:"),    @(sprintf(" %#70s", sort(indices(site_help))*"\n")/"\n")    }));    } else if (site_help[a[1]]) { -  send(214, ({ sprintf("Syntax: SITE %s %s", a[1], site_help[a[1]]) })); +  send(214, ({ sprintf(LOCALE(182, "Syntax: SITE %s %s"), +  a[1], site_help[a[1]]) }));    } else { -  send(504, ({ sprintf("Unknown SITE command %s.", a[1]) })); +  send(504, ({ sprintf(LOCALE(183, "Unknown SITE command %s."), a[1]) }));    } -  +  } else if ((args/" ")[0] == "OPTS") { +  array(string) a = (args/" ")-({""}); +  if (sizeof(a) == 1) { +  send(214, ({ LOCALE(184, "The following OPTS commands are recognized:"), +  @(sprintf(" %#70s", sort(indices(opts_help))*"\n")/"\n") +  })); +  } else if (opts_help[a[1]]) { +  send(214, ({ sprintf(LOCALE(185, "Syntax: OPTS %s %s"), +  a[1], opts_help[a[1]]) }));    } else { -  args = upper_case(args); +  send(504, ({ sprintf(LOCALE(186, "Unknown OPTS command %s."), a[1]) })); +  } +  } else {    if (cmd_help[args]) { -  send(214, ({ sprintf("Syntax: %s %s%s", args, +  send(214, ({ sprintf(LOCALE(187, "Syntax: %s %s%s"), args,    cmd_help[args],    (this_object()["ftp_"+args]?    "":"; unimplemented")) }));    } else { -  send(504, ({ sprintf("Unknown command %s.", args) })); +  send(504, ({ sprintf(LOCALE(188, "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]) })); +  send(502, ({ sprintf(LOCALE(189, "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.", +  send(502, ({ sprintf(LOCALE(190, "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", +  send(501, ({ sprintf(LOCALE(191, "'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" })); +  send(501, ({ LOCALE(192, "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.", +  send(250, ({ sprintf(LOCALE(193, "Changed permissions of %s to 0%o."),    fname, mode) }));    session->conf->log(([ "error":200 ]), session);    }    destruct(session);    }       void ftp_SITE_UMASK(array(string) args)    {    if (sizeof(args) < 1) { -  send(501, ({ sprintf("'SITE UMASK %s': incorrect arguments", +  send(501, ({ sprintf(LOCALE(194, "'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" })); +  send(501, ({ LOCALE(195, "SITE UMASK: mode should be between 0 and 0777") }));    return;    }       master_session->misc->umask = mode; -  send(250, ({ sprintf("Umask set to 0%o.", mode) })); +  send(250, ({ sprintf(LOCALE(196, "Umask set to 0%o."), mode) }));    }       void ftp_SITE_PRESTATE(array(string) args)    {    if (!sizeof(args)) {    master_session->prestate = (<>); -  send(200, ({ "Prestate cleared" })); +  send(200, ({ LOCALE(197, "Prestate cleared") }));    } else {    master_session->prestate = aggregate_multiset(@((args*" ")/","-({""}))); -  send(200, ({ "Prestate set" })); +  send(200, ({ LOCALE(198, "Prestate set") }));    }    }       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(421, ({ LOCALE(199, "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)) {
Roxen.git/server/protocols/ftp.pike:3843:    // 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();    }    }    + #ifdef FTP_USE_HANDLER_THREADS +  // Minimal layer for API compatibility with ADT.Queue. +  protected class CommandQueue +  { +  inherit Thread.Queue; +  +  protected int _sizeof() +  { +  return size(); +  } +  +  void put(mixed value) +  { +  write(value); +  } +  +  mixed get() +  { +  return try_read(); +  } +  } +  private CommandQueue cmd_queue = CommandQueue(); + #else    private ADT.Queue cmd_queue = ADT.Queue(); -  + #endif       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;
Roxen.git/server/protocols/ftp.pike:3878:    // Censor line, so that the password doesn't show    // in backtraces.    line = cmd + " CENSORED_PASSWORD";    }       cmd_queue->put(({ line, cmd, args }));    if (!busy)    next_cmd();    }    -  private void next_cmd() +  private void low_next_cmd()    { -  +  // Protect against multiple call_outs. +  if (busy || !sizeof(cmd_queue)) return; +  busy = 1; +  +  if (!conf) { +  // Configuration deleted during session. +  terminate_connection(); +  return; +  } +     array(string|array(string)) cmd_entry = cmd_queue->get(); -  if (!cmd_entry) return; +  if (!cmd_entry) { +  // Race? +  busy = 0; +  return; +  }       string line = cmd_entry[0];    string cmd = cmd_entry[1];    array(string) args = cmd_entry[2];    -  busy = 1; -  +     if (!line) {    // Command queue terminator.    terminate_connection();    return;    }    -  +  restore_locale(); +    #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." })); +  if (!(< "REIN", "USER", "PASS", "SYST", "AUTH", +  "ACCT", "QUIT", "ABOR", "HELP", "FEAT" >)[cmd]) { +  send(530, ({ LOCALE(200, "You need to login first.") }));       return;    }    }    if (!port_obj->query_option("rfc2428_support") &&    (< "EPRT", "EPSV" >)[cmd]) { -  send(502, ({ sprintf("support for '%s' is disabled.", cmd) })); +  send(502, ({ sprintf(LOCALE(201, "support for '%s' is disabled."), cmd) }));    return;    }    if (this_object()["ftp_"+cmd]) {    conf->requests++; - #if 1 + #ifndef FTP_USE_HANDLER_THREADS    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) { +  roxen->handle(lambda(function f, string cmd, string args, string line) { +  // For e.g. PASS the args string may contain +  // cleartext password. +  string args_copy = args; +  if (cmd == "PASS") +  args = "CENSORED"; +  +  restore_locale(); +     mixed err;    if (err = catch { -  f(args); +  f(args_copy);    }) {    report_error("Internal server error in FTP2\n"    "Handling command %O\n%s\n",    line, describe_backtrace(err));    } -  }, this_object()["ftp_"+cmd], args, line); +  }, this_object()["ftp_"+cmd], cmd, args, line);   #endif    } else { -  send(502, ({ sprintf("'%s' is not currently supported.", cmd) })); +  send(502, ({ sprintf(LOCALE(202, "'%s' is not currently supported."), cmd) }));    }    } else { -  send(502, ({ sprintf("Unknown command '%s'.", cmd) })); +  send(502, ({ sprintf(LOCALE(203, "Unknown command '%s'."), cmd) }));    }       touch_me();    }       private void terminate_connection()    {    DWRITE("FTP2: terminate_connection()\n");    -  +  if (fd) { +  // Close the command connection. +  // Note that we have delayed the closing to reduce the risk of races. +  fd->close(); +  destruct(fd); +  fd = 0; +  } +     logout();       if (pasv_port) {    destruct(pasv_port);    pasv_port = 0;    }       master_session->method = "QUIT";    master_session->not_query = user || "Anonymous";    conf->log(([ "error":204, "request_time":(time(1)-master_session->time) ]),    master_session);    // Make sure we disappear...    destruct();    }       void con_closed()    {    DWRITE("FTP2: con_closed()\n");    -  send(0, 0); // EOF marker. -  +     if (fd) { -  // There's no reason to keep the command connection around any more. -  fd->close(); -  destruct(fd); -  fd = 0; +  // Clear the read-side callbacks. +  fd->set_close_callback(0); +  fd->set_read_callback(0);    }    -  +  // Make sure that the TelnetSession level doesn't restore +  // the above callbacks. +  read_cb = 0; +  close_cb = 0; +  +  send(0, 0); // EOF marker. +     // Queue a command queue terminator.    // This will terminate the connection as soon as all pending commands    // have finished. There apparently exists ftp clients that shut down    // the command connection before their uploads etc have finished.    cmd_queue->put(({ 0, 0, 0 }));    if (!busy)    next_cmd();    }       void destroy()
Roxen.git/server/protocols/ftp.pike:4025:    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 */    +  if (fd->renegotiate) { +  // Default to PROT P for ftps (aka implicit ftp/ssl). +  use_ssl = SSL_ALL; +  } +     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; -  +  master_session->misc->pref_languages = PrefLanguages();    ::create(fd, got_command, 0, con_closed, ([]));       array a = fd->query_address(1)/" ";    local_addr = a[0];    local_port = (int)a[1];    e_mode = has_value(local_addr, ":")?"2":"1";       call_out(timeout, FTP2_TIMEOUT);       string s = c->query_option("FTPWelcome");